euv_core/reactive/signal/struct.rs
1use super::*;
2
3/// Inner state of a signal, holding the value and subscribed listeners.
4///
5/// This struct is not exposed directly; use `Signal` instead.
6#[derive(CustomDebug, Data, New)]
7pub(crate) struct SignalInner<T>
8where
9 T: Clone,
10{
11 /// The current value of the signal.
12 #[debug(skip)]
13 #[get(pub(crate))]
14 #[get_mut(pub(crate))]
15 #[set(pub(crate))]
16 pub(crate) value: T,
17 /// Callbacks to invoke when the value changes, each paired with its
18 /// subscription id so [`Signal::unsubscribe`] can detach a single
19 /// listener without disturbing the rest.
20 #[debug(skip)]
21 #[get(pub(crate))]
22 #[get_mut(pub(crate))]
23 #[set(pub(crate))]
24 pub(crate) listeners: Vec<ListenerEntry>,
25 /// Monotonic counter backing subscription ids for `listeners`.
26 #[get(type(copy))]
27 #[get_mut(pub(crate))]
28 #[set(pub(crate))]
29 #[new(skip)]
30 pub(crate) next_listener_id: usize,
31 /// Ids detached via [`Signal::unsubscribe`] while `update` had the
32 /// listener list swapped out. Drained by `update`'s merge-back pass so
33 /// a listener detached mid-notification is not resurrected.
34 #[debug(skip)]
35 #[get(pub(crate))]
36 #[get_mut(pub(crate))]
37 #[set(pub(crate))]
38 #[new(skip)]
39 pub(crate) removed_listener_ids: Vec<usize>,
40 /// `true` while `update` has the listener list swapped out for
41 /// notification. `unsubscribe` consults this flag to decide between
42 /// direct removal and deferred removal via `removed_listener_ids`.
43 #[get(type(copy))]
44 #[get_mut(pub(crate))]
45 #[set(pub(crate))]
46 #[new(skip)]
47 pub(crate) notifying: bool,
48 /// Whether this signal is still active. Set to `false` by `deactivate()`
49 /// (and `clear_signal_listeners`) to make subsequent `set()` calls
50 /// complete no-ops (no value update, no listener invocation, no
51 /// dispatch scheduling), ensuring stale closures like orphaned
52 /// `setInterval` handlers or pending `spawn_local` futures become harmless.
53 #[get(type(copy))]
54 #[get_mut(pub(crate))]
55 #[set(pub(crate))]
56 pub(crate) alive: bool,
57 /// IDs of dynamic nodes that depend on this signal for precise dirty marking.
58 /// When this signal changes, only these dynamic nodes are marked dirty
59 /// instead of broadcasting to all registered dynamic nodes.
60 #[debug(skip)]
61 #[get_mut(pub(crate))]
62 #[new(skip)]
63 pub(crate) dependents: Vec<usize>,
64}
65
66/// A reactive signal handle.
67///
68/// Allows reading, writing, and subscribing to changes.
69/// Implements `Clone` and `Copy` for ergonomic use; all copies share the same
70/// underlying state. The inner state lives in the global append-only signal
71/// slab and the handle carries its slot index as a `usize`. The `Copy`
72/// semantics are safe because only the slot index is copied — the actual
73/// slot is owned by the slab and is never freed or recycled.
74#[derive(CustomDebug, Data, Eq, Hash, New, Ord, PartialEq, PartialOrd)]
75#[new(pub(crate))]
76pub struct Signal<T>
77where
78 T: Clone + PartialEq + 'static,
79{
80 /// Slot index of the inner state in the global signal slab.
81 #[debug(skip)]
82 #[get(type(copy))]
83 #[get_mut(pub(crate))]
84 #[set(pub(crate))]
85 #[get_mut(pub(crate))]
86 #[set(pub(crate))]
87 pub(crate) inner: usize,
88 /// Marker for the generic type parameter (uses fn pointer to be `Copy`
89 /// regardless of `T`).
90 #[debug(skip)]
91 #[get(type(copy))]
92 #[get_mut(pub(crate))]
93 #[set(pub(crate))]
94 #[get(pub(crate))]
95 #[get_mut(pub(crate))]
96 #[set(pub(crate))]
97 pub(crate) _marker: PhantomData<fn() -> T>,
98}
99
100/// A `Sync` wrapper for single-threaded global `Signal` access.
101///
102/// SAFETY: This type is only safe to use in single-threaded contexts
103/// (e.g., WASM). It implements `Sync` to allow usage as a `static`
104/// variable, but concurrent access from multiple threads would be
105/// undefined behavior.
106#[derive(CustomDebug, Data, New)]
107#[new(pub(crate))]
108pub struct SignalCell<T>
109where
110 T: Clone + PartialEq + 'static,
111{
112 /// Interior-mutable storage for an optional signal handle.
113 #[debug(skip)]
114 #[get(pub(crate))]
115 #[get_mut(pub(crate))]
116 #[set(pub(crate))]
117 #[get(pub(crate))]
118 #[get_mut(pub(crate))]
119 #[set(pub(crate))]
120 pub(crate) inner: UnsafeCell<Option<Signal<T>>>,
121}
122
123/// Typed slab allocator for `SignalInner<T>`.
124///
125/// The slab owns all `SignalInner<T>` allocations for the program's
126/// lifetime. `Signal<T>` carries only a `usize` slot index, so signals are
127/// trivially `Copy` and stay cheap to clone. Slots are append-only and
128/// never recycled: a stale `Signal<T>` handle therefore always finds its
129/// original slot (marked `alive == false` after `Signal::deactivate`), so
130/// stale reads return the last stored value instead of hitting a reused
131/// slot of a different type or — worse — a `mem::zeroed()` fallback for
132/// a freed slot.
133#[derive(Data)]
134pub(crate) struct SignalSlab {
135 /// Slot storage. Index 0..len.
136 #[get(pub(crate))]
137 #[get_mut(pub(crate))]
138 #[set(pub(crate))]
139 pub(crate) entries: Vec<Box<dyn AnySignalInner>>,
140}
141
142/// A handle to a leaked `FnMut()` closure, stored as the closure's heap address.
143///
144/// The closure is double-boxed (`Box<Box<dyn FnMut()>>`) and leaked, so its
145/// memory outlives any `FireHandle` copy and can be safely invoked from any
146/// context via the raw pointer. The handle is `Copy` because it only holds
147/// the address — repeated invocations on captured copies all resolve to the
148/// same underlying closure.
149///
150/// This type replaces the inline `Box::leak(... as *mut Box<dyn FnMut()> as usize)`
151/// pattern that was used by `watch!`/`computed!` macros and the virtual list
152/// component, encapsulating the unsized coercion, double-boxing, and raw
153/// pointer arithmetic behind `From`/`Into` conversions and a dedicated
154/// `fire` method.
155#[derive(Clone, Copy, CustomDebug, Data, Eq, Hash, Ord, PartialEq, PartialOrd)]
156pub struct FireHandle {
157 /// Address of the leaked `Box<dyn FnMut()>` allocation.
158 #[get(type(copy))]
159 #[get_mut(pub(crate))]
160 #[set(pub(crate))]
161 #[get(pub(crate))]
162 #[get_mut(pub(crate))]
163 #[set(pub(crate))]
164 pub(crate) inner: usize,
165}