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}