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