Skip to main content

oximedia_graph/
node_registry.rs

1//! Node registry for the filter graph pipeline.
2//!
3//! Provides a typed catalogue of all nodes registered in a graph, supporting
4//! lookup by kind and counting of enabled nodes.
5
6#![allow(dead_code)]
7
8use std::collections::HashMap;
9
10/// Classification of a graph node by its role in the pipeline.
11#[derive(Debug, Clone, PartialEq, Eq, Hash)]
12pub enum NodeKind {
13    /// Produces frames (decoder, file reader, live capture).
14    Source,
15    /// Transforms frames (scaler, colour converter, denoiser).
16    Filter,
17    /// Consumes frames (encoder, display, file writer).
18    Sink,
19    /// Splits or merges multiple streams.
20    Mux,
21}
22
23impl NodeKind {
24    /// Returns `true` if this node kind originates data in the graph.
25    pub fn is_source(&self) -> bool {
26        *self == NodeKind::Source
27    }
28
29    /// Returns `true` if this node kind terminates a pipeline branch.
30    pub fn is_sink(&self) -> bool {
31        *self == NodeKind::Sink
32    }
33
34    /// Returns a short human-readable label for the kind.
35    pub fn label(&self) -> &'static str {
36        match self {
37            NodeKind::Source => "source",
38            NodeKind::Filter => "filter",
39            NodeKind::Sink => "sink",
40            NodeKind::Mux => "mux",
41        }
42    }
43}
44
45/// Lightweight descriptor for a node registered in the `NodeRegistry`.
46#[derive(Debug, Clone)]
47pub struct GraphNode {
48    /// Numeric identifier for this node (unique within a registry).
49    id: u64,
50    /// Human-readable name.
51    name: String,
52    /// Classification of this node.
53    kind: NodeKind,
54    /// Whether this node is currently enabled (participates in processing).
55    enabled: bool,
56}
57
58impl GraphNode {
59    /// Creates a new enabled `GraphNode`.
60    pub fn new(id: u64, name: impl Into<String>, kind: NodeKind) -> Self {
61        Self {
62            id,
63            name: name.into(),
64            kind,
65            enabled: true,
66        }
67    }
68
69    /// Returns the node ID.
70    pub fn id(&self) -> u64 {
71        self.id
72    }
73
74    /// Returns the node name.
75    pub fn name(&self) -> &str {
76        &self.name
77    }
78
79    /// Returns the node kind.
80    pub fn kind(&self) -> &NodeKind {
81        &self.kind
82    }
83
84    /// Returns `true` if the node is enabled.
85    pub fn is_enabled(&self) -> bool {
86        self.enabled
87    }
88
89    /// Enables or disables the node.
90    pub fn set_enabled(&mut self, enabled: bool) {
91        self.enabled = enabled;
92    }
93}
94
95/// A catalogue of `GraphNode` entries keyed by their numeric ID.
96#[derive(Debug, Clone, Default)]
97pub struct NodeRegistry {
98    nodes: HashMap<u64, GraphNode>,
99    next_id: u64,
100}
101
102impl NodeRegistry {
103    /// Creates an empty `NodeRegistry`.
104    pub fn new() -> Self {
105        Self {
106            nodes: HashMap::new(),
107            next_id: 0,
108        }
109    }
110
111    /// Adds a new node with the given name and kind, assigning a fresh ID.
112    /// Returns the assigned ID.
113    pub fn add(&mut self, name: impl Into<String>, kind: NodeKind) -> u64 {
114        let id = self.next_id;
115        self.next_id += 1;
116        self.nodes.insert(id, GraphNode::new(id, name, kind));
117        id
118    }
119
120    /// Looks up a node by ID.
121    pub fn get(&self, id: u64) -> Option<&GraphNode> {
122        self.nodes.get(&id)
123    }
124
125    /// Looks up a node by ID mutably.
126    pub fn get_mut(&mut self, id: u64) -> Option<&mut GraphNode> {
127        self.nodes.get_mut(&id)
128    }
129
130    /// Returns all nodes whose kind matches `kind`.
131    pub fn find_by_kind(&self, kind: &NodeKind) -> Vec<&GraphNode> {
132        self.nodes.values().filter(|n| n.kind() == kind).collect()
133    }
134
135    /// Returns the number of currently enabled nodes.
136    pub fn enabled_count(&self) -> usize {
137        self.nodes.values().filter(|n| n.is_enabled()).count()
138    }
139
140    /// Returns the total number of registered nodes.
141    pub fn len(&self) -> usize {
142        self.nodes.len()
143    }
144
145    /// Returns `true` if no nodes have been registered.
146    pub fn is_empty(&self) -> bool {
147        self.nodes.is_empty()
148    }
149
150    /// Removes a node by ID. Returns the removed node if present.
151    pub fn remove(&mut self, id: u64) -> Option<GraphNode> {
152        self.nodes.remove(&id)
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn test_node_kind_is_source() {
162        assert!(NodeKind::Source.is_source());
163        assert!(!NodeKind::Sink.is_source());
164    }
165
166    #[test]
167    fn test_node_kind_is_sink() {
168        assert!(NodeKind::Sink.is_sink());
169        assert!(!NodeKind::Filter.is_sink());
170    }
171
172    #[test]
173    fn test_node_kind_label() {
174        assert_eq!(NodeKind::Source.label(), "source");
175        assert_eq!(NodeKind::Filter.label(), "filter");
176        assert_eq!(NodeKind::Sink.label(), "sink");
177        assert_eq!(NodeKind::Mux.label(), "mux");
178    }
179
180    #[test]
181    fn test_graph_node_enabled_by_default() {
182        let n = GraphNode::new(0, "node0", NodeKind::Source);
183        assert!(n.is_enabled());
184    }
185
186    #[test]
187    fn test_graph_node_set_enabled() {
188        let mut n = GraphNode::new(0, "node0", NodeKind::Filter);
189        n.set_enabled(false);
190        assert!(!n.is_enabled());
191    }
192
193    #[test]
194    fn test_registry_add_returns_incremental_ids() {
195        let mut reg = NodeRegistry::new();
196        let id0 = reg.add("a", NodeKind::Source);
197        let id1 = reg.add("b", NodeKind::Sink);
198        assert_eq!(id0, 0);
199        assert_eq!(id1, 1);
200    }
201
202    #[test]
203    fn test_registry_get() {
204        let mut reg = NodeRegistry::new();
205        let id = reg.add("scaler", NodeKind::Filter);
206        let node = reg.get(id).expect("get should succeed");
207        assert_eq!(node.name(), "scaler");
208        assert_eq!(node.kind(), &NodeKind::Filter);
209    }
210
211    #[test]
212    fn test_registry_find_by_kind() {
213        let mut reg = NodeRegistry::new();
214        reg.add("src1", NodeKind::Source);
215        reg.add("src2", NodeKind::Source);
216        reg.add("sink1", NodeKind::Sink);
217        let sources = reg.find_by_kind(&NodeKind::Source);
218        assert_eq!(sources.len(), 2);
219    }
220
221    #[test]
222    fn test_registry_enabled_count() {
223        let mut reg = NodeRegistry::new();
224        let id0 = reg.add("a", NodeKind::Source);
225        let id1 = reg.add("b", NodeKind::Filter);
226        reg.get_mut(id1)
227            .expect("get_mut should succeed")
228            .set_enabled(false);
229        assert_eq!(reg.enabled_count(), 1);
230        let _ = id0;
231    }
232
233    #[test]
234    fn test_registry_remove() {
235        let mut reg = NodeRegistry::new();
236        let id = reg.add("x", NodeKind::Mux);
237        assert!(reg.remove(id).is_some());
238        assert!(reg.is_empty());
239    }
240
241    #[test]
242    fn test_registry_len() {
243        let mut reg = NodeRegistry::new();
244        reg.add("a", NodeKind::Source);
245        reg.add("b", NodeKind::Sink);
246        assert_eq!(reg.len(), 2);
247    }
248
249    #[test]
250    fn test_registry_find_by_kind_empty() {
251        let reg = NodeRegistry::new();
252        assert!(reg.find_by_kind(&NodeKind::Mux).is_empty());
253    }
254
255    #[test]
256    fn test_registry_default() {
257        let reg: NodeRegistry = Default::default();
258        assert!(reg.is_empty());
259    }
260
261    #[test]
262    fn test_node_id_accessor() {
263        let n = GraphNode::new(42, "node42", NodeKind::Filter);
264        assert_eq!(n.id(), 42);
265    }
266}