Skip to main content

rill_core/buffer/
registry.rs

1//! # Resource registry — named shared resources
2//!
3//! [`ResourceRegistry`] — a build-time registry that owns named resources
4//! (currently [`TapeLoop`](super::TapeLoop)s) and hands out capability handles
5//! to graph nodes during `GraphBuilder::build()`.
6//!
7//! Each registered tape yields a [`TapeWriter`] (unique) and a [`TapeReader`]
8//! (shared, cloneable). Nodes acquire the capability matching their role. The
9//! handles keep the underlying resource alive via reference counting, so the
10//! registry itself is only needed during assembly — it is dropped once every
11//! node has resolved its resources.
12
13use std::collections::HashMap;
14
15use super::tape::{tape_handles, TapeReader, TapeWriter};
16use super::TapeLoop;
17
18/// Registry of named shared resources.
19///
20/// Used in `GraphBuilder::build()` to allocate resources and distribute
21/// capability handles to graph nodes.
22pub struct ResourceRegistry<T> {
23    /// Unique writer handles, removed on first acquisition (single-writer).
24    writers: HashMap<String, TapeWriter<T>>,
25    /// Reader handles, cloned on each acquisition (many read taps).
26    readers: HashMap<String, TapeReader<T>>,
27}
28
29impl<T> ResourceRegistry<T> {
30    /// Create an empty registry.
31    pub fn new() -> Self {
32        Self {
33            writers: HashMap::new(),
34            readers: HashMap::new(),
35        }
36    }
37
38    /// Register a named tape loop, creating its writer/reader capability pair.
39    pub fn register_tape(&mut self, name: impl Into<String>, tape: TapeLoop<T>) {
40        let name = name.into();
41        let (writer, reader) = tape_handles(tape);
42        self.writers.insert(name.clone(), writer);
43        self.readers.insert(name, reader);
44    }
45
46    /// Acquire a read capability for the named tape (cloneable, many taps).
47    pub fn reader(&self, name: &str) -> Option<TapeReader<T>> {
48        self.readers.get(name).cloned()
49    }
50
51    /// Acquire the unique write capability for the named tape.
52    ///
53    /// Returns `Some` only on the first call for a given name; subsequent
54    /// calls return `None`. This enforces the single-writer invariant.
55    pub fn writer(&mut self, name: &str) -> Option<TapeWriter<T>> {
56        self.writers.remove(name)
57    }
58
59    /// Number of registered resources.
60    pub fn len(&self) -> usize {
61        self.readers.len()
62    }
63
64    /// Whether no resources are registered.
65    pub fn is_empty(&self) -> bool {
66        self.readers.is_empty()
67    }
68}
69
70impl<T> Default for ResourceRegistry<T> {
71    fn default() -> Self {
72        Self::new()
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_registry_writer_is_unique() {
82        let mut reg = ResourceRegistry::<f32>::new();
83        reg.register_tape("tape_0", TapeLoop::new(1024).unwrap());
84        assert_eq!(reg.len(), 1);
85        assert!(reg.reader("tape_0").is_some());
86        assert!(reg.reader("nonexistent").is_none());
87
88        // First writer succeeds, second is denied (single-writer invariant).
89        assert!(reg.writer("tape_0").is_some());
90        assert!(reg.writer("tape_0").is_none());
91    }
92
93    #[test]
94    fn test_registry_reader_writer_share_tape() {
95        let mut reg = ResourceRegistry::<f32>::new();
96        reg.register_tape("t", TapeLoop::new(64).unwrap());
97        let mut writer = reg.writer("t").unwrap();
98        let reader = reg.reader("t").unwrap();
99        writer.write(1.0);
100        writer.write(2.0);
101        assert_eq!(reader.read(0), 2.0);
102        assert_eq!(reader.read(1), 1.0);
103    }
104}