Skip to main content

react_rs_core/
signal.rs

1use std::cell::RefCell;
2use std::rc::Rc;
3
4use crate::effect::flush_effects;
5use crate::runtime::RUNTIME;
6
7type SubscriberId = usize;
8
9struct SignalInner<T> {
10    value: T,
11    subscribers: Vec<SubscriberId>,
12    version: u64,
13}
14
15pub struct ReadSignal<T> {
16    inner: Rc<RefCell<SignalInner<T>>>,
17}
18
19impl<T> Clone for ReadSignal<T> {
20    fn clone(&self) -> Self {
21        Self {
22            inner: self.inner.clone(),
23        }
24    }
25}
26
27pub struct WriteSignal<T> {
28    inner: Rc<RefCell<SignalInner<T>>>,
29}
30
31impl<T> Clone for WriteSignal<T> {
32    fn clone(&self) -> Self {
33        Self {
34            inner: self.inner.clone(),
35        }
36    }
37}
38
39pub fn create_signal<T>(value: T) -> (ReadSignal<T>, WriteSignal<T>) {
40    let inner = Rc::new(RefCell::new(SignalInner {
41        value,
42        subscribers: Vec::new(),
43        version: 0,
44    }));
45
46    (
47        ReadSignal {
48            inner: inner.clone(),
49        },
50        WriteSignal { inner },
51    )
52}
53
54impl<T: Clone> ReadSignal<T> {
55    pub fn get(&self) -> T {
56        self.track();
57        self.inner.borrow().value.clone()
58    }
59
60    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
61        self.track();
62        f(&self.inner.borrow().value)
63    }
64
65    pub fn get_untracked(&self) -> T {
66        self.inner.borrow().value.clone()
67    }
68
69    fn track(&self) {
70        RUNTIME.with(|rt| {
71            if let Some(effect_id) = rt.borrow().current_effect() {
72                let mut inner = self.inner.borrow_mut();
73                if !inner.subscribers.contains(&effect_id) {
74                    inner.subscribers.push(effect_id);
75                }
76            }
77        });
78    }
79}
80
81impl<T> WriteSignal<T> {
82    pub fn set(&self, value: T) {
83        {
84            let mut inner = self.inner.borrow_mut();
85            inner.value = value;
86            inner.version += 1;
87        }
88        self.notify_subscribers();
89    }
90
91    pub fn update(&self, f: impl FnOnce(&mut T)) {
92        {
93            let mut inner = self.inner.borrow_mut();
94            f(&mut inner.value);
95            inner.version += 1;
96        }
97        self.notify_subscribers();
98    }
99
100    fn notify_subscribers(&self) {
101        let inner = self.inner.borrow();
102        let should_flush = RUNTIME.with(|rt| {
103            let mut rt = rt.borrow_mut();
104            for &subscriber_id in &inner.subscribers {
105                rt.schedule_effect(subscriber_id);
106            }
107            !rt.is_batching()
108        });
109        drop(inner);
110
111        if should_flush {
112            flush_effects();
113        }
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn test_signal_read_write() {
123        let (read, write) = create_signal(0);
124        assert_eq!(read.get_untracked(), 0);
125        write.set(5);
126        assert_eq!(read.get_untracked(), 5);
127    }
128
129    #[test]
130    fn test_signal_update() {
131        let (read, write) = create_signal(vec![1, 2]);
132        write.update(|v| v.push(3));
133        assert_eq!(read.get_untracked(), vec![1, 2, 3]);
134    }
135
136    #[test]
137    fn test_signal_with() {
138        let (read, _write) = create_signal(String::from("hello"));
139        let len = read.with(|s| s.len());
140        assert_eq!(len, 5);
141    }
142}