perf: smoother interpolation

This commit is contained in:
cha0s 2024-09-06 19:21:58 -05:00
parent b912fee2e7
commit 3b88ab8969

View File

@ -1,47 +1,27 @@
export default class Interpolator { let authoritative = [];
duration = 0; let duration = 0;
latest = null; let last = performance.now();
location = 0; let latest = null;
penultimate; let location = 0;
tracking = []; let penultimate;
accept(state) { let tracking = [];
const packet = state;
if ('Tick' !== packet.type) { const interpolate = () => {
const now = performance.now();
const elapsed = (now - last) / 1000;
last = now;
if (authoritative.length > 0) {
for (const packet of authoritative) {
postMessage(packet); postMessage(packet);
return;
} }
this.penultimate = this.latest; authoritative = [];
this.latest = packet;
this.tracking = [];
if (this.penultimate) {
this.duration = this.penultimate.payload.elapsed;
const [from, to] = [this.penultimate.payload.ecs, this.latest.payload.ecs];
for (const entityId in from) {
for (const componentName in from[entityId]) {
if (
['Camera', 'Position'].includes(componentName)
&& to[entityId]?.[componentName]
) {
this.tracking.push({
entityId,
componentName,
properties: ['x', 'y'],
});
}
}
}
}
this.location = 0;
} }
interpolate(elapsed) { if (tracking.length > 0) {
if (0 === this.tracking.length) { location += elapsed;
return undefined; const fraction = location / duration;
} const [from, to] = [penultimate.payload.ecs, latest.payload.ecs];
this.location += elapsed;
const fraction = Math.min(1, this.location / this.duration);
const [from, to] = [this.penultimate.payload.ecs, this.latest.payload.ecs];
const interpolated = {}; const interpolated = {};
for (const {entityId, componentName, properties} of this.tracking) { for (const {entityId, componentName, properties} of tracking) {
if (!interpolated[entityId]) { if (!interpolated[entityId]) {
interpolated[entityId] = {}; interpolated[entityId] = {};
} }
@ -64,61 +44,66 @@ export default class Interpolator {
); );
} }
} }
return { postMessage({
type: 'Tick', type: 'Tick',
payload: { payload: {
...penultimate.payload,
ecs: interpolated, ecs: interpolated,
elapsed, elapsed,
frame: this.penultimate.payload.frame + fraction, frame: penultimate.payload.frame + fraction,
}, },
}; });
}
}
let handle;
const interpolator = new Interpolator();
let last;
const interpolate = (now) => {
const elapsed = (now - last) / 1000;
last = now;
const interpolated = interpolator.interpolate(elapsed);
if (interpolated) {
handle = requestAnimationFrame(interpolate);
postMessage(interpolated);
}
else {
handle = null;
} }
requestAnimationFrame(interpolate);
} }
requestAnimationFrame(interpolate);
onmessage = async (event) => { onmessage = async (event) => {
interpolator.accept(event.data); const packet = event.data;
switch (event.data.type) { switch (packet.type) {
case 'EcsChange': { case 'EcsChange': {
if (handle) { authoritative = [];
cancelAnimationFrame(handle) latest = null;
handle = null; tracking = [];
} postMessage(packet);
interpolator.latest = null;
break; break;
} }
case 'Tick': { case 'Tick': {
if (interpolator.penultimate) { penultimate = latest;
postMessage({ latest = packet;
if (penultimate) {
duration = penultimate.payload.elapsed;
location = 0;
tracking = [];
const [from, to] = [penultimate.payload.ecs, latest.payload.ecs];
for (const entityId in from) {
for (const componentName in from[entityId]) {
if (
['Camera', 'Position'].includes(componentName)
&& to[entityId]?.[componentName]
) {
tracking.push({
entityId,
componentName,
properties: ['x', 'y'],
});
}
}
}
authoritative.push({
type: 'Tick', type: 'Tick',
payload: { payload: {
ecs: interpolator.penultimate.payload.ecs, ...penultimate.payload,
elapsed: last ? (performance.now() - last) / 1000 : 0, elapsed: last ? (performance.now() - last) / 1000 : 0,
frame: interpolator.penultimate.payload.frame,
}, },
}); });
if (!handle) { last = performance.now();
last = performance.now();
handle = requestAnimationFrame(interpolate);
}
} }
break; break;
} }
default: {
postMessage(packet);
break;
}
} }
}; };