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