flow_rs_renderer/
performance.rs

1//! Performance optimization utilities for the Canvas2D renderer
2
3use crate::traits::{BackgroundConfig, EdgeStyle, NodeStyle};
4use flow_rs_core::{Position, Rect, Viewport};
5use std::collections::HashMap;
6
7/// Performance monitoring and optimization utilities
8pub struct PerformanceMonitor {
9    frame_times: Vec<f64>,
10    max_frame_samples: usize,
11    last_frame_time: f64,
12    render_stats: RenderStats,
13}
14
15/// Detailed rendering statistics
16#[derive(Debug, Clone)]
17pub struct RenderStats {
18    pub nodes_rendered: usize,
19    pub edges_rendered: usize,
20    pub nodes_culled: usize,
21    pub edges_culled: usize,
22    pub batch_count: usize,
23    pub draw_calls: usize,
24    pub frame_time_ms: f64,
25    pub memory_usage_bytes: usize,
26}
27
28impl Default for RenderStats {
29    fn default() -> Self {
30        Self {
31            nodes_rendered: 0,
32            edges_rendered: 0,
33            nodes_culled: 0,
34            edges_culled: 0,
35            batch_count: 0,
36            draw_calls: 0,
37            frame_time_ms: 0.0,
38            memory_usage_bytes: 0,
39        }
40    }
41}
42
43impl PerformanceMonitor {
44    pub fn new(max_samples: usize) -> Self {
45        Self {
46            frame_times: Vec::with_capacity(max_samples),
47            max_frame_samples: max_samples,
48            last_frame_time: 0.0,
49            render_stats: RenderStats::default(),
50        }
51    }
52
53    pub fn start_frame(&mut self) {
54        self.last_frame_time = web_sys::js_sys::Date::now();
55    }
56
57    pub fn end_frame(&mut self) {
58        let current_time = web_sys::js_sys::Date::now();
59        let frame_time = current_time - self.last_frame_time;
60
61        self.frame_times.push(frame_time);
62        if self.frame_times.len() > self.max_frame_samples {
63            self.frame_times.remove(0);
64        }
65
66        self.render_stats.frame_time_ms = frame_time;
67    }
68
69    pub fn get_average_frame_time(&self) -> f64 {
70        if self.frame_times.is_empty() {
71            return 0.0;
72        }
73
74        let sum: f64 = self.frame_times.iter().sum();
75        sum / self.frame_times.len() as f64
76    }
77
78    pub fn get_fps(&self) -> f64 {
79        let avg_frame_time = self.get_average_frame_time();
80        if avg_frame_time > 0.0 {
81            1000.0 / avg_frame_time
82        } else {
83            0.0
84        }
85    }
86
87    pub fn is_performance_good(&self) -> bool {
88        self.get_average_frame_time() < 16.67 // 60 FPS threshold
89    }
90
91    pub fn get_stats(&self) -> &RenderStats {
92        &self.render_stats
93    }
94
95    pub fn update_stats(&mut self, stats: RenderStats) {
96        self.render_stats = stats;
97    }
98}
99
100/// Spatial indexing for efficient culling
101pub struct SpatialIndex {
102    cell_size: f64,
103    cells: HashMap<(i32, i32), Vec<usize>>,
104    node_positions: Vec<Position>,
105    node_sizes: Vec<(f64, f64)>,
106}
107
108impl SpatialIndex {
109    pub fn new(cell_size: f64) -> Self {
110        Self {
111            cell_size,
112            cells: HashMap::new(),
113            node_positions: Vec::new(),
114            node_sizes: Vec::new(),
115        }
116    }
117
118    pub fn add_node(&mut self, position: Position, size: (f64, f64)) -> usize {
119        let index = self.node_positions.len();
120        self.node_positions.push(position);
121        self.node_sizes.push(size);
122
123        let cell_x = (position.x / self.cell_size).floor() as i32;
124        let cell_y = (position.y / self.cell_size).floor() as i32;
125
126        self.cells.entry((cell_x, cell_y)).or_default().push(index);
127        index
128    }
129
130    pub fn update_node(&mut self, index: usize, position: Position, size: (f64, f64)) {
131        if index >= self.node_positions.len() {
132            return;
133        }
134
135        let old_position = self.node_positions[index];
136        let old_cell_x = (old_position.x / self.cell_size).floor() as i32;
137        let old_cell_y = (old_position.y / self.cell_size).floor() as i32;
138
139        let new_cell_x = (position.x / self.cell_size).floor() as i32;
140        let new_cell_y = (position.y / self.cell_size).floor() as i32;
141
142        // Remove from old cell
143        if let Some(cell) = self.cells.get_mut(&(old_cell_x, old_cell_y)) {
144            cell.retain(|&i| i != index);
145        }
146
147        // Add to new cell
148        self.cells
149            .entry((new_cell_x, new_cell_y))
150            .or_default()
151            .push(index);
152
153        // Update position and size
154        self.node_positions[index] = position;
155        self.node_sizes[index] = size;
156    }
157
158    pub fn remove_node(&mut self, index: usize) {
159        if index >= self.node_positions.len() {
160            return;
161        }
162
163        let position = self.node_positions[index];
164        let cell_x = (position.x / self.cell_size).floor() as i32;
165        let cell_y = (position.y / self.cell_size).floor() as i32;
166
167        // Remove from cell
168        if let Some(cell) = self.cells.get_mut(&(cell_x, cell_y)) {
169            cell.retain(|&i| i != index);
170        }
171
172        // Remove from arrays
173        self.node_positions.remove(index);
174        self.node_sizes.remove(index);
175
176        // Update indices in all cells
177        for cell in self.cells.values_mut() {
178            for i in cell.iter_mut() {
179                if *i > index {
180                    *i -= 1;
181                }
182            }
183        }
184    }
185
186    pub fn query_rect(&self, rect: Rect) -> Vec<usize> {
187        let mut result = Vec::new();
188
189        let min_cell_x = (rect.x / self.cell_size).floor() as i32;
190        let min_cell_y = (rect.y / self.cell_size).floor() as i32;
191        let max_cell_x = ((rect.x + rect.width) / self.cell_size).ceil() as i32;
192        let max_cell_y = ((rect.y + rect.height) / self.cell_size).ceil() as i32;
193
194        for cell_x in min_cell_x..=max_cell_x {
195            for cell_y in min_cell_y..=max_cell_y {
196                if let Some(cell) = self.cells.get(&(cell_x, cell_y)) {
197                    for &index in cell {
198                        if index < self.node_positions.len() {
199                            let pos = self.node_positions[index];
200                            let size = self.node_sizes[index];
201
202                            // Check if node actually intersects with query rect
203                            if rect.intersects(&Rect::new(pos.x, pos.y, size.0, size.1)) {
204                                result.push(index);
205                            }
206                        }
207                    }
208                }
209            }
210        }
211
212        result
213    }
214
215    pub fn clear(&mut self) {
216        self.cells.clear();
217        self.node_positions.clear();
218        self.node_sizes.clear();
219    }
220}
221
222/// Batching system for efficient rendering
223pub struct RenderBatch {
224    pub nodes: Vec<BatchedNode>,
225    pub edges: Vec<BatchedEdge>,
226    pub background_config: Option<BackgroundConfig>,
227}
228
229#[derive(Debug, Clone, Default)]
230pub struct BatchedNode {
231    pub position: Position,
232    pub size: (f64, f64),
233    pub style: NodeStyle,
234    pub z_index: i32,
235}
236
237#[derive(Debug, Clone, Default)]
238pub struct BatchedEdge {
239    pub source_pos: Position,
240    pub target_pos: Position,
241    pub style: EdgeStyle,
242    pub z_index: i32,
243}
244
245impl Default for RenderBatch {
246    fn default() -> Self {
247        Self::new()
248    }
249}
250
251impl RenderBatch {
252    pub fn new() -> Self {
253        Self {
254            nodes: Vec::new(),
255            edges: Vec::new(),
256            background_config: None,
257        }
258    }
259
260    pub fn add_node(&mut self, node: BatchedNode) {
261        self.nodes.push(node);
262    }
263
264    pub fn add_edge(&mut self, edge: BatchedEdge) {
265        self.edges.push(edge);
266    }
267
268    pub fn sort_by_z_index(&mut self) {
269        self.nodes.sort_by_key(|node| node.z_index);
270        self.edges.sort_by_key(|edge| edge.z_index);
271    }
272
273    pub fn clear(&mut self) {
274        self.nodes.clear();
275        self.edges.clear();
276        self.background_config = None;
277    }
278}
279
280/// Level of detail system for performance optimization
281pub struct LODSystem {
282    zoom_levels: Vec<f64>,
283    detail_thresholds: Vec<usize>,
284}
285
286impl Default for LODSystem {
287    fn default() -> Self {
288        Self::new()
289    }
290}
291
292impl LODSystem {
293    pub fn new() -> Self {
294        Self {
295            zoom_levels: vec![0.5, 1.0, 2.0, 4.0],
296            detail_thresholds: vec![100, 50, 25, 10],
297        }
298    }
299
300    pub fn get_detail_level(&self, zoom: f64) -> usize {
301        for (i, &level) in self.zoom_levels.iter().enumerate() {
302            if zoom <= level {
303                return self.detail_thresholds[i];
304            }
305        }
306        self.detail_thresholds.last().copied().unwrap_or(10)
307    }
308
309    pub fn should_render_node(&self, zoom: f64, node_size: f64) -> bool {
310        let detail_level = self.get_detail_level(zoom);
311        node_size >= detail_level as f64
312    }
313
314    pub fn should_render_edge(&self, zoom: f64, edge_length: f64) -> bool {
315        let detail_level = self.get_detail_level(zoom);
316        edge_length >= detail_level as f64
317    }
318}
319
320/// Memory pool for efficient allocation
321pub struct MemoryPool<T> {
322    pool: Vec<T>,
323    available: Vec<usize>,
324    next_id: usize,
325}
326
327impl<T: Default> MemoryPool<T> {
328    pub fn new(initial_capacity: usize) -> Self {
329        let mut pool = Vec::with_capacity(initial_capacity);
330        let mut available = Vec::with_capacity(initial_capacity);
331
332        for i in 0..initial_capacity {
333            pool.push(T::default());
334            available.push(i);
335        }
336
337        Self {
338            pool,
339            available,
340            next_id: initial_capacity,
341        }
342    }
343
344    pub fn allocate(&mut self) -> usize {
345        if let Some(id) = self.available.pop() {
346            id
347        } else {
348            let id = self.next_id;
349            self.pool.push(T::default());
350            self.next_id += 1;
351            id
352        }
353    }
354
355    pub fn deallocate(&mut self, id: usize) {
356        if id < self.pool.len() {
357            self.available.push(id);
358        }
359    }
360
361    pub fn get(&self, id: usize) -> Option<&T> {
362        self.pool.get(id)
363    }
364
365    pub fn get_mut(&mut self, id: usize) -> Option<&mut T> {
366        self.pool.get_mut(id)
367    }
368
369    pub fn clear(&mut self) {
370        self.available.clear();
371        for i in 0..self.pool.len() {
372            self.available.push(i);
373        }
374    }
375}
376
377/// Performance optimization settings
378#[derive(Debug, Clone)]
379pub struct PerformanceSettings {
380    pub enable_culling: bool,
381    pub enable_batching: bool,
382    pub enable_lod: bool,
383    pub max_nodes_per_frame: usize,
384    pub max_edges_per_frame: usize,
385    pub target_fps: f64,
386    pub cull_margin: f64,
387}
388
389impl Default for PerformanceSettings {
390    fn default() -> Self {
391        Self {
392            enable_culling: true,
393            enable_batching: true,
394            enable_lod: true,
395            max_nodes_per_frame: 1000,
396            max_edges_per_frame: 2000,
397            target_fps: 60.0,
398            cull_margin: 100.0,
399        }
400    }
401}
402
403impl PerformanceSettings {
404    pub fn high_performance() -> Self {
405        Self {
406            enable_culling: true,
407            enable_batching: true,
408            enable_lod: true,
409            max_nodes_per_frame: 500,
410            max_edges_per_frame: 1000,
411            target_fps: 60.0,
412            cull_margin: 50.0,
413        }
414    }
415
416    pub fn balanced() -> Self {
417        Self::default()
418    }
419
420    pub fn high_quality() -> Self {
421        Self {
422            enable_culling: false,
423            enable_batching: true,
424            enable_lod: false,
425            max_nodes_per_frame: 2000,
426            max_edges_per_frame: 4000,
427            target_fps: 30.0,
428            cull_margin: 0.0,
429        }
430    }
431}
432
433/// Performance optimization manager
434pub struct PerformanceManager {
435    monitor: PerformanceMonitor,
436    #[allow(dead_code)]
437    spatial_index: SpatialIndex,
438    render_batch: RenderBatch,
439    lod_system: LODSystem,
440    settings: PerformanceSettings,
441    #[allow(dead_code)]
442    node_pool: MemoryPool<BatchedNode>,
443    #[allow(dead_code)]
444    edge_pool: MemoryPool<BatchedEdge>,
445}
446
447impl PerformanceManager {
448    pub fn new(settings: PerformanceSettings) -> Self {
449        Self {
450            monitor: PerformanceMonitor::new(100),
451            spatial_index: SpatialIndex::new(50.0),
452            render_batch: RenderBatch::new(),
453            lod_system: LODSystem::new(),
454            settings,
455            node_pool: MemoryPool::new(1000),
456            edge_pool: MemoryPool::new(2000),
457        }
458    }
459
460    pub fn start_frame(&mut self) {
461        self.monitor.start_frame();
462        self.render_batch.clear();
463    }
464
465    pub fn end_frame(&mut self) {
466        self.monitor.end_frame();
467    }
468
469    pub fn should_render_node(
470        &self,
471        position: Position,
472        size: (f64, f64),
473        viewport: &Viewport,
474    ) -> bool {
475        if !self.settings.enable_culling {
476            return true;
477        }
478
479        // Viewport culling
480        let node_rect = Rect::new(position.x, position.y, size.0, size.1);
481        let viewport_rect = viewport.bounds().expand(self.settings.cull_margin);
482
483        if !viewport_rect.intersects(&node_rect) {
484            return false;
485        }
486
487        // LOD culling
488        if self.settings.enable_lod {
489            let zoom = viewport.zoom;
490            let node_size = size.0.max(size.1);
491            return self.lod_system.should_render_node(zoom, node_size);
492        }
493
494        true
495    }
496
497    pub fn should_render_edge(
498        &self,
499        source_pos: Position,
500        target_pos: Position,
501        viewport: &Viewport,
502    ) -> bool {
503        if !self.settings.enable_culling {
504            return true;
505        }
506
507        // Viewport culling
508        let edge_rect = Rect::from_points(source_pos, target_pos);
509        let viewport_rect = viewport.bounds().expand(self.settings.cull_margin);
510
511        if !viewport_rect.intersects(&edge_rect) {
512            return false;
513        }
514
515        // LOD culling
516        if self.settings.enable_lod {
517            let zoom = viewport.zoom;
518            let edge_length = source_pos.distance_to(target_pos);
519            return self.lod_system.should_render_edge(zoom, edge_length);
520        }
521
522        true
523    }
524
525    pub fn add_node_to_batch(&mut self, node: BatchedNode) {
526        if self.render_batch.nodes.len() < self.settings.max_nodes_per_frame {
527            self.render_batch.add_node(node);
528        }
529    }
530
531    pub fn add_edge_to_batch(&mut self, edge: BatchedEdge) {
532        if self.render_batch.edges.len() < self.settings.max_edges_per_frame {
533            self.render_batch.add_edge(edge);
534        }
535    }
536
537    pub fn get_render_batch(&mut self) -> &mut RenderBatch {
538        self.render_batch.sort_by_z_index();
539        &mut self.render_batch
540    }
541
542    pub fn get_performance_stats(&self) -> &RenderStats {
543        self.monitor.get_stats()
544    }
545
546    pub fn update_settings(&mut self, settings: PerformanceSettings) {
547        self.settings = settings;
548    }
549
550    pub fn is_performance_good(&self) -> bool {
551        self.monitor.is_performance_good()
552    }
553
554    pub fn get_fps(&self) -> f64 {
555        self.monitor.get_fps()
556    }
557}
558
559// Helper trait for Rect operations
560#[allow(dead_code)]
561trait RectExt {
562    fn expand(&self, margin: f64) -> Self;
563    fn intersects(&self, other: &Self) -> bool;
564}
565
566impl RectExt for Rect {
567    fn expand(&self, margin: f64) -> Self {
568        Rect::new(
569            self.x - margin,
570            self.y - margin,
571            self.width + 2.0 * margin,
572            self.height + 2.0 * margin,
573        )
574    }
575
576    fn intersects(&self, other: &Self) -> bool {
577        self.x < other.x + other.width
578            && self.x + self.width > other.x
579            && self.y < other.y + other.height
580            && self.y + self.height > other.y
581    }
582}
583
584// Helper trait for Position operations
585#[allow(dead_code)]
586trait PositionExt {
587    fn distance_to(&self, other: Self) -> f64;
588}
589
590impl PositionExt for Position {
591    fn distance_to(&self, other: Self) -> f64 {
592        let dx = self.x - other.x;
593        let dy = self.y - other.y;
594        (dx * dx + dy * dy).sqrt()
595    }
596}