use std::collections::VecDeque;
use std::time::Duration;
use crate::manager::Frame;
use plaza_wire::frame;
use tokio::time::Instant;
pub const RETRANSMIT_PENALTY: Duration = Duration::from_millis(200);
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum Delivery {
#[default]
Reliable,
Datagram,
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct DirectionProfile {
pub delay: Duration,
pub jitter: Duration,
pub loss: f32,
pub delivery: Delivery,
}
impl DirectionProfile {
pub fn is_passthrough(&self) -> bool {
self.delay.is_zero() && self.jitter.is_zero() && self.loss <= 0.0
}
pub fn delayed(delay: Duration) -> Self {
Self {
delay,
..Self::default()
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct LinkProfile {
pub up: DirectionProfile,
pub down: DirectionProfile,
}
impl LinkProfile {
pub fn symmetric(profile: DirectionProfile) -> Self {
Self {
up: profile,
down: profile,
}
}
pub fn is_passthrough(&self) -> bool {
self.up.is_passthrough() && self.down.is_passthrough()
}
}
pub type LinkSink = std::sync::Arc<dyn Fn(LinkProfile) + Send + Sync>;
pub struct LinkPublisher {
sink: LinkSink,
published: parking_lot::Mutex<Option<LinkProfile>>,
}
impl LinkPublisher {
pub fn new(sink: LinkSink) -> Self {
Self {
sink,
published: parking_lot::Mutex::new(None),
}
}
pub fn publish(&self, profile: LinkProfile) -> bool {
let mut published = self.published.lock();
if *published == Some(profile) {
return false;
}
*published = Some(profile);
drop(published);
(self.sink)(profile);
true
}
}
struct XorShift64(u64);
impl XorShift64 {
fn new(seed: u64) -> Self {
let mut z = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
Self((z ^ (z >> 31)) | 1)
}
fn next_u64(&mut self) -> u64 {
let mut x = self.0;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.0 = x;
x
}
fn next_f32(&mut self) -> f32 {
(self.next_u64() >> 40) as f32 / (1u32 << 24) as f32
}
}
pub struct Conditioner {
queue: VecDeque<(Instant, Frame)>,
last_release: Option<Instant>,
rng: XorShift64,
capacity: usize,
}
impl Conditioner {
pub fn new(seed: u64, capacity: usize) -> Self {
Self {
queue: VecDeque::new(),
last_release: None,
rng: XorShift64::new(seed),
capacity,
}
}
pub fn is_empty(&self) -> bool {
self.queue.is_empty()
}
pub fn push(&mut self, frame_bytes: Frame, profile: &DirectionProfile, now: Instant) -> bool {
if self.queue.len() >= self.capacity && frame_bytes.first() == Some(&frame::Kind::Ops.as_byte()) {
return false;
}
let lost = profile.loss > 0.0 && self.rng.next_f32() < profile.loss;
let retransmit = match (lost, profile.delivery) {
(false, _) => Duration::ZERO,
(true, Delivery::Reliable) => RETRANSMIT_PENALTY,
(true, Delivery::Datagram) => return false,
};
let jitter = profile.jitter.mul_f32(self.rng.next_f32());
let earliest = now + profile.delay + jitter + retransmit;
let release = match self.last_release {
Some(previous) if previous > earliest => previous,
_ => earliest,
};
self.last_release = Some(release);
self.queue.push_back((release, frame_bytes));
true
}
pub fn next_release(&self) -> Option<Instant> {
self.queue.front().map(|(at, _)| *at)
}
pub fn pop_ready(&mut self, now: Instant) -> Option<Frame> {
match self.queue.front() {
Some((at, _)) if *at <= now => self.queue.pop_front().map(|(_, frame_bytes)| frame_bytes),
_ => None,
}
}
pub fn drain(&mut self) -> Vec<Frame> {
self.last_release = None;
self.queue.drain(..).map(|(_, frame_bytes)| frame_bytes).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::manager::DEFAULT_CONDITIONER_CAPACITY as CAP;
fn ops_frame(n: u8) -> Frame {
Frame::from(vec![frame::Kind::Ops.as_byte(), n])
}
fn ping_frame() -> Frame {
Frame::from(vec![frame::Kind::Ping.as_byte()])
}
#[test]
fn a_passthrough_profile_is_the_default() {
assert!(DirectionProfile::default().is_passthrough());
assert!(LinkProfile::default().is_passthrough());
}
#[tokio::test]
async fn jitter_stalls_the_queue_instead_of_reordering_it() {
let mut c = Conditioner::new(1, CAP);
let profile = DirectionProfile {
delay: Duration::from_millis(10),
jitter: Duration::from_millis(200),
..DirectionProfile::default()
};
let now = Instant::now();
for n in 0..32 {
assert!(c.push(ops_frame(n), &profile, now));
}
let mut releases = Vec::new();
let mut seen = Vec::new();
while let Some(at) = c.next_release() {
releases.push(at);
seen.push(c.pop_ready(at).expect("due at its own release time")[1]);
}
assert_eq!(seen, (0..32).collect::<Vec<_>>(), "arrival order survives jitter");
assert!(
releases.windows(2).all(|w| w[0] <= w[1]),
"a delayed frame holds up the ones behind it"
);
}
#[tokio::test]
async fn a_frame_is_not_released_before_its_time() {
let mut c = Conditioner::new(2, CAP);
let now = Instant::now();
c.push(ops_frame(1), &DirectionProfile::delayed(Duration::from_millis(50)), now);
assert!(c.pop_ready(now).is_none(), "not due yet");
assert!(c.pop_ready(now + Duration::from_millis(49)).is_none());
assert!(c.pop_ready(now + Duration::from_millis(50)).is_some());
}
#[tokio::test]
async fn a_reliable_link_pays_for_a_loss_in_time_rather_than_in_frames() {
let mut c = Conditioner::new(3, CAP);
let certain_loss = DirectionProfile {
loss: 1.0,
..DirectionProfile::default()
};
let now = Instant::now();
for n in 0..16 {
assert!(c.push(ops_frame(n), &certain_loss, now), "no frame is deleted");
}
assert!(c.push(ping_frame(), &certain_loss, now));
assert_eq!(
c.next_release(),
Some(now + RETRANSMIT_PENALTY),
"the first frame waits out one retransmission"
);
let mut seen = 0;
while c.pop_ready(now + RETRANSMIT_PENALTY * 2).is_some() {
seen += 1;
}
assert_eq!(seen, 17, "and every one of them still arrives");
}
#[tokio::test]
async fn a_datagram_link_loses_frames_outright() {
let mut c = Conditioner::new(3, CAP);
let certain_loss = DirectionProfile {
loss: 1.0,
delivery: Delivery::Datagram,
..DirectionProfile::default()
};
let now = Instant::now();
for n in 0..16 {
assert!(!c.push(ops_frame(n), &certain_loss, now), "gone, not delayed");
}
assert!(!c.push(ping_frame(), &certain_loss, now), "a probe is not exempt either");
assert!(c.is_empty());
}
#[tokio::test]
async fn a_full_queue_still_admits_control_frames() {
let mut c = Conditioner::new(4, CAP);
let profile = DirectionProfile::delayed(Duration::from_secs(1));
let now = Instant::now();
for _ in 0..crate::manager::DEFAULT_CONDITIONER_CAPACITY {
assert!(c.push(ops_frame(0), &profile, now));
}
assert!(!c.push(ops_frame(0), &profile, now), "the buffer is finite");
assert!(c.push(ping_frame(), &profile, now), "a probe is not what overflows it");
}
#[tokio::test]
async fn a_configured_capacity_is_what_fills() {
let mut c = Conditioner::new(4, 3);
let profile = DirectionProfile::delayed(Duration::from_secs(1));
let now = Instant::now();
for _ in 0..3 {
assert!(c.push(ops_frame(0), &profile, now));
}
assert!(!c.push(ops_frame(0), &profile, now));
}
#[tokio::test]
async fn an_empty_queue_has_nothing_to_wait_for() {
let mut c = Conditioner::new(5, CAP);
assert!(c.next_release().is_none());
assert!(c.pop_ready(Instant::now()).is_none());
}
}