tempus
Version:
one rAF to rule them all
226 lines (224 loc) • 6.19 kB
JavaScript
// packages/core/src/clock.ts
var Clock = class {
_elapsed = 0;
_currentTime = 0;
_startTime = void 0;
_lastTime = void 0;
_isPlaying = false;
_deltaTime = 0;
play() {
if (this._isPlaying) return;
this._currentTime = 0;
this._startTime = void 0;
this._isPlaying = true;
}
pause() {
if (!this._isPlaying) return;
this._deltaTime = 0;
this._isPlaying = false;
}
reset() {
this._elapsed = 0;
this._deltaTime = 0;
this._currentTime = 0;
this._lastTime = void 0;
this._isPlaying = false;
}
update(browserTime) {
if (!this._isPlaying) return;
if (!this._startTime) {
this._startTime = browserTime;
}
if (this._lastTime === void 0) {
this._lastTime = this._startTime;
this._currentTime = 0;
this._deltaTime = 0;
} else {
this._lastTime = this._currentTime;
this._currentTime = browserTime - this._startTime;
this._deltaTime = this._currentTime - this._lastTime;
this._elapsed += this._deltaTime;
}
}
get time() {
return this._elapsed;
}
get isPlaying() {
return this._isPlaying;
}
get deltaTime() {
return this._deltaTime;
}
};
// 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 = {};
clock = new Clock();
fps;
usage = 0;
rafId;
constructor() {
if (!isClient) return;
this.play();
}
restart() {
if (this.rafId) {
cancelAnimationFrame(this.rafId);
}
for (const framerate of Object.values(this.framerates)) {
framerate.framesCount = 0;
framerate.time = 0;
framerate.lastTickDate = performance.now();
}
this.clock.reset();
this.play();
}
play() {
if (!isClient || this.clock.isPlaying) return;
this.clock.play();
this.rafId = requestAnimationFrame(this.raf);
}
pause() {
if (!isClient || !this.rafId || !this.clock.isPlaying) return;
cancelAnimationFrame(this.rafId);
this.rafId = void 0;
this.clock.pause();
}
get isPlaying() {
return this.clock.isPlaying;
}
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 = (browserElapsed) => {
if (!isClient) return;
this.clock.update(browserElapsed);
const elapsed = this.clock.time;
const deltaTime = this.clock.deltaTime;
this.fps = 1e3 / deltaTime;
const now = performance.now();
for (const framerate of Object.values(this.framerates)) {
framerate.raf(elapsed, deltaTime);
}
const duration = performance.now() - now;
this.usage = duration / deltaTime;
this.rafId = 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) {
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