Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-core/src/reactive.rs
StratoSDK / crates / strato-core / src / reactive.rs
1//! Reactive programming primitives for StratoUI
2 
3use parking_lot::RwLock;
4use smallvec::SmallVec;
5use std::sync::Arc;
6// Removed unused std::fmt::Debug import
7use std::marker::PhantomData;
8 
9/// Effect function that runs when dependencies change
10pub type EffectFn = Box<dyn Fn() + Send + Sync>;
11 
12/// Dependency tracking for reactive values
13pub trait Reactive: Send + Sync {
14 /// Track this reactive value as a dependency
15 fn track(&self);
16 
17 /// Trigger updates for all dependents
18 fn trigger(&self);
19}
20 
21/// Computed value that derives from other reactive values
22pub struct Computed<T: Clone + Send + Sync + 'static> {
23 value: Arc<RwLock<Option<T>>>,
24 compute_fn: Arc<dyn Fn() -> T + Send + Sync>,
25 dependencies: Arc<RwLock<SmallVec<[Box<dyn Reactive>; 4]>>>,
26}
27 
28impl<T: Clone + Send + Sync + 'static> Computed<T> {
29 /// Create a new computed value
30 pub fn new<F>(compute_fn: F) -> Self
31 where
32 F: Fn() -> T + Send + Sync + 'static,
33 {
34 Self {
35 value: Arc::new(RwLock::new(None)),
36 compute_fn: Arc::new(compute_fn),
37 dependencies: Arc::new(RwLock::new(SmallVec::new())),
38 }
39 }
40 
41 /// Get the computed value, recomputing if necessary
42 pub fn get(&self) -> T {
43 let mut value = self.value.write();
44 if value.is_none() {
45 *value = Some((self.compute_fn)());
46 }
47 value.as_ref().unwrap().clone()
48 }
49 
50 /// Invalidate the cached value
51 pub fn invalidate(&self) {
52 *self.value.write() = None;
53 }
54 
55 /// Add a dependency
56 pub fn add_dependency(&self, dep: Box<dyn Reactive>) {
57 self.dependencies.write().push(dep);
58 }
59}
60 
61impl<T: Clone + Send + Sync + 'static> Reactive for Computed<T> {
62 fn track(&self) {
63 // Track this computed as a dependency
64 }
65 
66 fn trigger(&self) {
67 self.invalidate();
68 }
69}
70 
71/// Effect that runs when dependencies change
72pub struct Effect {
73 effect_fn: Arc<EffectFn>,
74 dependencies: Arc<RwLock<SmallVec<[Box<dyn Reactive>; 4]>>>,
75 active: Arc<RwLock<bool>>,
76}
77 
78impl Effect {
79 /// Create a new effect
80 pub fn new<F>(effect_fn: F) -> Self
81 where
82 F: Fn() + Send + Sync + 'static,
83 {
84 let effect = Self {
85 effect_fn: Arc::new(Box::new(effect_fn)),
86 dependencies: Arc::new(RwLock::new(SmallVec::new())),
87 active: Arc::new(RwLock::new(true)),
88 };
89 
90 // Run the effect immediately
91 effect.run();
92 
93 effect
94 }
95 
96 /// Run the effect
97 pub fn run(&self) {
98 if *self.active.read() {
99 (self.effect_fn)();
100 }
101 }
102 
103 /// Stop the effect
104 pub fn stop(&self) {
105 *self.active.write() = false;
106 }
107 
108 /// Resume the effect
109 pub fn resume(&self) {
110 *self.active.write() = true;
111 self.run();
112 }
113 
114 /// Add a dependency
115 pub fn add_dependency(&self, dep: Box<dyn Reactive>) {
116 self.dependencies.write().push(dep);
117 }
118}
119 
120/// Memo for caching expensive computations
121pub struct Memo<T: Clone + PartialEq + Send + Sync + 'static> {
122 value: Arc<RwLock<Option<T>>>,
123 compute_fn: Arc<dyn Fn() -> T + Send + Sync>,
124 _phantom: PhantomData<T>,
125}
126 
127impl<T: Clone + PartialEq + Send + Sync + 'static> Memo<T> {
128 /// Create a new memo
129 pub fn new<F>(compute_fn: F) -> Self
130 where
131 F: Fn() -> T + Send + Sync + 'static,
132 {
133 Self {
134 value: Arc::new(RwLock::new(None)),
135 compute_fn: Arc::new(compute_fn),
136 _phantom: PhantomData,
137 }
138 }
139 
140 /// Get the memoized value
141 pub fn get(&self) -> T {
142 let mut value = self.value.write();
143 if value.is_none() {
144 *value = Some((self.compute_fn)());
145 }
146 value.as_ref().unwrap().clone()
147 }
148 
149 /// Clear the memoized value
150 pub fn clear(&self) {
151 *self.value.write() = None;
152 }
153}
154 
155/// Watch for changes in a reactive value
156pub struct Watch<T: Clone + Send + Sync + 'static> {
157 value: Arc<RwLock<T>>,
158 callbacks: Arc<RwLock<SmallVec<[Box<dyn Fn(&T) + Send + Sync>; 2]>>>,
159}
160 
161impl<T: Clone + Send + Sync + 'static> Watch<T> {
162 /// Create a new watch
163 pub fn new(initial: T) -> Self {
164 Self {
165 value: Arc::new(RwLock::new(initial)),
166 callbacks: Arc::new(RwLock::new(SmallVec::new())),
167 }
168 }
169 
170 /// Get the current value
171 pub fn get(&self) -> T {
172 self.value.read().clone()
173 }
174 
175 /// Set a new value and trigger callbacks
176 pub fn set(&self, value: T) {
177 *self.value.write() = value.clone();
178 let callbacks = self.callbacks.read();
179 for callback in callbacks.iter() {
180 callback(&value);
181 }
182 }
183 
184 /// Watch for changes
185 pub fn on_change<F>(&self, callback: F)
186 where
187 F: Fn(&T) + Send + Sync + 'static,
188 {
189 self.callbacks.write().push(Box::new(callback));
190 }
191}
192 
193/// Batch multiple updates together
194pub struct Batch {
195 updates: Arc<RwLock<Vec<Box<dyn FnOnce() + Send>>>>,
196}
197 
198impl Batch {
199 /// Create a new batch
200 pub fn new() -> Self {
201 Self {
202 updates: Arc::new(RwLock::new(Vec::new())),
203 }
204 }
205 
206 /// Add an update to the batch
207 pub fn add<F>(&self, update: F)
208 where
209 F: FnOnce() + Send + 'static,
210 {
211 self.updates.write().push(Box::new(update));
212 }
213 
214 /// Execute all batched updates
215 pub fn flush(&self) {
216 let updates = std::mem::take(&mut *self.updates.write());
217 for update in updates {
218 update();
219 }
220 }
221}
222 
223impl Default for Batch {
224 fn default() -> Self {
225 Self::new()
226 }
227}
228 
229#[cfg(test)]
230mod tests {
231 use super::*;
232 
233 #[test]
234 fn test_computed() {
235 let counter = Arc::new(RwLock::new(0));
236 let counter_clone = counter.clone();
237 
238 let computed = Computed::new(move || *counter_clone.read() * 2);
239 
240 assert_eq!(computed.get(), 0);
241 
242 *counter.write() = 5;
243 computed.invalidate();
244 assert_eq!(computed.get(), 10);
245 }
246 
247 #[test]
248 fn test_watch() {
249 use std::sync::atomic::{AtomicI32, Ordering};
250 
251 let watch = Watch::new(0);
252 let received = Arc::new(AtomicI32::new(0));
253 let received_clone = received.clone();
254 
255 watch.on_change(move |value| {
256 received_clone.store(*value, Ordering::SeqCst);
257 });
258 
259 watch.set(42);
260 assert_eq!(received.load(Ordering::SeqCst), 42);
261 }
262 
263 #[test]
264 fn test_memo() {
265 let call_count = Arc::new(RwLock::new(0));
266 let call_count_clone = call_count.clone();
267 
268 let memo = Memo::new(move || {
269 *call_count_clone.write() += 1;
270 42
271 });
272 
273 assert_eq!(memo.get(), 42);
274 assert_eq!(memo.get(), 42); // Should not recompute
275 assert_eq!(*call_count.read(), 1);
276 
277 memo.clear();
278 assert_eq!(memo.get(), 42); // Recomputes after clear
279 assert_eq!(*call_count.read(), 2);
280 }
281}
282