presentation/timers/surface.timer.ts

presentation/timers/surface.timer.ts is a file in Bane's Lab Site. 65 lines of code and 17 definitions.

import { STEP_LIMIT_MS } from "#configuration/constants/surface.constants";
import type { Segment } from "#types/surface.types";

export class SurfaceTimer {
    private readonly segments: readonly Segment[];
    private index = 0;
    private elapsed = 0;
    private begun = false;
    private last: number | null = null;
    private handle: number | null = null;

    public constructor(segments: readonly Segment[]) {
        this.segments = segments;
    }

    public run(): void {
        if (this.handle !== null || this.segments.length === 0) {
            return;
        }
        this.last = null;
        this.request();
    }

    public halt(): void {
        if (this.handle !== null) {
            cancelAnimationFrame(this.handle);
            this.handle = null;
        }
    }

    public settle(count: number): void {
        for (const segment of this.segments.slice(0, count)) {
            segment.begin?.();
            segment.advance?.(1);
        }
        this.index = count % this.segments.length;
        this.elapsed = 0;
        this.begun = false;
    }

    private request(): void {
        this.handle = requestAnimationFrame((now) => {
            this.tick(now);
        });
    }

    private tick(now: number): void {
        const step = this.last === null ? 0 : Math.min(now - this.last, STEP_LIMIT_MS);
        this.last = now;
        this.advanceBy(step);
        this.request();
    }

    private current(): Segment | undefined {
        return this.segments[this.index];
    }

    private advanceBy(step: number): void {
        if (!this.begun) {
            this.begun = true;
            this.current()?.begin?.();
        }
        this.elapsed += step;
        let segment = this.current();
        while (segment !== undefined && this.elapsed >= segment.ms) {
            segment.advance?.(1);
            this.elapsed -= segment.ms;
            this.index = (this.index + 1) % this.segments.length;
            segment = this.current();
            segment?.begin?.();
        }
        segment?.advance?.(this.elapsed / Math.max(segment.ms, 1));
    }
}