# presentation/timers/surface.timer.ts

> 65 lines of code and 17 definitions.

Tree: Site tree
Language: typescript
Layer: runtime
Canonical: https://banes-lab.com/anatomy/tree#file-presentation-timers-surface-timer-ts
Source text: https://banes-lab.com/source/tree/presentation/timers/surface.timer.ts.txt

Listed in [presentation/timers](https://banes-lab.com/api/source/tree/presentation/timers.md).

## Definitions

- `SurfaceTimer` (class_declaration, line 4, exported)
- `segments` (public_field_definition, line 5, exported)
- `index` (public_field_definition, line 6, exported)
- `elapsed` (public_field_definition, line 7, exported)
- `begun` (public_field_definition, line 8, exported)
- `last` (public_field_definition, line 9, exported)
- `handle` (public_field_definition, line 10, exported)
- `constructor` (method_definition, line 12, exported)
- `run` (method_definition, line 16, exported)
- `halt` (method_definition, line 24, exported)
- `settle` (method_definition, line 31, exported)
- `request` (method_definition, line 41, exported)
- `tick` (method_definition, line 47, exported)
- `step` (lexical_declaration, line 48, exported)
- `current` (method_definition, line 54, exported)
- `advanceBy` (method_definition, line 58, exported)
- `segment` (lexical_declaration, line 64, exported)

## Source

```typescript
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));
    }
}
```
