UNPKG

@spearwolf/twopoint5d

Version:

Create 2.5D realtime graphics and pixelart with WebGL and three.js

172 lines 5.61 kB
import { emit, eventize, off, on } from '@spearwolf/eventize'; const OnRAF = Symbol.for('onRAF'); const OnFrame = Symbol.for('onFrame'); const MEASURE_FPS_AFTER_NTH_FRAME = 30; const MEASURE_COLLECTION_SIZE = 10; const rafUniqueInstances = new WeakMap(); let rafUniqueInstance = null; class RAF { static get(renderer) { if (renderer != null) { if (!rafUniqueInstances.has(renderer)) { rafUniqueInstances.set(renderer, new RAF(renderer)); } return rafUniqueInstances.get(renderer); } if (rafUniqueInstance == null) { rafUniqueInstance = new RAF(); } return rafUniqueInstance; } #rafID; constructor(renderer) { this.renderer = renderer; this.#rafID = 0; this.frameNo = 0; this.measureOnFrame = 0; this.measureTimeBegin = 0; this.measureTimeEnd = 0; this.measuredFps = 0; this.measuredFpsCollection = []; this.#onAnimationFrame = (now) => { if (this.renderer == null) { this.#rafID = requestAnimationFrame(this.#onAnimationFrame); } this.measureFps(now); ++this.frameNo; emit(this, OnRAF, now, this.frameNo, this.measuredFps); }; eventize(this); this.start(); } #onAnimationFrame; start() { if (this.#rafID !== 0) return; if (this.renderer) { this.renderer.setAnimationLoop(this.#onAnimationFrame); this.#rafID = 1; } else { this.#rafID = requestAnimationFrame(this.#onAnimationFrame); } } stop() { if (this.renderer) { this.renderer.setAnimationLoop(null); } else { cancelAnimationFrame(this.#rafID); } this.#rafID = 0; } measureFps(now) { if (this.frameNo === 0) { this.measureTimeBegin = now; this.measureOnFrame = MEASURE_FPS_AFTER_NTH_FRAME; return; } if (this.frameNo >= this.measureOnFrame) { this.measureTimeEnd = now; const measuredFps = Math.round(1000 / ((this.measureTimeEnd - this.measureTimeBegin) / MEASURE_FPS_AFTER_NTH_FRAME)); this.measureOnFrame = this.frameNo + MEASURE_FPS_AFTER_NTH_FRAME; this.measureTimeBegin = now; this.measuredFpsCollection.push(measuredFps); if (this.measuredFpsCollection.length >= MEASURE_COLLECTION_SIZE) { this.measuredFps = Math.round(this.measuredFpsCollection.reduce((sum, fps) => sum + fps, 0) / this.measuredFpsCollection.length); while (this.measuredFpsCollection.length > MEASURE_COLLECTION_SIZE) { this.measuredFpsCollection.shift(); } } else { this.measuredFps = measuredFps; } } } } export class FrameLoop { static { this.OnFrame = OnFrame; } #maxFps; #subscribers; #lastNow; #nextEmitAt; #emitTolerance; get subscriptionCount() { return this.#subscribers.size; } constructor(maxFps = 0, renderer) { this.#maxFps = 0; this.#subscribers = new Set(); this.#lastNow = undefined; this.#nextEmitAt = 0; this.#emitTolerance = 0; this.frameNo = 0; this.now = 0; this.deltaTime = 0; this.measuredFps = 0; eventize(this); this.raf = RAF.get(renderer); this.setFps(maxFps); } setFps(maxFps) { this.#maxFps = Number.isFinite(maxFps) ? Math.abs(maxFps) : 0; this.#nextEmitAt = 0; this.#emitTolerance = this.#maxFps > 0 ? (1000 / this.#maxFps) * 0.02 : 0; } start(target) { if (target == null) return; if (this.#subscribers.has(target)) return; this.#subscribers.add(target); if (this.subscriptionCount === 1) { on(this.raf, OnRAF, this); } on(this, FrameLoop.OnFrame, target); return () => { this.stop(target); }; } stop(target) { if (target == null) return; if (this.#subscribers.has(target)) { this.#subscribers.delete(target); off(this, FrameLoop.OnFrame, target); if (this.subscriptionCount === 0) { off(this.raf, OnRAF, this); } } } [OnRAF](now, _frameNo, measuredFps) { if (this.#maxFps !== 0 && now < this.#nextEmitAt - this.#emitTolerance) { return; } const prevNow = this.#lastNow; this.now = now; ++this.frameNo; this.measuredFps = measuredFps; this.deltaTime = prevNow == null ? 0 : now - prevNow; this.#lastNow = now; if (this.#maxFps > 0) { const interval = 1000 / this.#maxFps; this.#nextEmitAt = this.#nextEmitAt === 0 ? now + interval : this.#nextEmitAt + interval; if (now >= this.#nextEmitAt) { this.#nextEmitAt = now + interval; } } emit(this, FrameLoop.OnFrame, { now: now / 1000, lastNow: (prevNow ?? now) / 1000, frameNo: this.frameNo, deltaTime: this.deltaTime / 1000, measuredFps: this.measuredFps, }); } clear() { for (const target of Array.from(this.#subscribers)) { this.stop(target); } } } //# sourceMappingURL=FrameLoop.js.map