Skip to main content

hypen_engine/reconcile/
tree.rs

1use super::resolve::{resolve_props, resolve_props_full};
2use crate::ir::{Element, IRNode, NodeId, Props};
3use indexmap::IndexMap;
4use slotmap::SlotMap;
5use std::sync::Arc;
6
7/// Data sources type alias for readability
8type DataSources = indexmap::IndexMap<String, serde_json::Value>;
9
10/// The kind of control flow node for re-reconciliation
11#[derive(Debug, Clone)]
12pub enum ControlFlowKind {
13    /// ForEach iteration container
14    ForEach {
15        item_name: String,
16        key_path: Option<String>,
17    },
18    /// Conditional (When/If) container
19    Conditional,
20}
21
22/// Instance node - a concrete instance of an element in the tree
23///
24/// Uses im::Vector for children to enable O(1) structural sharing during clones.
25/// This is critical for reconciliation performance where nodes are frequently cloned.
26#[derive(Debug, Clone)]
27pub struct InstanceNode {
28    /// Unique node ID
29    pub id: NodeId,
30
31    /// Element type (e.g., "Column", "Text", "__ForEach", "__Conditional")
32    pub element_type: String,
33
34    /// Resolved props (bindings evaluated to actual values)
35    pub props: IndexMap<String, serde_json::Value>,
36
37    /// Raw props (including bindings) for change detection - Arc-wrapped for O(1) clone
38    pub raw_props: Props,
39
40    /// Original element template (for List re-rendering) - legacy
41    /// Only populated for List elements that need to re-render children
42    /// Arc-wrapped for O(1) clone during reconciliation
43    pub element_template: Option<Arc<Element>>,
44
45    /// Original IRNode template (for ForEach/Conditional re-rendering)
46    /// Used for control flow nodes that need to re-render on state change
47    /// Arc-wrapped for O(1) clone during reconciliation
48    pub ir_node_template: Option<Arc<IRNode>>,
49
50    /// Control flow metadata for ForEach/Conditional nodes
51    pub control_flow: Option<ControlFlowKind>,
52
53    // Event handling removed - now done at renderer level
54    /// Optional key for reconciliation
55    pub key: Option<String>,
56
57    /// Parent node ID
58    pub parent: Option<NodeId>,
59
60    /// Child node IDs (ordered) - uses im::Vector for O(1) clone
61    pub children: im::Vector<NodeId>,
62}
63
64impl InstanceNode {
65    pub fn new(id: NodeId, element: &Element, state: &serde_json::Value) -> Self {
66        Self::new_full(id, element, state, None)
67    }
68
69    pub fn new_full(
70        id: NodeId,
71        element: &Element,
72        state: &serde_json::Value,
73        data_sources: Option<&DataSources>,
74    ) -> Self {
75        let props = resolve_props_full(&element.props, state, None, data_sources);
76
77        Self {
78            id,
79            element_type: element.element_type.clone(),
80            props,
81            raw_props: element.props.clone(),
82            element_template: None,
83            ir_node_template: None,
84            control_flow: None,
85            key: element.key.clone(),
86            parent: None,
87            children: im::Vector::new(),
88        }
89    }
90
91    /// Create a control flow container node (ForEach or Conditional)
92    pub fn new_control_flow(
93        id: NodeId,
94        element_type: &str,
95        props: IndexMap<String, serde_json::Value>,
96        raw_props: Props,
97        control_flow: ControlFlowKind,
98        ir_node_template: IRNode,
99    ) -> Self {
100        Self {
101            id,
102            element_type: element_type.to_string(),
103            props,
104            raw_props,
105            element_template: None,
106            ir_node_template: Some(Arc::new(ir_node_template)),
107            control_flow: Some(control_flow),
108            key: None,
109            parent: None,
110            children: im::Vector::new(),
111        }
112    }
113
114    /// Update props by re-evaluating bindings against new state
115    pub fn update_props(&mut self, state: &serde_json::Value) {
116        self.props = resolve_props(&self.raw_props, state);
117    }
118
119    /// Update props by re-evaluating bindings against state and data sources
120    pub fn update_props_with_data_sources(
121        &mut self,
122        state: &serde_json::Value,
123        data_sources: Option<&indexmap::IndexMap<String, serde_json::Value>>,
124    ) {
125        self.props = resolve_props_full(&self.raw_props, state, None, data_sources);
126    }
127
128    /// Check if this is a ForEach control flow node
129    pub fn is_foreach(&self) -> bool {
130        matches!(self.control_flow, Some(ControlFlowKind::ForEach { .. }))
131    }
132
133    /// Check if this is a Conditional control flow node
134    pub fn is_conditional(&self) -> bool {
135        matches!(self.control_flow, Some(ControlFlowKind::Conditional))
136    }
137}
138
139/// The instance tree - maintains the current UI tree state
140pub struct InstanceTree {
141    /// All nodes in the tree
142    nodes: SlotMap<NodeId, InstanceNode>,
143
144    /// Root node ID
145    root: Option<NodeId>,
146}
147
148impl InstanceTree {
149    pub fn new() -> Self {
150        Self {
151            nodes: SlotMap::with_key(),
152            root: None,
153        }
154    }
155
156    /// Clear all nodes and reset the tree
157    pub fn clear(&mut self) {
158        self.nodes.clear();
159        self.root = None;
160    }
161
162    /// Create a new node and return its ID
163    pub fn create_node(&mut self, element: &Element, state: &serde_json::Value) -> NodeId {
164        self.nodes
165            .insert_with_key(|id| InstanceNode::new(id, element, state))
166    }
167
168    /// Create a new node with data sources and return its ID
169    pub fn create_node_full(
170        &mut self,
171        element: &Element,
172        state: &serde_json::Value,
173        data_sources: Option<&DataSources>,
174    ) -> NodeId {
175        self.nodes
176            .insert_with_key(|id| InstanceNode::new_full(id, element, state, data_sources))
177    }
178
179    /// Create a control flow node (ForEach or Conditional) and return its ID
180    pub fn create_control_flow_node(
181        &mut self,
182        element_type: &str,
183        props: IndexMap<String, serde_json::Value>,
184        raw_props: Props,
185        control_flow: ControlFlowKind,
186        ir_node_template: IRNode,
187    ) -> NodeId {
188        self.nodes.insert_with_key(|id| {
189            InstanceNode::new_control_flow(
190                id,
191                element_type,
192                props,
193                raw_props,
194                control_flow,
195                ir_node_template,
196            )
197        })
198    }
199
200    /// Get a node by ID
201    pub fn get(&self, id: NodeId) -> Option<&InstanceNode> {
202        self.nodes.get(id)
203    }
204
205    /// Get a mutable node by ID
206    pub fn get_mut(&mut self, id: NodeId) -> Option<&mut InstanceNode> {
207        self.nodes.get_mut(id)
208    }
209
210    /// Remove a node and all its descendants
211    pub fn remove(&mut self, id: NodeId) -> Option<InstanceNode> {
212        if let Some(node) = self.nodes.get(id) {
213            let children = node.children.clone();
214            // Remove all children recursively
215            for child_id in children {
216                self.remove(child_id);
217            }
218        }
219        self.nodes.remove(id)
220    }
221
222    /// Set the root node
223    pub fn set_root(&mut self, id: NodeId) {
224        self.root = Some(id);
225    }
226
227    /// Get the root node ID
228    pub fn root(&self) -> Option<NodeId> {
229        self.root
230    }
231
232    /// Add a child to a parent node
233    pub fn add_child(&mut self, parent_id: NodeId, child_id: NodeId, before: Option<NodeId>) {
234        if let Some(parent) = self.nodes.get_mut(parent_id) {
235            if let Some(before_id) = before {
236                if let Some(pos) = parent.children.iter().position(|&id| id == before_id) {
237                    parent.children.insert(pos, child_id);
238                } else {
239                    parent.children.push_back(child_id);
240                }
241            } else {
242                parent.children.push_back(child_id);
243            }
244        }
245
246        if let Some(child) = self.nodes.get_mut(child_id) {
247            child.parent = Some(parent_id);
248        }
249    }
250
251    /// Remove a child from its parent
252    pub fn remove_child(&mut self, parent_id: NodeId, child_id: NodeId) {
253        if let Some(parent) = self.nodes.get_mut(parent_id) {
254            parent.children = parent
255                .children
256                .iter()
257                .filter(|&&id| id != child_id)
258                .copied()
259                .collect();
260        }
261
262        if let Some(child) = self.nodes.get_mut(child_id) {
263            child.parent = None;
264        }
265    }
266
267    /// Update all nodes that depend on changed state
268    pub fn update_nodes(
269        &mut self,
270        node_ids: &indexmap::IndexSet<NodeId>,
271        state: &serde_json::Value,
272    ) {
273        for &node_id in node_ids {
274            if let Some(node) = self.nodes.get_mut(node_id) {
275                node.update_props(state);
276            }
277        }
278    }
279
280    /// Return the total number of nodes in the tree.
281    pub fn len(&self) -> usize {
282        self.nodes.len()
283    }
284
285    /// Return whether the tree is empty.
286    pub fn is_empty(&self) -> bool {
287        self.nodes.is_empty()
288    }
289
290    /// Iterate over all nodes
291    pub fn iter(&self) -> impl Iterator<Item = (NodeId, &InstanceNode)> {
292        self.nodes.iter()
293    }
294}
295
296impl Default for InstanceTree {
297    fn default() -> Self {
298        Self::new()
299    }
300}
301
302#[cfg(test)]
303mod tests {
304
305    use crate::reconcile::resolve::evaluate_binding;
306    use serde_json::json;
307
308    #[test]
309    fn test_evaluate_binding() {
310        use crate::reactive::Binding;
311
312        let state = json!({
313            "user": {
314                "name": "Alice",
315                "age": 30
316            }
317        });
318
319        let name_binding = Binding::state(vec!["user".to_string(), "name".to_string()]);
320        let age_binding = Binding::state(vec!["user".to_string(), "age".to_string()]);
321        let email_binding = Binding::state(vec!["user".to_string(), "email".to_string()]);
322
323        assert_eq!(
324            evaluate_binding(&name_binding, &state),
325            Some(json!("Alice"))
326        );
327        assert_eq!(evaluate_binding(&age_binding, &state), Some(json!(30)));
328        assert_eq!(evaluate_binding(&email_binding, &state), None);
329    }
330}