feat: local persistence
This commit is contained in:
parent
5b2db45e94
commit
ebb34eef95
|
@ -34,7 +34,8 @@ export default class Ecs {
|
||||||
const destroying = [];
|
const destroying = [];
|
||||||
const removing = [];
|
const removing = [];
|
||||||
const updating = [];
|
const updating = [];
|
||||||
for (const entityId in patch) {
|
for (const entityIdString in patch) {
|
||||||
|
const entityId = parseInt(entityIdString);
|
||||||
const components = patch[entityId];
|
const components = patch[entityId];
|
||||||
if (false === components) {
|
if (false === components) {
|
||||||
destroying.push(entityId);
|
destroying.push(entityId);
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import {join} from 'node:path';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MOVE_MAP,
|
MOVE_MAP,
|
||||||
TPS,
|
TPS,
|
||||||
|
@ -9,6 +7,10 @@ import Components from '@/ecs-components/index.js';
|
||||||
import Systems from '@/ecs-systems/index.js';
|
import Systems from '@/ecs-systems/index.js';
|
||||||
import {decode, encode} from '@/packets/index.js';
|
import {decode, encode} from '@/packets/index.js';
|
||||||
|
|
||||||
|
function join(...parts) {
|
||||||
|
return parts.join('/');
|
||||||
|
}
|
||||||
|
|
||||||
export default class Engine {
|
export default class Engine {
|
||||||
|
|
||||||
incomingActions = [];
|
incomingActions = [];
|
||||||
|
|
|
@ -5,20 +5,26 @@ import Server from '@/net/server/server.js';
|
||||||
|
|
||||||
import Engine from './engine.js';
|
import Engine from './engine.js';
|
||||||
|
|
||||||
test('visibility-based updates', async () => {
|
class TestServer extends Server {
|
||||||
const engine = new Engine(Server);
|
constructor() {
|
||||||
const data = {};
|
super();
|
||||||
engine.server.readData = async (path) => {
|
this.data = {};
|
||||||
if (path in data) {
|
}
|
||||||
return data[path];
|
async readData(path) {
|
||||||
|
if (path in this.data) {
|
||||||
|
return this.data[path];
|
||||||
}
|
}
|
||||||
const error = new Error();
|
const error = new Error();
|
||||||
error.code = 'ENOENT';
|
error.code = 'ENOENT';
|
||||||
throw error;
|
throw error;
|
||||||
};
|
}
|
||||||
engine.server.writeData = async (path, view) => {
|
async writeData(path, view) {
|
||||||
data[path] = view;
|
this.data[path] = view;
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('visibility-based updates', async () => {
|
||||||
|
const engine = new Engine(TestServer);
|
||||||
// Connect an entity.
|
// Connect an entity.
|
||||||
await engine.connectPlayer(0, 0);
|
await engine.connectPlayer(0, 0);
|
||||||
const ecs = engine.ecses['homesteads/0'];
|
const ecs = engine.ecses['homesteads/0'];
|
||||||
|
|
|
@ -6,12 +6,16 @@ export default class LocalClient extends Client {
|
||||||
new URL('../server/worker.js', import.meta.url),
|
new URL('../server/worker.js', import.meta.url),
|
||||||
{type: 'module'},
|
{type: 'module'},
|
||||||
);
|
);
|
||||||
this.worker.onmessage = (event) => {
|
this.worker.addEventListener('message', (event) => {
|
||||||
|
if (0 === event.data) {
|
||||||
|
this.worker.terminate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.accept(event.data);
|
this.accept(event.data);
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
disconnect() {
|
disconnect() {
|
||||||
this.worker.terminate();
|
this.worker.postMessage(0);
|
||||||
}
|
}
|
||||||
transmit(packed) {
|
transmit(packed) {
|
||||||
this.worker.postMessage(packed);
|
this.worker.postMessage(packed);
|
||||||
|
|
|
@ -1,26 +1,53 @@
|
||||||
import Engine from '../../engine/engine.js';
|
import {get, set} from 'idb-keyval';
|
||||||
|
|
||||||
import {encode} from '@/packets/index.js';
|
import {encode} from '@/packets/index.js';
|
||||||
|
|
||||||
|
import Engine from '../../engine/engine.js';
|
||||||
import Server from './server.js';
|
import Server from './server.js';
|
||||||
|
|
||||||
class WorkerServer extends Server {
|
class WorkerServer extends Server {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.fs = {};
|
||||||
|
}
|
||||||
|
static qualify(path) {
|
||||||
|
return ['UNIVERSE', path].join('/');
|
||||||
|
}
|
||||||
|
async readData(path) {
|
||||||
|
const data = await get(this.constructor.qualify(path));
|
||||||
|
if ('undefined' !== typeof data) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
const error = new Error();
|
||||||
|
error.code = 'ENOENT';
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
async writeData(path, view) {
|
||||||
|
await set(this.constructor.qualify(path), view);
|
||||||
|
}
|
||||||
transmit(connection, packed) { postMessage(packed); }
|
transmit(connection, packed) { postMessage(packed); }
|
||||||
}
|
}
|
||||||
|
|
||||||
const engine = new Engine(WorkerServer);
|
const engine = new Engine(WorkerServer);
|
||||||
|
|
||||||
onmessage = (event) => {
|
onmessage = async (event) => {
|
||||||
engine.server.accept(undefined, event.data);
|
if (0 === event.data) {
|
||||||
|
await engine.disconnectPlayer(0, 0);
|
||||||
|
postMessage(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
engine.server.accept(0, event.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
await engine.load();
|
await engine.load();
|
||||||
engine.start();
|
engine.start();
|
||||||
await engine.connectPlayer();
|
await engine.connectPlayer(0, 0);
|
||||||
postMessage(encode({type: 'ConnectionStatus', payload: 'connected'}));
|
postMessage(encode({type: 'ConnectionStatus', payload: 'connected'}));
|
||||||
})();
|
})();
|
||||||
|
|
||||||
import.meta.hot.accept('../../engine/engine.js', () => {
|
import.meta.hot.accept('../../engine/engine.js', async () => {
|
||||||
|
await engine.disconnectPlayer(0, 0);
|
||||||
postMessage(encode({type: 'ConnectionStatus', payload: 'aborted'}));
|
postMessage(encode({type: 'ConnectionStatus', payload: 'aborted'}));
|
||||||
close();
|
close();
|
||||||
});
|
});
|
||||||
|
|
|
@ -26,7 +26,7 @@ export default function Ui({disconnected}) {
|
||||||
if (disconnected) {
|
if (disconnected) {
|
||||||
handle = setTimeout(() => {
|
handle = setTimeout(() => {
|
||||||
setShowDisconnected(true);
|
setShowDisconnected(true);
|
||||||
}, 200);
|
}, 400);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setShowDisconnected(false)
|
setShowDisconnected(false)
|
||||||
|
|
|
@ -16,7 +16,7 @@ export default function PlaySpecific() {
|
||||||
const [client, setClient] = useState();
|
const [client, setClient] = useState();
|
||||||
const [disconnected, setDisconnected] = useState(false);
|
const [disconnected, setDisconnected] = useState(false);
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const [, url] = params['*'].split('/');
|
const [type, url] = params['*'].split('/');
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!Client) {
|
if (!Client) {
|
||||||
return;
|
return;
|
||||||
|
@ -31,6 +31,20 @@ export default function PlaySpecific() {
|
||||||
client.disconnect();
|
client.disconnect();
|
||||||
};
|
};
|
||||||
}, [Client, url]);
|
}, [Client, url]);
|
||||||
|
// Sneakily use beforeunload to snag some time to save.
|
||||||
|
useEffect(() => {
|
||||||
|
if ('local' !== type) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function onBeforeUnload(event) {
|
||||||
|
client.disconnect();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
addEventListener('beforeunload', onBeforeUnload);
|
||||||
|
return () => {
|
||||||
|
removeEventListener('beforeunload', onBeforeUnload);
|
||||||
|
};
|
||||||
|
});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!client) {
|
if (!client) {
|
||||||
return;
|
return;
|
||||||
|
|
6
package-lock.json
generated
6
package-lock.json
generated
|
@ -15,6 +15,7 @@
|
||||||
"@remix-run/react": "^2.9.2",
|
"@remix-run/react": "^2.9.2",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"idb-keyval": "^6.2.1",
|
||||||
"isbot": "^4.1.0",
|
"isbot": "^4.1.0",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
@ -11113,6 +11114,11 @@
|
||||||
"postcss": "^8.1.0"
|
"postcss": "^8.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/idb-keyval": {
|
||||||
|
"version": "6.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz",
|
||||||
|
"integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg=="
|
||||||
|
},
|
||||||
"node_modules/ieee754": {
|
"node_modules/ieee754": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
"@remix-run/react": "^2.9.2",
|
"@remix-run/react": "^2.9.2",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"idb-keyval": "^6.2.1",
|
||||||
"isbot": "^4.1.0",
|
"isbot": "^4.1.0",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
|
Loading…
Reference in New Issue
Block a user