Skip to main content

hypen_engine/reconcile/
patch.rs

1use crate::ir::NodeId;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use std::collections::HashMap;
5use std::sync::atomic::{AtomicU64, Ordering};
6use std::sync::Mutex;
7
8/// Monotonic counter for compact, stable node-ID serialization.
9static NEXT_ID: AtomicU64 = AtomicU64::new(1);
10
11/// Maps SlotMap `NodeId` keys to compact integer strings.
12/// Once a NodeId is assigned a string, it keeps it for the lifetime of the
13/// process.  This avoids depending on slotmap's `Debug` output format.
14static NODE_ID_MAP: Mutex<Option<HashMap<NodeId, String>>> = Mutex::new(None);
15
16/// Stable, compact serialization for NodeId.
17///
18/// Returns a short numeric string (e.g. `"1"`, `"42"`) instead of the
19/// previous `"NodeId(0v1)"` Debug format.  The mapping is deterministic
20/// per NodeId for the lifetime of the process.
21#[inline]
22pub fn node_id_str(id: NodeId) -> String {
23    let mut guard = NODE_ID_MAP.lock().unwrap();
24    let map = guard.get_or_insert_with(HashMap::new);
25    map.entry(id)
26        .or_insert_with(|| NEXT_ID.fetch_add(1, Ordering::Relaxed).to_string())
27        .clone()
28}
29
30/// Platform-agnostic patch operations for updating the UI
31#[derive(Debug, Clone, Serialize, Deserialize)]
32#[serde(tag = "type", rename_all = "camelCase")]
33pub enum Patch {
34    /// Create a new node
35    #[serde(rename_all = "camelCase")]
36    Create {
37        id: String,
38        element_type: String,
39        props: indexmap::IndexMap<String, Value>,
40    },
41
42    /// Set a property on an existing node
43    #[serde(rename_all = "camelCase")]
44    SetProp {
45        id: String,
46        name: String,
47        value: Value,
48    },
49
50    /// Remove a property from an existing node
51    #[serde(rename_all = "camelCase")]
52    RemoveProp { id: String, name: String },
53
54    /// Set text content of a node
55    #[serde(rename_all = "camelCase")]
56    SetText { id: String, text: String },
57
58    /// Insert a node into the tree
59    #[serde(rename_all = "camelCase")]
60    Insert {
61        parent_id: String,
62        id: String,
63        before_id: Option<String>,
64    },
65
66    /// Move a node to a new position
67    #[serde(rename_all = "camelCase")]
68    Move {
69        parent_id: String,
70        id: String,
71        before_id: Option<String>,
72    },
73
74    /// Remove a node from the tree
75    #[serde(rename_all = "camelCase")]
76    Remove { id: String },
77}
78
79impl Patch {
80    pub fn create(
81        id: NodeId,
82        element_type: String,
83        props: indexmap::IndexMap<String, Value>,
84    ) -> Self {
85        Self::Create {
86            id: node_id_str(id),
87            element_type,
88            props,
89        }
90    }
91
92    pub fn set_prop(id: NodeId, name: String, value: Value) -> Self {
93        Self::SetProp {
94            id: node_id_str(id),
95            name,
96            value,
97        }
98    }
99
100    pub fn remove_prop(id: NodeId, name: String) -> Self {
101        Self::RemoveProp {
102            id: node_id_str(id),
103            name,
104        }
105    }
106
107    pub fn set_text(id: NodeId, text: String) -> Self {
108        Self::SetText {
109            id: node_id_str(id),
110            text,
111        }
112    }
113
114    pub fn insert(parent_id: NodeId, id: NodeId, before_id: Option<NodeId>) -> Self {
115        Self::Insert {
116            parent_id: node_id_str(parent_id),
117            id: node_id_str(id),
118            before_id: before_id.map(node_id_str),
119        }
120    }
121
122    /// Insert a root node into the "root" container
123    pub fn insert_root(id: NodeId) -> Self {
124        Self::Insert {
125            parent_id: "root".to_string(),
126            id: node_id_str(id),
127            before_id: None,
128        }
129    }
130
131    pub fn move_node(parent_id: NodeId, id: NodeId, before_id: Option<NodeId>) -> Self {
132        Self::Move {
133            parent_id: node_id_str(parent_id),
134            id: node_id_str(id),
135            before_id: before_id.map(node_id_str),
136        }
137    }
138
139    pub fn remove(id: NodeId) -> Self {
140        Self::Remove {
141            id: node_id_str(id),
142        }
143    }
144}