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