@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
128 lines (97 loc) • 3.03 kB
JavaScript
import { assert } from "../../core/assert.js";
import Signal from "../../core/events/signal/Signal.js";
import Clock from '../Clock.js';
/**
* Simulation tick generator
* Dispatches "tick" every animation frame or "maxDelay" time, whichever happens to be soonest
* This is essentially the timing bus that all the simulation systems subscribe to for their step advancement
*
* @author Alex Goldring
* @copyright Company Named Limited (c) 2025
*/
class Ticker {
/**
* @readonly
* @type {Clock}
*/
clock = new Clock();
/**
* @private
* @type {boolean}
*/
#isRunning = false;
#animationFrameHandle = -1;
#timeoutHandle = -1;
/**
* Dispatches time delta in seconds since last tick
* @readonly
* @type {Signal<number>}
*/
onTick = new Signal();
/**
*
* @constructor
*/
constructor() {
this.clock.pause();
}
#isUpdateLoopActive() {
return this.#timeoutHandle !== -1 && this.#animationFrameHandle !== -1;
}
/**
*
* @param {number} [maxTimeout]
*/
start({ maxTimeout = 100 } = {}) {
assert.isNumber(maxTimeout, 'maxTimeout');
assert.greaterThan(maxTimeout, 0);
if (this.#isUpdateLoopActive()) {
throw new Error("Already running");
}
this.#isRunning = true;
const update = () => {
if (!this.#isRunning) {
return;
}
const delta = this.clock.getDelta();
this.onTick.send1(delta);
}
const timeoutCallback = () => {
cancelAnimationFrame(this.#animationFrameHandle);
animate();
}
const animationFrameCallback = () => {
clearTimeout(this.#timeoutHandle);
//push tick beyond animation frame stack allowing draw to happen
this.#timeoutHandle = setTimeout(animate, 0);
}
const animate = () => {
this.#animationFrameHandle = requestAnimationFrame(animationFrameCallback);
update();
this.#timeoutHandle = setTimeout(timeoutCallback, maxTimeout);
}
this.clock.getDelta(); //purge delta
this.clock.start();
requestAnimationFrame(animationFrameCallback);
}
pause() {
if (!this.#isUpdateLoopActive()) {
throw new Error("Not currently running");
}
this.#isRunning = false;
}
resume() {
if (!this.#isUpdateLoopActive()) {
throw new Error("Not currently running");
}
this.#isRunning = true;
}
stop() {
clearTimeout(this.#timeoutHandle);
cancelAnimationFrame(this.#animationFrameHandle);
this.#isRunning = false;
this.#timeoutHandle = -1;
this.#animationFrameHandle = -1;
}
}
export default Ticker;