From ec3bc0d2a134443fdbb3f326a0da760e45f083af Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Tue, 27 Oct 2026 22:14:35 +0100 Subject: [PATCH] feat(snake): 8 steps/s on fixed-period clock (draw time no longer adds to step) --- crates/apps/snake/src/main.rs | 41 +++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/crates/apps/snake/src/main.rs b/crates/apps/snake/src/main.rs index d573faf..ea79139 100644 --- a/crates/apps/snake/src/main.rs +++ b/crates/apps/snake/src/main.rs @@ -13,7 +13,9 @@ use libkos::syscall; use server_framework::Encode; use display_server::protocol::DisplayRequest; -const TICK_DELAY: u64 = 5_000_000; +/// Steps a second; QEMU virt's timebase is 10 MHz. +const STEPS_PER_SECOND: u64 = 8; +const TICK_DELAY: u64 = 10_000_000 / STEPS_PER_SECOND; const MAX_RENDER_CMDS: usize = 140; /// The display server, which the shell hands over because snake's manifest @@ -53,6 +55,7 @@ fn main() { }; // Takes focus; keys are polled every tick, the signal only wakes it. let _ = libkos::display::subscribe(display_ep(), alarm, types::badge::Badge::new(1 << 1)); + let mut clock = Clock::new(alarm); let mut snake = Snake::new(); let mut commands = [RenderCommand { x: 0, y: 0, w: 0, h: 0, color: 0 }; MAX_RENDER_CMDS]; @@ -71,12 +74,12 @@ fn main() { flush(); // Any key leaves. while next_input() == Input::None { - wait_tick(alarm); + clock.wait_tick(); } return; } - wait_tick(alarm); + clock.wait_tick(); if handle_input(&mut snake) == Input::Quit { return; } @@ -126,17 +129,31 @@ fn handle_input(snake: &mut Snake) -> Input { } } -/// Sleeps until the next tick on `alarm`, instead of spinning. Keys -/// signal it too (snake has one notification), so it waits again until -/// the tick is really due. -fn wait_tick(alarm: CapSlot) { - let deadline = syscall::get_time().raw() + TICK_DELAY; - if syscall::set_timer(alarm, Ticks::from_raw(deadline)).is_err() { - return; +/// The game's clock: a step every `TICK_DELAY`, counted from the last +/// step's deadline, so the time drawing takes does not slow it down. +struct Clock { + alarm: CapSlot, + next: u64, +} + +impl Clock { + fn new(alarm: CapSlot) -> Self { + Self { alarm, next: syscall::get_time().raw() } } - while syscall::get_time().raw() < deadline { - if syscall::wait(alarm).is_err() { + + /// Sleeps until the next step is due, instead of spinning. Keys + /// signal `alarm` too (snake has one notification), so it waits again + /// until the step really is due. + fn wait_tick(&mut self) { + // After a stall, carry on from now rather than rushing to catch up. + self.next = (self.next + TICK_DELAY).max(syscall::get_time().raw()); + if syscall::set_timer(self.alarm, Ticks::from_raw(self.next)).is_err() { return; } + while syscall::get_time().raw() < self.next { + if syscall::wait(self.alarm).is_err() { + return; + } + } } }