1use pyo3::prelude::*;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use uuid::Uuid;
5
6#[pyclass]
7#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, uniffi::Record)]
8pub struct SceneKey {
9 pub id: Vec<u8>,
10}
11
12#[pymethods]
13impl SceneKey {
14 #[new]
15 pub fn new() -> Self {
16 Self { id: Uuid::new_v4().as_bytes().to_vec() }
17 }
18
19 pub fn __repr__(&self) -> String {
20 Uuid::from_slice(&self.id).unwrap().to_string()
21 }
22}
23
24impl Default for SceneKey {
25 fn default() -> Self {
26 Self::new()
27 }
28}
29
30#[pyclass]
31#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, uniffi::Record)]
32pub struct SceneDescription {
33 #[pyo3(get, set)]
34 pub root: SceneNode,
35}
36
37#[pymethods]
38impl SceneDescription {
39 #[new]
40 pub fn py_new(root: SceneNode) -> Self {
41 Self { root }
42 }
43}
44
45#[pyclass]
46#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, uniffi::Record)]
47pub struct SceneNode {
48 #[pyo3(get, set)]
49 pub key: SceneKey,
50 #[pyo3(get, set)]
51 pub kind: NodeKind,
52 #[pyo3(get, set)]
53 pub properties: PropertyMap,
54 #[pyo3(get, set)]
55 pub children: Vec<SceneNode>,
56}
57
58#[pymethods]
59impl SceneNode {
60 #[new]
61 pub fn py_new(key: SceneKey, kind: NodeKind, properties: PropertyMap, children: Vec<SceneNode>) -> Self {
62 Self { key, kind, properties, children }
63 }
64}
65
66#[pyclass]
67#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, uniffi::Enum)]
68pub enum NodeKind {
69 Container {},
70 Text {},
71 Button {},
72 TextInput {},
73 Image {},
74 Custom { name: String },
75}
76
77#[pyclass]
78#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, uniffi::Record)]
79pub struct PropertyMap {
80 pub properties: HashMap<String, PropertyValue>,
81}
82
83#[pymethods]
84impl PropertyMap {
85 #[new]
86 pub fn new() -> Self {
87 Self { properties: HashMap::new() }
88 }
89
90 pub fn set(&mut self, key: String, value: PropertyValue) {
91 self.properties.insert(key, value);
92 }
93
94 pub fn get(&self, key: String) -> Option<PropertyValue> {
95 self.properties.get(&key).cloned()
96 }
97}
98
99#[pyclass]
100#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, uniffi::Enum)]
101pub enum PropertyValue {
102 String { value: String },
103 Number { value: f64 },
104 Boolean { value: bool },
105 Color { values: Vec<f32> },
106 Vec2 { values: Vec<f32> },
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, uniffi::Enum)]
110pub enum ScenePatch {
111 Insert { parent_key: SceneKey, index: u32, node: SceneNode },
112 Remove { key: SceneKey },
113 UpdateProp { key: SceneKey, prop: String, value: PropertyValue },
114 Replace { key: SceneKey, with: SceneNode },
115}
116
117impl SceneDescription {
118 pub fn diff(&self, other: &SceneDescription) -> Vec<ScenePatch> {
119 let mut patches = Vec::new();
120 diff_nodes(&self.root, &other.root, &mut patches);
121 patches
122 }
123
124 pub fn apply_patches(&mut self, patches: &[ScenePatch]) {
125 for patch in patches {
126 match patch {
127 ScenePatch::Insert { parent_key, index, node } => {
128 if let Some(parent) = find_node_mut(&mut self.root, parent_key.clone()) {
129 parent.children.insert(*index as usize, node.clone());
130 }
131 }
132 ScenePatch::Remove { key } => {
133 remove_node(&mut self.root, key.clone());
134 }
135 ScenePatch::UpdateProp { key, prop, value } => {
136 if let Some(node) = find_node_mut(&mut self.root, key.clone()) {
137 node.properties.properties.insert(prop.clone(), value.clone());
138 }
139 }
140 ScenePatch::Replace { key, with } => {
141 if self.root.key == *key {
142 self.root = with.clone();
143 } else {
144 replace_node(&mut self.root, key.clone(), with.clone());
146 }
147 }
148 }
149 }
150 }
151}
152
153fn find_node_mut(node: &mut SceneNode, key: SceneKey) -> Option<&mut SceneNode> {
154 if node.key == key {
155 return Some(node);
156 }
157 for child in &mut node.children {
158 if let Some(found) = find_node_mut(child, key.clone()) {
159 return Some(found);
160 }
161 }
162 None
163}
164
165fn replace_node(node: &mut SceneNode, key: SceneKey, with: SceneNode) -> bool {
166 for i in 0..node.children.len() {
167 if node.children[i].key == key {
168 node.children[i] = with;
169 return true;
170 }
171 if replace_node(&mut node.children[i], key.clone(), with.clone()) {
172 return true;
173 }
174 }
175 false
176}
177
178fn remove_node(node: &mut SceneNode, key: SceneKey) -> bool {
179 if let Some(pos) = node.children.iter().position(|c| c.key == key) {
180 node.children.remove(pos);
181 return true;
182 }
183 for child in &mut node.children {
184 if remove_node(child, key.clone()) {
185 return true;
186 }
187 }
188 false
189}
190
191fn diff_nodes(old: &SceneNode, new: &SceneNode, patches: &mut Vec<ScenePatch>) {
192 if old.key != new.key || old.kind != new.kind {
193 patches.push(ScenePatch::Replace { key: old.key.clone(), with: new.clone() });
194 return;
195 }
196
197 for (key, val) in &new.properties.properties {
199 if old.properties.properties.get(key) != Some(val) {
200 patches.push(ScenePatch::UpdateProp {
201 key: old.key.clone(),
202 prop: key.clone(),
203 value: val.clone(),
204 });
205 }
206 }
207
208 let old_len = old.children.len();
210 let new_len = new.children.len();
211
212 for i in 0..std::cmp::min(old_len, new_len) {
213 diff_nodes(&old.children[i], &new.children[i], patches);
214 }
215
216 if new_len > old_len {
217 for i in old_len..new_len {
218 patches.push(ScenePatch::Insert {
219 parent_key: old.key.clone(),
220 index: i as u32,
221 node: new.children[i].clone(),
222 });
223 }
224 } else if old_len > new_len {
225 for i in (new_len..old_len).rev() {
226 patches.push(ScenePatch::Remove { key: old.children[i].key.clone() });
227 }
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234 use proptest::prelude::*;
235
236 fn arb_scene_key() -> impl Strategy<Value = SceneKey> {
237 any::<[u8; 16]>().prop_map(|bytes| SceneKey { id: bytes.to_vec() })
238 }
239
240 fn arb_property_value() -> impl Strategy<Value = PropertyValue> {
241 prop_oneof![
242 any::<String>().prop_map(|value| PropertyValue::String { value }),
243 any::<f64>().prop_map(|value| PropertyValue::Number { value }),
244 any::<bool>().prop_map(|value| PropertyValue::Boolean { value }),
245 prop::collection::vec(any::<f32>(), 4).prop_map(|values| PropertyValue::Color { values }),
246 prop::collection::vec(any::<f32>(), 2).prop_map(|values| PropertyValue::Vec2 { values }),
247 ]
248 }
249
250 fn arb_node_kind() -> impl Strategy<Value = NodeKind> {
251 prop_oneof![
252 Just(NodeKind::Container {}),
253 Just(NodeKind::Text {}),
254 Just(NodeKind::Button {}),
255 Just(NodeKind::TextInput {}),
256 Just(NodeKind::Image {}),
257 any::<String>().prop_map(|name| NodeKind::Custom { name }),
258 ]
259 }
260
261 fn arb_scene_node(depth: u32) -> impl Strategy<Value = SceneNode> {
262 let key = arb_scene_key();
263 let kind = arb_node_kind();
264 let properties = prop::collection::hash_map(any::<String>(), arb_property_value(), 0..5)
265 .prop_map(|m| PropertyMap { properties: m });
266
267 (key, kind, properties).prop_flat_map(move |(key, kind, props)| {
268 let children = if depth > 0 {
269 prop::collection::vec(arb_scene_node(depth - 1), 0..3).boxed()
270 } else {
271 Just(vec![]).boxed()
272 };
273 children.prop_map(move |children| SceneNode {
274 key: key.clone(),
275 kind: kind.clone(),
276 properties: props.clone(),
277 children,
278 })
279 })
280 }
281
282 fn arb_scene_description() -> impl Strategy<Value = SceneDescription> {
283 arb_scene_node(3).prop_map(|root| SceneDescription { root })
284 }
285
286 proptest! {
287 #[test]
288 fn test_property_perfect_reconstruction(a in arb_scene_description(), b in arb_scene_description()) {
289 let patches = a.diff(&b);
290 let mut a_mut = a.clone();
291 a_mut.apply_patches(&patches);
292 assert_eq!(a_mut, b);
293 }
294
295 #[test]
296 fn test_property_identity_yields_empty_diff(a in arb_scene_description()) {
297 let patches = a.diff(&a);
298 assert!(patches.is_empty());
299 }
300
301 #[test]
302 fn test_property_key_stability(a in arb_scene_description()) {
303 let mut a_prime = a.clone();
304 fn mutate_props(node: &mut SceneNode) {
306 node.properties.properties.insert("test".to_string(), PropertyValue::Boolean { value: true });
307 for child in &mut node.children {
308 mutate_props(child);
309 }
310 }
311 mutate_props(&mut a_prime.root);
312
313 let patches = a.diff(&a_prime);
314 for patch in patches {
315 match patch {
316 ScenePatch::UpdateProp { .. } => {},
317 _ => panic!("Expected only UpdateProp patches, got {:?}", patch),
318 }
319 }
320 }
321 }
322}