1use crate::{
2 dispatch::{Action, ActionDispatcher},
3 ir::{ComponentRegistry, Element},
4 lifecycle::{ComponentLifecycle, ModuleInstance, ResourceCache},
5 reactive::{DependencyGraph, Scheduler},
6 reconcile::{reconcile, InstanceTree, Patch},
7 state::StateChange,
8};
9
10pub type RenderCallback = Box<dyn Fn(&[Patch]) + Send + Sync>;
12
13pub struct Engine {
15 component_registry: ComponentRegistry,
17
18 module: Option<ModuleInstance>,
20
21 tree: InstanceTree,
23
24 dependencies: DependencyGraph,
26
27 scheduler: Scheduler,
29
30 actions: ActionDispatcher,
32
33 lifecycle: ComponentLifecycle,
35
36 resources: ResourceCache,
38
39 render_callback: Option<RenderCallback>,
41
42 revision: u64,
44}
45
46impl Engine {
47 pub fn new() -> Self {
48 Self {
49 component_registry: ComponentRegistry::new(),
50 module: None,
51 tree: InstanceTree::new(),
52 dependencies: DependencyGraph::new(),
53 scheduler: Scheduler::new(),
54 actions: ActionDispatcher::new(),
55 lifecycle: ComponentLifecycle::new(),
56 resources: ResourceCache::new(),
57 render_callback: None,
58 revision: 0,
59 }
60 }
61
62 pub fn register_component(&mut self, component: crate::ir::Component) {
64 self.component_registry.register(component);
65 }
66
67 pub fn set_component_resolver<F>(&mut self, resolver: F)
71 where
72 F: Fn(&str, Option<&str>) -> Option<crate::ir::ResolvedComponent> + Send + Sync + 'static,
73 {
74 self.component_registry.set_resolver(std::sync::Arc::new(resolver));
75 }
76
77 pub fn set_module(&mut self, module: ModuleInstance) {
79 self.module = Some(module);
80 }
81
82 pub fn set_render_callback<F>(&mut self, callback: F)
84 where
85 F: Fn(&[Patch]) + Send + Sync + 'static,
86 {
87 self.render_callback = Some(Box::new(callback));
88 }
89
90 pub fn on_action<F>(&mut self, action_name: impl Into<String>, handler: F)
92 where
93 F: Fn(&Action) + Send + Sync + 'static,
94 {
95 self.actions.on(action_name, handler);
96 }
97
98 pub fn render(&mut self, element: &Element) {
100 let expanded = self.component_registry.expand(element);
102
103 let state = self
105 .module
106 .as_ref()
107 .map(|m| m.get_state().clone())
108 .unwrap_or(serde_json::Value::Null);
109
110 self.dependencies.clear();
112
113 let patches = reconcile(&mut self.tree, &expanded, None, &state, &mut self.dependencies);
115
116 self.emit_patches(patches);
118
119 self.revision += 1;
120 }
121
122 pub fn notify_state_change(&mut self, change: &StateChange) {
125 let mut affected_nodes = indexmap::IndexSet::new();
127 for path in change.paths() {
128 affected_nodes.extend(self.dependencies.get_affected_nodes(path));
129 }
130
131 self.scheduler.mark_many_dirty(affected_nodes.iter().copied());
133
134 self.render_dirty();
137 }
138
139 pub fn update_state(&mut self, state_patch: serde_json::Value) {
141 if let Some(module) = &mut self.module {
143 module.update_state(state_patch.clone());
144 }
145
146 let change = StateChange::from_json(&state_patch);
148 self.notify_state_change(&change);
149 }
150
151 pub fn dispatch_action(&mut self, action: Action) -> Result<(), String> {
153 self.actions.dispatch(&action)
154 }
155
156 fn render_dirty(&mut self) {
158 let patches = crate::render::render_dirty_nodes(
159 &mut self.scheduler,
160 &mut self.tree,
161 self.module.as_ref(),
162 );
163
164 if !patches.is_empty() {
165 self.emit_patches(patches);
166 self.revision += 1;
167 }
168 }
169
170 fn emit_patches(&self, patches: Vec<Patch>) {
172 if let Some(ref callback) = self.render_callback {
173 callback(&patches);
174 }
175 }
176
177 pub fn revision(&self) -> u64 {
179 self.revision
180 }
181
182 pub fn component_registry(&self) -> &ComponentRegistry {
184 &self.component_registry
185 }
186
187 pub fn component_registry_mut(&mut self) -> &mut ComponentRegistry {
189 &mut self.component_registry
190 }
191
192 pub fn resources(&self) -> &ResourceCache {
194 &self.resources
195 }
196
197 pub fn resources_mut(&mut self) -> &mut ResourceCache {
199 &mut self.resources
200 }
201}
202
203impl Default for Engine {
204 fn default() -> Self {
205 Self::new()
206 }
207}
208
209fn extract_changed_paths(patch: &serde_json::Value) -> Vec<String> {
211 let mut paths = Vec::new();
212 extract_paths_recursive(patch, String::new(), &mut paths);
213 paths
214}
215
216fn extract_paths_recursive(value: &serde_json::Value, prefix: String, paths: &mut Vec<String>) {
217 match value {
218 serde_json::Value::Object(map) => {
219 for (key, val) in map {
220 let path = if prefix.is_empty() {
221 key.clone()
222 } else {
223 format!("{}.{}", prefix, key)
224 };
225 paths.push(path.clone());
226 extract_paths_recursive(val, path, paths);
227 }
228 }
229 _ => {
230 if !prefix.is_empty() {
231 paths.push(prefix);
232 }
233 }
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240 use serde_json::json;
241
242 #[test]
243 fn test_extract_changed_paths() {
244 let patch = json!({
245 "user": {
246 "name": "Alice",
247 "age": 30
248 }
249 });
250
251 let paths = extract_changed_paths(&patch);
252 assert!(paths.contains(&"user".to_string()));
253 assert!(paths.contains(&"user.name".to_string()));
254 assert!(paths.contains(&"user.age".to_string()));
255 }
256}