fret_ui/tree/ui_tree_invalidation_walk/
propagate.rs1use super::super::*;
2
3impl<H: UiHost> UiTree<H> {
4 pub fn propagate_pending_model_changes(&mut self, app: &mut H) -> bool {
10 let changed = app.take_changed_models();
11 self.propagate_model_changes(app, &changed)
12 }
13
14 fn propagate_observation_masks(
15 &mut self,
16 app: &mut H,
17 masks: impl IntoIterator<Item = (NodeId, ObservationMask)>,
18 source: UiDebugInvalidationSource,
19 ) -> bool {
20 self.propagation_depth_generation = self.propagation_depth_generation.wrapping_add(1);
21 if self.propagation_depth_generation == 0 {
22 self.propagation_depth_generation = 1;
23 self.propagation_depth_cache.clear();
24 }
25 self.propagation_chain.clear();
26 self.propagation_entries.clear();
27
28 for (node, mask) in masks {
29 if mask.is_empty() || !self.nodes.contains_key(node) {
30 continue;
31 }
32
33 let (strength, inv) = if mask.hit_test {
34 (3, Invalidation::HitTest)
35 } else if mask.layout {
36 (2, Invalidation::Layout)
37 } else if mask.paint {
38 (1, Invalidation::Paint)
39 } else {
40 continue;
41 };
42
43 let depth = propagation_depth::propagation_depth_for(self, node);
44 let key = node.data().as_ffi();
45 self.propagation_entries
46 .push((strength, depth, key, node, inv));
47 }
48
49 if self.propagation_entries.is_empty() {
50 return false;
51 }
52
53 self.propagation_entries.sort_by(|a, b| {
54 b.0.cmp(&a.0)
56 .then(a.1.cmp(&b.1))
58 .then(a.2.cmp(&b.2))
60 });
61
62 let mut did_invalidate = false;
63 let mut visited = std::mem::take(&mut self.invalidation_dedup);
64 visited.begin();
65 let mut entries = std::mem::take(&mut self.propagation_entries);
66 for (_, _, _, node, inv) in entries.drain(..) {
67 self.mark_invalidation_dedup_with_source(node, inv, &mut visited, source);
68 did_invalidate = true;
69 }
70 self.invalidation_dedup = visited;
71 self.propagation_entries = entries;
72
73 if did_invalidate {
74 self.request_redraw_coalesced(app);
75 }
76
77 did_invalidate
78 }
79
80 fn propagate_model_changes_from_elements(&mut self, app: &mut H, changed: &[ModelId]) -> bool {
81 let Some(window) = self.window else {
82 return false;
83 };
84 if changed.is_empty() {
85 return false;
86 }
87
88 let changed: std::collections::HashSet<ModelId> = changed.iter().copied().collect();
89 let frame_id = app.frame_id();
90 let mut combined: HashMap<NodeId, ObservationMask> = HashMap::new();
91
92 app.with_global_mut_untracked(crate::elements::ElementRuntime::new, |runtime, _app| {
93 let Some(window_state) = runtime.for_window(window) else {
94 return;
95 };
96 window_state.for_each_observed_model_for_invalidation(
97 frame_id,
98 |element, observations| {
99 let mut mask = ObservationMask::default();
100 for (model, inv) in observations {
101 if changed.contains(model) {
102 mask.add(*inv);
103 }
104 }
105 if mask.is_empty() {
106 return;
107 }
108 let seeded = window_state.node_entry(element).map(|e| e.node);
109 let Some(node) =
110 self.resolve_live_attached_node_for_element_seeded(element, seeded)
111 else {
112 return;
113 };
114 combined
115 .entry(node)
116 .and_modify(|m| *m = m.union(mask))
117 .or_insert(mask);
118 },
119 );
120 });
121
122 if combined.is_empty() {
123 return false;
124 }
125 self.propagate_observation_masks(app, combined, UiDebugInvalidationSource::ModelChange)
126 }
127
128 fn propagate_global_changes_from_elements(&mut self, app: &mut H, changed: &[TypeId]) -> bool {
129 let Some(window) = self.window else {
130 return false;
131 };
132 if changed.is_empty() {
133 return false;
134 }
135
136 let changed: std::collections::HashSet<TypeId> = changed.iter().copied().collect();
137 let frame_id = app.frame_id();
138 let mut combined: HashMap<NodeId, ObservationMask> = HashMap::new();
139
140 app.with_global_mut_untracked(crate::elements::ElementRuntime::new, |runtime, _app| {
141 let Some(window_state) = runtime.for_window(window) else {
142 return;
143 };
144 window_state.for_each_observed_global_for_invalidation(
145 frame_id,
146 |element, observations| {
147 let mut mask = ObservationMask::default();
148 for (global, inv) in observations {
149 if changed.contains(global) {
150 mask.add(*inv);
151 }
152 }
153 if mask.is_empty() {
154 return;
155 }
156 let seeded = window_state.node_entry(element).map(|e| e.node);
157 let Some(node) =
158 self.resolve_live_attached_node_for_element_seeded(element, seeded)
159 else {
160 return;
161 };
162 combined
163 .entry(node)
164 .and_modify(|m| *m = m.union(mask))
165 .or_insert(mask);
166 },
167 );
168 });
169
170 if combined.is_empty() {
171 return false;
172 }
173 self.propagate_observation_masks(app, combined, UiDebugInvalidationSource::GlobalChange)
174 }
175
176 pub fn propagate_model_changes(&mut self, app: &mut H, changed: &[ModelId]) -> bool {
177 if changed.is_empty() {
178 return false;
179 }
180 let frame_id = app.frame_id();
181 #[cfg(debug_assertions)]
182 self.debug_forbid_propagate_after_declarative_render_root(frame_id);
183 self.begin_debug_frame_if_needed(frame_id);
184 if self.debug_enabled {
185 self.debug_model_change_hotspots.clear();
186 self.debug_model_change_unobserved.clear();
187 }
188
189 let mut did_invalidate = false;
190
191 if changed.len() == 1 {
192 let model = changed[0];
193 let layout_nodes = self.observed_in_layout.by_model.get(&model);
194 let paint_nodes = self.observed_in_paint.by_model.get(&model);
195 if let (Some(nodes), None) | (None, Some(nodes)) = (layout_nodes, paint_nodes) {
196 let masks: Vec<(NodeId, ObservationMask)> =
198 nodes.iter().map(|(&n, &m)| (n, m)).collect();
199 if self.debug_enabled {
200 self.debug_stats.model_change_invalidation_roots =
201 masks.len().min(u32::MAX as usize) as u32;
202 self.debug_stats.model_change_models = 1;
203 self.debug_stats.model_change_observation_edges =
204 masks.len().min(u32::MAX as usize) as u32;
205 self.debug_stats.model_change_unobserved_models = 0;
206 self.debug_model_change_hotspots = vec![UiDebugModelChangeHotspot {
207 model,
208 observation_edges: masks.len().min(u32::MAX as usize) as u32,
209 changed: app.models().debug_last_changed_info_for_id(model),
210 }];
211 }
212 did_invalidate |= self.propagate_observation_masks(
213 app,
214 masks,
215 UiDebugInvalidationSource::ModelChange,
216 );
217 did_invalidate |= self.propagate_model_changes_from_elements(app, changed);
218 return did_invalidate;
219 }
220 }
221
222 let mut combined_capacity = 0usize;
225 for &model in changed {
226 if let Some(nodes) = self.observed_in_layout.by_model.get(&model) {
227 combined_capacity = combined_capacity.saturating_add(nodes.len());
228 }
229 if let Some(nodes) = self.observed_in_paint.by_model.get(&model) {
230 combined_capacity = combined_capacity.saturating_add(nodes.len());
231 }
232 }
233 combined_capacity = combined_capacity.min(self.nodes.len());
234
235 let mut combined: HashMap<NodeId, ObservationMask> =
236 HashMap::with_capacity(combined_capacity.max(changed.len().saturating_mul(8)));
237 let mut observation_edges_scanned = 0usize;
238 let mut unobserved_models = 0usize;
239 for &model in changed {
240 let mut edges = 0usize;
241 if let Some(nodes) = self.observed_in_layout.by_model.get(&model) {
242 observation_edges_scanned = observation_edges_scanned.saturating_add(nodes.len());
243 edges = edges.saturating_add(nodes.len());
244 for (&node, &mask) in nodes {
245 combined
246 .entry(node)
247 .and_modify(|m| *m = m.union(mask))
248 .or_insert(mask);
249 }
250 }
251 if let Some(nodes) = self.observed_in_paint.by_model.get(&model) {
252 observation_edges_scanned = observation_edges_scanned.saturating_add(nodes.len());
253 edges = edges.saturating_add(nodes.len());
254 for (&node, &mask) in nodes {
255 combined
256 .entry(node)
257 .and_modify(|m| *m = m.union(mask))
258 .or_insert(mask);
259 }
260 }
261 if self.debug_enabled && edges > 0 {
262 self.debug_model_change_hotspots
263 .push(UiDebugModelChangeHotspot {
264 model,
265 observation_edges: edges.min(u32::MAX as usize) as u32,
266 changed: app.models().debug_last_changed_info_for_id(model),
267 });
268 }
269 if edges == 0 {
270 unobserved_models = unobserved_models.saturating_add(1);
271 if self.debug_enabled {
272 self.debug_model_change_unobserved
273 .push(UiDebugModelChangeUnobserved {
274 model,
275 created: app.models().debug_created_info_for_id(model),
276 changed: app.models().debug_last_changed_info_for_id(model),
277 });
278 }
279 }
280 }
281
282 if self.debug_enabled {
283 self.debug_stats.model_change_invalidation_roots =
284 combined.len().min(u32::MAX as usize) as u32;
285 self.debug_stats.model_change_models = changed.len().min(u32::MAX as usize) as u32;
286 self.debug_stats.model_change_observation_edges =
287 observation_edges_scanned.min(u32::MAX as usize) as u32;
288 self.debug_stats.model_change_unobserved_models =
289 unobserved_models.min(u32::MAX as usize) as u32;
290
291 self.debug_model_change_hotspots
292 .sort_by(|a, b| b.observation_edges.cmp(&a.observation_edges));
293 self.debug_model_change_hotspots.truncate(5);
294
295 self.debug_model_change_unobserved
296 .sort_by(|a, b| a.model.data().as_ffi().cmp(&b.model.data().as_ffi()));
297 self.debug_model_change_unobserved.truncate(5);
298 }
299 did_invalidate |=
300 self.propagate_observation_masks(app, combined, UiDebugInvalidationSource::ModelChange);
301 did_invalidate |= self.propagate_model_changes_from_elements(app, changed);
302 did_invalidate
303 }
304
305 pub fn propagate_global_changes(&mut self, app: &mut H, changed: &[TypeId]) -> bool {
306 if changed.is_empty() {
307 return false;
308 }
309 let frame_id = app.frame_id();
310 #[cfg(debug_assertions)]
311 self.debug_forbid_propagate_after_declarative_render_root(frame_id);
312 self.begin_debug_frame_if_needed(frame_id);
313 if self.debug_enabled {
314 self.debug_global_change_hotspots.clear();
315 self.debug_global_change_unobserved.clear();
316 }
317
318 let mut did_invalidate = false;
319
320 if changed.len() == 1 {
321 let global = changed[0];
322 let layout_nodes = self.observed_globals_in_layout.by_global.get(&global);
323 let paint_nodes = self.observed_globals_in_paint.by_global.get(&global);
324 if let (Some(nodes), None) | (None, Some(nodes)) = (layout_nodes, paint_nodes) {
325 let masks: Vec<(NodeId, ObservationMask)> =
327 nodes.iter().map(|(&n, &m)| (n, m)).collect();
328 if self.debug_enabled {
329 self.debug_stats.global_change_invalidation_roots =
330 masks.len().min(u32::MAX as usize) as u32;
331 self.debug_stats.global_change_globals = 1;
332 self.debug_stats.global_change_observation_edges =
333 masks.len().min(u32::MAX as usize) as u32;
334 self.debug_stats.global_change_unobserved_globals = 0;
335 }
336 did_invalidate |= self.propagate_observation_masks(
337 app,
338 masks,
339 UiDebugInvalidationSource::GlobalChange,
340 );
341 did_invalidate |= self.propagate_global_changes_from_elements(app, changed);
342 return did_invalidate;
343 }
344 }
345
346 let mut combined_capacity = 0usize;
349 for &global in changed {
350 if let Some(nodes) = self.observed_globals_in_layout.by_global.get(&global) {
351 combined_capacity = combined_capacity.saturating_add(nodes.len());
352 }
353 if let Some(nodes) = self.observed_globals_in_paint.by_global.get(&global) {
354 combined_capacity = combined_capacity.saturating_add(nodes.len());
355 }
356 }
357 combined_capacity = combined_capacity.min(self.nodes.len());
358
359 let mut combined: HashMap<NodeId, ObservationMask> =
360 HashMap::with_capacity(combined_capacity.max(changed.len().saturating_mul(8)));
361 let mut observation_edges_scanned = 0usize;
362 let mut unobserved_globals = 0usize;
363 for &global in changed {
364 let mut edges = 0usize;
365 if let Some(nodes) = self.observed_globals_in_layout.by_global.get(&global) {
366 observation_edges_scanned = observation_edges_scanned.saturating_add(nodes.len());
367 edges = edges.saturating_add(nodes.len());
368 for (&node, &mask) in nodes {
369 combined
370 .entry(node)
371 .and_modify(|m| *m = m.union(mask))
372 .or_insert(mask);
373 }
374 }
375 if let Some(nodes) = self.observed_globals_in_paint.by_global.get(&global) {
376 observation_edges_scanned = observation_edges_scanned.saturating_add(nodes.len());
377 edges = edges.saturating_add(nodes.len());
378 for (&node, &mask) in nodes {
379 combined
380 .entry(node)
381 .and_modify(|m| *m = m.union(mask))
382 .or_insert(mask);
383 }
384 }
385 if self.debug_enabled && edges > 0 {
386 self.debug_global_change_hotspots
387 .push(UiDebugGlobalChangeHotspot {
388 global,
389 observation_edges: edges.min(u32::MAX as usize) as u32,
390 });
391 }
392 if edges == 0 {
393 unobserved_globals = unobserved_globals.saturating_add(1);
394 if self.debug_enabled {
395 self.debug_global_change_unobserved
396 .push(UiDebugGlobalChangeUnobserved { global });
397 }
398 }
399 }
400
401 if self.debug_enabled {
402 self.debug_stats.global_change_invalidation_roots =
403 combined.len().min(u32::MAX as usize) as u32;
404 self.debug_stats.global_change_globals = changed.len().min(u32::MAX as usize) as u32;
405 self.debug_stats.global_change_observation_edges =
406 observation_edges_scanned.min(u32::MAX as usize) as u32;
407 self.debug_stats.global_change_unobserved_globals =
408 unobserved_globals.min(u32::MAX as usize) as u32;
409
410 self.debug_global_change_hotspots
411 .sort_by(|a, b| b.observation_edges.cmp(&a.observation_edges));
412 self.debug_global_change_hotspots.truncate(5);
413
414 self.debug_global_change_unobserved
415 .sort_by_key(|u| type_id_sort_key(u.global));
416 self.debug_global_change_unobserved.truncate(5);
417 }
418 did_invalidate |= self.propagate_observation_masks(
419 app,
420 combined,
421 UiDebugInvalidationSource::GlobalChange,
422 );
423 did_invalidate |= self.propagate_global_changes_from_elements(app, changed);
424 did_invalidate
425 }
426}