Skip to main content

euv_core/reactive/signal/
impl.rs

1use crate::*;
2
3/// Implementation of reactive signal operations.
4impl<T> Signal<T>
5where
6    T: Clone + PartialEq + 'static,
7{
8    /// Creates a new `Signal` with the given initial value.
9    ///
10    /// # Arguments
11    ///
12    /// - `T` - The initial value of the signal.
13    ///
14    /// # Returns
15    ///
16    /// - `Self` - A handle to the newly created reactive signal.
17    pub fn create(value: T) -> Self {
18        let signal_inner: Rc<RefCell<SignalInner<T>>> =
19            Rc::new(RefCell::new(SignalInner::new(value, Vec::new(), true)));
20        let addr: usize = Rc::as_ptr(&signal_inner) as usize;
21        signal_inner_registry_mut().insert(addr, signal_inner as Rc<dyn Any>);
22        let mut signal: Self = Self::new(0, std::marker::PhantomData);
23        signal.set_inner(addr);
24        signal
25    }
26
27    /// Returns the raw inner pointer address for identity comparison.
28    ///
29    /// # Returns
30    ///
31    /// - `usize` - The memory address of the inner `Rc`.
32    pub(crate) fn get_inner_addr(&self) -> usize {
33        self.get_inner()
34    }
35
36    /// Returns a reference to the inner `RefCell` for this signal.
37    ///
38    /// # Returns
39    ///
40    /// - `&'static RefCell<SignalInner<T>>` - A reference to the inner state.
41    #[inline(always)]
42    fn inner_ref(&self) -> &'static RefCell<SignalInner<T>> {
43        get_signal_inner_ref(self.get_inner())
44    }
45
46    /// Returns the current value of the signal.
47    ///
48    /// # Returns
49    ///
50    /// - `T` - The current value of the signal.
51    #[inline]
52    pub fn get(&self) -> T {
53        self.inner_ref().borrow().get_value().clone()
54    }
55
56    /// Attempts to return the current value of the signal without panicking.
57    ///
58    /// Unlike `get`, this method uses `try_borrow` and returns `None` if the
59    /// inner `RefCell` is already mutably borrowed, avoiding a panic.
60    ///
61    /// # Returns
62    ///
63    /// - `Some(T)` - The current value if the borrow succeeds.
64    /// - `None` - If the inner value is already mutably borrowed.
65    pub fn try_get(&self) -> Option<T> {
66        self.inner_ref()
67            .try_borrow()
68            .ok()
69            .map(|inner: Ref<SignalInner<T>>| inner.get_value().clone())
70    }
71
72    /// Subscribes a callback to be invoked when the signal changes.
73    ///
74    /// # Arguments
75    ///
76    /// - `FnMut() + 'static` - The callback to invoke when the signal changes.
77    pub fn subscribe<F>(&self, callback: F)
78    where
79        F: FnMut() + 'static,
80    {
81        self.inner_ref()
82            .borrow_mut()
83            .get_mut_listeners()
84            .push(Box::new(callback));
85    }
86
87    /// Replaces all listeners with a single new callback.
88    ///
89    /// Unlike `subscribe`, which appends a listener, this method clears any
90    /// existing listeners first and then adds the new one.
91    ///
92    /// # Arguments
93    ///
94    /// - `FnMut() + 'static` - The callback to invoke when the signal changes.
95    pub(crate) fn replace_subscribe<F>(&self, callback: F)
96    where
97        F: FnMut() + 'static,
98    {
99        let mut inner: RefMut<SignalInner<T>> = self.inner_ref().borrow_mut();
100        let listeners: &mut Vec<Box<dyn FnMut()>> = inner.get_mut_listeners();
101        listeners.clear();
102        listeners.push(Box::new(callback));
103    }
104
105    /// Removes all subscribed listeners from this signal and marks it as
106    /// inactive.
107    pub(crate) fn clear_listeners(&self) {
108        let mut inner: RefMut<SignalInner<T>> = self.inner_ref().borrow_mut();
109        inner.set_alive(false);
110        inner.get_mut_listeners().clear();
111    }
112
113    /// Core implementation of value update and listener notification.
114    ///
115    /// Returns `true` if the value was updated and listeners were notified.
116    #[inline]
117    fn update_and_notify(&self, value: T) -> bool {
118        let inner_ref: &RefCell<SignalInner<T>> = self.inner_ref();
119        let mut listeners: Vec<Box<dyn FnMut()>> = Vec::new();
120        {
121            let mut inner: RefMut<SignalInner<T>> = inner_ref.borrow_mut();
122            if !inner.get_alive() {
123                return false;
124            }
125            if *inner.get_value() == value {
126                return false;
127            }
128            inner.set_value(value);
129            swap(inner.get_mut_listeners(), &mut listeners);
130        }
131        for listener in listeners.iter_mut() {
132            listener();
133        }
134        {
135            let mut inner: RefMut<SignalInner<T>> = inner_ref.borrow_mut();
136            swap(inner.get_mut_listeners(), &mut listeners);
137        }
138        true
139    }
140
141    /// Sets the value of the signal and notifies listeners.
142    ///
143    /// # Arguments
144    ///
145    /// - `T` - The new value to assign to the signal.
146    #[inline]
147    pub fn set(&self, value: T) {
148        if self.update_and_notify(value) {
149            schedule_signal_update();
150        }
151    }
152
153    /// Sets the value of the signal and notifies listeners without scheduling
154    /// a global DOM update dispatch.
155    ///
156    /// # Arguments
157    ///
158    /// - `T` - The new value to assign to the signal.
159    #[inline]
160    pub fn set_silent(&self, value: T) {
161        self.update_and_notify(value);
162    }
163
164    /// Sets the value of the signal without notifying listeners or scheduling
165    /// a DOM update. This is useful for breaking circular watch dependencies
166    /// where two signals watch each other and would otherwise recurse infinitely.
167    ///
168    /// # Arguments
169    ///
170    /// - `T` - The new value to assign to the signal.
171    #[inline]
172    pub fn set_untracked(&self, value: T) {
173        let inner_ref: &RefCell<SignalInner<T>> = self.inner_ref();
174        let mut inner: RefMut<SignalInner<T>> = inner_ref.borrow_mut();
175        inner.set_value(value);
176    }
177
178    /// Attempts to set the value of the signal and notify listeners without panicking.
179    ///
180    /// Unlike `set`, this method uses `try_borrow_mut` and returns `false` if
181    /// the inner `RefCell` is already borrowed, avoiding a panic.
182    ///
183    /// # Arguments
184    ///
185    /// - `T` - The new value to assign to the signal.
186    ///
187    /// # Returns
188    ///
189    /// - `bool` - `true` if the value was successfully updated and listeners were notified, `false` if unchanged, inactive, or already borrowed.
190    pub fn try_set(&self, value: T) -> bool {
191        let inner_ref: &RefCell<SignalInner<T>> = self.inner_ref();
192        let mut listeners: Vec<Box<dyn FnMut()>> = Vec::new();
193        {
194            let mut inner: RefMut<SignalInner<T>> = match inner_ref.try_borrow_mut() {
195                Ok(inner) => inner,
196                Err(_) => return false,
197            };
198            if !inner.get_alive() {
199                return false;
200            }
201            if *inner.get_value() == value {
202                return false;
203            }
204            inner.set_value(value);
205            swap(inner.get_mut_listeners(), &mut listeners);
206        }
207        for listener in listeners.iter_mut() {
208            listener();
209        }
210        {
211            let mut inner: RefMut<SignalInner<T>> = inner_ref.borrow_mut();
212            swap(inner.get_mut_listeners(), &mut listeners);
213        }
214        schedule_signal_update();
215        true
216    }
217}
218
219/// Clones the signal, sharing the same inner state.
220///
221/// Since `Signal` is `Copy`, this simply returns `*self`.
222///
223/// # Returns
224///
225/// - `Self`: A copy of the signal handle sharing the same inner state.
226impl<T> Clone for Signal<T>
227where
228    T: Clone + PartialEq + 'static,
229{
230    fn clone(&self) -> Self {
231        *self
232    }
233}
234
235/// Copies the signal, sharing the same inner state.
236///
237/// Safe because only the inner address (a `usize`) is copied;
238/// the actual `Rc` reference is held by the global signal registry.
239impl<T> Copy for Signal<T> where T: Clone + PartialEq + 'static {}
240
241/// Marks `SignalCell` as `Sync` for single-threaded WASM contexts.
242///
243/// SAFETY: `SignalCell` is only used in single-threaded WASM contexts.
244/// Concurrent access from multiple threads would be undefined behavior.
245unsafe impl<T> Sync for SignalCell<T> where T: Clone + PartialEq + 'static {}
246
247/// Implementation of SignalCell construction and access.
248impl<T> SignalCell<T>
249where
250    T: Clone + PartialEq + 'static,
251{
252    /// Creates a new `SignalCell` with no signal stored.
253    ///
254    /// # Returns
255    ///
256    /// - `Self`: An empty `SignalCell` with `None` stored in the inner `UnsafeCell`.
257    pub const fn none() -> Self {
258        Self {
259            inner: UnsafeCell::new(None),
260        }
261    }
262
263    /// Stores a signal into the cell.
264    ///
265    /// # Arguments
266    ///
267    /// - `Signal<T>` - The signal to store.
268    ///
269    /// # Panics
270    ///
271    /// Panics if a signal has already been stored.
272    pub fn set(&self, signal: Signal<T>) {
273        unsafe {
274            let ptr: &mut Option<Signal<T>> = &mut *self.get_inner().get();
275            if ptr.is_some() {
276                panic!("SignalCell::set called on an already-initialized cell");
277            }
278            *ptr = Some(signal);
279        }
280    }
281
282    /// Returns the signal stored in the cell.
283    ///
284    /// # Returns
285    ///
286    /// - `Signal<T>` - The stored signal.
287    ///
288    /// # Panics
289    ///
290    /// Panics if no signal has been stored via `set`.
291    pub fn get(&self) -> Signal<T> {
292        unsafe {
293            let ptr: &Option<Signal<T>> = &*self.get_inner().get();
294            match ptr {
295                Some(signal) => *signal,
296                None => panic!("SignalCell::get called on an uninitialized cell"),
297            }
298        }
299    }
300}
301
302/// Provides a default empty `SignalCell`.
303///
304/// Creates a `SignalCell` with `None` stored in the inner `UnsafeCell`.
305///
306/// # Returns
307///
308/// - `Self`: An empty `SignalCell` with no signal stored.
309impl<T> Default for SignalCell<T>
310where
311    T: Clone + PartialEq + 'static,
312{
313    fn default() -> Self {
314        Self {
315            inner: UnsafeCell::new(None),
316        }
317    }
318}
319
320/// Marks `SignalInnerRegistryCell` as `Sync` for single-threaded WASM contexts.
321///
322/// SAFETY: `SignalInnerRegistryCell` is only used in single-threaded WASM contexts.
323/// Concurrent access from multiple threads would be undefined behavior.
324unsafe impl Sync for SignalInnerRegistryCell {}