refactor: upfront/sync resource loading
This commit is contained in:
parent
ff17dd883a
commit
cf4d21db1f
|
@ -1,14 +1,11 @@
|
|||
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';
|
||||
import {get, loadResources, readAsset} from '@/util/resources.js';
|
||||
|
||||
class PredictionEcs extends Ecs {
|
||||
async readAsset(path) {
|
||||
const resource = await readAsset(path);
|
||||
return resource
|
||||
? resource
|
||||
: new ArrayBuffer(0);
|
||||
readAsset(path) {
|
||||
return readAsset(path) ?? new ArrayBuffer(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,6 +14,8 @@ const Flow = {
|
|||
DOWN: 1,
|
||||
};
|
||||
|
||||
await loadResources(await get());
|
||||
|
||||
const actions = new Map();
|
||||
let ecs = new PredictionEcs({Components, Systems});
|
||||
let mainEntityId = 0;
|
||||
|
|
|
@ -422,25 +422,19 @@ export default class Ecs {
|
|||
}
|
||||
}
|
||||
|
||||
async readJson(uri) {
|
||||
readJson(uri) {
|
||||
const key = ['$$json', uri].join(':');
|
||||
if (!cache.has(key)) {
|
||||
const {promise, resolve, reject} = withResolvers();
|
||||
cache.set(key, promise);
|
||||
this.readAsset(uri)
|
||||
.then((chars) => {
|
||||
resolve(
|
||||
chars.byteLength > 0
|
||||
? JSON.parse(textDecoder.decode(chars))
|
||||
: {},
|
||||
);
|
||||
})
|
||||
.catch(reject);
|
||||
const buffer = this.readAsset(uri);
|
||||
const json = buffer.byteLength > 0
|
||||
? JSON.parse(textDecoder.decode(buffer))
|
||||
: {};
|
||||
cache.set(key, json);
|
||||
}
|
||||
return cache.get(key);
|
||||
}
|
||||
|
||||
async readScript(uriOrCode, context = {}) {
|
||||
readScript(uriOrCode, context = {}) {
|
||||
if (!uriOrCode) {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -449,7 +443,7 @@ export default class Ecs {
|
|||
code = uriOrCode;
|
||||
}
|
||||
else {
|
||||
const buffer = await this.readAsset(uriOrCode);
|
||||
const buffer = this.readAsset(uriOrCode);
|
||||
if (buffer.byteLength > 0) {
|
||||
code = textDecoder.decode(buffer);
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ export default class Server {
|
|||
addPacketListener(type, listener) {
|
||||
this.emitter.addListener(type, listener);
|
||||
}
|
||||
async load() {
|
||||
}
|
||||
async readJson(path) {
|
||||
return JSON.parse(textDecoder.decode(await this.readData(path)));
|
||||
}
|
||||
|
|
|
@ -13,10 +13,7 @@ export default class ClientEcs extends Ecs {
|
|||
}
|
||||
});
|
||||
}
|
||||
async readAsset(path) {
|
||||
const resource = await readAsset(path);
|
||||
return resource
|
||||
? resource
|
||||
: new ArrayBuffer(0);
|
||||
readAsset(path) {
|
||||
return readAsset(path) ?? new ArrayBuffer(0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,18 +3,12 @@ import {json, useLoaderData} from "@remix-run/react";
|
|||
import {useEffect, useState} from 'react';
|
||||
import {Outlet, useParams} from 'react-router-dom';
|
||||
|
||||
import {
|
||||
computeMissing,
|
||||
fetchResources,
|
||||
get,
|
||||
readAsset,
|
||||
set,
|
||||
} from '@/util/resources.js';
|
||||
import {fetchMissingResources, readAsset} from '@/util/resources.js';
|
||||
|
||||
import styles from './play.module.css';
|
||||
|
||||
settings.ADAPTER.fetch = async (path) => {
|
||||
const resource = await readAsset(path);
|
||||
const resource = readAsset(path);
|
||||
return resource ? new Response(resource) : new Response(undefined, {status: 404});
|
||||
};
|
||||
|
||||
|
@ -29,34 +23,17 @@ export async function loader({request}) {
|
|||
|
||||
export default function Play() {
|
||||
const {manifest} = useLoaderData();
|
||||
const [assetsLoaded, setAssetsLoaded] = useState(false);
|
||||
const [Client, setClient] = useState();
|
||||
const params = useParams();
|
||||
const [type] = params['*'].split('/');
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
const {signal} = controller;
|
||||
async function receiveResources() {
|
||||
const current = await get();
|
||||
const paths = await computeMissing(current, manifest);
|
||||
if (paths.length > 0 && !signal.aborted) {
|
||||
try {
|
||||
const resources = await fetchResources(paths, {signal});
|
||||
if (resources) {
|
||||
for (const key in resources) {
|
||||
current[key] = resources[key];
|
||||
}
|
||||
await set(current);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
if ((e instanceof DOMException) && 'AbortError' === e.name) {
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
receiveResources();
|
||||
fetchMissingResources(manifest, signal)
|
||||
.then(() => {
|
||||
setAssetsLoaded(true);
|
||||
});
|
||||
return () => {
|
||||
controller.abort();
|
||||
};
|
||||
|
@ -78,7 +55,9 @@ export default function Play() {
|
|||
}, [type]);
|
||||
return (
|
||||
<div className={styles.play}>
|
||||
<Outlet context={Client} />
|
||||
{assetsLoaded && (
|
||||
<Outlet context={Client} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import {LRUCache} from 'lru-cache';
|
||||
|
||||
import Ecs from '@/ecs/ecs.js';
|
||||
import {decode, encode} from '@/net/packets/index.js';
|
||||
import {
|
||||
|
@ -8,7 +6,6 @@ import {
|
|||
TPS,
|
||||
UPS,
|
||||
} from '@/util/constants.js';
|
||||
import {withResolvers} from '@/util/promise.js';
|
||||
|
||||
import createEcs from './create/ecs.js';
|
||||
import createForest from './create/forest.js';
|
||||
|
@ -19,10 +16,6 @@ import createTown from './create/town.js';
|
|||
|
||||
const UPS_PER_S = 1 / UPS;
|
||||
|
||||
const cache = new LRUCache({
|
||||
max: 128,
|
||||
});
|
||||
|
||||
const textEncoder = new TextEncoder();
|
||||
|
||||
export default class Engine {
|
||||
|
@ -56,15 +49,8 @@ export default class Engine {
|
|||
lookupPlayerEntity(id) {
|
||||
return engine.lookupPlayerEntity(id);
|
||||
}
|
||||
async readAsset(uri) {
|
||||
if (!cache.has(uri)) {
|
||||
const {promise, resolve, reject} = withResolvers();
|
||||
cache.set(uri, promise);
|
||||
server.readAsset(uri)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
}
|
||||
return cache.get(uri);
|
||||
readAsset(uri) {
|
||||
return server.readAsset(uri);
|
||||
}
|
||||
async switchEcs(entity, path, updates) {
|
||||
for (const [connection, connectedPlayer] of engine.connectedPlayers) {
|
||||
|
@ -372,6 +358,7 @@ export default class Engine {
|
|||
}
|
||||
|
||||
async load() {
|
||||
await this.server.load();
|
||||
let townData;
|
||||
try {
|
||||
townData = await this.server.readData('town');
|
||||
|
|
|
@ -5,35 +5,25 @@ import {WebSocketServer} from 'ws';
|
|||
|
||||
import Server from '@/net/server.js';
|
||||
import {getSession} from '@/server/session.server.js';
|
||||
import {loadResources, readAsset} from '@/util/resources.js';
|
||||
import {loadResources as loadServerResources} from '@/util/resources.server.js';
|
||||
|
||||
import Engine from './engine.js';
|
||||
|
||||
const {
|
||||
RESOURCES_PATH = [process.cwd(), 'resources'].join('/'),
|
||||
} = process.env;
|
||||
|
||||
global.__silphiusWebsocket = null;
|
||||
|
||||
class SocketServer extends Server {
|
||||
async ensurePath(path) {
|
||||
await mkdir(path, {recursive: true});
|
||||
}
|
||||
async load() {
|
||||
await loadResources(await loadServerResources());
|
||||
}
|
||||
static qualify(path) {
|
||||
return join(import.meta.dirname, '..', '..', 'data', 'remote', 'UNIVERSE', path);
|
||||
}
|
||||
async readAsset(path) {
|
||||
const {pathname} = new URL(path, 'http://example.org');
|
||||
const resourcePath = pathname.slice('/resources/'.length);
|
||||
try {
|
||||
const {buffer} = await readFile([RESOURCES_PATH, resourcePath].join('/'));
|
||||
return buffer;
|
||||
}
|
||||
catch (error) {
|
||||
if ('ENOENT' !== error.code) {
|
||||
throw error;
|
||||
}
|
||||
return new ArrayBuffer(0);
|
||||
}
|
||||
readAsset(path) {
|
||||
return readAsset(path) ?? new ArrayBuffer(0);
|
||||
}
|
||||
async readData(path) {
|
||||
const qualified = this.constructor.qualify(path);
|
||||
|
|
|
@ -3,7 +3,7 @@ import {del, get, set} from 'idb-keyval';
|
|||
import {encode} from '@/net/packets/index.js';
|
||||
import Server from '@/net/server.js';
|
||||
import {withResolvers} from '@/util/promise.js';
|
||||
import {readAsset} from '@/util/resources.js';
|
||||
import {get as getResources, loadResources, readAsset} from '@/util/resources.js';
|
||||
|
||||
import createEcs from './create/ecs.js';
|
||||
import './create/forest.js';
|
||||
|
@ -18,14 +18,14 @@ class WorkerServer extends Server {
|
|||
super();
|
||||
this.fs = {};
|
||||
}
|
||||
async load() {
|
||||
await loadResources(await getResources());
|
||||
}
|
||||
static qualify(path) {
|
||||
return ['UNIVERSE', path].join('/');
|
||||
}
|
||||
async readAsset(path) {
|
||||
const resource = await readAsset(path);
|
||||
return resource
|
||||
? resource
|
||||
: new ArrayBuffer(0);
|
||||
readAsset(path) {
|
||||
return readAsset(path) ?? new ArrayBuffer(0);
|
||||
}
|
||||
async readData(path) {
|
||||
const data = await get(this.constructor.qualify(path));
|
||||
|
@ -42,7 +42,9 @@ class WorkerServer extends Server {
|
|||
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);
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
import {LRUCache} from 'lru-cache';
|
||||
|
||||
const cache = new LRUCache({
|
||||
max: 128,
|
||||
});
|
||||
|
||||
export async function computeMissing(current, manifest) {
|
||||
const missing = [];
|
||||
for (const path in manifest) {
|
||||
|
@ -14,6 +8,57 @@ export async function computeMissing(current, manifest) {
|
|||
return missing;
|
||||
}
|
||||
|
||||
export async function fetchMissingResources(manifest, signal) {
|
||||
const current = await get();
|
||||
const paths = await computeMissing(current, manifest);
|
||||
if (paths.length > 0 && !signal.aborted) {
|
||||
try {
|
||||
const resources = await fetchResources(paths, {signal});
|
||||
if (resources) {
|
||||
for (const key in resources) {
|
||||
current[key] = resources[key];
|
||||
}
|
||||
await set(current);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
if (
|
||||
!(e instanceof DOMException)
|
||||
|| 'AbortError' !== e.name
|
||||
) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
loadResources(current);
|
||||
}
|
||||
|
||||
export async function fetchResources(paths, {signal} = {}) {
|
||||
const response = await fetch('/resources/stream', {
|
||||
body: JSON.stringify(paths),
|
||||
method: 'post',
|
||||
signal,
|
||||
});
|
||||
if (signal.aborted) {
|
||||
return undefined;
|
||||
}
|
||||
return parseResources(paths, await response.arrayBuffer());
|
||||
}
|
||||
|
||||
export async function get() {
|
||||
const {get} = await import('idb-keyval');
|
||||
const resources = await get('$$silphius_resources');
|
||||
return resources || {};
|
||||
}
|
||||
|
||||
const cache = new Map();
|
||||
|
||||
export function loadResources(resources) {
|
||||
for (const path in resources) {
|
||||
cache.set(path, resources[path].asset);
|
||||
}
|
||||
}
|
||||
|
||||
const octets = [];
|
||||
for (let i = 0; i < 256; ++i) {
|
||||
octets.push(i.toString(16).padStart(2, '0'));
|
||||
|
@ -44,35 +89,13 @@ export async function parseResources(resources, buffer) {
|
|||
return manifest;
|
||||
}
|
||||
|
||||
export async function fetchResources(paths, {signal} = {}) {
|
||||
const response = await fetch('/resources/stream', {
|
||||
body: JSON.stringify(paths),
|
||||
method: 'post',
|
||||
signal,
|
||||
});
|
||||
if (signal.aborted) {
|
||||
return undefined;
|
||||
}
|
||||
return parseResources(paths, await response.arrayBuffer());
|
||||
}
|
||||
|
||||
export async function get() {
|
||||
const {get} = await import('idb-keyval');
|
||||
const resources = await get('$$silphius_resources');
|
||||
return resources || {};
|
||||
export function readAsset(path) {
|
||||
const {pathname} = new URL(path, 'http://example.org');
|
||||
const resourcePath = pathname.slice('/resources/'.length);
|
||||
return cache.get(resourcePath);
|
||||
}
|
||||
|
||||
export async function set(resources) {
|
||||
const {set} = await import('idb-keyval');
|
||||
cache.clear();
|
||||
await set('$$silphius_resources', resources);
|
||||
}
|
||||
|
||||
export async function readAsset(path) {
|
||||
if (!cache.has(path)) {
|
||||
const {pathname} = new URL(path, 'http://example.org');
|
||||
const resourcePath = pathname.slice('/resources/'.length);
|
||||
cache.set(path, get().then((resources) => resources[resourcePath]?.asset));
|
||||
}
|
||||
return cache.get(path);
|
||||
}
|
||||
|
|
|
@ -90,17 +90,17 @@ export default class Script {
|
|||
}
|
||||
}
|
||||
|
||||
static async fromCode(code, context = {}) {
|
||||
static fromCode(code, context = {}) {
|
||||
if (!cache.has(code)) {
|
||||
cache.set(code, this.parse(code));
|
||||
}
|
||||
return new this(
|
||||
new Runner(await cache.get(code), this.createContext(context)),
|
||||
new Runner(cache.get(code), this.createContext(context)),
|
||||
code,
|
||||
);
|
||||
}
|
||||
|
||||
static async parse(code) {
|
||||
static parse(code) {
|
||||
return parse(
|
||||
code,
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue
Block a user