@spearwolf/twopoint5d
Version:
Create 2.5D realtime graphics and pixelart with WebGL and three.js
172 lines • 5.61 kB
JavaScript
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