feat(snake): 8 steps/s on fixed-period clock (draw time no longer adds to step)

This commit is contained in:
2026-10-27 22:14:35 +01:00
parent c8056ddac6
commit ec3bc0d2a1

View File

@@ -13,7 +13,9 @@ use libkos::syscall;
use server_framework::Encode; use server_framework::Encode;
use display_server::protocol::DisplayRequest; 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; const MAX_RENDER_CMDS: usize = 140;
/// The display server, which the shell hands over because snake's manifest /// 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. // 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 _ = 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 snake = Snake::new();
let mut commands = [RenderCommand { x: 0, y: 0, w: 0, h: 0, color: 0 }; MAX_RENDER_CMDS]; let mut commands = [RenderCommand { x: 0, y: 0, w: 0, h: 0, color: 0 }; MAX_RENDER_CMDS];
@@ -71,12 +74,12 @@ fn main() {
flush(); flush();
// Any key leaves. // Any key leaves.
while next_input() == Input::None { while next_input() == Input::None {
wait_tick(alarm); clock.wait_tick();
} }
return; return;
} }
wait_tick(alarm); clock.wait_tick();
if handle_input(&mut snake) == Input::Quit { if handle_input(&mut snake) == Input::Quit {
return; return;
} }
@@ -126,17 +129,31 @@ fn handle_input(snake: &mut Snake) -> Input {
} }
} }
/// Sleeps until the next tick on `alarm`, instead of spinning. Keys /// The game's clock: a step every `TICK_DELAY`, counted from the last
/// signal it too (snake has one notification), so it waits again until /// step's deadline, so the time drawing takes does not slow it down.
/// the tick is really due. struct Clock {
fn wait_tick(alarm: CapSlot) { alarm: CapSlot,
let deadline = syscall::get_time().raw() + TICK_DELAY; next: u64,
if syscall::set_timer(alarm, Ticks::from_raw(deadline)).is_err() { }
impl Clock {
fn new(alarm: CapSlot) -> Self {
Self { alarm, next: syscall::get_time().raw() }
}
/// 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; return;
} }
while syscall::get_time().raw() < deadline { while syscall::get_time().raw() < self.next {
if syscall::wait(alarm).is_err() { if syscall::wait(self.alarm).is_err() {
return; return;
} }
} }
}
} }