silphius/app/client/interpolator.js
2024-09-06 21:20:40 -05:00

120 lines
3.1 KiB
JavaScript

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);
}
authoritative = [];
}
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 tracking) {
if (!interpolated[entityId]) {
interpolated[entityId] = {};
}
if (!interpolated[entityId][componentName]) {
interpolated[entityId][componentName] = {};
}
for (const property of properties) {
if (
!(property in from[entityId][componentName])
|| !(property in to[entityId][componentName])
) {
continue;
}
interpolated[entityId][componentName][property] = (
from[entityId][componentName][property]
+ (
fraction
* (to[entityId][componentName][property] - from[entityId][componentName][property])
)
);
}
}
postMessage({
type: 'Tick',
payload: {
...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'],
});
}
if (
['Sprite'].includes(componentName)
&& to[entityId]?.[componentName]
) {
tracking.push({
entityId,
componentName,
properties: ['alpha', 'scaleX', 'scaleY'],
});
}
}
}
authoritative.push({
type: 'Tick',
payload: {
...penultimate.payload,
elapsed: last ? (performance.now() - last) / 1000 : 0,
},
});
last = performance.now();
}
break;
}
default: {
postMessage(packet);
break;
}
}
};