Skip to main content

lgui_core/core/component/
hook_state.rs

1use 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;