hybrid_logical_clock/
lib.rs

1#![no_std]
2
3use core::cmp::{max, Ordering};
4
5/// Represents a Hybrid Logical Clock (HLC).
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub struct HybridLogicalClock {
8    /// The physical component of the clock, often referred to as the "wallclock" time.
9    pub physical: u64,
10    /// The logical component of the clock, used to distinguish events occurring at the same physical time.
11    pub logical: u32,
12}
13
14impl HybridLogicalClock {
15    /// Creates a new HybridLogicalClock with the given physical time.
16    /// The logical time is initialized to 0.
17    ///
18    /// # Arguments
19    ///
20    /// * `physical` - The physical time.
21    ///
22    /// # Returns
23    ///
24    /// A new HybridLogicalClock with the given physical time and logical time set to 0.
25    ///
26    /// # Example
27    ///
28    /// ```
29    /// use hybrid_logical_clock::HybridLogicalClock;
30    ///
31    /// let hlc = HybridLogicalClock::new(1000);
32    /// assert_eq!(hlc.physical, 1000);
33    /// assert_eq!(hlc.logical, 0);
34    /// ```
35    pub fn new(physical: u64) -> Self {
36        Self {
37            physical,
38            logical: 0,
39        }
40    }
41
42    /// Creates a new HybridLogicalClock with both physical and logical components.
43    /// 
44    /// # Arguments
45    /// 
46    /// * `physical` - The physical time.
47    /// * `logical` - The logical time.
48    /// 
49    /// # Returns
50    /// 
51    /// A new HybridLogicalClock with the given physical and logical times.
52    /// 
53    /// # Example
54    /// 
55    /// ```
56    /// use hybrid_logical_clock::HybridLogicalClock;
57    /// 
58    /// let hlc = HybridLogicalClock::new_with_both_physical_and_logical_clock_time(1000, 5);
59    /// assert_eq!(hlc.physical, 1000);
60    /// assert_eq!(hlc.logical, 5);
61    /// ```
62    pub fn new_with_both_physical_and_logical_clock_time(physical: u64, logical: u32) -> Self {
63        Self { physical, logical }
64    }
65
66    /// Updates the clock based on a received timestamp.
67    /// 
68    /// # Arguments
69    /// 
70    /// * `received` - The received HybridLogicalClock.
71    /// * `now` - The current physical time.
72    /// 
73    /// # Example
74    /// 
75    /// ```
76    /// use hybrid_logical_clock::HybridLogicalClock;
77    /// 
78    /// let mut hlc = HybridLogicalClock::new(100);
79    /// let received = HybridLogicalClock::new_with_both_physical_and_logical_clock_time(150, 10);
80    /// hlc.update(&received, 200);
81    /// 
82    /// assert_eq!(hlc.physical, 200);
83    /// assert_eq!(hlc.logical, 1);
84    /// ```
85    pub fn update(&mut self, received: &Self, now: u64) {
86        // Update the physical time to the maximum of the current physical time, the received physical time, and the current time.
87        self.physical = max(max(self.physical, received.physical), now);
88        // If the physical time is the same as the received physical time and the current time, update the logical time.
89        if self.physical == received.physical && self.physical == now {
90            self.logical = max(self.logical, received.logical) + 1;
91        } else if self.physical == received.physical {
92            self.logical = max(self.logical, received.logical) + 1;
93        } else if self.physical == now {
94            self.logical += 1;
95        } else {
96            self.logical = 0;
97        }
98    }
99
100    /// Checks if this clock is concurrent with another hybrid logical clock.
101    /// 
102    /// This method is crucial for determining the causal relationship between events in a distributed system.
103    /// It's particularly useful when you need to detect and handle concurrent operations or updates.
104    /// 
105    /// Use this method when:
106    /// 1. Implementing conflict resolution strategies in distributed databases or systems.
107    /// 2. Detecting potential conflicts in collaborative editing systems.
108    /// 3. Ensuring consistency in replicated state machines.
109    /// 4. Implementing version control systems that need to handle parallel changes.
110    /// 
111    /// The importance of this method lies in its ability to identify events that happened without knowledge
112    /// of each other. In distributed systems, this information is vital for maintaining consistency and
113    /// correctly merging or reconciling divergent states.
114    /// 
115    /// # Arguments
116    /// 
117    /// * `other` - The other HybridLogicalClock to compare with.
118    /// 
119    /// # Returns
120    /// 
121    /// `true` if the clocks are concurrent, `false` otherwise.
122    /// 
123    /// # Example
124    /// 
125    /// ```
126    /// use hybrid_logical_clock::HybridLogicalClock;
127    /// 
128    /// let hlc1 = HybridLogicalClock::new_with_both_physical_and_logical_clock_time(100, 5);
129    /// let hlc2 = HybridLogicalClock::new_with_both_physical_and_logical_clock_time(100, 10);
130    /// assert!(hlc1.is_concurrent(&hlc2));
131    /// 
132    /// // Non-concurrent example
133    /// let hlc3 = HybridLogicalClock::new_with_both_physical_and_logical_clock_time(101, 1);
134    /// assert!(!hlc1.is_concurrent(&hlc3));
135    /// ```
136    pub fn is_concurrent(&self, other: &Self) -> bool {
137        self.physical == other.physical && self.logical != other.logical
138    }
139
140}
141
142impl PartialOrd for HybridLogicalClock {
143    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
144        Some(self.cmp(other))
145    }
146}
147
148impl Ord for HybridLogicalClock {
149    fn cmp(&self, other: &Self) -> Ordering {
150        match self.physical.cmp(&other.physical) {
151            Ordering::Equal => self.logical.cmp(&other.logical),
152            other => other,
153        }
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn test_new() {
163        let hlc = HybridLogicalClock::new(100);
164        assert_eq!(hlc.physical, 100);
165        assert_eq!(hlc.logical, 0);
166    }
167
168    #[test]
169    fn test_new_with_both() {
170        let hlc = HybridLogicalClock::new_with_both_physical_and_logical_clock_time(100, 50);
171        assert_eq!(hlc.physical, 100);
172        assert_eq!(hlc.logical, 50);
173    }
174
175    #[test]
176    fn test_update() {
177        let mut hlc1 = HybridLogicalClock::new(100);
178        let hlc2 = HybridLogicalClock::new_with_both_physical_and_logical_clock_time(150, 10);
179        hlc1.update(&hlc2, 200);
180        assert_eq!(hlc1.physical, 200);
181        assert_eq!(hlc1.logical, 1);
182    }
183
184    #[test]
185    fn test_is_concurrent() {
186        let hlc1 = HybridLogicalClock::new_with_both_physical_and_logical_clock_time(100, 5);
187        let hlc2 = HybridLogicalClock::new_with_both_physical_and_logical_clock_time(100, 10);
188        assert!(hlc1.is_concurrent(&hlc2));
189    }
190
191    #[test]
192    fn test_ordering() {
193        let hlc1 = HybridLogicalClock::new_with_both_physical_and_logical_clock_time(100, 5);
194        let hlc2 = HybridLogicalClock::new_with_both_physical_and_logical_clock_time(100, 10);
195        let hlc3 = HybridLogicalClock::new_with_both_physical_and_logical_clock_time(150, 0);
196        assert!(hlc1 < hlc2);
197        assert!(hlc2 < hlc3);
198    }
199}