UNPKG

@byloth/core

Version:

An unopinionated collection of useful functions and classes that I use widely in all my projects. 🔧

157 lines (140 loc) • 4.59 kB
import { TimeUnit } from "../../utils/date.js"; import Publisher from "../callbacks/publisher.js"; import { FatalErrorException, RangeException, RuntimeException } from "../exceptions/index.js"; import GameLoop from "./game-loop.js"; interface ClockEventsMap { start: () => void; stop: () => void; tick: (elapsedTime: number) => void; } /** * A class representing a clock. * * It can be started, stopped and, when running, it ticks at a specific frame rate. * It's possible to subscribe to these events to receive notifications when they occur. * * --- * * @example * ```ts * const clock = new Clock(); * * clock.onStart(() => console.log("The clock has started.")); * clock.onTick((elapsedTime) => console.log(`The clock has ticked at ${elapsedTime}ms.`)); * clock.onStop(() => console.log("The clock has stopped.")); * * clock.start(); * ``` */ export default class Clock extends GameLoop { /** * The {@link Publisher} object that will be used to publish the events of the clock. */ declare protected readonly _publisher: Publisher<ClockEventsMap>; /** * Initializes a new instance of the {@link Clock} class. * * --- * * @example * ```ts * const clock = new Clock(); * ``` * * --- * * @param msIfNotBrowser * The interval in milliseconds at which the clock will tick if the environment is not a browser. * `TimeUnit.Second` by default. */ public constructor(msIfNotBrowser: number = TimeUnit.Second) { super((elapsedTime) => this._publisher.publish("tick", elapsedTime), msIfNotBrowser); } /** * Starts the execution of the clock. * * If the clock is already running, a {@link RuntimeException} will be thrown. * * --- * * @example * ```ts * clock.onStart(() => { [...] }); // This callback will be executed. * clock.start(); * ``` * * --- * * @param elapsedTime The elapsed time to set as default when the clock starts. Default is `0`. */ public override start(elapsedTime = 0): void { if (this._isRunning) { throw new RuntimeException("The clock has already been started."); } this._startTime = performance.now() - elapsedTime; this._start(); this._isRunning = true; this._publisher.publish("start"); } /** * Stops the execution of the clock. * * If the clock hasn't yet started, a {@link RuntimeException} will be thrown. * * --- * * @example * ```ts * clock.onStop(() => { [...] }); // This callback will be executed. * clock.stop(); * ``` */ public override stop(): void { if (!(this._isRunning)) { throw new RuntimeException("The clock had already stopped or hadn't yet started."); } if (!(this._handle)) { throw new FatalErrorException(); } this._stop(); this._handle = undefined; this._isRunning = false; this._publisher.publish("stop"); } /** * Subscribes to the `tick` event of the clock. * * --- * * @example * ```ts * clock.onTick((elapsedTime) => { [...] }); // This callback will be executed. * clock.start(); * ``` * * --- * * @param callback The callback that will be executed when the clock ticks. * @param tickStep * The minimum time in milliseconds that must pass from the previous execution of the callback to the next one. * * - If it's a positive number, the callback will be executed only if the * time passed from the previous execution is greater than this number. * - If it's `0`, the callback will be executed every tick without even checking for the time. * - If it's a negative number, a {@link RangeException} will be thrown. * * @returns A function that can be used to unsubscribe from the event. */ public onTick(callback: (elapsedTime: number) => void, tickStep = 0): () => void { if (tickStep < 0) { throw new RangeException("The tick step must be a non-negative number."); } if (tickStep === 0) { return this._publisher.subscribe("tick", callback); } let lastTick = 0; return this._publisher.subscribe("tick", (elapsedTime: number) => { if ((elapsedTime - lastTick) < tickStep) { return; } callback(elapsedTime); lastTick = elapsedTime; }); } public override readonly [Symbol.toStringTag]: string = "Clock"; }