Skip to main content

atomr_view_core/
scene.rs

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                        // Use a non-recursive approach or a safer recursive one for finding parent
145                        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    // Diff properties
198    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    // Diff children (very basic positional diff for now)
209    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            // Mutate some properties
305            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}