1use super::{
2 AnimationRegistry, ComponentStateStore, ComponentTree, ContextRegistry, EffectRegistry,
3 HookStateStore, HostTree, InteractionRole, LayoutSpec, RenderCx, RenderPhase, TextStyle,
4 UiElement, UiId, UiInteractionState, UiNode, UiNodeKind, UiRect, UiRenderContext, UiScale,
5 UiScope, UiTaskSpawner, UiUpdateQueue, VisualStyle,
6};
7use std::collections::{HashMap, HashSet};
8use std::sync::Arc;
9
10pub trait RootComponent {
11 fn render_root(self, cx: &mut RenderCx<'_, '_>) -> super::Element;
12}
13
14#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
15pub struct HostProjectionMetrics {
16 pub visited_nodes: usize,
17 pub reused_component_roots: usize,
18}
19
20pub struct HostTreeBuilder {
21 tree: HostTree,
22 parent_stack: Vec<UiId>,
23 retained: bool,
24 fresh_owners: HashSet<super::ComponentId>,
25 seen_by_owner: HashMap<super::ComponentId, HashSet<UiId>>,
26 structure_changed: bool,
27 projection_metrics: HostProjectionMetrics,
28}
29
30struct RenderTransaction<'a> {
31 component_states: &'a ComponentStateStore,
32 component_tree: &'a ComponentTree,
33 contexts: &'a ContextRegistry,
34 hook_states: &'a HookStateStore,
35 effects: &'a EffectRegistry,
36 committed: bool,
37}
38
39impl RenderTransaction<'_> {
40 fn commit(&mut self) {
41 self.committed = true;
42 }
43}
44
45impl Drop for RenderTransaction<'_> {
46 fn drop(&mut self) {
47 if self.committed {
48 return;
49 }
50 self.component_tree.abort_render();
51 self.contexts.abort_render(self.component_tree);
52 self.hook_states.abort_render(self.component_tree);
53 self.component_states.abort_frame();
54 self.effects.abort_frame();
55 }
56}
57
58impl HostTreeBuilder {
59 pub fn new() -> Self {
60 Self {
61 tree: HostTree::new(),
62 parent_stack: Vec::new(),
63 retained: false,
64 fresh_owners: HashSet::new(),
65 seen_by_owner: HashMap::new(),
66 structure_changed: false,
67 projection_metrics: HostProjectionMetrics::default(),
68 }
69 }
70
71 pub fn from_retained(tree: HostTree) -> Self {
72 Self {
73 tree,
74 parent_stack: Vec::new(),
75 retained: true,
76 fresh_owners: HashSet::new(),
77 seen_by_owner: HashMap::new(),
78 structure_changed: false,
79 projection_metrics: HostProjectionMetrics::default(),
80 }
81 }
82
83 pub fn push(&mut self, mut node: UiNode) -> UiId {
84 if node.parent.is_none() {
85 node.parent = self.parent_stack.last().cloned();
86 }
87 let id = node.id.clone();
88 self.tree.push(node);
89 id
90 }
91
92 pub(crate) fn mount_element(&mut self, element: UiElement) -> UiId {
93 self.projection_metrics.visited_nodes += 1;
94 let (mut node, children, boundary) = element.into_parts();
95 if node.parent.is_none() {
96 node.parent = self.parent_stack.last().cloned();
97 }
98 let id = node.id.clone();
99 let previous_children = self
100 .tree
101 .node(&id)
102 .map(|current| current.children.clone())
103 .unwrap_or_default();
104 let retain_children = boundary.is_some_and(|boundary| boundary.retain_children);
105 if let Some(boundary) = boundary {
106 if !boundary.retain_children {
107 self.fresh_owners.insert(boundary.id);
108 }
109 }
110 node.children = previous_children.clone();
111 if retain_children {
112 self.projection_metrics.reused_component_roots += 1;
113 }
114 if let Some(owner) = node.component_owner {
115 self.seen_by_owner
116 .entry(owner)
117 .or_default()
118 .insert(id.clone());
119 }
120 if self.retained {
121 self.tree.upsert(node);
122 } else {
123 self.tree.push(node);
124 }
125 if !retain_children {
126 self.parent_stack.push(id.clone());
127 let next_children = children
128 .iter()
129 .cloned()
130 .map(|child| self.mount_element(child))
131 .collect::<Vec<_>>();
132 self.parent_stack.pop();
133 self.structure_changed |= previous_children != next_children;
134 self.tree.set_children(&id, next_children);
135 }
136 id
137 }
138
139 pub fn node(&mut self, id: UiId, kind: UiNodeKind, rect: UiRect) -> UiId {
140 self.push(UiNode::new(id, kind, rect))
141 }
142
143 pub fn interactive(
144 &mut self,
145 id: UiId,
146 kind: UiNodeKind,
147 rect: UiRect,
148 interaction: InteractionRole,
149 ) -> UiId {
150 self.push(UiNode::new(id, kind, rect).interaction(interaction))
151 }
152
153 pub fn styled(&mut self, id: UiId, kind: UiNodeKind, rect: UiRect, style: VisualStyle) -> UiId {
154 self.push(UiNode::new(id, kind, rect).style(style))
155 }
156
157 pub fn text(
158 &mut self,
159 id: UiId,
160 rect: UiRect,
161 text: impl Into<std::borrow::Cow<'static, str>>,
162 style: TextStyle,
163 ) -> UiId {
164 self.push(UiNode::new(id, UiNodeKind::Text, rect).text(text, style))
165 }
166
167 pub fn laid_out(
168 &mut self,
169 id: UiId,
170 kind: UiNodeKind,
171 rect: UiRect,
172 layout: LayoutSpec,
173 ) -> UiId {
174 self.push(UiNode::new(id, kind, rect).layout(layout))
175 }
176
177 pub fn in_phase(
178 &mut self,
179 id: UiId,
180 kind: UiNodeKind,
181 rect: UiRect,
182 phase: RenderPhase,
183 ) -> UiId {
184 self.push(UiNode::new(id, kind, rect).render_phase(phase))
185 }
186
187 pub fn with_parent<T>(&mut self, id: UiId, build: impl FnOnce(&mut Self) -> T) -> T {
188 self.parent_stack.push(id);
189 let result = build(self);
190 self.parent_stack.pop();
191 result
192 }
193
194 pub fn element(&mut self, element: UiElement) -> UiId {
195 element.mount(self)
196 }
197
198 pub fn mount(
199 &mut self,
200 component: impl RootComponent,
201 viewport: UiRect,
202 interaction: &UiInteractionState,
203 animations: &AnimationRegistry,
204 component_states: &ComponentStateStore,
205 component_tree: &ComponentTree,
206 contexts: &ContextRegistry,
207 hook_states: &HookStateStore,
208 hook_updates: &Arc<UiUpdateQueue>,
209 task_spawner: Option<&UiTaskSpawner>,
210 effects: &EffectRegistry,
211 scale: UiScale,
212 ) -> UiId {
213 component_states.begin_frame();
214 component_tree.begin_render();
215 contexts.begin_render();
216 hook_states.begin_render();
217 effects.begin_frame();
218 let mut transaction = RenderTransaction {
219 component_states,
220 component_tree,
221 contexts,
222 hook_states,
223 effects,
224 committed: false,
225 };
226 let scope = UiScope::new("ui");
227 let context = UiRenderContext::new(
228 interaction,
229 animations,
230 component_states,
231 component_tree,
232 contexts,
233 hook_states,
234 hook_updates,
235 task_spawner,
236 effects,
237 viewport,
238 scale,
239 );
240 let root = {
241 let mut cx = RenderCx::new(&scope, &context);
242 let _current_context = context.contexts().enter_current(cx.component_id());
243 let view = component.render_root(&mut cx);
244 let root = cx.component_id();
245 self.element(
246 cx.compile(view)
247 .claim_component_owner(root)
248 .component_boundary(root),
249 )
250 };
251 contexts.validate_listener_hooks();
252 component_states.end_frame();
253 component_tree.end_render();
254 self.fresh_owners
255 .extend(component_tree.take_executed_in_render());
256 for owner in self.fresh_owners.clone() {
257 let keep = self.seen_by_owner.get(&owner).cloned().unwrap_or_default();
258 self.structure_changed |= self.tree.retain_owner_nodes(owner, &keep);
259 }
260 self.structure_changed |= self.tree.prune_dead_component_owners(component_tree);
261 if self.structure_changed {
262 self.tree.reorder_by_hierarchy();
263 }
264 contexts.commit_listener_effects(component_tree, effects);
265 contexts.end_render(component_tree);
266 hook_states.end_render(component_tree);
267 effects.end_frame(component_tree);
268 transaction.commit();
269 root
270 }
271
272 pub fn finish(self) -> HostTree {
273 self.tree
274 }
275
276 pub fn projection_metrics(&self) -> HostProjectionMetrics {
277 self.projection_metrics
278 }
279}
280
281impl Default for HostTreeBuilder {
282 fn default() -> Self {
283 Self::new()
284 }
285}
286
287#[cfg(test)]
288#[path = "builder_test.rs"]
289mod tests;