1use std::collections::HashMap;
7use std::time::{Duration, Instant};
8
9use crate::component::{ComponentError, ComponentId};
10
11#[derive(Debug, Clone)]
13pub struct StateSnapshot {
14 pub timestamp: Instant,
16 pub state_hash: u64,
18 pub fields: HashMap<String, StateValue>,
20}
21
22#[derive(Debug, Clone, PartialEq)]
24pub enum StateValue {
25 String(String),
26 Integer(i64),
27 Float(f64),
28 Boolean(bool),
29 Array(Vec<StateValue>),
30 Object(HashMap<String, StateValue>),
31 Null,
32}
33
34#[derive(Debug, Clone)]
36pub struct StateChange {
37 pub field_name: String,
39 pub old_value: Option<StateValue>,
41 pub new_value: StateValue,
43 pub timestamp: Instant,
45 pub priority: ChangePriority,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
51pub enum ChangePriority {
52 Low,
53 Normal,
54 High,
55 Critical,
56}
57
58#[derive(Debug, Clone)]
60pub struct StateChanges {
61 pub changes: Vec<StateChange>,
63 pub batch_timestamp: Instant,
65 pub immediate: bool,
67}
68
69pub struct StateTracker {
71 component_id: ComponentId,
73 previous_state: Option<StateSnapshot>,
75 current_state: Option<StateSnapshot>,
77 change_batch: Vec<StateChange>,
79 dirty_fields: HashMap<String, bool>,
81 config: StateTrackingConfig,
83}
84
85#[derive(Debug, Clone)]
87pub struct StateTrackingConfig {
88 pub max_batch_time: Duration,
90 pub max_batch_size: usize,
92 pub deep_comparison: bool,
94 pub snapshot_throttle: Duration,
96}
97
98impl Default for StateTrackingConfig {
99 fn default() -> Self {
100 Self {
101 max_batch_time: Duration::from_millis(16), max_batch_size: 50,
103 deep_comparison: true,
104 snapshot_throttle: Duration::from_millis(1),
105 }
106 }
107}
108
109impl StateSnapshot {
110 pub fn new(fields: HashMap<String, StateValue>) -> Self {
112 let state_hash = Self::compute_hash(&fields);
113 Self {
114 timestamp: Instant::now(),
115 state_hash,
116 fields,
117 }
118 }
119
120 fn compute_hash(fields: &HashMap<String, StateValue>) -> u64 {
122 use std::collections::hash_map::DefaultHasher;
123 use std::hash::{Hash, Hasher};
124
125 let mut hasher = DefaultHasher::new();
126
127 let mut sorted_keys: Vec<_> = fields.keys().collect();
129 sorted_keys.sort();
130
131 for key in sorted_keys {
132 key.hash(&mut hasher);
133 if let Some(value) = fields.get(key) {
134 value.hash(&mut hasher);
135 }
136 }
137
138 hasher.finish()
139 }
140
141 pub fn diff(&self, other: &StateSnapshot) -> Vec<StateChange> {
143 let mut changes = Vec::new();
144 let now = Instant::now();
145
146 for (field_name, new_value) in &other.fields {
148 let old_value = self.fields.get(field_name);
149
150 if old_value.map(|v| v != new_value).unwrap_or(true) {
151 changes.push(StateChange {
152 field_name: field_name.clone(),
153 old_value: old_value.cloned(),
154 new_value: new_value.clone(),
155 timestamp: now,
156 priority: ChangePriority::Normal,
157 });
158 }
159 }
160
161 for (field_name, old_value) in &self.fields {
163 if !other.fields.contains_key(field_name) {
164 changes.push(StateChange {
165 field_name: field_name.clone(),
166 old_value: Some(old_value.clone()),
167 new_value: StateValue::Null,
168 timestamp: now,
169 priority: ChangePriority::Normal,
170 });
171 }
172 }
173
174 changes
175 }
176}
177
178impl std::hash::Hash for StateValue {
179 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
180 match self {
181 StateValue::String(s) => {
182 0u8.hash(state);
183 s.hash(state);
184 }
185 StateValue::Integer(i) => {
186 1u8.hash(state);
187 i.hash(state);
188 }
189 StateValue::Float(f) => {
190 2u8.hash(state);
191 f.to_bits().hash(state);
192 }
193 StateValue::Boolean(b) => {
194 3u8.hash(state);
195 b.hash(state);
196 }
197 StateValue::Array(arr) => {
198 4u8.hash(state);
199 arr.hash(state);
200 }
201 StateValue::Object(obj) => {
202 5u8.hash(state);
203 let mut sorted_keys: Vec<_> = obj.keys().collect();
204 sorted_keys.sort();
205 for key in sorted_keys {
206 key.hash(state);
207 if let Some(value) = obj.get(key) {
208 value.hash(state);
209 }
210 }
211 }
212 StateValue::Null => {
213 6u8.hash(state);
214 }
215 }
216 }
217}
218
219impl StateTracker {
220 pub fn new(component_id: ComponentId, config: StateTrackingConfig) -> Self {
222 Self {
223 component_id,
224 previous_state: None,
225 current_state: None,
226 change_batch: Vec::new(),
227 dirty_fields: HashMap::new(),
228 config,
229 }
230 }
231
232 pub fn new_default(component_id: ComponentId) -> Self {
234 Self::new(component_id, StateTrackingConfig::default())
235 }
236 pub fn update_state(
238 &mut self,
239 new_fields: HashMap<String, StateValue>,
240 ) -> Result<Option<StateChanges>, ComponentError> {
241 let new_snapshot = StateSnapshot::new(new_fields);
242
243 if let Some(ref current) = self.current_state {
245 if new_snapshot.timestamp.duration_since(current.timestamp)
246 < self.config.snapshot_throttle
247 {
248 return Ok(None);
249 }
250 }
251
252 let changes = if let Some(ref previous) = self.current_state {
254 previous.diff(&new_snapshot)
255 } else {
256 new_snapshot
258 .fields
259 .iter()
260 .map(|(field_name, value)| StateChange {
261 field_name: field_name.clone(),
262 old_value: None,
263 new_value: value.clone(),
264 timestamp: new_snapshot.timestamp,
265 priority: ChangePriority::Normal,
266 })
267 .collect()
268 };
269
270 self.previous_state = self.current_state.take();
272 self.current_state = Some(new_snapshot); for change in changes {
274 self.dirty_fields.insert(change.field_name.clone(), true);
275 self.change_batch.push(change);
276 }
277
278 if self.should_flush_batch() {
280 Ok(Some(self.flush_batch()))
281 } else {
282 Ok(None)
283 }
284 }
285
286 pub fn is_field_dirty(&self, field_name: &str) -> bool {
288 self.dirty_fields.get(field_name).copied().unwrap_or(false)
289 }
290
291 pub fn mark_field_clean(&mut self, field_name: &str) {
293 self.dirty_fields.insert(field_name.to_string(), false);
294 }
295
296 pub fn get_dirty_fields(&self) -> Vec<String> {
298 self.dirty_fields
299 .iter()
300 .filter_map(
301 |(field, is_dirty)| {
302 if *is_dirty {
303 Some(field.clone())
304 } else {
305 None
306 }
307 },
308 )
309 .collect()
310 }
311
312 pub fn has_dirty_fields(&self) -> bool {
314 self.dirty_fields.values().any(|&dirty| dirty)
315 }
316 pub fn flush_batch(&mut self) -> StateChanges {
318 StateChanges {
319 changes: std::mem::take(&mut self.change_batch),
320 batch_timestamp: Instant::now(),
321 immediate: false,
322 }
323
324 }
327
328 fn should_flush_batch(&self) -> bool {
330 if self.change_batch.is_empty() {
331 return false;
332 }
333
334 if self.change_batch.len() >= self.config.max_batch_size {
336 return true;
337 }
338
339 if let Some(oldest_change) = self.change_batch.first() {
341 if oldest_change.timestamp.elapsed() >= self.config.max_batch_time {
342 return true;
343 }
344 }
345
346 self.change_batch
348 .iter()
349 .any(|change| change.priority == ChangePriority::Critical)
350 }
351
352 pub fn component_id(&self) -> ComponentId {
354 self.component_id
355 }
356
357 pub fn current_snapshot(&self) -> Option<&StateSnapshot> {
359 self.current_state.as_ref()
360 }
361
362 pub fn previous_snapshot(&self) -> Option<&StateSnapshot> {
364 self.previous_state.as_ref()
365 }
366
367 pub fn clear(&mut self) {
369 self.previous_state = None;
370 self.current_state = None;
371 self.change_batch.clear();
372 self.dirty_fields.clear();
373 }
374}
375
376impl StateChanges {
377 pub fn new(changes: Vec<StateChange>, immediate: bool) -> Self {
379 Self {
380 changes,
381 batch_timestamp: Instant::now(),
382 immediate,
383 }
384 }
385
386 pub fn is_empty(&self) -> bool {
388 self.changes.is_empty()
389 }
390
391 pub fn len(&self) -> usize {
393 self.changes.len()
394 }
395
396 pub fn changes_for_field(&self, field_name: &str) -> Vec<&StateChange> {
398 self.changes
399 .iter()
400 .filter(|change| change.field_name == field_name)
401 .collect()
402 }
403
404 pub fn has_critical_changes(&self) -> bool {
406 self.changes
407 .iter()
408 .any(|change| change.priority == ChangePriority::Critical)
409 }
410
411 pub fn sort_by_priority(&mut self) {
413 self.changes.sort_by(|a, b| b.priority.cmp(&a.priority));
414 }
415}
416
417#[cfg(test)]
418mod tests {
419 use super::*;
420
421 #[test]
422 fn test_state_snapshot_creation() {
423 let mut fields = HashMap::new();
424 fields.insert("count".to_string(), StateValue::Integer(42));
425 fields.insert("name".to_string(), StateValue::String("test".to_string()));
426
427 let snapshot = StateSnapshot::new(fields);
428
429 assert_eq!(snapshot.fields.len(), 2);
430 assert_eq!(snapshot.fields.get("count"), Some(&StateValue::Integer(42)));
431 assert_eq!(
432 snapshot.fields.get("name"),
433 Some(&StateValue::String("test".to_string()))
434 );
435 }
436
437 #[test]
438 fn test_state_diff_detection() {
439 let mut fields1 = HashMap::new();
440 fields1.insert("count".to_string(), StateValue::Integer(1));
441 let snapshot1 = StateSnapshot::new(fields1);
442
443 let mut fields2 = HashMap::new();
444 fields2.insert("count".to_string(), StateValue::Integer(2));
445 let snapshot2 = StateSnapshot::new(fields2);
446
447 let changes = snapshot1.diff(&snapshot2);
448
449 assert_eq!(changes.len(), 1);
450 assert_eq!(changes[0].field_name, "count");
451 assert_eq!(changes[0].old_value, Some(StateValue::Integer(1)));
452 assert_eq!(changes[0].new_value, StateValue::Integer(2));
453 }
454 #[test]
455 fn test_state_tracker_dirty_fields() {
456 let component_id = ComponentId::new();
457 let mut tracker = StateTracker::new(
458 component_id,
459 StateTrackingConfig {
460 max_batch_size: 1, ..Default::default()
462 },
463 );
464
465 let mut fields = HashMap::new();
466 fields.insert("count".to_string(), StateValue::Integer(1));
467
468 let changes = tracker.update_state(fields).unwrap();
469
470 assert!(tracker.is_field_dirty("count"));
471 assert!(changes.is_some());
472
473 tracker.mark_field_clean("count");
474 assert!(!tracker.is_field_dirty("count"));
475 }
476 #[test]
477 fn test_batch_flushing() {
478 let component_id = ComponentId::new();
479 let mut tracker = StateTracker::new(
480 component_id,
481 StateTrackingConfig {
482 max_batch_size: 2, snapshot_throttle: Duration::from_nanos(1), ..Default::default()
485 },
486 );
487
488 let mut fields1 = HashMap::new();
490 fields1.insert("count".to_string(), StateValue::Integer(1));
491 let changes1 = tracker.update_state(fields1).unwrap();
492 assert!(changes1.is_none()); let mut fields2 = HashMap::new();
496 fields2.insert("count".to_string(), StateValue::Integer(2));
497 let changes2 = tracker.update_state(fields2).unwrap();
498 assert!(changes2.is_some()); let changes = changes2.unwrap();
501 assert_eq!(changes.changes.len(), 2);
502 }
503}