Skip to main content

lgui_core/runtime/host/
commit.rs

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        // Snapshot every removal while the old parent graph is still intact.
89        // A projection may remove a parent and its descendants in the same
90        // commit, so releasing either one before this pass would leave stale
91        // HostNodeIds in the remaining nodes' ancestry.
92        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}