use crate::{
dispatch::{Action, ActionDispatcher},
error::EngineError,
ir::{ComponentRegistry, Element, IRNode},
lifecycle::{ModuleInstance, ResourceCache},
reactive::{DependencyGraph, Scheduler},
reconcile::{reconcile_ir_with_ds, InstanceTree, Patch},
state::StateChange,
};
static NULL_STATE: serde_json::Value = serde_json::Value::Null;
pub type RenderCallback = Box<dyn Fn(&[Patch]) + Send + Sync>;
pub struct Engine {
component_registry: ComponentRegistry,
module: Option<ModuleInstance>,
tree: InstanceTree,
dependencies: DependencyGraph,
scheduler: Scheduler,
actions: ActionDispatcher,
resources: ResourceCache,
render_callback: Option<RenderCallback>,
revision: u64,
data_sources: indexmap::IndexMap<String, serde_json::Value>,
}
impl Engine {
pub fn new() -> Self {
Self {
component_registry: ComponentRegistry::new(),
module: None,
tree: InstanceTree::new(),
dependencies: DependencyGraph::new(),
scheduler: Scheduler::new(),
actions: ActionDispatcher::new(),
resources: ResourceCache::new(),
render_callback: None,
revision: 0,
data_sources: indexmap::IndexMap::new(),
}
}
pub fn register_component(&mut self, component: crate::ir::Component) {
self.component_registry.register(component);
}
pub fn set_component_resolver<F>(&mut self, resolver: F)
where
F: Fn(&str, Option<&str>) -> Option<crate::ir::ResolvedComponent> + Send + Sync + 'static,
{
self.component_registry
.set_resolver(std::sync::Arc::new(resolver));
}
pub fn set_module(&mut self, module: ModuleInstance) {
self.module = Some(module);
}
pub fn set_render_callback<F>(&mut self, callback: F)
where
F: Fn(&[Patch]) + Send + Sync + 'static,
{
self.render_callback = Some(Box::new(callback));
}
pub fn on_action<F>(&mut self, action_name: impl Into<String>, handler: F)
where
F: Fn(&Action) + Send + Sync + 'static,
{
self.actions.on(action_name, handler);
}
pub fn render(&mut self, element: &Element) {
let ir_node = IRNode::Element(element.clone());
self.render_ir_node(&ir_node);
}
pub fn render_ir_node(&mut self, ir_node: &IRNode) {
let expanded = self.component_registry.expand_ir_node(ir_node);
let state: &serde_json::Value = self
.module
.as_ref()
.map(|m| m.get_state())
.unwrap_or(&NULL_STATE);
self.dependencies.clear();
let ds = if self.data_sources.is_empty() {
None
} else {
Some(&self.data_sources)
};
let patches = reconcile_ir_with_ds(
&mut self.tree,
&expanded,
None,
state,
&mut self.dependencies,
ds,
);
self.emit_patches(patches);
self.revision += 1;
}
pub fn notify_state_change(&mut self, change: &StateChange) {
let mut affected_nodes = indexmap::IndexSet::new();
for path in change.paths() {
affected_nodes.extend(self.dependencies.get_affected_nodes(path));
}
self.scheduler
.mark_many_dirty(affected_nodes.iter().copied());
self.render_dirty();
}
pub fn update_state(&mut self, state_patch: serde_json::Value) {
if let Some(module) = &mut self.module {
module.update_state(state_patch.clone());
}
let change = StateChange::from_json(&state_patch);
self.notify_state_change(&change);
}
pub fn dispatch_action(&mut self, action: Action) -> Result<(), EngineError> {
self.actions.dispatch(&action)
}
fn render_dirty(&mut self) {
let ds = if self.data_sources.is_empty() {
None
} else {
Some(&self.data_sources)
};
let patches = crate::render::render_dirty_nodes_full(
&mut self.scheduler,
&mut self.tree,
self.module.as_ref(),
&mut self.dependencies,
ds,
);
if !patches.is_empty() {
self.emit_patches(patches);
self.revision += 1;
}
}
fn emit_patches(&self, patches: Vec<Patch>) {
if let Some(ref callback) = self.render_callback {
callback(&patches);
}
}
pub fn revision(&self) -> u64 {
self.revision
}
pub fn component_registry(&self) -> &ComponentRegistry {
&self.component_registry
}
pub fn component_registry_mut(&mut self) -> &mut ComponentRegistry {
&mut self.component_registry
}
pub fn resources(&self) -> &ResourceCache {
&self.resources
}
pub fn resources_mut(&mut self) -> &mut ResourceCache {
&mut self.resources
}
pub fn set_context(&mut self, name: &str, data: serde_json::Value) {
self.dependencies.register_data_source_provider(name);
let mut affected = indexmap::IndexSet::new();
if let Some(obj) = data.as_object() {
for key in obj.keys() {
let namespaced = format!("ds:{}:{}", name, key);
affected.extend(self.dependencies.get_affected_nodes(&namespaced));
}
}
affected.extend(
self.dependencies
.get_affected_nodes(&format!("ds:{}", name)),
);
self.data_sources.insert(name.to_string(), data);
self.scheduler
.mark_many_dirty(affected.iter().copied());
self.render_dirty();
}
pub fn remove_context(&mut self, name: &str) {
self.data_sources.shift_remove(name);
let affected = self.dependencies.get_data_source_affected_nodes(name);
if !affected.is_empty() {
self.scheduler
.mark_many_dirty(affected.iter().copied());
self.render_dirty();
}
}
pub fn data_sources(&self) -> &indexmap::IndexMap<String, serde_json::Value> {
&self.data_sources
}
}
impl Default for Engine {
fn default() -> Self {
Self::new()
}
}