lgui_core/core/component/
hook_state.rs1use std::{
2 any::Any,
3 cell::{Cell, RefCell},
4 collections::{HashMap, HashSet},
5 sync::{
6 atomic::{AtomicBool, Ordering},
7 Arc, Mutex, RwLock,
8 },
9};
10
11use super::{ComponentId, ComponentTree, HookId, UiId};
12
13type HookValue = Box<dyn Any>;
14type PendingHookUpdate = Box<dyn FnOnce(&HookStateStore) -> bool + Send + 'static>;
15pub type UiWake = Arc<dyn Fn() + Send + Sync + 'static>;
16
17struct QueuedHookUpdate {
18 owner: ComponentId,
19 invalidation_id: UiId,
20 apply: PendingHookUpdate,
21}
22
23#[derive(Default)]
24pub struct HookStateStore {
25 values: RefCell<HashMap<HookId, HookValue>>,
26 seen: RefCell<HashSet<HookId>>,
27 inserted: RefCell<HashSet<HookId>>,
28 tracking_render: Cell<bool>,
29}
30
31impl HookStateStore {
32 pub fn new() -> Self {
33 Self::default()
34 }
35
36 pub fn begin_render(&self) {
37 if self.tracking_render.get() {
38 self.abort_render_state();
39 }
40 self.seen.borrow_mut().clear();
41 self.inserted.borrow_mut().clear();
42 self.tracking_render.set(true);
43 }
44
45 pub fn end_render(&self, components: &ComponentTree) {
46 self.values
47 .borrow_mut()
48 .retain(|id, _| components.is_alive(id.component()));
49 self.tracking_render.set(false);
50 self.seen.borrow_mut().clear();
51 self.inserted.borrow_mut().clear();
52 }
53
54 pub fn abort_render(&self, _components: &ComponentTree) {
55 self.abort_render_state();
56 }
57
58 pub fn value<T>(&self, id: HookId, initial: impl FnOnce() -> T) -> T
59 where
60 T: Clone + 'static,
61 {
62 self.mark_seen(id);
63 let mut values = self.values.borrow_mut();
64 let existed = values.contains_key(&id);
65 let value = values.entry(id).or_insert_with(|| Box::new(initial()));
66 if self.tracking_render.get() && !existed {
67 self.inserted.borrow_mut().insert(id);
68 }
69 value
70 .downcast_ref::<T>()
71 .unwrap_or_else(|| panic!("hook state type mismatch for `{id}`"))
72 .clone()
73 }
74
75 pub fn update_existing<T>(&self, id: HookId, update: impl FnOnce(&mut T)) -> bool
76 where
77 T: 'static,
78 {
79 let mut values = self.values.borrow_mut();
80 let Some(value) = values.get_mut(&id) else {
81 return false;
82 };
83 let value = value
84 .downcast_mut::<T>()
85 .unwrap_or_else(|| panic!("hook state type mismatch for `{id}`"));
86 update(value);
87 true
88 }
89
90 pub fn contains(&self, id: HookId) -> bool {
91 self.values.borrow().contains_key(&id)
92 }
93
94 pub fn clear(&self) {
95 self.values.borrow_mut().clear();
96 self.seen.borrow_mut().clear();
97 self.inserted.borrow_mut().clear();
98 self.tracking_render.set(false);
99 }
100
101 fn mark_seen(&self, id: HookId) {
102 if self.tracking_render.get() {
103 self.seen.borrow_mut().insert(id);
104 }
105 }
106
107 fn abort_render_state(&self) {
108 let inserted = std::mem::take(&mut *self.inserted.borrow_mut());
109 self.values
110 .borrow_mut()
111 .retain(|id, _| !inserted.contains(id));
112 self.tracking_render.set(false);
113 self.seen.borrow_mut().clear();
114 }
115}
116
117#[derive(Default)]
118pub struct UiUpdateQueue {
119 pending: Mutex<Vec<QueuedHookUpdate>>,
120 focus_request: Mutex<Option<UiId>>,
121 frame_requested: AtomicBool,
122 wake: RwLock<Option<UiWake>>,
123}
124
125impl UiUpdateQueue {
126 pub fn new() -> Self {
127 Self::default()
128 }
129
130 pub fn set_wake(&self, wake: UiWake) {
131 *self.wake.write().expect("hook wake lock poisoned") = Some(wake);
132 }
133
134 pub fn clear_wake(&self) {
135 *self.wake.write().expect("hook wake lock poisoned") = None;
136 }
137
138 pub fn request_frame(&self) {
139 self.frame_requested.store(true, Ordering::Release);
140 self.wake();
141 }
142
143 pub fn take_frame_request(&self) -> bool {
144 self.frame_requested.swap(false, Ordering::AcqRel)
145 }
146
147 pub fn invalidate(&self, owner: ComponentId, invalidation_id: UiId) {
148 self.pending
149 .lock()
150 .expect("hook update queue poisoned")
151 .push(QueuedHookUpdate {
152 owner,
153 invalidation_id,
154 apply: Box::new(|_| true),
155 });
156 self.wake();
157 }
158
159 pub fn request_focus(&self, target: UiId) {
160 *self
161 .focus_request
162 .lock()
163 .expect("hook focus request lock poisoned") = Some(target);
164 self.wake();
165 }
166
167 pub fn take_focus_request(&self) -> Option<UiId> {
168 self.focus_request
169 .lock()
170 .expect("hook focus request lock poisoned")
171 .take()
172 }
173
174 pub fn enqueue<T>(&self, owner: ComponentId, invalidation_id: UiId, id: HookId, value: T)
175 where
176 T: Send + 'static,
177 {
178 self.enqueue_update(owner, invalidation_id, id, move |current: &mut T| {
179 *current = value
180 });
181 }
182
183 pub fn enqueue_update<T>(
184 &self,
185 owner: ComponentId,
186 invalidation_id: UiId,
187 id: HookId,
188 update: impl FnOnce(&mut T) + Send + 'static,
189 ) where
190 T: 'static,
191 {
192 self.pending
193 .lock()
194 .expect("hook update queue poisoned")
195 .push(QueuedHookUpdate {
196 owner,
197 invalidation_id,
198 apply: Box::new(move |store| store.update_existing(id, update)),
199 });
200 self.wake();
201 }
202
203 pub fn apply(&self, store: &HookStateStore, components: &ComponentTree) -> Vec<UiId> {
204 let pending =
205 std::mem::take(&mut *self.pending.lock().expect("hook update queue poisoned"));
206 let mut dirty = HashSet::new();
207 for update in pending {
208 if components.is_alive(update.owner) && (update.apply)(store) {
209 components.mark_dirty(update.owner);
210 dirty.insert(update.invalidation_id);
211 }
212 }
213 dirty.into_iter().collect()
214 }
215
216 pub fn is_empty(&self) -> bool {
217 self.pending
218 .lock()
219 .expect("hook update queue poisoned")
220 .is_empty()
221 && self
222 .focus_request
223 .lock()
224 .expect("hook focus request lock poisoned")
225 .is_none()
226 && !self.frame_requested.load(Ordering::Acquire)
227 }
228
229 pub fn clear(&self) {
230 self.pending
231 .lock()
232 .expect("hook update queue poisoned")
233 .clear();
234 self.focus_request
235 .lock()
236 .expect("hook focus request lock poisoned")
237 .take();
238 self.frame_requested.store(false, Ordering::Release);
239 }
240
241 fn wake(&self) {
242 if let Some(wake) = self
243 .wake
244 .read()
245 .expect("hook wake lock poisoned")
246 .as_ref()
247 .cloned()
248 {
249 wake();
250 }
251 }
252}
253
254#[cfg(test)]
255#[path = "hook_state_test.rs"]
256mod tests;