use super::{LaneSlot, PoolConfig, PoolEvent};
use crate::{CancelToken, Merger, RateModel, SampleCursor, SampleRange};
use indicatrix_net::SceneState;
use std::{
sync::{Arc, Condvar, Mutex, MutexGuard, PoisonError, RwLock},
time::{Duration, Instant},
};
const POLL: Duration = Duration::from_millis(25);
pub(super) struct Sched {
in_flight: Mutex<u32>,
wake: Condvar,
}
impl Sched {
pub(super) const fn new() -> Self {
Self {
in_flight: Mutex::new(0),
wake: Condvar::new(),
}
}
fn lock(&self) -> MutexGuard<'_, u32> {
self.in_flight
.lock()
.unwrap_or_else(PoisonError::into_inner)
}
fn wait<'g>(&self, guard: MutexGuard<'g, u32>, timeout: Duration) -> MutexGuard<'g, u32> {
self.wake
.wait_timeout(guard, timeout)
.unwrap_or_else(PoisonError::into_inner)
.0
}
}
pub(super) struct Roster {
state: Mutex<RosterState>,
}
struct RosterState {
active: usize,
closed: bool,
}
impl Roster {
pub(super) const fn new(active: usize) -> Self {
Self {
state: Mutex::new(RosterState {
active,
closed: active == 0,
}),
}
}
fn lock(&self) -> MutexGuard<'_, RosterState> {
self.state.lock().unwrap_or_else(PoisonError::into_inner)
}
pub(super) fn is_closed(&self) -> bool {
self.lock().closed
}
pub(super) fn try_join(&self) -> bool {
let mut state = self.lock();
if state.closed {
return false;
}
state.active += 1;
true
}
pub(super) fn leave(&self) {
let mut state = self.lock();
state.active = state.active.saturating_sub(1);
if state.active == 0 {
state.closed = true;
}
}
pub(super) fn leave_unless_last(&self) -> bool {
let mut state = self.lock();
let leave = state.active > 1;
state.active -= usize::from(leave);
drop(state);
leave
}
}
pub(super) struct Claim<'e> {
epoch: &'e Epoch<'e>,
range: SampleRange,
settled: bool,
}
impl Claim<'_> {
pub(super) const fn range(&self) -> SampleRange {
self.range
}
pub(super) fn settle(mut self, done: u32) {
self.settled = true;
self.epoch.release(self.range, done);
}
}
impl Drop for Claim<'_> {
fn drop(&mut self) {
if !self.settled {
self.epoch.release(self.range, 0);
}
}
}
pub(super) struct Epoch<'a> {
pub(super) scene: &'a SceneState,
pub(super) cursor: SampleCursor,
pub(super) merger: &'a Merger,
pub(super) cancel: &'a CancelToken,
pub(super) events: &'a (dyn Fn(PoolEvent) + Sync),
pub(super) config: &'a PoolConfig,
pub(super) target: u32,
pub(super) pixels: usize,
pub(super) sched: Sched,
pub(super) lanes: &'a RwLock<Vec<Arc<LaneSlot>>>,
pub(super) roster: Roster,
}
impl Epoch<'_> {
pub(super) fn lane_count(&self) -> usize {
self.lanes
.read()
.unwrap_or_else(PoisonError::into_inner)
.len()
}
pub(super) fn push_lane(&self, slot: Arc<LaneSlot>) -> usize {
let mut lanes = self.lanes.write().unwrap_or_else(PoisonError::into_inner);
lanes.push(slot);
lanes.len() - 1
}
pub(super) fn emit(&self, event: PoolEvent) {
(self.events)(event);
}
pub(super) fn want(&self, rate: &Mutex<RateModel>) -> u32 {
let (current, scale) = {
let mut model = rate.lock().unwrap_or_else(PoisonError::into_inner);
if !model.is_calibrated() {
return self.config.policy.first_chunk_samples();
}
(model.rate(), model.take_first_chunk_scale())
};
let remaining = self.target.saturating_sub(self.merger.total());
let size = self
.config
.policy
.tail_aware_samples(current, remaining, self.sum_rates());
scale.map_or(size, |scale| self.config.policy.scaled_samples(size, scale))
}
fn sum_rates(&self) -> f64 {
self.lanes
.read()
.unwrap_or_else(PoisonError::into_inner)
.iter()
.map(|slot| {
slot.rate
.lock()
.unwrap_or_else(PoisonError::into_inner)
.rate()
})
.sum()
}
pub(super) fn claim(&self, want: u32) -> Option<Claim<'_>> {
let mut in_flight = self.sched.lock();
loop {
if self.cancel.is_cancelled() {
return None;
}
if let Some((first, count)) = self.cursor.claim_any(want) {
*in_flight += 1;
return Some(Claim {
epoch: self,
range: SampleRange::new(first, count),
settled: false,
});
}
if *in_flight == 0 {
return None;
}
in_flight = self.sched.wait(in_flight, POLL);
}
}
fn release(&self, range: SampleRange, done: u32) {
let tail = range.after_prefix(done);
let mut in_flight = self.sched.lock();
self.cursor.requeue(tail.first_sample, tail.samples);
*in_flight = in_flight.saturating_sub(1);
drop(in_flight);
self.sched.wake.notify_all();
}
pub(super) fn wait_for_frontier(&self, seen: u32) {
let earliest_exit = Instant::now() + POLL;
let mut in_flight = self.sched.lock();
loop {
if self.cancel.is_cancelled() || self.merger.frontier() != seen {
return;
}
if *in_flight == 0 && Instant::now() >= earliest_exit {
return;
}
in_flight = self.sched.wait(in_flight, POLL);
}
}
pub(super) fn pause(&self, total: Duration) -> bool {
let deadline = Instant::now() + total;
let mut in_flight = self.sched.lock();
loop {
if self.cancel.is_cancelled() || (*in_flight == 0 && self.cursor.fully_claimed()) {
return false;
}
let now = Instant::now();
if now >= deadline {
return true;
}
in_flight = self.sched.wait(in_flight, (deadline - now).min(POLL));
}
}
}