Skip to main content

state_store/
lib.rs

1//! Internal implementation detail of [`sonos-sdk`](https://crates.io/crates/sonos-sdk). Not intended for direct use.
2//!
3//! Generic State Management Library
4//!
5//! A type-safe, generic state management library with change detection
6//! and blocking iteration patterns.
7//!
8//! # Features
9//!
10//! - **Type-safe Storage**: Store and retrieve strongly-typed properties
11//! - **Change Detection**: Only emit events when values actually change
12//! - **Watch Pattern**: Register interest in specific properties
13//! - **Blocking Iteration**: Consume change events via blocking iterators
14//! - **Generic Entity IDs**: Use any hashable type as entity identifiers
15//!
16//! # Quick Start
17//!
18//! ```rust,ignore
19//! use state_store::{StateStore, Property};
20//!
21//! // Define a property type
22//! #[derive(Clone, PartialEq, Debug)]
23//! struct Temperature(f32);
24//!
25//! impl Property for Temperature {
26//!     const KEY: &'static str = "temperature";
27//! }
28//!
29//! // Create a store with String entity IDs
30//! let store = StateStore::<String>::new();
31//!
32//! // Watch for temperature changes on sensor-1
33//! store.watch("sensor-1".to_string(), Temperature::KEY);
34//!
35//! // Set a value (will emit change event since watched)
36//! store.set(&"sensor-1".to_string(), Temperature(72.5));
37//!
38//! // Get current value
39//! let temp = store.get::<Temperature>(&"sensor-1".to_string());
40//! assert_eq!(temp, Some(Temperature(72.5)));
41//! ```
42//!
43//! # Iteration Patterns
44//!
45//! ```rust,ignore
46//! // Blocking iteration (waits for events)
47//! for event in store.iter() {
48//!     println!("{} changed on {:?}", event.property_key, event.entity_id);
49//! }
50//!
51//! // Non-blocking (processes available events)
52//! for event in store.iter().try_iter() {
53//!     println!("Event: {:?}", event);
54//! }
55//!
56//! // With timeout
57//! use std::time::Duration;
58//! if let Some(event) = store.iter().recv_timeout(Duration::from_secs(1)) {
59//!     println!("Got event: {:?}", event);
60//! }
61//! ```
62//!
63//! # Architecture
64//!
65//! ```text
66//! StateStore<Id>
67//!     │
68//!     ├── entities: HashMap<Id, PropertyBag>
69//!     │       │
70//!     │       └── PropertyBag: HashMap<TypeId, Box<dyn Any>>
71//!     │
72//!     ├── watched: HashSet<(Id, property_key)>
73//!     │
74//!     └── event_channel: mpsc::channel<ChangeEvent<Id>>
75//!             │
76//!             └── ChangeIterator<Id>
77//! ```
78
79// Modules
80pub mod event;
81pub mod iter;
82pub mod property;
83pub mod store;
84
85// Re-exports - Public API
86pub use event::ChangeEvent;
87pub use iter::{ChangeIterator, TimeoutIter, TryIter};
88pub use property::Property;
89pub use store::{PropertyBag, StateStore};
90
91/// Prelude for convenient imports
92pub mod prelude {
93    pub use crate::event::ChangeEvent;
94    pub use crate::iter::ChangeIterator;
95    pub use crate::property::Property;
96    pub use crate::store::{PropertyBag, StateStore};
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[derive(Clone, PartialEq, Debug)]
104    struct Volume(u8);
105
106    impl Property for Volume {
107        const KEY: &'static str = "volume";
108    }
109
110    #[derive(Clone, PartialEq, Debug)]
111    struct Mute(bool);
112
113    impl Property for Mute {
114        const KEY: &'static str = "mute";
115    }
116
117    #[test]
118    fn test_full_workflow() {
119        // Create store
120        let store = StateStore::<String>::new();
121
122        // Add some properties
123        store.set(&"speaker-1".to_string(), Volume(50));
124        store.set(&"speaker-1".to_string(), Mute(false));
125
126        // Verify values
127        assert_eq!(
128            store.get::<Volume>(&"speaker-1".to_string()),
129            Some(Volume(50))
130        );
131        assert_eq!(
132            store.get::<Mute>(&"speaker-1".to_string()),
133            Some(Mute(false))
134        );
135
136        // Watch and verify events
137        store.watch("speaker-1".to_string(), Volume::KEY);
138
139        // Change value
140        store.set(&"speaker-1".to_string(), Volume(75));
141
142        // Get event
143        let event = store
144            .iter()
145            .recv_timeout(std::time::Duration::from_millis(100));
146        assert!(event.is_some());
147        assert_eq!(event.unwrap().property_key, Volume::KEY);
148    }
149
150    #[test]
151    fn test_multiple_entities() {
152        let store = StateStore::<String>::new();
153
154        store.set(&"speaker-1".to_string(), Volume(50));
155        store.set(&"speaker-2".to_string(), Volume(75));
156        store.set(&"speaker-3".to_string(), Volume(100));
157
158        assert_eq!(store.entity_count(), 3);
159        assert_eq!(
160            store.get::<Volume>(&"speaker-1".to_string()),
161            Some(Volume(50))
162        );
163        assert_eq!(
164            store.get::<Volume>(&"speaker-2".to_string()),
165            Some(Volume(75))
166        );
167        assert_eq!(
168            store.get::<Volume>(&"speaker-3".to_string()),
169            Some(Volume(100))
170        );
171    }
172
173    #[test]
174    fn test_store_clone_shares_state() {
175        let store1 = StateStore::<String>::new();
176        let store2 = store1.clone();
177
178        store1.set(&"speaker-1".to_string(), Volume(50));
179
180        // Both clones see the same data
181        assert_eq!(
182            store2.get::<Volume>(&"speaker-1".to_string()),
183            Some(Volume(50))
184        );
185    }
186}