playa/entities/
node.rs

1//! Node trait - base interface for all node types in the project.
2//!
3//! Nodes are the building blocks of the compositing graph:
4//! - FileNode: loads image sequences/video from disk
5//! - CompNode: composites multiple layers
6//!
7//! Each node can compute a frame at given time, has attributes,
8//! and participates in dirty tracking for efficient caching.
9//!
10//! ## Play Range Helpers
11//!
12//! For timeline bounds and work area, see [`NodeKind`](super::node_kind::NodeKind):
13//! - `play_range(use_work_area)` → `(start, end)` frame range
14//! - `bounds(use_trim, selection_only)` → content bounds
15//! - `frame_count()` → total frames
16
17use enum_dispatch::enum_dispatch;
18use std::sync::Arc;
19use uuid::Uuid;
20
21use crate::config::{DEFAULT_DIM, DEFAULT_FPS, DEFAULT_SRC_LEN};
22
23use super::attrs::Attrs;
24use super::frame::Frame;
25use super::keys::{
26    A_FPS, A_FRAME, A_HEIGHT, A_IN, A_OUT, A_SRC_LEN, A_TRIM_IN, A_TRIM_OUT, A_WIDTH,
27};
28use super::traits::{FrameCache, WorkerPool};
29
30/// Context passed to node compute and preload functions.
31/// Contains references to project resources needed for computation.
32///
33/// ## Why Arc<NodeKind> in media?
34///
35/// Workers need read access during compute (50-500ms), but UI needs write
36/// access for playhead updates. Without Arc, workers block UI with read locks.
37///
38/// With Arc<NodeKind>:
39/// - Workers take snapshot (clone HashMap of Arcs) in microseconds
40/// - Lock released immediately, UI never blocked
41/// - Compute uses owned snapshot, safe from concurrent mutation
42pub struct ComputeContext<'a> {
43    /// Global frame cache (trait object for dependency inversion)
44    pub cache: &'a dyn FrameCache,
45    /// Cache Arc for worker thread access in preload (clone this for workers)
46    pub cache_arc: Option<Arc<dyn FrameCache + Send + Sync>>,
47    /// Media pool for looking up source nodes.
48    /// Values are Arc<NodeKind> for cheap cloning - workers snapshot this
49    /// and release lock before expensive compute operations.
50    pub media: &'a std::collections::HashMap<Uuid, Arc<super::node_kind::NodeKind>>,
51    /// Media pool Arc for worker thread access in preload.
52    /// Workers clone this, take snapshot of inner HashMap, then release lock.
53    pub media_arc: Option<std::sync::Arc<std::sync::RwLock<std::collections::HashMap<Uuid, Arc<super::node_kind::NodeKind>>>>>,
54    /// Worker pool for background loading (trait object, None during synchronous compute)
55    pub workers: Option<&'a dyn WorkerPool>,
56    /// Current epoch for cancelling stale preload requests
57    pub epoch: u64,
58}
59
60/// Base trait for all node types.
61/// Provides common interface for identification, attributes, and computation.
62#[enum_dispatch]
63pub trait Node: Send + Sync {
64    /// Unique identifier for this node
65    fn uuid(&self) -> Uuid;
66    
67    /// Display name of the node
68    fn name(&self) -> &str;
69    
70    /// Type identifier string ("File", "Comp", etc.)
71    fn node_type(&self) -> &'static str;
72    
73    /// Access to node's persistent attributes
74    fn attrs(&self) -> &Attrs;
75    
76    /// Mutable access to node's attributes
77    fn attrs_mut(&mut self) -> &mut Attrs;
78    
79    /// Source nodes that this node depends on (via layers).
80    /// Empty for leaf nodes like FileNode.
81    fn inputs(&self) -> Vec<Uuid>;
82    
83    /// Compute output frame at given frame index.
84    /// Result should be cached in global_cache[uuid][frame].
85    /// Returns None if computation fails or no frame available.
86    fn compute(&self, frame: i32, ctx: &ComputeContext) -> Option<Frame>;
87    
88    /// Check if node needs recomputation (attrs changed).
89    ///
90    /// # Arguments
91    /// - `ctx`: If Some, also checks source nodes recursively. If None, checks only self.
92    fn is_dirty(&self, ctx: Option<&ComputeContext>) -> bool;
93    
94    /// Mark node as needing recomputation
95    fn mark_dirty(&self);
96    
97    /// Clear dirty flag after successful computation
98    fn clear_dirty(&self);
99    
100    /// Preload frames around center position for background loading.
101    /// Default implementation is no-op (for nodes without preload support).
102    /// FileNode/CompNode override this to enqueue frame loading via workers.
103    /// `radius` - max number of frames to preload around center
104    fn preload(&self, _center: i32, _radius: i32, _ctx: &ComputeContext) {
105        // Default no-op
106    }
107    
108    // --- Convenience methods with default implementations ---
109    
110    /// Get attribute value by key
111    fn get_attr(&self, key: &str) -> Option<&super::attrs::AttrValue> {
112        self.attrs().get(key)
113    }
114    
115    /// Set attribute value
116    fn set_attr(&mut self, key: &str, value: super::attrs::AttrValue) {
117        self.attrs_mut().set(key, value);
118    }
119    
120    /// Get i32 attribute
121    fn get_i32(&self, key: &str) -> Option<i32> {
122        self.attrs().get_i32(key)
123    }
124    
125    /// Get f32 attribute
126    fn get_float(&self, key: &str) -> Option<f32> {
127        self.attrs().get_float(key)
128    }
129    
130    /// Get string attribute
131    fn get_str(&self, key: &str) -> Option<&str> {
132        self.attrs().get_str(key)
133    }
134    
135    /// Get uuid attribute
136    fn get_uuid_attr(&self, key: &str) -> Option<Uuid> {
137        self.attrs().get_uuid(key)
138    }
139    
140    // --- Timeline/timing methods (for enum_dispatch unification) ---
141    
142    /// Play range: (start_frame, end_frame) for playback.
143    /// Default uses attrs.layer_start()/layer_end() which respects in/trim/speed.
144    fn play_range(&self, _use_work_area: bool) -> (i32, i32) {
145        (self.attrs().layer_start(), self.attrs().layer_end())
146    }
147    
148    /// Content bounds for zoom-to-fit. Default delegates to play_range.
149    /// CompNode overrides to calculate from layers with dynamic src_len from media.
150    fn bounds(&self, use_trim: bool, _selection_only: bool, _media: &std::collections::HashMap<Uuid, Arc<super::node_kind::NodeKind>>) -> (i32, i32) {
151        self.play_range(use_trim)
152    }
153    
154    // --- Timing methods with defaults ---
155    
156    /// Start frame (in point). Default: 0
157    fn _in(&self) -> i32 {
158        self.attrs().get_i32(A_IN).unwrap_or(0)
159    }
160    
161    /// End frame (out point). Default: src_len or DEFAULT_SRC_LEN
162    fn _out(&self) -> i32 {
163        self.attrs().get_i32(A_OUT).unwrap_or_else(|| {
164            self.attrs().get_i32(A_SRC_LEN).unwrap_or(DEFAULT_SRC_LEN)
165        })
166    }
167    
168    /// Frames per second. Default: DEFAULT_FPS (24.0)
169    fn fps(&self) -> f32 {
170        self.attrs().get_float(A_FPS).unwrap_or(DEFAULT_FPS)
171    }
172    
173    /// Current playhead frame. Default: _in()
174    fn frame(&self) -> i32 {
175        self.attrs().get_i32(A_FRAME).unwrap_or_else(|| self._in())
176    }
177    
178    /// Work area (trimmed range) in absolute frames.
179    /// Returns (in + trim_in, out - trim_out)
180    fn work_area(&self) -> (i32, i32) {
181        let trim_in = self.attrs().get_i32(A_TRIM_IN).unwrap_or(0);
182        let trim_out = self.attrs().get_i32(A_TRIM_OUT).unwrap_or(0);
183        (self._in() + trim_in, self._out() - trim_out)
184    }
185    
186    /// Total source frames: out - in + 1 (inclusive range)
187    fn frame_count(&self) -> i32 {
188        (self._out() - self._in() + 1).max(0)
189    }
190    
191    /// Play frame count (respects trims): work_area duration
192    fn play_frame_count(&self) -> i32 {
193        let (start, end) = self.work_area();
194        (end - start + 1).max(0)
195    }
196    
197    /// Dimensions (width, height). Default: DEFAULT_DIM (1920x1080)
198    fn dim(&self) -> (usize, usize) {
199        let w = self.attrs().get_u32(A_WIDTH).unwrap_or(DEFAULT_DIM.0 as u32) as usize;
200        let h = self.attrs().get_u32(A_HEIGHT).unwrap_or(DEFAULT_DIM.1 as u32) as usize;
201        (w.max(1), h.max(1))
202    }
203    
204    /// Placeholder frame with node dimensions
205    fn placeholder_frame(&self) -> Frame {
206        let (w, h) = self.dim();
207        Frame::placeholder(w, h)
208    }
209}