orbit/component/
enhanced_context.rs

1//! Enhanced component context implementation
2//!
3//! This module defines the core Context structure that integrates state management,
4//! event handling, and component updates into a single unified system.
5
6use std::{
7    collections::HashMap,
8    sync::{Arc, Mutex, RwLock},
9};
10
11use crate::{
12    component::{
13        update_scheduler::{UpdatePriority, UpdateScheduler},
14        AnyComponent, ComponentId, LifecyclePhase,
15    },
16    events::EventEmitter,
17    state::{ReactiveScope, StateContainer},
18};
19
20/// A context hook that can be executed at specific lifecycle phases
21type LifecycleHook = Box<dyn FnMut(&mut dyn AnyComponent) + Send + Sync>;
22type LifecycleHooks = HashMap<LifecyclePhase, Vec<LifecycleHook>>;
23
24/// Context passed to components providing access to state and events
25#[derive(Clone)]
26pub struct Context {
27    /// State container for managing component and app state
28    state: StateContainer,
29
30    /// Reactive scope for fine-grained reactivity
31    reactive_scope: Arc<ReactiveScope>,
32
33    /// Event emitter for handling UI events
34    events: EventEmitter,
35
36    /// Update scheduler for batching component updates
37    update_scheduler: UpdateScheduler,
38
39    /// Component lifecycle hooks
40    lifecycle_hooks: Arc<Mutex<LifecycleHooks>>,
41
42    /// Current lifecycle phase
43    lifecycle_phase: Arc<RwLock<LifecyclePhase>>,
44
45    /// Component ID that owns this context
46    component_id: Option<ComponentId>,
47}
48
49impl Default for Context {
50    fn default() -> Self {
51        Self::new()
52    }
53}
54
55impl Context {
56    /// Create a new component context
57    pub fn new() -> Self {
58        Self {
59            state: StateContainer::new(),
60            reactive_scope: Arc::new(ReactiveScope::new()),
61            events: EventEmitter::new(),
62            update_scheduler: UpdateScheduler::new(),
63            lifecycle_hooks: Arc::new(Mutex::new(HashMap::new())),
64            lifecycle_phase: Arc::new(RwLock::new(LifecyclePhase::Created)),
65            component_id: None,
66        }
67    }
68
69    /// Create a context for a specific component
70    pub fn for_component(component_id: ComponentId) -> Self {
71        let mut context = Self::new();
72        context.component_id = Some(component_id);
73        context
74    }
75
76    /// Get a reference to the state container
77    pub fn state(&self) -> &StateContainer {
78        &self.state
79    }
80
81    /// Get a mutable reference to the state container
82    pub fn state_mut(&mut self) -> &mut StateContainer {
83        &mut self.state
84    }
85
86    /// Get a reference to the reactive scope
87    pub fn reactive_scope(&self) -> &Arc<ReactiveScope> {
88        &self.reactive_scope
89    }
90
91    /// Get a reference to the event emitter
92    pub fn events(&self) -> &EventEmitter {
93        &self.events
94    }
95
96    /// Get a mutable reference to the event emitter
97    pub fn events_mut(&mut self) -> &mut EventEmitter {
98        &mut self.events
99    }
100
101    /// Get the update scheduler
102    pub fn update_scheduler(&self) -> &UpdateScheduler {
103        &self.update_scheduler
104    }
105
106    /// Get current lifecycle phase
107    pub fn lifecycle_phase(&self) -> LifecyclePhase {
108        self.lifecycle_phase
109            .read()
110            .map(|phase| *phase)
111            .unwrap_or_else(|_| {
112                // If we can't read the lock, default to Created
113                // This should never happen in normal operation
114                eprintln!("Error reading lifecycle phase, defaulting to Created");
115                LifecyclePhase::Created
116            })
117    }
118
119    /// Set current lifecycle phase
120    pub fn set_lifecycle_phase(&self, phase: LifecyclePhase) {
121        if let Ok(mut current_phase) = self.lifecycle_phase.write() {
122            *current_phase = phase;
123        }
124    }
125
126    /// Register a hook to be called at a specific lifecycle phase
127    pub fn register_lifecycle_hook<F>(&self, phase: LifecyclePhase, hook: F)
128    where
129        F: FnMut(&mut dyn AnyComponent) + Send + Sync + 'static,
130    {
131        if let Ok(mut hooks) = self.lifecycle_hooks.lock() {
132            hooks
133                .entry(phase)
134                .or_insert_with(Vec::new)
135                .push(Box::new(hook));
136        }
137    }
138
139    /// Execute all hooks for the current lifecycle phase
140    pub fn execute_lifecycle_hooks(&self, phase: LifecyclePhase, component: &mut dyn AnyComponent) {
141        if let Ok(mut hooks) = self.lifecycle_hooks.lock() {
142            if let Some(phase_hooks) = hooks.get_mut(&phase) {
143                for hook in phase_hooks.iter_mut() {
144                    hook(component);
145                }
146            }
147        }
148    }
149
150    /// Request a component update with normal priority
151    pub fn request_update(&self, component_id: ComponentId) -> Result<(), String> {
152        self.update_scheduler
153            .schedule_update(component_id, UpdatePriority::Normal)
154    }
155
156    /// Request a critical update (executed immediately if possible)
157    pub fn request_critical_update(&self, component_id: ComponentId) -> Result<(), String> {
158        self.update_scheduler
159            .schedule_update(component_id, UpdatePriority::Critical)
160    }
161
162    /// Process all pending updates
163    pub fn process_updates<F>(&self, update_fn: F) -> Result<usize, String>
164    where
165        F: FnMut(ComponentId) -> Result<(), String>,
166    {
167        self.update_scheduler.process_updates(update_fn)
168    }
169
170    /// Get current component ID if set
171    pub fn component_id(&self) -> Option<ComponentId> {
172        self.component_id
173    }
174
175    /// Set the component ID for this context
176    pub fn set_component_id(&mut self, id: ComponentId) {
177        self.component_id = Some(id);
178    }
179
180    /// Create a child context with this context as parent
181    pub fn create_child_context(&self, child_id: ComponentId) -> Self {
182        let mut child = self.clone();
183        child.set_component_id(child_id);
184        child
185    }
186}
187
188impl std::fmt::Debug for Context {
189    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190        f.debug_struct("Context")
191            .field("state", &self.state)
192            .field("reactive_scope", &self.reactive_scope)
193            .field("events", &self.events)
194            .field("update_scheduler", &self.update_scheduler)
195            .field(
196                "lifecycle_hooks",
197                &format!(
198                    "<{} hooks>",
199                    self.lifecycle_hooks
200                        .lock()
201                        .map(|hooks| hooks.len())
202                        .unwrap_or(0)
203                ),
204            )
205            .field("lifecycle_phase", &self.lifecycle_phase)
206            .field("component_id", &self.component_id)
207            .finish()
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214    use crate::component::{ComponentError, ComponentId, LifecyclePhase, Props};
215
216    // Mock component for testing
217    struct TestComponent {
218        id: ComponentId,
219        context: Context,
220        lifecycle_events: Vec<String>,
221        phase: LifecyclePhase,
222    }
223
224    impl TestComponent {
225        fn new(context: Context) -> Self {
226            let id = ComponentId::new();
227            Self {
228                id,
229                context,
230                lifecycle_events: Vec::new(),
231                phase: LifecyclePhase::Created,
232            }
233        }
234    }
235
236    impl AnyComponent for TestComponent {
237        fn component_id(&self) -> ComponentId {
238            self.id
239        }
240
241        fn lifecycle_phase(&self) -> LifecyclePhase {
242            self.phase
243        }
244
245        fn set_lifecycle_phase(&mut self, phase: LifecyclePhase) {
246            self.phase = phase;
247        }
248
249        fn request_update(&mut self) -> Result<(), ComponentError> {
250            self.context
251                .request_update(self.id)
252                .map_err(|e| ComponentError::UpdateError(e))
253        }
254
255        fn as_any(&self) -> &dyn std::any::Any {
256            self
257        }
258
259        fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
260            self
261        }
262
263        fn type_name(&self) -> &'static str {
264            "TestComponent"
265        }
266
267        fn any_initialize(&mut self) -> Result<(), ComponentError> {
268            self.lifecycle_events.push("initialize".to_string());
269            Ok(())
270        }
271
272        fn any_mount(&mut self) -> Result<(), ComponentError> {
273            self.lifecycle_events.push("mount".to_string());
274            Ok(())
275        }
276
277        fn any_before_update(&mut self, _props: Box<dyn Props>) -> Result<(), ComponentError> {
278            self.lifecycle_events.push("before_update".to_string());
279            Ok(())
280        }
281
282        fn any_update(&mut self, _props: Box<dyn Props>) -> Result<(), ComponentError> {
283            self.lifecycle_events.push("update".to_string());
284            Ok(())
285        }
286
287        fn any_after_update(&mut self) -> Result<(), ComponentError> {
288            self.lifecycle_events.push("after_update".to_string());
289            Ok(())
290        }
291
292        fn any_before_unmount(&mut self) -> Result<(), ComponentError> {
293            self.lifecycle_events.push("before_unmount".to_string());
294            Ok(())
295        }
296
297        fn any_unmount(&mut self) -> Result<(), ComponentError> {
298            self.lifecycle_events.push("unmount".to_string());
299            Ok(())
300        }
301
302        fn any_on_mount(
303            &mut self,
304            _context: &crate::component::MountContext,
305        ) -> Result<(), ComponentError> {
306            self.lifecycle_events.push("on_mount".to_string());
307            Ok(())
308        }
309
310        fn any_before_mount(&mut self) -> Result<(), ComponentError> {
311            self.lifecycle_events.push("before_mount".to_string());
312            Ok(())
313        }
314
315        fn any_after_mount(&mut self) -> Result<(), ComponentError> {
316            self.lifecycle_events.push("after_mount".to_string());
317            Ok(())
318        }
319
320        fn any_on_update(
321            &mut self,
322            _changes: &crate::component::state_tracking::StateChanges,
323        ) -> Result<(), ComponentError> {
324            self.lifecycle_events.push("on_update".to_string());
325            Ok(())
326        }
327
328        fn any_on_unmount(
329            &mut self,
330            _context: &crate::component::UnmountContext,
331        ) -> Result<(), ComponentError> {
332            self.lifecycle_events.push("on_unmount".to_string());
333            Ok(())
334        }
335
336        fn any_after_unmount(&mut self) -> Result<(), ComponentError> {
337            self.lifecycle_events.push("after_unmount".to_string());
338            Ok(())
339        }
340    }
341
342    #[test]
343    fn test_context_lifecycle_hooks() {
344        // Create a context
345        let context = Context::new();
346
347        // Register a couple of hooks
348        context.register_lifecycle_hook(LifecyclePhase::Mounting, |component| {
349            let any = component.as_any_mut();
350            if let Some(test_component) = any.downcast_mut::<TestComponent>() {
351                test_component
352                    .lifecycle_events
353                    .push("mounting_hook".to_string());
354            }
355        });
356
357        context.register_lifecycle_hook(LifecyclePhase::Unmounting, |component| {
358            let any = component.as_any_mut();
359            if let Some(test_component) = any.downcast_mut::<TestComponent>() {
360                test_component
361                    .lifecycle_events
362                    .push("unmounting_hook".to_string());
363            }
364        });
365
366        // Create a test component
367        let mut component = TestComponent::new(context.clone());
368
369        // Execute mounting hooks
370        context.execute_lifecycle_hooks(LifecyclePhase::Mounting, &mut component);
371
372        // Verify the hook was executed
373        assert!(component
374            .lifecycle_events
375            .contains(&"mounting_hook".to_string()));
376
377        // Execute unmounting hooks
378        context.execute_lifecycle_hooks(LifecyclePhase::Unmounting, &mut component);
379
380        // Verify the hook was executed
381        assert!(component
382            .lifecycle_events
383            .contains(&"unmounting_hook".to_string()));
384    }
385
386    #[test]
387    fn test_context_update_scheduling() {
388        // Create a context
389        let context = Context::new();
390
391        // Create some component IDs
392        let c1 = ComponentId::new();
393        let c2 = ComponentId::new();
394
395        // Schedule updates
396        context.request_update(c1).unwrap();
397        context.request_critical_update(c2).unwrap();
398
399        // Process updates
400        let mut processed = Vec::new();
401        let updated = context
402            .process_updates(|id| {
403                processed.push(id);
404                Ok(())
405            })
406            .unwrap();
407
408        // Verify updates were processed
409        assert_eq!(updated, 2);
410        assert_eq!(processed.len(), 2);
411
412        // Critical update should be first
413        assert_eq!(processed[0], c2);
414        assert_eq!(processed[1], c1);
415    }
416
417    #[test]
418    fn test_child_context() {
419        // Create a parent context
420        let parent_context = Context::new();
421        let parent_id = ComponentId::new();
422
423        // Create a child context
424        let child_id = ComponentId::new();
425        let child_context = parent_context.create_child_context(child_id);
426
427        // Verify the child has the correct ID
428        assert_eq!(child_context.component_id(), Some(child_id));
429
430        // Verify they share the same update scheduler
431        parent_context.request_update(parent_id).unwrap();
432
433        // Both contexts should see the pending update
434        assert!(parent_context
435            .update_scheduler
436            .has_pending_updates()
437            .unwrap());
438        assert!(child_context
439            .update_scheduler
440            .has_pending_updates()
441            .unwrap());
442    }
443}