tempus
Version:
one rAF to rule them all
149 lines (148 loc) • 4.38 kB
JavaScript
// packages/core/src/uid.ts
var index = 0;
function getUID() {
return index++;
}
// packages/core/src/tempus.ts
var isClient = typeof window !== "undefined";
var originalRAF = isClient && window.requestAnimationFrame;
var originalCancelRAF = isClient && window.cancelAnimationFrame;
var Framerate = class {
callbacks = [];
fps;
time = 0;
lastTickDate = performance.now();
framesCount = 0;
constructor(fps = Number.POSITIVE_INFINITY) {
this.fps = fps;
}
get isRelativeFps() {
return typeof this.fps === "string" && this.fps.endsWith("%");
}
get maxFramesCount() {
if (!this.isRelativeFps) return 1;
return Math.max(1, Math.round(100 / Number(this.fps.replace("%", ""))));
}
get executionTime() {
if (this.isRelativeFps) return 0;
return 1e3 / this.fps;
}
dispatch(time, deltaTime) {
for (let i = 0; i < this.callbacks.length; i++) {
const now = performance.now();
this.callbacks[i]?.callback(time, deltaTime);
const duration = performance.now() - now;
this.callbacks[i].samples?.push(duration);
this.callbacks[i].samples = this.callbacks[i].samples?.slice(-9);
}
}
raf(time, deltaTime) {
this.time += deltaTime;
if (this.isRelativeFps) {
if (this.framesCount === 0) {
this.dispatch(time, deltaTime);
}
this.framesCount++;
this.framesCount %= this.maxFramesCount;
} else {
if (this.fps === Number.POSITIVE_INFINITY) {
this.dispatch(time, deltaTime);
} else if (this.time >= this.executionTime) {
this.time = this.time % this.executionTime;
const deltaTime2 = time - this.lastTickDate;
this.lastTickDate = time;
this.dispatch(time, deltaTime2);
}
}
}
add({
callback,
priority,
label
}) {
if (typeof callback !== "function") {
console.warn("Tempus.add: callback is not a function");
return;
}
const uid = getUID();
this.callbacks.push({ callback, priority, uid, label, samples: [] });
this.callbacks.sort((a, b) => a.priority - b.priority);
return () => this.remove(uid);
}
remove(uid) {
this.callbacks = this.callbacks.filter(({ uid: u }) => uid !== u);
}
};
var TempusImpl = class {
framerates;
time;
fps;
usage;
constructor() {
this.framerates = {};
this.time = isClient ? performance.now() : 0;
this.usage = 0;
if (!isClient) return;
requestAnimationFrame(this.raf);
}
add(callback, {
priority = 0,
fps = Number.POSITIVE_INFINITY,
label = ""
} = {}) {
if (!isClient) return;
if (typeof fps === "number" || typeof fps === "string" && fps.endsWith("%")) {
if (!this.framerates[fps]) this.framerates[fps] = new Framerate(fps);
return this.framerates[fps].add({ callback, priority, label });
}
console.warn('Tempus.add: fps is not a number or a string ending with "%"');
}
raf = (time) => {
if (!isClient) return;
const deltaTime = time - this.time;
this.time = time;
this.fps = 1e3 / deltaTime;
const now = performance.now();
for (const framerate of Object.values(this.framerates)) {
framerate.raf(time, deltaTime);
}
const duration = performance.now() - now;
this.usage = duration / deltaTime;
requestAnimationFrame(this.raf);
};
patch() {
if (!isClient) return;
window.requestAnimationFrame = (callback, { priority = 0, fps = Number.POSITIVE_INFINITY } = {}) => {
if (callback === this.raf || !callback.toString().includes("requestAnimationFrame(")) {
return originalRAF(callback);
}
if (!callback.__tempusPatched) {
console.log("patching", callback.name, callback);
callback.__tempusPatched = true;
callback.__tempusUnsubscribe = this.add(callback, {
priority,
fps,
label: callback.name
});
}
return callback.__tempusUnsubscribe;
};
window.cancelAnimationFrame = (callback) => {
if (typeof callback === "function") {
callback?.();
return;
}
return originalCancelRAF(callback);
};
}
unpatch() {
if (!isClient) return;
window.requestAnimationFrame = originalRAF;
window.cancelAnimationFrame = originalCancelRAF;
}
};
var Tempus = new TempusImpl();
export {
Tempus as default
};
//# sourceMappingURL=tempus.mjs.map