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 a reference to the inner `RefCell` for this signal.
28    ///
29    /// # Returns
30    ///
31    /// - `&'static RefCell<SignalInner<T>>` - A reference to the inner state.
32    fn inner_ref(&self) -> &'static RefCell<SignalInner<T>> {
33        get_signal_inner_ref(self.get_inner())
34    }
35
36    /// Returns the current value of the signal.
37    ///
38    /// Uses `try_borrow` to avoid panicking when the inner `RefCell` is
39    /// already mutably borrowed (e.g., during a `set()` notification cycle).
40    /// In that case, falls back to an unsafe direct read of the value,
41    /// which is safe in single-threaded WASM contexts because the value
42    /// itself is not being mutated at the point of read (only the `RefCell`
43    /// borrow flag is contested).
44    ///
45    /// If a tracking context is active (i.e., a DynamicNode is being rendered),
46    /// automatically registers the current dynamic node as a dependent of
47    /// this signal for precise reactive updates.
48    ///
49    /// # Returns
50    ///
51    /// - `T` - The current value of the signal.
52    pub fn get(&self) -> T {
53        let tracking_id: usize = CURRENT_TRACKING_DYNAMIC_ID.load(Ordering::Relaxed);
54        if tracking_id != usize::MAX {
55            self.add_dependent(tracking_id);
56        }
57        let inner_ref: &RefCell<SignalInner<T>> = self.inner_ref();
58        let Ok(inner) = inner_ref.try_borrow() else {
59            return unsafe { (*inner_ref.as_ptr()).get_value().clone() };
60        };
61        inner.get_value().clone()
62    }
63
64    /// Subscribes a callback to be invoked when the signal changes.
65    ///
66    /// If the inner `RefCell` is already borrowed, this is a no-op to avoid
67    /// panicking during re-entrant signal updates.
68    ///
69    /// # Arguments
70    ///
71    /// - `FnMut() + 'static` - The callback to invoke when the signal changes.
72    pub fn subscribe<F>(&self, callback: F)
73    where
74        F: FnMut() + 'static,
75    {
76        if let Ok(mut inner) = self.inner_ref().try_borrow_mut() {
77            inner.get_mut_listeners().push(Box::new(callback));
78        }
79    }
80
81    /// Replaces all listeners with a single new callback.
82    ///
83    /// Unlike `subscribe`, which appends a listener, this method clears any
84    /// existing listeners first and then adds the new one. If the inner
85    /// `RefCell` is already borrowed, this is a no-op.
86    ///
87    /// # Arguments
88    ///
89    /// - `FnMut() + 'static` - The callback to invoke when the signal changes.
90    pub(crate) fn replace_subscribe<F>(&self, callback: F)
91    where
92        F: FnMut() + 'static,
93    {
94        if let Ok(mut inner) = self.inner_ref().try_borrow_mut() {
95            let listeners: &mut Vec<Box<dyn FnMut()>> = inner.get_mut_listeners();
96            listeners.clear();
97            listeners.push(Box::new(callback));
98        }
99    }
100
101    /// Removes all subscribed listeners from this signal, clears its
102    /// dependent dynamic node list, and marks it as inactive.
103    /// If the inner `RefCell` is already borrowed, this is a no-op.
104    ///
105    /// NOTE: Does NOT remove from `SIGNAL_INNER_REGISTRY`. The registry
106    /// must keep the `Rc` alive because `Signal` is `Copy` and other copies
107    /// may still hold the raw address. Removing would cause use-after-free.
108    pub(crate) fn clear_listeners(&self) {
109        if let Ok(mut inner) = self.inner_ref().try_borrow_mut() {
110            inner.set_alive(false);
111            inner.get_mut_listeners().clear();
112            inner.get_mut_dependents().clear();
113        }
114    }
115
116    /// Core implementation of value update and listener notification.
117    ///
118    /// Returns `true` if the value was updated and listeners were notified.
119    /// Returns `false` if the inner `RefCell` is already mutably borrowed
120    /// (re-entrant access), the signal is inactive, or the value is unchanged.
121    fn update_and_notify(&self, value: T) -> bool {
122        let inner_ref: &RefCell<SignalInner<T>> = self.inner_ref();
123        let mut listeners: Vec<Box<dyn FnMut()>> = Vec::new();
124        let Ok(mut inner) = inner_ref.try_borrow_mut() else {
125            return false;
126        };
127        if !inner.get_alive() {
128            return false;
129        }
130        if *inner.get_value() == value {
131            return false;
132        }
133        inner.set_value(value);
134        swap(inner.get_mut_listeners(), &mut listeners);
135        drop(inner);
136        for mut listener in listeners {
137            listener();
138            if let Ok(mut inner) = inner_ref.try_borrow_mut() {
139                inner.get_mut_listeners().push(listener);
140            }
141        }
142        true
143    }
144
145    /// Registers a dynamic node ID as a dependent of this signal.
146    ///
147    /// When this signal changes, only its registered dependents will be
148    /// marked dirty for re-rendering, enabling precise updates instead
149    /// of broadcasting to all dynamic nodes.
150    ///
151    /// # Arguments
152    ///
153    /// - `usize` - The dynamic node ID to register as a dependent.
154    pub(crate) fn add_dependent(&self, dynamic_id: usize) {
155        if let Ok(mut inner) = self.inner_ref().try_borrow_mut() {
156            let deps: &mut Vec<usize> = inner.get_mut_dependents();
157            if !deps.contains(&dynamic_id) {
158                deps.push(dynamic_id);
159            }
160        }
161    }
162
163    /// Removes a dynamic node ID from the dependents list of this signal.
164    ///
165    /// Called during cleanup when a dynamic node is removed from the DOM
166    /// and its dependency relationships need to be severed.
167    ///
168    /// # Arguments
169    ///
170    /// - `usize` - The dynamic node ID to remove.
171    #[allow(dead_code)]
172    pub(crate) fn remove_dependent(&self, dynamic_id: usize) {
173        if let Ok(mut inner) = self.inner_ref().try_borrow_mut() {
174            inner.get_mut_dependents().retain(|id| *id != dynamic_id);
175        }
176    }
177
178    /// Returns the list of dependent dynamic node IDs for this signal.
179    ///
180    /// # Returns
181    ///
182    /// - `Vec<usize>` - Clone of the dependents list.
183    pub(crate) fn get_dependents(&self) -> Vec<usize> {
184        if let Ok(inner) = self.inner_ref().try_borrow() {
185            inner.get_dependents().clone()
186        } else {
187            Vec::new()
188        }
189    }
190
191    /// Sets the value of the signal and notifies listeners.
192    ///
193    /// Uses precise dirty marking: only dynamic nodes that depend on
194    /// this signal are marked dirty, avoiding full broadcast.
195    ///
196    /// # Arguments
197    ///
198    /// - `T` - The new value to assign to the signal.
199    pub fn set(&self, value: T) {
200        if self.update_and_notify(value) {
201            let dependents: Vec<usize> = self.get_dependents();
202            schedule_signal_update_targeted(&dependents);
203        }
204    }
205
206    /// Sets the value of the signal and notifies listeners without scheduling
207    /// a global DOM update dispatch.
208    ///
209    /// # Arguments
210    ///
211    /// - `T` - The new value to assign to the signal.
212    pub fn set_silent(&self, value: T) {
213        self.update_and_notify(value);
214    }
215
216    /// Sets the value of the signal without notifying listeners or scheduling
217    /// a DOM update. This is useful for breaking circular watch dependencies
218    /// where two signals watch each other and would otherwise recurse infinitely.
219    /// If the inner `RefCell` is already borrowed, this is a no-op.
220    ///
221    /// # Arguments
222    ///
223    /// - `T` - The new value to assign to the signal.
224    pub fn set_untracked(&self, value: T) {
225        let inner_ref: &RefCell<SignalInner<T>> = self.inner_ref();
226        if let Ok(mut inner) = inner_ref.try_borrow_mut() {
227            inner.set_value(value);
228        }
229    }
230}
231
232/// Provides a safe default for `Signal<T>` by creating a valid signal
233/// initialized with `T::default()`.
234///
235/// This prevents the creation of invalid signals with `inner = 0` (null
236/// pointer), which would cause a panic when `.get()` is called.
237///
238/// # Returns
239///
240/// - `Self`: A valid signal initialized with `T::default()`.
241impl<T> Default for Signal<T>
242where
243    T: Clone + Default + PartialEq + 'static,
244{
245    fn default() -> Self {
246        Self::create(T::default())
247    }
248}
249
250/// Clones the signal, sharing the same inner state.
251///
252/// Since `Signal` is `Copy`, this simply returns `*self`.
253///
254/// # Returns
255///
256/// - `Self`: A copy of the signal handle sharing the same inner state.
257impl<T> Clone for Signal<T>
258where
259    T: Clone + PartialEq + 'static,
260{
261    fn clone(&self) -> Self {
262        *self
263    }
264}
265
266/// Copies the signal, sharing the same inner state.
267///
268/// Safe because only the inner address (a `usize`) is copied;
269/// the actual `Rc` reference is held by the global signal registry.
270impl<T> Copy for Signal<T> where T: Clone + PartialEq + 'static {}
271
272/// Marks `SignalCell` as `Sync` for single-threaded WASM contexts.
273///
274/// SAFETY: `SignalCell` is only used in single-threaded WASM contexts.
275/// Concurrent access from multiple threads would be undefined behavior.
276unsafe impl<T> Sync for SignalCell<T> where T: Clone + PartialEq + 'static {}
277
278/// Implementation of SignalCell construction and access.
279impl<T> SignalCell<T>
280where
281    T: Clone + PartialEq + 'static,
282{
283    /// Creates a new `SignalCell` with no signal stored.
284    ///
285    /// # Returns
286    ///
287    /// - `Self`: An empty `SignalCell` with `None` stored in the inner `UnsafeCell`.
288    pub const fn none() -> Self {
289        Self {
290            inner: UnsafeCell::new(None),
291        }
292    }
293
294    /// Stores a signal into the cell.
295    ///
296    /// # Arguments
297    ///
298    /// - `Signal<T>` - The signal to store.
299    ///
300    /// # Panics
301    ///
302    /// Panics if a signal has already been stored.
303    pub fn set(&self, signal: Signal<T>) {
304        unsafe {
305            let ptr: &mut Option<Signal<T>> = &mut *self.get_inner().get();
306            if ptr.is_some() {
307                panic!("SignalCell::set called on an already-initialized cell");
308            }
309            *ptr = Some(signal);
310        }
311    }
312
313    /// Returns the signal stored in the cell.
314    ///
315    /// # Returns
316    ///
317    /// - `Signal<T>` - The stored signal.
318    ///
319    /// # Panics
320    ///
321    /// Panics if no signal has been stored via `set`.
322    pub fn get(&self) -> Signal<T> {
323        unsafe {
324            let ptr: &Option<Signal<T>> = &*self.get_inner().get();
325            match ptr {
326                Some(signal) => *signal,
327                None => panic!("SignalCell::get called on an uninitialized cell"),
328            }
329        }
330    }
331}
332
333/// Provides a default empty `SignalCell`.
334///
335/// Creates a `SignalCell` with `None` stored in the inner `UnsafeCell`.
336///
337/// # Returns
338///
339/// - `Self`: An empty `SignalCell` with no signal stored.
340impl<T> Default for SignalCell<T>
341where
342    T: Clone + PartialEq + 'static,
343{
344    fn default() -> Self {
345        Self {
346            inner: UnsafeCell::new(None),
347        }
348    }
349}
350
351/// Marks `SignalInnerRegistryCell` as `Sync` for single-threaded WASM contexts.
352///
353/// SAFETY: `SignalInnerRegistryCell` is only used in single-threaded WASM contexts.
354/// Concurrent access from multiple threads would be undefined behavior.
355unsafe impl Sync for SignalInnerRegistryCell {}