euv_core/reactive/schedule/fn.rs
1use crate::*;
2
3/// Dispatches a custom `NativeEventName::EuvSignalUpdate.to_string()` event on the global window.
4///
5/// Used by the scheduler to trigger a DOM update cycle after signal changes.
6/// Does nothing if the window object is unavailable.
7///
8/// # Panics
9///
10/// Panics if `Event::new` fails to create the event object.
11pub(crate) fn dispatch_signal_update() {
12 if let Some(window_value) = window() {
13 let event: Event = Event::new(&NativeEventName::EuvSignalUpdate.to_string()).unwrap();
14 let _ = window_value.dispatch_event(&event);
15 }
16}
17
18/// Ensures the `window.__euv_dispatch` callback is registered.
19///
20/// Creates a `Closure` that resets the `SCHEDULED` flag and dispatches
21/// the signal update event, then stores it on the `window` object
22/// so it can be invoked via `queueMicrotask`.
23///
24/// # Panics
25///
26/// Panics if `window()` returns `None`.
27fn ensure_dispatch_callback() {
28 let window_value: Window = window().unwrap();
29 let key: JsValue = JsValue::from_str(EUV_DISPATCH);
30 if Reflect::get(&window_value, &key)
31 .unwrap_or(JsValue::UNDEFINED)
32 .is_undefined()
33 {
34 let closure: closure::Closure<dyn FnMut()> = closure::Closure::wrap(Box::new(|| {
35 SCHEDULED.store(false, Ordering::Relaxed);
36 dispatch_signal_update();
37 }));
38 let _ = Reflect::set(&window_value, &key, closure.as_ref());
39 closure.forget();
40 }
41}
42
43/// Schedules a deferred `NativeEventName::EuvSignalUpdate.to_string()` event via a microtask.
44///
45/// If a schedule is already pending (`SCHEDULED` is true) or updates
46/// are suppressed (`SUPPRESS_SCHEDULE` is true), this is a no-op.
47/// Otherwise, sets `SCHEDULED` to true and queues the
48/// `window.__euv_dispatch` callback via `queueMicrotask` on WASM
49/// targets. On non-WASM targets, resets `SCHEDULED` immediately
50/// since there is no event loop to schedule on.
51pub(crate) fn schedule_signal_update() {
52 if SCHEDULED.load(Ordering::Relaxed) || SUPPRESS_SCHEDULE.load(Ordering::Relaxed) {
53 return;
54 }
55 SCHEDULED.store(true, Ordering::Relaxed);
56 let window_option: Option<Window> = window();
57 if window_option.is_none() {
58 SCHEDULED.store(false, Ordering::Relaxed);
59 return;
60 }
61 ensure_dispatch_callback();
62 let window_value: Window = window_option.unwrap();
63 let dispatch_fn: JsValue =
64 Reflect::get(&window_value, &JsValue::from_str(EUV_DISPATCH)).unwrap_or(JsValue::UNDEFINED);
65 if dispatch_fn.is_undefined() {
66 SCHEDULED.store(false, Ordering::Relaxed);
67 return;
68 }
69 let queue_microtask_val: JsValue =
70 Reflect::get(&window_value, &JsValue::from_str(QUEUE_MICROTASK))
71 .unwrap_or(JsValue::UNDEFINED);
72 if queue_microtask_val.is_undefined() {
73 SCHEDULED.store(false, Ordering::Relaxed);
74 return;
75 }
76 let queue_microtask: Function = queue_microtask_val.into();
77 let _ = queue_microtask.call1(&JsValue::NULL, &dispatch_fn);
78}
79
80/// Batches signal updates within a closure, deferring DOM synchronization until completion.
81///
82/// Saves the current `SUPPRESS_SCHEDULE` flag, sets it to `true`,
83/// executes the closure, and restores the previous flag value.
84/// This prevents `schedule_signal_update` from queuing microtasks
85/// during the closure execution, allowing multiple signal mutations
86/// to be applied before triggering a single DOM update cycle.
87///
88/// # Arguments
89///
90/// - `F`- The closure to execute with batched updates.
91///
92/// # Returns
93///
94/// - `R`- The result of the closure execution.
95pub fn batch_updates<F, R>(callback: F) -> R
96where
97 F: FnOnce() -> R,
98{
99 let previous: bool = SUPPRESS_SCHEDULE.load(Ordering::Relaxed);
100 SUPPRESS_SCHEDULE.store(true, Ordering::Relaxed);
101 let result: R = callback();
102 SUPPRESS_SCHEDULE.store(previous, Ordering::Relaxed);
103 result
104}
105
106/// Subscribes an attribute signal to the global `NativeEventName::EuvSignalUpdate.to_string()` event.
107///
108/// Creates a callback that re-computes the attribute value and sets
109/// it on the signal whenever a signal update cycle runs. The callback
110/// is registered in the signal update registry using the signal's
111/// inner address as the key.
112///
113/// # Arguments
114///
115/// - `Signal<String>`- The attribute signal to subscribe.
116/// - `F`- A closure that computes the current attribute value string.
117pub(crate) fn subscribe_attr_signal<F>(attr_signal: Signal<String>, compute: F)
118where
119 F: Fn() -> String + 'static,
120{
121 let signal_key: usize = attr_signal.get_inner_addr();
122 let callback: Box<dyn FnMut()> = Box::new(move || {
123 let new_value: String = compute();
124 attr_signal.set_silent(new_value);
125 });
126 register_attr_signal_listener(signal_key, callback);
127}
128
129/// Converts a bool signal into a reactive `Signal<String>` attribute value.
130///
131/// Creates a `Signal<String>` initialized with the bool's string
132/// representation, then subscribes to the source signal so that
133/// whenever the bool changes, the string signal is updated accordingly.
134///
135/// # Arguments
136///
137/// - `Signal<bool>`- The source boolean signal.
138///
139/// # Returns
140///
141/// - `AttributeValue`- An `AttributeValue::Signal` wrapping the derived string signal.
142pub(crate) fn bool_signal_to_string_attribute_value(source: Signal<bool>) -> AttributeValue {
143 let initial: String = source.get().to_string();
144 let string_signal: Signal<String> = Signal::create(initial);
145 let string_signal_clone: Signal<String> = string_signal;
146 source.replace_subscribe({
147 let source_inner: Signal<bool> = source;
148 move || {
149 let new_value: String = source_inner.get().to_string();
150 string_signal_clone.set_silent(new_value);
151 }
152 });
153 AttributeValue::Signal(string_signal)
154}
155
156/// Returns a mutable reference to the current hook context.
157///
158/// SAFETY: Must only be called from the main thread (WASM single-threaded context).
159///
160/// # Returns
161///
162/// - `&'static mut Option<HookContextRc>`: A mutable reference to the global hook context.
163#[allow(static_mut_refs)]
164pub(crate) fn current_hook_context_mut() -> &'static mut Option<HookContextRc> {
165 unsafe { &mut *CURRENT_HOOK_CONTEXT.get_0().get() }
166}
167
168/// Returns a shared reference to the current hook context.
169///
170/// SAFETY: Must only be called from the main thread (WASM single-threaded context).
171///
172/// # Returns
173///
174/// - `&'static Option<HookContextRc>`: A shared reference to the global hook context.
175#[allow(static_mut_refs)]
176pub(crate) fn current_hook_context() -> &'static Option<HookContextRc> {
177 unsafe { &*CURRENT_HOOK_CONTEXT.get_0().get() }
178}