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