atagen

monolith

public
Star0
Watch0

Commit e3e5f00d41e2

tarball

atagen <boss@atagen.co>2026-10-17 05:02 UTC

1 file changed+75-1
monolith: test frame pacing and stop allocating per budget read

Budget's hold-then-decay and percentile behaviour took several bare-metal rounds to get right and had no coverage. Budget::ns runs several times a frame, so it now sorts on the stack.
Modified file: crates/monolith/src/pacing.rs+75 additions−1 deletions
@@ -53,7 +53,11 @@ impl Budget {
5353 }
5454
5555 pub fn ns(&self) -> u64 {
56- let mut renders: Vec<u64> = self.renders.iter().copied().collect();
56+ let mut buf = [0; Self::KEEP];
57+ let renders = &mut buf[..self.renders.len()];
58+ for (slot, r) in renders.iter_mut().zip(&self.renders) {
59+ *slot = *r;
60+ }
5761 renders.sort_unstable();
5862 let p90 = renders
5963 .get(renders.len().saturating_sub(1) * 9 / 10)
@@ -85,3 +89,73 @@ impl Budget {
8589 self.slack_ns = self.slack_ns.saturating_sub(Self::DECAY_NS).max(floor);
8690 }
8791 }
92+
93+#[cfg(test)]
94+mod tests {
95+ use super::*;
96+
97+ const REFRESH: u64 = 6_060_606;
98+
99+ #[test]
100+ fn one_slow_render_does_not_move_the_budget() {
101+ let mut b = Budget::new();
102+ for _ in 0..31 {
103+ b.record(1_000_000);
104+ }
105+ b.record(9_000_000);
106+ assert_eq!(b.ns(), 1_000_000 + b.slack_ns);
107+ }
108+
109+ #[test]
110+ fn a_miss_holds_its_slack_before_decaying() {
111+ let mut b = Budget::new();
112+ b.missed(REFRESH);
113+ let held = b.slack_ns;
114+ assert_eq!(held, 2_500_000);
115+ let hold = Budget::FLOOR_NS / REFRESH;
116+ for _ in 1..hold {
117+ b.landed();
118+ }
119+ assert_eq!(b.slack_ns, held);
120+ for _ in 0..100 {
121+ b.landed();
122+ }
123+ assert!(b.slack_ns < held);
124+ for _ in 0..1_000_000 {
125+ b.landed();
126+ }
127+ assert_eq!(b.slack_ns, Budget::MIN_SLACK_NS);
128+ }
129+
130+ #[test]
131+ fn misses_never_push_slack_past_half_a_refresh() {
132+ let mut b = Budget::new();
133+ for _ in 0..100 {
134+ b.missed(REFRESH);
135+ }
136+ assert_eq!(b.slack_ns, REFRESH / 2);
137+ }
138+
139+ #[test]
140+ fn deadline_picks_the_first_vblank_still_reachable() {
141+ let last = 1_000_000_000;
142+ let (vblank, start) = deadline(last, REFRESH, 2_000_000, last + 1).unwrap();
143+ assert_eq!(
144+ (vblank, start),
145+ (last + REFRESH, last + REFRESH - 2_000_000)
146+ );
147+ let late = last + REFRESH - 1_000_000;
148+ assert_eq!(
149+ deadline(last, REFRESH, 2_000_000, late).unwrap().0,
150+ last + 2 * REFRESH
151+ );
152+ let stale = last + 10 * REFRESH + 1;
153+ assert_eq!(
154+ deadline(last, REFRESH, 0, stale).unwrap().0,
155+ last + 11 * REFRESH
156+ );
157+ let (vblank, start) = deadline(last, REFRESH, 3 * REFRESH, last).unwrap();
158+ assert_eq!(vblank - start, REFRESH);
159+ assert_eq!(deadline(last, 0, 0, last), None);
160+ }
161+}