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 {
53        id: String,
54        name: String,
55    },
56
57    /// Set text content of a node
58    #[serde(rename_all = "camelCase")]
59    SetText {
60        id: String,
61        text: String,
62    },
63
64    /// Insert a node into the tree
65    #[serde(rename_all = "camelCase")]
66    Insert {
67        parent_id: String,
68        id: String,
69        before_id: Option<String>,
70    },
71
72    /// Move a node to a new position
73    #[serde(rename_all = "camelCase")]
74    Move {
75        parent_id: String,
76        id: String,
77        before_id: Option<String>,
78    },
79
80    /// Remove a node from the tree
81    #[serde(rename_all = "camelCase")]
82    Remove {
83        id: String,
84    },
85}
86
87impl Patch {
88    pub fn create(id: NodeId, element_type: String, props: indexmap::IndexMap<String, Value>) -> Self {
89        Self::Create {
90            id: node_id_str(id),
91            element_type,
92            props,
93        }
94    }
95
96    pub fn set_prop(id: NodeId, name: String, value: Value) -> Self {
97        Self::SetProp {
98            id: node_id_str(id),
99            name,
100            value,
101        }
102    }
103
104    pub fn remove_prop(id: NodeId, name: String) -> Self {
105        Self::RemoveProp {
106            id: node_id_str(id),
107            name,
108        }
109    }
110
111    pub fn set_text(id: NodeId, text: String) -> Self {
112        Self::SetText {
113            id: node_id_str(id),
114            text,
115        }
116    }
117
118    pub fn insert(parent_id: NodeId, id: NodeId, before_id: Option<NodeId>) -> Self {
119        Self::Insert {
120            parent_id: node_id_str(parent_id),
121            id: node_id_str(id),
122            before_id: before_id.map(node_id_str),
123        }
124    }
125
126    /// Insert a root node into the "root" container
127    pub fn insert_root(id: NodeId) -> Self {
128        Self::Insert {
129            parent_id: "root".to_string(),
130            id: node_id_str(id),
131            before_id: None,
132        }
133    }
134
135    pub fn move_node(parent_id: NodeId, id: NodeId, before_id: Option<NodeId>) -> Self {
136        Self::Move {
137            parent_id: node_id_str(parent_id),
138            id: node_id_str(id),
139            before_id: before_id.map(node_id_str),
140        }
141    }
142
143    pub fn remove(id: NodeId) -> Self {
144        Self::Remove {
145            id: node_id_str(id),
146        }
147    }
148
149}