feat: local persistence

This commit is contained in:
cha0s 2024-06-15 20:59:11 -05:00
parent 5b2db45e94
commit ebb34eef95
9 changed files with 84 additions and 23 deletions

View File

@ -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);

View File

@ -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 = [];

View File

@ -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'];

View File

@ -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);

View File

@ -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();
}); });

View File

@ -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)

View File

@ -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
View File

@ -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",

View File

@ -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",