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    /// - `FnMut() + 'static`: 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: RefMut<dyn FnMut()> = 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: RefMut<dyn FnMut()> = 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 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 CssClass {
260    fn into_reactive_value(self) -> AttributeValue {
261        AttributeValue::Css(self.clone())
262    }
263}
264
265/// Converts a `String` into its own value for reactive string storage.
266impl IntoReactiveString for String {
267    fn into_reactive_string(self) -> String {
268        self
269    }
270}
271
272/// Converts a string slice into an owned string for reactive string storage.
273impl IntoReactiveString for &str {
274    fn into_reactive_string(self) -> String {
275        self.to_string()
276    }
277}
278
279/// Converts a `CssClass` into its class name for reactive string storage.
280impl IntoReactiveString for CssClass {
281    fn into_reactive_string(self) -> String {
282        self.get_name().to_string()
283    }
284}
285
286/// Converts a reference to a `CssClass` into its class name for reactive string storage.
287impl IntoReactiveString for &'static CssClass {
288    fn into_reactive_string(self) -> String {
289        self.get_name().to_string()
290    }
291}
292
293/// Converts a `bool` into `"true"` or `"false"` for reactive string storage.
294impl IntoReactiveString for bool {
295    fn into_reactive_string(self) -> String {
296        self.to_string()
297    }
298}
299
300/// Converts an `i32` into a string for reactive string storage.
301impl IntoReactiveString for i32 {
302    fn into_reactive_string(self) -> String {
303        self.to_string()
304    }
305}
306
307/// Converts a `u32` into a string for reactive string storage.
308impl IntoReactiveString for u32 {
309    fn into_reactive_string(self) -> String {
310        self.to_string()
311    }
312}
313
314/// Converts a `f64` into a string for reactive string storage.
315impl IntoReactiveString for f64 {
316    fn into_reactive_string(self) -> String {
317        self.to_string()
318    }
319}
320
321/// Converts a string signal into a reactive string by resolving its current value.
322impl IntoReactiveString for Signal<String> {
323    fn into_reactive_string(self) -> String {
324        self.get()
325    }
326}
327
328/// Converts a bool signal into a reactive string by resolving its current value.
329impl IntoReactiveString for Signal<bool> {
330    fn into_reactive_string(self) -> String {
331        self.get().to_string()
332    }
333}
334
335/// Converts a closure into a callback attribute value.
336impl<F> IntoCallbackAttribute for F
337where
338    F: FnMut(NativeEvent) + 'static,
339{
340    fn into_callback_attribute(self) -> AttributeValue {
341        AttributeValue::Event(NativeEventHandler::new(
342            NativeEventName::Other("callback".to_string()),
343            self,
344        ))
345    }
346}
347
348/// Converts an owned event handler into a callback attribute value.
349impl IntoCallbackAttribute for NativeEventHandler {
350    fn into_callback_attribute(self) -> AttributeValue {
351        AttributeValue::Event(self)
352    }
353}
354
355/// Converts an optional event handler into a callback attribute value.
356impl IntoCallbackAttribute for Option<NativeEventHandler> {
357    fn into_callback_attribute(self) -> AttributeValue {
358        match self {
359            Some(handler) => AttributeValue::Event(handler),
360            None => AttributeValue::Text(String::new()),
361        }
362    }
363}
364
365/// Implementation of hook context lifecycle and hook index management.
366impl HookContext {
367    /// Creates a new `HookContext` from an existing raw pointer.
368    ///
369    /// # Safety
370    ///
371    /// The caller must ensure the pointer was allocated via `Box::leak`
372    /// and remains valid for the entire program lifetime.
373    pub fn from_inner(inner: *mut HookContextInner) -> Self {
374        HookContext { inner }
375    }
376
377    /// Returns a mutable reference to the inner hook context state.
378    ///
379    /// # Safety
380    ///
381    /// The caller must ensure no other references to the inner state exist.
382    /// In single-threaded WASM this is always safe.
383    #[allow(clippy::mut_from_ref)]
384    fn get_inner_mut(&self) -> &mut HookContextInner {
385        unsafe { &mut *self.inner }
386    }
387
388    /// Returns the current hook index.
389    pub fn get_hook_index(&self) -> usize {
390        *self.get_inner_mut().get_hook_index()
391    }
392
393    /// Sets the hook index.
394    ///
395    /// # Arguments
396    ///
397    /// - `usize`: The new hook index value.
398    pub fn set_hook_index(&mut self, index: usize) {
399        self.get_inner_mut().set_hook_index(index);
400    }
401
402    /// Returns a reference to the hooks storage.
403    pub fn get_hooks(&self) -> &Vec<Box<dyn Any>> {
404        self.get_inner_mut().get_hooks()
405    }
406
407    /// Returns a mutable reference to the hooks storage.
408    pub fn get_mut_hooks(&mut self) -> &mut Vec<Box<dyn Any>> {
409        self.get_inner_mut().get_mut_hooks()
410    }
411
412    /// Resets the hook index for a new render cycle.
413    pub fn reset_hook_index(&mut self) {
414        self.set_hook_index(0_usize);
415    }
416}
417
418/// Clones the hook context, sharing the same inner state.
419impl Clone for HookContext {
420    fn clone(&self) -> Self {
421        *self
422    }
423}
424
425/// Copies the hook context, sharing the same inner state.
426///
427/// A `HookContext` is just a raw pointer; copying it is a trivial bitwise copy.
428impl Copy for HookContext {}
429
430/// Provides a default empty hook context.
431impl Default for HookContext {
432    fn default() -> Self {
433        let boxed: Box<HookContextInner> = Box::default();
434        HookContext::from_inner(Box::leak(boxed) as *mut HookContextInner)
435    }
436}
437
438/// Implementation of HookContextInner construction.
439impl HookContextInner {
440    /// Creates a new empty hook context inner.
441    pub const fn new() -> Self {
442        HookContextInner {
443            hooks: Vec::new(),
444            hook_index: 0_usize,
445        }
446    }
447}
448
449/// Provides a default empty hook context inner.
450impl Default for HookContextInner {
451    fn default() -> Self {
452        Self::new()
453    }
454}
455
456/// SAFETY: `HookContextCell` is only used in single-threaded WASM contexts.
457/// Concurrent access from multiple threads would be undefined behavior.
458unsafe impl Sync for HookContextCell {}
459
460/// Implementation of SignalCell construction and access.
461impl<T> SignalCell<T>
462where
463    T: Clone + PartialEq,
464{
465    /// Creates a new empty `SignalCell` with no signal stored.
466    ///
467    /// # Returns
468    ///
469    /// - `SignalCell<T>`: An empty cell ready to hold a signal.
470    pub const fn new() -> Self {
471        SignalCell {
472            inner: UnsafeCell::new(None),
473        }
474    }
475
476    /// Stores a signal into the cell.
477    ///
478    /// # Arguments
479    ///
480    /// - `Signal<T>`: The signal to store.
481    ///
482    /// # Panics
483    ///
484    /// Panics if a signal has already been stored.
485    pub fn set(&self, signal: Signal<T>) {
486        unsafe {
487            let ptr: &mut Option<Signal<T>> = &mut *self.inner.get();
488            if ptr.is_some() {
489                panic!("SignalCell::set called on an already-initialized cell");
490            }
491            *ptr = Some(signal);
492        }
493    }
494
495    /// Returns the signal stored in the cell.
496    ///
497    /// # Returns
498    ///
499    /// - `Signal<T>`: The stored signal.
500    ///
501    /// # Panics
502    ///
503    /// Panics if no signal has been stored via `set`.
504    pub fn get(&self) -> Signal<T> {
505        unsafe {
506            let ptr: &Option<Signal<T>> = &*self.inner.get();
507            match ptr {
508                Some(signal) => *signal,
509                None => panic!("SignalCell::get called on an uninitialized cell"),
510            }
511        }
512    }
513}
514
515/// SAFETY: `SignalCell` is only used in single-threaded WASM contexts.
516/// Concurrent access from multiple threads would be undefined behavior.
517unsafe impl<T> Sync for SignalCell<T> where T: Clone + PartialEq {}
518
519/// Provides a default empty `SignalCell`.
520impl<T> Default for SignalCell<T>
521where
522    T: Clone + PartialEq,
523{
524    fn default() -> Self {
525        Self::new()
526    }
527}