tessera_ui/
component_tree.rs

1mod constraint;
2mod node;
3
4use std::{any::TypeId, num::NonZero, sync::Arc, time::Instant};
5
6use parking_lot::RwLock;
7use rayon::prelude::*;
8use tracing::{debug, warn};
9
10use crate::{
11    Clipboard, ComputeResourceManager, Px, PxRect,
12    cursor::CursorEvent,
13    px::{PxPosition, PxSize},
14    renderer::Command,
15};
16
17pub use constraint::{Constraint, DimensionValue};
18pub use node::{
19    ComponentNode, ComponentNodeMetaData, ComponentNodeMetaDatas, ComponentNodeTree, ComputedData,
20    ImeRequest, InputHandlerFn, InputHandlerInput, MeasureFn, MeasureInput, MeasurementError,
21    WindowRequests, measure_node, measure_nodes, place_node,
22};
23
24/// Parameters for the compute function
25pub struct ComputeParams<'a> {
26    pub screen_size: PxSize,
27    pub cursor_position: Option<PxPosition>,
28    pub cursor_events: Vec<CursorEvent>,
29    pub keyboard_events: Vec<winit::event::KeyEvent>,
30    pub ime_events: Vec<winit::event::Ime>,
31    pub modifiers: winit::keyboard::ModifiersState,
32    pub compute_resource_manager: Arc<RwLock<ComputeResourceManager>>,
33    pub gpu: &'a wgpu::Device,
34    pub clipboard: &'a mut Clipboard,
35}
36
37/// Respents a component tree
38pub struct ComponentTree {
39    /// We use indextree as the tree structure
40    tree: indextree::Arena<ComponentNode>,
41    /// Components' metadatas
42    metadatas: ComponentNodeMetaDatas,
43    /// Used to remember the current node
44    node_queue: Vec<indextree::NodeId>,
45}
46
47impl Default for ComponentTree {
48    fn default() -> Self {
49        Self::new()
50    }
51}
52
53impl ComponentTree {
54    /// Create a new ComponentTree
55    pub fn new() -> Self {
56        let tree = indextree::Arena::new();
57        let node_queue = Vec::new();
58        let metadatas = ComponentNodeMetaDatas::new();
59        Self {
60            tree,
61            node_queue,
62            metadatas,
63        }
64    }
65
66    /// Clear the component tree
67    pub fn clear(&mut self) {
68        self.tree.clear();
69        self.metadatas.clear();
70        self.node_queue.clear();
71    }
72
73    /// Get node by NodeId
74    pub fn get(&self, node_id: indextree::NodeId) -> Option<&ComponentNode> {
75        self.tree.get(node_id).map(|n| n.get())
76    }
77
78    /// Get mutable node by NodeId
79    pub fn get_mut(&mut self, node_id: indextree::NodeId) -> Option<&mut ComponentNode> {
80        self.tree.get_mut(node_id).map(|n| n.get_mut())
81    }
82
83    /// Get current node
84    pub fn current_node(&self) -> Option<&ComponentNode> {
85        self.node_queue
86            .last()
87            .and_then(|node_id| self.get(*node_id))
88    }
89
90    /// Get mutable current node
91    pub fn current_node_mut(&mut self) -> Option<&mut ComponentNode> {
92        let node_id = self.node_queue.last()?;
93        self.get_mut(*node_id)
94    }
95
96    /// Add a new node to the tree
97    /// Nodes now store their intrinsic constraints in their metadata.
98    /// The `node_component` itself primarily holds the measure_fn.
99    pub fn add_node(&mut self, node_component: ComponentNode) {
100        let new_node_id = self.tree.new_node(node_component);
101        if let Some(current_node_id) = self.node_queue.last_mut() {
102            current_node_id.append(new_node_id, &mut self.tree);
103        }
104        let metadata = ComponentNodeMetaData::none();
105        self.metadatas.insert(new_node_id, metadata);
106        self.node_queue.push(new_node_id);
107    }
108
109    /// Pop the last node from the queue
110    pub fn pop_node(&mut self) {
111        self.node_queue.pop();
112    }
113
114    /// Get a reference to the underlying tree structure.
115    ///
116    /// This is used for accessibility tree building and other introspection needs.
117    pub(crate) fn tree(&self) -> &indextree::Arena<ComponentNode> {
118        &self.tree
119    }
120
121    /// Get a reference to the node metadatas.
122    ///
123    /// This is used for accessibility tree building and other introspection needs.
124    pub(crate) fn metadatas(&self) -> &ComponentNodeMetaDatas {
125        &self.metadatas
126    }
127
128    /// Compute the ComponentTree into a list of rendering commands
129    ///
130    /// This method processes the component tree through three main phases:
131    /// 1. **Measure Phase**: Calculate sizes and positions for all components
132    /// 2. **Command Generation**: Extract draw commands from component metadata
133    /// 3. **State Handling**: Process user interactions and events
134    ///
135    /// Returns a tuple of (commands, window_requests) where commands contain
136    /// the rendering instructions with their associated sizes and positions.
137    #[tracing::instrument(level = "debug", skip(self, params))]
138    pub fn compute(
139        &mut self,
140        params: ComputeParams<'_>,
141    ) -> (Vec<(Command, TypeId, PxSize, PxPosition)>, WindowRequests) {
142        let ComputeParams {
143            screen_size,
144            mut cursor_position,
145            mut cursor_events,
146            mut keyboard_events,
147            mut ime_events,
148            modifiers,
149            compute_resource_manager,
150            gpu,
151            clipboard,
152        } = params;
153        let Some(root_node) = self.tree.get_node_id_at(NonZero::new(1).unwrap()) else {
154            return (vec![], WindowRequests::default());
155        };
156        let screen_constraint = Constraint::new(
157            DimensionValue::Fixed(screen_size.width),
158            DimensionValue::Fixed(screen_size.height),
159        );
160
161        let measure_timer = Instant::now();
162        debug!("Start measuring the component tree...");
163
164        // Call measure_node with &self.tree and &self.metadatas
165        // Handle the Result from measure_node
166        match measure_node(
167            root_node,
168            &screen_constraint,
169            &self.tree,
170            &self.metadatas,
171            compute_resource_manager,
172            gpu,
173        ) {
174            Ok(_root_computed_data) => {
175                debug!("Component tree measured in {:?}", measure_timer.elapsed());
176            }
177            Err(e) => {
178                panic!(
179                    "Root node ({root_node:?}) measurement failed: {e:?}. Aborting draw command computation."
180                );
181            }
182        }
183
184        let compute_draw_timer = Instant::now();
185        debug!("Start computing draw commands...");
186        // compute_draw_commands_parallel expects &ComponentNodeTree and &ComponentNodeMetaDatas
187        // It also uses get_mut on metadatas internally, which is fine for DashMap with &self.
188        let commands = compute_draw_commands_parallel(
189            root_node,
190            &self.tree,
191            &self.metadatas,
192            screen_size.width.0,
193            screen_size.height.0,
194        );
195        debug!(
196            "Draw commands computed in {:?}, total commands: {}",
197            compute_draw_timer.elapsed(),
198            commands.len()
199        );
200
201        let input_handler_timer = Instant::now();
202        let mut window_requests = WindowRequests::default();
203        debug!("Start executing input handlers...");
204
205        for node_id in root_node
206            .reverse_traverse(&self.tree)
207            .filter_map(|edge| match edge {
208                indextree::NodeEdge::Start(id) => Some(id),
209                indextree::NodeEdge::End(_) => None,
210            })
211        {
212            let Some(input_handler) = self
213                .tree
214                .get(node_id)
215                .and_then(|n| n.get().input_handler_fn.as_ref())
216            else {
217                continue;
218            };
219
220            let metadata = self.metadatas.get(&node_id).unwrap();
221            let abs_pos = metadata.abs_position.unwrap();
222            let event_clip_rect = metadata.event_clip_rect;
223            let node_computed_data = metadata.computed_data;
224            drop(metadata); // release DashMap guard so handlers can mutate metadata if needed
225
226            let mut cursor_position_ref = &mut cursor_position;
227            let mut dummy_cursor_position = None;
228            let mut cursor_events_ref = &mut cursor_events;
229            let mut empty_dummy_cursor_events = Vec::new();
230            if let (Some(cursor_pos), Some(clip_rect)) = (*cursor_position_ref, event_clip_rect) {
231                // check if the cursor is inside the clip rect
232                if !clip_rect.contains(cursor_pos) {
233                    // If not, set cursor relative inputs to None
234                    cursor_position_ref = &mut dummy_cursor_position;
235                    cursor_events_ref = &mut empty_dummy_cursor_events;
236                }
237            }
238            let current_cursor_position = cursor_position_ref.map(|pos| pos - abs_pos);
239
240            if let Some(node_computed_data) = node_computed_data {
241                let input = InputHandlerInput {
242                    computed_data: node_computed_data,
243                    cursor_position_rel: current_cursor_position,
244                    cursor_position_abs: cursor_position_ref,
245                    cursor_events: cursor_events_ref,
246                    keyboard_events: &mut keyboard_events,
247                    ime_events: &mut ime_events,
248                    key_modifiers: modifiers,
249                    requests: &mut window_requests,
250                    clipboard,
251                    current_node_id: node_id,
252                    metadatas: &self.metadatas,
253                };
254                input_handler(input);
255                // if input_handler set ime request, it's position must be None, and we set it here
256                if let Some(ref mut ime_request) = window_requests.ime_request
257                    && ime_request.position.is_none()
258                {
259                    ime_request.position = Some(abs_pos);
260                }
261            } else {
262                warn!(
263                    "Computed data not found for node {:?} during input handler execution.",
264                    node_id
265                );
266            }
267        }
268
269        debug!(
270            "Input Handlers executed in {:?}",
271            input_handler_timer.elapsed()
272        );
273        (commands, window_requests)
274    }
275}
276
277/// Parallel computation of draw commands from the component tree
278///
279/// This function traverses the component tree and extracts rendering commands
280/// from each node's metadata. It uses parallel processing for better performance
281/// when dealing with large component trees.
282///
283/// The function maintains thread-safety by using DashMap's concurrent access
284/// capabilities, allowing multiple threads to safely read and modify metadata.
285#[tracing::instrument(level = "trace", skip(tree, metadatas))]
286fn compute_draw_commands_parallel(
287    node_id: indextree::NodeId,
288    tree: &ComponentNodeTree,
289    metadatas: &ComponentNodeMetaDatas,
290    screen_width: i32,
291    screen_height: i32,
292) -> Vec<(Command, TypeId, PxSize, PxPosition)> {
293    compute_draw_commands_inner_parallel(
294        PxPosition::ZERO,
295        true,
296        node_id,
297        tree,
298        metadatas,
299        screen_width,
300        screen_height,
301        None,
302    )
303}
304
305#[tracing::instrument(level = "trace", skip(tree, metadatas))]
306fn compute_draw_commands_inner_parallel(
307    start_pos: PxPosition,
308    is_root: bool,
309    node_id: indextree::NodeId,
310    tree: &ComponentNodeTree,
311    metadatas: &ComponentNodeMetaDatas,
312    screen_width: i32,
313    screen_height: i32,
314    clip_rect: Option<PxRect>,
315) -> Vec<(Command, TypeId, PxSize, PxPosition)> {
316    let mut local_commands = Vec::new();
317
318    // Get metadata and calculate absolute position. This MUST happen for all nodes.
319    let mut metadata = metadatas.get_mut(&node_id).unwrap();
320    let rel_pos = match metadata.rel_position {
321        Some(pos) => pos,
322        None if is_root => PxPosition::ZERO,
323        _ => return local_commands, // Skip nodes that were not placed at all.
324    };
325    let self_pos = start_pos + rel_pos;
326    metadata.abs_position = Some(self_pos);
327
328    let size = metadata
329        .computed_data
330        .map(|d| PxSize {
331            width: d.width,
332            height: d.height,
333        })
334        .unwrap_or_default();
335
336    let node_rect = PxRect {
337        x: self_pos.x,
338        y: self_pos.y,
339        width: size.width,
340        height: size.height,
341    };
342
343    let mut clip_rect = clip_rect;
344    if let Some(clip_rect) = clip_rect {
345        metadata.event_clip_rect = Some(clip_rect);
346    }
347
348    let clips_children = metadata.clips_children;
349    // Add Clip command if the node clips its children
350    if clips_children {
351        let new_clip_rect = if let Some(existing_clip) = clip_rect {
352            existing_clip
353                .intersection(&node_rect)
354                .unwrap_or(PxRect::ZERO)
355        } else {
356            node_rect
357        };
358
359        clip_rect = Some(new_clip_rect);
360
361        local_commands.push((
362            Command::ClipPush(new_clip_rect),
363            TypeId::of::<Command>(),
364            size,
365            self_pos,
366        ));
367    }
368
369    // Viewport culling check
370    let screen_rect = PxRect {
371        x: Px(0),
372        y: Px(0),
373        width: Px(screen_width),
374        height: Px(screen_height),
375    };
376
377    // Only drain commands if the node is visible.
378    if size.width.0 > 0 && size.height.0 > 0 && !node_rect.is_orthogonal(&screen_rect) {
379        for (cmd, type_id) in metadata.commands.drain(..) {
380            local_commands.push((cmd, type_id, size, self_pos));
381        }
382    }
383
384    drop(metadata); // Release lock before recursing
385
386    // ALWAYS recurse to children to ensure their abs_position is calculated.
387    let children: Vec<_> = node_id.children(tree).collect();
388    let child_results: Vec<Vec<_>> = children
389        .into_par_iter()
390        .map(|child| {
391            // The unwrap is safe because we just set the parent's abs_position.
392            let parent_abs_pos = metadatas.get(&node_id).unwrap().abs_position.unwrap();
393            compute_draw_commands_inner_parallel(
394                parent_abs_pos, // Pass the calculated absolute position
395                false,
396                child,
397                tree,
398                metadatas,
399                screen_width,
400                screen_height,
401                clip_rect,
402            )
403        })
404        .collect();
405
406    for child_cmds in child_results {
407        local_commands.extend(child_cmds);
408    }
409
410    // If the node clips its children, we need to pop the clip command
411    if clips_children {
412        local_commands.push((Command::ClipPop, TypeId::of::<Command>(), size, self_pos));
413    }
414
415    local_commands
416}