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 /// # Returns
46 ///
47 /// - `T` - The current value of the signal.
48 pub fn get(&self) -> T {
49 let inner_ref: &RefCell<SignalInner<T>> = self.inner_ref();
50 let Ok(inner) = inner_ref.try_borrow() else {
51 return unsafe { (*inner_ref.as_ptr()).get_value().clone() };
52 };
53 inner.get_value().clone()
54 }
55
56 /// Subscribes a callback to be invoked when the signal changes.
57 ///
58 /// If the inner `RefCell` is already borrowed, this is a no-op to avoid
59 /// panicking during re-entrant signal updates.
60 ///
61 /// # Arguments
62 ///
63 /// - `FnMut() + 'static` - The callback to invoke when the signal changes.
64 pub fn subscribe<F>(&self, callback: F)
65 where
66 F: FnMut() + 'static,
67 {
68 if let Ok(mut inner) = self.inner_ref().try_borrow_mut() {
69 inner.get_mut_listeners().push(Box::new(callback));
70 }
71 }
72
73 /// Replaces all listeners with a single new callback.
74 ///
75 /// Unlike `subscribe`, which appends a listener, this method clears any
76 /// existing listeners first and then adds the new one. If the inner
77 /// `RefCell` is already borrowed, this is a no-op.
78 ///
79 /// # Arguments
80 ///
81 /// - `FnMut() + 'static` - The callback to invoke when the signal changes.
82 pub(crate) fn replace_subscribe<F>(&self, callback: F)
83 where
84 F: FnMut() + 'static,
85 {
86 if let Ok(mut inner) = self.inner_ref().try_borrow_mut() {
87 let listeners: &mut Vec<Box<dyn FnMut()>> = inner.get_mut_listeners();
88 listeners.clear();
89 listeners.push(Box::new(callback));
90 }
91 }
92
93 /// Removes all subscribed listeners from this signal and marks it as
94 /// inactive. If the inner `RefCell` is already borrowed, this is a no-op.
95 pub(crate) fn clear_listeners(&self) {
96 if let Ok(mut inner) = self.inner_ref().try_borrow_mut() {
97 inner.set_alive(false);
98 inner.get_mut_listeners().clear();
99 }
100 }
101
102 /// Core implementation of value update and listener notification.
103 ///
104 /// Returns `true` if the value was updated and listeners were notified.
105 /// Returns `false` if the inner `RefCell` is already mutably borrowed
106 /// (re-entrant access), the signal is inactive, or the value is unchanged.
107 fn update_and_notify(&self, value: T) -> bool {
108 let inner_ref: &RefCell<SignalInner<T>> = self.inner_ref();
109 let mut listeners: Vec<Box<dyn FnMut()>> = Vec::new();
110 let Ok(mut inner) = inner_ref.try_borrow_mut() else {
111 return false;
112 };
113 if !inner.get_alive() {
114 return false;
115 }
116 if *inner.get_value() == value {
117 return false;
118 }
119 inner.set_value(value);
120 swap(inner.get_mut_listeners(), &mut listeners);
121 for listener in listeners.iter_mut() {
122 listener();
123 }
124 if let Ok(mut inner) = inner_ref.try_borrow_mut() {
125 swap(inner.get_mut_listeners(), &mut listeners);
126 }
127 true
128 }
129
130 /// Sets the value of the signal and notifies listeners.
131 ///
132 /// # Arguments
133 ///
134 /// - `T` - The new value to assign to the signal.
135 pub fn set(&self, value: T) {
136 if self.update_and_notify(value) {
137 schedule_signal_update();
138 }
139 }
140
141 /// Sets the value of the signal and notifies listeners without scheduling
142 /// a global DOM update dispatch.
143 ///
144 /// # Arguments
145 ///
146 /// - `T` - The new value to assign to the signal.
147 pub fn set_silent(&self, value: T) {
148 self.update_and_notify(value);
149 }
150
151 /// Sets the value of the signal without notifying listeners or scheduling
152 /// a DOM update. This is useful for breaking circular watch dependencies
153 /// where two signals watch each other and would otherwise recurse infinitely.
154 /// If the inner `RefCell` is already borrowed, this is a no-op.
155 ///
156 /// # Arguments
157 ///
158 /// - `T` - The new value to assign to the signal.
159 pub fn set_untracked(&self, value: T) {
160 let inner_ref: &RefCell<SignalInner<T>> = self.inner_ref();
161 if let Ok(mut inner) = inner_ref.try_borrow_mut() {
162 inner.set_value(value);
163 }
164 }
165}
166
167/// Clones the signal, sharing the same inner state.
168///
169/// Since `Signal` is `Copy`, this simply returns `*self`.
170///
171/// # Returns
172///
173/// - `Self`: A copy of the signal handle sharing the same inner state.
174impl<T> Clone for Signal<T>
175where
176 T: Clone + PartialEq + 'static,
177{
178 fn clone(&self) -> Self {
179 *self
180 }
181}
182
183/// Copies the signal, sharing the same inner state.
184///
185/// Safe because only the inner address (a `usize`) is copied;
186/// the actual `Rc` reference is held by the global signal registry.
187impl<T> Copy for Signal<T> where T: Clone + PartialEq + 'static {}
188
189/// Marks `SignalCell` as `Sync` for single-threaded WASM contexts.
190///
191/// SAFETY: `SignalCell` is only used in single-threaded WASM contexts.
192/// Concurrent access from multiple threads would be undefined behavior.
193unsafe impl<T> Sync for SignalCell<T> where T: Clone + PartialEq + 'static {}
194
195/// Implementation of SignalCell construction and access.
196impl<T> SignalCell<T>
197where
198 T: Clone + PartialEq + 'static,
199{
200 /// Creates a new `SignalCell` with no signal stored.
201 ///
202 /// # Returns
203 ///
204 /// - `Self`: An empty `SignalCell` with `None` stored in the inner `UnsafeCell`.
205 pub const fn none() -> Self {
206 Self {
207 inner: UnsafeCell::new(None),
208 }
209 }
210
211 /// Stores a signal into the cell.
212 ///
213 /// # Arguments
214 ///
215 /// - `Signal<T>` - The signal to store.
216 ///
217 /// # Panics
218 ///
219 /// Panics if a signal has already been stored.
220 pub fn set(&self, signal: Signal<T>) {
221 unsafe {
222 let ptr: &mut Option<Signal<T>> = &mut *self.get_inner().get();
223 if ptr.is_some() {
224 panic!("SignalCell::set called on an already-initialized cell");
225 }
226 *ptr = Some(signal);
227 }
228 }
229
230 /// Returns the signal stored in the cell.
231 ///
232 /// # Returns
233 ///
234 /// - `Signal<T>` - The stored signal.
235 ///
236 /// # Panics
237 ///
238 /// Panics if no signal has been stored via `set`.
239 pub fn get(&self) -> Signal<T> {
240 unsafe {
241 let ptr: &Option<Signal<T>> = &*self.get_inner().get();
242 match ptr {
243 Some(signal) => *signal,
244 None => panic!("SignalCell::get called on an uninitialized cell"),
245 }
246 }
247 }
248}
249
250/// Provides a default empty `SignalCell`.
251///
252/// Creates a `SignalCell` with `None` stored in the inner `UnsafeCell`.
253///
254/// # Returns
255///
256/// - `Self`: An empty `SignalCell` with no signal stored.
257impl<T> Default for SignalCell<T>
258where
259 T: Clone + PartialEq + 'static,
260{
261 fn default() -> Self {
262 Self {
263 inner: UnsafeCell::new(None),
264 }
265 }
266}
267
268/// Marks `SignalInnerRegistryCell` as `Sync` for single-threaded WASM contexts.
269///
270/// SAFETY: `SignalInnerRegistryCell` is only used in single-threaded WASM contexts.
271/// Concurrent access from multiple threads would be undefined behavior.
272unsafe impl Sync for SignalInnerRegistryCell {}