Skip to main content

euv_core/reactive/signal/
impl.rs

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