orbit/component/
performance.rs

1//! Performance optimization hooks for Orbit components
2//!
3//! This module provides various hooks and utilities for optimizing component performance,
4//! including memoization, batching, lazy loading, and render optimization.
5
6use std::collections::HashMap;
7use std::hash::Hash;
8use std::sync::{Arc, Mutex, RwLock};
9use std::time::{Duration, Instant};
10
11use crate::component::{Component, ComponentError, ComponentId, Context, Node, StateChanges};
12
13/// Trait for memoizable components
14pub trait Memoizable {
15    type MemoKey: Hash + Eq + Clone;
16
17    /// Generate a key for memoization
18    fn memo_key(&self) -> Self::MemoKey;
19
20    /// Check if component should re-render based on memo key
21    fn should_memo_update(&self, old_key: &Self::MemoKey, new_key: &Self::MemoKey) -> bool {
22        old_key != new_key
23    }
24}
25
26/// Memoization cache for component render results
27pub struct MemoCache<K, V> {
28    cache: RwLock<HashMap<K, CacheEntry<V>>>,
29    max_size: usize,
30    ttl: Duration,
31}
32
33#[derive(Clone)]
34struct CacheEntry<V> {
35    value: V,
36    created_at: Instant,
37    access_count: u64,
38}
39
40impl<K, V> MemoCache<K, V>
41where
42    K: Hash + Eq + Clone,
43    V: Clone,
44{
45    pub fn new(max_size: usize, ttl: Duration) -> Self {
46        Self {
47            cache: RwLock::new(HashMap::new()),
48            max_size,
49            ttl,
50        }
51    }
52
53    pub fn get(&self, key: &K) -> Option<V> {
54        let mut cache = self.cache.write().ok()?;
55
56        if let Some(entry) = cache.get_mut(key) {
57            // Check if entry is still valid
58            if entry.created_at.elapsed() < self.ttl {
59                entry.access_count += 1;
60                return Some(entry.value.clone());
61            } else {
62                // Remove expired entry
63                cache.remove(key);
64            }
65        }
66        None
67    }
68
69    pub fn set(&self, key: K, value: V) {
70        if let Ok(mut cache) = self.cache.write() {
71            // If cache is at capacity, remove least recently used item
72            if cache.len() >= self.max_size {
73                self.evict_lru(&mut cache);
74            }
75
76            cache.insert(
77                key,
78                CacheEntry {
79                    value,
80                    created_at: Instant::now(),
81                    access_count: 1,
82                },
83            );
84        }
85    }
86
87    fn evict_lru(&self, cache: &mut HashMap<K, CacheEntry<V>>) {
88        if let Some((key_to_remove, _)) = cache
89            .iter()
90            .min_by_key(|(_, entry)| entry.access_count)
91            .map(|(k, v)| (k.clone(), v.clone()))
92        {
93            cache.remove(&key_to_remove);
94        }
95    }
96
97    pub fn clear(&self) {
98        if let Ok(mut cache) = self.cache.write() {
99            cache.clear();
100        }
101    }
102
103    pub fn size(&self) -> usize {
104        self.cache.read().map(|c| c.len()).unwrap_or(0)
105    }
106}
107
108/// Memoized component wrapper
109pub struct MemoComponent<T>
110where
111    T: Component + Memoizable,
112{
113    component: T,
114    last_memo_key: Option<T::MemoKey>,
115    #[allow(dead_code)]
116    cached_render: Option<Vec<Node>>,
117    cache: Arc<MemoCache<T::MemoKey, Vec<Node>>>,
118}
119
120impl<T> MemoComponent<T>
121where
122    T: Component + Memoizable,
123    T::MemoKey: Send + Sync + 'static,
124{
125    pub fn new(component: T) -> Self {
126        Self {
127            component,
128            last_memo_key: None,
129            cached_render: None,
130            cache: Arc::new(MemoCache::new(100, Duration::from_secs(300))), // 5min TTL
131        }
132    }
133
134    pub fn with_cache(component: T, cache: Arc<MemoCache<T::MemoKey, Vec<Node>>>) -> Self {
135        Self {
136            component,
137            last_memo_key: None,
138            cached_render: None,
139            cache,
140        }
141    }
142}
143
144impl<T> Component for MemoComponent<T>
145where
146    T: Component + Memoizable + Send + Sync + 'static,
147    T::Props: Send + Sync + 'static,
148    T::MemoKey: Send + Sync + 'static,
149{
150    type Props = T::Props;
151    fn component_id(&self) -> ComponentId {
152        Component::component_id(&self.component)
153    }
154
155    fn create(props: Self::Props, context: Context) -> Self {
156        Self::new(T::create(props, context))
157    }
158
159    fn update(&mut self, props: Self::Props) -> Result<(), ComponentError> {
160        self.component.update(props)
161    }
162
163    fn should_update(&self, new_props: &Self::Props) -> bool {
164        self.component.should_update(new_props)
165    }
166
167    fn render(&self) -> Result<Vec<Node>, ComponentError> {
168        let current_key = self.component.memo_key();
169
170        // Check if we can use cached result
171        if let Some(ref last_key) = self.last_memo_key {
172            if !self.component.should_memo_update(last_key, &current_key) {
173                if let Some(cached) = self.cache.get(&current_key) {
174                    return Ok(cached);
175                }
176            }
177        }
178
179        // Render and cache result
180        let result = self.component.render()?;
181        self.cache.set(current_key, result.clone());
182        Ok(result)
183    }
184
185    fn as_any(&self) -> &dyn std::any::Any {
186        self
187    }
188
189    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
190        self
191    }
192}
193
194/// Performance monitoring hooks
195pub struct PerformanceMonitor {
196    render_times: RwLock<HashMap<ComponentId, Vec<Duration>>>,
197    update_times: RwLock<HashMap<ComponentId, Vec<Duration>>>,
198    mount_times: RwLock<HashMap<ComponentId, Duration>>,
199}
200
201impl Clone for PerformanceMonitor {
202    fn clone(&self) -> Self {
203        Self::new() // Create a new instance instead of cloning the data
204    }
205}
206
207impl PerformanceMonitor {
208    pub fn new() -> Self {
209        Self {
210            render_times: RwLock::new(HashMap::new()),
211            update_times: RwLock::new(HashMap::new()),
212            mount_times: RwLock::new(HashMap::new()),
213        }
214    }
215
216    pub fn start_render_timing(&self, component_id: ComponentId) -> RenderTimer {
217        RenderTimer::new(component_id, Arc::new(self.clone()))
218    }
219
220    pub fn record_render_time(&self, component_id: ComponentId, duration: Duration) {
221        if let Ok(mut times) = self.render_times.write() {
222            times
223                .entry(component_id)
224                .or_insert_with(Vec::new)
225                .push(duration);
226
227            // Keep only last 100 measurements
228            if let Some(component_times) = times.get_mut(&component_id) {
229                if component_times.len() > 100 {
230                    component_times.remove(0);
231                }
232            }
233        }
234    }
235
236    pub fn record_update_time(&self, component_id: ComponentId, duration: Duration) {
237        if let Ok(mut times) = self.update_times.write() {
238            times
239                .entry(component_id)
240                .or_insert_with(Vec::new)
241                .push(duration);
242
243            // Keep only last 100 measurements
244            if let Some(component_times) = times.get_mut(&component_id) {
245                if component_times.len() > 100 {
246                    component_times.remove(0);
247                }
248            }
249        }
250    }
251
252    pub fn record_mount_time(&self, component_id: ComponentId, duration: Duration) {
253        if let Ok(mut times) = self.mount_times.write() {
254            times.insert(component_id, duration);
255        }
256    }
257
258    pub fn get_average_render_time(&self, component_id: ComponentId) -> Option<Duration> {
259        if let Ok(times) = self.render_times.read() {
260            if let Some(component_times) = times.get(&component_id) {
261                if !component_times.is_empty() {
262                    let total: Duration = component_times.iter().sum();
263                    return Some(total / component_times.len() as u32);
264                }
265            }
266        }
267        None
268    }
269
270    pub fn get_render_statistics(&self, component_id: ComponentId) -> RenderStatistics {
271        if let Ok(times) = self.render_times.read() {
272            if let Some(component_times) = times.get(&component_id) {
273                if !component_times.is_empty() {
274                    let total: Duration = component_times.iter().sum();
275                    let average = total / component_times.len() as u32;
276                    let min = *component_times.iter().min().unwrap();
277                    let max = *component_times.iter().max().unwrap();
278
279                    return RenderStatistics {
280                        count: component_times.len(),
281                        total,
282                        average,
283                        min,
284                        max,
285                    };
286                }
287            }
288        }
289        RenderStatistics::default()
290    }
291}
292
293impl Default for PerformanceMonitor {
294    fn default() -> Self {
295        Self::new()
296    }
297}
298
299#[derive(Debug, Clone)]
300pub struct RenderStatistics {
301    pub count: usize,
302    pub total: Duration,
303    pub average: Duration,
304    pub min: Duration,
305    pub max: Duration,
306}
307
308impl Default for RenderStatistics {
309    fn default() -> Self {
310        Self {
311            count: 0,
312            total: Duration::ZERO,
313            average: Duration::ZERO,
314            min: Duration::ZERO,
315            max: Duration::ZERO,
316        }
317    }
318}
319
320/// Timer for measuring render performance
321pub struct RenderTimer {
322    component_id: ComponentId,
323    start_time: Instant,
324    monitor: Arc<PerformanceMonitor>,
325}
326
327impl RenderTimer {
328    fn new(component_id: ComponentId, monitor: Arc<PerformanceMonitor>) -> Self {
329        Self {
330            component_id,
331            start_time: Instant::now(),
332            monitor,
333        }
334    }
335}
336
337impl Drop for RenderTimer {
338    fn drop(&mut self) {
339        let duration = self.start_time.elapsed();
340        self.monitor.record_render_time(self.component_id, duration);
341    }
342}
343
344/// Batched update system for performance optimization
345pub struct UpdateBatcher {
346    pending_updates: Mutex<HashMap<ComponentId, Vec<StateChanges>>>,
347    batch_timeout: Duration,
348    max_batch_size: usize,
349}
350
351impl UpdateBatcher {
352    pub fn new(batch_timeout: Duration, max_batch_size: usize) -> Self {
353        Self {
354            pending_updates: Mutex::new(HashMap::new()),
355            batch_timeout,
356            max_batch_size,
357        }
358    }
359
360    pub fn queue_update(&self, component_id: ComponentId, changes: StateChanges) {
361        if let Ok(mut pending) = self.pending_updates.lock() {
362            pending
363                .entry(component_id)
364                .or_insert_with(Vec::new)
365                .push(changes);
366        }
367    }
368
369    pub fn flush_updates(&self) -> HashMap<ComponentId, Vec<StateChanges>> {
370        if let Ok(mut pending) = self.pending_updates.lock() {
371            let updates = pending.clone();
372            pending.clear();
373            updates
374        } else {
375            HashMap::new()
376        }
377    }
378
379    pub fn should_flush(&self, component_id: ComponentId) -> bool {
380        if let Ok(pending) = self.pending_updates.lock() {
381            if let Some(updates) = pending.get(&component_id) {
382                return updates.len() >= self.max_batch_size
383                    || updates
384                        .first()
385                        .map(|u| u.batch_timestamp.elapsed() >= self.batch_timeout)
386                        .unwrap_or(false);
387            }
388        }
389        false
390    }
391}
392
393/// Lazy loading component wrapper
394pub struct LazyComponent<T>
395where
396    T: Component,
397{
398    component: Option<T>,
399    props: Option<T::Props>,
400    context: Context,
401    loaded: bool,
402    load_trigger: LoadTrigger,
403}
404
405#[derive(Clone)]
406pub enum LoadTrigger {
407    Immediate,
408    OnMount,
409    OnFirstRender,
410    OnVisible,
411}
412
413impl<T> LazyComponent<T>
414where
415    T: Component,
416{
417    pub fn new(context: Context, load_trigger: LoadTrigger) -> Self {
418        Self {
419            component: None,
420            props: None,
421            context,
422            loaded: false,
423            load_trigger,
424        }
425    }
426
427    fn ensure_loaded(&mut self) -> Result<(), ComponentError> {
428        if !self.loaded {
429            if let Some(props) = self.props.clone() {
430                self.component = Some(T::create(props, self.context.clone()));
431                self.loaded = true;
432            }
433        }
434        Ok(())
435    }
436}
437
438impl<T> Component for LazyComponent<T>
439where
440    T: Component + Send + Sync + 'static,
441    T::Props: Send + Sync + 'static,
442{
443    type Props = T::Props;
444    fn component_id(&self) -> ComponentId {
445        if let Some(ref component) = self.component {
446            Component::component_id(component)
447        } else {
448            ComponentId::new() // Temporary ID until loaded
449        }
450    }
451
452    fn create(props: Self::Props, context: Context) -> Self {
453        let mut lazy = Self::new(context, LoadTrigger::OnMount);
454        lazy.props = Some(props);
455        lazy
456    }
457
458    fn mount(&mut self) -> Result<(), ComponentError> {
459        if matches!(
460            self.load_trigger,
461            LoadTrigger::OnMount | LoadTrigger::Immediate
462        ) {
463            self.ensure_loaded()?;
464            if let Some(ref mut component) = self.component {
465                component.mount()?;
466            }
467        }
468        Ok(())
469    }
470
471    fn update(&mut self, props: Self::Props) -> Result<(), ComponentError> {
472        self.props = Some(props.clone());
473        if let Some(ref mut component) = self.component {
474            component.update(props)?;
475        }
476        Ok(())
477    }
478
479    fn render(&self) -> Result<Vec<Node>, ComponentError> {
480        if let Some(ref component) = self.component {
481            component.render()
482        } else {
483            // Return empty or placeholder nodes for unloaded components
484            Ok(Vec::new())
485        }
486    }
487
488    fn as_any(&self) -> &dyn std::any::Any {
489        self
490    }
491
492    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
493        self
494    }
495}
496
497/// Global performance optimization registry
498pub struct PerformanceRegistry {
499    monitor: Arc<PerformanceMonitor>,
500    memo_cache: Arc<MemoCache<String, Vec<Node>>>,
501    update_batcher: Arc<UpdateBatcher>,
502}
503
504impl PerformanceRegistry {
505    pub fn new() -> Self {
506        Self {
507            monitor: Arc::new(PerformanceMonitor::new()),
508            memo_cache: Arc::new(MemoCache::new(1000, Duration::from_secs(600))),
509            update_batcher: Arc::new(UpdateBatcher::new(
510                Duration::from_millis(16), // ~60fps
511                10,                        // max 10 updates per batch
512            )),
513        }
514    }
515
516    pub fn monitor(&self) -> Arc<PerformanceMonitor> {
517        self.monitor.clone()
518    }
519
520    pub fn memo_cache(&self) -> Arc<MemoCache<String, Vec<Node>>> {
521        self.memo_cache.clone()
522    }
523
524    pub fn update_batcher(&self) -> Arc<UpdateBatcher> {
525        self.update_batcher.clone()
526    }
527}
528
529impl Default for PerformanceRegistry {
530    fn default() -> Self {
531        Self::new()
532    }
533}
534
535/// Macros for easy performance optimization
536///
537/// Create a memoized component
538#[macro_export]
539macro_rules! memo {
540    ($component:expr) => {
541        MemoComponent::new($component)
542    };
543    ($component:expr, $cache:expr) => {
544        MemoComponent::with_cache($component, $cache)
545    };
546}
547
548/// Create a lazy component
549#[macro_export]
550macro_rules! lazy {
551    ($component_type:ty, $context:expr) => {
552        LazyComponent::<$component_type>::new($context, LoadTrigger::OnMount)
553    };
554    ($component_type:ty, $context:expr, $trigger:expr) => {
555        LazyComponent::<$component_type>::new($context, $trigger)
556    };
557}
558
559#[cfg(test)]
560mod tests {
561    use super::*;
562    use crate::component::ComponentBase;
563
564    #[derive(Clone, Hash, PartialEq, Eq)]
565    struct TestMemoKey {
566        id: u64,
567        version: u32,
568    }
569
570    #[derive(Clone)]
571    struct TestProps {
572        id: u64,
573        version: u32,
574    }
575    // Using the blanket implementation of Props instead of implementing it manually
576
577    struct TestComponent {
578        base: ComponentBase,
579        props: TestProps,
580    }
581
582    impl Component for TestComponent {
583        type Props = TestProps;
584
585        fn component_id(&self) -> ComponentId {
586            self.base.id()
587        }
588
589        fn create(props: Self::Props, context: Context) -> Self {
590            Self {
591                base: ComponentBase::new(context),
592                props,
593            }
594        }
595
596        fn update(&mut self, props: Self::Props) -> Result<(), ComponentError> {
597            self.props = props;
598            Ok(())
599        }
600
601        fn render(&self) -> Result<Vec<Node>, ComponentError> {
602            Ok(vec![])
603        }
604
605        fn as_any(&self) -> &dyn std::any::Any {
606            self
607        }
608
609        fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
610            self
611        }
612    }
613
614    impl Memoizable for TestComponent {
615        type MemoKey = TestMemoKey;
616
617        fn memo_key(&self) -> Self::MemoKey {
618            TestMemoKey {
619                id: self.props.id,
620                version: self.props.version,
621            }
622        }
623    }
624
625    #[test]
626    fn test_memo_cache() {
627        let cache: MemoCache<String, Vec<Node>> = MemoCache::new(2, Duration::from_secs(1));
628
629        cache.set("key1".to_string(), vec![]);
630        cache.set("key2".to_string(), vec![]);
631        assert_eq!(cache.size(), 2);
632
633        // Adding third item should evict least used
634        cache.set("key3".to_string(), vec![]);
635        assert_eq!(cache.size(), 2);
636    }
637
638    #[test]
639    fn test_performance_monitor() {
640        let monitor = PerformanceMonitor::new();
641        let component_id = ComponentId::new();
642
643        monitor.record_render_time(component_id, Duration::from_millis(10));
644        monitor.record_render_time(component_id, Duration::from_millis(20));
645
646        let avg = monitor.get_average_render_time(component_id).unwrap();
647        assert_eq!(avg, Duration::from_millis(15));
648    }
649
650    #[test]
651    fn test_update_batcher() {
652        let batcher = UpdateBatcher::new(Duration::from_millis(100), 5);
653        let component_id = ComponentId::new();
654
655        let changes = StateChanges {
656            changes: vec![],
657            batch_timestamp: std::time::Instant::now(),
658            immediate: false,
659        };
660
661        batcher.queue_update(component_id, changes);
662        let updates = batcher.flush_updates();
663        assert!(updates.contains_key(&component_id));
664    }
665
666    #[test]
667    fn test_memoized_component() {
668        let context = Context::new();
669        let props = TestProps { id: 1, version: 1 };
670        let component = TestComponent::create(props, context);
671        let memo_component = MemoComponent::new(component);
672
673        assert!(memo_component.render().is_ok());
674    }
675}