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