use std::fmt::{Debug, Formatter};
use std::sync::Mutex;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use tokio::task::{self, Id};
use super::window::Round;
struct Entry {
task: Id,
round: Round,
bound: Vec<u64>,
}
#[derive(Default)]
pub(crate) struct Rounds {
open: AtomicUsize,
entries: Mutex<Vec<Entry>>,
next_publisher: AtomicU64,
}
impl Debug for Rounds {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Rounds")
.field("open", &self.open.load(Ordering::Relaxed))
.finish_non_exhaustive()
}
}
impl Rounds {
pub(crate) fn publisher(&self) -> u64 {
self.next_publisher.fetch_add(1, Ordering::Relaxed)
}
fn with<R>(&self, apply: impl FnOnce(&mut Vec<Entry>) -> R) -> R {
let mut entries = self.entries.lock().expect("round registry poisoned");
let result = apply(&mut entries);
self.open.store(entries.len(), Ordering::Relaxed);
drop(entries);
result
}
pub(crate) fn enter(&self, round: &Round) {
let Some(current) = task::try_id() else {
return;
};
self.with(|entries| {
if let Some(entry) = entries.iter_mut().find(|entry| entry.round.same(round)) {
entry.task = current;
} else {
entries.push(Entry {
task: current,
round: round.clone(),
bound: Vec::new(),
});
}
});
}
pub(crate) fn bind(&self, round: &Round, publisher: u64) {
let Some(current) = task::try_id() else {
return;
};
self.with(|entries| {
if let Some(entry) = entries.iter_mut().find(|entry| entry.round.same(round)) {
entry.task = current;
if !entry.bound.contains(&publisher) {
entry.bound.push(publisher);
}
} else {
entries.push(Entry {
task: current,
round: round.clone(),
bound: vec![publisher],
});
}
});
}
pub(crate) fn leave(&self, round: &Round) {
if self.open.load(Ordering::Relaxed) == 0 {
return;
}
self.with(|entries| entries.retain(|entry| !entry.round.same(round)));
}
pub(crate) fn joined(&self, publisher: u64, reply: bool) -> Option<Round> {
if self.open.load(Ordering::Relaxed) == 0 {
return None;
}
let current = task::try_id()?;
let entries = self.entries.lock().expect("round registry poisoned");
let round = entries
.iter()
.find(|entry| entry.task == current && (reply || entry.bound.contains(&publisher)))
.map(|entry| entry.round.clone());
drop(entries);
round
}
}