1use std::collections::HashMap;
36use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
37use std::sync::{Arc, Mutex, RwLock};
38use std::thread::{self, JoinHandle};
39use std::time::Instant;
40
41use astrelis_core::geometry::Size;
42use astrelis_core::profiling::profile_function;
43
44use crate::plugin::registry::WidgetTypeRegistry;
45use crate::tree::{LayoutRect, NodeId, UiTree};
46
47type WorkerChannels = (
49 Option<std::sync::mpsc::Sender<WorkerMessage>>,
50 Option<std::sync::mpsc::Receiver<LayoutResult>>,
51 Option<JoinHandle<()>>,
52);
53
54#[derive(Debug, Clone, Default)]
56pub enum LayoutMode {
57 #[default]
60 Synchronous,
61
62 Asynchronous {
65 max_stale_frames: u32,
68 },
69
70 Hybrid {
72 async_threshold: usize,
74 max_stale_frames: u32,
76 },
77}
78
79#[derive(Debug, Clone)]
81pub struct NodeSnapshot {
82 pub node_id: NodeId,
84 pub style: taffy::Style,
86 pub parent: Option<usize>,
88 pub children: Vec<usize>,
90 pub caches_measurement: bool,
92 pub text_measurement: Option<(f32, f32)>,
94}
95
96#[derive(Debug, Clone)]
98pub struct TreeSnapshot {
99 pub nodes: Vec<NodeSnapshot>,
101 pub root: Option<usize>,
103 pub dirty_nodes: Vec<usize>,
105 pub id_to_index: HashMap<NodeId, usize>,
107}
108
109impl TreeSnapshot {
110 pub fn from_tree(tree: &UiTree, widget_registry: &WidgetTypeRegistry) -> Self {
112 let mut nodes = Vec::new();
113 let mut id_to_index = HashMap::new();
114 let mut dirty_nodes = Vec::new();
115
116 for (node_id, node) in tree.iter() {
118 let index = nodes.len();
119 id_to_index.insert(node_id, index);
120
121 if !node.dirty_flags.is_empty() {
122 dirty_nodes.push(index);
123 }
124
125 let caches_measurement =
127 widget_registry.caches_measurement(node.widget.as_any().type_id());
128
129 nodes.push(NodeSnapshot {
130 node_id,
131 style: node.widget.style().layout.clone(),
132 parent: None, children: Vec::new(), caches_measurement,
135 text_measurement: node.text_measurement,
136 });
137 }
138
139 for (node_id, node) in tree.iter() {
141 if let Some(&index) = id_to_index.get(&node_id) {
142 if let Some(parent_id) = node.parent
144 && let Some(&parent_index) = id_to_index.get(&parent_id)
145 {
146 nodes[index].parent = Some(parent_index);
147 }
148
149 let child_indices: Vec<usize> = node
151 .children
152 .iter()
153 .filter_map(|child_id| id_to_index.get(child_id).copied())
154 .collect();
155 nodes[index].children = child_indices;
156 }
157 }
158
159 let root = tree.root().and_then(|id| id_to_index.get(&id).copied());
160
161 Self {
162 nodes,
163 root,
164 dirty_nodes,
165 id_to_index,
166 }
167 }
168
169 pub fn node_count(&self) -> usize {
171 self.nodes.len()
172 }
173}
174
175#[derive(Debug)]
177pub struct LayoutRequest {
178 tree_snapshot: TreeSnapshot,
180 viewport_size: Size<f32>,
182 frame_id: u64,
184 _timestamp: Instant,
186}
187
188#[derive(Debug, Clone)]
190pub struct LayoutResult {
191 pub frame_id: u64,
193 pub layouts: HashMap<NodeId, LayoutRect>,
195 pub compute_time: std::time::Duration,
197 pub is_partial: bool,
199}
200
201struct LayoutCache {
203 front: RwLock<HashMap<NodeId, LayoutRect>>,
205 back: Mutex<HashMap<NodeId, LayoutRect>>,
207 front_frame_id: AtomicU64,
209 swap_pending: AtomicBool,
211}
212
213impl LayoutCache {
214 fn new() -> Self {
215 Self {
216 front: RwLock::new(HashMap::new()),
217 back: Mutex::new(HashMap::new()),
218 front_frame_id: AtomicU64::new(0),
219 swap_pending: AtomicBool::new(false),
220 }
221 }
222
223 fn get(&self, node_id: NodeId) -> Option<LayoutRect> {
225 self.front.read().ok()?.get(&node_id).copied()
226 }
227
228 fn write_back(&self, node_id: NodeId, layout: LayoutRect) {
230 if let Ok(mut back) = self.back.lock() {
231 back.insert(node_id, layout);
232 }
233 }
234
235 fn swap(&self, frame_id: u64) {
237 if let (Ok(mut front), Ok(mut back)) = (self.front.write(), self.back.lock()) {
238 std::mem::swap(&mut *front, &mut *back);
239 self.front_frame_id.store(frame_id, Ordering::SeqCst);
240 back.clear();
241 self.swap_pending.store(false, Ordering::SeqCst);
242 }
243 }
244
245 fn mark_swap_pending(&self) {
247 self.swap_pending.store(true, Ordering::SeqCst);
248 }
249
250 fn is_swap_pending(&self) -> bool {
252 self.swap_pending.load(Ordering::SeqCst)
253 }
254
255 fn front_frame_id(&self) -> u64 {
257 self.front_frame_id.load(Ordering::SeqCst)
258 }
259}
260
261enum WorkerMessage {
263 Compute(LayoutRequest),
265 Shutdown,
267}
268
269pub struct LayoutEngine {
271 mode: LayoutMode,
273 cache: Arc<LayoutCache>,
275 frame_id: u64,
277 last_completed_frame: u64,
279
280 request_sender: Option<std::sync::mpsc::Sender<WorkerMessage>>,
283 result_receiver: Option<std::sync::mpsc::Receiver<LayoutResult>>,
285 worker_handle: Option<JoinHandle<()>>,
287 layout_in_progress: Arc<AtomicBool>,
289}
290
291impl LayoutEngine {
292 pub fn new(mode: LayoutMode) -> Self {
294 let cache = Arc::new(LayoutCache::new());
295 let layout_in_progress = Arc::new(AtomicBool::new(false));
296
297 let (request_sender, result_receiver, worker_handle) = match &mode {
298 LayoutMode::Synchronous => (None, None, None),
299 LayoutMode::Asynchronous { .. } | LayoutMode::Hybrid { .. } => {
300 Self::spawn_worker(cache.clone(), layout_in_progress.clone())
301 }
302 };
303
304 Self {
305 mode,
306 cache,
307 frame_id: 0,
308 last_completed_frame: 0,
309 request_sender,
310 result_receiver,
311 worker_handle,
312 layout_in_progress,
313 }
314 }
315
316 fn spawn_worker(cache: Arc<LayoutCache>, in_progress: Arc<AtomicBool>) -> WorkerChannels {
318 let (request_tx, request_rx) = std::sync::mpsc::channel::<WorkerMessage>();
319 let (result_tx, result_rx) = std::sync::mpsc::channel::<LayoutResult>();
320
321 let handle = thread::Builder::new()
322 .name("layout-worker".to_string())
323 .spawn(move || {
324 Self::worker_loop(request_rx, result_tx, cache, in_progress);
325 })
326 .expect("Failed to spawn layout worker thread");
327
328 (Some(request_tx), Some(result_rx), Some(handle))
329 }
330
331 fn worker_loop(
333 request_rx: std::sync::mpsc::Receiver<WorkerMessage>,
334 result_tx: std::sync::mpsc::Sender<LayoutResult>,
335 cache: Arc<LayoutCache>,
336 in_progress: Arc<AtomicBool>,
337 ) {
338 while let Ok(msg) = request_rx.recv() {
339 match msg {
340 WorkerMessage::Compute(request) => {
341 in_progress.store(true, Ordering::SeqCst);
342 let start = Instant::now();
343
344 let layouts =
346 Self::compute_layout_sync(&request.tree_snapshot, request.viewport_size);
347
348 for (node_id, layout) in &layouts {
350 cache.write_back(*node_id, *layout);
351 }
352
353 cache.mark_swap_pending();
355
356 let result = LayoutResult {
357 frame_id: request.frame_id,
358 layouts,
359 compute_time: start.elapsed(),
360 is_partial: false,
361 };
362
363 let _ = result_tx.send(result);
364 in_progress.store(false, Ordering::SeqCst);
365 }
366 WorkerMessage::Shutdown => break,
367 }
368 }
369 }
370
371 fn compute_layout_sync(
373 snapshot: &TreeSnapshot,
374 viewport_size: Size<f32>,
375 ) -> HashMap<NodeId, LayoutRect> {
376 let mut taffy = taffy::TaffyTree::new();
377 let mut taffy_nodes: HashMap<usize, taffy::NodeId> = HashMap::new();
378 let mut results = HashMap::new();
379
380 for (index, node) in snapshot.nodes.iter().enumerate() {
382 let taffy_node = if node.children.is_empty() {
383 taffy.new_leaf(node.style.clone()).unwrap()
384 } else {
385 taffy.new_with_children(node.style.clone(), &[]).unwrap()
386 };
387 taffy_nodes.insert(index, taffy_node);
388 }
389
390 for (index, node) in snapshot.nodes.iter().enumerate() {
392 if !node.children.is_empty() {
393 let children: Vec<taffy::NodeId> = node
394 .children
395 .iter()
396 .filter_map(|&child_idx| taffy_nodes.get(&child_idx).copied())
397 .collect();
398
399 if let Some(&parent_taffy) = taffy_nodes.get(&index) {
400 let _ = taffy.set_children(parent_taffy, &children);
401 }
402 }
403 }
404
405 if let Some(root_idx) = snapshot.root
407 && let Some(&root_taffy) = taffy_nodes.get(&root_idx)
408 {
409 let available = taffy::Size {
410 width: taffy::AvailableSpace::Definite(viewport_size.width),
411 height: taffy::AvailableSpace::Definite(viewport_size.height),
412 };
413
414 let measure_fn = |known_dimensions: taffy::Size<Option<f32>>,
416 _available_space: taffy::Size<taffy::AvailableSpace>,
417 node_id: taffy::NodeId,
418 _node_context: Option<&mut ()>,
419 _style: &taffy::Style|
420 -> taffy::Size<f32> {
421 for (idx, &tn) in &taffy_nodes {
423 if tn == node_id
424 && let Some(node) = snapshot.nodes.get(*idx)
425 && let Some((w, h)) = node.text_measurement
426 {
427 return taffy::Size {
428 width: known_dimensions.width.unwrap_or(w),
429 height: known_dimensions.height.unwrap_or(h),
430 };
431 }
432 }
433 taffy::Size {
434 width: known_dimensions.width.unwrap_or(0.0),
435 height: known_dimensions.height.unwrap_or(0.0),
436 }
437 };
438
439 let _ = taffy.compute_layout_with_measure(root_taffy, available, measure_fn);
440 }
441
442 for (index, node) in snapshot.nodes.iter().enumerate() {
444 if let Some(&taffy_node) = taffy_nodes.get(&index)
445 && let Ok(layout) = taffy.layout(taffy_node)
446 {
447 results.insert(
448 node.node_id,
449 LayoutRect {
450 x: layout.location.x,
451 y: layout.location.y,
452 width: layout.size.width,
453 height: layout.size.height,
454 },
455 );
456 }
457 }
458
459 results
460 }
461
462 pub fn set_mode(&mut self, mode: LayoutMode) {
464 if let Some(sender) = self.request_sender.take() {
466 let _ = sender.send(WorkerMessage::Shutdown);
467 }
468 if let Some(handle) = self.worker_handle.take() {
469 let _ = handle.join();
470 }
471 self.result_receiver = None;
472
473 let (request_sender, result_receiver, worker_handle) = match &mode {
475 LayoutMode::Synchronous => (None, None, None),
476 LayoutMode::Asynchronous { .. } | LayoutMode::Hybrid { .. } => {
477 Self::spawn_worker(self.cache.clone(), self.layout_in_progress.clone())
478 }
479 };
480
481 self.mode = mode;
482 self.request_sender = request_sender;
483 self.result_receiver = result_receiver;
484 self.worker_handle = worker_handle;
485 }
486
487 pub fn mode(&self) -> &LayoutMode {
489 &self.mode
490 }
491
492 pub fn compute_layout(
497 &mut self,
498 tree: &UiTree,
499 viewport_size: Size<f32>,
500 widget_registry: &WidgetTypeRegistry,
501 ) {
502 profile_function!();
503 self.frame_id += 1;
504
505 match &self.mode {
506 LayoutMode::Synchronous => {
507 self.compute_layout_synchronous(tree, viewport_size, widget_registry);
508 }
509 LayoutMode::Asynchronous { max_stale_frames } => {
510 let frames_stale = self.frame_id.saturating_sub(self.last_completed_frame);
511 if frames_stale > *max_stale_frames as u64 {
512 self.compute_layout_synchronous(tree, viewport_size, widget_registry);
514 } else {
515 self.compute_layout_async(tree, viewport_size, widget_registry);
516 }
517 }
518 LayoutMode::Hybrid {
519 async_threshold,
520 max_stale_frames,
521 } => {
522 let node_count = tree.iter().count();
523 if node_count < *async_threshold {
524 self.compute_layout_synchronous(tree, viewport_size, widget_registry);
525 } else {
526 let frames_stale = self.frame_id.saturating_sub(self.last_completed_frame);
527 if frames_stale > *max_stale_frames as u64 {
528 self.compute_layout_synchronous(tree, viewport_size, widget_registry);
529 } else {
530 self.compute_layout_async(tree, viewport_size, widget_registry);
531 }
532 }
533 }
534 }
535 }
536
537 fn compute_layout_synchronous(
539 &mut self,
540 tree: &UiTree,
541 viewport_size: Size<f32>,
542 widget_registry: &WidgetTypeRegistry,
543 ) {
544 let snapshot = TreeSnapshot::from_tree(tree, widget_registry);
545 let layouts = Self::compute_layout_sync(&snapshot, viewport_size);
546
547 if let Ok(mut front) = self.cache.front.write() {
549 front.clear();
550 front.extend(layouts);
551 }
552
553 self.cache
554 .front_frame_id
555 .store(self.frame_id, Ordering::SeqCst);
556 self.last_completed_frame = self.frame_id;
557 }
558
559 fn compute_layout_async(
561 &mut self,
562 tree: &UiTree,
563 viewport_size: Size<f32>,
564 widget_registry: &WidgetTypeRegistry,
565 ) {
566 if self.layout_in_progress.load(Ordering::SeqCst) {
568 return;
569 }
570
571 if let Some(sender) = &self.request_sender {
572 let snapshot = TreeSnapshot::from_tree(tree, widget_registry);
573 let request = LayoutRequest {
574 tree_snapshot: snapshot,
575 viewport_size,
576 frame_id: self.frame_id,
577 _timestamp: Instant::now(),
578 };
579 let _ = sender.send(WorkerMessage::Compute(request));
580 }
581 }
582
583 pub fn poll_results(&mut self) -> usize {
587 let mut count = 0;
588
589 if let Some(receiver) = &self.result_receiver {
590 while let Ok(result) = receiver.try_recv() {
591 self.last_completed_frame = result.frame_id;
592 count += 1;
593 }
594 }
595
596 if self.cache.is_swap_pending() {
598 self.cache.swap(self.last_completed_frame);
599 }
600
601 count
602 }
603
604 pub fn get_layout(&self, node_id: NodeId) -> Option<LayoutRect> {
608 self.cache.get(node_id)
609 }
610
611 pub fn is_layout_current(&self) -> bool {
613 self.cache.front_frame_id() >= self.frame_id
614 }
615
616 pub fn is_layout_in_progress(&self) -> bool {
618 self.layout_in_progress.load(Ordering::SeqCst)
619 }
620
621 pub fn frames_stale(&self) -> u64 {
623 self.frame_id.saturating_sub(self.cache.front_frame_id())
624 }
625
626 pub fn clear(&mut self) {
628 if let Ok(mut front) = self.cache.front.write() {
629 front.clear();
630 }
631 if let Ok(mut back) = self.cache.back.lock() {
632 back.clear();
633 }
634 }
635}
636
637impl Drop for LayoutEngine {
638 fn drop(&mut self) {
639 if let Some(sender) = self.request_sender.take() {
641 let _ = sender.send(WorkerMessage::Shutdown);
642 }
643 if let Some(handle) = self.worker_handle.take() {
644 let _ = handle.join();
645 }
646 }
647}
648
649impl Default for LayoutEngine {
650 fn default() -> Self {
651 Self::new(LayoutMode::default())
652 }
653}
654
655#[cfg(test)]
656mod tests {
657 use super::*;
658
659 #[test]
660 fn test_layout_mode_default() {
661 let mode = LayoutMode::default();
662 assert!(matches!(mode, LayoutMode::Synchronous));
663 }
664
665 #[test]
666 fn test_tree_snapshot() {
667 let registry = WidgetTypeRegistry::new();
668 let mut tree = UiTree::new();
669 let root = tree.add_widget(Box::new(crate::widgets::Container::new()));
670 let child = tree.add_widget(Box::new(crate::widgets::Container::new()));
671 tree.add_child(root, child);
672 tree.set_root(root);
673
674 let snapshot = TreeSnapshot::from_tree(&tree, ®istry);
675 assert_eq!(snapshot.node_count(), 2);
676 assert!(snapshot.root.is_some());
677 }
678
679 #[test]
680 fn test_layout_engine_sync() {
681 let registry = WidgetTypeRegistry::new();
682 let mut engine = LayoutEngine::new(LayoutMode::Synchronous);
683
684 let mut tree = UiTree::new();
685 let root = tree.add_widget(Box::new(crate::widgets::Container::new()));
686 tree.set_root(root);
687
688 engine.compute_layout(&tree, Size::new(800.0, 600.0), ®istry);
689
690 assert!(engine.is_layout_current());
691 assert!(!engine.is_layout_in_progress());
692 }
693
694 #[test]
695 fn test_layout_engine_mode_switch() {
696 let mut engine = LayoutEngine::new(LayoutMode::Synchronous);
697 assert!(matches!(engine.mode(), LayoutMode::Synchronous));
698
699 engine.set_mode(LayoutMode::Asynchronous {
700 max_stale_frames: 2,
701 });
702 assert!(matches!(engine.mode(), LayoutMode::Asynchronous { .. }));
703
704 engine.set_mode(LayoutMode::Synchronous);
705 assert!(matches!(engine.mode(), LayoutMode::Synchronous));
706 }
707
708 #[test]
709 fn test_layout_cache() {
710 let cache = LayoutCache::new();
711 let node_id = NodeId(1);
712 let layout = LayoutRect {
713 x: 0.0,
714 y: 0.0,
715 width: 100.0,
716 height: 50.0,
717 };
718
719 cache.write_back(node_id, layout);
721
722 assert!(cache.get(node_id).is_none());
724
725 cache.mark_swap_pending();
727 cache.swap(1);
728
729 let result = cache.get(node_id);
731 assert!(result.is_some());
732 assert_eq!(result.unwrap().width, 100.0);
733 }
734
735 #[test]
736 fn test_frames_stale() {
737 let registry = WidgetTypeRegistry::new();
738 let mut engine = LayoutEngine::new(LayoutMode::Synchronous);
739
740 let mut tree = UiTree::new();
741 let root = tree.add_widget(Box::new(crate::widgets::Container::new()));
742 tree.set_root(root);
743
744 engine.compute_layout(&tree, Size::new(800.0, 600.0), ®istry);
745 assert_eq!(engine.frames_stale(), 0);
746 }
747}