euv_core/reactive/signal/impl.rs
1use crate::*;
2
3/// Implementation of reactive signal operations.
4impl<T> Signal<T>
5where
6 T: Clone + PartialEq + 'static,
7{
8 /// Creates a new `Signal` with the given initial value.
9 ///
10 /// # Arguments
11 ///
12 /// - `T` - The initial value of the signal.
13 ///
14 /// # Returns
15 ///
16 /// - `Self` - A handle to the newly created reactive signal.
17 pub fn create(value: T) -> Self {
18 let signal_inner: Rc<RefCell<SignalInner<T>>> =
19 Rc::new(RefCell::new(SignalInner::new(value, Vec::new(), true)));
20 let addr: usize = Rc::as_ptr(&signal_inner) as usize;
21 signal_inner_registry_mut().insert(addr, signal_inner as Rc<dyn Any>);
22 let mut signal: Self = Self::new(0, std::marker::PhantomData);
23 signal.set_inner(addr);
24 signal
25 }
26
27 /// Returns a reference to the inner `RefCell` for this signal.
28 ///
29 /// # Returns
30 ///
31 /// - `&'static RefCell<SignalInner<T>>` - A reference to the inner state.
32 fn inner_ref(&self) -> &'static RefCell<SignalInner<T>> {
33 get_signal_inner_ref(self.get_inner())
34 }
35
36 /// Returns the current value of the signal.
37 ///
38 /// Uses `try_borrow` to avoid panicking when the inner `RefCell` is
39 /// already mutably borrowed (e.g., during a `set()` notification cycle).
40 /// In that case, falls back to an unsafe direct read of the value,
41 /// which is safe in single-threaded WASM contexts because the value
42 /// itself is not being mutated at the point of read (only the `RefCell`
43 /// borrow flag is contested).
44 ///
45 /// If a tracking context is active (i.e., a DynamicNode is being rendered),
46 /// automatically registers the current dynamic node as a dependent of
47 /// this signal for precise reactive updates.
48 ///
49 /// # Returns
50 ///
51 /// - `T` - The current value of the signal.
52 pub fn get(&self) -> T {
53 let tracking_id: usize = CURRENT_TRACKING_DYNAMIC_ID.load(Ordering::Relaxed);
54 if tracking_id != usize::MAX {
55 self.add_dependent(tracking_id);
56 }
57 let inner_ref: &RefCell<SignalInner<T>> = self.inner_ref();
58 let Ok(inner) = inner_ref.try_borrow() else {
59 return unsafe { (*inner_ref.as_ptr()).get_value().clone() };
60 };
61 inner.get_value().clone()
62 }
63
64 /// Subscribes a callback to be invoked when the signal changes.
65 ///
66 /// If the inner `RefCell` is already borrowed, this is a no-op to avoid
67 /// panicking during re-entrant signal updates.
68 ///
69 /// # Arguments
70 ///
71 /// - `FnMut() + 'static` - The callback to invoke when the signal changes.
72 pub fn subscribe<F>(&self, callback: F)
73 where
74 F: FnMut() + 'static,
75 {
76 if let Ok(mut inner) = self.inner_ref().try_borrow_mut() {
77 inner.get_mut_listeners().push(Box::new(callback));
78 }
79 }
80
81 /// Replaces all listeners with a single new callback.
82 ///
83 /// Unlike `subscribe`, which appends a listener, this method clears any
84 /// existing listeners first and then adds the new one. If the inner
85 /// `RefCell` is already borrowed, this is a no-op.
86 ///
87 /// # Arguments
88 ///
89 /// - `FnMut() + 'static` - The callback to invoke when the signal changes.
90 pub(crate) fn replace_subscribe<F>(&self, callback: F)
91 where
92 F: FnMut() + 'static,
93 {
94 if let Ok(mut inner) = self.inner_ref().try_borrow_mut() {
95 let listeners: &mut Vec<Box<dyn FnMut()>> = inner.get_mut_listeners();
96 listeners.clear();
97 listeners.push(Box::new(callback));
98 }
99 }
100
101 /// Removes all subscribed listeners from this signal, clears its
102 /// dependent dynamic node list, and marks it as inactive.
103 /// If the inner `RefCell` is already borrowed, this is a no-op.
104 ///
105 /// NOTE: Does NOT remove from `SIGNAL_INNER_REGISTRY`. The registry
106 /// must keep the `Rc` alive because `Signal` is `Copy` and other copies
107 /// may still hold the raw address. Removing would cause use-after-free.
108 pub(crate) fn clear_listeners(&self) {
109 if let Ok(mut inner) = self.inner_ref().try_borrow_mut() {
110 inner.set_alive(false);
111 inner.get_mut_listeners().clear();
112 inner.get_mut_dependents().clear();
113 }
114 }
115
116 /// Core implementation of value update and listener notification.
117 ///
118 /// Returns `true` if the value was updated and listeners were notified.
119 /// Returns `false` if the inner `RefCell` is already mutably borrowed
120 /// (re-entrant access), the signal is inactive, or the value is unchanged.
121 fn update_and_notify(&self, value: T) -> bool {
122 let inner_ref: &RefCell<SignalInner<T>> = self.inner_ref();
123 let mut listeners: Vec<Box<dyn FnMut()>> = Vec::new();
124 let Ok(mut inner) = inner_ref.try_borrow_mut() else {
125 return false;
126 };
127 if !inner.get_alive() {
128 return false;
129 }
130 if *inner.get_value() == value {
131 return false;
132 }
133 inner.set_value(value);
134 swap(inner.get_mut_listeners(), &mut listeners);
135 drop(inner);
136 for mut listener in listeners {
137 listener();
138 if let Ok(mut inner) = inner_ref.try_borrow_mut() {
139 inner.get_mut_listeners().push(listener);
140 }
141 }
142 true
143 }
144
145 /// Registers a dynamic node ID as a dependent of this signal.
146 ///
147 /// When this signal changes, only its registered dependents will be
148 /// marked dirty for re-rendering, enabling precise updates instead
149 /// of broadcasting to all dynamic nodes.
150 ///
151 /// # Arguments
152 ///
153 /// - `usize` - The dynamic node ID to register as a dependent.
154 pub(crate) fn add_dependent(&self, dynamic_id: usize) {
155 if let Ok(mut inner) = self.inner_ref().try_borrow_mut() {
156 let deps: &mut Vec<usize> = inner.get_mut_dependents();
157 if !deps.contains(&dynamic_id) {
158 deps.push(dynamic_id);
159 }
160 }
161 }
162
163 /// Removes a dynamic node ID from the dependents list of this signal.
164 ///
165 /// Called during cleanup when a dynamic node is removed from the DOM
166 /// and its dependency relationships need to be severed.
167 ///
168 /// # Arguments
169 ///
170 /// - `usize` - The dynamic node ID to remove.
171 #[allow(dead_code)]
172 pub(crate) fn remove_dependent(&self, dynamic_id: usize) {
173 if let Ok(mut inner) = self.inner_ref().try_borrow_mut() {
174 inner.get_mut_dependents().retain(|id| *id != dynamic_id);
175 }
176 }
177
178 /// Returns the list of dependent dynamic node IDs for this signal.
179 ///
180 /// # Returns
181 ///
182 /// - `Vec<usize>` - Clone of the dependents list.
183 pub(crate) fn get_dependents(&self) -> Vec<usize> {
184 if let Ok(inner) = self.inner_ref().try_borrow() {
185 inner.get_dependents().clone()
186 } else {
187 Vec::new()
188 }
189 }
190
191 /// Sets the value of the signal and notifies listeners.
192 ///
193 /// Uses precise dirty marking: only dynamic nodes that depend on
194 /// this signal are marked dirty, avoiding full broadcast.
195 ///
196 /// # Arguments
197 ///
198 /// - `T` - The new value to assign to the signal.
199 pub fn set(&self, value: T) {
200 if self.update_and_notify(value) {
201 let dependents: Vec<usize> = self.get_dependents();
202 schedule_signal_update_targeted(&dependents);
203 }
204 }
205
206 /// Sets the value of the signal and notifies listeners without scheduling
207 /// a global DOM update dispatch.
208 ///
209 /// # Arguments
210 ///
211 /// - `T` - The new value to assign to the signal.
212 pub fn set_silent(&self, value: T) {
213 self.update_and_notify(value);
214 }
215
216 /// Sets the value of the signal without notifying listeners or scheduling
217 /// a DOM update. This is useful for breaking circular watch dependencies
218 /// where two signals watch each other and would otherwise recurse infinitely.
219 /// If the inner `RefCell` is already borrowed, this is a no-op.
220 ///
221 /// # Arguments
222 ///
223 /// - `T` - The new value to assign to the signal.
224 pub fn set_untracked(&self, value: T) {
225 let inner_ref: &RefCell<SignalInner<T>> = self.inner_ref();
226 if let Ok(mut inner) = inner_ref.try_borrow_mut() {
227 inner.set_value(value);
228 }
229 }
230}
231
232/// Provides a safe default for `Signal<T>` by creating a valid signal
233/// initialized with `T::default()`.
234///
235/// This prevents the creation of invalid signals with `inner = 0` (null
236/// pointer), which would cause a panic when `.get()` is called.
237///
238/// # Returns
239///
240/// - `Self`: A valid signal initialized with `T::default()`.
241impl<T> Default for Signal<T>
242where
243 T: Clone + Default + PartialEq + 'static,
244{
245 fn default() -> Self {
246 Self::create(T::default())
247 }
248}
249
250/// Clones the signal, sharing the same inner state.
251///
252/// Since `Signal` is `Copy`, this simply returns `*self`.
253///
254/// # Returns
255///
256/// - `Self`: A copy of the signal handle sharing the same inner state.
257impl<T> Clone for Signal<T>
258where
259 T: Clone + PartialEq + 'static,
260{
261 fn clone(&self) -> Self {
262 *self
263 }
264}
265
266/// Copies the signal, sharing the same inner state.
267///
268/// Safe because only the inner address (a `usize`) is copied;
269/// the actual `Rc` reference is held by the global signal registry.
270impl<T> Copy for Signal<T> where T: Clone + PartialEq + 'static {}
271
272/// Marks `SignalCell` as `Sync` for single-threaded WASM contexts.
273///
274/// SAFETY: `SignalCell` is only used in single-threaded WASM contexts.
275/// Concurrent access from multiple threads would be undefined behavior.
276unsafe impl<T> Sync for SignalCell<T> where T: Clone + PartialEq + 'static {}
277
278/// Implementation of SignalCell construction and access.
279impl<T> SignalCell<T>
280where
281 T: Clone + PartialEq + 'static,
282{
283 /// Creates a new `SignalCell` with no signal stored.
284 ///
285 /// # Returns
286 ///
287 /// - `Self`: An empty `SignalCell` with `None` stored in the inner `UnsafeCell`.
288 pub const fn none() -> Self {
289 Self {
290 inner: UnsafeCell::new(None),
291 }
292 }
293
294 /// Stores a signal into the cell.
295 ///
296 /// # Arguments
297 ///
298 /// - `Signal<T>` - The signal to store.
299 ///
300 /// # Panics
301 ///
302 /// Panics if a signal has already been stored.
303 pub fn set(&self, signal: Signal<T>) {
304 unsafe {
305 let ptr: &mut Option<Signal<T>> = &mut *self.get_inner().get();
306 if ptr.is_some() {
307 panic!("SignalCell::set called on an already-initialized cell");
308 }
309 *ptr = Some(signal);
310 }
311 }
312
313 /// Returns the signal stored in the cell.
314 ///
315 /// # Returns
316 ///
317 /// - `Signal<T>` - The stored signal.
318 ///
319 /// # Panics
320 ///
321 /// Panics if no signal has been stored via `set`.
322 pub fn get(&self) -> Signal<T> {
323 unsafe {
324 let ptr: &Option<Signal<T>> = &*self.get_inner().get();
325 match ptr {
326 Some(signal) => *signal,
327 None => panic!("SignalCell::get called on an uninitialized cell"),
328 }
329 }
330 }
331}
332
333/// Provides a default empty `SignalCell`.
334///
335/// Creates a `SignalCell` with `None` stored in the inner `UnsafeCell`.
336///
337/// # Returns
338///
339/// - `Self`: An empty `SignalCell` with no signal stored.
340impl<T> Default for SignalCell<T>
341where
342 T: Clone + PartialEq + 'static,
343{
344 fn default() -> Self {
345 Self {
346 inner: UnsafeCell::new(None),
347 }
348 }
349}
350
351/// Marks `SignalInnerRegistryCell` as `Sync` for single-threaded WASM contexts.
352///
353/// SAFETY: `SignalInnerRegistryCell` is only used in single-threaded WASM contexts.
354/// Concurrent access from multiple threads would be undefined behavior.
355unsafe impl Sync for SignalInnerRegistryCell {}