UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

128 lines (97 loc) 3.03 kB
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;