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 /// Notifies the hook context that a match arm is being entered.
418 /// Toggles the `arm_changed` flag; if it differs from the previous value,
419 /// the hooks array is cleared to prevent signal leakage between arms.
420 pub fn set_arm_changed(&mut self, changed: bool) {
421 let inner: &mut HookContextInner = self.get_inner_mut();
422 if inner.get_arm_changed() != changed {
423 inner.get_mut_hooks().clear();
424 inner.set_arm_changed(changed);
425 }
426 self.reset_hook_index();
427 }
428}
429
430/// Clones the hook context, sharing the same inner state.
431impl Clone for HookContext {
432 fn clone(&self) -> Self {
433 *self
434 }
435}
436
437/// Copies the hook context, sharing the same inner state.
438///
439/// A `HookContext` is just a raw pointer; copying it is a trivial bitwise copy.
440impl Copy for HookContext {}
441
442/// Provides a default empty hook context.
443impl Default for HookContext {
444 fn default() -> Self {
445 let boxed: Box<HookContextInner> = Box::default();
446 HookContext::from_inner(Box::leak(boxed) as *mut HookContextInner)
447 }
448}
449
450/// Implementation of HookContextInner construction.
451impl HookContextInner {
452 /// Creates a new empty hook context inner.
453 pub const fn new() -> Self {
454 HookContextInner {
455 hooks: Vec::new(),
456 arm_changed: false,
457 hook_index: 0_usize,
458 }
459 }
460}
461
462/// Provides a default empty hook context inner.
463impl Default for HookContextInner {
464 fn default() -> Self {
465 Self::new()
466 }
467}
468
469/// SAFETY: `HookContextCell` is only used in single-threaded WASM contexts.
470/// Concurrent access from multiple threads would be undefined behavior.
471unsafe impl Sync for HookContextCell {}
472
473/// Implementation of SignalCell construction and access.
474impl<T> SignalCell<T>
475where
476 T: Clone + PartialEq,
477{
478 /// Creates a new empty `SignalCell` with no signal stored.
479 ///
480 /// # Returns
481 ///
482 /// - `SignalCell<T>` - An empty cell ready to hold a signal.
483 pub const fn new() -> Self {
484 SignalCell {
485 inner: UnsafeCell::new(None),
486 }
487 }
488
489 /// Stores a signal into the cell.
490 ///
491 /// # Arguments
492 ///
493 /// - `Signal<T>` - The signal to store.
494 ///
495 /// # Panics
496 ///
497 /// Panics if a signal has already been stored.
498 pub fn set(&self, signal: Signal<T>) {
499 unsafe {
500 let ptr: &mut Option<Signal<T>> = &mut *self.inner.get();
501 if ptr.is_some() {
502 panic!("SignalCell::set called on an already-initialized cell");
503 }
504 *ptr = Some(signal);
505 }
506 }
507
508 /// Returns the signal stored in the cell.
509 ///
510 /// # Returns
511 ///
512 /// - `Signal<T>` - The stored signal.
513 ///
514 /// # Panics
515 ///
516 /// Panics if no signal has been stored via `set`.
517 pub fn get(&self) -> Signal<T> {
518 unsafe {
519 let ptr: &Option<Signal<T>> = &*self.inner.get();
520 match ptr {
521 Some(signal) => *signal,
522 None => panic!("SignalCell::get called on an uninitialized cell"),
523 }
524 }
525 }
526}
527
528/// SAFETY: `SignalCell` is only used in single-threaded WASM contexts.
529/// Concurrent access from multiple threads would be undefined behavior.
530unsafe impl<T> Sync for SignalCell<T> where T: Clone + PartialEq {}
531
532/// Provides a default empty `SignalCell`.
533impl<T> Default for SignalCell<T>
534where
535 T: Clone + PartialEq,
536{
537 fn default() -> Self {
538 Self::new()
539 }
540}