use std::sync::{
atomic::{AtomicBool, AtomicU64, Ordering},
Arc,
};
#[derive(Clone, Default)]
pub struct LiveTurnHandle {
active: Arc<AtomicBool>,
generation: Arc<AtomicU64>,
}
impl LiveTurnHandle {
pub fn begin(&self) -> LiveTurnGuard {
let generation = self.generation.fetch_add(1, Ordering::SeqCst) + 1;
self.active.store(true, Ordering::SeqCst);
LiveTurnGuard {
handle: self.clone(),
generation,
}
}
pub fn current_generation(&self) -> u64 {
self.generation.load(Ordering::SeqCst)
}
pub fn is_active(&self) -> bool {
self.active.load(Ordering::SeqCst)
}
}
pub struct LiveTurnGuard {
handle: LiveTurnHandle,
generation: u64,
}
impl LiveTurnGuard {
pub fn generation(&self) -> u64 {
self.generation
}
pub fn is_current(&self) -> bool {
self.handle.current_generation() == self.generation
}
}
impl Drop for LiveTurnGuard {
fn drop(&mut self) {
if self.is_current() {
self.handle.active.store(false, Ordering::SeqCst);
if !self.is_current() {
self.handle.active.store(true, Ordering::SeqCst);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stale_guard_drop_does_not_clear_newer_turn() {
let handle = LiveTurnHandle::default();
let old = handle.begin();
let new = handle.begin();
assert!(!old.is_current());
assert!(new.is_current());
drop(old);
assert!(handle.is_active(), "stale drop must not idle a live turn");
drop(new);
assert!(!handle.is_active());
}
#[test]
fn generations_are_monotonic_and_latest_drop_clears() {
let handle = LiveTurnHandle::default();
assert_eq!(handle.current_generation(), 0);
let first = handle.begin();
assert_eq!(first.generation(), 1);
drop(first);
assert!(!handle.is_active());
let second = handle.begin();
assert_eq!(second.generation(), 2);
assert!(handle.is_active());
}
}