tessera_ui/
component_tree.rs

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