euv_core/reactive/hook/fn.rs
1use crate::*;
2
3/// Returns the currently active `HookContext`.
4///
5/// When called outside a `with_hook_context` scope, returns a reference
6/// to the default empty context.
7///
8/// # Returns
9///
10/// - `HookContext` - The currently active hook context.
11pub fn get_current_hook_context() -> HookContext {
12 unsafe { HookContext::from_inner(CURRENT_HOOK_CONTEXT) }
13}
14
15/// Runs a closure with the given `HookContext` set as the active context.
16///
17/// This is called by the renderer before invoking a `DynamicNode`'s
18/// render function, enabling `use_signal` and other hooks to access
19/// and persist state across re-renders.
20///
21/// # Arguments
22///
23/// - `HookContext` - The hook context to set as active.
24/// - `FnOnce() -> R` - The closure to execute with the active context.
25///
26/// # Returns
27///
28/// - `R` - The return value of the closure.
29pub fn with_hook_context<F, R>(context: HookContext, f: F) -> R
30where
31 F: FnOnce() -> R,
32{
33 let previous: &'static mut HookContextInner = unsafe { CURRENT_HOOK_CONTEXT };
34 unsafe {
35 CURRENT_HOOK_CONTEXT = context.get_inner();
36 }
37 let result: R = f();
38 unsafe {
39 CURRENT_HOOK_CONTEXT = previous;
40 }
41 result
42}
43
44/// Creates a new `HookContext` allocated via `Box::leak`.
45///
46/// The allocated memory lives for the remainder of the program and will
47/// never be freed. This is acceptable for WASM single-threaded contexts
48/// where `DynamicNode` instances persist for the application lifetime.
49///
50/// # Returns
51///
52/// - `HookContext` - A handle to the newly allocated hook context.
53pub fn create_hook_context() -> HookContext {
54 let ctx: Box<HookContextInner> = Box::default();
55 HookContext::from_inner(Box::leak(ctx) as *mut HookContextInner)
56}
57
58/// Creates a new reactive signal with the given initial value.
59///
60/// When called inside a `DynamicNode` render function (within a
61/// `with_hook_context` scope), the signal state is persisted across
62/// re-renders by storing it in the active `HookContext`. Subsequent
63/// re-renders return the same signal handle, preserving its current value.
64///
65/// When called outside a hook context, a fresh signal is created each time.
66///
67/// # Arguments
68///
69/// - `FnOnce() -> T` - A closure that returns the initial value of the signal.
70///
71/// # Returns
72///
73/// - `Signal<T>` - A mutable handle to the newly created or persisted reactive signal.
74pub fn use_signal<T, F>(init: F) -> Signal<T>
75where
76 T: Clone + PartialEq + 'static,
77 F: FnOnce() -> T,
78{
79 let ctx: HookContext = get_current_hook_context();
80 let ctx_inner: &mut HookContextInner = ctx.get_inner();
81 let index: usize = ctx_inner.get_hook_index();
82 ctx_inner.set_hook_index(index + 1);
83 if index < ctx_inner.get_hooks().len()
84 && let Some(existing) = ctx_inner.get_hooks()[index].downcast_ref::<Signal<T>>()
85 {
86 return *existing;
87 }
88 let signal: Signal<T> = Signal::new(init());
89 let cleanup_signal: Signal<T> = signal;
90 ctx_inner
91 .get_mut_cleanups()
92 .push(Box::new(move || cleanup_signal.clear_listeners()));
93 if index < ctx_inner.get_hooks().len() {
94 ctx_inner.get_mut_hooks()[index] = Box::new(signal);
95 } else {
96 ctx_inner.get_mut_hooks().push(Box::new(signal));
97 }
98 signal
99}