Skip to main content

vst_audio_ring/
lib.rs

1#![forbid(unsafe_code)]
2
3//! A bounded, allocation-free shared-memory audio block ring.
4//!
5//! This is a Rust port of EqualizerAPO-XT `runtime/ipc/StreamRing.{h,cpp}` at
6//! commit `1432f61cc437c99fde8e8e84e10e648fe516d4ce`. Copyright 2026 115dkk.
7//! It is ported to MIT with the copyright holder's permission (ADR 0021).
8//!
9//! The wire image is little-endian. Every value is accessed atomically. The
10//! mapping is split into a prefix of [`AtomicU32`] words and a 128-byte suffix
11//! of [`AtomicU64`] words. This keeps f32 samples in `AtomicU32`, gives the
12//! counters their natural alignment, and never accesses one byte at two widths.
13//! A platform adapter must create those two non-overlapping slices and keep the
14//! mapping alive longer than the [`Region`].
15//!
16//! # Header layout
17//!
18//! | Offset | Bytes | Field |
19//! |---:|---:|---|
20//! | 0 | 4 | magic `SSRR` |
21//! | 4 | 4 | layout version |
22//! | 8 | 4 | total bytes, including the counter suffix |
23//! | 12 | 4 | lane count |
24//! | 16 | 4 | input channels |
25//! | 20 | 4 | output channels |
26//! | 24 | 4 | maximum frames |
27//! | 28 | 4 | sample rate, Hz |
28//! | 32 | 4 | latency bound, frames |
29//! | 36 | 4 | state |
30//! | 40 | 4 | fault code |
31//! | 44 | 4 | producer pid |
32//! | 48 | 4 | consumer pid |
33//! | 52 | 4 | sample format |
34//! | 56 | 4 | slots per lane |
35//! | 64 | 24 | lane geometry: first slot offset, slot bytes, slot count, per lane |
36//! | 128 | 8 | lane sequences (one u32 per lane; own cache line) |
37//! | 192 | 8 | lane completions (one u32 per lane; own cache line) |
38//! | 512 | variable | `slots` 64-byte-aligned sample slots per lane |
39//! | rounded slot end | 64 | heartbeat u64 and padding |
40//! | +64 | 64 | last error u64 and padding |
41
42use std::array;
43use std::fmt;
44use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
45use std::sync::{Arc, Condvar, Mutex};
46use std::time::{Duration, Instant};
47
48pub mod events;
49use events::{CONTEXT_WORDS, EVENT_WORDS, METADATA_WORDS, PACKET_WORDS};
50pub use events::{Event, EventBuffer, EventData, MAX_EVENTS, MAX_SYSEX_BYTES, Transport};
51#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
52#[repr(u32)]
53pub enum SampleFormat {
54    #[default]
55    F32 = 0,
56    F64 = 1,
57}
58impl SampleFormat {
59    pub fn words(self) -> usize {
60        if self == Self::F64 { 2 } else { 1 }
61    }
62}
63
64pub const MAGIC: u32 = u32::from_le_bytes(*b"SSRR");
65pub const LAYOUT_VERSION: u32 = 3;
66pub const HEADER_BYTES: usize = 512;
67pub const CACHE_LINE: usize = 64;
68pub const MAX_CHANNELS: u32 = 64;
69pub const MAX_FRAMES: u32 = 65_536;
70pub const MAX_SLOTS: u32 = 16;
71pub const MAX_REGION_BYTES: usize = 128 * 1024 * 1024;
72const COUNTER_BYTES: usize = 2 * CACHE_LINE;
73const HEADER_WORDS: usize = HEADER_BYTES / 4;
74const COUNTER_WORDS: usize = COUNTER_BYTES / 8;
75
76const MAGIC_I: usize = 0;
77const VERSION_I: usize = 1;
78const TOTAL_I: usize = 2;
79const LANES_I: usize = 3;
80const INPUTS_I: usize = 4;
81const OUTPUTS_I: usize = 5;
82const FRAMES_I: usize = 6;
83const RATE_I: usize = 7;
84const LATENCY_I: usize = 8;
85const STATE_I: usize = 9;
86const FAULT_I: usize = 10;
87const PRODUCER_PID_I: usize = 11;
88const CONSUMER_PID_I: usize = 12;
89const FORMAT_I: usize = 13;
90const SLOTS_I: usize = 14;
91const LANE_GEOMETRY_I: usize = 16;
92const SEQUENCE_I: usize = 32;
93const COMPLETED_I: usize = 48;
94
95#[derive(Clone, Copy, Debug, Eq, PartialEq)]
96#[repr(u32)]
97pub enum State {
98    Empty = 0,
99    Announced = 1,
100    Ready = 2,
101    Closing = 3,
102    Fault = 4,
103}
104
105impl State {
106    fn from_wire(value: u32) -> Option<Self> {
107        Some(match value {
108            0 => Self::Empty,
109            1 => Self::Announced,
110            2 => Self::Ready,
111            3 => Self::Closing,
112            4 => Self::Fault,
113            _ => return None,
114        })
115    }
116}
117
118#[derive(Clone, Copy, Debug, Eq, PartialEq)]
119#[repr(u32)]
120pub enum Fault {
121    None = 0,
122    LayoutMismatch = 1,
123    EngineFailed = 2,
124    HostShuttingDown = 3,
125}
126
127#[derive(Clone, Copy, Debug, Eq, PartialEq)]
128pub struct Geometry {
129    pub sample_format: SampleFormat,
130    pub lanes: u32,
131    pub slots: u32,
132    pub input_channels: u32,
133    pub output_channels: u32,
134    pub max_frames: u32,
135    pub sample_rate: u32,
136    pub latency_bound_frames: u32,
137}
138
139impl Geometry {
140    pub fn validate(self) -> Result<Self, GeometryError> {
141        if !(1..=2).contains(&self.lanes) {
142            return Err(GeometryError::LaneCount);
143        }
144        if !(2..=MAX_SLOTS).contains(&self.slots) {
145            return Err(GeometryError::Slots);
146        }
147        if self.input_channels > MAX_CHANNELS || self.output_channels > MAX_CHANNELS {
148            return Err(GeometryError::Channels);
149        }
150        if !(1..=MAX_FRAMES).contains(&self.max_frames) {
151            return Err(GeometryError::Frames);
152        }
153        if !(1_000..=1_000_000).contains(&self.sample_rate) {
154            return Err(GeometryError::SampleRate);
155        }
156        if layout(self).is_none() {
157            return Err(GeometryError::RegionTooLarge);
158        }
159        Ok(self)
160    }
161
162    pub fn planes(self) -> usize {
163        self.input_channels.max(self.output_channels) as usize
164    }
165
166    pub fn samples_per_slot(self) -> usize {
167        self.planes() * self.max_frames as usize
168    }
169
170    pub fn total_bytes(self) -> Result<usize, GeometryError> {
171        self.validate()?;
172        Ok(layout(self)
173            .expect("validated geometry has a layout")
174            .total_bytes)
175    }
176}
177
178#[derive(Clone, Copy, Debug, Eq, PartialEq)]
179pub enum GeometryError {
180    LaneCount,
181    Slots,
182    Channels,
183    Frames,
184    SampleRate,
185    RegionTooLarge,
186}
187
188#[derive(Clone, Copy, Debug)]
189struct Layout {
190    slot_bytes: usize,
191    first_slot_offsets: [usize; 2],
192    slots: u32,
193    prefix_bytes: usize,
194    total_bytes: usize,
195}
196
197impl Layout {
198    fn slot_offset(self, lane: usize, sequence: u32) -> usize {
199        self.first_slot_offsets[lane] + (sequence % self.slots) as usize * self.slot_bytes
200    }
201}
202
203fn round_up(value: usize, alignment: usize) -> Option<usize> {
204    value
205        .checked_add(alignment - 1)
206        .map(|v| v / alignment * alignment)
207}
208
209fn layout(geometry: Geometry) -> Option<Layout> {
210    let audio_words = geometry
211        .samples_per_slot()
212        .checked_mul(geometry.sample_format.words())?;
213    let slot_bytes = round_up(
214        audio_words.checked_add(METADATA_WORDS)?.checked_mul(4)?,
215        CACHE_LINE,
216    )?;
217    let mut offset = HEADER_BYTES;
218    let mut first_slot_offsets = [0; 2];
219    for first_slot_offset in first_slot_offsets.iter_mut().take(geometry.lanes as usize) {
220        *first_slot_offset = offset;
221        offset = offset.checked_add(slot_bytes.checked_mul(geometry.slots as usize)?)?;
222    }
223    let prefix_bytes = round_up(offset, CACHE_LINE)?;
224    let total_bytes = prefix_bytes.checked_add(COUNTER_BYTES)?;
225    (total_bytes < MAX_REGION_BYTES).then_some(Layout {
226        slot_bytes,
227        first_slot_offsets,
228        slots: geometry.slots,
229        prefix_bytes,
230        total_bytes,
231    })
232}
233
234pub trait Signal: Send + Sync {
235    fn signal(&self);
236}
237
238pub trait Wait: Send + Sync {
239    fn wait(&self, timeout: Duration) -> Waited;
240}
241
242#[derive(Clone, Copy, Debug, Eq, PartialEq)]
243pub enum Waited {
244    Signalled,
245    TimedOut,
246    PeerGone,
247}
248
249#[derive(Debug)]
250struct EventInner {
251    generation: AtomicU64,
252    gone: AtomicBool,
253    observed: Mutex<u64>,
254    changed: Condvar,
255}
256
257/// Same-process event for tests and adapters. `signal` only performs an atomic
258/// increment and a condition-variable notification; it neither locks nor
259/// allocates.
260#[derive(Clone, Debug)]
261pub struct InProcessEvent(Arc<EventInner>);
262
263impl InProcessEvent {
264    pub fn new() -> Self {
265        Self(Arc::new(EventInner {
266            generation: AtomicU64::new(0),
267            gone: AtomicBool::new(false),
268            observed: Mutex::new(0),
269            changed: Condvar::new(),
270        }))
271    }
272
273    pub fn peer_gone(&self) {
274        self.0.gone.store(true, Ordering::Release);
275        self.0.changed.notify_all();
276    }
277}
278
279impl Default for InProcessEvent {
280    fn default() -> Self {
281        Self::new()
282    }
283}
284
285impl Signal for InProcessEvent {
286    fn signal(&self) {
287        self.0.generation.fetch_add(1, Ordering::Release);
288        self.0.changed.notify_one();
289    }
290}
291
292impl Wait for InProcessEvent {
293    fn wait(&self, timeout: Duration) -> Waited {
294        if self.0.gone.load(Ordering::Acquire) {
295            return Waited::PeerGone;
296        }
297        let mut observed = self.0.observed.lock().unwrap_or_else(|e| e.into_inner());
298        let current = self.0.generation.load(Ordering::Acquire);
299        if current != *observed {
300            *observed = current;
301            return Waited::Signalled;
302        }
303        let (mut observed, result) = self
304            .0
305            .changed
306            .wait_timeout_while(observed, timeout, |seen| {
307                !self.0.gone.load(Ordering::Acquire)
308                    && self.0.generation.load(Ordering::Acquire) == *seen
309            })
310            .unwrap_or_else(|e| e.into_inner());
311        if self.0.gone.load(Ordering::Acquire) {
312            Waited::PeerGone
313        } else {
314            let current = self.0.generation.load(Ordering::Acquire);
315            if current != *observed {
316                *observed = current;
317                Waited::Signalled
318            } else if result.timed_out() {
319                Waited::TimedOut
320            } else {
321                Waited::Signalled
322            }
323        }
324    }
325}
326
327/// Borrowed shared mapping. The u32 prefix contains the header and samples;
328/// the u64 suffix contains only heartbeat and last-error cache lines.
329#[derive(Clone, Copy, Debug)]
330pub struct Region<'a> {
331    words32: &'a [AtomicU32],
332    words64: &'a [AtomicU64],
333}
334
335impl<'a> Region<'a> {
336    pub fn new(words32: &'a [AtomicU32], words64: &'a [AtomicU64]) -> Self {
337        Self { words32, words64 }
338    }
339
340    pub fn state(self) -> Option<State> {
341        State::from_wire(self.load32(STATE_I, Ordering::Acquire))
342    }
343
344    pub fn fault_code(self) -> u32 {
345        self.load32(FAULT_I, Ordering::Acquire)
346    }
347
348    pub fn heartbeat(self) -> u64 {
349        self.words64
350            .first()
351            .map_or(0, |v| v.load(Ordering::Acquire))
352    }
353
354    pub fn last_error(self) -> u64 {
355        self.words64
356            .get(CACHE_LINE / 8)
357            .map_or(0, |v| v.load(Ordering::Acquire))
358    }
359
360    fn load32(self, index: usize, ordering: Ordering) -> u32 {
361        self.words32.get(index).map_or(0, |v| v.load(ordering))
362    }
363
364    fn store32(self, index: usize, value: u32, ordering: Ordering) {
365        if let Some(word) = self.words32.get(index) {
366            word.store(value, ordering);
367        }
368    }
369}
370
371#[derive(Debug)]
372pub struct RegionBuf {
373    words32: Box<[AtomicU32]>,
374    words64: Box<[AtomicU64]>,
375}
376
377impl RegionBuf {
378    pub fn new(geometry: Geometry) -> Result<Self, GeometryError> {
379        let geometry = geometry.validate()?;
380        let layout = layout(geometry).expect("validated geometry has a layout");
381        Ok(Self {
382            words32: (0..layout.prefix_bytes / 4)
383                .map(|_| AtomicU32::new(0))
384                .collect(),
385            words64: (0..COUNTER_WORDS).map(|_| AtomicU64::new(0)).collect(),
386        })
387    }
388
389    pub fn region(&self) -> Region<'_> {
390        Region::new(&self.words32, &self.words64)
391    }
392}
393
394/// Memory that owns a mapping and lends it as a [`Region`]: an in-process
395/// [`RegionBuf`], or a platform adapter's shared-memory view. An owner that
396/// outlives the borrow lets a [`Publisher`] travel to another thread without
397/// the [`Producer`] that announced the header.
398pub trait RegionMemory: Send + Sync {
399    fn region(&self) -> Region<'_>;
400}
401
402impl RegionMemory for RegionBuf {
403    fn region(&self) -> Region<'_> {
404        self.region()
405    }
406}
407
408impl<T: RegionMemory + ?Sized> RegionMemory for Arc<T> {
409    fn region(&self) -> Region<'_> {
410        T::region(self)
411    }
412}
413
414#[derive(Clone, Copy, Debug, Eq, PartialEq)]
415pub enum HeaderError {
416    TooShort,
417    Magic,
418    Version,
419    State,
420    Geometry(GeometryError),
421    TotalBytes,
422    LaneGeometry,
423}
424
425#[derive(Clone, Copy, Debug, Eq, PartialEq)]
426pub enum Publish {
427    Published,
428    Dropped,
429}
430
431/// The producer's real-time half: the announced geometry and slot layout,
432/// cached so the per-block path needs no [`Producer`] and no borrow of the
433/// mapping beyond each call. Every method takes the [`Region`] it works on and
434/// neither blocks, locks nor allocates; `work` is only signalled.
435#[derive(Clone, Copy, Debug)]
436pub struct Publisher {
437    geometry: Geometry,
438    layout: Layout,
439}
440
441impl Publisher {
442    pub fn geometry(&self) -> Geometry {
443        self.geometry
444    }
445
446    pub fn state(&self, region: Region<'_>) -> Option<State> {
447        region.state()
448    }
449
450    /// The last sequence the consumer completed on `lane` (0 outside the
451    /// geometry).
452    pub fn completed(&self, region: Region<'_>, lane: usize) -> u32 {
453        if lane >= self.geometry.lanes as usize {
454            return 0;
455        }
456        u32::from_le(region.load32(COMPLETED_I + lane, Ordering::Acquire))
457    }
458
459    /// Writes `sequence` (never 0) into its slot and publishes it, then
460    /// signals `work`. `Dropped`, touching nothing, unless the consumer is
461    /// `Ready` and has completed `sequence - slots`, whose slot this reuses.
462    /// Sequences start at 1, advance once per publish, and must not wrap; a
463    /// lane therefore carries at most `u32::MAX` sequences.
464    pub fn try_publish<F>(
465        &self,
466        region: Region<'_>,
467        lane: usize,
468        sequence: u32,
469        work: Option<&dyn Signal>,
470        write: F,
471    ) -> Publish
472    where
473        F: FnOnce(&mut SlotWriter<'_>),
474    {
475        if region.state() != Some(State::Ready)
476            || lane >= self.geometry.lanes as usize
477            || sequence == 0
478        {
479            return Publish::Dropped;
480        }
481        let completed = self.completed(region, lane);
482        if sequence > self.geometry.slots
483            && !sequence_at_least(completed, sequence - self.geometry.slots)
484        {
485            return Publish::Dropped;
486        }
487        let offset = self.layout.slot_offset(lane, sequence) / 4;
488        let Some(samples) = region.words32.get(
489            offset..offset + self.geometry.samples_per_slot() * self.geometry.sample_format.words(),
490        ) else {
491            return Publish::Dropped;
492        };
493        let meta_offset =
494            offset + self.geometry.samples_per_slot() * self.geometry.sample_format.words();
495        let metadata = &region.words32[meta_offset..meta_offset + METADATA_WORDS];
496        let mut writer = SlotWriter {
497            samples,
498            metadata,
499            format: self.geometry.sample_format,
500            frames: self.geometry.max_frames as usize,
501            planes: self.geometry.planes(),
502        };
503        writer.set_transport(Transport::default());
504        writer.set_events(&[]);
505        metadata[CONTEXT_WORDS + PACKET_WORDS].store(0, Ordering::Relaxed);
506        metadata[CONTEXT_WORDS + PACKET_WORDS + 1].store(0, Ordering::Relaxed);
507        write(&mut writer);
508        region.store32(SEQUENCE_I + lane, sequence.to_le(), Ordering::Release);
509        if let Some(signal) = work {
510            signal.signal();
511        }
512        Publish::Published
513    }
514
515    /// Lends `sequence`'s slot to `read` once the consumer has completed it.
516    pub fn try_read<F>(&self, region: Region<'_>, lane: usize, sequence: u32, read: F) -> bool
517    where
518        F: FnOnce(SlotReader<'_>),
519    {
520        if lane >= self.geometry.lanes as usize
521            || !sequence_at_least(self.completed(region, lane), sequence)
522        {
523            return false;
524        }
525        let published = u32::from_le(region.load32(SEQUENCE_I + lane, Ordering::Acquire));
526        if published.wrapping_sub(sequence) >= self.geometry.slots {
527            return false;
528        }
529        let offset = self.layout.slot_offset(lane, sequence) / 4;
530        let Some(samples) = region.words32.get(
531            offset..offset + self.geometry.samples_per_slot() * self.geometry.sample_format.words(),
532        ) else {
533            return false;
534        };
535        let meta_offset =
536            offset + self.geometry.samples_per_slot() * self.geometry.sample_format.words();
537        read(SlotReader {
538            samples,
539            metadata: &region.words32[meta_offset..meta_offset + METADATA_WORDS],
540            format: self.geometry.sample_format,
541            frames: self.geometry.max_frames as usize,
542            planes: self.geometry.planes(),
543        });
544        true
545    }
546}
547
548pub struct Producer<'a> {
549    region: Region<'a>,
550    geometry: Option<Geometry>,
551    layout: Option<Layout>,
552    work: [Option<&'a dyn Signal>; 2],
553    ready: &'a dyn Wait,
554}
555
556impl fmt::Debug for Producer<'_> {
557    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
558        formatter
559            .debug_struct("Producer")
560            .field("region", &self.region)
561            .field("geometry", &self.geometry)
562            .field("layout", &self.layout)
563            .finish_non_exhaustive()
564    }
565}
566
567impl<'a> Producer<'a> {
568    pub fn new(region: Region<'a>, work: [Option<&'a dyn Signal>; 2], ready: &'a dyn Wait) -> Self {
569        Self {
570            region,
571            geometry: None,
572            layout: None,
573            work,
574            ready,
575        }
576    }
577
578    pub fn announce(&mut self, geometry: Geometry, producer_pid: u32) -> Result<(), GeometryError> {
579        let geometry = geometry.validate()?;
580        let layout = layout(geometry).expect("validated geometry has a layout");
581        if self.region.words32.len() * 4 < layout.prefix_bytes
582            || self.region.words64.len() < COUNTER_WORDS
583        {
584            return Err(GeometryError::RegionTooLarge);
585        }
586        for word in self.region.words32.iter().take(HEADER_WORDS) {
587            word.store(0, Ordering::Relaxed);
588        }
589        for word in self.region.words64 {
590            word.store(0, Ordering::Relaxed);
591        }
592        let fields = [
593            (MAGIC_I, MAGIC),
594            (VERSION_I, LAYOUT_VERSION),
595            (TOTAL_I, layout.total_bytes as u32),
596            (LANES_I, geometry.lanes),
597            (INPUTS_I, geometry.input_channels),
598            (OUTPUTS_I, geometry.output_channels),
599            (FRAMES_I, geometry.max_frames),
600            (RATE_I, geometry.sample_rate),
601            (FORMAT_I, geometry.sample_format as u32),
602            (SLOTS_I, geometry.slots),
603            (LATENCY_I, geometry.latency_bound_frames),
604            (PRODUCER_PID_I, producer_pid),
605        ];
606        for (index, value) in fields {
607            self.region.store32(index, value.to_le(), Ordering::Relaxed);
608        }
609        for lane in 0..geometry.lanes as usize {
610            let base = LANE_GEOMETRY_I + lane * 3;
611            self.region.store32(
612                base,
613                (layout.first_slot_offsets[lane] as u32).to_le(),
614                Ordering::Relaxed,
615            );
616            self.region.store32(
617                base + 1,
618                (layout.slot_bytes as u32).to_le(),
619                Ordering::Relaxed,
620            );
621            self.region
622                .store32(base + 2, geometry.slots.to_le(), Ordering::Relaxed);
623        }
624        self.region
625            .store32(FAULT_I, Fault::None as u32, Ordering::Release);
626        self.region
627            .store32(STATE_I, State::Announced as u32, Ordering::Release);
628        self.geometry = Some(geometry);
629        self.layout = Some(layout);
630        Ok(())
631    }
632
633    pub fn wait_ready(&self, timeout: Duration) -> Waited {
634        let deadline = Instant::now() + timeout;
635        loop {
636            match self.region.state() {
637                Some(State::Ready) => return Waited::Signalled,
638                Some(State::Fault | State::Closing) | None => return Waited::PeerGone,
639                _ => {}
640            }
641            let now = Instant::now();
642            if now >= deadline {
643                return Waited::TimedOut;
644            }
645            match self.ready.wait(deadline.saturating_duration_since(now)) {
646                Waited::Signalled => {}
647                other => return other,
648            }
649        }
650    }
651
652    /// The real-time half, once [`Producer::announce`] has succeeded.
653    pub fn publisher(&self) -> Option<Publisher> {
654        Some(Publisher {
655            geometry: self.geometry?,
656            layout: self.layout?,
657        })
658    }
659
660    pub fn completed(&self, lane: usize) -> u32 {
661        self.publisher()
662            .map_or(0, |publisher| publisher.completed(self.region, lane))
663    }
664
665    pub fn try_publish<F>(&self, lane: usize, sequence: u32, write: F) -> Publish
666    where
667        F: FnOnce(&mut SlotWriter<'_>),
668    {
669        let Some(publisher) = self.publisher() else {
670            return Publish::Dropped;
671        };
672        publisher.try_publish(self.region, lane, sequence, self.work[lane], write)
673    }
674
675    pub fn try_read<F>(&self, lane: usize, sequence: u32, read: F) -> bool
676    where
677        F: FnOnce(SlotReader<'_>),
678    {
679        self.publisher()
680            .is_some_and(|publisher| publisher.try_read(self.region, lane, sequence, read))
681    }
682
683    pub fn close(&self) {
684        self.region
685            .store32(STATE_I, State::Closing as u32, Ordering::Release);
686        for signal in self.work.iter().flatten() {
687            signal.signal();
688        }
689    }
690}
691
692#[derive(Debug)]
693pub struct SlotWriter<'a> {
694    metadata: &'a [AtomicU32],
695    format: SampleFormat,
696    samples: &'a [AtomicU32],
697    frames: usize,
698    planes: usize,
699}
700
701impl SlotWriter<'_> {
702    pub fn frames(&self) -> usize {
703        self.frames
704    }
705
706    pub fn planes(&self) -> usize {
707        self.planes
708    }
709
710    pub fn write(&mut self, plane: usize, frame: usize, sample: f32) {
711        self.write_f64(plane, frame, f64::from(sample));
712    }
713
714    pub fn write_f64(&mut self, plane: usize, frame: usize, sample: f64) {
715        let index = (plane * self.frames + frame) * self.format.words();
716        if self.format == SampleFormat::F64 {
717            self.samples[index].store((sample.to_bits() as u32).to_le(), Ordering::Relaxed);
718            self.samples[index + 1]
719                .store(((sample.to_bits() >> 32) as u32).to_le(), Ordering::Relaxed);
720        } else {
721            self.samples[index].store((sample as f32).to_bits().to_le(), Ordering::Relaxed);
722        }
723    }
724    pub fn set_transport(&mut self, transport: Transport) {
725        for (to, from) in self.metadata.iter().zip(transport.words()) {
726            to.store(from.to_le(), Ordering::Relaxed);
727        }
728    }
729    pub fn set_events(&mut self, events: &[Event]) -> bool {
730        if events.len() > MAX_EVENTS || events.iter().any(|e| !e.valid(self.frames as u32)) {
731            return false;
732        }
733        store_packet(
734            &self.metadata[CONTEXT_WORDS..CONTEXT_WORDS + PACKET_WORDS],
735            events,
736            0,
737        );
738        true
739    }
740    pub fn copy_from(&mut self, samples: &[f32]) -> bool {
741        if self.format != SampleFormat::F32 || samples.len() != self.samples.len() {
742            return false;
743        }
744        for (destination, sample) in self.samples.iter().zip(samples) {
745            destination.store(sample.to_bits(), Ordering::Relaxed);
746        }
747        true
748    }
749}
750
751#[derive(Clone, Copy, Debug)]
752pub struct SlotReader<'a> {
753    metadata: &'a [AtomicU32],
754    format: SampleFormat,
755    samples: &'a [AtomicU32],
756    frames: usize,
757    planes: usize,
758}
759
760impl SlotReader<'_> {
761    pub fn frames(self) -> usize {
762        self.frames
763    }
764
765    pub fn planes(self) -> usize {
766        self.planes
767    }
768
769    pub fn read(self, plane: usize, frame: usize) -> f32 {
770        self.read_f64(plane, frame) as f32
771    }
772
773    pub fn read_f64(self, plane: usize, frame: usize) -> f64 {
774        let index = (plane * self.frames + frame) * self.format.words();
775        let lo = u32::from_le(self.samples[index].load(Ordering::Relaxed));
776        if self.format == SampleFormat::F64 {
777            let hi = u32::from_le(self.samples[index + 1].load(Ordering::Relaxed));
778            f64::from_bits(u64::from(lo) | (u64::from(hi) << 32))
779        } else {
780            f64::from(f32::from_bits(lo))
781        }
782    }
783    pub fn output_events(self, events: &mut EventBuffer) -> bool {
784        load_packet(
785            &self.metadata[CONTEXT_WORDS + PACKET_WORDS..],
786            self.frames as u32,
787            events,
788        )
789    }
790    pub fn copy_to(self, samples: &mut [f32]) -> bool {
791        if self.format != SampleFormat::F32 || samples.len() != self.samples.len() {
792            return false;
793        }
794        for (destination, sample) in samples.iter_mut().zip(self.samples) {
795            *destination = f32::from_bits(sample.load(Ordering::Relaxed));
796        }
797        true
798    }
799}
800
801fn store_packet(words: &[AtomicU32], events: &[Event], dropped: u32) {
802    words[0].store((events.len() as u32).to_le(), Ordering::Relaxed);
803    words[1].store(dropped.to_le(), Ordering::Relaxed);
804    for (target, event) in words[2..].as_chunks::<EVENT_WORDS>().0.iter().zip(events) {
805        for (to, from) in target.iter().zip(event.words()) {
806            to.store(from.to_le(), Ordering::Relaxed);
807        }
808    }
809}
810fn load_packet(words: &[AtomicU32], frames: u32, events: &mut EventBuffer) -> bool {
811    events.clear();
812    let count = u32::from_le(words[0].load(Ordering::Relaxed)) as usize;
813    if count > MAX_EVENTS {
814        return false;
815    }
816    events.dropped = u32::from_le(words[1].load(Ordering::Relaxed));
817    for source in words[2..].as_chunks::<EVENT_WORDS>().0.iter().take(count) {
818        let mut values = [0; EVENT_WORDS];
819        for (to, from) in values.iter_mut().zip(source) {
820            *to = u32::from_le(from.load(Ordering::Relaxed));
821        }
822        let Some(event) = Event::from_words(&values, frames) else {
823            events.clear();
824            return false;
825        };
826        events.push(event);
827    }
828    true
829}
830
831pub struct Consumer<'a> {
832    region: Region<'a>,
833    geometry: Option<Geometry>,
834    layout: Option<Layout>,
835    ready: &'a dyn Signal,
836    done: [Option<&'a dyn Signal>; 2],
837}
838
839impl fmt::Debug for Consumer<'_> {
840    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
841        formatter
842            .debug_struct("Consumer")
843            .field("region", &self.region)
844            .field("geometry", &self.geometry)
845            .field("layout", &self.layout)
846            .finish_non_exhaustive()
847    }
848}
849
850impl<'a> Consumer<'a> {
851    pub fn new(
852        region: Region<'a>,
853        ready: &'a dyn Signal,
854        done: [Option<&'a dyn Signal>; 2],
855    ) -> Self {
856        Self {
857            region,
858            geometry: None,
859            layout: None,
860            ready,
861            done,
862        }
863    }
864
865    pub fn validate_and_ready(&mut self, consumer_pid: u32) -> Result<Geometry, HeaderError> {
866        let geometry = validate_header(self.region)?;
867        let layout = layout(geometry).expect("validated geometry has a layout");
868        self.geometry = Some(geometry);
869        self.layout = Some(layout);
870        self.region
871            .store32(CONSUMER_PID_I, consumer_pid.to_le(), Ordering::Relaxed);
872        self.region
873            .store32(FAULT_I, Fault::None as u32, Ordering::Relaxed);
874        self.region
875            .store32(STATE_I, State::Ready as u32, Ordering::Release);
876        self.ready.signal();
877        Ok(geometry)
878    }
879
880    pub fn fault(&self, code: u32) {
881        self.region.store32(FAULT_I, code, Ordering::Relaxed);
882        self.region
883            .store32(STATE_I, State::Fault as u32, Ordering::Release);
884        self.ready.signal();
885        for signal in self.done.iter().flatten() {
886            signal.signal();
887        }
888    }
889
890    pub fn next_published(&self, lane: usize) -> Option<u32> {
891        let geometry = self.geometry?;
892        if lane >= geometry.lanes as usize || self.region.state() != Some(State::Ready) {
893            return None;
894        }
895        let published = u32::from_le(self.region.load32(SEQUENCE_I + lane, Ordering::Acquire));
896        let completed = u32::from_le(self.region.load32(COMPLETED_I + lane, Ordering::Acquire));
897        let next = completed.wrapping_add(1);
898        (published != completed && sequence_at_least(published, next)).then_some(next)
899    }
900
901    /// Copies one slot to `buffer`, lets the caller process ordinary f32s, and
902    /// copies it back. No plugin ever receives shared-memory atomics.
903    pub fn slot_mut<F>(&self, lane: usize, sequence: u32, buffer: &mut [f32], process: F) -> bool
904    where
905        F: FnOnce(&mut [f32]),
906    {
907        let Some((geometry, layout)) = self.geometry.zip(self.layout) else {
908            return false;
909        };
910        if geometry.sample_format != SampleFormat::F32
911            || lane >= geometry.lanes as usize
912            || buffer.len() != geometry.samples_per_slot()
913        {
914            return false;
915        }
916        let offset = layout.slot_offset(lane, sequence) / 4;
917        let samples = &self.region.words32[offset..offset + buffer.len()];
918        for (destination, sample) in buffer.iter_mut().zip(samples) {
919            *destination = f32::from_bits(sample.load(Ordering::Relaxed));
920        }
921        process(buffer);
922        for (destination, sample) in samples.iter().zip(buffer) {
923            destination.store(sample.to_bits(), Ordering::Relaxed);
924        }
925        true
926    }
927
928    pub fn slot_mut_f64<F>(
929        &self,
930        lane: usize,
931        sequence: u32,
932        buffer: &mut [f64],
933        process: F,
934    ) -> bool
935    where
936        F: FnOnce(&mut [f64]),
937    {
938        let Some((geometry, layout)) = self.geometry.zip(self.layout) else {
939            return false;
940        };
941        if geometry.sample_format != SampleFormat::F64
942            || lane >= geometry.lanes as usize
943            || buffer.len() != geometry.samples_per_slot()
944        {
945            return false;
946        }
947        let offset = layout.slot_offset(lane, sequence) / 4;
948        let samples = &self.region.words32[offset..offset + buffer.len() * 2];
949        for (to, from) in buffer.iter_mut().zip(samples.as_chunks::<2>().0.iter()) {
950            *to = f64::from_bits(
951                u64::from(u32::from_le(from[0].load(Ordering::Relaxed)))
952                    | (u64::from(u32::from_le(from[1].load(Ordering::Relaxed))) << 32),
953            );
954        }
955        process(buffer);
956        for (to, from) in samples.as_chunks::<2>().0.iter().zip(buffer) {
957            to[0].store((from.to_bits() as u32).to_le(), Ordering::Relaxed);
958            to[1].store(((from.to_bits() >> 32) as u32).to_le(), Ordering::Relaxed);
959        }
960        true
961    }
962    fn metadata(&self, lane: usize, sequence: u32) -> Option<&[AtomicU32]> {
963        let geometry = self.geometry?;
964        let layout = self.layout?;
965        if lane >= geometry.lanes as usize {
966            return None;
967        }
968        let offset = layout.slot_offset(lane, sequence) / 4
969            + geometry.samples_per_slot() * geometry.sample_format.words();
970        self.region.words32.get(offset..offset + METADATA_WORDS)
971    }
972    pub fn input_packet(
973        &self,
974        lane: usize,
975        sequence: u32,
976        context: &mut Transport,
977        events: &mut EventBuffer,
978    ) -> bool {
979        let Some(words) = self.metadata(lane, sequence) else {
980            return false;
981        };
982        let mut raw = [0; CONTEXT_WORDS];
983        for (to, from) in raw.iter_mut().zip(words) {
984            *to = u32::from_le(from.load(Ordering::Relaxed));
985        }
986        let Some(value) = Transport::from_words(&raw) else {
987            return false;
988        };
989        *context = value;
990        load_packet(
991            &words[CONTEXT_WORDS..CONTEXT_WORDS + PACKET_WORDS],
992            self.geometry.expect("validated").max_frames,
993            events,
994        )
995    }
996    pub fn output_packet(&self, lane: usize, sequence: u32, events: &EventBuffer) -> bool {
997        let Some(words) = self.metadata(lane, sequence) else {
998            return false;
999        };
1000        if events
1001            .as_slice()
1002            .iter()
1003            .any(|e| !e.valid(self.geometry.expect("validated").max_frames))
1004        {
1005            return false;
1006        }
1007        store_packet(
1008            &words[CONTEXT_WORDS + PACKET_WORDS..],
1009            events.as_slice(),
1010            events.dropped,
1011        );
1012        true
1013    }
1014
1015    pub fn complete(&self, lane: usize, sequence: u32) {
1016        self.region
1017            .store32(COMPLETED_I + lane, sequence.to_le(), Ordering::Release);
1018        if let Some(signal) = self.done[lane] {
1019            signal.signal();
1020        }
1021    }
1022
1023    pub fn beat(&self) -> u64 {
1024        self.region
1025            .words64
1026            .first()
1027            .map_or(0, |v| v.fetch_add(1, Ordering::Release).wrapping_add(1))
1028    }
1029
1030    pub fn set_last_error(&self, error: u64) {
1031        if let Some(word) = self.region.words64.get(CACHE_LINE / 8) {
1032            word.store(error, Ordering::Release);
1033        }
1034    }
1035}
1036
1037fn validate_header(region: Region<'_>) -> Result<Geometry, HeaderError> {
1038    if region.words32.len() < HEADER_WORDS || region.words64.len() < COUNTER_WORDS {
1039        return Err(HeaderError::TooShort);
1040    }
1041    if u32::from_le(region.load32(MAGIC_I, Ordering::Relaxed)) != MAGIC {
1042        return Err(HeaderError::Magic);
1043    }
1044    if u32::from_le(region.load32(VERSION_I, Ordering::Relaxed)) != LAYOUT_VERSION {
1045        return Err(HeaderError::Version);
1046    }
1047    if region.state() != Some(State::Announced) {
1048        return Err(HeaderError::State);
1049    }
1050    let geometry = Geometry {
1051        sample_format: match u32::from_le(region.load32(FORMAT_I, Ordering::Relaxed)) {
1052            0 => SampleFormat::F32,
1053            1 => SampleFormat::F64,
1054            _ => return Err(HeaderError::Version),
1055        },
1056        lanes: u32::from_le(region.load32(LANES_I, Ordering::Relaxed)),
1057        slots: u32::from_le(region.load32(SLOTS_I, Ordering::Relaxed)),
1058        input_channels: u32::from_le(region.load32(INPUTS_I, Ordering::Relaxed)),
1059        output_channels: u32::from_le(region.load32(OUTPUTS_I, Ordering::Relaxed)),
1060        max_frames: u32::from_le(region.load32(FRAMES_I, Ordering::Relaxed)),
1061        sample_rate: u32::from_le(region.load32(RATE_I, Ordering::Relaxed)),
1062        latency_bound_frames: u32::from_le(region.load32(LATENCY_I, Ordering::Relaxed)),
1063    }
1064    .validate()
1065    .map_err(HeaderError::Geometry)?;
1066    let expected = layout(geometry).expect("validated geometry has a layout");
1067    if u32::from_le(region.load32(TOTAL_I, Ordering::Relaxed)) as usize != expected.total_bytes
1068        || region.words32.len() * 4 < expected.prefix_bytes
1069    {
1070        return Err(HeaderError::TotalBytes);
1071    }
1072    for lane in 0..geometry.lanes as usize {
1073        let base = LANE_GEOMETRY_I + lane * 3;
1074        let actual = [
1075            u32::from_le(region.load32(base, Ordering::Relaxed)) as usize,
1076            u32::from_le(region.load32(base + 1, Ordering::Relaxed)) as usize,
1077            u32::from_le(region.load32(base + 2, Ordering::Relaxed)) as usize,
1078        ];
1079        if actual
1080            != [
1081                expected.first_slot_offsets[lane],
1082                expected.slot_bytes,
1083                expected.slots as usize,
1084            ]
1085        {
1086            return Err(HeaderError::LaneGeometry);
1087        }
1088    }
1089    Ok(geometry)
1090}
1091
1092fn sequence_at_least(value: u32, reference: u32) -> bool {
1093    value.wrapping_sub(reference) as i32 >= 0
1094}
1095
1096#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1097struct PipelineEntry {
1098    sequence: u32,
1099    app_block: u64,
1100    generation: u64,
1101    valid: bool,
1102}
1103
1104/// Metadata enforcing a fixed-depth pipeline.
1105///
1106/// An application that publishes block `k` and then reads must use
1107/// `depth <= slots - 1`; read-then-publish order allows `depth <= slots`.
1108#[derive(Debug)]
1109pub struct Pipeline {
1110    entries: [PipelineEntry; MAX_SLOTS as usize],
1111    depth: u32,
1112}
1113
1114impl Default for Pipeline {
1115    fn default() -> Self {
1116        Self::with_depth(1)
1117    }
1118}
1119
1120impl Pipeline {
1121    pub fn new() -> Self {
1122        Self::default()
1123    }
1124
1125    pub fn with_depth(depth: u32) -> Self {
1126        assert!((1..=MAX_SLOTS).contains(&depth));
1127        Self {
1128            entries: array::from_fn(|_| PipelineEntry::default()),
1129            depth,
1130        }
1131    }
1132
1133    pub fn depth(&self) -> u32 {
1134        self.depth
1135    }
1136
1137    pub fn set_depth(&mut self, depth: u32) {
1138        assert!((1..=MAX_SLOTS).contains(&depth));
1139        self.depth = depth;
1140        self.clear();
1141    }
1142
1143    pub fn published(&mut self, sequence: u32, app_block: u64, generation: u64) {
1144        self.entries[sequence as usize % MAX_SLOTS as usize] = PipelineEntry {
1145            sequence,
1146            app_block,
1147            generation,
1148            valid: true,
1149        };
1150    }
1151
1152    /// Returns the sequence for `app_block - depth` only when that exact block
1153    /// was published, completed, and belongs to the current generation.
1154    pub fn output_sequence(
1155        &mut self,
1156        app_block: u64,
1157        generation: u64,
1158        completed: u32,
1159    ) -> Option<u32> {
1160        let wanted = app_block.checked_sub(u64::from(self.depth))?;
1161        let found = self.entries.iter_mut().find(|entry| {
1162            entry.valid && entry.app_block == wanted && entry.generation == generation
1163        })?;
1164        if sequence_at_least(completed, found.sequence) {
1165            found.valid = false;
1166            Some(found.sequence)
1167        } else {
1168            // Once its output instant passes, a late result must never be used.
1169            found.valid = false;
1170            None
1171        }
1172    }
1173
1174    pub fn clear(&mut self) {
1175        self.entries = array::from_fn(|_| PipelineEntry::default());
1176    }
1177}