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.get_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 signal inner 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    pub(crate) fn leak_mut(&self) -> &'static mut SignalInner<T> {
156        let address: usize = (*self).into();
157        address.into()
158    }
159
160    /// Returns the current value of the signal.
161    ///
162    /// # Returns
163    ///
164    /// - `T` - The current value of the signal.
165    pub fn get(&self) -> T {
166        self.leak_mut().get_value().clone()
167    }
168
169    /// Attempts to return the current value of the signal without panicking.
170    ///
171    /// # Returns
172    ///
173    /// - `Some(T)` - The current value if the borrow succeeds.
174    /// - `None` - If the inner value is already mutably borrowed.
175    pub fn try_get(&self) -> Option<T> {
176        Some(self.leak_mut().get_value().clone())
177    }
178
179    /// Subscribes a callback to be invoked when the signal changes.
180    ///
181    /// # Arguments
182    ///
183    /// - `FnMut() + 'static` - The callback to invoke when the signal changes.
184    pub fn subscribe<F>(&self, callback: F)
185    where
186        F: FnMut() + 'static,
187    {
188        self.leak_mut().get_mut_listeners().push(Box::new(callback));
189    }
190
191    /// Replaces all listeners with a single new callback.
192    ///
193    /// Unlike `subscribe`, which appends a listener, this method clears any
194    /// existing listeners first and then adds the new one. This prevents
195    /// listener accumulation across re-renders: each signal is guaranteed
196    /// to have at most one active listener at any time, eliminating
197    /// cascading `set()` calls that would otherwise grow exponentially.
198    ///
199    /// # Arguments
200    ///
201    /// - `FnMut() + 'static` - The callback to invoke when the signal changes.
202    pub fn replace_subscribe<F>(&self, callback: F)
203    where
204        F: FnMut() + 'static,
205    {
206        let listeners: &mut Vec<Box<dyn FnMut()>> = self.leak_mut().get_mut_listeners();
207        listeners.clear();
208        listeners.push(Box::new(callback));
209    }
210
211    /// Removes all subscribed listeners from this signal and marks it as
212    /// inactive. After calling this method, subsequent `set()` and
213    /// `try_set()` calls become complete no-ops: the value is not updated,
214    /// no listeners are invoked, and `schedule_signal_update()` is not
215    /// called. This is used during hook context cleanup when a `match`
216    /// arm switch discards old signals, ensuring that stale `setInterval`
217    /// closures referencing these signals become entirely harmless.
218    pub fn clear_listeners(&self) {
219        let inner: &mut SignalInner<T> = self.leak_mut();
220        inner.set_alive(false);
221        inner.get_mut_listeners().clear();
222    }
223
224    /// Sets the value of the signal and notifies listeners.
225    ///
226    /// If the signal has been marked as inactive (via `clear_listeners()`),
227    /// this method is a complete no-op: the value is not updated, no
228    /// listeners are invoked, and no global update is scheduled.
229    ///
230    /// If the new value is equal to the current value, no update is performed
231    /// and no listeners are notified. This prevents unnecessary re-renders and
232    /// avoids cascading no-op updates through intermediate signal chains.
233    ///
234    /// # Arguments
235    ///
236    /// - `T` - The new value to assign to the signal.
237    pub fn set(&self, value: T) {
238        let inner: &mut SignalInner<T> = self.leak_mut();
239        if !inner.get_alive() {
240            return;
241        }
242        if inner.get_value() == &value {
243            return;
244        }
245        inner.set_value(value);
246        for listener in inner.get_mut_listeners().iter_mut() {
247            listener();
248        }
249        schedule_signal_update();
250    }
251
252    /// Sets the value of the signal and notifies listeners without scheduling
253    /// a global DOM update dispatch.
254    ///
255    /// This is identical to `set` except it does not call
256    /// `schedule_signal_update()`, meaning no `__euv_signal_update__` event
257    /// will be dispatched. Use this for internal bookkeeping signals whose
258    /// changes should not trigger DynamicNode re-renders.
259    ///
260    /// If the new value is equal to the current value, no update is performed
261    /// and no listeners are notified.
262    ///
263    /// If the signal has been marked as inactive (via `clear_listeners()`),
264    /// this method is a complete no-op.
265    ///
266    /// # Arguments
267    ///
268    /// - `T` - The new value to assign to the signal.
269    pub fn set_silent(&self, value: T) {
270        let inner: &mut SignalInner<T> = self.leak_mut();
271        if !inner.get_alive() {
272            return;
273        }
274        if inner.get_value() == &value {
275            return;
276        }
277        inner.set_value(value);
278        for listener in inner.get_mut_listeners().iter_mut() {
279            listener();
280        }
281    }
282
283    /// Attempts to set the value of the signal and notify listeners without panicking.
284    ///
285    /// If the new value is equal to the current value, no update is performed.
286    ///
287    /// # Arguments
288    ///
289    /// - `T` - The new value to assign to the signal.
290    ///
291    /// # Returns
292    ///
293    /// - `bool` - `true` if the value was successfully updated and listeners were notified, `false` if unchanged or inactive.
294    pub fn try_set(&self, value: T) -> bool {
295        let inner: &mut SignalInner<T> = self.leak_mut();
296        if !inner.get_alive() {
297            return false;
298        }
299        if inner.get_value() == &value {
300            return false;
301        }
302        inner.set_value(value);
303        for listener in inner.get_mut_listeners().iter_mut() {
304            listener();
305        }
306        schedule_signal_update();
307        true
308    }
309}
310
311/// Prevents direct dereference of a signal to enforce explicit API usage.
312impl<T> Deref for Signal<T>
313where
314    T: Clone + PartialEq + 'static,
315{
316    type Target = T;
317
318    /// Panics with a message directing the caller to use `.get()` instead.
319    fn deref(&self) -> &Self::Target {
320        panic!("Signal does not support direct dereference; use .get() instead");
321    }
322}
323
324/// Prevents direct mutable dereference of a signal to enforce explicit API usage.
325impl<T> DerefMut for Signal<T>
326where
327    T: Clone + PartialEq + 'static,
328{
329    /// Panics with a message directing the caller to use `.set()` instead.
330    fn deref_mut(&mut self) -> &mut Self::Target {
331        panic!("Signal does not support direct dereference; use .set() instead");
332    }
333}
334
335/// Clones the signal, sharing the same inner state.
336impl<T> Clone for Signal<T>
337where
338    T: Clone + PartialEq + 'static,
339{
340    /// Returns a bitwise copy of this signal.
341    fn clone(&self) -> Self {
342        *self
343    }
344}
345
346/// Copies the signal, sharing the same inner state.
347///
348/// A `Signal` is just a raw pointer; copying it is a trivial bitwise copy.
349impl<T> Copy for Signal<T> where T: Clone + PartialEq + 'static {}
350
351/// SAFETY: `SignalCell` is only used in single-threaded WASM contexts.
352/// Concurrent access from multiple threads would be undefined behavior.
353unsafe impl<T> Sync for SignalCell<T> where T: Clone + PartialEq + 'static {}
354
355/// Implementation of SignalCell construction and access.
356impl<T> SignalCell<T>
357where
358    T: Clone + PartialEq + 'static,
359{
360    /// Creates a new empty `SignalCell` with no signal stored.
361    ///
362    /// # Returns
363    ///
364    /// - `Self` - An empty cell ready to hold a signal.
365    pub const fn new() -> Self {
366        SignalCell {
367            inner: UnsafeCell::new(None),
368        }
369    }
370
371    /// Returns a raw pointer to the inner `Option<Signal<T>>`.
372    ///
373    /// # Returns
374    ///
375    /// - `*mut Option<Signal<T>>` - The raw pointer to the inner option.
376    pub(crate) fn get_inner(&self) -> *mut Option<Signal<T>> {
377        self.inner.get()
378    }
379
380    /// Stores a signal into the cell.
381    ///
382    /// # Arguments
383    ///
384    /// - `Signal<T>` - The signal to store.
385    ///
386    /// # Panics
387    ///
388    /// Panics if a signal has already been stored.
389    pub fn set(&self, signal: Signal<T>) {
390        unsafe {
391            let ptr: &mut Option<Signal<T>> = &mut *self.get_inner();
392            if ptr.is_some() {
393                panic!("SignalCell::set called on an already-initialized cell");
394            }
395            *ptr = Some(signal);
396        }
397    }
398
399    /// Returns the signal stored in the cell.
400    ///
401    /// # Returns
402    ///
403    /// - `Signal<T>` - The stored signal.
404    ///
405    /// # Panics
406    ///
407    /// Panics if no signal has been stored via `set`.
408    pub fn get(&self) -> Signal<T> {
409        unsafe {
410            let ptr: &Option<Signal<T>> = &*self.get_inner();
411            match ptr {
412                Some(signal) => *signal,
413                None => panic!("SignalCell::get called on an uninitialized cell"),
414            }
415        }
416    }
417}
418
419/// Provides a default empty `SignalCell`.
420impl<T> Default for SignalCell<T>
421where
422    T: Clone + PartialEq + 'static,
423{
424    /// Returns a default `SignalCell` by delegating to `SignalCell::new`.
425    fn default() -> Self {
426        Self::new()
427    }
428}