Server actually ticks
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
import { assert, assertEquals } from "@std/assert";
|
||||
import { GameLoop, LoopClock } from "$/server/game/game_loop.ts";
|
||||
|
||||
// a clock tests move by hand. ticks can move it too, to pretend they were slow
|
||||
class FakeClock implements LoopClock {
|
||||
time = 0;
|
||||
#timers: { at: number; fn: () => void; id: number }[] = [];
|
||||
#next_id = 1;
|
||||
|
||||
now() {
|
||||
return this.time;
|
||||
}
|
||||
schedule(fn: () => void, ms: number) {
|
||||
const id = this.#next_id++;
|
||||
this.#timers.push({ at: this.time + ms, fn, id });
|
||||
return id;
|
||||
}
|
||||
clear(handle: unknown) {
|
||||
this.#timers = this.#timers.filter((t) => t.id !== handle);
|
||||
}
|
||||
// runs timers in order until `ms` have passed
|
||||
advance(ms: number) {
|
||||
const end = this.time + ms;
|
||||
while (true) {
|
||||
this.#timers.sort((a, b) => a.at - b.at);
|
||||
const timer = this.#timers[0];
|
||||
if (!timer || timer.at > end) break;
|
||||
this.#timers.shift();
|
||||
this.time = Math.max(this.time, timer.at);
|
||||
timer.fn();
|
||||
}
|
||||
this.time = end;
|
||||
}
|
||||
}
|
||||
|
||||
Deno.test("runs 20 ticks a second", () => {
|
||||
const clock = new FakeClock();
|
||||
let ticks = 0;
|
||||
const loop = new GameLoop(() => ticks++, 20, clock);
|
||||
loop.start();
|
||||
clock.advance(10_000);
|
||||
// the first tick runs right away
|
||||
assertEquals(ticks, 201);
|
||||
assertEquals(loop.stats.tps, 20);
|
||||
loop.stop();
|
||||
clock.advance(1000);
|
||||
assertEquals(ticks, 201);
|
||||
});
|
||||
|
||||
Deno.test("a slow tick is made up for, so game time keeps up", () => {
|
||||
const clock = new FakeClock();
|
||||
let ticks = 0;
|
||||
const loop = new GameLoop(
|
||||
() => {
|
||||
ticks++;
|
||||
// tick 10 takes 200 ms, four ticks' worth
|
||||
if (ticks === 10) clock.time += 200;
|
||||
},
|
||||
20,
|
||||
clock,
|
||||
);
|
||||
loop.start();
|
||||
clock.advance(2000);
|
||||
assertEquals(ticks, 41);
|
||||
assertEquals(loop.stats.skipped, 0);
|
||||
});
|
||||
|
||||
Deno.test("falling too far behind skips ticks and warns", () => {
|
||||
const clock = new FakeClock();
|
||||
const warnings: string[] = [];
|
||||
const warn = console.warn;
|
||||
console.warn = (message: string) => warnings.push(message);
|
||||
try {
|
||||
let ticks = 0;
|
||||
const loop = new GameLoop(
|
||||
() => {
|
||||
ticks++;
|
||||
// one 2 second hiccup, forty ticks' worth
|
||||
if (ticks === 5) clock.time += 2000;
|
||||
},
|
||||
20,
|
||||
clock,
|
||||
);
|
||||
loop.start();
|
||||
clock.advance(5000);
|
||||
// it catches up ten, skips the rest and carries on at 20 per second
|
||||
assert(loop.stats.skipped >= 25 && loop.stats.skipped <= 31, `skipped ${loop.stats.skipped}`);
|
||||
assertEquals(loop.stats.tps, 20);
|
||||
assertEquals(warnings.length, 1);
|
||||
assert(warnings[0].startsWith("Can't keep up"), warnings[0]);
|
||||
} finally {
|
||||
console.warn = warn;
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("a tick that throws doesn't stop the loop", () => {
|
||||
const clock = new FakeClock();
|
||||
let ticks = 0;
|
||||
const error = console.error;
|
||||
console.error = () => {};
|
||||
try {
|
||||
const loop = new GameLoop(
|
||||
() => {
|
||||
ticks++;
|
||||
if (ticks === 3) throw new Error("oops");
|
||||
},
|
||||
20,
|
||||
clock,
|
||||
);
|
||||
loop.start();
|
||||
clock.advance(1000);
|
||||
assertEquals(ticks, 21);
|
||||
} finally {
|
||||
console.error = error;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user