169 lines
4.8 KiB
JavaScript
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;
|
|
}
|
|
}
|
|
};
|