use serde::Serialize;
pub const MAX_PENDING: usize = 16;
#[derive(Debug, Clone, Copy)]
pub struct PendingVerification {
pub task_id: u32,
pub completed_on_tick: u64,
active: bool,
}
impl Default for PendingVerification {
fn default() -> Self {
Self {
task_id: 0,
completed_on_tick: 0,
active: false,
}
}
}
#[derive(Debug, Clone, Copy, Serialize)]
pub struct VerificationResult {
pub task_id: u32,
pub completed_on_tick: u64,
pub rendered_tick: u64,
pub latency_ticks: u64,
}
pub struct DeferredPoller {
pending: [PendingVerification; MAX_PENDING],
count: usize,
}
impl DeferredPoller {
pub fn new() -> Self {
Self {
pending: [PendingVerification::default(); MAX_PENDING],
count: 0,
}
}
pub fn enqueue(&mut self, task_id: u32, completed_on_tick: u64) -> bool {
if self.count >= MAX_PENDING {
return false;
}
for slot in &mut self.pending {
if !slot.active {
slot.task_id = task_id;
slot.completed_on_tick = completed_on_tick;
slot.active = true;
self.count += 1;
return true;
}
}
false
}
pub fn on_frame_rendered(
&mut self,
rendered_tick: u64,
) -> ([VerificationResult; MAX_PENDING], usize) {
let mut results = [VerificationResult {
task_id: 0,
completed_on_tick: 0,
rendered_tick: 0,
latency_ticks: 0,
}; MAX_PENDING];
let mut fired = 0usize;
for slot in &mut self.pending {
if slot.active && rendered_tick >= slot.completed_on_tick {
results[fired] = VerificationResult {
task_id: slot.task_id,
completed_on_tick: slot.completed_on_tick,
rendered_tick,
latency_ticks: rendered_tick - slot.completed_on_tick,
};
fired += 1;
slot.active = false;
self.count -= 1;
}
}
(results, fired)
}
pub fn pending_count(&self) -> usize {
self.count
}
pub fn is_empty(&self) -> bool {
self.count == 0
}
pub fn clear(&mut self) {
for slot in &mut self.pending {
slot.active = false;
}
self.count = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn enqueue_and_fire_basic() {
let mut poller = DeferredPoller::new();
assert!(poller.is_empty());
assert!(poller.enqueue(42, 100));
assert_eq!(poller.pending_count(), 1);
let (_, fired) = poller.on_frame_rendered(99);
assert_eq!(fired, 0);
assert_eq!(poller.pending_count(), 1);
let (results, fired) = poller.on_frame_rendered(100);
assert_eq!(fired, 1);
assert_eq!(results[0].task_id, 42);
assert_eq!(results[0].completed_on_tick, 100);
assert_eq!(results[0].latency_ticks, 0);
assert!(poller.is_empty());
}
#[test]
fn fires_when_gpu_ahead() {
let mut poller = DeferredPoller::new();
poller.enqueue(1, 50);
let (results, fired) = poller.on_frame_rendered(200);
assert_eq!(fired, 1);
assert_eq!(results[0].latency_ticks, 150);
}
#[test]
fn multiple_pending_different_ticks() {
let mut poller = DeferredPoller::new();
poller.enqueue(1, 100);
poller.enqueue(2, 150);
poller.enqueue(3, 200);
let (_, fired) = poller.on_frame_rendered(150);
assert_eq!(fired, 2);
assert_eq!(poller.pending_count(), 1);
let (results, fired) = poller.on_frame_rendered(200);
assert_eq!(fired, 1);
assert_eq!(results[0].task_id, 3);
assert!(poller.is_empty());
}
#[test]
fn queue_full_returns_false() {
let mut poller = DeferredPoller::new();
for i in 0..MAX_PENDING {
assert!(poller.enqueue(i as u32, i as u64 * 10));
}
assert!(!poller.enqueue(999, 999));
}
#[test]
fn clear_resets_all() {
let mut poller = DeferredPoller::new();
poller.enqueue(1, 100);
poller.enqueue(2, 200);
poller.clear();
assert!(poller.is_empty());
let (_, fired) = poller.on_frame_rendered(1000);
assert_eq!(fired, 0);
}
#[test]
fn slot_reuse_after_fire() {
let mut poller = DeferredPoller::new();
for i in 0..MAX_PENDING {
poller.enqueue(i as u32, 10);
}
let (_, fired) = poller.on_frame_rendered(10);
assert_eq!(fired, MAX_PENDING);
assert!(poller.enqueue(99, 20));
assert_eq!(poller.pending_count(), 1);
}
proptest! {
#[test]
fn prop_all_enqueued_eventually_fire(
tasks in proptest::collection::vec((0u32..1000, 0u64..10000), 1..=MAX_PENDING)
) {
let mut poller = DeferredPoller::new();
let mut max_tick = 0u64;
for &(task_id, tick) in &tasks {
poller.enqueue(task_id, tick);
max_tick = max_tick.max(tick);
}
let (_, fired) = poller.on_frame_rendered(max_tick);
prop_assert_eq!(fired, tasks.len());
prop_assert!(poller.is_empty());
}
#[test]
fn prop_latency_is_correct(
completed_tick in 0u64..10000,
rendered_tick in 0u64..20000,
) {
if rendered_tick < completed_tick { return Ok(()); }
let mut poller = DeferredPoller::new();
poller.enqueue(1, completed_tick);
let (results, fired) = poller.on_frame_rendered(rendered_tick);
prop_assert_eq!(fired, 1);
prop_assert_eq!(results[0].latency_ticks, rendered_tick - completed_tick);
}
#[test]
fn prop_no_fire_before_tick(completed_tick in 1u64..10000) {
let mut poller = DeferredPoller::new();
poller.enqueue(1, completed_tick);
let (_, fired) = poller.on_frame_rendered(completed_tick - 1);
prop_assert_eq!(fired, 0);
prop_assert_eq!(poller.pending_count(), 1);
}
}
}