Skip to main content

euv_core/reactive/
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    pub fn new(value: T) -> Self {
10        let inner: SignalInner<T> = SignalInner {
11            value,
12            listeners: Vec::new(),
13        };
14        inner
15    }
16}
17
18/// Implementation of reactive signal operations.
19impl<T> Signal<T>
20where
21    T: Clone + PartialEq,
22{
23    /// Creates a new `Signal` with the given initial value.
24    ///
25    /// The inner state is allocated via `Box::leak` and lives for the
26    /// remainder of the program. This is safe in single-threaded WASM
27    /// contexts where no concurrent access can occur.
28    ///
29    /// # Arguments
30    ///
31    /// - `T`: The initial value of the signal.
32    ///
33    /// # Returns
34    ///
35    /// - `Signal<T>`: A handle to the newly created reactive signal.
36    pub fn new(value: T) -> Self {
37        let boxed: Box<SignalInner<T>> = Box::new(SignalInner::new(value));
38        Signal {
39            inner: Box::leak(boxed) as *mut SignalInner<T>,
40        }
41    }
42
43    /// Creates a new `Signal` from an existing raw pointer.
44    ///
45    /// # Safety
46    ///
47    /// The caller must ensure the pointer was allocated via `Box::leak`
48    /// and remains valid for the entire program lifetime.
49    pub(crate) fn from_inner(inner: *mut SignalInner<T>) -> Self {
50        Signal { inner }
51    }
52
53    /// Returns a mutable reference to the inner signal state.
54    ///
55    /// # Safety
56    ///
57    /// The caller must ensure no other references to the inner state exist.
58    /// In single-threaded WASM this is always safe.
59    #[allow(clippy::mut_from_ref)]
60    fn get_inner_mut(&self) -> &mut SignalInner<T> {
61        unsafe { &mut *self.inner }
62    }
63
64    /// Returns the current value of the signal.
65    ///
66    /// # Returns
67    ///
68    /// - `T`: The current value of the signal.
69    pub fn get(&self) -> T {
70        self.get_inner_mut().get_value().clone()
71    }
72
73    /// Attempts to return the current value of the signal without panicking.
74    ///
75    /// # Returns
76    ///
77    /// - `Some(T)`: The current value if the borrow succeeds.
78    /// - `None`: If the inner value is already mutably borrowed.
79    pub fn try_get(&self) -> Option<T> {
80        Some(self.get_inner_mut().get_value().clone())
81    }
82
83    /// Subscribes a callback to be invoked when the signal changes.
84    ///
85    /// # Arguments
86    ///
87    /// - `F`: The callback to invoke when the signal changes.
88    pub fn subscribe<F>(&self, callback: F)
89    where
90        F: FnMut() + 'static,
91    {
92        self.get_inner_mut()
93            .get_mut_listeners()
94            .push(Rc::new(RefCell::new(callback)));
95    }
96
97    /// Sets the value of the signal and notifies listeners.
98    ///
99    /// If the new value is equal to the current value, no update is performed
100    /// and no listeners are notified. This prevents unnecessary re-renders and
101    /// avoids cascading no-op updates through intermediate signal chains.
102    ///
103    /// # Arguments
104    ///
105    /// - `T`: The new value to assign to the signal.
106    pub fn set(&self, value: T) {
107        let inner: &mut SignalInner<T> = self.get_inner_mut();
108        if inner.get_value() == &value {
109            return;
110        }
111        let listeners: ListenerList = {
112            inner.set_value(value);
113            inner.get_listeners().iter().map(Rc::clone).collect()
114        };
115        for listener in &listeners {
116            let mut borrowed = listener.borrow_mut();
117            borrowed();
118        }
119        schedule_signal_update();
120    }
121
122    /// Sets the value of the signal and notifies listeners without scheduling
123    /// a global DOM update dispatch.
124    ///
125    /// This is identical to `set` except it does not call
126    /// `schedule_signal_update()`, meaning no `__euv_signal_update__` event
127    /// will be dispatched. Use this for internal bookkeeping signals whose
128    /// changes should not trigger DynamicNode re-renders.
129    ///
130    /// # When to use
131    ///
132    /// Prefer `set` in almost all cases. Only use `set_silent` when the
133    /// signal change is guaranteed not to affect any DynamicNode output
134    /// (e.g., internal guard flags, derived-value caches already at the
135    /// correct value, or within a `with_suppressed_updates` block where
136    /// the caller takes responsibility for batching the update).
137    ///
138    /// If the new value is equal to the current value, no update is performed
139    /// and no listeners are notified.
140    pub fn set_silent(&self, value: T) {
141        let inner: &mut SignalInner<T> = self.get_inner_mut();
142        if inner.get_value() == &value {
143            return;
144        }
145        let listeners: ListenerList = {
146            inner.set_value(value);
147            inner.get_listeners().iter().map(Rc::clone).collect()
148        };
149        for listener in &listeners {
150            let mut borrowed = listener.borrow_mut();
151            borrowed();
152        }
153    }
154
155    /// Attempts to set the value of the signal and notify listeners without panicking.
156    ///
157    /// If the new value is equal to the current value, no update is performed.
158    ///
159    /// # Arguments
160    ///
161    /// - `T`: The new value to assign to the signal.
162    ///
163    /// # Returns
164    ///
165    /// - `bool`: `true` if the value was successfully updated and listeners were notified, `false` if unchanged.
166    pub fn try_set(&self, value: T) -> bool {
167        let inner: &mut SignalInner<T> = self.get_inner_mut();
168        if inner.get_value() == &value {
169            return false;
170        }
171        let listeners: ListenerList = {
172            inner.set_value(value);
173            inner.get_listeners().iter().map(Rc::clone).collect()
174        };
175        for listener in &listeners {
176            listener.borrow_mut()();
177        }
178        schedule_signal_update();
179        true
180    }
181}
182
183/// Prevents direct dereference of a signal to enforce explicit API usage.
184impl<T> Deref for Signal<T>
185where
186    T: Clone + PartialEq,
187{
188    type Target = T;
189
190    fn deref(&self) -> &Self::Target {
191        panic!("Signal does not support direct dereference; use .get() instead");
192    }
193}
194
195/// Prevents direct mutable dereference of a signal to enforce explicit API usage.
196impl<T> DerefMut for Signal<T>
197where
198    T: Clone + PartialEq,
199{
200    fn deref_mut(&mut self) -> &mut Self::Target {
201        panic!("Signal does not support direct dereference; use .set() instead");
202    }
203}
204
205/// Clones the signal, sharing the same inner state.
206impl<T> Clone for Signal<T>
207where
208    T: Clone + PartialEq,
209{
210    fn clone(&self) -> Self {
211        *self
212    }
213}
214
215/// Copies the signal, sharing the same inner state.
216///
217/// A `Signal` is just a raw pointer; copying it is a trivial bitwise copy.
218impl<T> Copy for Signal<T> where T: Clone + PartialEq {}
219
220/// Converts a static `String` into a text attribute value.
221impl IntoReactiveValue for String {
222    fn into_reactive_value(self) -> AttributeValue {
223        AttributeValue::Text(self)
224    }
225}
226
227/// Converts a string slice into a text attribute value.
228impl IntoReactiveValue for &str {
229    fn into_reactive_value(self) -> AttributeValue {
230        AttributeValue::Text(self.to_string())
231    }
232}
233
234/// Converts a string signal into a reactive attribute value.
235impl IntoReactiveValue for Signal<String> {
236    fn into_reactive_value(self) -> AttributeValue {
237        AttributeValue::Signal(self)
238    }
239}
240
241/// Converts a mutable bool signal into a reactive attribute value.
242///
243/// The signal is mapped to a `Signal<String>` that yields `"true"` or `"false"`,
244/// enabling boolean attributes like `checked` to reactively update the DOM.
245impl IntoReactiveValue for Signal<bool> {
246    fn into_reactive_value(self) -> AttributeValue {
247        bool_signal_to_string_attribute_value(self)
248    }
249}
250
251/// Converts a CSS class reference into an attribute value.
252impl IntoReactiveValue for crate::vdom::CssClass {
253    fn into_reactive_value(self) -> AttributeValue {
254        AttributeValue::Css(self)
255    }
256}
257
258/// Converts a reference to a CSS class into an attribute value by cloning.
259impl IntoReactiveValue for &'static crate::vdom::CssClass {
260    fn into_reactive_value(self) -> AttributeValue {
261        AttributeValue::Css(self.clone())
262    }
263}
264
265/// Converts a closure into a callback attribute value.
266impl<F> IntoCallbackAttribute for F
267where
268    F: FnMut(NativeEvent) + 'static,
269{
270    fn into_callback_attribute(self) -> AttributeValue {
271        AttributeValue::Event(NativeEventHandler::new(
272            NativeEventName::Other("callback".to_string()),
273            self,
274        ))
275    }
276}
277
278/// Converts an owned event handler into a callback attribute value.
279impl IntoCallbackAttribute for NativeEventHandler {
280    fn into_callback_attribute(self) -> AttributeValue {
281        AttributeValue::Event(self)
282    }
283}
284
285/// Converts an optional event handler into a callback attribute value.
286impl IntoCallbackAttribute for Option<NativeEventHandler> {
287    fn into_callback_attribute(self) -> AttributeValue {
288        match self {
289            Some(handler) => AttributeValue::Event(handler),
290            None => AttributeValue::Text(String::new()),
291        }
292    }
293}
294
295/// Implementation of hook context lifecycle and hook index management.
296impl HookContext {
297    /// Creates a new `HookContext` from an existing raw pointer.
298    ///
299    /// # Safety
300    ///
301    /// The caller must ensure the pointer was allocated via `Box::leak`
302    /// and remains valid for the entire program lifetime.
303    pub fn from_inner(inner: *mut HookContextInner) -> Self {
304        HookContext { inner }
305    }
306
307    /// Returns a mutable reference to the inner hook context state.
308    ///
309    /// # Safety
310    ///
311    /// The caller must ensure no other references to the inner state exist.
312    /// In single-threaded WASM this is always safe.
313    #[allow(clippy::mut_from_ref)]
314    fn get_inner_mut(&self) -> &mut HookContextInner {
315        unsafe { &mut *self.inner }
316    }
317
318    /// Returns the current hook index.
319    pub fn get_hook_index(&self) -> usize {
320        *self.get_inner_mut().get_hook_index()
321    }
322
323    /// Sets the hook index.
324    ///
325    /// # Arguments
326    ///
327    /// - `usize`: The new hook index value.
328    pub fn set_hook_index(&mut self, index: usize) {
329        self.get_inner_mut().set_hook_index(index);
330    }
331
332    /// Returns a reference to the hooks storage.
333    pub fn get_hooks(&self) -> &Vec<Box<dyn Any>> {
334        self.get_inner_mut().get_hooks()
335    }
336
337    /// Returns a mutable reference to the hooks storage.
338    pub fn get_mut_hooks(&mut self) -> &mut Vec<Box<dyn Any>> {
339        self.get_inner_mut().get_mut_hooks()
340    }
341
342    /// Resets the hook index for a new render cycle.
343    pub fn reset_hook_index(&mut self) {
344        self.set_hook_index(0_usize);
345    }
346}
347
348/// Clones the hook context, sharing the same inner state.
349impl Clone for HookContext {
350    fn clone(&self) -> Self {
351        *self
352    }
353}
354
355/// Copies the hook context, sharing the same inner state.
356///
357/// A `HookContext` is just a raw pointer; copying it is a trivial bitwise copy.
358impl Copy for HookContext {}
359
360/// Provides a default empty hook context.
361impl Default for HookContext {
362    fn default() -> Self {
363        let boxed: Box<HookContextInner> = Box::default();
364        HookContext::from_inner(Box::leak(boxed) as *mut HookContextInner)
365    }
366}
367
368/// Implementation of HookContextInner construction.
369impl HookContextInner {
370    /// Creates a new empty hook context inner.
371    pub const fn new() -> Self {
372        HookContextInner {
373            hooks: Vec::new(),
374            hook_index: 0_usize,
375        }
376    }
377}
378
379/// Provides a default empty hook context inner.
380impl Default for HookContextInner {
381    fn default() -> Self {
382        Self::new()
383    }
384}
385
386/// SAFETY: `HookContextCell` is only used in single-threaded WASM contexts.
387/// Concurrent access from multiple threads would be undefined behavior.
388unsafe impl Sync for HookContextCell {}
389
390/// Implementation of SignalCell construction and access.
391impl<T> SignalCell<T>
392where
393    T: Clone + PartialEq,
394{
395    /// Creates a new empty `SignalCell` with no signal stored.
396    ///
397    /// # Returns
398    ///
399    /// - `SignalCell<T>`: An empty cell ready to hold a signal.
400    pub const fn new() -> Self {
401        SignalCell {
402            inner: UnsafeCell::new(None),
403        }
404    }
405
406    /// Stores a signal into the cell.
407    ///
408    /// # Arguments
409    ///
410    /// - `Signal<T>`: The signal to store.
411    ///
412    /// # Panics
413    ///
414    /// Panics if a signal has already been stored.
415    pub fn set(&self, signal: Signal<T>) {
416        unsafe {
417            let ptr: &mut Option<Signal<T>> = &mut *self.inner.get();
418            if ptr.is_some() {
419                panic!("SignalCell::set called on an already-initialized cell");
420            }
421            *ptr = Some(signal);
422        }
423    }
424
425    /// Returns the signal stored in the cell.
426    ///
427    /// # Returns
428    ///
429    /// - `Signal<T>`: The stored signal.
430    ///
431    /// # Panics
432    ///
433    /// Panics if no signal has been stored via `set`.
434    pub fn get(&self) -> Signal<T> {
435        unsafe {
436            let ptr: &Option<Signal<T>> = &*self.inner.get();
437            match ptr {
438                Some(signal) => *signal,
439                None => panic!("SignalCell::get called on an uninitialized cell"),
440            }
441        }
442    }
443}
444
445/// SAFETY: `SignalCell` is only used in single-threaded WASM contexts.
446/// Concurrent access from multiple threads would be undefined behavior.
447unsafe impl<T> Sync for SignalCell<T> where T: Clone + PartialEq {}
448
449/// Provides a default empty `SignalCell`.
450impl<T> Default for SignalCell<T>
451where
452    T: Clone + PartialEq,
453{
454    fn default() -> Self {
455        Self::new()
456    }
457}