Skip to main content

maolan_engine/midi/
io.rs

1use arc_swap::ArcSwap;
2use std::cell::UnsafeCell;
3use std::sync::atomic::{AtomicBool, Ordering};
4use std::sync::{Arc, Weak};
5
6#[derive(Clone, Debug, PartialEq, Eq)]
7pub struct MidiEvent {
8    pub frame: u32,
9    pub data: Vec<u8>,
10}
11
12impl MidiEvent {
13    pub fn new(frame: u32, data: Vec<u8>) -> Self {
14        Self { frame, data }
15    }
16}
17
18/// MIDI port.
19///
20/// Lock-free MIDI port shape (see `LOCKLESS.md` Phase 3):
21///
22/// - `sources` is published RCU-style by control-side connect/disconnect and
23///   read on the RT path by `process()`.
24/// - `connections` is control-side only (routing queries, disconnect, plan
25///   compiler); no RT reader exists.
26/// - `buffer`/`finished` are accessed under the plan's single-writer
27///   invariant: every port buffer has exactly one writer per cycle, all
28///   readers run in later plan nodes, the engine's pre-cycle writes are
29///   serialized by cycle start, and its post-cycle reads by cycle
30///   completion. That invariant is what makes the `unsafe` accessors sound;
31///   it is enforced by the render plan's MIDI edges and checked by
32///   `RenderPlan::verify()`.
33#[derive(Debug, Default)]
34#[allow(clippy::upper_case_acronyms)]
35pub struct MIDIIO {
36    /// Ports that feed events into this port (consumers see producers here).
37    sources: ArcSwap<Vec<Weak<MIDIIO>>>,
38    /// Ports that this port feeds events into (producers see consumers here).
39    /// Control-side only, COW-published like `sources`.
40    connections: ArcSwap<Vec<Weak<MIDIIO>>>,
41    buffer: UnsafeCell<Vec<MidiEvent>>,
42    finished: AtomicBool,
43}
44
45// Safety: the only non-atomic interior mutability is `buffer`, whose access
46// discipline is the single-writer/plan-ordered invariant documented on the
47// type and on each accessor. Concurrent threads never alias the buffer
48// mutably because plan edges serialize the writer before any reader.
49unsafe impl Sync for MIDIIO {}
50
51/// Mutable access to a MIDI port's event buffer, handed out by
52/// [`MIDIIO::buffer_mut`] under the single-writer invariant. Derefs to
53/// `Vec<MidiEvent>`.
54#[derive(Debug)]
55pub struct MidiBufferMut<'a> {
56    buffer: &'a mut Vec<MidiEvent>,
57}
58
59impl std::ops::Deref for MidiBufferMut<'_> {
60    type Target = Vec<MidiEvent>;
61
62    fn deref(&self) -> &Self::Target {
63        self.buffer
64    }
65}
66
67impl std::ops::DerefMut for MidiBufferMut<'_> {
68    fn deref_mut(&mut self) -> &mut Self::Target {
69        self.buffer
70    }
71}
72
73impl MIDIIO {
74    pub fn new() -> Self {
75        Self::default()
76    }
77
78    /// Connect producer `from` to consumer `to`. Updates both sides so
79    /// `to.sources` contains `from` and `from.connections` contains `to`.
80    /// Control-side only; publishes the new `sources` list RCU-style.
81    pub fn connect(from: &Arc<MIDIIO>, to: &Arc<MIDIIO>) {
82        let mut conns = from.connections();
83        if !conns.iter().any(|c| Arc::ptr_eq(c, to)) {
84            conns.push(to.clone());
85            from.store_connections(conns);
86        }
87        let mut sources = to.sources();
88        if !sources.iter().any(|s| Arc::ptr_eq(s, from)) {
89            sources.push(from.clone());
90            to.store_sources(sources);
91        }
92    }
93
94    /// Disconnect producer `from` from consumer `to`. Removes from both sides.
95    /// Control-side only.
96    pub fn disconnect(from: &Arc<MIDIIO>, to: &Arc<MIDIIO>) -> Result<(), String> {
97        let mut removed = false;
98        let mut conns = from.connections();
99        let before = conns.len();
100        conns.retain(|c| !Arc::ptr_eq(c, to));
101        if conns.len() < before {
102            from.store_connections(conns);
103            removed = true;
104        }
105        let mut sources = to.sources();
106        sources.retain(|s| !Arc::ptr_eq(s, from));
107        to.store_sources(sources);
108        if removed {
109            Ok(())
110        } else {
111            Err("Connection not found".to_string())
112        }
113    }
114
115    /// Control-side only: record `to` in this port's `connections` without
116    /// touching `to.sources` (duplicate-safe). Used by folder/child
117    /// reparenting, where events flow by direct buffer writes ordered by
118    /// plan edges rather than by source merging.
119    pub fn add_connection(&self, to: &Arc<MIDIIO>) {
120        let mut conns = self.connections();
121        if !conns.iter().any(|c| Arc::ptr_eq(c, to)) {
122            conns.push(to.clone());
123            self.store_connections(conns);
124        }
125    }
126
127    /// Control-side snapshot of the ports this port feeds. Used by routing
128    /// queries and the plan compiler; never on the RT path.
129    pub fn connections(&self) -> Vec<Arc<MIDIIO>> {
130        let connections = self.connections.load_full();
131        let live = Self::live_ports(&connections);
132        if live.len() != connections.len() {
133            self.store_connections(live.clone());
134        }
135        live
136    }
137
138    /// Control-side snapshot of the ports feeding this port.
139    pub fn sources(&self) -> Vec<Arc<MIDIIO>> {
140        let sources = self.sources.load_full();
141        let live = Self::live_ports(&sources);
142        if live.len() != sources.len() {
143            self.store_sources(live.clone());
144        }
145        live
146    }
147
148    /// Prepare this port for a new processing cycle.
149    ///
150    /// # Safety
151    /// The caller must be this port's sole writer for the coming cycle and
152    /// no reader may be active (single-writer invariant).
153    pub unsafe fn setup(&self) {
154        // Safety: forwarded from the caller — sole writer, no active reader.
155        unsafe { &mut *self.buffer.get() }.clear();
156        self.finished.store(false, Ordering::Release);
157    }
158
159    /// Merge events from all connected sources into this port's buffer.
160    /// Source buffers are left intact so multiple consumers can read them.
161    ///
162    /// # Safety
163    /// The single-writer invariant must hold for this port (sole writer this
164    /// cycle), and every source's producer must have completed earlier in
165    /// the plan (MIDI edge) or belong to a finished cycle.
166    pub unsafe fn process(&self) {
167        // Safety: forwarded from the caller.
168        let buffer = unsafe { &mut *self.buffer.get() };
169        buffer.clear();
170        let sources = self.sources.load();
171        for source in sources.iter().filter_map(Weak::upgrade) {
172            // Safety: sources are read-only here; their producers completed
173            // earlier in the plan (MIDI edge) or in a finished cycle.
174            let src = unsafe { &*source.buffer.get() };
175            buffer.extend_from_slice(src);
176        }
177        buffer.sort_by_key(|e| e.frame);
178        self.finished.store(true, Ordering::Release);
179    }
180
181    /// Read the port's event buffer.
182    ///
183    /// # Safety
184    /// No writer may be active: the buffer's producer must have completed
185    /// (plan ordering or cycle boundary).
186    pub unsafe fn buffer(&self) -> &[MidiEvent] {
187        // Safety: forwarded from the caller.
188        unsafe { &*self.buffer.get() }
189    }
190
191    /// Write the port's event buffer.
192    ///
193    /// Returns a guard instead of a bare `&mut` so the signature is not
194    /// `&self -> &mut T` (`clippy::mut_from_ref`); the guard derefs to
195    /// `Vec<MidiEvent>`, so call sites use it like the old field access.
196    ///
197    /// # Safety
198    /// The caller must be this port's sole writer this cycle and no reader
199    /// may be active (single-writer invariant).
200    pub unsafe fn buffer_mut(&self) -> MidiBufferMut<'_> {
201        // Safety: forwarded from the caller.
202        MidiBufferMut {
203            buffer: unsafe { &mut *self.buffer.get() },
204        }
205    }
206
207    /// Returns true if this port has finished processing this cycle.
208    /// A port with no sources is considered ready once it has finished
209    /// producing; a port with sources is ready only when all sources have.
210    pub fn ready(&self) -> bool {
211        let sources = self.sources.load();
212        let mut has_source = false;
213        for source in sources.iter().filter_map(Weak::upgrade) {
214            has_source = true;
215            if !source.finished.load(Ordering::Acquire) {
216                return false;
217            }
218        }
219        if has_source {
220            true
221        } else {
222            self.finished.load(Ordering::Acquire)
223        }
224    }
225
226    fn store_connections(&self, connections: Vec<Arc<MIDIIO>>) {
227        self.connections
228            .store(Arc::new(connections.iter().map(Arc::downgrade).collect()));
229    }
230
231    fn store_sources(&self, sources: Vec<Arc<MIDIIO>>) {
232        self.sources
233            .store(Arc::new(sources.iter().map(Arc::downgrade).collect()));
234    }
235
236    fn live_ports(ports: &[Weak<MIDIIO>]) -> Vec<Arc<MIDIIO>> {
237        ports.iter().filter_map(Weak::upgrade).collect()
238    }
239
240    /// Mark this port as finished without processing (used by producers
241    /// such as track inputs that fill their buffer directly).
242    pub fn mark_finished(&self) {
243        self.finished.store(true, Ordering::Release);
244    }
245}
246
247#[cfg(test)]
248mod tests {
249    use super::*;
250
251    #[test]
252    fn midi_event_new_sets_fields() {
253        let event = MidiEvent::new(42, vec![0x90, 60, 100]);
254
255        assert_eq!(event.frame, 42);
256        assert_eq!(event.data, vec![0x90, 60, 100]);
257    }
258
259    #[test]
260    fn connect_and_disconnect_manage_both_sides() {
261        let from = Arc::new(MIDIIO::new());
262        let to = Arc::new(MIDIIO::new());
263
264        MIDIIO::connect(&from, &to);
265        let from_connections = from.connections();
266        assert_eq!(from_connections.len(), 1);
267        assert!(Arc::ptr_eq(&from_connections[0], &to));
268        let to_sources = to.sources();
269        assert_eq!(to_sources.len(), 1);
270        assert!(Arc::ptr_eq(&to_sources[0], &from));
271
272        assert!(MIDIIO::disconnect(&from, &to).is_ok());
273        assert!(from.connections().is_empty());
274        assert!(to.sources().is_empty());
275    }
276
277    #[test]
278    fn disconnect_returns_error_for_missing_connection() {
279        let from = Arc::new(MIDIIO::new());
280        let to = Arc::new(MIDIIO::new());
281
282        let err = MIDIIO::disconnect(&from, &to).expect_err("missing connection should error");
283        assert_eq!(err, "Connection not found");
284    }
285
286    #[test]
287    fn disconnect_removes_all_duplicate_connections_for_same_target() {
288        let from = Arc::new(MIDIIO::new());
289        let to = Arc::new(MIDIIO::new());
290
291        MIDIIO::connect(&from, &to);
292        MIDIIO::connect(&from, &to);
293
294        assert!(MIDIIO::disconnect(&from, &to).is_ok());
295        assert!(from.connections().is_empty());
296        assert!(to.sources().is_empty());
297    }
298
299    #[test]
300    fn process_merges_and_sorts_sources() {
301        let source_a = Arc::new(MIDIIO::new());
302        let source_b = Arc::new(MIDIIO::new());
303        let consumer = Arc::new(MIDIIO::new());
304
305        // Safety: tests are single-threaded; the single-writer invariant
306        // holds trivially.
307        unsafe {
308            source_a
309                .buffer_mut()
310                .push(MidiEvent::new(10, vec![0x90, 60, 100]));
311            source_b
312                .buffer_mut()
313                .push(MidiEvent::new(5, vec![0x80, 60, 100]));
314        }
315
316        MIDIIO::connect(&source_a, &consumer);
317        MIDIIO::connect(&source_b, &consumer);
318
319        // Safety: as above; producers "completed" before the merge.
320        unsafe { consumer.process() };
321
322        let events = unsafe { consumer.buffer() }.to_vec();
323        assert_eq!(events.len(), 2);
324        assert_eq!(events[0].frame, 5);
325        assert_eq!(events[1].frame, 10);
326    }
327
328    #[test]
329    fn ready_requires_all_sources_finished() {
330        let source = Arc::new(MIDIIO::new());
331        let consumer = Arc::new(MIDIIO::new());
332
333        MIDIIO::connect(&source, &consumer);
334
335        assert!(!consumer.ready());
336
337        source.mark_finished();
338        assert!(consumer.ready());
339    }
340
341    #[test]
342    fn no_source_port_ready_after_mark_finished() {
343        let io = MIDIIO::new();
344        assert!(!io.ready());
345        io.mark_finished();
346        assert!(io.ready());
347    }
348
349    #[test]
350    fn setup_clears_buffer_and_finished() {
351        let io = MIDIIO::new();
352        // Safety: single-threaded test.
353        unsafe {
354            io.buffer_mut().push(MidiEvent::new(0, vec![0x90, 60, 100]));
355            io.mark_finished();
356            io.setup();
357            assert!(io.buffer().is_empty());
358        }
359        assert!(!io.ready());
360    }
361}