Skip to main content

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        drop(inner);
122        for mut listener in listeners {
123            listener();
124            if let Ok(mut inner) = inner_ref.try_borrow_mut() {
125                inner.get_mut_listeners().push(listener);
126            }
127        }
128        true
129    }
130
131    /// Sets the value of the signal and notifies listeners.
132    ///
133    /// # Arguments
134    ///
135    /// - `T` - The new value to assign to the signal.
136    pub fn set(&self, value: T) {
137        if self.update_and_notify(value) {
138            schedule_signal_update();
139        }
140    }
141
142    /// Sets the value of the signal and notifies listeners without scheduling
143    /// a global DOM update dispatch.
144    ///
145    /// # Arguments
146    ///
147    /// - `T` - The new value to assign to the signal.
148    pub fn set_silent(&self, value: T) {
149        self.update_and_notify(value);
150    }
151
152    /// Sets the value of the signal without notifying listeners or scheduling
153    /// a DOM update. This is useful for breaking circular watch dependencies
154    /// where two signals watch each other and would otherwise recurse infinitely.
155    /// If the inner `RefCell` is already borrowed, this is a no-op.
156    ///
157    /// # Arguments
158    ///
159    /// - `T` - The new value to assign to the signal.
160    pub fn set_untracked(&self, value: T) {
161        let inner_ref: &RefCell<SignalInner<T>> = self.inner_ref();
162        if let Ok(mut inner) = inner_ref.try_borrow_mut() {
163            inner.set_value(value);
164        }
165    }
166}
167
168/// Clones the signal, sharing the same inner state.
169///
170/// Since `Signal` is `Copy`, this simply returns `*self`.
171///
172/// # Returns
173///
174/// - `Self`: A copy of the signal handle sharing the same inner state.
175impl<T> Clone for Signal<T>
176where
177    T: Clone + PartialEq + 'static,
178{
179    fn clone(&self) -> Self {
180        *self
181    }
182}
183
184/// Copies the signal, sharing the same inner state.
185///
186/// Safe because only the inner address (a `usize`) is copied;
187/// the actual `Rc` reference is held by the global signal registry.
188impl<T> Copy for Signal<T> where T: Clone + PartialEq + 'static {}
189
190/// Marks `SignalCell` as `Sync` for single-threaded WASM contexts.
191///
192/// SAFETY: `SignalCell` is only used in single-threaded WASM contexts.
193/// Concurrent access from multiple threads would be undefined behavior.
194unsafe impl<T> Sync for SignalCell<T> where T: Clone + PartialEq + 'static {}
195
196/// Implementation of SignalCell construction and access.
197impl<T> SignalCell<T>
198where
199    T: Clone + PartialEq + 'static,
200{
201    /// Creates a new `SignalCell` with no signal stored.
202    ///
203    /// # Returns
204    ///
205    /// - `Self`: An empty `SignalCell` with `None` stored in the inner `UnsafeCell`.
206    pub const fn none() -> Self {
207        Self {
208            inner: UnsafeCell::new(None),
209        }
210    }
211
212    /// Stores a signal into the cell.
213    ///
214    /// # Arguments
215    ///
216    /// - `Signal<T>` - The signal to store.
217    ///
218    /// # Panics
219    ///
220    /// Panics if a signal has already been stored.
221    pub fn set(&self, signal: Signal<T>) {
222        unsafe {
223            let ptr: &mut Option<Signal<T>> = &mut *self.get_inner().get();
224            if ptr.is_some() {
225                panic!("SignalCell::set called on an already-initialized cell");
226            }
227            *ptr = Some(signal);
228        }
229    }
230
231    /// Returns the signal stored in the cell.
232    ///
233    /// # Returns
234    ///
235    /// - `Signal<T>` - The stored signal.
236    ///
237    /// # Panics
238    ///
239    /// Panics if no signal has been stored via `set`.
240    pub fn get(&self) -> Signal<T> {
241        unsafe {
242            let ptr: &Option<Signal<T>> = &*self.get_inner().get();
243            match ptr {
244                Some(signal) => *signal,
245                None => panic!("SignalCell::get called on an uninitialized cell"),
246            }
247        }
248    }
249}
250
251/// Provides a default empty `SignalCell`.
252///
253/// Creates a `SignalCell` with `None` stored in the inner `UnsafeCell`.
254///
255/// # Returns
256///
257/// - `Self`: An empty `SignalCell` with no signal stored.
258impl<T> Default for SignalCell<T>
259where
260    T: Clone + PartialEq + 'static,
261{
262    fn default() -> Self {
263        Self {
264            inner: UnsafeCell::new(None),
265        }
266    }
267}
268
269/// Marks `SignalInnerRegistryCell` as `Sync` for single-threaded WASM contexts.
270///
271/// SAFETY: `SignalInnerRegistryCell` is only used in single-threaded WASM contexts.
272/// Concurrent access from multiple threads would be undefined behavior.
273unsafe impl Sync for SignalInnerRegistryCell {}