silphius/app/client/predictor.js
2024-09-29 01:53:23 -05:00

169 lines
4.8 KiB
JavaScript

import Components from '@/ecs/components/index.js';
import Ecs from '@/ecs/ecs.js';
import Systems from '@/ecs/systems/index.js';
import {readAsset} from '@/util/resources.js';
class PredictionEcs extends Ecs {
async readAsset(path) {
const resource = await readAsset(path);
return resource
? resource
: new ArrayBuffer(0);
}
}
const Flow = {
UP: 0,
DOWN: 1,
};
const actions = new Map();
let ecs = new PredictionEcs({Components, Systems});
let mainEntityId = 0;
function applyClientActions(elapsed) {
if (actions.size > 0) {
const main = ecs.get(mainEntityId);
const {Controlled} = main;
const finished = [];
for (const [id, action] of actions) {
if (0 === action.finished && !action.ack) {
if (!Controlled.locked) {
switch (action.action.type) {
case 'moveUp':
case 'moveRight':
case 'moveDown':
case 'moveLeft': {
Controlled[action.action.type] = action.action.value;
break;
}
}
}
}
if (1 === action.finished) {
if (!Controlled.locked) {
switch (action.action.type) {
case 'moveUp':
case 'moveRight':
case 'moveDown':
case 'moveLeft': {
Controlled[action.action.type] = 0;
break;
}
}
}
action.finished = 2;
}
if (!action.ack) {
action.stop += elapsed;
}
if (action.ack && 2 === action.finished) {
action.start += elapsed;
if (action.start >= action.stop) {
finished.push(id);
continue;
}
}
ecs.predict(main, action.stop - action.start);
}
for (const id of finished) {
actions.delete(id);
}
}
}
let downPromise;
const pending = new Map();
onmessage = async (event) => {
const [flow, packet] = event.data;
switch (flow) {
case Flow.UP: {
switch (packet.type) {
case 'Action': {
switch (packet.payload.type) {
case 'moveUp':
case 'moveRight':
case 'moveDown':
case 'moveLeft': {
if (0 === packet.payload.value) {
const ack = pending.get(packet.payload.type);
const action = actions.get(ack);
action.finished = 1;
pending.delete(packet.payload.type);
}
else {
const now = performance.now() / 1000;
const tx = {
action: packet.payload,
ack: false,
finished: 0,
start: now,
stop: now,
};
packet.payload.ack = Math.random();
pending.set(packet.payload.type, packet.payload.ack);
actions.set(packet.payload.ack, tx);
}
}
}
break;
}
}
postMessage([0, packet]);
break;
}
case Flow.DOWN: {
downPromise = Promise.resolve(downPromise).then(async () => {
switch (packet.type) {
case 'ActionAck': {
const action = actions.get(packet.payload.ack);
action.ack = true;
return;
}
case 'EcsChange': {
ecs = new PredictionEcs({Components, Systems});
mainEntityId = 0;
break;
}
case 'Tick': {
for (const entityId in packet.payload.ecs) {
if (packet.payload.ecs[entityId]) {
if (packet.payload.ecs[entityId].MainEntity) {
mainEntityId = parseInt(entityId);
}
}
}
await ecs.apply(packet.payload.ecs);
if (actions.size > 0) {
const main = ecs.get(mainEntityId);
const authoritative = structuredClone(main.toNet(main));
applyClientActions(packet.payload.elapsed);
if (ecs.diff[mainEntityId]) {
packet.payload.ecs[mainEntityId] ??= {};
ecs.mergeDiff(
packet.payload.ecs[mainEntityId],
ecs.diff[mainEntityId],
);
const reset = {};
for (const componentName in ecs.diff[mainEntityId]) {
reset[componentName] = {};
for (const property in ecs.diff[mainEntityId][componentName]) {
reset[componentName][property] = authoritative[componentName][property];
}
}
await ecs.apply({[mainEntityId]: reset});
}
}
ecs.setClean();
break;
}
}
postMessage([1, packet]);
});
break;
}
}
};