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 {
duration = 0;
latest = null;
location = 0;
penultimate;
tracking = [];
accept(state) {
const packet = state;
if ('Tick' !== packet.type) {
let authoritative = [];
let duration = 0;
let last = performance.now();
let latest = null;
let location = 0;
let penultimate;
let tracking = [];
const interpolate = () => {
const now = performance.now();
const elapsed = (now - last) / 1000;
last = now;
if (authoritative.length > 0) {
for (const packet of authoritative) {
postMessage(packet);
return;
}
this.penultimate = this.latest;
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'],
});
authoritative = [];
}
}
}
}
this.location = 0;
}
interpolate(elapsed) {
if (0 === this.tracking.length) {
return undefined;
}
this.location += elapsed;
const fraction = Math.min(1, this.location / this.duration);
const [from, to] = [this.penultimate.payload.ecs, this.latest.payload.ecs];
if (tracking.length > 0) {
location += elapsed;
const fraction = location / duration;
const [from, to] = [penultimate.payload.ecs, latest.payload.ecs];
const interpolated = {};
for (const {entityId, componentName, properties} of this.tracking) {
for (const {entityId, componentName, properties} of tracking) {
if (!interpolated[entityId]) {
interpolated[entityId] = {};
}
@ -64,60 +44,65 @@ export default class Interpolator {
);
}
}
return {
type: 'Tick',
payload: {
ecs: interpolated,
elapsed,
frame: this.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;
}
}
onmessage = async (event) => {
interpolator.accept(event.data);
switch (event.data.type) {
case 'EcsChange': {
if (handle) {
cancelAnimationFrame(handle)
handle = null;
}
interpolator.latest = null;
break;
}
case 'Tick': {
if (interpolator.penultimate) {
postMessage({
type: 'Tick',
payload: {
ecs: interpolator.penultimate.payload.ecs,
elapsed: last ? (performance.now() - last) / 1000 : 0,
frame: interpolator.penultimate.payload.frame,
...penultimate.payload,
ecs: interpolated,
elapsed,
frame: penultimate.payload.frame + fraction,
},
});
}
requestAnimationFrame(interpolate);
}
requestAnimationFrame(interpolate);
onmessage = async (event) => {
const packet = event.data;
switch (packet.type) {
case 'EcsChange': {
authoritative = [];
latest = null;
tracking = [];
postMessage(packet);
break;
}
case 'Tick': {
penultimate = latest;
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',
payload: {
...penultimate.payload,
elapsed: last ? (performance.now() - last) / 1000 : 0,
},
});
if (!handle) {
last = performance.now();
handle = requestAnimationFrame(interpolate);
}
break;
}
default: {
postMessage(packet);
break;
}
}