1use crate::traits::{BackgroundConfig, EdgeStyle, NodeStyle};
4use flow_rs_core::{Position, Rect, Viewport};
5use std::collections::HashMap;
6
7pub struct PerformanceMonitor {
9 frame_times: Vec<f64>,
10 max_frame_samples: usize,
11 last_frame_time: f64,
12 render_stats: RenderStats,
13}
14
15#[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 }
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
100pub 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 if let Some(cell) = self.cells.get_mut(&(old_cell_x, old_cell_y)) {
144 cell.retain(|&i| i != index);
145 }
146
147 self.cells
149 .entry((new_cell_x, new_cell_y))
150 .or_default()
151 .push(index);
152
153 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 if let Some(cell) = self.cells.get_mut(&(cell_x, cell_y)) {
169 cell.retain(|&i| i != index);
170 }
171
172 self.node_positions.remove(index);
174 self.node_sizes.remove(index);
175
176 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 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
222pub 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
280pub 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
320pub 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#[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
433pub 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 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 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 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 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#[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#[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}