perf: assets
This commit is contained in:
parent
c339491590
commit
b12183a6ee
|
@ -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;
|
||||
}
|
||||
|
|
25
app/context/assets.js
Normal file
25
app/context/assets.js
Normal file
|
@ -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];
|
||||
}
|
|
@ -43,16 +43,19 @@ export default class Inventory extends Component {
|
|||
instanceFromSchema() {
|
||||
const Instance = super.instanceFromSchema();
|
||||
const Component = this;
|
||||
Instance.prototype.item = async function (slot) {
|
||||
return class InventoryInstance extends Instance {
|
||||
async item(slot) {
|
||||
const {slots} = this;
|
||||
if (!(slot in slots)) {
|
||||
return undefined;
|
||||
}
|
||||
const {readAsset} = Component.ecs;
|
||||
const json = await (
|
||||
readAsset([slots[slot].source, 'item.json'].join('/'))
|
||||
.then((response) => (response.ok ? response.json() : {}))
|
||||
);
|
||||
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,
|
||||
|
@ -72,8 +75,8 @@ export default class Inventory extends Component {
|
|||
},
|
||||
});
|
||||
return proxy;
|
||||
};
|
||||
Instance.prototype.swapSlots = function(l, r) {
|
||||
}
|
||||
swapSlots(l, r) {
|
||||
const {slots} = this;
|
||||
const tmp = slots[l];
|
||||
const change = {};
|
||||
|
@ -92,8 +95,8 @@ export default class Inventory extends Component {
|
|||
delete slots[r];
|
||||
}
|
||||
Component.markChange(this.entity, 'slotChange', change);
|
||||
};
|
||||
return Instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
static properties = {
|
||||
slots: {
|
||||
|
|
|
@ -4,15 +4,19 @@ export default class Ticking extends Component {
|
|||
instanceFromSchema() {
|
||||
const Instance = super.instanceFromSchema();
|
||||
|
||||
Instance.prototype.$$finished = [];
|
||||
Instance.prototype.$$tickingPromises = [];
|
||||
Instance.prototype.addTickingPromise = function(tickingPromise) {
|
||||
return class TickingInstance extends Instance {
|
||||
|
||||
$$finished = [];
|
||||
$$tickingPromises = [];
|
||||
|
||||
addTickingPromise(tickingPromise) {
|
||||
this.$$tickingPromises.push(tickingPromise);
|
||||
tickingPromise.then(() => {
|
||||
this.$$finished.push(tickingPromise);
|
||||
});
|
||||
}
|
||||
Instance.prototype.tick = function(elapsed) {
|
||||
|
||||
tick(elapsed) {
|
||||
for (const tickingPromise of this.$$finished) {
|
||||
this.$$tickingPromises.splice(
|
||||
this.$$tickingPromises.indexOf(tickingPromise),
|
||||
|
@ -23,15 +27,9 @@ export default class Ticking extends Component {
|
|||
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'},
|
||||
|
|
|
@ -37,7 +37,8 @@ export default class TileLayers extends Component {
|
|||
instanceFromSchema() {
|
||||
const Instance = super.instanceFromSchema();
|
||||
const Component = this;
|
||||
Instance.prototype.layer = function (index) {
|
||||
return class TileLayersInstance extends Instance {
|
||||
layer(index) {
|
||||
const {layers} = this;
|
||||
if (!(index in layers)) {
|
||||
return undefined;
|
||||
|
@ -82,8 +83,8 @@ export default class TileLayers extends Component {
|
|||
}
|
||||
}
|
||||
return new LayerProxy(layers[index]);
|
||||
};
|
||||
return Instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
static properties = {
|
||||
layers: {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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() {
|
|||
<MainEntityContext.Provider value={mainEntityTuple}>
|
||||
<EcsContext.Provider value={ecsTuple}>
|
||||
<DebugContext.Provider value={debugTuple}>
|
||||
<AssetsContext.Provider value={assetsTuple}>
|
||||
<Ui disconnected={disconnected} />
|
||||
</AssetsContext.Provider>
|
||||
</DebugContext.Provider>
|
||||
</EcsContext.Provider>
|
||||
</MainEntityContext.Provider>
|
||||
|
|
|
@ -69,16 +69,18 @@ export default class Script {
|
|||
// }
|
||||
// }
|
||||
|
||||
static async fromCode(code, context = {}) {
|
||||
let ast;
|
||||
if (cache.has(code)) {
|
||||
ast = cache.get(code);
|
||||
evaluateSync() {
|
||||
this.sandbox.reset();
|
||||
const {value: {value}} = this.sandbox.step();
|
||||
return value;
|
||||
}
|
||||
else {
|
||||
cache.set(code, ast = await this.parse(code));
|
||||
|
||||
static async fromCode(code, context = {}) {
|
||||
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)),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue
Block a user