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}