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