1use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct CompiledArtifact {
9 pub root_id: u64,
11 pub view: SerializedView,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct SerializedView {
18 pub view_type: String,
20 pub props: serde_json::Value,
22 pub children: Vec<SerializedView>,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
28pub enum RuntimePatch {
29 ReplaceView {
31 node_id: u64,
33 new_view: SerializedView,
35 },
36 UpdateState {
38 node_id: u64,
40 field: String,
42 value: serde_json::Value,
44 },
45 Batch(Vec<RuntimePatch>),
47}
48
49pub struct PatchEngine {
55 previous_view: Option<SerializedView>,
56}
57
58impl Default for PatchEngine {
59 fn default() -> Self {
60 Self::new()
61 }
62}
63
64impl PatchEngine {
65 pub fn new() -> Self {
67 Self {
68 previous_view: None,
69 }
70 }
71
72 pub fn generate_patch(&mut self, artifact: CompiledArtifact) -> RuntimePatch {
74 let mut patches = Vec::new();
75
76 if let Some(prev) = &self.previous_view {
77 self.diff_recursive(artifact.root_id, prev, &artifact.view, &mut patches);
78 } else {
79 patches.push(RuntimePatch::ReplaceView {
81 node_id: artifact.root_id,
82 new_view: artifact.view.clone(),
83 });
84 }
85
86 self.previous_view = Some(artifact.view);
87
88 if patches.len() == 1 {
89 patches.remove(0)
90 } else {
91 RuntimePatch::Batch(patches)
92 }
93 }
94
95 fn diff_recursive(
96 &self,
97 node_id: u64,
98 old: &SerializedView,
99 new: &SerializedView,
100 patches: &mut Vec<RuntimePatch>,
101 ) {
102 if old.view_type != new.view_type {
104 patches.push(RuntimePatch::ReplaceView {
105 node_id,
106 new_view: new.clone(),
107 });
108 return;
109 }
110
111 if old.props != new.props || old.children.len() != new.children.len() {
114 patches.push(RuntimePatch::ReplaceView {
115 node_id,
116 new_view: new.clone(),
117 });
118 return;
119 }
120
121 for (i, (old_child, new_child)) in old.children.iter().zip(new.children.iter()).enumerate()
124 {
125 let child_id = node_id * 100 + (i as u64 + 1);
128 self.diff_recursive(child_id, old_child, new_child, patches);
129 }
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use serde_json::json;
137
138 #[test]
139 fn test_patch_engine_first_run() {
140 let mut engine = PatchEngine::new();
141 let artifact = CompiledArtifact {
142 root_id: 1,
143 view: SerializedView {
144 view_type: "Text".to_string(),
145 props: json!({"text": "Hello"}),
146 children: vec![],
147 },
148 };
149
150 let patch = engine.generate_patch(artifact);
151 if let RuntimePatch::ReplaceView { node_id, .. } = patch {
152 assert_eq!(node_id, 1);
153 } else {
154 panic!("Expected ReplaceView patch");
155 }
156 }
157
158 #[test]
159 fn test_patch_engine_diff_same() {
160 let mut engine = PatchEngine::new();
161 let view = SerializedView {
162 view_type: "Text".to_string(),
163 props: json!({"text": "Hello"}),
164 children: vec![],
165 };
166
167 let artifact1 = CompiledArtifact { root_id: 1, view: view.clone() };
168 let artifact2 = CompiledArtifact { root_id: 1, view: view.clone() };
169
170 engine.generate_patch(artifact1);
171 let patch = engine.generate_patch(artifact2);
172
173 if let RuntimePatch::Batch(patches) = patch {
175 assert!(patches.is_empty());
176 } else {
177 }
182 }
183}