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; } } };