1use super::{reconcile::*, *};
2
3impl HostRuntime {
4 #[cfg(test)]
5 pub fn new() -> Self {
6 Self::default()
7 }
8
9 #[cfg(test)]
10 pub fn commit(
11 &mut self,
12 tree: &HostTree,
13 interaction: &UiInteractionState,
14 viewport: UiRect,
15 invalidations: &mut InvalidationSet,
16 ) -> HostCommit {
17 let next_sources = tree
18 .nodes()
19 .iter()
20 .map(|node| node.id.clone())
21 .collect::<HashSet<_>>();
22 let changes = crate::core::ProjectionChanges {
23 changed: next_sources.clone(),
24 removed: self
25 .sources
26 .keys()
27 .filter(|source| !next_sources.contains(*source))
28 .cloned()
29 .collect(),
30 structure_changed: true,
31 ..crate::core::ProjectionChanges::default()
32 };
33 self.commit_projection(tree, interaction, viewport, invalidations, changes)
34 }
35
36 pub(crate) fn commit_projection(
37 &mut self,
38 tree: &HostTree,
39 interaction: &UiInteractionState,
40 viewport: UiRect,
41 invalidations: &mut InvalidationSet,
42 changes: crate::core::ProjectionChanges,
43 ) -> HostCommit {
44 #[cfg(feature = "diagnostics-timing")]
45 let change_scan_started = Instant::now();
46 let semantic_changed = changes.changed.clone();
47 let semantic_removed = changes.removed.clone();
48 let semantic_full = !self.initialized;
49 let mut mutations = Vec::new();
50 let mut dirty_scene_sources = HashSet::new();
51 let mut compositing_updates = HashMap::new();
52
53 let kind_changed_sources = changes
54 .changed
55 .iter()
56 .filter_map(|source| {
57 let id = self.sources.get(source)?;
58 tree.node(source)
59 .is_some_and(|next| self.node(*id).kind != next.kind)
60 .then(|| source.clone())
61 })
62 .collect::<Vec<_>>();
63 let scene_topology_changed = changes.changed.iter().any(|source| {
64 let Some(id) = self.sources.get(source) else {
65 return false;
66 };
67 let Some(next) = tree.node(source) else {
68 return false;
69 };
70 let previous = &self.node(*id).node;
71 previous.parent != next.parent
72 || previous.kind != next.kind
73 || previous.render_phase != next.render_phase
74 || previous.shadow.is_some() != next.shadow.is_some()
75 });
76 let scene_structure_changed =
77 changes.structure_changed || !changes.removed.is_empty() || scene_topology_changed;
78 #[cfg(feature = "diagnostics-timing")]
79 let change_scan_ms = elapsed_ms(change_scan_started);
80 #[cfg(feature = "diagnostics-timing")]
81 let node_patch_started = Instant::now();
82 let mut removed_sources = changes.removed.clone();
83 removed_sources.extend(kind_changed_sources);
84 let removed = removed_sources
85 .into_iter()
86 .filter_map(|source| self.sources.get(&source).copied().map(|id| (source, id)))
87 .collect::<Vec<_>>();
88 for (source, id) in &removed {
93 let old_bounds = self.node(*id).paint_bounds;
94 if let Some(owner) = self.scene_owner_source(*id) {
95 if let Some(bounds) = self
96 .sources
97 .get(&owner)
98 .and_then(|id| self.scene.get(id))
99 .and_then(|scene| super::scene::shadow_bounds(&scene.commands))
100 {
101 invalidations.invalidate_rect(bounds);
102 }
103 dirty_scene_sources.insert(owner);
104 }
105 mutations.push(HostMutation::RemoveNode {
106 id: *id,
107 source: source.clone(),
108 old_bounds,
109 });
110 }
111 for (_, id) in removed {
112 self.remove(id);
113 }
114
115 let initializing = !self.initialized;
116 let changed_nodes = if initializing {
117 tree.nodes().iter().map(|node| node.as_ref()).collect()
118 } else {
119 tree.changed_nodes(&changes.changed)
120 };
121 for node in &changed_nodes {
122 if !self.sources.contains_key(&node.id) {
123 let id = self.allocate(node.id.clone());
124 self.sources.insert(node.id.clone(), id);
125 }
126 }
127
128 let order_changed = if changes.structure_changed || !self.initialized {
129 let next_order = tree
130 .nodes()
131 .iter()
132 .map(|node| self.sources[&node.id])
133 .collect::<Vec<_>>();
134 let changed = self.initialized && self.paint_order != next_order;
135 self.paint_order = next_order;
136 changed
137 } else {
138 false
139 };
140
141 for node in &changed_nodes {
142 let id = self.sources[&node.id];
143 let parent = node
144 .parent
145 .as_ref()
146 .and_then(|parent| self.sources.get(parent))
147 .copied();
148 let children = node
149 .children
150 .iter()
151 .filter_map(|child| self.sources.get(child))
152 .copied()
153 .collect::<Vec<_>>();
154 let flags = interaction.flags_for(&node.id);
155 let existing = self.node(id);
156 let was_mounted = existing.mounted;
157 let old_layout = existing.layout_bounds;
158 let old_paint = existing.paint_bounds;
159 let old_parent = existing.parent;
160 let old_children = existing.children.clone();
161 let old_interaction = existing.interaction;
162 let next_paint = effective_node_paint_bounds(node);
163 let compositing_only = was_mounted
164 && compositing_spec_only_changed(&existing.node, node)
165 && !tree.has_shadow_ancestor(&node.id);
166
167 if !was_mounted {
168 mutations.push(HostMutation::InsertNode {
169 id,
170 source: node.id.clone(),
171 bounds: next_paint,
172 });
173 } else {
174 if old_layout != node.layout_rect || old_paint != next_paint {
175 mutations.push(HostMutation::UpdateProps {
176 id,
177 source: node.id.clone(),
178 kind: HostUpdateKind::Layout,
179 old_bounds: old_paint,
180 new_bounds: next_paint,
181 });
182 if !compositing_only {
183 dirty_scene_sources.insert(node.id.clone());
184 }
185 }
186 if paint_props_changed(&existing.node, node) {
187 mutations.push(HostMutation::UpdateProps {
188 id,
189 source: node.id.clone(),
190 kind: HostUpdateKind::Paint,
191 old_bounds: old_paint,
192 new_bounds: next_paint,
193 });
194 if compositing_only {
195 compositing_updates.insert(
196 node.id.clone(),
197 node.compositing_layer.expect("compositing layer spec"),
198 );
199 } else {
200 dirty_scene_sources.insert(node.id.clone());
201 }
202 }
203 if old_interaction != flags {
204 mutations.push(HostMutation::UpdateProps {
205 id,
206 source: node.id.clone(),
207 kind: HostUpdateKind::Interaction,
208 old_bounds: old_paint,
209 new_bounds: next_paint,
210 });
211 }
212 if old_parent != parent || old_children != children {
213 let bounds = child_structure_damage(
214 self,
215 tree,
216 &old_children,
217 &children,
218 old_paint.union(next_paint),
219 );
220 mutations.push(HostMutation::ReorderChildren {
221 id,
222 source: node.id.clone(),
223 bounds,
224 });
225 dirty_scene_sources.insert(node.id.clone());
226 }
227 }
228
229 if !was_mounted {
230 dirty_scene_sources.insert(node.id.clone());
231 }
232
233 let current = self.node_mut(id);
234 current.parent = parent;
235 current.mounted = true;
236 current.children = children;
237 current.kind = node.kind;
238 current.layout_bounds = node.layout_rect;
239 current.paint_bounds = next_paint;
240 current.interaction = flags;
241 current.node = (*node).clone();
242 }
243 #[cfg(feature = "diagnostics-timing")]
244 let node_patch_ms = elapsed_ms(node_patch_started);
245 #[cfg(feature = "diagnostics-timing")]
246 let scene_reconcile_started = Instant::now();
247 let (scene_mutations, scene, reused_scene_nodes, compiled_scene_nodes, _scene_snapshot_ms) =
248 self.reconcile_scene(
249 tree,
250 dirty_scene_sources,
251 compositing_updates,
252 order_changed,
253 scene_structure_changed,
254 invalidations,
255 );
256 #[cfg(feature = "diagnostics-timing")]
257 let scene_reconcile_ms =
258 (elapsed_ms(scene_reconcile_started) - _scene_snapshot_ms).max(0.0);
259 #[cfg(feature = "diagnostics-timing")]
260 let damage_started = Instant::now();
261 let damage = self.calculate_damage(viewport, &mutations, invalidations);
262 let semantics = self.reconcile_semantics(
263 tree,
264 semantic_changed,
265 semantic_removed,
266 interaction.focused.clone(),
267 semantic_full,
268 );
269 #[cfg(feature = "diagnostics-timing")]
270 let damage_ms = elapsed_ms(damage_started);
271 #[cfg(feature = "diagnostics-timing")]
272 let finalize_started = Instant::now();
273 self.initialized = true;
274 let metrics = HostCommitMetrics {
275 host_nodes: self.sources.len(),
276 scene_nodes: self.scene.len(),
277 visited_host_nodes: changed_nodes.len(),
278 compiled_scene_nodes,
279 host_mutations: mutations.len(),
280 scene_mutations: scene_mutations.len(),
281 reused_scene_nodes,
282 };
283 #[cfg(feature = "diagnostics-timing")]
284 let finalize_ms = elapsed_ms(finalize_started);
285 HostCommit {
286 mutations,
287 scene_mutations,
288 damage,
289 scene,
290 metrics,
291 semantics,
292 #[cfg(feature = "diagnostics-timing")]
293 timings: HostCommitTimings {
294 change_scan_ms,
295 node_patch_ms,
296 scene_reconcile_ms,
297 scene_snapshot_ms: _scene_snapshot_ms,
298 damage_ms,
299 finalize_ms,
300 },
301 }
302 }
303
304 pub fn clear(&mut self) {
305 *self = Self::default();
306 }
307}