Skip to main content

logicaffeine_data/crdt/
lww.rs

1//! Last-Write-Wins Register CRDT
2//!
3//! A register that resolves conflicts using timestamps.
4//! The value with the highest timestamp wins on merge.
5//!
6//! ## Time Injection (Lamport Invariant)
7//!
8//! This type is pure - it does not access system time.
9//! Callers must provide timestamps, enabling WASM compatibility.
10
11use super::Merge;
12use serde::{Deserialize, Serialize};
13
14/// A register that resolves conflicts using "last write wins" semantics.
15///
16/// Each write records a timestamp, and on merge the value with
17/// the higher timestamp is kept.
18///
19/// ## Time Injection
20///
21/// Timestamps must be provided by the caller. This makes the type
22/// pure and WASM-compatible. Use a logical clock or system time
23/// at the call site.
24#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
25pub struct LWWRegister<T> {
26    value: T,
27    /// Logical timestamp (e.g., microseconds since epoch, or lamport clock)
28    timestamp: u64,
29}
30
31impl<T: Default> Default for LWWRegister<T> {
32    fn default() -> Self {
33        Self::new(T::default(), 0)
34    }
35}
36
37impl<T> LWWRegister<T> {
38    /// Create a new register with the given initial value and timestamp.
39    ///
40    /// The timestamp should be provided by the caller (e.g., from system time
41    /// or a logical clock). This enables WASM compatibility.
42    pub fn new(value: T, timestamp: u64) -> Self {
43        Self { value, timestamp }
44    }
45
46    /// Set a new value with the given timestamp.
47    ///
48    /// The timestamp should be greater than or equal to the current timestamp
49    /// to ensure the new value takes precedence on merge.
50    pub fn set(&mut self, value: T, timestamp: u64) {
51        self.value = value;
52        self.timestamp = timestamp;
53    }
54
55    /// Get the current value.
56    pub fn get(&self) -> &T {
57        &self.value
58    }
59
60    /// Get the timestamp of the last write.
61    pub fn timestamp(&self) -> u64 {
62        self.timestamp
63    }
64}
65
66impl<T: Clone> Merge for LWWRegister<T> {
67    /// Merge another register into this one.
68    ///
69    /// The value with the higher timestamp wins.
70    /// If timestamps are equal, the other value wins (arbitrary but deterministic).
71    fn merge(&mut self, other: &Self) {
72        if other.timestamp >= self.timestamp {
73            self.value = other.value.clone();
74            self.timestamp = other.timestamp;
75        }
76    }
77}
78
79// NOTE: Showable impl is in logicaffeine_system (io module)
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn test_lww_new() {
87        let reg = LWWRegister::new("hello".to_string(), 100);
88        assert_eq!(reg.get(), "hello");
89        assert_eq!(reg.timestamp(), 100);
90    }
91
92    #[test]
93    fn test_lww_set() {
94        let mut reg = LWWRegister::new("hello".to_string(), 100);
95        reg.set("world".to_string(), 200);
96        assert_eq!(reg.get(), "world");
97        assert_eq!(reg.timestamp(), 200);
98    }
99
100    #[test]
101    fn test_lww_merge_newer_wins() {
102        let r1 = LWWRegister::new("old".to_string(), 100);
103        let r2 = LWWRegister::new("new".to_string(), 200);
104
105        let mut r1_copy = r1.clone();
106        r1_copy.merge(&r2);
107        assert_eq!(r1_copy.get(), "new");
108    }
109
110    #[test]
111    fn test_lww_merge_older_loses() {
112        let r1 = LWWRegister::new("old".to_string(), 100);
113        let r2 = LWWRegister::new("new".to_string(), 200);
114
115        let mut r2_copy = r2.clone();
116        r2_copy.merge(&r1);
117        // r2 had higher timestamp, so it keeps its value
118        assert_eq!(r2_copy.get(), "new");
119    }
120
121    #[test]
122    fn test_lww_merge_idempotent() {
123        let reg = LWWRegister::new("test".to_string(), 100);
124        let mut reg_copy = reg.clone();
125        reg_copy.merge(&reg);
126        assert_eq!(reg_copy.get(), "test");
127    }
128
129    #[test]
130    fn test_lww_with_int() {
131        let mut reg = LWWRegister::new(42i64, 100);
132        assert_eq!(*reg.get(), 42);
133        reg.set(100, 200);
134        assert_eq!(*reg.get(), 100);
135    }
136
137    #[test]
138    fn test_lww_with_bool() {
139        let mut reg = LWWRegister::new(false, 100);
140        assert_eq!(*reg.get(), false);
141        reg.set(true, 200);
142        assert_eq!(*reg.get(), true);
143    }
144}