Skip to main content

flexaudio_core/
raw_ring.rs

1//! 生 interleaved f32 デバイスフレーム用の SPSC・RT 安全リング(rtrb バック)。
2//!
3//! producer(RT コールバック)は slice を非ブロッキングに push する。満杯時は overflow
4//! カウンタ([`AtomicU64`])を増やして該当分をドロップし、RT スレッドはブロックしない。
5//! consumer は取り込みスレッド側で pop する。
6
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::sync::Arc;
9
10use rtrb::{Consumer, Producer, RingBuffer};
11
12/// 生フレームリングを作る。`capacity_samples` は f32 サンプル数での容量。
13///
14/// 返り値の producer は RT コールバックスレッドへ、consumer は取り込みスレッドへ渡す
15/// (SPSC)。`overflow` カウンタは両者で共有し、ドロップ済みサンプル数を数える。
16pub fn raw_ring(capacity_samples: usize) -> (RawProducer, RawConsumer) {
17    let cap = capacity_samples.max(1);
18    let (prod, cons) = RingBuffer::<f32>::new(cap);
19    let overflow = Arc::new(AtomicU64::new(0));
20    (
21        RawProducer {
22            inner: prod,
23            overflow: overflow.clone(),
24        },
25        RawConsumer {
26            inner: cons,
27            overflow,
28        },
29    )
30}
31
32/// RT コールバック側のハンドル。非ブロッキング push のみを行う。
33pub struct RawProducer {
34    inner: Producer<f32>,
35    overflow: Arc<AtomicU64>,
36}
37
38impl RawProducer {
39    /// interleaved サンプル slice を非ブロッキングに push する。
40    ///
41    /// 書ける分だけ書き、入り切らなかった残りはドロップして overflow カウンタに加算
42    /// する。返り値は実際に書き込めたサンプル数。決してブロックしない。
43    pub fn push_slice(&mut self, samples: &[f32]) -> usize {
44        if samples.is_empty() {
45            return 0;
46        }
47        let free = self.inner.slots();
48        let writable = free.min(samples.len());
49
50        if writable > 0 {
51            // write_chunk_uninit でアロケート無しにまとめて書く。
52            if let Ok(mut chunk) = self.inner.write_chunk_uninit(writable) {
53                let (a, b) = chunk.as_mut_slices();
54                let (head, tail) = samples.split_at(a.len().min(samples.len()));
55                for (dst, &src) in a.iter_mut().zip(head.iter()) {
56                    dst.write(src);
57                }
58                let tail = &tail[..b.len().min(tail.len())];
59                for (dst, &src) in b.iter_mut().zip(tail.iter()) {
60                    dst.write(src);
61                }
62                // SAFETY: writable 個の MaybeUninit を確かに初期化した。
63                unsafe { chunk.commit_all() };
64            }
65        }
66
67        let dropped = samples.len() - writable;
68        if dropped > 0 {
69            self.overflow.fetch_add(dropped as u64, Ordering::Relaxed);
70        }
71        writable
72    }
73
74    /// これまでにドロップした累計サンプル数。
75    pub fn overflow_count(&self) -> u64 {
76        self.overflow.load(Ordering::Relaxed)
77    }
78}
79
80/// 取り込みスレッド側のハンドル。pop する。
81pub struct RawConsumer {
82    inner: Consumer<f32>,
83    overflow: Arc<AtomicU64>,
84}
85
86impl RawConsumer {
87    /// 利用可能なサンプルを最大 `dst.len()` 個まで `dst` へ取り出す。返り値は取り出し数。
88    pub fn pop_slice(&mut self, dst: &mut [f32]) -> usize {
89        let avail = self.inner.slots();
90        let n = avail.min(dst.len());
91        if n == 0 {
92            return 0;
93        }
94        if let Ok(chunk) = self.inner.read_chunk(n) {
95            let (a, b) = chunk.as_slices();
96            let (alen, blen) = (a.len(), b.len());
97            dst[..alen].copy_from_slice(a);
98            dst[alen..alen + blen].copy_from_slice(b);
99            chunk.commit_all();
100            alen + blen
101        } else {
102            0
103        }
104    }
105
106    /// 1 サンプル取り出す(無ければ `None`)。
107    pub fn pop(&mut self) -> Option<f32> {
108        self.inner.pop().ok()
109    }
110
111    /// 取り出し可能なサンプル数。
112    pub fn available(&self) -> usize {
113        self.inner.slots()
114    }
115
116    /// これまでに producer 側がドロップした累計サンプル数。
117    pub fn overflow_count(&self) -> u64 {
118        self.overflow.load(Ordering::Relaxed)
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn push_and_pop_roundtrip() {
128        let (mut p, mut c) = raw_ring(16);
129        let n = p.push_slice(&[1.0, 2.0, 3.0, 4.0]);
130        assert_eq!(n, 4);
131        let mut out = [0.0f32; 4];
132        let got = c.pop_slice(&mut out);
133        assert_eq!(got, 4);
134        assert_eq!(out, [1.0, 2.0, 3.0, 4.0]);
135        assert_eq!(p.overflow_count(), 0);
136    }
137
138    #[test]
139    fn overflow_counts_dropped_and_never_blocks() {
140        let (mut p, mut c) = raw_ring(4);
141        // 容量 4 に 6 サンプル push → 4 書けて 2 ドロップ。
142        let written = p.push_slice(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
143        assert_eq!(written, 4);
144        assert_eq!(p.overflow_count(), 2);
145        assert_eq!(c.overflow_count(), 2);
146
147        let mut out = [0.0f32; 8];
148        let got = c.pop_slice(&mut out);
149        assert_eq!(got, 4);
150        assert_eq!(&out[..4], &[1.0, 2.0, 3.0, 4.0]);
151    }
152
153    #[test]
154    fn wraps_around() {
155        let (mut p, mut c) = raw_ring(4);
156        p.push_slice(&[1.0, 2.0, 3.0]);
157        let mut out = [0.0f32; 2];
158        c.pop_slice(&mut out); // 2 消費 → read 索引前進
159        assert_eq!(out, [1.0, 2.0]);
160        // 残 1 + 新 3 = 4 で満杯。折り返し書き込みを検証。
161        let w = p.push_slice(&[4.0, 5.0, 6.0]);
162        assert_eq!(w, 3);
163        let mut out2 = [0.0f32; 4];
164        let got = c.pop_slice(&mut out2);
165        assert_eq!(got, 4);
166        assert_eq!(out2, [3.0, 4.0, 5.0, 6.0]);
167    }
168
169    /// 空 slice の push は 0 を返し overflow を増やさない(早期 return 経路)。
170    #[test]
171    fn push_empty_is_noop() {
172        let (mut p, _c) = raw_ring(4);
173        assert_eq!(p.push_slice(&[]), 0);
174        assert_eq!(p.overflow_count(), 0);
175    }
176
177    /// 満杯のリングへさらに push すると全量ドロップ(writable=0)し、
178    /// overflow がそのサンプル数ぶん増える。RT 経路がブロックしないことの裏取り。
179    #[test]
180    fn full_ring_drops_entire_push() {
181        let (mut p, _c) = raw_ring(4);
182        assert_eq!(p.push_slice(&[1.0, 2.0, 3.0, 4.0]), 4); // 満杯。
183                                                            // もう入らない → 5 サンプル全ドロップ。
184        let w = p.push_slice(&[5.0; 5]);
185        assert_eq!(w, 0, "満杯なら 1 つも書けない");
186        assert_eq!(
187            p.overflow_count(),
188            5,
189            "全 5 サンプルが overflow に計上される"
190        );
191    }
192
193    /// `pop_slice` は dst が available より大きくても available 個だけ取り出す(off-by-one 防止)。
194    /// 残量より大きい dst・空リングからの pop=0 を確認。
195    #[test]
196    fn pop_slice_respects_available_and_dst_len() {
197        let (mut p, mut c) = raw_ring(8);
198        p.push_slice(&[1.0, 2.0, 3.0]);
199        assert_eq!(c.available(), 3);
200        // dst が大きくても available(3) だけ取れる。
201        let mut big = [0.0f32; 16];
202        assert_eq!(c.pop_slice(&mut big), 3);
203        assert_eq!(&big[..3], &[1.0, 2.0, 3.0]);
204        // 空になったので次は 0。
205        assert_eq!(c.available(), 0);
206        assert_eq!(c.pop_slice(&mut big), 0);
207    }
208
209    /// 連続ドロップで overflow カウンタが u32::MAX を超えても飽和せず u64 で増え続ける
210    /// (overflow は AtomicU64・dropped_before の u32 飽和とは別経路)。
211    #[test]
212    fn overflow_counter_exceeds_u32_max() {
213        let (mut p, _c) = raw_ring(1);
214        // 1 サンプルだけ書いて満杯にし、以降は全ドロップにする。
215        assert_eq!(p.push_slice(&[0.0]), 1);
216        // u32::MAX を跨ぐ量をドロップさせる。大 slice を 1 回 push すれば一気に積める。
217        let big = vec![0.0f32; 1000];
218        let over_u32 = u64::from(u32::MAX) + 2_000;
219        let mut total_dropped = 0u64;
220        while total_dropped < over_u32 {
221            let w = p.push_slice(&big);
222            assert_eq!(w, 0, "満杯なので 1 つも書けない");
223            total_dropped += big.len() as u64;
224        }
225        assert!(
226            p.overflow_count() > u64::from(u32::MAX),
227            "overflow は u32::MAX を超えて積み上がる: {}",
228            p.overflow_count()
229        );
230    }
231
232    /// 単発 `pop()` は 1 サンプルずつ FIFO で返し、空なら None。
233    #[test]
234    fn single_pop_is_fifo_then_none() {
235        let (mut p, mut c) = raw_ring(4);
236        p.push_slice(&[10.0, 20.0]);
237        assert_eq!(c.pop(), Some(10.0));
238        assert_eq!(c.pop(), Some(20.0));
239        assert_eq!(c.pop(), None);
240    }
241
242    /// 容量 0 指定でも `max(1)` で最低 1 を確保し、push/pop が成立する(panic しない)。
243    #[test]
244    fn zero_capacity_is_clamped_to_one() {
245        let (mut p, mut c) = raw_ring(0);
246        assert_eq!(
247            p.push_slice(&[7.0, 8.0]),
248            1,
249            "容量 1 に丸められ 1 サンプルだけ入る"
250        );
251        assert_eq!(p.overflow_count(), 1);
252        assert_eq!(c.pop(), Some(7.0));
253    }
254
255    /// producer/consumer は overflow カウンタを共有する(同じ Arc)。
256    #[test]
257    fn overflow_count_is_shared_between_ends() {
258        let (mut p, c) = raw_ring(2);
259        p.push_slice(&[1.0, 2.0, 3.0, 4.0]); // 2 書けて 2 ドロップ。
260        assert_eq!(p.overflow_count(), 2);
261        assert_eq!(
262            c.overflow_count(),
263            2,
264            "consumer 側も同じ overflow を観測する"
265        );
266    }
267}