Skip to main content

driven/state/
dirty_bits.rs

1//! Dirty-Bit Change Tracking
2//!
3//! O(1) change detection using bitmask tracking.
4
5use std::sync::atomic::{AtomicU64, Ordering};
6
7/// 64-bit dirty mask for tracking up to 64 rule sections
8#[derive(Debug)]
9pub struct DirtyMask(AtomicU64);
10
11impl DirtyMask {
12    /// Create a clean mask
13    pub fn new() -> Self {
14        Self(AtomicU64::new(0))
15    }
16
17    /// Create a fully dirty mask
18    pub fn all_dirty() -> Self {
19        Self(AtomicU64::new(u64::MAX))
20    }
21
22    /// Mark a bit as dirty
23    pub fn mark_dirty(&self, bit: u8) {
24        if bit < 64 {
25            self.0.fetch_or(1 << bit, Ordering::SeqCst);
26        }
27    }
28
29    /// Clear a bit
30    pub fn clear(&self, bit: u8) {
31        if bit < 64 {
32            self.0.fetch_and(!(1 << bit), Ordering::SeqCst);
33        }
34    }
35
36    /// Clear all bits
37    pub fn clear_all(&self) {
38        self.0.store(0, Ordering::SeqCst);
39    }
40
41    /// Check if a bit is dirty
42    pub fn is_dirty(&self, bit: u8) -> bool {
43        if bit >= 64 {
44            return false;
45        }
46        (self.0.load(Ordering::SeqCst) & (1 << bit)) != 0
47    }
48
49    /// Check if any bits are dirty
50    pub fn any_dirty(&self) -> bool {
51        self.0.load(Ordering::SeqCst) != 0
52    }
53
54    /// Get raw value
55    pub fn raw(&self) -> u64 {
56        self.0.load(Ordering::SeqCst)
57    }
58
59    /// Count dirty bits
60    pub fn count(&self) -> u32 {
61        self.0.load(Ordering::SeqCst).count_ones()
62    }
63
64    /// Get dirty bit indices
65    pub fn dirty_indices(&self) -> Vec<u8> {
66        let raw = self.raw();
67        (0..64).filter(|&i| (raw & (1 << i)) != 0).collect()
68    }
69
70    /// Swap and get previous value
71    pub fn swap(&self, new_value: u64) -> u64 {
72        self.0.swap(new_value, Ordering::SeqCst)
73    }
74}
75
76impl Default for DirtyMask {
77    fn default() -> Self {
78        Self::new()
79    }
80}
81
82impl Clone for DirtyMask {
83    fn clone(&self) -> Self {
84        Self(AtomicU64::new(self.0.load(Ordering::SeqCst)))
85    }
86}
87
88/// Dirty-bit tracker for rule sections
89#[derive(Debug)]
90pub struct DirtyBits {
91    /// Mask for persona changes
92    pub persona: DirtyMask,
93    /// Mask for standards changes
94    pub standards: DirtyMask,
95    /// Mask for workflow changes
96    pub workflow: DirtyMask,
97    /// Mask for context changes
98    pub context: DirtyMask,
99    /// Global change counter
100    change_counter: AtomicU64,
101    /// Last sync counter
102    last_sync: AtomicU64,
103}
104
105impl DirtyBits {
106    /// Create new tracker
107    pub fn new() -> Self {
108        Self {
109            persona: DirtyMask::new(),
110            standards: DirtyMask::new(),
111            workflow: DirtyMask::new(),
112            context: DirtyMask::new(),
113            change_counter: AtomicU64::new(0),
114            last_sync: AtomicU64::new(0),
115        }
116    }
117
118    /// Mark persona as dirty
119    pub fn dirty_persona(&self, index: u8) {
120        self.persona.mark_dirty(index);
121        self.change_counter.fetch_add(1, Ordering::SeqCst);
122    }
123
124    /// Mark standard as dirty
125    pub fn dirty_standard(&self, index: u8) {
126        self.standards.mark_dirty(index);
127        self.change_counter.fetch_add(1, Ordering::SeqCst);
128    }
129
130    /// Mark workflow step as dirty
131    pub fn dirty_workflow(&self, index: u8) {
132        self.workflow.mark_dirty(index);
133        self.change_counter.fetch_add(1, Ordering::SeqCst);
134    }
135
136    /// Mark context as dirty
137    pub fn dirty_context(&self, index: u8) {
138        self.context.mark_dirty(index);
139        self.change_counter.fetch_add(1, Ordering::SeqCst);
140    }
141
142    /// Check if any changes exist since last sync
143    pub fn has_changes(&self) -> bool {
144        self.change_counter.load(Ordering::SeqCst) > self.last_sync.load(Ordering::SeqCst)
145    }
146
147    /// Clear all dirty bits and mark as synced
148    pub fn mark_synced(&self) {
149        self.persona.clear_all();
150        self.standards.clear_all();
151        self.workflow.clear_all();
152        self.context.clear_all();
153        self.last_sync
154            .store(self.change_counter.load(Ordering::SeqCst), Ordering::SeqCst);
155    }
156
157    /// Get change count
158    pub fn change_count(&self) -> u64 {
159        self.change_counter.load(Ordering::SeqCst)
160    }
161
162    /// Get changes since last sync
163    pub fn pending_changes(&self) -> u64 {
164        self.change_counter.load(Ordering::SeqCst) - self.last_sync.load(Ordering::SeqCst)
165    }
166
167    /// Get summary of dirty sections
168    pub fn summary(&self) -> DirtySummary {
169        DirtySummary {
170            persona_dirty: self.persona.any_dirty(),
171            standards_dirty: self.standards.any_dirty(),
172            workflow_dirty: self.workflow.any_dirty(),
173            context_dirty: self.context.any_dirty(),
174            total_dirty: self.persona.count()
175                + self.standards.count()
176                + self.workflow.count()
177                + self.context.count(),
178        }
179    }
180}
181
182impl Default for DirtyBits {
183    fn default() -> Self {
184        Self::new()
185    }
186}
187
188/// Summary of dirty state
189#[derive(Debug, Clone)]
190pub struct DirtySummary {
191    /// Persona section has changes
192    pub persona_dirty: bool,
193    /// Standards section has changes
194    pub standards_dirty: bool,
195    /// Workflow section has changes
196    pub workflow_dirty: bool,
197    /// Context section has changes
198    pub context_dirty: bool,
199    /// Total dirty bit count
200    pub total_dirty: u32,
201}
202
203impl DirtySummary {
204    /// Check if any section is dirty
205    pub fn any_dirty(&self) -> bool {
206        self.persona_dirty || self.standards_dirty || self.workflow_dirty || self.context_dirty
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    #[test]
215    fn test_dirty_mask() {
216        let mask = DirtyMask::new();
217        assert!(!mask.any_dirty());
218
219        mask.mark_dirty(0);
220        assert!(mask.is_dirty(0));
221        assert!(mask.any_dirty());
222
223        mask.mark_dirty(5);
224        assert!(mask.is_dirty(5));
225        assert_eq!(mask.count(), 2);
226
227        mask.clear(0);
228        assert!(!mask.is_dirty(0));
229        assert!(mask.is_dirty(5));
230    }
231
232    #[test]
233    fn test_dirty_bits() {
234        let bits = DirtyBits::new();
235        assert!(!bits.has_changes());
236
237        bits.dirty_standard(0);
238        assert!(bits.has_changes());
239        assert_eq!(bits.pending_changes(), 1);
240
241        bits.dirty_standard(1);
242        assert_eq!(bits.pending_changes(), 2);
243
244        bits.mark_synced();
245        assert!(!bits.has_changes());
246        assert_eq!(bits.pending_changes(), 0);
247    }
248
249    #[test]
250    fn test_dirty_summary() {
251        let bits = DirtyBits::new();
252        bits.dirty_persona(0);
253        bits.dirty_workflow(5);
254
255        let summary = bits.summary();
256        assert!(summary.persona_dirty);
257        assert!(!summary.standards_dirty);
258        assert!(summary.workflow_dirty);
259        assert!(summary.any_dirty());
260    }
261}