use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Mutex, MutexGuard, PoisonError};
pub struct ReadbackIndices {
lock: Mutex<()>,
read_revision: AtomicU64,
next_write_revision: AtomicU64,
write_revision: AtomicU64,
last_completed_write: AtomicU64,
}
impl ReadbackIndices {
pub fn new() -> Self {
Self {
lock: Mutex::new(()),
read_revision: AtomicU64::new(0),
next_write_revision: AtomicU64::new(1),
write_revision: AtomicU64::new(0),
last_completed_write: AtomicU64::new(0),
}
}
pub fn lock(&self) -> MutexGuard<'_, ()> {
self.lock.lock().unwrap_or_else(PoisonError::into_inner)
}
pub fn read_revision(&self) -> u64 {
self.read_revision.load(Ordering::SeqCst)
}
pub fn write_revision(&self) -> u64 {
self.write_revision.load(Ordering::SeqCst)
}
pub fn last_completed_write(&self) -> u64 {
self.last_completed_write.load(Ordering::SeqCst)
}
pub fn next_read(&self) {
let _guard = self.lock();
self.read_revision.store(self.last_completed_write.load(Ordering::SeqCst), Ordering::SeqCst);
}
pub fn begin_write(&self) -> ReadbackWriteScope<'_> {
let guard = self.lock();
let revision = self.next_write_revision.fetch_add(1, Ordering::SeqCst);
self.write_revision.store(revision, Ordering::SeqCst);
ReadbackWriteScope { indices: self, _guard: guard }
}
}
impl Default for ReadbackIndices {
fn default() -> Self {
Self::new()
}
}
#[must_use = "the write ends as soon as the scope is dropped"]
pub struct ReadbackWriteScope<'a> {
indices: &'a ReadbackIndices,
_guard: MutexGuard<'a, ()>,
}
impl ReadbackWriteScope<'_> {
pub fn end_write(self) {}
}
impl Drop for ReadbackWriteScope<'_> {
fn drop(&mut self) {
self.indices
.last_completed_write
.store(self.indices.write_revision.load(Ordering::SeqCst), Ordering::SeqCst);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
fn assert_send_sync<T: Send + Sync>() {}
#[test]
fn is_send_and_sync() {
assert_send_sync::<ReadbackIndices>();
}
#[test]
fn initial_state() {
let indices = ReadbackIndices::new();
assert_eq!(0, indices.read_revision());
assert_eq!(0, indices.write_revision());
assert_eq!(0, indices.last_completed_write());
indices.next_read();
assert_eq!(0, indices.read_revision());
}
#[test]
fn write_revisions_advance_and_complete_on_end_write() {
let indices = ReadbackIndices::new();
let write = indices.begin_write();
assert_eq!(1, indices.write_revision());
assert_eq!(0, indices.last_completed_write());
write.end_write();
assert_eq!(1, indices.last_completed_write());
assert_eq!(0, indices.read_revision());
indices.next_read();
assert_eq!(1, indices.read_revision());
indices.begin_write().end_write();
indices.begin_write().end_write();
assert_eq!(3, indices.write_revision());
assert_eq!(3, indices.last_completed_write());
assert_eq!(1, indices.read_revision());
indices.next_read();
assert_eq!(3, indices.read_revision());
}
#[test]
fn dropping_the_scope_ends_the_write() {
let indices = ReadbackIndices::new();
{
let _write = indices.begin_write();
assert_eq!(0, indices.last_completed_write());
}
assert_eq!(1, indices.last_completed_write());
drop(indices.lock());
}
#[test]
fn the_lock_is_held_during_a_write() {
let indices = ReadbackIndices::new();
let write = indices.begin_write();
assert!(indices.lock.try_lock().is_err());
write.end_write();
assert!(indices.lock.try_lock().is_ok());
}
#[test]
fn next_read_waits_for_a_write_in_progress() {
let indices = Arc::new(ReadbackIndices::new());
let (started_tx, started_rx) = std::sync::mpsc::channel();
let (finish_tx, finish_rx) = std::sync::mpsc::channel::<()>();
let writer = {
let indices = indices.clone();
std::thread::spawn(move || {
let write = indices.begin_write();
started_tx.send(()).unwrap();
finish_rx.recv().unwrap();
write.end_write();
})
};
started_rx.recv().unwrap();
assert_eq!(1, indices.write_revision());
assert_eq!(0, indices.last_completed_write());
let reader = {
let indices = indices.clone();
std::thread::spawn(move || {
indices.next_read();
indices.read_revision()
})
};
finish_tx.send(()).unwrap();
writer.join().unwrap();
assert_eq!(1, reader.join().unwrap());
}
}