use std::
{
time::{ Duration, Instant },
sync::atomic::
{
AtomicBool,
AtomicU32,
AtomicU64,
Ordering,
},
};
use tokio::net::TcpStream;
use crate::network::screen::
{
self,
consts,
};
static SENT: AtomicU64 = AtomicU64::new(0); static BUSY: AtomicU64 = AtomicU64::new(0); static BITRATE: AtomicU32 = AtomicU32::new(0); static DELAY: AtomicU64 = AtomicU64::new(u64::MAX); static REMOTE: AtomicU64 = AtomicU64::new(u64::MAX); static SHED: AtomicBool = AtomicBool::new(false);
pub struct RateControl {
window: Instant,
held: Instant, ceiling: f64, congested: bool, }
impl RateControl
{
pub fn new() -> Self
{
Self { window: Instant::now(), held: Instant::now(), ceiling: f64::INFINITY, congested: false }
}
pub fn bitrate(&self) -> u32
{
BITRATE.load(Ordering::Relaxed)
}
pub fn update(&mut self) -> Option<u32> {
let elapsed = self.window.elapsed();
if elapsed < consts::RATE_WINDOW { return None; }
self.window = Instant::now();
let sent = SENT.swap(0, Ordering::Relaxed) as f64 * 8.0;
let busy = Duration::from_micros(BUSY.swap(0, Ordering::Relaxed)).as_secs_f64() / elapsed.as_secs_f64();
let delay = Duration::from_micros(take_min(&DELAY).max(take_min(&REMOTE)));
let shed = SHED.swap(false, Ordering::Relaxed);
let current = f64::from(self.bitrate());
let throughput = sent / elapsed.as_secs_f64();
let carried = throughput.min(current);
let next = if busy >= consts::RATE_SATURATED || delay >= consts::RATE_DELAY || shed
{
if !self.congested { self.ceiling = current; }
if busy >= consts::RATE_SATURATED { self.ceiling = self.ceiling.min(throughput); }
self.congested = true;
self.held = Instant::now() + consts::RATE_HOLD;
carried * consts::RATE_BACKOFF
} else if busy <= consts::RATE_IDLE && delay < consts::RATE_DELAY / 2 && Instant::now() >= self.held && throughput >= current * consts::RATE_USED
{
self.congested = false;
if current < self.ceiling * consts::RATE_NEAR
{
let resume = if self.ceiling.is_finite() { self.ceiling * consts::RATE_BACKOFF } else { 0.0 };
(current * consts::RATE_GROWTH).max(resume)
} else
{
current * consts::RATE_CREEP
}
} else
{
current
};
let next = (next as u32).clamp(consts::MIN_BITRATE, consts::H264_BITRATE);
if next == self.bitrate() { return None; }
BITRATE.store(next, Ordering::Relaxed);
Some(next)
}
}
pub fn start() {
SENT.store(0, Ordering::Relaxed);
BUSY.store(0, Ordering::Relaxed);
DELAY.store(u64::MAX, Ordering::Relaxed);
REMOTE.store(u64::MAX, Ordering::Relaxed);
SHED.store(false, Ordering::Relaxed);
BITRATE.store(consts::START_BITRATE, Ordering::Relaxed);
}
pub fn record(bytes: usize, busy: Duration) {
SENT.fetch_add(bytes as u64, Ordering::Relaxed);
BUSY.fetch_add(busy.as_micros() as u64, Ordering::Relaxed);
}
pub fn probe(stream: &TcpStream) {
let Some((unsent, inflated)) = screen::tcp_backlog(stream) else { return };
DELAY.fetch_min(at_target(unsent) + inflated.as_micros() as u64, Ordering::Relaxed);
}
pub fn report(delay: u32, unsent: u32, shed: bool) {
REMOTE.fetch_min(u64::from(delay) + at_target(u64::from(unsent)), Ordering::Relaxed);
if shed { SHED.store(true, Ordering::Relaxed); }
}
fn at_target(bytes: u64) -> u64 {
bytes * 8_000_000 / u64::from(BITRATE.load(Ordering::Relaxed).max(1))
}
fn take_min(slot: &AtomicU64) -> u64 {
match slot.swap(u64::MAX, Ordering::Relaxed)
{
u64::MAX => 0,
micros => micros,
}
}