use super::{LaneSlot, PoolConfig, PoolEvent};
use crate::{CancelToken, Merger, RateModel, SampleCursor, SampleRange};
use indicatrix_net::SceneState;
use std::{
sync::{Condvar, Mutex, MutexGuard, PoisonError},
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 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 [LaneSlot],
}
impl Epoch<'_> {
pub(super) fn emit(&self, event: PoolEvent) {
(self.events)(event);
}
pub(super) fn want(&self, rate: &Mutex<RateModel>) -> u32 {
let current = {
let model = rate.lock().unwrap_or_else(PoisonError::into_inner);
if !model.is_calibrated() {
return self.config.policy.first_chunk_samples();
}
model.rate()
};
let remaining = self.target.saturating_sub(self.merger.total());
self.config
.policy
.tail_aware_samples(current, remaining, self.sum_rates())
}
fn sum_rates(&self) -> f64 {
self.lanes
.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));
}
}
}