mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 03:14:00 +00:00
perf(time): the app clock's frame latch without closure boxing
The clock keeps its numbers in object fields (stored in place) instead of closure variables, and its default wall clock is one module-level function. Micro-measure (Node, --expose-gc, 200k calls): the flights render-time arithmetic allocates about 25-35 B per call with the app clock against 66-102 B with JulianDate.now/addSeconds; an engine tick (beginFrame + ticked) about 20-30 B.
This commit is contained in:
1 parent
c33ee31efc
commit
bb4e6bafa4
1 file changed
+42
-32
+42
-32
@@ -38,73 +38,83 @@ function finite(value, label) {
|
|||||||
* @param {{ now?: () => number }} [options] `now`: the wall clock (Date.now).
|
* @param {{ now?: () => number }} [options] `now`: the wall clock (Date.now).
|
||||||
* @returns {AppClock}
|
* @returns {AppClock}
|
||||||
*/
|
*/
|
||||||
export function createAppClock({ now: source = () => Date.now() } = {}) {
|
// The wall clock, read through Date.now at each call (so a mocked Date.now
|
||||||
|
// is the app clock's too).
|
||||||
|
const wallClock = () => Date.now();
|
||||||
|
|
||||||
|
export function createAppClock({ now: source = wallClock } = {}) {
|
||||||
if (typeof source !== 'function')
|
if (typeof source !== 'function')
|
||||||
throw new TypeError('createAppClock needs a now() function');
|
throw new TypeError('createAppClock needs a now() function');
|
||||||
// Rate 1: wall + offsetMs, exact. Any other rate: anchorValue plus the wall
|
// Rate 1: wall + offsetMs, exact. Any other rate: anchorValue plus the wall
|
||||||
// time since anchorSource, scaled.
|
// time since anchorSource, scaled. Numbers live in object fields (stored in
|
||||||
let offsetMs = 0;
|
// place), not closure variables, so the per-frame latch allocates nothing.
|
||||||
let rateValue = 1;
|
const state = {
|
||||||
let anchorValue = 0;
|
offsetMs: 0,
|
||||||
let anchorSource = 0;
|
rate: 1,
|
||||||
let frozen = null;
|
anchorValue: 0,
|
||||||
let latched = null;
|
anchorSource: 0,
|
||||||
|
frozen: false,
|
||||||
|
frozenMs: 0,
|
||||||
|
latched: false,
|
||||||
|
latchedMs: 0,
|
||||||
|
};
|
||||||
|
|
||||||
const running = () =>
|
const running = () =>
|
||||||
rateValue === 1
|
state.rate === 1
|
||||||
? source() + offsetMs
|
? source() + state.offsetMs
|
||||||
: anchorValue + (source() - anchorSource) * rateValue;
|
: state.anchorValue + (source() - state.anchorSource) * state.rate;
|
||||||
const now = () => (frozen === null ? running() : frozen);
|
const now = () => (state.frozen ? state.frozenMs : running());
|
||||||
// Make the running clock read `value` from this moment on.
|
// Make the running clock read `value` from this moment on.
|
||||||
const runFrom = (value) => {
|
const runFrom = (value) => {
|
||||||
if (rateValue === 1) offsetMs = value - source();
|
if (state.rate === 1) state.offsetMs = value - source();
|
||||||
else {
|
else {
|
||||||
anchorValue = value;
|
state.anchorValue = value;
|
||||||
anchorSource = source();
|
state.anchorSource = source();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
now,
|
now,
|
||||||
frameTime: () => (latched === null ? now() : latched),
|
frameTime: () => (state.latched ? state.latchedMs : now()),
|
||||||
beginFrame() {
|
beginFrame() {
|
||||||
latched = now();
|
state.latchedMs = now();
|
||||||
return latched;
|
state.latched = true;
|
||||||
|
return state.latchedMs;
|
||||||
},
|
},
|
||||||
freeze(ms) {
|
freeze(ms) {
|
||||||
frozen = ms === undefined ? now() : finite(ms, 'Frozen time');
|
state.frozenMs = ms === undefined ? now() : finite(ms, 'Frozen time');
|
||||||
|
state.frozen = true;
|
||||||
},
|
},
|
||||||
resume() {
|
resume() {
|
||||||
if (frozen === null) return;
|
if (!state.frozen) return;
|
||||||
const value = frozen;
|
state.frozen = false;
|
||||||
frozen = null;
|
runFrom(state.frozenMs);
|
||||||
runFrom(value);
|
|
||||||
},
|
},
|
||||||
offset(ms) {
|
offset(ms) {
|
||||||
const value = source() + finite(ms, 'Clock offset');
|
const value = source() + finite(ms, 'Clock offset');
|
||||||
frozen = null;
|
state.frozen = false;
|
||||||
runFrom(value);
|
runFrom(value);
|
||||||
},
|
},
|
||||||
rate(rate) {
|
rate(rate) {
|
||||||
if (!(isFiniteNumber(rate) && rate >= 0))
|
if (!(isFiniteNumber(rate) && rate >= 0))
|
||||||
throw new TypeError(`Clock rate must be a finite number >= 0: ${rate}`);
|
throw new TypeError(`Clock rate must be a finite number >= 0: ${rate}`);
|
||||||
if (frozen !== null) {
|
if (state.frozen) {
|
||||||
rateValue = rate;
|
state.rate = rate;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const value = running();
|
const value = running();
|
||||||
rateValue = rate;
|
state.rate = rate;
|
||||||
runFrom(value);
|
runFrom(value);
|
||||||
},
|
},
|
||||||
reset() {
|
reset() {
|
||||||
offsetMs = 0;
|
state.offsetMs = 0;
|
||||||
rateValue = 1;
|
state.rate = 1;
|
||||||
frozen = null;
|
state.frozen = false;
|
||||||
},
|
},
|
||||||
state: () => ({
|
state: () => ({
|
||||||
frozen: frozen !== null,
|
frozen: state.frozen,
|
||||||
offsetMs: now() - source(),
|
offsetMs: now() - source(),
|
||||||
rate: rateValue,
|
rate: state.rate,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user