81 lines
1.7 KiB
TypeScript
81 lines
1.7 KiB
TypeScript
import { ReactiveController, ReactiveControllerHost } from '@lit/reactive-element';
|
|
import { requestAnimationFrame, cancelAnimationFrame } from './rAF';
|
|
|
|
export interface AnimationFrameControllerHost extends ReactiveControllerHost {
|
|
tick(): void;
|
|
render(): void;
|
|
}
|
|
|
|
export class AnimationFrameController implements ReactiveController {
|
|
#host;
|
|
#lastTime = 0;
|
|
#dtAccumulator = 0;
|
|
#fixedTimestep = 1 / 60;
|
|
#timeoutMs;
|
|
#timeoutId = -1;
|
|
|
|
get fixedTimestep() {
|
|
return this.#fixedTimestep;
|
|
}
|
|
|
|
#isRunning = false;
|
|
get isRunning() {
|
|
return this.#isRunning;
|
|
}
|
|
|
|
constructor(host: AnimationFrameControllerHost, timeoutMs = 5000) {
|
|
this.#host = host;
|
|
this.#timeoutMs = timeoutMs;
|
|
host.addController(this);
|
|
}
|
|
|
|
hostConnected() {
|
|
this.start();
|
|
}
|
|
|
|
hostUpdated() {
|
|
this.reset();
|
|
}
|
|
|
|
hostDisconnected() {
|
|
this.stop();
|
|
}
|
|
|
|
#tick = (timestamp: DOMHighResTimeStamp = performance.now()) => {
|
|
requestAnimationFrame(this.#tick);
|
|
|
|
const actualDelta = (timestamp - this.#lastTime) * 0.001;
|
|
this.#lastTime = timestamp;
|
|
|
|
// Accumulate delta time, but clamp to avoid spiral of death
|
|
this.#dtAccumulator = Math.min(this.#dtAccumulator + actualDelta, 0.2);
|
|
|
|
while (this.#dtAccumulator >= this.#fixedTimestep) {
|
|
this.#host.tick();
|
|
this.#dtAccumulator -= this.#fixedTimestep;
|
|
}
|
|
|
|
this.#host.render();
|
|
};
|
|
|
|
start() {
|
|
if (this.isRunning) return;
|
|
|
|
this.#lastTime = 0;
|
|
this.#isRunning = true;
|
|
this.#tick();
|
|
}
|
|
|
|
reset() {
|
|
window.clearTimeout(this.#timeoutId);
|
|
this.#timeoutId = window.setTimeout(this.stop, this.#timeoutMs);
|
|
this.start();
|
|
}
|
|
|
|
stop = () => {
|
|
cancelAnimationFrame(this.#tick);
|
|
window.clearTimeout(this.#timeoutId);
|
|
this.#isRunning = false;
|
|
};
|
|
}
|