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}