Skip to main content

state_store/
store.rs

1//! Type-erased property storage and state management
2//!
3//! This module provides the core storage primitives for state management:
4//! - `PropertyBag`: Type-erased storage for a single entity's properties
5//! - `StateStore<Id>`: Collection of entities with their property bags
6
7use std::any::{Any, TypeId};
8use std::collections::{HashMap, HashSet};
9use std::hash::Hash;
10use std::sync::{mpsc, Arc, Mutex, RwLock};
11use std::time::Instant;
12
13use crate::event::ChangeEvent;
14use crate::iter::ChangeIterator;
15use crate::property::Property;
16
17// ============================================================================
18// PropertyBag - type-erased property storage for a single entity
19// ============================================================================
20
21/// Type-erased storage for an entity's properties
22///
23/// Uses `TypeId` to store and retrieve strongly-typed values.
24/// Change detection is built-in via `PartialEq` comparison.
25///
26/// # Example
27///
28/// ```rust,ignore
29/// use state_store::{PropertyBag, Property};
30///
31/// #[derive(Clone, PartialEq, Debug)]
32/// struct Volume(u8);
33/// impl Property for Volume {
34///     const KEY: &'static str = "volume";
35/// }
36///
37/// let mut bag = PropertyBag::new();
38/// assert!(bag.get::<Volume>().is_none());
39///
40/// // First set returns true (value changed)
41/// assert!(bag.set(Volume(50)));
42///
43/// // Same value returns false (no change)
44/// assert!(!bag.set(Volume(50)));
45///
46/// // Different value returns true
47/// assert!(bag.set(Volume(75)));
48///
49/// assert_eq!(bag.get::<Volume>(), Some(Volume(75)));
50/// ```
51pub struct PropertyBag {
52    values: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
53}
54
55impl PropertyBag {
56    /// Create a new empty property bag
57    pub fn new() -> Self {
58        Self {
59            values: HashMap::new(),
60        }
61    }
62
63    /// Get a property value by type
64    ///
65    /// Returns `None` if the property has not been set.
66    pub fn get<P: Property>(&self) -> Option<P> {
67        let type_id = TypeId::of::<P>();
68        self.values
69            .get(&type_id)
70            .and_then(|boxed| boxed.downcast_ref::<P>())
71            .cloned()
72    }
73
74    /// Set a property value, returning whether the value changed
75    ///
76    /// Uses `PartialEq` comparison to detect actual changes.
77    /// Returns `true` if the value was different (or newly set),
78    /// `false` if the value was the same.
79    pub fn set<P: Property>(&mut self, value: P) -> bool {
80        let type_id = TypeId::of::<P>();
81        let current = self
82            .values
83            .get(&type_id)
84            .and_then(|boxed| boxed.downcast_ref::<P>());
85
86        if current != Some(&value) {
87            self.values.insert(type_id, Box::new(value));
88            true
89        } else {
90            false
91        }
92    }
93
94    /// Remove a property, returning whether it existed
95    pub fn remove<P: Property>(&mut self) -> bool {
96        let type_id = TypeId::of::<P>();
97        self.values.remove(&type_id).is_some()
98    }
99
100    /// Check if a property exists
101    pub fn contains<P: Property>(&self) -> bool {
102        let type_id = TypeId::of::<P>();
103        self.values.contains_key(&type_id)
104    }
105
106    /// Get the number of properties stored
107    pub fn len(&self) -> usize {
108        self.values.len()
109    }
110
111    /// Check if the bag is empty
112    pub fn is_empty(&self) -> bool {
113        self.values.is_empty()
114    }
115
116    /// Clear all properties
117    pub fn clear(&mut self) {
118        self.values.clear();
119    }
120}
121
122impl Default for PropertyBag {
123    fn default() -> Self {
124        Self::new()
125    }
126}
127
128impl std::fmt::Debug for PropertyBag {
129    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130        f.debug_struct("PropertyBag")
131            .field("property_count", &self.values.len())
132            .finish()
133    }
134}
135
136// ============================================================================
137// StateStore<Id> - generic state store for entities
138// ============================================================================
139
140/// Generic state store for managing entity properties with change detection
141///
142/// The store is generic over the entity ID type, allowing it to be used
143/// with any identifier (strings, custom IDs, etc.).
144///
145/// # Features
146///
147/// - Type-safe property storage and retrieval
148/// - Change detection (only emits events when values actually change)
149/// - Watch pattern (register interest in property changes)
150/// - Blocking iteration over change events
151///
152/// # Example
153///
154/// ```rust,ignore
155/// use state_store::{StateStore, Property};
156///
157/// #[derive(Clone, PartialEq, Debug)]
158/// struct Temperature(f32);
159/// impl Property for Temperature {
160///     const KEY: &'static str = "temperature";
161/// }
162///
163/// let store = StateStore::<String>::new();
164/// let sensor_id = "sensor-1".to_string();
165///
166/// // Watch for temperature changes on sensor-1
167/// store.watch(sensor_id.clone(), Temperature::KEY);
168///
169/// // Set temperature (will emit change event since watched)
170/// store.set(&sensor_id, Temperature(72.5));
171///
172/// // Get current value
173/// let temp = store.get::<Temperature>(&sensor_id);
174/// assert_eq!(temp, Some(Temperature(72.5)));
175/// ```
176pub struct StateStore<Id>
177where
178    Id: Clone + Eq + Hash + Send + Sync + 'static,
179{
180    /// Entity property storage: entity_id -> PropertyBag
181    entities: Arc<RwLock<HashMap<Id, PropertyBag>>>,
182
183    /// Watched properties: (entity_id, property_key)
184    watched: Arc<RwLock<HashSet<(Id, &'static str)>>>,
185
186    /// Channel sender for change events
187    event_tx: mpsc::Sender<ChangeEvent<Id>>,
188
189    /// Channel receiver for change events (wrapped for cloning)
190    event_rx: Arc<Mutex<mpsc::Receiver<ChangeEvent<Id>>>>,
191}
192
193impl<Id> StateStore<Id>
194where
195    Id: Clone + Eq + Hash + Send + Sync + 'static,
196{
197    /// Create a new empty state store
198    pub fn new() -> Self {
199        let (event_tx, event_rx) = mpsc::channel();
200
201        Self {
202            entities: Arc::new(RwLock::new(HashMap::new())),
203            watched: Arc::new(RwLock::new(HashSet::new())),
204            event_tx,
205            event_rx: Arc::new(Mutex::new(event_rx)),
206        }
207    }
208
209    /// Get a property value for an entity
210    ///
211    /// Returns `None` if the entity doesn't exist or the property isn't set.
212    pub fn get<P: Property>(&self, entity_id: &Id) -> Option<P> {
213        let entities = self.entities.read().ok()?;
214        entities.get(entity_id)?.get::<P>()
215    }
216
217    /// Set a property value for an entity
218    ///
219    /// If the value changes and the property is being watched,
220    /// a change event is emitted.
221    pub fn set<P: Property>(&self, entity_id: &Id, value: P) {
222        let changed = {
223            let mut entities = match self.entities.write() {
224                Ok(e) => e,
225                Err(_) => return,
226            };
227            let bag = entities
228                .entry(entity_id.clone())
229                .or_insert_with(PropertyBag::new);
230            bag.set(value)
231        };
232
233        if changed {
234            self.maybe_emit_change(entity_id, P::KEY);
235        }
236    }
237
238    /// Register interest in a property for an entity
239    ///
240    /// After watching, changes to this property will appear in `iter()`.
241    pub fn watch(&self, entity_id: Id, property_key: &'static str) {
242        if let Ok(mut watched) = self.watched.write() {
243            watched.insert((entity_id, property_key));
244        }
245    }
246
247    /// Unregister interest in a property
248    pub fn unwatch(&self, entity_id: &Id, property_key: &'static str) {
249        if let Ok(mut watched) = self.watched.write() {
250            watched.remove(&(entity_id.clone(), property_key));
251        }
252    }
253
254    /// Check if a property is being watched
255    pub fn is_watched(&self, entity_id: &Id, property_key: &'static str) -> bool {
256        self.watched
257            .read()
258            .map(|w| w.contains(&(entity_id.clone(), property_key)))
259            .unwrap_or(false)
260    }
261
262    /// Create a blocking iterator over change events
263    ///
264    /// Only emits events for properties that have been watched.
265    pub fn iter(&self) -> ChangeIterator<Id> {
266        ChangeIterator::new(Arc::clone(&self.event_rx))
267    }
268
269    /// Get the number of entities in the store
270    pub fn entity_count(&self) -> usize {
271        self.entities.read().map(|e| e.len()).unwrap_or(0)
272    }
273
274    /// Check if the store is empty
275    pub fn is_empty(&self) -> bool {
276        self.entity_count() == 0
277    }
278
279    /// Get all entity IDs
280    pub fn entity_ids(&self) -> Vec<Id> {
281        self.entities
282            .read()
283            .map(|e| e.keys().cloned().collect())
284            .unwrap_or_default()
285    }
286
287    /// Remove an entity and all its properties
288    pub fn remove_entity(&self, entity_id: &Id) -> bool {
289        self.entities
290            .write()
291            .map(|mut e| e.remove(entity_id).is_some())
292            .unwrap_or(false)
293    }
294
295    /// Clear all entities and properties
296    pub fn clear(&self) {
297        if let Ok(mut entities) = self.entities.write() {
298            entities.clear();
299        }
300        if let Ok(mut watched) = self.watched.write() {
301            watched.clear();
302        }
303    }
304
305    /// Get the event sender for external event injection
306    ///
307    /// This is useful for testing or for injecting events from
308    /// external sources (e.g., network callbacks).
309    pub fn event_sender(&self) -> mpsc::Sender<ChangeEvent<Id>> {
310        self.event_tx.clone()
311    }
312
313    /// Emit a change event if the property is being watched
314    fn maybe_emit_change(&self, entity_id: &Id, property_key: &'static str) {
315        let is_watched = self
316            .watched
317            .read()
318            .map(|w| w.contains(&(entity_id.clone(), property_key)))
319            .unwrap_or(false);
320
321        if is_watched {
322            let event = ChangeEvent {
323                entity_id: entity_id.clone(),
324                property_key,
325                timestamp: Instant::now(),
326            };
327            let _ = self.event_tx.send(event);
328        }
329    }
330}
331
332impl<Id> Default for StateStore<Id>
333where
334    Id: Clone + Eq + Hash + Send + Sync + 'static,
335{
336    fn default() -> Self {
337        Self::new()
338    }
339}
340
341impl<Id> Clone for StateStore<Id>
342where
343    Id: Clone + Eq + Hash + Send + Sync + 'static,
344{
345    fn clone(&self) -> Self {
346        Self {
347            entities: Arc::clone(&self.entities),
348            watched: Arc::clone(&self.watched),
349            event_tx: self.event_tx.clone(),
350            event_rx: Arc::clone(&self.event_rx),
351        }
352    }
353}
354
355impl<Id> std::fmt::Debug for StateStore<Id>
356where
357    Id: Clone + Eq + Hash + Send + Sync + std::fmt::Debug + 'static,
358{
359    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
360        f.debug_struct("StateStore")
361            .field("entity_count", &self.entity_count())
362            .finish()
363    }
364}
365
366#[cfg(test)]
367mod tests {
368    use super::*;
369
370    #[derive(Clone, PartialEq, Debug)]
371    struct TestProp(i32);
372
373    impl Property for TestProp {
374        const KEY: &'static str = "test";
375    }
376
377    #[derive(Clone, PartialEq, Debug)]
378    struct OtherProp(String);
379
380    impl Property for OtherProp {
381        const KEY: &'static str = "other";
382    }
383
384    #[test]
385    fn test_property_bag_basic() {
386        let mut bag = PropertyBag::new();
387
388        // Initially empty
389        assert!(bag.is_empty());
390        assert!(bag.get::<TestProp>().is_none());
391
392        // Set returns true (value changed)
393        assert!(bag.set(TestProp(42)));
394        assert!(!bag.is_empty());
395        assert_eq!(bag.get::<TestProp>(), Some(TestProp(42)));
396
397        // Same value returns false
398        assert!(!bag.set(TestProp(42)));
399
400        // Different value returns true
401        assert!(bag.set(TestProp(99)));
402        assert_eq!(bag.get::<TestProp>(), Some(TestProp(99)));
403    }
404
405    #[test]
406    fn test_property_bag_multiple_types() {
407        let mut bag = PropertyBag::new();
408
409        bag.set(TestProp(42));
410        bag.set(OtherProp("hello".to_string()));
411
412        assert_eq!(bag.len(), 2);
413        assert_eq!(bag.get::<TestProp>(), Some(TestProp(42)));
414        assert_eq!(bag.get::<OtherProp>(), Some(OtherProp("hello".to_string())));
415    }
416
417    #[test]
418    fn test_state_store_basic() {
419        let store = StateStore::<String>::new();
420
421        // Initially empty
422        assert!(store.is_empty());
423        assert!(store.get::<TestProp>(&"entity-1".to_string()).is_none());
424
425        // Set creates entity
426        store.set(&"entity-1".to_string(), TestProp(42));
427        assert_eq!(store.entity_count(), 1);
428        assert_eq!(
429            store.get::<TestProp>(&"entity-1".to_string()),
430            Some(TestProp(42))
431        );
432    }
433
434    #[test]
435    fn test_state_store_watch() {
436        let store = StateStore::<String>::new();
437        let entity_id = "entity-1".to_string();
438
439        // Not watched initially
440        assert!(!store.is_watched(&entity_id, TestProp::KEY));
441
442        // Watch
443        store.watch(entity_id.clone(), TestProp::KEY);
444        assert!(store.is_watched(&entity_id, TestProp::KEY));
445
446        // Unwatch
447        store.unwatch(&entity_id, TestProp::KEY);
448        assert!(!store.is_watched(&entity_id, TestProp::KEY));
449    }
450
451    #[test]
452    fn test_state_store_change_event() {
453        let store = StateStore::<String>::new();
454        let entity_id = "entity-1".to_string();
455
456        // Watch the property
457        store.watch(entity_id.clone(), TestProp::KEY);
458
459        // Set value (should emit event)
460        store.set(&entity_id, TestProp(42));
461
462        // Get event via iter
463        let iter = store.iter();
464        let event = iter.recv_timeout(std::time::Duration::from_millis(100));
465        assert!(event.is_some());
466
467        let event = event.unwrap();
468        assert_eq!(event.entity_id, entity_id);
469        assert_eq!(event.property_key, TestProp::KEY);
470    }
471
472    #[test]
473    fn test_state_store_no_event_when_not_watched() {
474        let store = StateStore::<String>::new();
475        let entity_id = "entity-1".to_string();
476
477        // Set without watching
478        store.set(&entity_id, TestProp(42));
479
480        // No event should be emitted
481        let iter = store.iter();
482        let event = iter.recv_timeout(std::time::Duration::from_millis(50));
483        assert!(event.is_none());
484    }
485
486    #[test]
487    fn test_state_store_no_event_when_same_value() {
488        let store = StateStore::<String>::new();
489        let entity_id = "entity-1".to_string();
490
491        store.watch(entity_id.clone(), TestProp::KEY);
492
493        // First set emits event
494        store.set(&entity_id, TestProp(42));
495
496        let iter = store.iter();
497        let event = iter.recv_timeout(std::time::Duration::from_millis(100));
498        assert!(event.is_some());
499
500        // Same value does not emit event
501        store.set(&entity_id, TestProp(42));
502        let event = iter.recv_timeout(std::time::Duration::from_millis(50));
503        assert!(event.is_none());
504    }
505
506    #[test]
507    fn test_state_store_clone() {
508        let store = StateStore::<String>::new();
509        let cloned = store.clone();
510
511        // Both share the same state
512        store.set(&"entity-1".to_string(), TestProp(42));
513        assert_eq!(
514            cloned.get::<TestProp>(&"entity-1".to_string()),
515            Some(TestProp(42))
516        );
517    }
518}