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