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