use crate::SurfaceStats;
use std::sync::{Arc, Mutex, Weak};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Slot {
Free,
Writing,
Published,
}
#[derive(Debug)]
struct State {
slots: Vec<Slot>,
presents: u64,
would_block: u64,
}
#[derive(Clone, Debug)]
pub(crate) struct Ring(Arc<Mutex<State>>);
#[derive(Debug)]
pub(crate) struct Writing {
ring: Arc<Mutex<State>>,
index: usize,
published: bool,
}
#[derive(Debug)]
pub(crate) struct Lease {
ring: Weak<Mutex<State>>,
index: usize,
}
impl Ring {
pub fn new(slots: usize) -> Self {
Self(Arc::new(Mutex::new(State {
slots: vec![Slot::Free; slots],
presents: 0,
would_block: 0,
})))
}
pub fn acquire(&self, count_block: bool) -> Option<Writing> {
let mut state = self.0.lock().unwrap();
match state.slots.iter().position(|slot| *slot == Slot::Free) {
Some(index) => {
state.slots[index] = Slot::Writing;
Some(Writing {
ring: self.0.clone(),
index,
published: false,
})
}
None => {
if count_block {
state.would_block += 1;
}
None
}
}
}
pub fn publish(&self, mut writing: Writing) -> Arc<Lease> {
assert!(
Arc::ptr_eq(&self.0, &writing.ring),
"slot belongs to another surface"
);
let mut state = self.0.lock().unwrap();
debug_assert_eq!(state.slots[writing.index], Slot::Writing);
state.slots[writing.index] = Slot::Published;
state.presents += 1;
writing.published = true;
Arc::new(Lease {
ring: Arc::downgrade(&self.0),
index: writing.index,
})
}
pub fn stats(&self) -> SurfaceStats {
let state = self.0.lock().unwrap();
let count = |kind| state.slots.iter().filter(|slot| **slot == kind).count();
SurfaceStats {
free: count(Slot::Free),
writing: count(Slot::Writing),
published: count(Slot::Published),
presents: state.presents,
would_block: state.would_block,
waiting: 0,
composited: 0,
unshown: 0,
composite_latency_ms: 0.,
}
}
pub fn owns(&self, writing: &Writing) -> bool {
Arc::ptr_eq(&self.0, &writing.ring)
}
}
impl Writing {
pub fn index(&self) -> usize {
self.index
}
}
impl Drop for Writing {
fn drop(&mut self) {
if !self.published {
let mut state = self.ring.lock().unwrap();
state.slots[self.index] = Slot::Free;
}
}
}
impl Drop for Lease {
fn drop(&mut self) {
if let Some(ring) = self.ring.upgrade() {
let mut state = ring.lock().unwrap();
state.slots[self.index] = Slot::Free;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn acquire_until_full_then_would_block() {
let ring = Ring::new(2);
let a = ring.acquire(true).unwrap();
let b = ring.acquire(true).unwrap();
assert_ne!(a.index(), b.index());
assert!(ring.acquire(true).is_none());
assert_eq!(ring.stats().would_block, 1);
drop(a);
assert!(
ring.acquire(true).is_some(),
"dropped unpublished slot is free"
);
}
#[test]
fn published_slot_returns_only_after_last_lease() {
let ring = Ring::new(2);
let lease = ring.publish(ring.acquire(true).unwrap());
let retained_scene = lease.clone();
let _writing = ring.acquire(true).unwrap();
assert!(
ring.acquire(true).is_none(),
"published slot must not be reused"
);
drop(lease);
assert!(
ring.acquire(true).is_none(),
"a retained scene still holds it"
);
drop(retained_scene);
assert!(ring.acquire(true).is_some());
let stats = ring.stats();
assert_eq!(stats.presents, 1);
}
#[test]
fn lease_outliving_the_ring_is_harmless() {
let ring = Ring::new(2);
let lease = ring.publish(ring.acquire(true).unwrap());
drop(ring);
drop(lease);
}
#[test]
fn lease_drop_from_another_thread() {
let ring = Ring::new(2);
let lease = ring.publish(ring.acquire(true).unwrap());
std::thread::spawn(move || drop(lease)).join().unwrap();
assert_eq!(ring.stats().free, 2);
}
#[test]
#[should_panic(expected = "another surface")]
fn rejects_foreign_slots() {
let a = Ring::new(2);
let b = Ring::new(2);
let writing = b.acquire(true).unwrap();
a.publish(writing);
}
}