maolan_engine/
triple_buffer.rs1use 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
20unsafe 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 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 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 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}