Skip to main content

ursus_core/render/
triple_buffer.rs

1use std::cell::UnsafeCell;
2use std::sync::atomic::{AtomicU8, Ordering};
3
4pub struct TripleBuffer<T: Send> {
5    slots: [UnsafeCell<T>; 3],
6    state: AtomicU8,
7}
8
9unsafe impl<T: Send> Send for TripleBuffer<T> {}
10unsafe impl<T: Send> Sync for TripleBuffer<T> {}
11
12const NEW_FRAME_BIT: u8 = 0b1000_0000;
13const READY_SHIFT: u8 = 4;
14const WRITE_SHIFT: u8 = 2;
15const IDX_MASK: u8 = 0b11;
16
17const INITIAL_STATE: u8 = (1 << READY_SHIFT) | (0 << WRITE_SHIFT);
18
19impl<T: Send + Default> TripleBuffer<T> {
20    pub fn new() -> Self {
21        Self {
22            slots: [
23                UnsafeCell::new(T::default()),
24                UnsafeCell::new(T::default()),
25                UnsafeCell::new(T::default()),
26            ],
27            state: AtomicU8::new(INITIAL_STATE),
28        }
29    }
30}
31
32impl<T: Send> TripleBuffer<T> {
33    pub fn new_with(a: T, b: T, c: T) -> Self {
34        Self {
35            slots: [UnsafeCell::new(a), UnsafeCell::new(b), UnsafeCell::new(c)],
36            state: AtomicU8::new(INITIAL_STATE),
37        }
38    }
39
40    // todo: Currently, writer exclusivity is guaranteed by the architecture,
41    // not by the type system. The API should be redesigned so that ownership of the write handle
42    // is explicit, so that clippy::mut_from_ref doesn't have to be manually muted.
43    #[allow(clippy::mut_from_ref)]
44    pub fn write_slot(&self) -> &mut T {
45        let state = self.state.load(Ordering::Relaxed);
46        let idx = ((state >> WRITE_SHIFT) & IDX_MASK) as usize;
47        unsafe { &mut *self.slots[idx].get() }
48    }
49
50    pub fn publish(&self) {
51        let mut state = self.state.load(Ordering::Relaxed);
52        loop {
53            let write = (state >> WRITE_SHIFT) & IDX_MASK;
54            let ready = (state >> READY_SHIFT) & IDX_MASK;
55
56            let new_state = NEW_FRAME_BIT | (write << READY_SHIFT) | (ready << WRITE_SHIFT);
57
58            match self.state.compare_exchange_weak(state, new_state, Ordering::Release, Ordering::Relaxed) {
59                Ok(_) => return,
60                Err(s) => state = s,
61            }
62        }
63    }
64
65    pub fn consume(&self, render_idx: &mut usize) -> bool {
66        let mut state = self.state.load(Ordering::Acquire);
67
68        if state & NEW_FRAME_BIT == 0 {
69            return false;
70        }
71
72        loop {
73            let ready = ((state >> READY_SHIFT) & IDX_MASK) as usize;
74            let render = *render_idx;
75
76            let new_state = (state & !NEW_FRAME_BIT) & !(IDX_MASK << READY_SHIFT) | ((render as u8) << READY_SHIFT);
77
78            match self.state.compare_exchange_weak(state, new_state, Ordering::AcqRel, Ordering::Acquire) {
79                Ok(_) => {
80                    *render_idx = ready;
81                    return true;
82                }
83                Err(s) => {
84                    state = s;
85                    if state & NEW_FRAME_BIT == 0 {
86                        return false;
87                    }
88                }
89            }
90        }
91    }
92
93    pub fn render_slot(&self, render_idx: usize) -> &T {
94        debug_assert!(render_idx < 3, "render_idx вне диапазона");
95        unsafe { &*self.slots[render_idx].get() }
96    }
97}
98
99impl<T: Send + Default> Default for TripleBuffer<T> {
100    fn default() -> Self {
101        Self::new()
102    }
103}