lgui_core/core/component/
effect.rs1use std::{any::Any, cell::RefCell, collections::HashMap};
2
3use super::{ComponentTree, HookId};
4
5pub type UiEffect = Box<dyn FnOnce() + 'static>;
6
7pub trait IntoEffectCleanup {
8 fn into_cleanup(self) -> Option<UiEffect>;
9}
10
11impl IntoEffectCleanup for () {
12 fn into_cleanup(self) -> Option<UiEffect> {
13 None
14 }
15}
16
17impl<F> IntoEffectCleanup for F
18where
19 F: FnOnce() + 'static,
20{
21 fn into_cleanup(self) -> Option<UiEffect> {
22 Some(Box::new(self))
23 }
24}
25
26struct EffectEntry {
27 deps: Box<dyn Any>,
28 generation: u64,
29 cleanup: Option<UiEffect>,
30}
31
32struct StagedEffect {
33 id: HookId,
34 deps: Box<dyn Any>,
35 deps_equal: fn(&dyn Any, &dyn Any) -> bool,
36 run: Box<dyn FnOnce() -> Option<UiEffect> + 'static>,
37}
38
39struct PendingEffect {
40 id: HookId,
41 generation: u64,
42 run: Box<dyn FnOnce() -> Option<UiEffect> + 'static>,
43}
44
45#[derive(Default)]
46pub struct EffectRegistry {
47 entries: RefCell<HashMap<HookId, EffectEntry>>,
48 staged: RefCell<Vec<StagedEffect>>,
49 pending_cleanups: RefCell<Vec<UiEffect>>,
50 pending_effects: RefCell<Vec<PendingEffect>>,
51}
52
53impl EffectRegistry {
54 pub fn new() -> Self {
55 Self::default()
56 }
57
58 pub fn register<D, F, R>(&self, id: HookId, deps: D, effect: F)
59 where
60 D: Clone + PartialEq + 'static,
61 F: FnOnce() -> R + 'static,
62 R: IntoEffectCleanup,
63 {
64 self.register_erased(
65 id,
66 Box::new(deps),
67 deps_equal::<D>,
68 Box::new(move || effect().into_cleanup()),
69 );
70 }
71
72 pub(super) fn register_erased(
73 &self,
74 id: HookId,
75 deps: Box<dyn Any>,
76 deps_equal: fn(&dyn Any, &dyn Any) -> bool,
77 run: Box<dyn FnOnce() -> Option<UiEffect> + 'static>,
78 ) {
79 let mut staged = self.staged.borrow_mut();
80 if staged.iter().any(|candidate| candidate.id == id) {
81 panic!("effect hook `{id}` was registered more than once in one render");
82 }
83 staged.push(StagedEffect {
84 id,
85 deps,
86 deps_equal,
87 run,
88 });
89 }
90
91 pub fn begin_frame(&self) {
92 self.staged.borrow_mut().clear();
93 }
94
95 pub fn abort_frame(&self) {
96 self.staged.borrow_mut().clear();
97 }
98
99 pub fn end_frame(&self, components: &ComponentTree) {
100 let staged = self.staged.borrow_mut().drain(..).collect::<Vec<_>>();
101 for candidate in staged {
102 if !components.is_alive(candidate.id.component()) {
103 continue;
104 }
105 let generation = {
106 let mut entries = self.entries.borrow_mut();
107 match entries.get_mut(&candidate.id) {
108 Some(entry) => {
109 if (candidate.deps_equal)(entry.deps.as_ref(), candidate.deps.as_ref()) {
110 continue;
111 }
112 if let Some(cleanup) = entry.cleanup.take() {
113 self.pending_cleanups.borrow_mut().push(cleanup);
114 }
115 entry.deps = candidate.deps;
116 entry.generation = entry.generation.wrapping_add(1);
117 entry.generation
118 }
119 None => {
120 entries.insert(
121 candidate.id,
122 EffectEntry {
123 deps: candidate.deps,
124 generation: 1,
125 cleanup: None,
126 },
127 );
128 1
129 }
130 }
131 };
132 self.pending_effects.borrow_mut().push(PendingEffect {
133 id: candidate.id,
134 generation,
135 run: candidate.run,
136 });
137 }
138
139 let removed = {
140 let mut entries = self.entries.borrow_mut();
141 let mut removed_ids: Vec<HookId> = entries
142 .keys()
143 .filter(|id| !components.is_alive(id.component()))
144 .cloned()
145 .collect();
146 removed_ids.sort_unstable();
147 removed_ids
148 .into_iter()
149 .filter_map(|id| entries.remove(&id).and_then(|entry| entry.cleanup))
150 .collect::<Vec<_>>()
151 };
152 self.pending_cleanups.borrow_mut().extend(removed);
153 self.pending_effects
154 .borrow_mut()
155 .retain(|pending| components.is_alive(pending.id.component()));
156 }
157
158 pub fn run_pending(&self) {
159 let cleanups = self
160 .pending_cleanups
161 .borrow_mut()
162 .drain(..)
163 .collect::<Vec<_>>();
164 for cleanup in cleanups {
165 cleanup();
166 }
167
168 let effects = self
169 .pending_effects
170 .borrow_mut()
171 .drain(..)
172 .collect::<Vec<_>>();
173 for pending in effects {
174 let is_current = self
175 .entries
176 .borrow()
177 .get(&pending.id)
178 .is_some_and(|entry| entry.generation == pending.generation);
179 if !is_current {
180 continue;
181 }
182 let cleanup = (pending.run)();
183 if let Some(entry) = self.entries.borrow_mut().get_mut(&pending.id) {
184 if entry.generation == pending.generation {
185 entry.cleanup = cleanup;
186 }
187 }
188 }
189 }
190
191 pub fn clear(&self) {
192 self.staged.borrow_mut().clear();
193 self.pending_effects.borrow_mut().clear();
194 let mut cleanups = self
195 .pending_cleanups
196 .borrow_mut()
197 .drain(..)
198 .collect::<Vec<_>>();
199 cleanups.extend(
200 self.entries
201 .borrow_mut()
202 .drain()
203 .filter_map(|(_, entry)| entry.cleanup),
204 );
205 for cleanup in cleanups {
206 cleanup();
207 }
208 }
209}
210
211fn deps_equal<D>(left: &dyn Any, right: &dyn Any) -> bool
212where
213 D: PartialEq + 'static,
214{
215 let left = left
216 .downcast_ref::<D>()
217 .unwrap_or_else(|| panic!("effect dependency type changed between renders"));
218 let right = right
219 .downcast_ref::<D>()
220 .unwrap_or_else(|| panic!("effect dependency type changed during render"));
221 left == right
222}
223
224#[cfg(test)]
225#[path = "effect_test.rs"]
226mod tests;