windjammer_ui/
runtime.rs

1//! Component runtime - manages lifecycle, state, and re-rendering
2//!
3//! Design Philosophy:
4//! - Components are just data + render function
5//! - State changes trigger automatic re-renders
6//! - Runtime handles all the complexity
7//!
8//! Architecture:
9//! ```text
10//! Component State → Signal<T> → Update → Notify Runtime → Re-render → Patch DOM
11//! ```
12
13use crate::component::Component;
14use crate::events::ComponentEventDispatcher;
15use crate::vdom::VNode;
16use std::cell::RefCell;
17use std::rc::Rc;
18
19/// Component runtime manages the lifecycle of a mounted component
20pub struct ComponentRuntime<C: Component> {
21    #[allow(dead_code)]
22    component: Rc<RefCell<C>>,
23    #[allow(dead_code)]
24    current_vnode: Rc<RefCell<Option<VNode>>>,
25    event_dispatcher: Rc<RefCell<ComponentEventDispatcher>>,
26    #[cfg(target_arch = "wasm32")]
27    root_element: Option<web_sys::Element>,
28}
29
30impl<C: Component + 'static> ComponentRuntime<C> {
31    /// Create a new component runtime
32    pub fn new(component: C) -> Self {
33        Self {
34            component: Rc::new(RefCell::new(component)),
35            current_vnode: Rc::new(RefCell::new(None)),
36            event_dispatcher: Rc::new(RefCell::new(ComponentEventDispatcher::new())),
37            #[cfg(target_arch = "wasm32")]
38            root_element: None,
39        }
40    }
41
42    /// Mount the component to a DOM element
43    #[cfg(target_arch = "wasm32")]
44    pub fn mount(&mut self, target: web_sys::Element) -> Result<(), String> {
45        use crate::renderer::WebRenderer;
46
47        // Initial render
48        let vnode = self.component.borrow().render();
49
50        // Create DOM from VNode
51        let renderer = WebRenderer::new();
52        let dom_node = renderer.create_element(&vnode)?;
53
54        // Clear target and append
55        while let Some(child) = target.first_child() {
56            target
57                .remove_child(&child)
58                .map_err(|_| "Failed to clear target")?;
59        }
60
61        target
62            .append_child(&dom_node)
63            .map_err(|_| "Failed to mount component")?;
64
65        // Store current state
66        *self.current_vnode.borrow_mut() = Some(vnode);
67        self.root_element = Some(target);
68
69        Ok(())
70    }
71
72    /// Re-render the component (called when state changes)
73    #[cfg(target_arch = "wasm32")]
74    pub fn re_render(&self) -> Result<(), String> {
75        use crate::renderer::{Renderer, WebRenderer};
76        use crate::vdom::diff;
77
78        let new_vnode = self.component.borrow().render();
79
80        // Get the old VNode
81        let old_vnode = self.current_vnode.borrow();
82
83        if let Some(old) = old_vnode.as_ref() {
84            // Compute diff patches
85            let patches = diff(old, &new_vnode);
86
87            // Apply patches efficiently instead of full re-render
88            if !patches.is_empty() {
89                let mut renderer = WebRenderer::new();
90                renderer.patch(&patches)?;
91            }
92        } else {
93            // First render - do full render
94            if let Some(root) = &self.root_element {
95                let renderer = WebRenderer::new();
96                let dom_node = renderer.create_element(&new_vnode)?;
97                root.append_child(&dom_node)
98                    .map_err(|_| "Failed to append new content")?;
99            }
100        }
101
102        // Update stored VNode
103        drop(old_vnode); // Release borrow before mut borrow
104        *self.current_vnode.borrow_mut() = Some(new_vnode);
105
106        Ok(())
107    }
108
109    /// Register an event handler
110    pub fn register_event<F>(&mut self, event_name: String, handler: F)
111    where
112        F: Fn() + 'static,
113    {
114        self.event_dispatcher
115            .borrow_mut()
116            .register(event_name, handler);
117    }
118
119    /// Get the event dispatcher (for wiring up DOM events)
120    pub fn dispatcher(&self) -> Rc<RefCell<ComponentEventDispatcher>> {
121        self.event_dispatcher.clone()
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128    use crate::vdom::{VElement, VNode, VText};
129
130    struct TestComponent {
131        count: i32,
132    }
133
134    impl Component for TestComponent {
135        fn render(&self) -> VNode {
136            VElement::new("div")
137                .child(VNode::Text(VText::new(format!("Count: {}", self.count))))
138                .into()
139        }
140    }
141
142    #[test]
143    fn test_runtime_creation() {
144        let component = TestComponent { count: 0 };
145        let runtime = ComponentRuntime::new(component);
146        assert!(runtime.current_vnode.borrow().is_none());
147    }
148
149    #[test]
150    fn test_event_registration() {
151        let component = TestComponent { count: 0 };
152        let mut runtime = ComponentRuntime::new(component);
153
154        let called = Rc::new(RefCell::new(false));
155        let called_clone = called.clone();
156
157        runtime.register_event("click".to_string(), move || {
158            *called_clone.borrow_mut() = true;
159        });
160
161        assert!(!*called.borrow());
162        runtime.dispatcher().borrow().dispatch("click").unwrap();
163        assert!(*called.borrow());
164    }
165}