simple_rsx/
signals.rs

1use std::cell::RefCell;
2use std::collections::{HashMap, HashSet};
3use std::sync::Arc;
4
5use crate::Node;
6
7thread_local! {
8    // Track the stack of active scopes
9    static SCOPE_STACK: RefCell<Vec<usize>> = RefCell::new(Vec::new());
10
11    // Track the current scope being executed
12    static CURRENT_SCOPE: RefCell<Option<usize>> = RefCell::new(None);
13
14    // Track if we're currently inside a scope render
15    static RENDERING_SCOPE: RefCell<bool> = RefCell::new(false);
16
17    // Store next scope ID
18    static NEXT_SCOPE_ID: RefCell<usize> = RefCell::new(1);
19
20    // Store signal counter for each scope
21    static SCOPE_SIGNAL_COUNTERS: RefCell<HashMap<usize, usize>> = RefCell::new(HashMap::new());
22
23    // Track signals that changed during current scope execution (for batching)
24    static SCOPE_SIGNAL_CHANGES: RefCell<HashSet<(usize, usize)>> = RefCell::new(HashSet::new());
25
26    static SIGNALS: RefCell<HashMap<(usize, usize), SignalValue>> = RefCell::new(HashMap::new());
27
28    // Store scope functions that can be re-executed
29    static SCOPE_FUNCTIONS: RefCell<HashMap<usize, Arc<dyn Fn() -> Node + Send>>> = RefCell::new(HashMap::new());
30
31    // Store next effect ID for each scope
32    static SCOPE_EFFECT_COUNTERS: RefCell<HashMap<usize, usize>> = RefCell::new(HashMap::new());
33
34    // Store effects with their IDs
35    static SCOPE_EFFECTS: RefCell<HashMap<(usize, usize), Box<dyn Fn() + Send + Sync>>> = RefCell::new(HashMap::new());
36
37    // Track which scopes depend on which signals
38    static SIGNAL_DEPENDENCIES: RefCell<HashMap<(usize, usize), HashSet<usize>>> = RefCell::new(HashMap::new());
39
40    // Queue for scopes that need to re-render
41    static PENDING_SCOPE_RENDERS: RefCell<HashSet<usize>> = RefCell::new(HashSet::new());
42}
43
44pub trait DynamicValue {
45    fn as_any(&self) -> Option<&dyn std::any::Any>;
46}
47
48impl DynamicValue for String {
49    fn as_any(&self) -> Option<&dyn std::any::Any> {
50        Some(self)
51    }
52}
53
54impl DynamicValue for &'static str {
55    fn as_any(&self) -> Option<&dyn std::any::Any> {
56        Some(self)
57    }
58}
59
60impl DynamicValue for i64 {
61    fn as_any(&self) -> Option<&dyn std::any::Any> {
62        Some(self)
63    }
64}
65
66impl DynamicValue for i128 {
67    fn as_any(&self) -> Option<&dyn std::any::Any> {
68        Some(self)
69    }
70}
71
72impl DynamicValue for i16 {
73    fn as_any(&self) -> Option<&dyn std::any::Any> {
74        Some(self)
75    }
76}
77
78impl DynamicValue for i32 {
79    fn as_any(&self) -> Option<&dyn std::any::Any> {
80        Some(self)
81    }
82}
83
84impl DynamicValue for i8 {
85    fn as_any(&self) -> Option<&dyn std::any::Any> {
86        Some(self)
87    }
88}
89
90impl DynamicValue for usize {
91    fn as_any(&self) -> Option<&dyn std::any::Any> {
92        Some(self)
93    }
94}
95
96impl DynamicValue for u64 {
97    fn as_any(&self) -> Option<&dyn std::any::Any> {
98        Some(self)
99    }
100}
101
102impl DynamicValue for u128 {
103    fn as_any(&self) -> Option<&dyn std::any::Any> {
104        Some(self)
105    }
106}
107
108impl DynamicValue for u16 {
109    fn as_any(&self) -> Option<&dyn std::any::Any> {
110        Some(self)
111    }
112}
113
114impl DynamicValue for u32 {
115    fn as_any(&self) -> Option<&dyn std::any::Any> {
116        Some(self)
117    }
118}
119
120impl DynamicValue for u8 {
121    fn as_any(&self) -> Option<&dyn std::any::Any> {
122        Some(self)
123    }
124}
125
126impl DynamicValue for f64 {
127    fn as_any(&self) -> Option<&dyn std::any::Any> {
128        Some(self)
129    }
130}
131
132impl DynamicValue for f32 {
133    fn as_any(&self) -> Option<&dyn std::any::Any> {
134        Some(self)
135    }
136}
137
138impl DynamicValue for bool {
139    fn as_any(&self) -> Option<&dyn std::any::Any> {
140        Some(self)
141    }
142}
143
144impl DynamicValue for char {
145    fn as_any(&self) -> Option<&dyn std::any::Any> {
146        Some(self)
147    }
148}
149
150impl DynamicValue for () {
151    fn as_any(&self) -> Option<&dyn std::any::Any> {
152        Some(self)
153    }
154}
155
156impl<T: DynamicValue + 'static> DynamicValue for Option<T> {
157    fn as_any(&self) -> Option<&dyn std::any::Any> {
158        Some(self)
159    }
160}
161
162#[derive(Clone, Copy, Debug)]
163pub struct Signal<T> {
164    id: (usize, usize),
165    _marker: std::marker::PhantomData<T>,
166}
167
168struct SignalValue {
169    value: Box<dyn DynamicValue>,
170}
171
172impl<T: DynamicValue + PartialEq + Clone + 'static> Signal<T> {
173    pub fn set(&self, value: T) {
174        let mut changed = false;
175        // Update the signal value
176        SIGNALS.with(|signals| {
177            if let Some(stored) = signals.borrow_mut().get_mut(&self.id) {
178                // Only update if the value actually changed
179                if let Some(should_update) = stored
180                    .value
181                    .as_any()
182                    .and_then(|any| any.downcast_ref::<T>().and_then(|val| Some(val != &value)))
183                    .or(Some(false))
184                {
185                    if should_update {
186                        *stored = SignalValue {
187                            value: Box::new(value),
188                        };
189                        changed = true;
190                    }
191                }
192            }
193        });
194
195        if changed {
196            SCOPE_SIGNAL_CHANGES.with(|changes| {
197                changes.borrow_mut().insert(self.id);
198            });
199        }
200    }
201
202    pub fn get(&self) -> T {
203        if let Some(current_scope) = get_current_scope() {
204            SIGNAL_DEPENDENCIES.with(|deps| {
205                let mut deps = deps.borrow_mut();
206                let scopes = deps.entry(self.id).or_insert_with(HashSet::new);
207                scopes.insert(current_scope);
208            });
209        }
210
211        SIGNALS
212            .with(|signals| {
213                if let Some(stored) = signals.borrow().get(&self.id) {
214                    if let Some(parsed) = stored
215                        .value
216                        .as_any()
217                        .and_then(|any| any.downcast_ref::<T>())
218                    {
219                        return Some(parsed.clone());
220                    }
221                }
222                None
223            })
224            .unwrap()
225    }
226}
227
228#[derive(Debug)]
229pub enum SignalCreationError {
230    OutsideScope,
231}
232
233impl std::fmt::Display for SignalCreationError {
234    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235        match self {
236            SignalCreationError::OutsideScope => {
237                write!(f, "Signals can only be created within a scope context")
238            }
239        }
240    }
241}
242
243impl std::error::Error for SignalCreationError {}
244
245fn get_current_scope() -> Option<usize> {
246    CURRENT_SCOPE.with(|scope| *scope.borrow())
247}
248
249fn set_current_scope(scope_id: Option<usize>) {
250    CURRENT_SCOPE.with(|scope| {
251        *scope.borrow_mut() = scope_id;
252    });
253    if let Some(id) = scope_id {
254        SCOPE_STACK.with(|stack| {
255            let mut stack = stack.borrow_mut();
256            if !stack.contains(&id) {
257                stack.push(id);
258            }
259        });
260    }
261}
262
263fn get_next_signal_id_for_scope(scope_id: usize) -> usize {
264    SCOPE_SIGNAL_COUNTERS.with(|counters| {
265        let mut counters = counters.borrow_mut();
266        let counter = counters.entry(scope_id).or_insert(0);
267        *counter += 1;
268        *counter
269    })
270}
271
272fn reset_signal_counters(scope_id: usize) {
273    SCOPE_SIGNAL_COUNTERS.with(|counters| {
274        counters.borrow_mut().remove(&scope_id);
275    });
276}
277
278fn schedule_dependent_scopes_for_rerender(signal_id: (usize, usize)) {
279    let dependent_scopes = SIGNAL_DEPENDENCIES.with(|deps| {
280        if let Ok(deps) = deps.try_borrow() {
281            deps.get(&signal_id).cloned().unwrap_or_default()
282        } else {
283            HashSet::new()
284        }
285    });
286
287    PENDING_SCOPE_RENDERS.with(|pending| {
288        if let Ok(mut pending) = pending.try_borrow_mut() {
289            for scope_id in dependent_scopes {
290                pending.insert(scope_id);
291            }
292        }
293    });
294}
295
296fn process_pending_renders() {
297    loop {
298        let scopes_to_render = PENDING_SCOPE_RENDERS.with(|pending| {
299            if let Ok(mut pending) = pending.try_borrow_mut() {
300                if pending.is_empty() {
301                    return Vec::new();
302                }
303                let scopes = pending.iter().copied().collect::<Vec<_>>();
304                pending.clear();
305                scopes
306            } else {
307                Vec::new()
308            }
309        });
310
311        if scopes_to_render.is_empty() {
312            break;
313        }
314
315        for scope_id in scopes_to_render {
316            render_scope(scope_id);
317        }
318    }
319}
320
321struct ScopeGuard {
322    previous_scope: Option<usize>,
323}
324
325impl Drop for ScopeGuard {
326    fn drop(&mut self) {
327        set_current_scope(self.previous_scope);
328    }
329}
330
331fn render_scope(scope_id: usize) -> Option<Node> {
332    // Create a scope guard that will restore the previous scope when dropped
333    let _guard = ScopeGuard {
334        previous_scope: get_current_scope(),
335    };
336
337    // Set the current scope for rendering
338    set_current_scope(Some(scope_id));
339
340    // Clear dependencies for this scope
341    SIGNAL_DEPENDENCIES.with(|deps| {
342        if let Ok(mut deps) = deps.try_borrow_mut() {
343            for (_, scopes) in deps.iter_mut() {
344                scopes.remove(&scope_id);
345            }
346        }
347    });
348
349    // Set rendering flag and clear changes
350    RENDERING_SCOPE.with(|flag| {
351        if let Ok(mut flag) = flag.try_borrow_mut() {
352            *flag = true;
353        }
354    });
355
356    SCOPE_SIGNAL_CHANGES.with(|changes| {
357        if let Ok(mut changes) = changes.try_borrow_mut() {
358            changes.clear();
359        }
360    });
361
362    // Execute the scope function
363    let scope_fn = SCOPE_FUNCTIONS.with(|scope_functions| {
364        let scope_functions = scope_functions.borrow();
365        if let Some(scope_fn) = scope_functions.get(&scope_id) {
366            return Some(scope_fn.clone());
367        }
368        return None;
369    });
370
371    let mut node = None;
372
373    if let Some(scope_fn) = scope_fn {
374        node = Some(scope_fn());
375    }
376
377    reset_signal_counters(scope_id);
378    run_scope_effects(scope_id);
379    reset_effect_counters(scope_id);
380
381    // Collect signal changes
382    let signal_changes = SCOPE_SIGNAL_CHANGES.with(|stored_changes| {
383        if let Ok(mut changes) = stored_changes.try_borrow_mut() {
384            let collected = changes.clone();
385            changes.clear();
386            collected
387        } else {
388            HashSet::new()
389        }
390    });
391
392    RENDERING_SCOPE.with(|flag| {
393        if let Ok(mut flag) = flag.try_borrow_mut() {
394            *flag = false;
395        }
396    });
397
398    // Schedule dependent scopes for rerender
399    for signal_id in signal_changes {
400        schedule_dependent_scopes_for_rerender(signal_id);
401    }
402
403    node
404    // Guard will automatically restore previous scope when dropped
405}
406
407fn run_scope_effects(scope_id: usize) {
408    SCOPE_EFFECTS.with(|effects| {
409        let effects = effects.borrow();
410        for (&(effect_scope_id, _), effect) in effects.iter() {
411            if effect_scope_id == scope_id {
412                effect();
413            }
414        }
415    });
416}
417
418pub fn create_signal<T: DynamicValue + PartialEq + 'static>(initial_value: T) -> Signal<T> {
419    let scope_id = get_current_scope()
420        .ok_or(SignalCreationError::OutsideScope)
421        .unwrap();
422
423    let signal_id = get_next_signal_id_for_scope(scope_id);
424    let signal = Signal {
425        id: (scope_id, signal_id),
426        _marker: std::marker::PhantomData,
427    };
428
429    SIGNALS.with(|signals| {
430        if signals.borrow_mut().get_mut(&signal.id).is_none() {
431            signals.borrow_mut().insert(
432                signal.id,
433                SignalValue {
434                    value: Box::new(initial_value),
435                },
436            );
437        }
438    });
439
440    signal
441}
442
443#[derive(Clone, Copy, Debug)]
444struct Effect {
445    id: (usize, usize),
446}
447
448fn get_next_effect_id_for_scope(scope_id: usize) -> usize {
449    SCOPE_EFFECT_COUNTERS.with(|counters| {
450        let mut counters = counters.borrow_mut();
451        let counter = counters.entry(scope_id).or_insert(0);
452        *counter += 1;
453        *counter
454    })
455}
456
457fn reset_effect_counters(scope_id: usize) {
458    SCOPE_EFFECT_COUNTERS.with(|counters| {
459        counters.borrow_mut().remove(&scope_id);
460    });
461}
462
463pub fn create_effect(effect: impl Fn() + Send + Sync + 'static) {
464    let scope_id = get_current_scope()
465        .ok_or(SignalCreationError::OutsideScope)
466        .unwrap();
467
468    let effect_id = get_next_effect_id_for_scope(scope_id);
469    let effect_struct = Effect {
470        id: (scope_id, effect_id),
471    };
472
473    SCOPE_EFFECTS.with(|effects| {
474        effects
475            .borrow_mut()
476            .insert(effect_struct.id, Box::new(effect));
477    });
478}
479
480pub fn run_scope(scope_fn: impl Fn() -> Node + Send + Sync + 'static) -> Option<Node> {
481    // Get next scope ID
482    let scope_id = NEXT_SCOPE_ID.with(|id| {
483        if let Ok(mut id) = id.try_borrow_mut() {
484            let current = *id;
485            *id = current + 1;
486            current
487        } else {
488            panic!("Failed to get next scope ID")
489        }
490    });
491
492    // Store the scope function so it can be re-executed
493    SCOPE_FUNCTIONS.with(|scope_functions| {
494        let mut scope_functions = scope_functions.borrow_mut();
495        scope_functions.insert(scope_id, Arc::new(scope_fn));
496    });
497
498    // Initial render of the scope
499    let node = render_scope(scope_id);
500
501    // Process any pending renders that might have been triggered
502    process_pending_renders();
503
504    node
505}
506
507// Helper function to manually trigger all scopes to re-render (useful for debugging)
508pub fn rerender_all_scopes() {
509    SCOPE_FUNCTIONS.with(|scope_functions| {
510        let scope_functions = scope_functions.borrow();
511        for scope_id in scope_functions.keys().cloned() {
512            render_scope(scope_id);
513        }
514    });
515
516    process_pending_renders();
517}
518
519#[cfg(test)]
520mod tests {
521    use super::*;
522    use std::sync::Arc;
523    use std::sync::atomic::{AtomicUsize, Ordering};
524
525    #[test]
526    fn test_nested_scopes() {
527        run_scope(|| {
528            let outer_signal = create_signal(0);
529
530            run_scope(move || {
531                let inner_signal = create_signal("hello");
532                assert!(inner_signal.get() == "hello");
533                outer_signal.set(42); // Can access outer scope's signals
534                Node::Empty
535            });
536
537            // assert_ne!(outer_scope_id, inner_scope_id);
538            assert_eq!(outer_signal.get(), 42);
539
540            Node::Empty
541        });
542    }
543
544    #[test]
545    fn test_signal_and_effect_in_scope() {
546        run_scope(move || {
547            let effect_count = Arc::new(AtomicUsize::new(0));
548            let effect_count_clone = effect_count.clone();
549            let signal = create_signal(0);
550
551            create_effect(move || {
552                let _ = signal.get();
553                effect_count_clone.fetch_add(1, Ordering::SeqCst);
554                // Effect should run once initially
555                assert!(effect_count.load(Ordering::SeqCst) > 0);
556                // Update signal value
557                signal.set(1);
558            });
559
560            Node::Empty
561        });
562    }
563
564    #[test]
565    fn test_multiple_signals_and_dependencies() {
566        run_scope(|| {
567            let signal1 = create_signal("hello");
568            let signal2 = create_signal(0);
569
570            create_effect(move || {
571                let str_val = signal1.get();
572                let num_val = signal2.get();
573
574                println!("Effect running with values: {}, {}", str_val, num_val);
575            });
576
577            signal1.set("world");
578            signal2.set(42);
579
580            // Verify final values
581            assert_eq!(signal1.get(), "world");
582            assert_eq!(signal2.get(), 42);
583
584            Node::Empty
585        });
586    }
587}