use std::collections::VecDeque;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[allow(
clippy::exhaustive_structs,
reason = "a value defined by its fields; there is nothing to add to it"
)]
pub struct Rect {
pub x: i32,
pub y: i32,
pub width: u32,
pub height: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(
clippy::exhaustive_enums,
reason = "every producer handles each kind; a new one has to break them so none repaints the wrong region"
)]
pub enum Repaint {
Full,
Region(Vec<Rect>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Lease {
pub slot: usize,
pub fresh: bool,
pub repaint: Repaint,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum State {
Free,
InFlight,
Scanning,
PendingRelease,
}
#[derive(Debug, Clone, Copy)]
struct Slot {
state: State,
last_present: u64,
}
#[derive(Debug)]
pub struct BufferRing {
slots: Vec<Slot>,
damage: VecDeque<Vec<Rect>>,
present_seq: u64,
base_seq: u64,
max_slots: usize,
max_damage_rects: usize,
}
impl BufferRing {
pub const DEFAULT_MAX_SLOTS: usize = 3;
pub const DEFAULT_MAX_DAMAGE_RECTS: usize = 16;
#[must_use]
pub fn new(max_slots: usize, max_damage_rects: usize) -> Self {
Self {
slots: Vec::new(),
damage: VecDeque::new(),
present_seq: 0,
base_seq: 0,
max_slots: max_slots.max(1),
max_damage_rects,
}
}
#[must_use]
pub fn len(&self) -> usize {
self.slots.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.slots.is_empty()
}
#[must_use]
pub const fn max_slots(&self) -> usize {
self.max_slots
}
#[must_use]
pub fn in_flight(&self) -> usize {
self.slots
.iter()
.filter(|slot| slot.state == State::InFlight)
.count()
}
#[must_use]
pub fn age(&self, slot: usize) -> u32 {
match self.slots.get(slot) {
Some(s) if s.last_present != 0 => {
u32::try_from(self.present_seq - s.last_present).unwrap_or(u32::MAX)
}
_ => 0,
}
}
pub fn acquire(&mut self) -> Option<Lease> {
let best = self
.slots
.iter()
.enumerate()
.filter(|(_, slot)| slot.state == State::Free)
.max_by_key(|(_, slot)| slot.last_present)
.map(|(index, _)| index);
if let Some(index) = best {
self.slots[index].state = State::InFlight;
return Some(Lease {
slot: index,
fresh: false,
repaint: self.repaint_for(self.slots[index].last_present),
});
}
if self.slots.len() < self.max_slots {
let index = self.slots.len();
self.slots.push(Slot {
state: State::InFlight,
last_present: 0,
});
return Some(Lease {
slot: index,
fresh: true,
repaint: Repaint::Full,
});
}
None
}
pub fn present(&mut self, slot: usize, frame_damage: &[Rect]) {
if slot >= self.slots.len() {
return;
}
for s in &mut self.slots {
if s.state == State::Scanning {
s.state = State::PendingRelease;
}
}
self.present_seq += 1;
self.damage.push_back(frame_damage.to_vec());
while self.damage.len() > self.max_slots {
self.damage.pop_front();
self.base_seq += 1;
}
self.slots[slot].state = State::Scanning;
self.slots[slot].last_present = self.present_seq;
}
pub fn release(&mut self, slot: usize) {
if let Some(s) = self.slots.get_mut(slot)
&& s.state != State::Scanning
{
s.state = State::Free;
}
}
fn repaint_for(&self, last_present: u64) -> Repaint {
if last_present == 0 || last_present < self.base_seq {
return Repaint::Full;
}
let from = usize::try_from(last_present - self.base_seq).unwrap_or(usize::MAX);
let mut region = Vec::new();
for frame in self.damage.iter().skip(from) {
for rect in frame {
region.push(*rect);
if region.len() > self.max_damage_rects {
return Repaint::Full;
}
}
}
Repaint::Region(region)
}
}
impl Default for BufferRing {
fn default() -> Self {
Self::new(Self::DEFAULT_MAX_SLOTS, Self::DEFAULT_MAX_DAMAGE_RECTS)
}
}