Skip to main content

maolan_engine/
triple_buffer.rs

1use std::cell::UnsafeCell;
2use std::sync::{
3    Arc,
4    atomic::{AtomicBool, AtomicUsize, Ordering},
5};
6
7const BUFFER_COUNT: usize = 3;
8const INDEX_MASK: usize = 0b11;
9const FRONT_SHIFT: usize = 0;
10const MIDDLE_SHIFT: usize = 2;
11const BACK_SHIFT: usize = 4;
12const DIRTY: usize = 1 << 6;
13
14#[derive(Debug)]
15struct Inner<T> {
16    buffers: [UnsafeCell<T>; BUFFER_COUNT],
17    state: AtomicUsize,
18}
19
20// Safety: the producer and consumer own disjoint buffer indexes. Index handoff
21// is serialized through `state`; `T` must be `Send` because the
22// handles may live on different threads.
23unsafe impl<T: Send> Send for Inner<T> {}
24unsafe impl<T: Send> Sync for Inner<T> {}
25
26#[derive(Debug)]
27pub struct TripleBufferProducer<T> {
28    inner: Arc<Inner<T>>,
29    back: usize,
30}
31
32#[derive(Debug)]
33pub struct TripleBufferConsumer<T> {
34    inner: Arc<Inner<T>>,
35    front: AtomicUsize,
36    active: AtomicBool,
37}
38
39pub fn triple_buffer<T: Clone>(initial: T) -> (TripleBufferProducer<T>, TripleBufferConsumer<T>) {
40    let inner = Arc::new(Inner {
41        buffers: std::array::from_fn(|_| UnsafeCell::new(initial.clone())),
42        state: AtomicUsize::new(pack_state(0, 1, 2, false)),
43    });
44    (
45        TripleBufferProducer {
46            inner: inner.clone(),
47            back: 2,
48        },
49        TripleBufferConsumer {
50            inner,
51            front: AtomicUsize::new(0),
52            active: AtomicBool::new(false),
53        },
54    )
55}
56
57impl<T> TripleBufferProducer<T> {
58    pub fn write_buffer(&mut self) -> &mut T {
59        // Safety: `self.back` is owned only by this producer until `publish`.
60        unsafe { &mut *self.inner.buffers[self.back].get() }
61    }
62
63    pub fn publish(&mut self) {
64        let mut state = self.inner.state.load(Ordering::Acquire);
65        loop {
66            let front = front_index(state);
67            let middle = middle_index(state);
68            let next = pack_state(front, self.back, middle, true);
69            match self.inner.state.compare_exchange_weak(
70                state,
71                next,
72                Ordering::AcqRel,
73                Ordering::Acquire,
74            ) {
75                Ok(_) => {
76                    self.back = middle;
77                    return;
78                }
79                Err(current) => state = current,
80            }
81        }
82    }
83}
84
85impl<T> TripleBufferConsumer<T> {
86    pub fn refresh(&mut self) -> bool {
87        self.refresh_inner().is_some()
88    }
89
90    pub fn read_buffer(&self) -> &T {
91        let front = self.front.load(Ordering::Acquire);
92        // Safety: callers using this low-level API must ensure no concurrent
93        // consumer refresh runs while the returned reference is live.
94        unsafe { &*self.inner.buffers[front].get() }
95    }
96
97    pub fn read_latest_clone(&self) -> Option<T>
98    where
99        T: Clone,
100    {
101        let _guard = self.try_enter()?;
102        let front = self.refresh_inner()?;
103        // Safety: `ConsumerAccess` serializes consumer refreshes/clones, and
104        // the producer never writes the current front buffer.
105        Some(unsafe { (&*self.inner.buffers[front].get()).clone() })
106    }
107
108    fn refresh_inner(&self) -> Option<usize> {
109        let mut state = self.inner.state.load(Ordering::Acquire);
110        loop {
111            if !is_dirty(state) {
112                return None;
113            }
114            let front = front_index(state);
115            let middle = middle_index(state);
116            let back = back_index(state);
117            let next = pack_state(middle, front, back, false);
118            match self.inner.state.compare_exchange_weak(
119                state,
120                next,
121                Ordering::AcqRel,
122                Ordering::Acquire,
123            ) {
124                Ok(_) => {
125                    self.front.store(middle, Ordering::Release);
126                    return Some(middle);
127                }
128                Err(current) => state = current,
129            }
130        }
131    }
132
133    fn try_enter(&self) -> Option<ConsumerAccess<'_>> {
134        self.active
135            .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
136            .ok()
137            .map(|_| ConsumerAccess {
138                active: &self.active,
139            })
140    }
141}
142
143struct ConsumerAccess<'a> {
144    active: &'a AtomicBool,
145}
146
147impl Drop for ConsumerAccess<'_> {
148    fn drop(&mut self) {
149        self.active.store(false, Ordering::Release);
150    }
151}
152
153fn pack_state(front: usize, middle: usize, back: usize, dirty: bool) -> usize {
154    (front << FRONT_SHIFT)
155        | (middle << MIDDLE_SHIFT)
156        | (back << BACK_SHIFT)
157        | if dirty { DIRTY } else { 0 }
158}
159
160fn front_index(state: usize) -> usize {
161    (state >> FRONT_SHIFT) & INDEX_MASK
162}
163
164fn middle_index(state: usize) -> usize {
165    (state >> MIDDLE_SHIFT) & INDEX_MASK
166}
167
168fn back_index(state: usize) -> usize {
169    (state >> BACK_SHIFT) & INDEX_MASK
170}
171
172fn is_dirty(state: usize) -> bool {
173    state & DIRTY != 0
174}
175
176#[cfg(test)]
177mod tests {
178    use super::triple_buffer;
179    use std::sync::{
180        Arc,
181        atomic::{AtomicBool, Ordering},
182    };
183    use std::thread;
184
185    #[test]
186    fn consumer_reads_initial_value_before_publish() {
187        let (_producer, consumer) = triple_buffer(7usize);
188
189        assert_eq!(*consumer.read_buffer(), 7);
190    }
191
192    #[test]
193    fn refresh_publishes_latest_value() {
194        let (mut producer, mut consumer) = triple_buffer(0usize);
195
196        *producer.write_buffer() = 1;
197        producer.publish();
198        *producer.write_buffer() = 2;
199        producer.publish();
200
201        assert!(consumer.refresh());
202        assert_eq!(*consumer.read_buffer(), 2);
203        assert!(!consumer.refresh());
204    }
205
206    #[test]
207    fn shared_consumer_clones_latest_value_without_mutex() {
208        let (mut producer, consumer) = triple_buffer(0usize);
209
210        *producer.write_buffer() = 11;
211        producer.publish();
212        assert_eq!(consumer.read_latest_clone(), Some(11));
213        assert_eq!(consumer.read_latest_clone(), None);
214
215        *producer.write_buffer() = 12;
216        producer.publish();
217        assert_eq!(consumer.read_latest_clone(), Some(12));
218    }
219
220    #[test]
221    fn producer_can_reuse_buffers_after_consumer_refresh() {
222        let (mut producer, mut consumer) = triple_buffer(Vec::<usize>::new());
223
224        producer.write_buffer().push(1);
225        producer.publish();
226        assert!(consumer.refresh());
227        assert_eq!(consumer.read_buffer().as_slice(), &[1]);
228
229        producer.write_buffer().clear();
230        producer.write_buffer().extend_from_slice(&[2, 3]);
231        producer.publish();
232        assert!(consumer.refresh());
233        assert_eq!(consumer.read_buffer().as_slice(), &[2, 3]);
234    }
235
236    #[test]
237    fn concurrent_latest_value_is_monotonic() {
238        let (mut producer, mut consumer) = triple_buffer(0usize);
239        let done = Arc::new(AtomicBool::new(false));
240        let producer_done = done.clone();
241
242        let handle = thread::spawn(move || {
243            for value in 1..=10_000 {
244                *producer.write_buffer() = value;
245                producer.publish();
246            }
247            producer_done.store(true, Ordering::Release);
248        });
249
250        let mut last = 0;
251        while !done.load(Ordering::Acquire) {
252            if consumer.refresh() {
253                let value = *consumer.read_buffer();
254                assert!(value >= last);
255                last = value;
256            }
257        }
258        while consumer.refresh() {
259            let value = *consumer.read_buffer();
260            assert!(value >= last);
261            last = value;
262        }
263        handle.join().unwrap();
264        assert_eq!(last, 10_000);
265    }
266}