From ebb34eef95bdf4ff91daa690639bee8603f7d5b8 Mon Sep 17 00:00:00 2001 From: cha0s Date: Sat, 15 Jun 2024 20:59:11 -0500 Subject: [PATCH] feat: local persistence --- app/ecs/ecs.js | 3 +- app/engine/engine.js | 6 ++-- app/engine/engine.test.js | 26 ++++++++++------- app/net/client/local.js | 10 +++++-- app/net/server/worker.js | 37 ++++++++++++++++++++---- app/react-components/ui.jsx | 2 +- app/routes/_main-menu.play.$.$/route.jsx | 16 +++++++++- package-lock.json | 6 ++++ package.json | 1 + 9 files changed, 84 insertions(+), 23 deletions(-) diff --git a/app/ecs/ecs.js b/app/ecs/ecs.js index 74dd58c..f0be338 100644 --- a/app/ecs/ecs.js +++ b/app/ecs/ecs.js @@ -34,7 +34,8 @@ export default class Ecs { const destroying = []; const removing = []; const updating = []; - for (const entityId in patch) { + for (const entityIdString in patch) { + const entityId = parseInt(entityIdString); const components = patch[entityId]; if (false === components) { destroying.push(entityId); diff --git a/app/engine/engine.js b/app/engine/engine.js index 2c8792c..8ac2488 100644 --- a/app/engine/engine.js +++ b/app/engine/engine.js @@ -1,5 +1,3 @@ -import {join} from 'node:path'; - import { MOVE_MAP, TPS, @@ -9,6 +7,10 @@ import Components from '@/ecs-components/index.js'; import Systems from '@/ecs-systems/index.js'; import {decode, encode} from '@/packets/index.js'; +function join(...parts) { + return parts.join('/'); +} + export default class Engine { incomingActions = []; diff --git a/app/engine/engine.test.js b/app/engine/engine.test.js index 350c7a2..49ddb18 100644 --- a/app/engine/engine.test.js +++ b/app/engine/engine.test.js @@ -5,20 +5,26 @@ import Server from '@/net/server/server.js'; import Engine from './engine.js'; -test('visibility-based updates', async () => { - const engine = new Engine(Server); - const data = {}; - engine.server.readData = async (path) => { - if (path in data) { - return data[path]; +class TestServer extends Server { + constructor() { + super(); + this.data = {}; + } + async readData(path) { + if (path in this.data) { + return this.data[path]; } const error = new Error(); error.code = 'ENOENT'; throw error; - }; - engine.server.writeData = async (path, view) => { - data[path] = view; - }; + } + async writeData(path, view) { + this.data[path] = view; + } +} + +test('visibility-based updates', async () => { + const engine = new Engine(TestServer); // Connect an entity. await engine.connectPlayer(0, 0); const ecs = engine.ecses['homesteads/0']; diff --git a/app/net/client/local.js b/app/net/client/local.js index 54a1385..8f69622 100644 --- a/app/net/client/local.js +++ b/app/net/client/local.js @@ -6,12 +6,16 @@ export default class LocalClient extends Client { new URL('../server/worker.js', import.meta.url), {type: 'module'}, ); - this.worker.onmessage = (event) => { + this.worker.addEventListener('message', (event) => { + if (0 === event.data) { + this.worker.terminate(); + return; + } this.accept(event.data); - }; + }); } disconnect() { - this.worker.terminate(); + this.worker.postMessage(0); } transmit(packed) { this.worker.postMessage(packed); diff --git a/app/net/server/worker.js b/app/net/server/worker.js index c9a48c9..b1c80e6 100644 --- a/app/net/server/worker.js +++ b/app/net/server/worker.js @@ -1,26 +1,53 @@ -import Engine from '../../engine/engine.js'; +import {get, set} from 'idb-keyval'; + import {encode} from '@/packets/index.js'; +import Engine from '../../engine/engine.js'; import Server from './server.js'; 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); } } const engine = new Engine(WorkerServer); -onmessage = (event) => { - engine.server.accept(undefined, event.data); +onmessage = async (event) => { + if (0 === event.data) { + await engine.disconnectPlayer(0, 0); + postMessage(0); + return; + } + engine.server.accept(0, event.data); }; (async () => { await engine.load(); engine.start(); - await engine.connectPlayer(); + await engine.connectPlayer(0, 0); 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'})); close(); }); diff --git a/app/react-components/ui.jsx b/app/react-components/ui.jsx index f59a9d5..2c55405 100644 --- a/app/react-components/ui.jsx +++ b/app/react-components/ui.jsx @@ -26,7 +26,7 @@ export default function Ui({disconnected}) { if (disconnected) { handle = setTimeout(() => { setShowDisconnected(true); - }, 200); + }, 400); } else { setShowDisconnected(false) diff --git a/app/routes/_main-menu.play.$.$/route.jsx b/app/routes/_main-menu.play.$.$/route.jsx index 3dbf0b6..0f83a07 100644 --- a/app/routes/_main-menu.play.$.$/route.jsx +++ b/app/routes/_main-menu.play.$.$/route.jsx @@ -16,7 +16,7 @@ export default function PlaySpecific() { const [client, setClient] = useState(); const [disconnected, setDisconnected] = useState(false); const params = useParams(); - const [, url] = params['*'].split('/'); + const [type, url] = params['*'].split('/'); useEffect(() => { if (!Client) { return; @@ -31,6 +31,20 @@ export default function PlaySpecific() { client.disconnect(); }; }, [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(() => { if (!client) { return; diff --git a/package-lock.json b/package-lock.json index 1ce4ca5..5a063bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@remix-run/react": "^2.9.2", "compression": "^1.7.4", "express": "^4.18.2", + "idb-keyval": "^6.2.1", "isbot": "^4.1.0", "morgan": "^1.10.0", "react": "^18.2.0", @@ -11113,6 +11114,11 @@ "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": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", diff --git a/package.json b/package.json index 7bf0748..c8d5709 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@remix-run/react": "^2.9.2", "compression": "^1.7.4", "express": "^4.18.2", + "idb-keyval": "^6.2.1", "isbot": "^4.1.0", "morgan": "^1.10.0", "react": "^18.2.0",