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) {
|
run(ops = 1000) {
|
||||||
let result;
|
let result;
|
||||||
for (let i = 0; i < ops; ++i) {
|
for (let i = 0; i < ops; ++i) {
|
||||||
|
@ -265,8 +270,7 @@ export default class Sandbox {
|
||||||
}
|
}
|
||||||
const result = this.generator.next();
|
const result = this.generator.next();
|
||||||
if (result.done) {
|
if (result.done) {
|
||||||
this.generator = undefined;
|
this.reset();
|
||||||
this.compile();
|
|
||||||
}
|
}
|
||||||
return result;
|
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,57 +43,60 @@ export default class Inventory extends Component {
|
||||||
instanceFromSchema() {
|
instanceFromSchema() {
|
||||||
const Instance = super.instanceFromSchema();
|
const Instance = super.instanceFromSchema();
|
||||||
const Component = this;
|
const Component = this;
|
||||||
Instance.prototype.item = async function (slot) {
|
return class InventoryInstance extends Instance {
|
||||||
const {slots} = this;
|
async item(slot) {
|
||||||
if (!(slot in slots)) {
|
const {slots} = this;
|
||||||
return undefined;
|
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;
|
swapSlots(l, r) {
|
||||||
const json = await (
|
const {slots} = this;
|
||||||
readAsset([slots[slot].source, 'item.json'].join('/'))
|
const tmp = slots[l];
|
||||||
.then((response) => (response.ok ? response.json() : {}))
|
const change = {};
|
||||||
);
|
if (slots[r]) {
|
||||||
const item = {
|
change[l] = slots[l] = slots[r];
|
||||||
...slots[slot],
|
}
|
||||||
...json,
|
else {
|
||||||
};
|
change[l] = false;
|
||||||
const instance = this;
|
delete slots[l];
|
||||||
const proxy = new Proxy(item, {
|
}
|
||||||
set(target, property, value) {
|
if (tmp) {
|
||||||
slots[slot][property] = value;
|
change[r] = slots[r] = tmp;
|
||||||
if ('qty' === property && value <= 0) {
|
}
|
||||||
Component.markChange(instance.entity, 'slotChange', {[slot]: false});
|
else {
|
||||||
delete slots[slot];
|
change[r] = false;
|
||||||
}
|
delete slots[r];
|
||||||
else {
|
}
|
||||||
Component.markChange(instance.entity, 'slotChange', {[slot]: {[property]: value}});
|
Component.markChange(this.entity, 'slotChange', change);
|
||||||
}
|
|
||||||
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];
|
|
||||||
}
|
}
|
||||||
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 = {
|
static properties = {
|
||||||
slots: {
|
slots: {
|
||||||
|
|
|
@ -4,34 +4,32 @@ export default class Ticking extends Component {
|
||||||
instanceFromSchema() {
|
instanceFromSchema() {
|
||||||
const Instance = super.instanceFromSchema();
|
const Instance = super.instanceFromSchema();
|
||||||
|
|
||||||
Instance.prototype.$$finished = [];
|
return class TickingInstance extends Instance {
|
||||||
Instance.prototype.$$tickingPromises = [];
|
|
||||||
Instance.prototype.addTickingPromise = function(tickingPromise) {
|
$$finished = [];
|
||||||
this.$$tickingPromises.push(tickingPromise);
|
$$tickingPromises = [];
|
||||||
tickingPromise.then(() => {
|
|
||||||
this.$$finished.push(tickingPromise);
|
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 = {
|
static properties = {
|
||||||
isTicking: {defaultValue: 1, type: 'uint8'},
|
isTicking: {defaultValue: 1, type: 'uint8'},
|
||||||
|
|
|
@ -37,53 +37,54 @@ export default class TileLayers extends Component {
|
||||||
instanceFromSchema() {
|
instanceFromSchema() {
|
||||||
const Instance = super.instanceFromSchema();
|
const Instance = super.instanceFromSchema();
|
||||||
const Component = this;
|
const Component = this;
|
||||||
Instance.prototype.layer = function (index) {
|
return class TileLayersInstance extends Instance {
|
||||||
const {layers} = this;
|
layer(index) {
|
||||||
if (!(index in layers)) {
|
const {layers} = this;
|
||||||
return undefined;
|
if (!(index in layers)) {
|
||||||
}
|
return undefined;
|
||||||
const instance = this;
|
|
||||||
class LayerProxy {
|
|
||||||
constructor(layer) {
|
|
||||||
this.layer = layer;
|
|
||||||
}
|
}
|
||||||
get area() {
|
const instance = this;
|
||||||
return this.layer.area;
|
class LayerProxy {
|
||||||
}
|
constructor(layer) {
|
||||||
get source() {
|
this.layer = layer;
|
||||||
return this.layer.source;
|
}
|
||||||
}
|
get area() {
|
||||||
stamp(at, data) {
|
return this.layer.area;
|
||||||
const changes = {};
|
}
|
||||||
for (const row in data) {
|
get source() {
|
||||||
const columns = data[row];
|
return this.layer.source;
|
||||||
for (const column in columns) {
|
}
|
||||||
const tile = columns[column];
|
stamp(at, data) {
|
||||||
const x = at.x + parseInt(column);
|
const changes = {};
|
||||||
const y = at.y + parseInt(row);
|
for (const row in data) {
|
||||||
if (x < 0 || y < 0 || x >= this.layer.area.x || y >= this.layer.area.y) {
|
const columns = data[row];
|
||||||
continue;
|
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) {
|
||||||
tile({x, y}) {
|
return undefined;
|
||||||
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 = {
|
static properties = {
|
||||||
layers: {
|
layers: {
|
||||||
|
|
|
@ -71,15 +71,14 @@ export default class Wielder extends Component {
|
||||||
const activeItem = await this.activeItem();
|
const activeItem = await this.activeItem();
|
||||||
if (activeItem) {
|
if (activeItem) {
|
||||||
ecs.readAsset([activeItem.source, state ? 'start.js' : 'stop.js'].join('/'))
|
ecs.readAsset([activeItem.source, state ? 'start.js' : 'stop.js'].join('/'))
|
||||||
.then((script) => (script.ok ? script.text() : ''))
|
|
||||||
.then((code) => {
|
.then((code) => {
|
||||||
if (code) {
|
if (code.byteLength > 0) {
|
||||||
const context = {
|
const context = {
|
||||||
ecs,
|
ecs,
|
||||||
item: activeItem,
|
item: activeItem,
|
||||||
wielder: entity,
|
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('/');
|
return ['UNIVERSE', path].join('/');
|
||||||
}
|
}
|
||||||
async readAsset(path) {
|
async readAsset(path) {
|
||||||
return fetch(path);
|
return fetch(path).then((response) => (
|
||||||
|
response.ok ? response.arrayBuffer() : new ArrayBuffer(0)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
async readData(path) {
|
async readData(path) {
|
||||||
const data = await get(this.constructor.qualify(path));
|
const data = await get(this.constructor.qualify(path));
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {BaseTexture} from '@pixi/core';
|
||||||
import {createElement, useContext} from 'react';
|
import {createElement, useContext} from 'react';
|
||||||
|
|
||||||
import {RESOLUTION} from '@/constants.js';
|
import {RESOLUTION} from '@/constants.js';
|
||||||
|
import AssetsContext from '@/context/assets.js';
|
||||||
import ClientContext from '@/context/client.js';
|
import ClientContext from '@/context/client.js';
|
||||||
import DebugContext from '@/context/debug.js';
|
import DebugContext from '@/context/debug.js';
|
||||||
import EcsContext from '@/context/ecs.js';
|
import EcsContext from '@/context/ecs.js';
|
||||||
|
@ -16,7 +17,7 @@ import styles from './pixi.module.css';
|
||||||
|
|
||||||
BaseTexture.defaultOptions.scaleMode = SCALE_MODES.NEAREST;
|
BaseTexture.defaultOptions.scaleMode = SCALE_MODES.NEAREST;
|
||||||
|
|
||||||
const Contexts = [ClientContext, DebugContext, EcsContext, MainEntityContext];
|
const Contexts = [AssetsContext, ClientContext, DebugContext, EcsContext, MainEntityContext];
|
||||||
|
|
||||||
const ContextBridge = ({children, render}) => {
|
const ContextBridge = ({children, render}) => {
|
||||||
const contexts = Contexts.map(useContext);
|
const contexts = Contexts.map(useContext);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {Sprite as PixiSprite} from '@pixi/react';
|
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}) {
|
export default function Sprite({entity}) {
|
||||||
const asset = useAsset(entity.Sprite.source);
|
const asset = useAsset(entity.Sprite.source);
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {PixiComponent} from '@pixi/react';
|
||||||
import '@pixi/spritesheet'; // NECESSARY!
|
import '@pixi/spritesheet'; // NECESSARY!
|
||||||
import {CompositeTilemap} from '@pixi/tilemap';
|
import {CompositeTilemap} from '@pixi/tilemap';
|
||||||
|
|
||||||
import useAsset from '@/hooks/use-asset.js';
|
import {useAsset} from '@/context/assets.js';
|
||||||
|
|
||||||
const TileLayerInternal = PixiComponent('TileLayer', {
|
const TileLayerInternal = PixiComponent('TileLayer', {
|
||||||
create: () => new CompositeTilemap(),
|
create: () => new CompositeTilemap(),
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {json} from "@remix-run/node";
|
||||||
import {useEffect, useState} from 'react';
|
import {useEffect, useState} from 'react';
|
||||||
import {useOutletContext, useParams} from 'react-router-dom';
|
import {useOutletContext, useParams} from 'react-router-dom';
|
||||||
|
|
||||||
|
import AssetsContext from '@/context/assets.js';
|
||||||
import ClientContext from '@/context/client.js';
|
import ClientContext from '@/context/client.js';
|
||||||
import DebugContext from '@/context/debug.js';
|
import DebugContext from '@/context/debug.js';
|
||||||
import EcsContext from '@/context/ecs.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 Ui from '@/react-components/ui.jsx';
|
||||||
import {juggleSession} from '@/session.server';
|
import {juggleSession} from '@/session.server';
|
||||||
|
|
||||||
|
import {LRUCache} from 'lru-cache';
|
||||||
|
|
||||||
|
export const cache = new LRUCache({
|
||||||
|
max: 128,
|
||||||
|
});
|
||||||
|
|
||||||
class ClientEcs extends Ecs {
|
class ClientEcs extends Ecs {
|
||||||
readAsset(uri) {
|
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() {
|
export default function PlaySpecific() {
|
||||||
const Client = useOutletContext();
|
const Client = useOutletContext();
|
||||||
|
const assetsTuple = useState({});
|
||||||
const [client, setClient] = useState();
|
const [client, setClient] = useState();
|
||||||
const mainEntityTuple = useState();
|
const mainEntityTuple = useState();
|
||||||
const debugTuple = useState(false);
|
const debugTuple = useState(false);
|
||||||
|
@ -102,7 +123,9 @@ export default function PlaySpecific() {
|
||||||
<MainEntityContext.Provider value={mainEntityTuple}>
|
<MainEntityContext.Provider value={mainEntityTuple}>
|
||||||
<EcsContext.Provider value={ecsTuple}>
|
<EcsContext.Provider value={ecsTuple}>
|
||||||
<DebugContext.Provider value={debugTuple}>
|
<DebugContext.Provider value={debugTuple}>
|
||||||
<Ui disconnected={disconnected} />
|
<AssetsContext.Provider value={assetsTuple}>
|
||||||
|
<Ui disconnected={disconnected} />
|
||||||
|
</AssetsContext.Provider>
|
||||||
</DebugContext.Provider>
|
</DebugContext.Provider>
|
||||||
</EcsContext.Provider>
|
</EcsContext.Provider>
|
||||||
</MainEntityContext.Provider>
|
</MainEntityContext.Provider>
|
||||||
|
|
|
@ -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 = {}) {
|
static async fromCode(code, context = {}) {
|
||||||
let ast;
|
if (!cache.has(code)) {
|
||||||
if (cache.has(code)) {
|
cache.set(code, this.parse(code));
|
||||||
ast = cache.get(code);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
cache.set(code, ast = await this.parse(code));
|
|
||||||
}
|
}
|
||||||
return new this(
|
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) {
|
if ('production' === process.env.NODE_ENV) {
|
||||||
url.protocol = 'http:';
|
url.protocol = 'http:';
|
||||||
}
|
}
|
||||||
return fetch(url.href);
|
return fetch(url.href).then((response) => (
|
||||||
|
response.ok ? response.arrayBuffer() : new ArrayBuffer(0)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
async readData(path) {
|
async readData(path) {
|
||||||
const qualified = this.constructor.qualify(path);
|
const qualified = this.constructor.qualify(path);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user