diff --git a/app/astride/sandbox.js b/app/astride/sandbox.js index 7ab8b9d..59731eb 100644 --- a/app/astride/sandbox.js +++ b/app/astride/sandbox.js @@ -248,6 +248,11 @@ export default class Sandbox { } } + reset() { + this.generator = undefined; + this.compile(); + } + run(ops = 1000) { let result; for (let i = 0; i < ops; ++i) { @@ -265,8 +270,7 @@ export default class Sandbox { } const result = this.generator.next(); if (result.done) { - this.generator = undefined; - this.compile(); + this.reset(); } return result; } diff --git a/app/context/assets.js b/app/context/assets.js new file mode 100644 index 0000000..a493e95 --- /dev/null +++ b/app/context/assets.js @@ -0,0 +1,25 @@ +import {Assets} from '@pixi/assets'; +import {createContext, useContext, useEffect} from 'react'; + +const context = createContext(); + +export default context; + +const loading = {}; + +export function useAsset(source) { + const [assets, setAssets] = useContext(context); + useEffect(() => { + if (!assets[source]) { + if (!loading[source]) { + (loading[source] = Assets.load(source)).then((asset) => { + setAssets((assets) => ({ + ...assets, + [source]: asset, + })); + }); + } + } + }, [assets, setAssets, source]); + return assets[source]; +} diff --git a/app/ecs-components/inventory.js b/app/ecs-components/inventory.js index 3f70728..198c294 100644 --- a/app/ecs-components/inventory.js +++ b/app/ecs-components/inventory.js @@ -43,57 +43,60 @@ export default class Inventory extends Component { instanceFromSchema() { const Instance = super.instanceFromSchema(); const Component = this; - Instance.prototype.item = async function (slot) { - const {slots} = this; - if (!(slot in slots)) { - return undefined; + return class InventoryInstance extends Instance { + async item(slot) { + const {slots} = this; + if (!(slot in slots)) { + return undefined; + } + const {readAsset} = Component.ecs; + const chars = await readAsset([slots[slot].source, 'item.json'].join('/')) + const json = chars.byteLength > 0 + ? JSON.parse( + (new TextDecoder()).decode(chars), + ) + : {}; + const item = { + ...slots[slot], + ...json, + }; + const instance = this; + const proxy = new Proxy(item, { + set(target, property, value) { + slots[slot][property] = value; + if ('qty' === property && value <= 0) { + Component.markChange(instance.entity, 'slotChange', {[slot]: false}); + delete slots[slot]; + } + else { + Component.markChange(instance.entity, 'slotChange', {[slot]: {[property]: value}}); + } + return true; + }, + }); + return proxy; } - const {readAsset} = Component.ecs; - const json = await ( - readAsset([slots[slot].source, 'item.json'].join('/')) - .then((response) => (response.ok ? response.json() : {})) - ); - const item = { - ...slots[slot], - ...json, - }; - const instance = this; - const proxy = new Proxy(item, { - set(target, property, value) { - slots[slot][property] = value; - if ('qty' === property && value <= 0) { - Component.markChange(instance.entity, 'slotChange', {[slot]: false}); - delete slots[slot]; - } - else { - Component.markChange(instance.entity, 'slotChange', {[slot]: {[property]: value}}); - } - return true; - }, - }); - return proxy; - }; - Instance.prototype.swapSlots = function(l, r) { - const {slots} = this; - const tmp = slots[l]; - const change = {}; - if (slots[r]) { - change[l] = slots[l] = slots[r]; + swapSlots(l, r) { + const {slots} = this; + const tmp = slots[l]; + const change = {}; + if (slots[r]) { + change[l] = slots[l] = slots[r]; + } + else { + change[l] = false; + delete slots[l]; + } + if (tmp) { + change[r] = slots[r] = tmp; + } + else { + change[r] = false; + delete slots[r]; + } + Component.markChange(this.entity, 'slotChange', change); } - else { - change[l] = false; - delete slots[l]; - } - if (tmp) { - change[r] = slots[r] = tmp; - } - else { - change[r] = false; - delete slots[r]; - } - Component.markChange(this.entity, 'slotChange', change); - }; - return Instance; + } } static properties = { slots: { diff --git a/app/ecs-components/ticking.js b/app/ecs-components/ticking.js index 4cd9320..d9a6725 100644 --- a/app/ecs-components/ticking.js +++ b/app/ecs-components/ticking.js @@ -4,34 +4,32 @@ export default class Ticking extends Component { instanceFromSchema() { const Instance = super.instanceFromSchema(); - Instance.prototype.$$finished = []; - Instance.prototype.$$tickingPromises = []; - Instance.prototype.addTickingPromise = function(tickingPromise) { - this.$$tickingPromises.push(tickingPromise); - tickingPromise.then(() => { - this.$$finished.push(tickingPromise); - }); + return class TickingInstance extends Instance { + + $$finished = []; + $$tickingPromises = []; + + addTickingPromise(tickingPromise) { + this.$$tickingPromises.push(tickingPromise); + tickingPromise.then(() => { + this.$$finished.push(tickingPromise); + }); + } + + tick(elapsed) { + for (const tickingPromise of this.$$finished) { + this.$$tickingPromises.splice( + this.$$tickingPromises.indexOf(tickingPromise), + 1, + ); + } + this.$$finished = []; + for (const tickingPromise of this.$$tickingPromises) { + tickingPromise.tick(elapsed); + } + } + } - Instance.prototype.tick = function(elapsed) { - for (const tickingPromise of this.$$finished) { - this.$$tickingPromises.splice( - this.$$tickingPromises.indexOf(tickingPromise), - 1, - ); - } - this.$$finished = []; - for (const tickingPromise of this.$$tickingPromises) { - tickingPromise.tick(elapsed); - } - for (const tickingPromise of this.$$finished) { - this.$$tickingPromises.splice( - this.$$tickingPromises.indexOf(tickingPromise), - 1, - ); - } - this.$$finished = []; - } - return Instance; } static properties = { isTicking: {defaultValue: 1, type: 'uint8'}, diff --git a/app/ecs-components/tile-layers.js b/app/ecs-components/tile-layers.js index c262de5..fb57a2d 100644 --- a/app/ecs-components/tile-layers.js +++ b/app/ecs-components/tile-layers.js @@ -37,53 +37,54 @@ export default class TileLayers extends Component { instanceFromSchema() { const Instance = super.instanceFromSchema(); const Component = this; - Instance.prototype.layer = function (index) { - const {layers} = this; - if (!(index in layers)) { - return undefined; - } - const instance = this; - class LayerProxy { - constructor(layer) { - this.layer = layer; + return class TileLayersInstance extends Instance { + layer(index) { + const {layers} = this; + if (!(index in layers)) { + return undefined; } - get area() { - return this.layer.area; - } - get source() { - return this.layer.source; - } - stamp(at, data) { - const changes = {}; - for (const row in data) { - const columns = data[row]; - for (const column in columns) { - const tile = columns[column]; - const x = at.x + parseInt(column); - const y = at.y + parseInt(row); - if (x < 0 || y < 0 || x >= this.layer.area.x || y >= this.layer.area.y) { - continue; + const instance = this; + class LayerProxy { + constructor(layer) { + this.layer = layer; + } + get area() { + return this.layer.area; + } + get source() { + return this.layer.source; + } + stamp(at, data) { + const changes = {}; + for (const row in data) { + const columns = data[row]; + for (const column in columns) { + const tile = columns[column]; + const x = at.x + parseInt(column); + const y = at.y + parseInt(row); + if (x < 0 || y < 0 || x >= this.layer.area.x || y >= this.layer.area.y) { + continue; + } + const calculated = y * this.layer.area.x + x; + this.layer.data[calculated] = tile; + changes[calculated] = tile; } - const calculated = y * this.layer.area.x + x; - this.layer.data[calculated] = tile; - changes[calculated] = tile; } + Component.markChange(instance.entity, 'layerChange', {[index]: changes}); } - Component.markChange(instance.entity, 'layerChange', {[index]: changes}); - } - tile({x, y}) { - if (x < 0 || y < 0 || x >= this.layer.area.x || y >= this.layer.area.y) { - return undefined; + tile({x, y}) { + if (x < 0 || y < 0 || x >= this.layer.area.x || y >= this.layer.area.y) { + return undefined; + } + return this.layer.data[y * this.layer.area.x + x]; + } + get tileSize() { + return this.layer.tileSize; } - return this.layer.data[y * this.layer.area.x + x]; - } - get tileSize() { - return this.layer.tileSize; } + return new LayerProxy(layers[index]); } - return new LayerProxy(layers[index]); - }; - return Instance; + } } static properties = { layers: { diff --git a/app/ecs-components/wielder.js b/app/ecs-components/wielder.js index 57c2ca4..73383fd 100644 --- a/app/ecs-components/wielder.js +++ b/app/ecs-components/wielder.js @@ -71,15 +71,14 @@ export default class Wielder extends Component { const activeItem = await this.activeItem(); if (activeItem) { ecs.readAsset([activeItem.source, state ? 'start.js' : 'stop.js'].join('/')) - .then((script) => (script.ok ? script.text() : '')) .then((code) => { - if (code) { + if (code.byteLength > 0) { const context = { ecs, item: activeItem, wielder: entity, }; - Ticking.addTickingPromise(Script.tickingPromise(code, context)); + Ticking.addTickingPromise(Script.tickingPromise((new TextDecoder()).decode(code), context)); } }); } diff --git a/app/hooks/use-asset.js b/app/hooks/use-asset.js deleted file mode 100644 index 32dea61..0000000 --- a/app/hooks/use-asset.js +++ /dev/null @@ -1,15 +0,0 @@ -import {Assets} from '@pixi/assets'; -import {useEffect, useState} from 'react'; - -export default function useAsset(source) { - const [asset, setAsset] = useState(); - useEffect(() => { - if (Assets.cache.has(source)) { - setAsset(Assets.get(source)); - } - else { - Assets.load(source).then(setAsset); - } - }, [setAsset, source]); - return asset; -} diff --git a/app/net/server/worker.js b/app/net/server/worker.js index ca2aafb..b548e3d 100644 --- a/app/net/server/worker.js +++ b/app/net/server/worker.js @@ -14,7 +14,9 @@ class WorkerServer extends Server { return ['UNIVERSE', path].join('/'); } async readAsset(path) { - return fetch(path); + return fetch(path).then((response) => ( + response.ok ? response.arrayBuffer() : new ArrayBuffer(0) + )); } async readData(path) { const data = await get(this.constructor.qualify(path)); diff --git a/app/react-components/pixi.jsx b/app/react-components/pixi.jsx index 09ce749..13bb4ac 100644 --- a/app/react-components/pixi.jsx +++ b/app/react-components/pixi.jsx @@ -6,6 +6,7 @@ import {BaseTexture} from '@pixi/core'; import {createElement, useContext} from 'react'; import {RESOLUTION} from '@/constants.js'; +import AssetsContext from '@/context/assets.js'; import ClientContext from '@/context/client.js'; import DebugContext from '@/context/debug.js'; import EcsContext from '@/context/ecs.js'; @@ -16,7 +17,7 @@ import styles from './pixi.module.css'; BaseTexture.defaultOptions.scaleMode = SCALE_MODES.NEAREST; -const Contexts = [ClientContext, DebugContext, EcsContext, MainEntityContext]; +const Contexts = [AssetsContext, ClientContext, DebugContext, EcsContext, MainEntityContext]; const ContextBridge = ({children, render}) => { const contexts = Contexts.map(useContext); diff --git a/app/react-components/sprite.jsx b/app/react-components/sprite.jsx index c57c5fb..b9619d3 100644 --- a/app/react-components/sprite.jsx +++ b/app/react-components/sprite.jsx @@ -1,6 +1,6 @@ import {Sprite as PixiSprite} from '@pixi/react'; -import useAsset from '@/hooks/use-asset.js'; +import {useAsset} from '@/context/assets.js'; export default function Sprite({entity}) { const asset = useAsset(entity.Sprite.source); diff --git a/app/react-components/tile-layer.jsx b/app/react-components/tile-layer.jsx index 87db0c7..4305589 100644 --- a/app/react-components/tile-layer.jsx +++ b/app/react-components/tile-layer.jsx @@ -2,7 +2,7 @@ import {PixiComponent} from '@pixi/react'; import '@pixi/spritesheet'; // NECESSARY! import {CompositeTilemap} from '@pixi/tilemap'; -import useAsset from '@/hooks/use-asset.js'; +import {useAsset} from '@/context/assets.js'; const TileLayerInternal = PixiComponent('TileLayer', { create: () => new CompositeTilemap(), diff --git a/app/routes/_main-menu.play.$.$/route.jsx b/app/routes/_main-menu.play.$.$/route.jsx index 1f1c326..eeaa10f 100644 --- a/app/routes/_main-menu.play.$.$/route.jsx +++ b/app/routes/_main-menu.play.$.$/route.jsx @@ -2,6 +2,7 @@ import {json} from "@remix-run/node"; import {useEffect, useState} from 'react'; import {useOutletContext, useParams} from 'react-router-dom'; +import AssetsContext from '@/context/assets.js'; import ClientContext from '@/context/client.js'; import DebugContext from '@/context/debug.js'; import EcsContext from '@/context/ecs.js'; @@ -12,9 +13,28 @@ import Systems from '@/ecs-systems/index.js'; import Ui from '@/react-components/ui.jsx'; import {juggleSession} from '@/session.server'; +import {LRUCache} from 'lru-cache'; + +export const cache = new LRUCache({ + max: 128, +}); + class ClientEcs extends Ecs { readAsset(uri) { - return fetch(new URL(uri, window.location.origin)); + if (!cache.has(uri)) { + let promise, resolve, reject; + promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + cache.set(uri, promise); + fetch(new URL(uri, window.location.origin)) + .then(async (response) => { + resolve(response.ok ? response.arrayBuffer() : new ArrayBuffer(0)); + }) + .catch(reject); + } + return cache.get(uri); } } @@ -25,6 +45,7 @@ export async function loader({request}) { export default function PlaySpecific() { const Client = useOutletContext(); + const assetsTuple = useState({}); const [client, setClient] = useState(); const mainEntityTuple = useState(); const debugTuple = useState(false); @@ -102,7 +123,9 @@ export default function PlaySpecific() { - + + + diff --git a/app/util/script.js b/app/util/script.js index 28e82a9..802b160 100644 --- a/app/util/script.js +++ b/app/util/script.js @@ -69,16 +69,18 @@ export default class Script { // } // } + evaluateSync() { + this.sandbox.reset(); + const {value: {value}} = this.sandbox.step(); + return value; + } + static async fromCode(code, context = {}) { - let ast; - if (cache.has(code)) { - ast = cache.get(code); - } - else { - cache.set(code, ast = await this.parse(code)); + if (!cache.has(code)) { + cache.set(code, this.parse(code)); } return new this( - new Sandbox(ast, this.createContext(context)), + new Sandbox(await cache.get(code), this.createContext(context)), ); } diff --git a/app/websocket.js b/app/websocket.js index 6ae2d8c..8498848 100644 --- a/app/websocket.js +++ b/app/websocket.js @@ -53,7 +53,9 @@ class SocketServer extends Server { if ('production' === process.env.NODE_ENV) { url.protocol = 'http:'; } - return fetch(url.href); + return fetch(url.href).then((response) => ( + response.ok ? response.arrayBuffer() : new ArrayBuffer(0) + )); } async readData(path) { const qualified = this.constructor.qualify(path);