2024-09-05 07:15:55 -05:00
|
|
|
import {LRUCache} from 'lru-cache';
|
|
|
|
|
|
|
|
import Components from '@/ecs/components/index.js';
|
|
|
|
import Ecs from '@/ecs/ecs.js';
|
|
|
|
import Systems from '@/ecs/systems/index.js';
|
|
|
|
import {withResolvers} from '@/util/promise.js';
|
|
|
|
|
|
|
|
const cache = new LRUCache({
|
|
|
|
max: 128,
|
|
|
|
});
|
|
|
|
|
|
|
|
class PredictionEcs extends Ecs {
|
|
|
|
async readAsset(uri) {
|
|
|
|
if (!cache.has(uri)) {
|
|
|
|
const {promise, resolve, reject} = withResolvers();
|
|
|
|
cache.set(uri, promise);
|
|
|
|
fetch(uri)
|
|
|
|
.then((response) => resolve(response.ok ? response.arrayBuffer() : new ArrayBuffer(0)))
|
|
|
|
.catch(reject);
|
|
|
|
}
|
|
|
|
return cache.get(uri);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2024-09-08 17:19:59 -05:00
|
|
|
if (0 === action.finished && !action.ack) {
|
2024-09-05 07:15:55 -05:00
|
|
|
if (!Controlled.locked) {
|
|
|
|
switch (action.action.type) {
|
|
|
|
case 'moveUp':
|
|
|
|
case 'moveRight':
|
|
|
|
case 'moveDown':
|
|
|
|
case 'moveLeft': {
|
|
|
|
Controlled[action.action.type] = action.action.value;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
action.steps.push(elapsed);
|
|
|
|
}
|
2024-09-08 17:19:59 -05:00
|
|
|
if (1 === action.finished) {
|
2024-09-05 07:15:55 -05:00
|
|
|
if (!Controlled.locked) {
|
|
|
|
switch (action.action.type) {
|
|
|
|
case 'moveUp':
|
|
|
|
case 'moveRight':
|
|
|
|
case 'moveDown':
|
|
|
|
case 'moveLeft': {
|
|
|
|
Controlled[action.action.type] = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-09-08 17:19:59 -05:00
|
|
|
action.finished = 2;
|
2024-09-05 07:15:55 -05:00
|
|
|
}
|
2024-09-08 17:19:59 -05:00
|
|
|
if (action.ack && 2 === action.finished) {
|
2024-09-05 07:15:55 -05:00
|
|
|
action.steps.shift();
|
|
|
|
if (0 === action.steps.length) {
|
|
|
|
finished.push(id);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let leap = 0;
|
|
|
|
for (const step of action.steps) {
|
|
|
|
leap += step;
|
|
|
|
}
|
|
|
|
if (leap > 0) {
|
|
|
|
ecs.predict(main, leap);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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);
|
2024-09-08 17:19:59 -05:00
|
|
|
action.finished = 1;
|
2024-09-05 07:15:55 -05:00
|
|
|
pending.delete(packet.payload.type);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
const tx = {
|
|
|
|
action: packet.payload,
|
2024-09-08 17:19:59 -05:00
|
|
|
ack: false,finished: 0,
|
2024-09-05 07:15:55 -05:00
|
|
|
steps: [],
|
|
|
|
};
|
|
|
|
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);
|
2024-09-08 17:19:59 -05:00
|
|
|
action.ack = true;
|
2024-09-05 07:15:55 -05:00
|
|
|
return;
|
|
|
|
}
|
2024-09-05 17:02:09 -05:00
|
|
|
case 'EcsChange': {
|
|
|
|
ecs = new PredictionEcs({Components, Systems});
|
2024-09-08 17:19:59 -05:00
|
|
|
mainEntityId = 0;
|
2024-09-05 17:02:09 -05:00
|
|
|
break;
|
|
|
|
}
|
2024-09-05 07:15:55 -05:00
|
|
|
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.diff[mainEntityId];
|
|
|
|
}
|
|
|
|
await ecs.apply({[mainEntityId]: authoritative});
|
|
|
|
}
|
|
|
|
ecs.setClean();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
postMessage([1, packet]);
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|