UNPKG

steploop

Version:

a foundation for building loops that execute at a consistent, specified rate

443 lines (442 loc) 16.3 kB
/** * @fileoverview Provides the {@link StepLoop} class, a foundation for building loops that execute at a consistent, specified rate. * * To define a new loop, extend the {@link StepLoop} class and override its methods to implement custom behavior. * * ### Lifecycle * * The {@link StepLoop} class executes in three distinct stages, with hooks that can be overridden to add custom logic: * * 1. **Initialization:** Runs once at the beginning of the loop * - {@link StepLoop.initial()} * 2. **Looping:** The core of the loop, which repeatedly executes the following sequence: * - {@link StepLoop.background()} (async) * - {@link StepLoop.before()} * - {@link StepLoop.step()} * - {@link StepLoop.after()} * 3. **Termination:** Runs once when the loop ends, either by reaching the end of its lifespan or being manually stopped * - {@link StepLoop.final()} * * The loop can run indefinitely or for a set number of steps, and its execution can be precisely controlled, allowing it to be paused, resumed, and dynamically modified at runtime. * * @module steploop */ /** * A base class for building loops that execute at a consistent, specified rate. * * {@link StepLoop} provides a structured lifecycle with methods that can be overridden to implement custom behavior. * * The {@link StepLoop} class manages the timing and execution flow, supporting both fixed-step updates via {@link setTimeout()} and smoother, display-synchronized updates using {@link window.requestAnimationFrame()}. * * The loop can run indefinitely or for a set number of steps, and its execution can be precisely controlled, allowing it to be paused, resumed, and dynamically modified at runtime. * * @example * ```ts * import { StepLoop } from "steploop"; * * class App extends StepLoop { * override initial(): void { * console.log("Loop starting"); * } * * override step(): void { * console.log(`Executing step: ${this.get_step()}`); * } * * override final(): void { * console.log("Loop finished"); * } * } * * // Create a new loop that runs at 60 steps-per-second for 100 steps * const loop = new App(60, 100); * loop.start(); * ``` * @class */ export declare class StepLoop { private _step_num; private _lifespan; private _sps; private _interval; private _startTime; private _lastStepTime; private _lastStepDuration; private _timeoutId; private _initialized; private _running; private _paused; private _kill; /** * Create a `StepLoop`, with options to define the steps-per-second and the lifespan of the loop. * @param {number} sps - the steps-per-second of the loop (note: values that are greater than about 250 may result in unexpected behavior); default value is 60 * @param {number | undefined} lifespan - the number of steps that are executed before the loop ends; setting to `undefined` will result in an unlimited lifespan; default value is `undefined` */ constructor(sps?: number, lifespan?: number | undefined, RAF?: boolean); /** * Override {@link StepLoop.initial()} to add an initial block of code to execute at the very beginning of the loop. * * The first code executed in the {@link StepLoop}. Called once at the beginning of the {@link StepLoop} lifecycle, and then moves on to the first {@link StepLoop.background()} call in the looping stage after resolving. Executed right after {@link StepLoop.start()} is called. * * @returns {void} `void` * @example * ```ts * class App extends StepLoop { * public override initial(): void { * console.log(`initial: ${Date.now()}`); * } * } * ``` * @instance */ initial(): void; /** * Override {@link StepLoop.background()} to add a block of code to run in the background of each step of your loop. * * Executed in the background at the beginning of the looping stage. Called asynchronously before the rest of the loop, executes while the rest of the loop does. Starts before {@link StepLoop.before()} but may not resolve before it is called. * * @returns {Promise<void>} `Promise<void>` * @example * ```js * class App extends StepLoop { * public override async background(): Promise<void> { * console.log(`background: ${this.get_step()}`); * } * } * ``` * @instance */ background(): Promise<void>; /** * Override {@link StepLoop.before()} to add a block of code to run before each step of your loop. * * Executed in the looping stage before the main {@link StepLoop.step()} code. Resolves before calling {@link StepLoop.step()}. Use this function to set up anything you need before {@link StepLoop.step()} is called. * * @returns {void} `void` * @example * ```ts * class App extends StepLoop { * public override before(): void { * console.log(`before: ${this.get_step()}`); * } * } * ``` * @instance */ before(): void; /** * Override {@link StepLoop.step()} to add the code for the main step of your loop. * * The main loop code executed in the looping stage. Called after {@link StepLoop.before()} resolves, and resolves before {@link StepLoop.after()} is called. Use {@link StepLoop.step()} as the main update function of your {@link StepLoop}. * * @returns {void} `void` * @example * ```ts * class App extends StepLoop { * public override step(): void { * console.log(`step: ${this.get_step()}`); * } * } * ``` * @instance */ step(): void; /** * Override {@link StepLoop.after()} to add a block of code to run after each step of your loop. * * Executed in the looping stage after the main {@link StepLoop.step()} code. Called after {@link StepLoop.step()} resolves. Use this function to clean up anything after {@link StepLoop.step()} resolves. * * @returns {void} `void` * @example * ```ts * class App extends StepLoop { * public override after(): void { * console.log(`after: ${this.get_step()}`); * } * } * ``` * @instance */ after(): void; /** * Override {@link StepLoop.final()} to add a final block of code to run at the very end of the loop. * * The last code executed in the {@link StepLoop}, called after the looping stage is done. Executed once at the end of the {@link StepLoop} lifecycle, and then kills the loop. Called when the number of steps executed is greater than the lifespan of the {@link StepLoop} (i. e. {@link StepLoop.get_step()} `>` {@link StepLoop.get_lifespan()}) or when {@link StepLoop.finish()} is called. * * @returns {void} `void` * @example * ```ts * class App extends StepLoop { * public override final(): void { * console.log(`final: ${Date.now()}`); * } * } * ``` * @instance */ final(): void; /** * Override {@link StepLoop.on_pause()} to add a block of code to execute immediately after calling {@link StepLoop.pause()}. * * Called only when the {@link StepLoop} is paused, then stops executing until {@link StepLoop.play()} is called. * * @returns {void} `void` * @example * ```ts * class App extends StepLoop { * public override on_pause(): void { * console.log(`paused`); * } * } * ``` * @instance */ on_pause(): void; /** * Override {@link StepLoop.on_play()} to add a block of code to execute immediately after calling {@link StepLoop.play()}. * * Called only when the {@link StepLoop} is played, then proceeds with the rest of the loop. * * @returns {void} `void` * @example * ```ts * class App extends StepLoop { * public override on_play(): void { * console.log(`played`); * } * } * ``` * @instance */ on_play(): void; /** * Returns `true` if the {@link StepLoop} is running and false otherwise. * * @returns {boolean} `true` if the loop is currently running * @example * ```ts * class App extends StepLoop {} * let app: App = new App(); * app.start() * * console.log(app.is_running()) // Output -> `true` * ``` * @instance */ is_running(): boolean; /** * Returns `true` if the {@link StepLoop} is paused and false otherwise. * * @returns {boolean} `true` if the loop is currently paused * @example * ```ts * class App extends StepLoop {} * let app: App = new App(); * app.start() * * console.log(app.is_paused()) // Output -> `false` * ``` * @instance */ is_paused(): boolean; /** * Returns the current step number (the number of times the loop has run). * * @returns {number} the current step number * @example * ```ts * class App extends StepLoop {} * let app: App = new App(); * app.start() * * console.log(app.get_step()) // Output -> `1` * ``` * @instance */ get_step(): number; /** * Returns the current steps-per-second (sps). * * @returns {number} the current steps-per-second (sps) * @example * ```ts * class App extends StepLoop {} * let app: App = new App(); * app.start() * * console.log(app.get_sps()) // Output -> `60` * ``` * @instance */ get_sps(): number; /** * Returns the real steps-per-second (sps) based on the time between the last two steps. This value may not be accurate until after the first few steps have completed. * * @returns {number} the real steps-per-second (sps) * @example * ```ts * class App extends StepLoop {} * let app: App = new App(); * app.start() * * console.log(app.get_real_sps()) * ``` * @instance */ get_real_sps(): number; /** * Returns the current lifespan of the {@link StepLoop} (in steps). * * @returns {number | undefined} the current loop lifespan; returns `undefined` if the lifespan is unlimited * @example * ```ts * class App extends StepLoop {} * let app: App = new App(500); * app.start() * * console.log(app.get_lifespan()) // Output -> `500` * ``` * @instance */ get_lifespan(): number | undefined; /** * Sets the current steps-per-second (sps). Alters the speed at which the {@link StepLoop} runs: higher values will result in more steps in a faster step-speed and lower values will result in a lower step-speed. Default speed is 60 steps-per-second. * * @param {number} sps - the target steps-per-second; default value is `60` * @returns {number} the new steps-per-second * @example * ```ts * class App extends StepLoop {} * let app: App = new App(); * app.start() * * console.log(app.set_sps(120)) // Output -> `120` * ``` * @instance */ set_sps(sps: number): number; /** * Set whether or not to use {@link window.requestAnimationFrame()} for the {@link StepLoop}. When set to `true`, the loop will synchronize with the browser's rendering cycle (if the loop is running in a browser), which can result in smoother animations and better performance. When disabled, the loop will use a step-scheduler based on {@link setTimeout()}, which may be less efficient but more predictable. * * @param {boolean} status - `true` to use `requestAnimationFrame`, `false` to use the step scheduler. * @returns {boolean} the new status of `requestAnimationFrame` * @example * ```ts * class App extends StepLoop {} * let app: App = new App(); * * app.set_use_RAF(true) * app.start() * ``` * @instance */ set_use_RAF(status: boolean): boolean; /** * Set the lifespan of the {@link StepLoop} to the specified number of steps, or removes the limit on the {@link StepLoop}'s lifespan (will run until {@link StepLoop.finish()} is called). * * If {@link StepLoop.set_lifespan()} is called after the lifespan limit is reached, {@link StepLoop.play()} can be called to resume executing the {@link StepLoop}. The termination stage will be executed again when the limit is reached again. * * @param {number} [steps] - the target lifespan (in number of steps); if `undefined` the lifespan becomes unlimited; default value is `undefined` if not provided * @returns {number | undefined} the new lifespan * @example * ```ts * class App extends StepLoop {} * let app: App = new App(); * app.start() * * console.log(app.set_lifespan(100)) // Output -> `100` * ``` * @instance */ set_lifespan(steps?: number): number | undefined; /** * Extend (or reduce) the lifespan of the {@link StepLoop}. Adds the specified number of steps to the current lifespan. * * If {@link StepLoop.extend_lifespan()} is called after the lifespan limit is reached, {@link StepLoop.play()} can be called to resume executing the {@link StepLoop}. The termination stage will be executed again when the limit is reached again. * * @param {number} steps - the number of steps to add to the lifespan * @returns {number | undefined} the new lifespan; returns undefined if the loop is uninitialized * @example * ```ts * class App extends StepLoop {} * let app: App = new App(); * app.start() * * console.log(app.extend_lifespan(100)) // Output -> `100` * ``` * @instance */ extend_lifespan(steps: number): number | undefined; /** * Pause the execution of the {@link StepLoop} after the current step resolves. Steps will not advance and the current step ({@link StepLoop.get_step()}) will not increase while the {@link StepLoop} is paused. Use {@link StepLoop.play()} to resume execution and continue the loop. * * @returns {void} `void` * @example * ```ts * class App extends StepLoop {} * let app: App = new App(); * app.start() * * app.pause() * ``` * @instance */ pause(): void; /** * Resume execution of the {@link StepLoop} after calling {@link StepLoop.pause()} to pause it. Will resume execution on the next step in the {@link StepLoop} lifespan. Use {@link StepLoop.pause()} to pause execution and stop the loop. * * @returns {void} `void` * @example * ```ts * class App extends StepLoop {} * let app: App = new App(); * app.start() * * app.pause() * app.play() * ``` * @instance */ play(): void; /** * Begin execution of the {@link StepLoop} lifecycle. Calls {@link StepLoop.initial()} to execute the initialization stage, then proceeds to the looping stage. The termination stage will not execute until {@link StepLoop.finish()} is called. * * If {@link StepLoop.start()} is called after the termination stage has ended, the loop will restart at the beginning of the initialization stage. * * @returns {void} `void` * @example * ```ts * class App extends StepLoop {} * let app: App = new App(); * * app.start() * ``` * @instance */ start(): void; /** * Ends the {@link StepLoop}. Executes the termination stage of the {@link StepLoop} lifecycle. Calls {@link StepLoop.final()} and then kills the loop. * * @returns {void} `void` * @example * ```ts * class App extends StepLoop {} * let app: App = new App(); * app.start() * * app.finish() * ``` * @instance */ finish(): void; private _RAFAvailable; private _RAFActive; private _RAFId; private _request_next_step; private _cancel_next_step; private _check_for_end_trigger; private _init; private _run; private _term; private _main; }