1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
9pub struct Hlc {
10 pub wall_ms: u64,
11 pub logical: u32,
12}
13
14impl Hlc {
15 pub const ZERO: Hlc = Hlc {
16 wall_ms: 0,
17 logical: 0,
18 };
19
20 pub fn tick(self, now_ms: u64) -> Hlc {
22 let wall = now_ms.max(self.wall_ms);
23 let logical = if wall == self.wall_ms {
24 self.logical + 1
25 } else {
26 0
27 };
28 Hlc {
29 wall_ms: wall,
30 logical,
31 }
32 }
33
34 pub fn merge(self, remote: Hlc, now_ms: u64) -> Hlc {
36 let wall = now_ms.max(self.wall_ms).max(remote.wall_ms);
37 let logical = match (wall == self.wall_ms, wall == remote.wall_ms) {
38 (true, true) => self.logical.max(remote.logical) + 1,
39 (true, false) => self.logical + 1,
40 (false, true) => remote.logical + 1,
41 (false, false) => 0,
42 };
43 Hlc {
44 wall_ms: wall,
45 logical,
46 }
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 #[test]
55 fn tick_monotonic_when_clock_stalls() {
56 let a = Hlc::ZERO.tick(1000);
57 let b = a.tick(1000);
58 assert!(b > a);
59 assert_eq!(b.wall_ms, 1000);
60 assert_eq!(b.logical, 1);
61 }
62
63 #[test]
64 fn merge_dominates_both_inputs() {
65 let local = Hlc {
66 wall_ms: 100,
67 logical: 2,
68 };
69 let remote = Hlc {
70 wall_ms: 200,
71 logical: 5,
72 };
73 let merged = local.merge(remote, 150);
74 assert!(merged > local);
75 assert!(merged > remote);
76 }
77}