lgui_core/core/component/
component_state.rs1use std::{
2 any::Any,
3 cell::{Cell, RefCell},
4 collections::{HashMap, HashSet},
5};
6
7use super::{ComponentId, CompositingLayerSpec, UiAction, UiId, UiScope};
8
9#[derive(Clone, Debug, Default, PartialEq, Eq)]
10pub struct ComponentActionOutcome {
11 pub handled: bool,
12 pub changed: bool,
13 pub events: Vec<UiAction>,
14}
15
16impl ComponentActionOutcome {
17 pub const fn ignored() -> Self {
18 Self {
19 handled: false,
20 changed: false,
21 events: Vec::new(),
22 }
23 }
24
25 pub const fn handled(changed: bool) -> Self {
26 Self {
27 handled: true,
28 changed,
29 events: Vec::new(),
30 }
31 }
32
33 pub fn emit(mut self, event: UiAction) -> Self {
34 self.events.push(event);
35 self
36 }
37}
38
39impl From<bool> for ComponentActionOutcome {
40 fn from(changed: bool) -> Self {
41 if changed {
42 Self::handled(true)
43 } else {
44 Self::ignored()
45 }
46 }
47}
48
49pub trait ComponentState: Any {
50 fn as_any(&self) -> &dyn Any;
51
52 fn as_any_mut(&mut self) -> &mut dyn Any;
53
54 fn handle_action(&mut self, _action: &UiAction) -> ComponentActionOutcome {
55 ComponentActionOutcome::ignored()
56 }
57
58 fn advance(&mut self, _elapsed_ms: f32) -> bool {
59 false
60 }
61
62 fn wants_frame(&self) -> bool {
63 false
64 }
65
66 fn frame_interval_ms(&self) -> u64 {
67 16
68 }
69
70 fn take_route_invalidation(&mut self) -> bool {
71 false
72 }
73}
74
75pub trait CompositingLayerAnimation: Clone + Default + 'static {
80 fn advance(&mut self, elapsed_ms: f32) -> bool;
81
82 fn compositing_layer_spec(&self) -> CompositingLayerSpec;
83
84 fn wants_frame(&self) -> bool {
85 true
86 }
87
88 fn frame_interval_ms(&self) -> u64 {
89 16
90 }
91}
92
93#[derive(Clone, Default)]
94struct CompositingLayerAnimationState<T>(T);
95
96impl<T> ComponentState for CompositingLayerAnimationState<T>
97where
98 T: CompositingLayerAnimation,
99{
100 fn as_any(&self) -> &dyn Any {
101 self
102 }
103
104 fn as_any_mut(&mut self) -> &mut dyn Any {
105 self
106 }
107
108 fn advance(&mut self, elapsed_ms: f32) -> bool {
109 self.0.advance(elapsed_ms)
110 }
111
112 fn wants_frame(&self) -> bool {
113 self.0.wants_frame()
114 }
115
116 fn frame_interval_ms(&self) -> u64 {
117 self.0.frame_interval_ms()
118 }
119}
120
121type CompositingLayerProjection = fn(&dyn ComponentState) -> CompositingLayerSpec;
122
123#[derive(Clone)]
124pub(crate) enum ComponentStateBinding {
125 Component {
126 owner: ComponentId,
127 invalidation_id: UiId,
128 },
129 CompositingLayer {
130 target_id: UiId,
131 project: CompositingLayerProjection,
132 },
133}
134
135#[derive(Clone, Copy, Debug, PartialEq, Eq)]
136pub(crate) enum RetainedNodeUpdate {
137 CompositingLayer(CompositingLayerSpec),
138}
139
140pub(crate) struct ComponentStateInvalidation {
141 pub state_id: UiId,
142 pub target_id: UiId,
143 pub owner: Option<ComponentId>,
144 pub frame_interval_ms: u64,
145 pub retained_update: Option<RetainedNodeUpdate>,
146}
147
148struct ComponentStateEntry {
149 state: Box<dyn ComponentState>,
150 binding: Option<ComponentStateBinding>,
151}
152
153#[derive(Default)]
154pub struct ComponentStateStore {
155 states: RefCell<HashMap<UiId, ComponentStateEntry>>,
156 seen: RefCell<HashSet<UiId>>,
157 rollback: RefCell<HashMap<UiId, Option<ComponentStateEntry>>>,
158 tracking_frame: Cell<bool>,
159}
160
161impl ComponentStateStore {
162 pub fn new() -> Self {
163 Self::default()
164 }
165
166 pub fn begin_frame(&self) {
167 if self.tracking_frame.get() {
168 self.abort_frame();
169 }
170 self.seen.borrow_mut().clear();
171 self.rollback.borrow_mut().clear();
172 self.tracking_frame.set(true);
173 }
174
175 pub fn end_frame(&self) {
176 let seen = self.seen.borrow();
177 self.states.borrow_mut().retain(|id, _| seen.contains(id));
178 self.tracking_frame.set(false);
179 drop(seen);
180 self.seen.borrow_mut().clear();
181 self.rollback.borrow_mut().clear();
182 }
183
184 pub fn abort_frame(&self) {
185 let rollback = std::mem::take(&mut *self.rollback.borrow_mut());
186 let mut states = self.states.borrow_mut();
187 for (id, previous) in rollback {
188 match previous {
189 Some(previous) => {
190 states.insert(id, previous);
191 }
192 None => {
193 states.remove(&id);
194 }
195 }
196 }
197 self.tracking_frame.set(false);
198 self.seen.borrow_mut().clear();
199 }
200
201 pub fn with_mut<T, R>(&self, id: &UiId, f: impl FnOnce(&mut T) -> R) -> R
202 where
203 T: ComponentState + Clone + Default + 'static,
204 {
205 self.with_mut_inner(id, None, f)
206 }
207
208 pub fn with_mut_for_component<T, R>(
209 &self,
210 id: &UiId,
211 owner: ComponentId,
212 invalidation_id: UiId,
213 f: impl FnOnce(&mut T) -> R,
214 ) -> R
215 where
216 T: ComponentState + Clone + Default + 'static,
217 {
218 self.with_mut_inner(
219 id,
220 Some(ComponentStateBinding::Component {
221 owner,
222 invalidation_id,
223 }),
224 f,
225 )
226 }
227
228 pub(crate) fn with_mut_for_compositing_layer<T, R>(
229 &self,
230 id: &UiId,
231 target_id: UiId,
232 f: impl FnOnce(&mut T) -> R,
233 ) -> (R, CompositingLayerSpec, bool)
234 where
235 T: CompositingLayerAnimation,
236 {
237 fn project<T>(state: &dyn ComponentState) -> CompositingLayerSpec
238 where
239 T: CompositingLayerAnimation,
240 {
241 state
242 .as_any()
243 .downcast_ref::<CompositingLayerAnimationState<T>>()
244 .expect("compositing layer animation type mismatch for UiId")
245 .0
246 .compositing_layer_spec()
247 }
248
249 self.with_mut_inner(
250 id,
251 Some(ComponentStateBinding::CompositingLayer {
252 target_id,
253 project: project::<T>,
254 }),
255 |state: &mut CompositingLayerAnimationState<T>| {
256 let result = f(&mut state.0);
257 (
258 result,
259 state.0.compositing_layer_spec(),
260 state.0.wants_frame(),
261 )
262 },
263 )
264 }
265
266 fn with_mut_inner<T, R>(
267 &self,
268 id: &UiId,
269 binding: Option<ComponentStateBinding>,
270 f: impl FnOnce(&mut T) -> R,
271 ) -> R
272 where
273 T: ComponentState + Clone + Default + 'static,
274 {
275 self.mark_seen(id);
276 let mut states = self.states.borrow_mut();
277 if self.tracking_frame.get() && !self.rollback.borrow().contains_key(id) {
278 let previous = states.get(id).map(|entry| ComponentStateEntry {
279 state: Box::new(
280 entry
281 .state
282 .as_any()
283 .downcast_ref::<T>()
284 .expect("component state type mismatch for UiId")
285 .clone(),
286 ) as Box<dyn ComponentState>,
287 binding: entry.binding.clone(),
288 });
289 self.rollback.borrow_mut().insert(id.clone(), previous);
290 }
291 let entry = states
292 .entry(id.clone())
293 .or_insert_with(|| ComponentStateEntry {
294 state: Box::<T>::default(),
295 binding: None,
296 });
297 entry.binding = binding;
298 let state = entry
299 .state
300 .as_any_mut()
301 .downcast_mut::<T>()
302 .expect("component state type mismatch for UiId");
303 f(state)
304 }
305
306 pub fn handle_action(&self, id: &UiId, action: &UiAction) -> ComponentActionOutcome {
307 self.states
308 .borrow_mut()
309 .get_mut(id)
310 .map_or_else(ComponentActionOutcome::ignored, |entry| {
311 entry.state.handle_action(action)
312 })
313 }
314
315 pub fn contains(&self, id: &UiId) -> bool {
316 self.states.borrow().contains_key(id)
317 }
318
319 pub fn take_route_invalidation(&self, id: &UiId) -> bool {
320 self.states
321 .borrow_mut()
322 .get_mut(id)
323 .is_some_and(|entry| entry.state.take_route_invalidation())
324 }
325
326 pub fn advance(&self, elapsed_ms: f32) -> Vec<UiId> {
327 self.advance_invalidations(elapsed_ms)
328 .into_iter()
329 .map(|invalidation| invalidation.state_id)
330 .collect()
331 }
332
333 pub(crate) fn requested_frame_interval_ms(&self) -> Option<u64> {
334 self.states
335 .borrow()
336 .values()
337 .filter(|entry| entry.state.wants_frame())
338 .map(|entry| entry.state.frame_interval_ms().max(1))
339 .min()
340 }
341
342 pub(crate) fn advance_invalidations(&self, elapsed_ms: f32) -> Vec<ComponentStateInvalidation> {
343 let mut dirty = Vec::new();
344 for (id, entry) in self.states.borrow_mut().iter_mut() {
345 if entry.state.advance(elapsed_ms) {
346 let (target_id, owner, retained_update) = match entry.binding.as_ref() {
347 Some(ComponentStateBinding::Component {
348 owner,
349 invalidation_id,
350 }) => (invalidation_id.clone(), Some(*owner), None),
351 Some(ComponentStateBinding::CompositingLayer { target_id, project }) => (
352 target_id.clone(),
353 None,
354 Some(RetainedNodeUpdate::CompositingLayer(project(
355 entry.state.as_ref(),
356 ))),
357 ),
358 None => (id.clone(), None, None),
359 };
360 dirty.push(ComponentStateInvalidation {
361 state_id: id.clone(),
362 target_id,
363 owner,
364 frame_interval_ms: entry.state.frame_interval_ms().max(1),
365 retained_update,
366 });
367 }
368 }
369 dirty
370 }
371
372 #[cfg(test)]
373 pub fn with<T, R>(&self, id: &UiId, f: impl FnOnce(&T) -> R) -> Option<R>
374 where
375 T: ComponentState + 'static,
376 {
377 self.mark_seen(id);
378 let states = self.states.borrow();
379 let state = states.get(id)?.state.as_any().downcast_ref::<T>()?;
380 Some(f(state))
381 }
382
383 pub fn preserve_scope(&self, scope: &UiScope) {
384 if !self.tracking_frame.get() {
385 return;
386 }
387 let scope_id = scope.scope_id();
388 let prefix = scope_id.as_str();
389 let nested_prefix = format!("{prefix}.");
390 let states = self.states.borrow();
391 let mut seen = self.seen.borrow_mut();
392 seen.extend(
393 states
394 .keys()
395 .filter(|id| id.as_str() == prefix || id.as_str().starts_with(&nested_prefix))
396 .cloned(),
397 );
398 }
399
400 fn mark_seen(&self, id: &UiId) {
401 if self.tracking_frame.get() {
402 self.seen.borrow_mut().insert(id.clone());
403 }
404 }
405}
406
407#[cfg(test)]
408#[path = "component_state_test.rs"]
409mod tests;