orbit/state/
mod.rs

1//! State management system for the Orbit UI framework
2//!
3//! This module provides several approaches to state management:
4//!
5//! 1. `StateContainer` - A basic state container that stores values and notifies subscribers when values change
6//! 2. `State<T>` - A typed reference to a value in the state container
7//! 3. `Computed<T>` - A derived state value that depends on other values
8//! 4. The reactive module - A fine-grained reactive system with signals and effects
9
10mod reactive;
11
12pub use reactive::{
13    create_computed, create_effect, create_signal, Effect, ReactiveComputed, ReactiveScope, Signal,
14    SignalError,
15};
16
17use std::{
18    any::TypeId,
19    collections::HashMap,
20    sync::{Arc, Mutex},
21};
22
23// Define a type alias for the complex subscriber type
24type SubscriberCallback = Box<dyn Fn() + Send + Sync>;
25type SubscriberMap = HashMap<TypeId, Vec<SubscriberCallback>>;
26// Define a type for the complex state value container
27type StateValue = Arc<Mutex<Box<dyn std::any::Any + Send + Sync>>>;
28type StateMap = HashMap<TypeId, StateValue>;
29
30/// State management for Orbit applications
31///
32/// This is the original state management system that is being enhanced with the reactive system.
33/// It's kept for backward compatibility but new code should prefer the reactive system.
34#[derive(Clone)]
35pub struct StateContainer {
36    // Using Arc<Mutex<>> for thread-safe interior mutability
37    // TypeId is used to identify the type of the stored value
38    #[allow(clippy::type_complexity)]
39    values: Arc<Mutex<StateMap>>,
40    // Subscribers are functions that are called when a value changes
41    // Using Arc<Mutex<>> for thread-safe interior mutability
42    pub(crate) subscribers: Arc<Mutex<SubscriberMap>>,
43}
44
45impl std::fmt::Debug for StateContainer {
46    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47        f.debug_struct("StateContainer")
48            .field("values", &"[StateMap]")
49            .field("subscribers", &"[SubscriberMap]")
50            .finish()
51    }
52}
53
54impl StateContainer {
55    /// Create a new state container
56    pub fn new() -> Self {
57        Self {
58            values: Arc::new(Mutex::new(HashMap::new())),
59            subscribers: Arc::new(Mutex::new(HashMap::new())),
60        }
61    }
62
63    /// Create a new state value
64    pub fn create<T: 'static + Clone + Send + Sync>(&self, initial: T) -> State<T> {
65        let type_id = TypeId::of::<T>();
66
67        // Store initial value with the correct type (Arc<Mutex<Box<dyn Any + Send + Sync>>>)
68        self.values
69            .lock()
70            .unwrap()
71            .insert(type_id, Arc::new(Mutex::new(Box::new(initial.clone()))));
72
73        State {
74            container: self.clone(),
75            type_id,
76            _marker: std::marker::PhantomData,
77        }
78    }
79
80    /// Create computed state dependent on other state
81    pub fn computed<T, F>(&self, compute: F, dependencies: Vec<TypeId>) -> Computed<T>
82    where
83        T: 'static + Clone + Send + Sync,
84        F: Fn() -> T + Send + Sync + 'static,
85    {
86        // Create initial value
87        let initial = compute();
88        let type_id = TypeId::of::<T>();
89
90        // Store initial value with correct type
91        self.values
92            .lock()
93            .unwrap()
94            .insert(type_id, Arc::new(Mutex::new(Box::new(initial))));
95
96        Computed::new(self.clone(), compute, dependencies)
97    }
98
99    /// Subscribe to state changes
100    pub fn subscribe<T: 'static + Send + Sync, F: Fn() + Send + Sync + 'static>(
101        &self,
102        callback: F,
103    ) {
104        let type_id = TypeId::of::<T>();
105        let mut subscribers = self.subscribers.lock().unwrap();
106        subscribers
107            .entry(type_id)
108            .or_default()
109            .push(Box::new(callback));
110    }
111
112    /// Notify subscribers of a change to a value
113    pub fn notify(&self, type_id: TypeId) {
114        let subscribers = self.subscribers.lock().unwrap();
115
116        if let Some(callbacks) = subscribers.get(&type_id) {
117            for callback in callbacks {
118                callback();
119            }
120        }
121    }
122}
123
124impl Default for StateContainer {
125    fn default() -> Self {
126        Self::new()
127    }
128}
129
130/// Represents a reactive value that can be observed for changes
131pub struct State<T> {
132    /// State container
133    container: StateContainer,
134
135    /// Type ID for this state
136    type_id: TypeId,
137
138    /// Phantom data for type
139    _marker: std::marker::PhantomData<T>,
140}
141
142impl<T: 'static + Clone + Send + Sync> State<T> {
143    /// Get current value
144    pub fn get(&self) -> T {
145        let values = self.container.values.lock().unwrap();
146
147        values
148            .get(&self.type_id)
149            .and_then(|value| {
150                let lock = value.lock().unwrap();
151                lock.downcast_ref::<T>().cloned()
152            })
153            .unwrap()
154    }
155
156    /// Set new value
157    pub fn set(&self, value: T) {
158        // Update value
159        self.container
160            .values
161            .lock()
162            .unwrap()
163            .insert(self.type_id, Arc::new(Mutex::new(Box::new(value))));
164
165        // Notify subscribers
166        self.container.notify(self.type_id);
167    }
168    /// Update value with a function
169    pub fn update<F>(&self, f: F)
170    where
171        F: FnOnce(&T) -> T,
172    {
173        let values = self.container.values.lock().unwrap();
174
175        if let Some(value_container) = values.get(&self.type_id) {
176            let mut value_lock = value_container.lock().unwrap();
177
178            // Apply the function to get the new value
179            if let Some(old_value) = value_lock.downcast_ref::<T>() {
180                let new_value = f(old_value);
181                *value_lock = Box::new(new_value);
182
183                // Drop locks before notifying subscribers
184                drop(value_lock);
185                drop(values);
186
187                // Notify subscribers
188                self.container.notify(self.type_id);
189            }
190        }
191    }
192
193    /// Add a callback that will be called when the state changes
194    pub fn on_change<F>(&self, callback: F)
195    where
196        F: Fn(&T) + Send + Sync + 'static,
197    {
198        let container = self.container.clone();
199        let type_id = self.type_id;
200
201        self.container.subscribe::<T, _>(move || {
202            // Get the current value and call the callback with it
203            let values = container.values.lock().unwrap();
204            if let Some(value_container) = values.get(&type_id) {
205                let value_lock = value_container.lock().unwrap();
206                if let Some(value) = value_lock.downcast_ref::<T>() {
207                    callback(value);
208                }
209            }
210        });
211    }
212}
213
214/// Represents a computed state value that depends on other state
215pub struct Computed<T> {
216    /// State container
217    container: StateContainer,
218
219    /// Type ID for this state
220    type_id: TypeId,
221
222    /// Compute function
223    #[allow(dead_code)]
224    compute: Arc<Box<dyn Fn() -> T + Send + Sync>>,
225
226    /// Dependencies
227    dependencies: Vec<TypeId>,
228}
229
230impl<T: 'static + Clone + Send + Sync> Computed<T> {
231    /// Create new computed state
232    pub fn new<F>(container: StateContainer, compute: F, dependencies: Vec<TypeId>) -> Self
233    where
234        F: Fn() -> T + Send + Sync + 'static,
235    {
236        // Initial value already stored in container
237        let type_id = TypeId::of::<T>();
238
239        // Wrap compute function in Arc for cloning
240        let compute_arc = Arc::new(Box::new(compute) as Box<dyn Fn() -> T + Send + Sync>);
241
242        // Create computed state
243        let computed = Self {
244            container: container.clone(),
245            type_id,
246            compute: compute_arc.clone(),
247            dependencies,
248        };
249
250        // Subscribe to dependencies
251        for &dep_id in computed.dependencies.iter() {
252            // Clone Arc wrapper for each dependency
253            let compute_fn = compute_arc.clone();
254            let container_clone = container.clone();
255            let type_id_clone = type_id;
256
257            // Create a closure for this dependency
258            let callback = move || {
259                // Recompute value when dependency changes
260                let new_value = (*compute_fn)();
261                container_clone
262                    .values
263                    .lock()
264                    .unwrap()
265                    .insert(type_id_clone, Arc::new(Mutex::new(Box::new(new_value))));
266            };
267
268            // Add closure to subscribers for this dependency type
269            let mut subscribers = container.subscribers.lock().unwrap();
270            subscribers
271                .entry(dep_id)
272                .or_default()
273                .push(Box::new(callback));
274        }
275
276        computed
277    }
278
279    /// Get current value
280    pub fn get(&self) -> T {
281        let values = self.container.values.lock().unwrap();
282        values
283            .get(&self.type_id)
284            .and_then(|value| {
285                let lock = value.lock().unwrap();
286                lock.downcast_ref::<T>().cloned()
287            })
288            .unwrap()
289    }
290}