Skip to main content

euv_core/reactive/signal/
impl.rs

1use crate::*;
2
3/// Implementation of SignalInner construction.
4impl<T> SignalInner<T>
5where
6    T: Clone,
7{
8    /// Creates a new signal inner with the given initial value and no listeners.
9    ///
10    /// # Arguments
11    ///
12    /// - `T` - The initial value of the signal inner.
13    ///
14    /// # Returns
15    ///
16    /// - `Self` - A new signal inner with the given value and empty listeners.
17    pub fn new(value: T) -> Self {
18        let inner: SignalInner<T> = SignalInner {
19            value,
20            listeners: Vec::new(),
21            alive: true,
22        };
23        inner
24    }
25}
26
27/// Implementation of reactive signal operations.
28impl<T> Signal<T>
29where
30    T: Clone + PartialEq,
31{
32    /// Creates a new `Signal` with the given initial value.
33    ///
34    /// The inner state is allocated via `Box::leak` and lives for the
35    /// remainder of the program. This is safe in single-threaded WASM
36    /// contexts where no concurrent access can occur.
37    ///
38    /// # Arguments
39    ///
40    /// - `T` - The initial value of the signal.
41    ///
42    /// # Returns
43    ///
44    /// - `Self` - A handle to the newly created reactive signal.
45    pub fn new(value: T) -> Self {
46        let boxed: Box<SignalInner<T>> = Box::new(SignalInner::new(value));
47        Signal {
48            inner: Box::leak(boxed) as *mut SignalInner<T>,
49        }
50    }
51
52    /// Creates a new `Signal` from an existing raw pointer.
53    ///
54    /// # Arguments
55    ///
56    /// - `*mut SignalInner<T>` - A raw pointer to the heap-allocated signal inner state.
57    ///
58    /// # Returns
59    ///
60    /// - `Self` - A signal handle wrapping the given pointer.
61    ///
62    /// # Safety
63    ///
64    /// The caller must ensure the pointer was allocated via `Box::leak`
65    /// and remains valid for the entire program lifetime.
66    pub(crate) fn from_inner(inner: *mut SignalInner<T>) -> Self {
67        Signal { inner }
68    }
69
70    /// Returns the raw pointer to the inner signal state.
71    ///
72    /// # Returns
73    ///
74    /// - `*mut SignalInner<T>` - The raw pointer to the inner signal state.
75    pub(crate) fn get_inner(&self) -> *mut SignalInner<T> {
76        self.inner
77    }
78
79    /// Returns a mutable reference to the inner signal state.
80    ///
81    /// # Returns
82    ///
83    /// - `&mut SignalInner<T>` - A mutable reference to the inner signal state.
84    ///
85    /// # Safety
86    ///
87    /// The caller must ensure no other references to the inner state exist.
88    /// In single-threaded WASM this is always safe.
89    #[allow(clippy::mut_from_ref)]
90    fn get_inner_mut(&self) -> &mut SignalInner<T> {
91        unsafe { &mut *self.get_inner() }
92    }
93
94    /// Returns the current value of the signal.
95    ///
96    /// # Returns
97    ///
98    /// - `T` - The current value of the signal.
99    pub fn get(&self) -> T {
100        self.get_inner_mut().get_value().clone()
101    }
102
103    /// Attempts to return the current value of the signal without panicking.
104    ///
105    /// # Returns
106    ///
107    /// - `Some(T)` - The current value if the borrow succeeds.
108    /// - `None` - If the inner value is already mutably borrowed.
109    pub fn try_get(&self) -> Option<T> {
110        Some(self.get_inner_mut().get_value().clone())
111    }
112
113    /// Subscribes a callback to be invoked when the signal changes.
114    ///
115    /// # Arguments
116    ///
117    /// - `FnMut() + 'static` - The callback to invoke when the signal changes.
118    pub fn subscribe<F>(&self, callback: F)
119    where
120        F: FnMut() + 'static,
121    {
122        self.get_inner_mut()
123            .get_mut_listeners()
124            .push(Box::new(callback));
125    }
126
127    /// Replaces all listeners with a single new callback.
128    ///
129    /// Unlike `subscribe`, which appends a listener, this method clears any
130    /// existing listeners first and then adds the new one. This prevents
131    /// listener accumulation across re-renders: each signal is guaranteed
132    /// to have at most one active listener at any time, eliminating
133    /// cascading `set()` calls that would otherwise grow exponentially.
134    ///
135    /// # Arguments
136    ///
137    /// - `FnMut() + 'static` - The callback to invoke when the signal changes.
138    pub fn replace_subscribe<F>(&self, callback: F)
139    where
140        F: FnMut() + 'static,
141    {
142        let listeners: &mut Vec<Box<dyn FnMut()>> = self.get_inner_mut().get_mut_listeners();
143        listeners.clear();
144        listeners.push(Box::new(callback));
145    }
146
147    /// Removes all subscribed listeners from this signal and marks it as
148    /// inactive. After calling this method, subsequent `set()` and
149    /// `try_set()` calls become complete no-ops: the value is not updated,
150    /// no listeners are invoked, and `schedule_signal_update()` is not
151    /// called. This is used during hook context cleanup when a `match`
152    /// arm switch discards old signals, ensuring that stale `setInterval`
153    /// closures referencing these signals become entirely harmless.
154    pub fn clear_listeners(&self) {
155        let inner: &mut SignalInner<T> = self.get_inner_mut();
156        inner.set_alive(false);
157        inner.get_mut_listeners().clear();
158    }
159
160    /// Sets the value of the signal and notifies listeners.
161    ///
162    /// If the signal has been marked as inactive (via `clear_listeners()`),
163    /// this method is a complete no-op: the value is not updated, no
164    /// listeners are invoked, and no global update is scheduled.
165    ///
166    /// If the new value is equal to the current value, no update is performed
167    /// and no listeners are notified. This prevents unnecessary re-renders and
168    /// avoids cascading no-op updates through intermediate signal chains.
169    ///
170    /// # Arguments
171    ///
172    /// - `T` - The new value to assign to the signal.
173    pub fn set(&self, value: T) {
174        let inner: &mut SignalInner<T> = self.get_inner_mut();
175        if !inner.get_alive() {
176            return;
177        }
178        if inner.get_value() == &value {
179            return;
180        }
181        inner.set_value(value);
182        for listener in inner.get_mut_listeners().iter_mut() {
183            listener();
184        }
185        schedule_signal_update();
186    }
187
188    /// Sets the value of the signal and notifies listeners without scheduling
189    /// a global DOM update dispatch.
190    ///
191    /// This is identical to `set` except it does not call
192    /// `schedule_signal_update()`, meaning no `__euv_signal_update__` event
193    /// will be dispatched. Use this for internal bookkeeping signals whose
194    /// changes should not trigger DynamicNode re-renders.
195    ///
196    /// If the new value is equal to the current value, no update is performed
197    /// and no listeners are notified.
198    ///
199    /// If the signal has been marked as inactive (via `clear_listeners()`),
200    /// this method is a complete no-op.
201    ///
202    /// # Arguments
203    ///
204    /// - `T` - The new value to assign to the signal.
205    pub fn set_silent(&self, value: T) {
206        let inner: &mut SignalInner<T> = self.get_inner_mut();
207        if !inner.get_alive() {
208            return;
209        }
210        if inner.get_value() == &value {
211            return;
212        }
213        inner.set_value(value);
214        for listener in inner.get_mut_listeners().iter_mut() {
215            listener();
216        }
217    }
218
219    /// Attempts to set the value of the signal and notify listeners without panicking.
220    ///
221    /// If the new value is equal to the current value, no update is performed.
222    ///
223    /// # Arguments
224    ///
225    /// - `T` - The new value to assign to the signal.
226    ///
227    /// # Returns
228    ///
229    /// - `bool` - `true` if the value was successfully updated and listeners were notified, `false` if unchanged or inactive.
230    pub fn try_set(&self, value: T) -> bool {
231        let inner: &mut SignalInner<T> = self.get_inner_mut();
232        if !inner.get_alive() {
233            return false;
234        }
235        if inner.get_value() == &value {
236            return false;
237        }
238        inner.set_value(value);
239        for listener in inner.get_mut_listeners().iter_mut() {
240            listener();
241        }
242        schedule_signal_update();
243        true
244    }
245}
246
247/// Prevents direct dereference of a signal to enforce explicit API usage.
248impl<T> Deref for Signal<T>
249where
250    T: Clone + PartialEq,
251{
252    type Target = T;
253
254    /// Panics with a message directing the caller to use `.get()` instead.
255    fn deref(&self) -> &Self::Target {
256        panic!("Signal does not support direct dereference; use .get() instead");
257    }
258}
259
260/// Prevents direct mutable dereference of a signal to enforce explicit API usage.
261impl<T> DerefMut for Signal<T>
262where
263    T: Clone + PartialEq,
264{
265    /// Panics with a message directing the caller to use `.set()` instead.
266    fn deref_mut(&mut self) -> &mut Self::Target {
267        panic!("Signal does not support direct dereference; use .set() instead");
268    }
269}
270
271/// Clones the signal, sharing the same inner state.
272impl<T> Clone for Signal<T>
273where
274    T: Clone + PartialEq,
275{
276    /// Returns a bitwise copy of this signal.
277    fn clone(&self) -> Self {
278        *self
279    }
280}
281
282/// Copies the signal, sharing the same inner state.
283///
284/// A `Signal` is just a raw pointer; copying it is a trivial bitwise copy.
285impl<T> Copy for Signal<T> where T: Clone + PartialEq {}
286
287/// SAFETY: `SignalCell` is only used in single-threaded WASM contexts.
288/// Concurrent access from multiple threads would be undefined behavior.
289unsafe impl<T> Sync for SignalCell<T> where T: Clone + PartialEq {}
290
291/// Implementation of SignalCell construction and access.
292impl<T> SignalCell<T>
293where
294    T: Clone + PartialEq,
295{
296    /// Creates a new empty `SignalCell` with no signal stored.
297    ///
298    /// # Returns
299    ///
300    /// - `Self` - An empty cell ready to hold a signal.
301    pub const fn new() -> Self {
302        SignalCell {
303            inner: UnsafeCell::new(None),
304        }
305    }
306
307    /// Returns a raw pointer to the inner `Option<Signal<T>>`.
308    ///
309    /// # Returns
310    ///
311    /// - `*mut Option<Signal<T>>` - The raw pointer to the inner option.
312    pub(crate) fn get_inner(&self) -> *mut Option<Signal<T>> {
313        self.inner.get()
314    }
315
316    /// Stores a signal into the cell.
317    ///
318    /// # Arguments
319    ///
320    /// - `Signal<T>` - The signal to store.
321    ///
322    /// # Panics
323    ///
324    /// Panics if a signal has already been stored.
325    pub fn set(&self, signal: Signal<T>) {
326        unsafe {
327            let ptr: &mut Option<Signal<T>> = &mut *self.get_inner();
328            if ptr.is_some() {
329                panic!("SignalCell::set called on an already-initialized cell");
330            }
331            *ptr = Some(signal);
332        }
333    }
334
335    /// Returns the signal stored in the cell.
336    ///
337    /// # Returns
338    ///
339    /// - `Signal<T>` - The stored signal.
340    ///
341    /// # Panics
342    ///
343    /// Panics if no signal has been stored via `set`.
344    pub fn get(&self) -> Signal<T> {
345        unsafe {
346            let ptr: &Option<Signal<T>> = &*self.get_inner();
347            match ptr {
348                Some(signal) => *signal,
349                None => panic!("SignalCell::get called on an uninitialized cell"),
350            }
351        }
352    }
353}
354
355/// Provides a default empty `SignalCell`.
356impl<T> Default for SignalCell<T>
357where
358    T: Clone + PartialEq,
359{
360    /// Returns a default `SignalCell` by delegating to `SignalCell::new`.
361    fn default() -> Self {
362        Self::new()
363    }
364}