Compare commits

...

16 Commits

Author SHA1 Message Date
cha0s
51a3576bca fix: resilience 2024-06-21 06:04:22 -05:00
cha0s
0f08de7872 refactor: item input 2024-06-21 06:03:56 -05:00
cha0s
10267f2146 fix: no default filter for diff 2024-06-21 05:59:51 -05:00
cha0s
4c5fa9e6ef chore: loosen lint for assets 2024-06-21 04:55:07 -05:00
cha0s
36607d3f12 flow: main entity, actions, active slot 2024-06-21 04:53:09 -05:00
cha0s
5f139f8960 refactor: wait for save 2024-06-21 04:43:32 -05:00
cha0s
18734debff refactor: diff-based 2024-06-21 02:33:47 -05:00
cha0s
493bf025f0 refactor: decorated components 2024-06-21 01:33:44 -05:00
cha0s
9d6d6eb269 feat: transient 2024-06-20 02:47:05 -05:00
cha0s
f363702777 refactor: type 2024-06-20 00:57:23 -05:00
cha0s
fa7f340b2d refactor: control 2024-06-19 02:09:00 -05:00
cha0s
9b2dab91ac feat: top-level return 2024-06-18 22:29:40 -05:00
cha0s
8ba0c0681f chore: tidy 2024-06-18 21:46:51 -05:00
cha0s
925256dcdc feat: debug entity crosshair 2024-06-18 07:24:20 -05:00
cha0s
8e25ee7070 feat: targeting ghost 2024-06-18 07:24:04 -05:00
cha0s
abb140c889 fix: top-level await guard 2024-06-18 04:36:32 -05:00
42 changed files with 764 additions and 284 deletions

View File

@ -74,5 +74,15 @@ module.exports = {
node: true, node: true,
}, },
}, },
// Assets
{
files: [
'assets/**/*.js',
],
rules: {
'no-undef': false,
},
}
], ],
}; };

View File

@ -10,17 +10,3 @@ export const RESOLUTION = {
export const SERVER_LATENCY = 0; export const SERVER_LATENCY = 0;
export const TPS = 60; export const TPS = 60;
export const ACTION_MAP = {
w: 'moveUp',
d: 'moveRight',
s: 'moveDown',
a: 'moveLeft',
};
export const MOVE_MAP = {
'moveUp': 'up',
'moveRight': 'right',
'moveDown': 'down',
'moveLeft': 'left',
};

View File

@ -0,0 +1,10 @@
import {createContext, useContext} from 'react';
const context = createContext();
export default context;
export function useMainEntity() {
return useContext(context);
}

View File

@ -1,6 +1,7 @@
export default { export default {
up: {type: 'float32'}, moveUp: {type: 'float32'},
right: {type: 'float32'}, moveRight: {type: 'float32'},
down: {type: 'float32'}, moveDown: {type: 'float32'},
left: {type: 'float32'}, moveLeft: {type: 'float32'},
changeSlot: {type: 'int8'},
}; };

View File

@ -3,24 +3,35 @@ import Base from '@/ecs/base.js';
import Schema from '@/ecs/schema.js'; import Schema from '@/ecs/schema.js';
import gather from '@/engine/gather.js'; import gather from '@/engine/gather.js';
const specificationsOrClasses = gather( const specificationsAndOrDecorators = gather(
import.meta.glob('./*.js', {eager: true, import: 'default'}), import.meta.glob('./*.js', {eager: true, import: 'default'}),
); );
const Components = {}; const Components = {};
for (const componentName in specificationsOrClasses) { for (const componentName in specificationsAndOrDecorators) {
const specificationOrClass = specificationsOrClasses[componentName]; // TODO: byKey, byId, ...
if (specificationOrClass instanceof Base) { if (Number.isInteger(+componentName)) {
Components[componentName] = specificationOrClass; continue;
}
const specificationOrDecorator = specificationsAndOrDecorators[componentName];
if ('function' === typeof specificationOrDecorator) {
Components[componentName] = specificationOrDecorator(
class Decorated extends Arbitrary {
static name = componentName;
}
);
if (!Components[componentName]) {
throw new Error(`Component ${componentName} decorator returned nothing`);
}
} }
else { else {
Components[componentName] = class Component extends Arbitrary { Components[componentName] = class Component extends Arbitrary {
static name = componentName; static name = componentName;
static schema = new Schema({ static schema = new Schema({
type: 'object', type: 'object',
properties: specificationOrClass, properties: specificationOrDecorator,
}); });
}; }
} }
} }

View File

@ -0,0 +1,3 @@
export default {
speed: {type: 'float32'},
};

View File

@ -0,0 +1 @@
export default {};

View File

@ -0,0 +1,3 @@
export default {
activeSlot: {type: 'uint16'},
};

View File

@ -0,0 +1,17 @@
import {System} from '@/ecs/index.js';
export default class ApplyControlMovement extends System {
tick() {
const {diff} = this.ecs;
for (const id in diff) {
if (diff[id].Controlled) {
const {Controlled, Momentum, Speed} = this.ecs.get(id);
Momentum.x = Speed.speed * (Controlled.moveRight - Controlled.moveLeft);
Momentum.y = Speed.speed * (Controlled.moveDown - Controlled.moveUp);
}
}
}
}

View File

@ -7,17 +7,17 @@ export default class ControlDirection extends System {
for (const id in diff) { for (const id in diff) {
const {Controlled} = diff[id]; const {Controlled} = diff[id];
if (Controlled) { if (Controlled) {
const {Controlled: {up, right, down, left}, Direction} = this.ecs.get(id); const {Controlled: {moveUp, moveRight, moveDown, moveLeft}, Direction} = this.ecs.get(id);
if (up > 0) { if (moveUp > 0) {
Direction.direction = 0; Direction.direction = 0;
} }
if (down > 0) { if (moveDown > 0) {
Direction.direction = 2; Direction.direction = 2;
} }
if (left > 0) { if (moveLeft > 0) {
Direction.direction = 3; Direction.direction = 3;
} }
if (right > 0) { if (moveRight > 0) {
Direction.direction = 1; Direction.direction = 1;
} }
} }

View File

@ -1,21 +0,0 @@
import {System} from '@/ecs/index.js';
const SPEED = 100;
export default class ControlMovement extends System {
static queries() {
return {
default: ['Controlled', 'Momentum'],
};
}
tick() {
for (const [controlled, momentum] of this.select('default')) {
momentum.x = SPEED * (controlled.right - controlled.left);
momentum.y = SPEED * (controlled.down - controlled.up);
}
}
}

View File

@ -13,8 +13,8 @@ export default class SpriteDirection extends System {
const entity = this.ecs.get(entityId); const entity = this.ecs.get(entityId);
const parts = []; const parts = [];
if (entity.Controlled) { if (entity.Controlled) {
const {up, right, down, left} = entity.Controlled; const {moveUp, moveRight, moveDown, moveLeft} = entity.Controlled;
if (up > 0 || right > 0 || down > 0 || left > 0) { if (moveUp > 0 || moveRight > 0 || moveDown > 0 || moveLeft > 0) {
parts.push('moving'); parts.push('moving');
} }
else { else {

View File

@ -2,7 +2,7 @@ export default class Base {
ecs; ecs;
map = []; map = {};
pool = []; pool = [];
@ -60,10 +60,6 @@ export default class Base {
} }
} }
static gathered(id, key) {
this.name = key;
}
insertMany(entities) { insertMany(entities) {
const creating = []; const creating = [];
for (let i = 0; i < entities.length; i++) { for (let i = 0; i < entities.length; i++) {

View File

@ -122,7 +122,10 @@ export default class Ecs {
const componentNames = Object.keys(ecs.Components); const componentNames = Object.keys(ecs.Components);
const {entities, systems} = decoder.decode(view.buffer); const {entities, systems} = decoder.decode(view.buffer);
for (const system of systems) { for (const system of systems) {
ecs.system(system).active = true; const System = ecs.system(system);
if (System) {
System.active = true;
}
} }
const specifics = []; const specifics = [];
let max = 1; let max = 1;
@ -230,7 +233,7 @@ export default class Ecs {
for (const componentName in components) { for (const componentName in components) {
filtered[componentName] = false === components[componentName] filtered[componentName] = false === components[componentName]
? false ? false
: this.Components[componentName].constructor.filterDefaults(components[componentName]); : components[componentName];
} }
this.diff[entityId] = filtered; this.diff[entityId] = filtered;
} }

View File

@ -1,5 +1,4 @@
import { import {
MOVE_MAP,
TPS, TPS,
} from '@/constants.js'; } from '@/constants.js';
import Ecs from '@/ecs/ecs.js'; import Ecs from '@/ecs/ecs.js';
@ -82,7 +81,7 @@ export default class Engine {
}, },
}); });
const defaultSystems = [ const defaultSystems = [
'ControlMovement', 'ApplyControlMovement',
'ApplyMomentum', 'ApplyMomentum',
'ClampPositions', 'ClampPositions',
'FollowCamera', 'FollowCamera',
@ -93,7 +92,10 @@ export default class Engine {
'RunAnimations', 'RunAnimations',
]; ];
defaultSystems.forEach((defaultSystem) => { defaultSystems.forEach((defaultSystem) => {
ecs.system(defaultSystem).active = true; const System = ecs.system(defaultSystem);
if (System) {
System.active = true;
}
}); });
const view = Ecs.serialize(ecs); const view = Ecs.serialize(ecs);
await this.server.writeData( await this.server.writeData(
@ -105,12 +107,13 @@ export default class Engine {
async createPlayer(id) { async createPlayer(id) {
const player = { const player = {
Camera: {}, Camera: {},
Controlled: {up: 0, right: 0, down: 0, left: 0}, Controlled: {},
Direction: {direction: 2}, Direction: {direction: 2},
Ecs: {path: join('homesteads', `${id}`)}, Ecs: {path: join('homesteads', `${id}`)},
Momentum: {}, Momentum: {},
Position: {x: 368, y: 368}, Position: {x: 368, y: 368},
VisibleAabb: {}, VisibleAabb: {},
Speed: {speed: 100},
Sprite: { Sprite: {
animation: 'moving:down', animation: 'moving:down',
frame: 0, frame: 0,
@ -118,6 +121,9 @@ export default class Engine {
source: '/assets/dude.json', source: '/assets/dude.json',
speed: 0.115, speed: 0.115,
}, },
Wielder: {
activeSlot: 0,
},
}; };
const buffer = (new TextEncoder()).encode(JSON.stringify(player)); const buffer = (new TextEncoder()).encode(JSON.stringify(player));
await this.server.writeData( await this.server.writeData(
@ -181,9 +187,18 @@ export default class Engine {
for (const i in this.ecses) { for (const i in this.ecses) {
this.ecses[i].setClean(); this.ecses[i].setClean();
} }
for (const [{Controlled}, payload] of this.incomingActions) { for (const [{Controlled, Wielder}, payload] of this.incomingActions) {
if (payload.type in MOVE_MAP) { switch (payload.type) {
Controlled[MOVE_MAP[payload.type]] = payload.value; case 'moveUp':
case 'moveRight':
case 'moveDown':
case 'moveLeft': {
Controlled[payload.type] = payload.value;
break;
}
case 'changeSlot': {
Wielder.activeSlot = payload.value - 1;
}
} }
} }
this.incomingActions = []; this.incomingActions = [];

View File

@ -9,6 +9,7 @@ export default class LocalClient extends Client {
this.worker.addEventListener('message', (event) => { this.worker.addEventListener('message', (event) => {
if (0 === event.data) { if (0 === event.data) {
this.worker.terminate(); this.worker.terminate();
this.worker = undefined;
return; return;
} }
this.accept(event.data); this.accept(event.data);

View File

@ -5,6 +5,8 @@ const WIRE_MAP = {
'moveRight': 1, 'moveRight': 1,
'moveDown': 2, 'moveDown': 2,
'moveLeft': 3, 'moveLeft': 3,
'use': 4,
'changeSlot': 5,
}; };
Object.entries(WIRE_MAP) Object.entries(WIRE_MAP)
.forEach(([k, v]) => { .forEach(([k, v]) => {

View File

@ -2,18 +2,20 @@ import {Container} from '@pixi/react';
import {useState} from 'react'; import {useState} from 'react';
import {RESOLUTION} from '@/constants.js'; import {RESOLUTION} from '@/constants.js';
import {useMainEntity} from '@/context/main-entity.js';
import Ecs from '@/ecs/ecs.js'; import Ecs from '@/ecs/ecs.js';
import Components from '@/ecs-components/index.js'; import Components from '@/ecs-components/index.js';
import Systems from '@/ecs-systems/index.js'; import Systems from '@/ecs-systems/index.js';
import usePacket from '@/hooks/use-packet.js'; import usePacket from '@/hooks/use-packet.js';
import Entities from './entities.jsx'; import Entities from './entities.jsx';
import TargetingGhost from './targeting-ghost.jsx';
import TileLayer from './tile-layer.jsx'; import TileLayer from './tile-layer.jsx';
export default function EcsComponent() { export default function EcsComponent() {
const [ecs] = useState(new Ecs({Components, Systems})); const [ecs] = useState(new Ecs({Components, Systems}));
const [entities, setEntities] = useState({}); const [entities, setEntities] = useState({});
const [mainEntity, setMainEntity] = useState(); const [mainEntity] = useMainEntity();
usePacket('Tick', (payload) => { usePacket('Tick', (payload) => {
if (0 === Object.keys(payload.ecs).length) { if (0 === Object.keys(payload.ecs).length) {
return; return;
@ -26,9 +28,6 @@ export default function EcsComponent() {
} }
else { else {
updatedEntities[id] = ecs.get(id); updatedEntities[id] = ecs.get(id);
if (updatedEntities[id].MainEntity) {
setMainEntity(ecs.get(id));
}
} }
} }
setEntities(updatedEntities); setEntities(updatedEntities);
@ -36,7 +35,7 @@ export default function EcsComponent() {
if (!mainEntity) { if (!mainEntity) {
return false; return false;
} }
const {Camera} = mainEntity; const {Camera, Position} = ecs.get(mainEntity);
const {TileLayers} = ecs.get(1); const {TileLayers} = ecs.get(1);
const [cx, cy] = [ const [cx, cy] = [
Math.round(Camera.x - RESOLUTION.x / 2), Math.round(Camera.x - RESOLUTION.x / 2),
@ -54,6 +53,12 @@ export default function EcsComponent() {
x={-cx} x={-cx}
y={-cy} y={-cy}
/> />
<TargetingGhost
px={Position.x}
py={Position.y}
cx={cx}
cy={cy}
/>
</Container> </Container>
) )
} }

View File

@ -1,24 +1,45 @@
import {Container} from '@pixi/react'; import {Container, Graphics} from '@pixi/react';
import {useCallback} from 'react';
import Sprite from './sprite.jsx'; import Sprite from './sprite.jsx';
function Crosshair({x, y}) {
const draw = useCallback((g) => {
g.clear();
g.lineStyle(1, 0xffff00);
g.moveTo(-5, 0);
g.lineTo(5, 0);
g.moveTo(0, -5);
g.lineTo(0, 5);
g.lineStyle(1, 0xffffff);
g.drawCircle(0, 0, 3);
}, []);
return (
<Graphics draw={draw} x={x} y={y} />
);
}
export default function Entities({entities, x, y}) { export default function Entities({entities, x, y}) {
const sprites = []; const renderables = [];
for (const id in entities) { for (const id in entities) {
const entity = entities[id]; const entity = entities[id];
if (!entity.Position || !entity.Sprite) { if (!entity.Position || !entity.Sprite) {
continue; continue;
} }
sprites.push( renderables.push(
<Container
key={id}
>
<Sprite <Sprite
entity={entity} entity={entity}
key={id}
/> />
<Crosshair x={entity.Position.x} y={entity.Position.y} />
</Container>
); );
} }
return ( return (
<Container x={x} y={y}> <Container x={x} y={y}>
{sprites} {renderables}
</Container> </Container>
) )
} }

View File

@ -3,29 +3,37 @@ import {
} from '@pixi/react'; } from '@pixi/react';
import {SCALE_MODES} from '@pixi/constants'; import {SCALE_MODES} from '@pixi/constants';
import {BaseTexture} from '@pixi/core'; import {BaseTexture} from '@pixi/core';
import {createElement, useContext} from 'react';
import {RESOLUTION} from '@/constants.js'; import {RESOLUTION} from '@/constants.js';
import ClientContext from '@/context/client.js'; import ClientContext from '@/context/client.js';
import MainEntityContext from '@/context/main-entity.js';
import Ecs from './ecs.jsx'; import Ecs from './ecs.jsx';
import styles from './pixi.module.css'; import styles from './pixi.module.css';
BaseTexture.defaultOptions.scaleMode = SCALE_MODES.NEAREST; BaseTexture.defaultOptions.scaleMode = SCALE_MODES.NEAREST;
const ContextBridge = ({ children, Context, render }) => { const ContextBridge = ({children, Contexts, render}) => {
return ( const contexts = Contexts.map(useContext);
<Context.Consumer> return render(
{(value) => <>
render(<Context.Provider value={value}>{children}</Context.Provider>) {
Contexts.reduce(
(children, Context, i) => ([
createElement(Context.Provider, {value: contexts[i], key: Context}, children)
]),
children,
)
} }
</Context.Consumer> </>
); );
}; };
export const Stage = ({children, ...props}) => { export const Stage = ({children, ...props}) => {
return ( return (
<ContextBridge <ContextBridge
Context={ClientContext} Contexts={[ClientContext, MainEntityContext]}
render={(children) => <PixiStage {...props}>{children}</PixiStage>} render={(children) => <PixiStage {...props}>{children}</PixiStage>}
> >
{children} {children}

View File

@ -0,0 +1,95 @@
import {RenderTexture} from '@pixi/core';
import {Container} from '@pixi/display';
import {BlurFilter} from '@pixi/filter-blur';
import {Graphics} from '@pixi/graphics';
import {PixiComponent, useApp} from '@pixi/react';
import {Sprite} from '@pixi/sprite';
import {useEffect, useState} from 'react';
const tileSize = {x: 16, y: 16};
const radius = 11;
function makeFade(renderer) {
const fade = new Graphics();
fade.beginFill(0xffffff);
fade.alpha = 0.35;
fade.drawCircle(
tileSize.x * (radius / 2),
tileSize.y * (radius / 2),
((tileSize.x + tileSize.y) / 2) * (radius * 0.35),
)
fade.filters = [new BlurFilter(((tileSize.x + tileSize.y) / 2) * 1.25)];
const renderTexture = RenderTexture.create({
width: tileSize.x * radius,
height: tileSize.y * radius,
});
renderer.render(fade, {renderTexture});
return new Sprite(renderTexture);
}
const TargetingGhostInternal = PixiComponent('TargetingGhost', {
create: ({app}) => {
const fade = makeFade(app.renderer);
const grid = new Graphics();
const lineWidth = 1;
grid.lineStyle(lineWidth, 0xffffff);
for (let y = 0; y < radius; ++y) {
for (let x = 0; x < radius; ++x) {
grid.drawRect(
(x * tileSize.x) + (lineWidth / 2),
(y * tileSize.y) + (lineWidth / 2),
tileSize.x,
tileSize.y,
);
}
}
grid.mask = fade;
const innerGrid = new Graphics();
innerGrid.lineStyle(lineWidth, 0x777777);
for (let y = 0; y < radius; ++y) {
for (let x = 0; x < radius; ++x) {
innerGrid.drawRect(
(x * tileSize.x) + (lineWidth / 2) + 1,
(y * tileSize.y) + (lineWidth / 2) + 1,
tileSize.x - 2,
tileSize.y - 2,
);
}
}
innerGrid.mask = fade;
const container = new Container();
container.addChild(fade, grid, innerGrid);
return container;
},
applyProps: (container, oldProps, {cx, cy, px, py, tint}) => {
container.x = px - (px % tileSize.x) - cx + (tileSize.x / 2) - (tileSize.x * (radius / 2));
container.y = py - (py % tileSize.y) - cy + (tileSize.y / 2) - (tileSize.y * (radius / 2));
container.children[0].x = px % tileSize.x - (tileSize.x / 2) ;
container.children[0].y = py % tileSize.y - (tileSize.y / 2) ;
const color = Math.round(255 - (tint * 255));
container.children[2].tint = `rgb(${color}, ${color}, ${color})`;
},
})
export default function TargetingGhost({cx, cy, px, py}) {
const app = useApp();
const [radians, setRadians] = useState(0);
useEffect(() => {
const handle = setInterval(() => {
setRadians((radians) => (radians + 0.2) % (Math.PI * 2))
}, 50);
return () => {
clearInterval(handle);
};
}, []);
return (
<TargetingGhostInternal
app={app}
cx={cx}
cy={cy}
px={px}
py={py}
tint={(Math.cos(radians) + 1) * 0.5}
/>
);
}

View File

@ -1,8 +1,10 @@
import {useContext, useEffect, useState} from 'react'; import {useContext, useEffect, useState} from 'react';
import addKeyListener from '@/add-key-listener.js'; import addKeyListener from '@/add-key-listener.js';
import {ACTION_MAP, RESOLUTION} from '@/constants.js'; import {RESOLUTION} from '@/constants.js';
import ClientContext from '@/context/client.js'; import ClientContext from '@/context/client.js';
import {useMainEntity} from '@/context/main-entity.js';
import usePacket from '@/hooks/use-packet.js';
import Disconnected from './disconnected.jsx'; import Disconnected from './disconnected.jsx';
import Dom from './dom.jsx'; import Dom from './dom.jsx';
@ -12,21 +14,18 @@ import styles from './ui.module.css';
const ratio = RESOLUTION.x / RESOLUTION.y; const ratio = RESOLUTION.x / RESOLUTION.y;
const KEY_MAP = {
keyDown: 1,
keyUp: 0,
};
export default function Ui({disconnected}) { export default function Ui({disconnected}) {
// Key input. // Key input.
const client = useContext(ClientContext); const client = useContext(ClientContext);
const [mainEntity, setMainEntity] = useMainEntity();
const [showDisconnected, setShowDisconnected] = useState(false); const [showDisconnected, setShowDisconnected] = useState(false);
const [activeSlot, setActiveSlot] = useState(0);
useEffect(() => { useEffect(() => {
let handle; let handle;
if (disconnected) { if (disconnected) {
handle = setTimeout(() => { handle = setTimeout(() => {
setShowDisconnected(true); setShowDisconnected(true);
}, 400); }, 1000);
} }
else { else {
setShowDisconnected(false) setShowDisconnected(false)
@ -37,17 +36,117 @@ export default function Ui({disconnected}) {
}, [disconnected]); }, [disconnected]);
useEffect(() => { useEffect(() => {
return addKeyListener(document.body, ({type, payload}) => { return addKeyListener(document.body, ({type, payload}) => {
if (type in KEY_MAP && payload in ACTION_MAP) { const KEY_MAP = {
keyDown: 1,
keyUp: 0,
};
let actionPayload;
switch (payload) {
case 'w': {
actionPayload = {type: 'moveUp', value: KEY_MAP[type]};
break;
}
case 'a': {
actionPayload = {type: 'moveLeft', value: KEY_MAP[type]};
break;
}
case 's': {
actionPayload = {type: 'moveDown', value: KEY_MAP[type]};
break;
}
case 'd': {
actionPayload = {type: 'moveRight', value: KEY_MAP[type]};
break;
}
case ' ': {
actionPayload = {type: 'use', value: KEY_MAP[type]};
break;
}
case '1': {
if ('keyDown' === type) {
actionPayload = {type: 'changeSlot', value: 1};
}
break;
}
case '2': {
if ('keyDown' === type) {
actionPayload = {type: 'changeSlot', value: 2};
}
break;
}
case '3': {
if ('keyDown' === type) {
actionPayload = {type: 'changeSlot', value: 3};
}
break;
}
case '4': {
if ('keyDown' === type) {
actionPayload = {type: 'changeSlot', value: 4};
}
break;
}
case '5': {
if ('keyDown' === type) {
actionPayload = {type: 'changeSlot', value: 5};
}
break;
}
case '6': {
if ('keyDown' === type) {
actionPayload = {type: 'changeSlot', value: 6};
}
break;
}
case '7': {
if ('keyDown' === type) {
actionPayload = {type: 'changeSlot', value: 7};
}
break;
}
case '8': {
if ('keyDown' === type) {
actionPayload = {type: 'changeSlot', value: 8};
}
break;
}
case '9': {
if ('keyDown' === type) {
actionPayload = {type: 'changeSlot', value: 9};
}
break;
}
case '0': {
if ('keyDown' === type) {
actionPayload = {type: 'changeSlot', value: 10};
}
break;
}
}
if (actionPayload) {
client.send({ client.send({
type: 'Action', type: 'Action',
payload: { payload: actionPayload,
type: ACTION_MAP[payload],
value: KEY_MAP[type],
},
}); });
} }
}); });
}); });
usePacket('Tick', (payload) => {
if (0 === Object.keys(payload.ecs).length) {
return;
}
let localMainEntity = mainEntity;
for (const id in payload.ecs) {
if (payload.ecs[id]?.MainEntity) {
setMainEntity(localMainEntity = id);
}
if (localMainEntity === id) {
if (payload.ecs[id].Wielder && 'activeSlot' in payload.ecs[id].Wielder) {
setActiveSlot(payload.ecs[id].Wielder.activeSlot);
}
}
}
}, [mainEntity, setMainEntity]);
return ( return (
<div className={styles.ui}> <div className={styles.ui}>
<style> <style>
@ -57,12 +156,23 @@ export default function Ui({disconnected}) {
`} `}
</style> </style>
<Pixi /> <Pixi />
{mainEntity && (
<Dom> <Dom>
<HotBar active={1} slots={Array(10).fill(0).map(() => {})} /> <HotBar
active={activeSlot}
onActivate={(i) => {
client.send({
type: 'Action',
payload: {type: 'changeSlot', value: i + 1},
});
}}
slots={Array(10).fill(0).map(() => {})}
/>
{showDisconnected && ( {showDisconnected && (
<Disconnected /> <Disconnected />
)} )}
</Dom> </Dom>
)}
</div> </div>
); );
} }

View File

@ -3,6 +3,8 @@ import {useEffect, useState} from 'react';
import {useOutletContext, useParams} from 'react-router-dom'; import {useOutletContext, useParams} from 'react-router-dom';
import ClientContext from '@/context/client.js'; import ClientContext from '@/context/client.js';
import MainEntityContext from '@/context/main-entity.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';
@ -14,6 +16,7 @@ export async function loader({request}) {
export default function PlaySpecific() { export default function PlaySpecific() {
const Client = useOutletContext(); const Client = useOutletContext();
const [client, setClient] = useState(); const [client, setClient] = useState();
const mainEntityTuple = useState();
const [disconnected, setDisconnected] = useState(false); const [disconnected, setDisconnected] = useState(false);
const params = useParams(); const params = useParams();
const [type, url] = params['*'].split('/'); const [type, url] = params['*'].split('/');
@ -36,9 +39,12 @@ export default function PlaySpecific() {
if ('local' !== type) { if ('local' !== type) {
return; return;
} }
function onBeforeUnload(event) { async function onBeforeUnload(event) {
client.disconnect(); client.disconnect();
event.preventDefault(); function waitForSave() {
return new Promise((resolve) => setTimeout(resolve, 0));
}
while (client.worker) { await waitForSave(); }
} }
addEventListener('beforeunload', onBeforeUnload); addEventListener('beforeunload', onBeforeUnload);
return () => { return () => {
@ -81,7 +87,9 @@ export default function PlaySpecific() {
}, [client, disconnected, url]); }, [client, disconnected, url]);
return ( return (
<ClientContext.Provider value={client}> <ClientContext.Provider value={client}>
<MainEntityContext.Provider value={mainEntityTuple}>
<Ui disconnected={disconnected} /> <Ui disconnected={disconnected} />
</MainEntityContext.Provider>
</ClientContext.Provider> </ClientContext.Provider>
); );
} }

View File

@ -1,15 +1,15 @@
import {expect, test} from 'vitest'; import {expect, test} from 'vitest';
import {first} from '@/swcx/builders.js';
import evaluate from '@/swcx/evaluate.js'; import evaluate from '@/swcx/evaluate.js';
import expression from '@/swcx/test/expression.js';
test('evaluates array of literals', async () => { test('evaluates array of literals', async () => {
expect(evaluate(await first('[1.5, 2, "three"]'))) expect(evaluate(await expression('[1.5, 2, "three"]')))
.to.deep.include({value: [1.5, 2, 'three']}); .to.deep.include({value: [1.5, 2, 'three']});
}); });
test('evaluates array containing promises', async () => { test('evaluates array containing promises', async () => {
const evaluated = evaluate(await first('[1.5, 2, await "three"]')); const evaluated = evaluate(await expression('[1.5, 2, await "three"]'));
expect(evaluated.async) expect(evaluated.async)
.to.equal(true); .to.equal(true);
expect(await evaluated.value) expect(await evaluated.value)

View File

@ -1,7 +1,7 @@
import {expect, test} from 'vitest'; import {expect, test} from 'vitest';
import {first} from '@/swcx/builders.js';
import evaluate from '@/swcx/evaluate.js'; import evaluate from '@/swcx/evaluate.js';
import expression from '@/swcx/test/expression.js';
const scopeTest = test.extend({ const scopeTest = test.extend({
scope: async ({}, use) => { scope: async ({}, use) => {
@ -14,11 +14,11 @@ const scopeTest = test.extend({
}); });
scopeTest('evaluates =', async ({scope}) => { scopeTest('evaluates =', async ({scope}) => {
expect(evaluate(await first('x = 4'), {scope})) expect(evaluate(await expression('x = 4'), {scope}))
.to.deep.include({value: 4}); .to.deep.include({value: 4});
expect(scope.get('x')) expect(scope.get('x'))
.to.equal(4); .to.equal(4);
expect(evaluate(await first('O.x = 4'), {scope})) expect(evaluate(await expression('O.x = 4'), {scope}))
.to.deep.include({value: 4}); .to.deep.include({value: 4});
expect(scope.get('O').x) expect(scope.get('O').x)
.to.equal(4); .to.equal(4);
@ -26,12 +26,12 @@ scopeTest('evaluates =', async ({scope}) => {
scopeTest('evaluates +=', async ({scope}) => { scopeTest('evaluates +=', async ({scope}) => {
scope.set('x', 1); scope.set('x', 1);
expect(evaluate(await first('x += 4'), {scope})) expect(evaluate(await expression('x += 4'), {scope}))
.to.deep.include({value: 5}); .to.deep.include({value: 5});
expect(scope.get('x')) expect(scope.get('x'))
.to.equal(5); .to.equal(5);
scope.set('O', {x: 1}); scope.set('O', {x: 1});
expect(evaluate(await first('O.x += 4'), {scope})) expect(evaluate(await expression('O.x += 4'), {scope}))
.to.deep.include({value: 5}); .to.deep.include({value: 5});
expect(scope.get('O').x) expect(scope.get('O').x)
.to.equal(5); .to.equal(5);
@ -39,12 +39,12 @@ scopeTest('evaluates +=', async ({scope}) => {
scopeTest('evaluates -=', async ({scope}) => { scopeTest('evaluates -=', async ({scope}) => {
scope.set('x', 5); scope.set('x', 5);
expect(evaluate(await first('x -= 4'), {scope})) expect(evaluate(await expression('x -= 4'), {scope}))
.to.deep.include({value: 1}); .to.deep.include({value: 1});
expect(scope.get('x')) expect(scope.get('x'))
.to.equal(1); .to.equal(1);
scope.set('O', {x: 5}); scope.set('O', {x: 5});
expect(evaluate(await first('O.x -= 4'), {scope})) expect(evaluate(await expression('O.x -= 4'), {scope}))
.to.deep.include({value: 1}); .to.deep.include({value: 1});
expect(scope.get('O').x) expect(scope.get('O').x)
.to.equal(1); .to.equal(1);
@ -52,12 +52,12 @@ scopeTest('evaluates -=', async ({scope}) => {
scopeTest('evaluates *=', async ({scope}) => { scopeTest('evaluates *=', async ({scope}) => {
scope.set('x', 5); scope.set('x', 5);
expect(evaluate(await first('x *= 4'), {scope})) expect(evaluate(await expression('x *= 4'), {scope}))
.to.deep.include({value: 20}); .to.deep.include({value: 20});
expect(scope.get('x')) expect(scope.get('x'))
.to.equal(20); .to.equal(20);
scope.set('O', {x: 5}); scope.set('O', {x: 5});
expect(evaluate(await first('O.x *= 4'), {scope})) expect(evaluate(await expression('O.x *= 4'), {scope}))
.to.deep.include({value: 20}); .to.deep.include({value: 20});
expect(scope.get('O').x) expect(scope.get('O').x)
.to.equal(20); .to.equal(20);
@ -65,12 +65,12 @@ scopeTest('evaluates *=', async ({scope}) => {
scopeTest('evaluates /=', async ({scope}) => { scopeTest('evaluates /=', async ({scope}) => {
scope.set('x', 25); scope.set('x', 25);
expect(evaluate(await first('x /= 5'), {scope})) expect(evaluate(await expression('x /= 5'), {scope}))
.to.deep.include({value: 5}); .to.deep.include({value: 5});
expect(scope.get('x')) expect(scope.get('x'))
.to.equal(5); .to.equal(5);
scope.set('O', {x: 25}); scope.set('O', {x: 25});
expect(evaluate(await first('O.x /= 5'), {scope})) expect(evaluate(await expression('O.x /= 5'), {scope}))
.to.deep.include({value: 5}); .to.deep.include({value: 5});
expect(scope.get('O').x) expect(scope.get('O').x)
.to.equal(5); .to.equal(5);
@ -78,12 +78,12 @@ scopeTest('evaluates /=', async ({scope}) => {
scopeTest('evaluates %=', async ({scope}) => { scopeTest('evaluates %=', async ({scope}) => {
scope.set('x', 5); scope.set('x', 5);
expect(evaluate(await first('x %= 2'), {scope})) expect(evaluate(await expression('x %= 2'), {scope}))
.to.deep.include({value: 1}); .to.deep.include({value: 1});
expect(scope.get('x')) expect(scope.get('x'))
.to.equal(1); .to.equal(1);
scope.set('O', {x: 5}); scope.set('O', {x: 5});
expect(evaluate(await first('O.x %= 2'), {scope})) expect(evaluate(await expression('O.x %= 2'), {scope}))
.to.deep.include({value: 1}); .to.deep.include({value: 1});
expect(scope.get('O').x) expect(scope.get('O').x)
.to.equal(1); .to.equal(1);
@ -91,12 +91,12 @@ scopeTest('evaluates %=', async ({scope}) => {
scopeTest('evaluates **=', async ({scope}) => { scopeTest('evaluates **=', async ({scope}) => {
scope.set('x', 5); scope.set('x', 5);
expect(evaluate(await first('x **= 3'), {scope})) expect(evaluate(await expression('x **= 3'), {scope}))
.to.deep.include({value: 125}); .to.deep.include({value: 125});
expect(scope.get('x')) expect(scope.get('x'))
.to.equal(125); .to.equal(125);
scope.set('O', {x: 5}); scope.set('O', {x: 5});
expect(evaluate(await first('O.x **= 3'), {scope})) expect(evaluate(await expression('O.x **= 3'), {scope}))
.to.deep.include({value: 125}); .to.deep.include({value: 125});
expect(scope.get('O').x) expect(scope.get('O').x)
.to.equal(125); .to.equal(125);
@ -104,12 +104,12 @@ scopeTest('evaluates **=', async ({scope}) => {
scopeTest('evaluates <<=', async ({scope}) => { scopeTest('evaluates <<=', async ({scope}) => {
scope.set('x', 2); scope.set('x', 2);
expect(evaluate(await first('x <<= 1'), {scope})) expect(evaluate(await expression('x <<= 1'), {scope}))
.to.deep.include({value: 4}); .to.deep.include({value: 4});
expect(scope.get('x')) expect(scope.get('x'))
.to.equal(4); .to.equal(4);
scope.set('O', {x: 2}); scope.set('O', {x: 2});
expect(evaluate(await first('O.x <<= 1'), {scope})) expect(evaluate(await expression('O.x <<= 1'), {scope}))
.to.deep.include({value: 4}); .to.deep.include({value: 4});
expect(scope.get('O').x) expect(scope.get('O').x)
.to.equal(4); .to.equal(4);
@ -117,12 +117,12 @@ scopeTest('evaluates <<=', async ({scope}) => {
scopeTest('evaluates >>=', async ({scope}) => { scopeTest('evaluates >>=', async ({scope}) => {
scope.set('x', 8); scope.set('x', 8);
expect(evaluate(await first('x >>= 2'), {scope})) expect(evaluate(await expression('x >>= 2'), {scope}))
.to.deep.include({value: 2}); .to.deep.include({value: 2});
expect(scope.get('x')) expect(scope.get('x'))
.to.equal(2); .to.equal(2);
scope.set('O', {x: 8}); scope.set('O', {x: 8});
expect(evaluate(await first('O.x >>= 2'), {scope})) expect(evaluate(await expression('O.x >>= 2'), {scope}))
.to.deep.include({value: 2}); .to.deep.include({value: 2});
expect(scope.get('O').x) expect(scope.get('O').x)
.to.equal(2); .to.equal(2);
@ -130,12 +130,12 @@ scopeTest('evaluates >>=', async ({scope}) => {
scopeTest('evaluates >>>=', async ({scope}) => { scopeTest('evaluates >>>=', async ({scope}) => {
scope.set('x', -1); scope.set('x', -1);
expect(evaluate(await first('x >>>= 1'), {scope})) expect(evaluate(await expression('x >>>= 1'), {scope}))
.to.deep.include({value: 2147483647}); .to.deep.include({value: 2147483647});
expect(scope.get('x')) expect(scope.get('x'))
.to.equal(2147483647); .to.equal(2147483647);
scope.set('O', {x: -1}); scope.set('O', {x: -1});
expect(evaluate(await first('O.x >>>= 1'), {scope})) expect(evaluate(await expression('O.x >>>= 1'), {scope}))
.to.deep.include({value: 2147483647}); .to.deep.include({value: 2147483647});
expect(scope.get('O').x) expect(scope.get('O').x)
.to.equal(2147483647); .to.equal(2147483647);
@ -143,12 +143,12 @@ scopeTest('evaluates >>>=', async ({scope}) => {
scopeTest('evaluates |=', async ({scope}) => { scopeTest('evaluates |=', async ({scope}) => {
scope.set('x', 3); scope.set('x', 3);
expect(evaluate(await first('x |= 5'), {scope})) expect(evaluate(await expression('x |= 5'), {scope}))
.to.deep.include({value: 7}); .to.deep.include({value: 7});
expect(scope.get('x')) expect(scope.get('x'))
.to.equal(7); .to.equal(7);
scope.set('O', {x: 3}); scope.set('O', {x: 3});
expect(evaluate(await first('O.x |= 5'), {scope})) expect(evaluate(await expression('O.x |= 5'), {scope}))
.to.deep.include({value: 7}); .to.deep.include({value: 7});
expect(scope.get('O').x) expect(scope.get('O').x)
.to.equal(7); .to.equal(7);
@ -156,12 +156,12 @@ scopeTest('evaluates |=', async ({scope}) => {
scopeTest('evaluates ^=', async ({scope}) => { scopeTest('evaluates ^=', async ({scope}) => {
scope.set('x', 7); scope.set('x', 7);
expect(evaluate(await first('x ^= 2'), {scope})) expect(evaluate(await expression('x ^= 2'), {scope}))
.to.deep.include({value: 5}); .to.deep.include({value: 5});
expect(scope.get('x')) expect(scope.get('x'))
.to.equal(5); .to.equal(5);
scope.set('O', {x: 7}); scope.set('O', {x: 7});
expect(evaluate(await first('O.x ^= 2'), {scope})) expect(evaluate(await expression('O.x ^= 2'), {scope}))
.to.deep.include({value: 5}); .to.deep.include({value: 5});
expect(scope.get('O').x) expect(scope.get('O').x)
.to.equal(5); .to.equal(5);
@ -169,12 +169,12 @@ scopeTest('evaluates ^=', async ({scope}) => {
scopeTest('evaluates &=', async ({scope}) => { scopeTest('evaluates &=', async ({scope}) => {
scope.set('x', 5); scope.set('x', 5);
expect(evaluate(await first('x &= 3'), {scope})) expect(evaluate(await expression('x &= 3'), {scope}))
.to.deep.include({value: 1}); .to.deep.include({value: 1});
expect(scope.get('x')) expect(scope.get('x'))
.to.equal(1); .to.equal(1);
scope.set('O', {x: 5}); scope.set('O', {x: 5});
expect(evaluate(await first('O.x &= 3'), {scope})) expect(evaluate(await expression('O.x &= 3'), {scope}))
.to.deep.include({value: 1}); .to.deep.include({value: 1});
expect(scope.get('O').x) expect(scope.get('O').x)
.to.equal(1); .to.equal(1);
@ -182,12 +182,12 @@ scopeTest('evaluates &=', async ({scope}) => {
scopeTest('evaluates ||=', async ({scope}) => { scopeTest('evaluates ||=', async ({scope}) => {
scope.set('x', false); scope.set('x', false);
expect(evaluate(await first('x ||= true'), {scope})) expect(evaluate(await expression('x ||= true'), {scope}))
.to.deep.include({value: true}); .to.deep.include({value: true});
expect(scope.get('x')) expect(scope.get('x'))
.to.equal(true); .to.equal(true);
scope.set('O', {x: false}); scope.set('O', {x: false});
expect(evaluate(await first('O.x ||= true'), {scope})) expect(evaluate(await expression('O.x ||= true'), {scope}))
.to.deep.include({value: true}); .to.deep.include({value: true});
expect(scope.get('O').x) expect(scope.get('O').x)
.to.equal(true); .to.equal(true);
@ -195,20 +195,20 @@ scopeTest('evaluates ||=', async ({scope}) => {
scopeTest('evaluates &&=', async ({scope}) => { scopeTest('evaluates &&=', async ({scope}) => {
scope.set('x', true); scope.set('x', true);
expect(evaluate(await first('x &&= true'), {scope})) expect(evaluate(await expression('x &&= true'), {scope}))
.to.deep.include({value: true}); .to.deep.include({value: true});
expect(scope.get('x')) expect(scope.get('x'))
.to.equal(true); .to.equal(true);
expect(evaluate(await first('x &&= false'), {scope})) expect(evaluate(await expression('x &&= false'), {scope}))
.to.deep.include({value: false}); .to.deep.include({value: false});
expect(scope.get('x')) expect(scope.get('x'))
.to.equal(false); .to.equal(false);
scope.set('O', {x: true}); scope.set('O', {x: true});
expect(evaluate(await first('O.x &&= true'), {scope})) expect(evaluate(await expression('O.x &&= true'), {scope}))
.to.deep.include({value: true}); .to.deep.include({value: true});
expect(scope.get('O').x) expect(scope.get('O').x)
.to.equal(true); .to.equal(true);
expect(evaluate(await first('O.x &&= false'), {scope})) expect(evaluate(await expression('O.x &&= false'), {scope}))
.to.deep.include({value: false}); .to.deep.include({value: false});
expect(scope.get('O').x) expect(scope.get('O').x)
.to.equal(false); .to.equal(false);
@ -216,48 +216,48 @@ scopeTest('evaluates &&=', async ({scope}) => {
scopeTest('evaluates ??=', async ({scope}) => { scopeTest('evaluates ??=', async ({scope}) => {
scope.set('x', null); scope.set('x', null);
expect(evaluate(await first('x ??= 2'), {scope})) expect(evaluate(await expression('x ??= 2'), {scope}))
.to.deep.include({value: 2}); .to.deep.include({value: 2});
expect(scope.get('x')) expect(scope.get('x'))
.to.equal(2); .to.equal(2);
expect(evaluate(await first('x ??= 4'), {scope})) expect(evaluate(await expression('x ??= 4'), {scope}))
.to.deep.include({value: 2}); .to.deep.include({value: 2});
expect(scope.get('x')) expect(scope.get('x'))
.to.equal(2); .to.equal(2);
scope.set('O', {x: null}); scope.set('O', {x: null});
expect(evaluate(await first('O.x ??= 2'), {scope})) expect(evaluate(await expression('O.x ??= 2'), {scope}))
.to.deep.include({value: 2}); .to.deep.include({value: 2});
expect(scope.get('O').x) expect(scope.get('O').x)
.to.equal(2); .to.equal(2);
expect(evaluate(await first('O.x ??= 4'), {scope})) expect(evaluate(await expression('O.x ??= 4'), {scope}))
.to.deep.include({value: 2}); .to.deep.include({value: 2});
expect(scope.get('O').x) expect(scope.get('O').x)
.to.equal(2); .to.equal(2);
}); });
scopeTest('evaluates promised assignment', async ({scope}) => { scopeTest('evaluates promised assignment', async ({scope}) => {
const evaluated = evaluate(await first('x = await 4'), {scope}); const evaluated = evaluate(await expression('x = await 4'), {scope});
expect(evaluated.async) expect(evaluated.async)
.to.equal(true); .to.equal(true);
expect(await evaluated.value) expect(await evaluated.value)
.to.equal(4); .to.equal(4);
expect(await scope.get('x')) expect(await scope.get('x'))
.to.equal(4); .to.equal(4);
const evaluatedComputedObject = evaluate(await first('O["x"] = await 4'), {scope}); const evaluatedComputedObject = evaluate(await expression('O["x"] = await 4'), {scope});
expect(evaluatedComputedObject.async) expect(evaluatedComputedObject.async)
.to.equal(true); .to.equal(true);
expect(await evaluatedComputedObject.value) expect(await evaluatedComputedObject.value)
.to.equal(4); .to.equal(4);
expect(await scope.get('O').x) expect(await scope.get('O').x)
.to.equal(4); .to.equal(4);
const evaluatedObject = evaluate(await first('O.x = await 4'), {scope}); const evaluatedObject = evaluate(await expression('O.x = await 4'), {scope});
expect(evaluatedObject.async) expect(evaluatedObject.async)
.to.equal(true); .to.equal(true);
expect(await evaluatedObject.value) expect(await evaluatedObject.value)
.to.equal(4); .to.equal(4);
expect(await scope.get('O').x) expect(await scope.get('O').x)
.to.equal(4); .to.equal(4);
const evaluatedPromisedObject = evaluate(await first('(await O).x = await 4'), {scope}); const evaluatedPromisedObject = evaluate(await expression('(await O).x = await 4'), {scope});
expect(evaluatedPromisedObject.async) expect(evaluatedPromisedObject.async)
.to.equal(true); .to.equal(true);
expect(await evaluatedPromisedObject.value) expect(await evaluatedPromisedObject.value)

View File

@ -1,10 +1,10 @@
import {expect, test} from 'vitest'; import {expect, test} from 'vitest';
import {first} from '@/swcx/builders.js';
import evaluate from '@/swcx/evaluate.js'; import evaluate from '@/swcx/evaluate.js';
import expression from '@/swcx/test/expression.js';
test('evaluates await expressions', async () => { test('evaluates await expressions', async () => {
const evaluated = evaluate(await first('await 1')); const evaluated = evaluate(await expression('await 1'));
expect(evaluated.async) expect(evaluated.async)
.to.equal(true); .to.equal(true);
expect(await evaluated.value) expect(await evaluated.value)
@ -12,7 +12,7 @@ test('evaluates await expressions', async () => {
}); });
test('coalesces promises', async () => { test('coalesces promises', async () => {
const evaluated = evaluate(await first('await await await 1')); const evaluated = evaluate(await expression('await await await 1'));
expect(evaluated.async) expect(evaluated.async)
.to.equal(true); .to.equal(true);
expect(await evaluated.value) expect(await evaluated.value)

View File

@ -1,150 +1,150 @@
import {expect, test} from 'vitest'; import {expect, test} from 'vitest';
import {first} from '@/swcx/builders.js';
import evaluate from '@/swcx/evaluate.js'; import evaluate from '@/swcx/evaluate.js';
import expression from '@/swcx/test/expression.js';
test('evaluates +', async () => { test('evaluates +', async () => {
expect(evaluate(await first('10 + 2'))) expect(evaluate(await expression('10 + 2')))
.to.deep.include({value: 12}); .to.deep.include({value: 12});
}); });
test('evaluates -', async () => { test('evaluates -', async () => {
expect(evaluate(await first('10 - 2'))) expect(evaluate(await expression('10 - 2')))
.to.deep.include({value: 8}); .to.deep.include({value: 8});
}); });
test('evaluates /', async () => { test('evaluates /', async () => {
expect(evaluate(await first('10 / 2'))) expect(evaluate(await expression('10 / 2')))
.to.deep.include({value: 5}); .to.deep.include({value: 5});
}); });
test('evaluates %', async () => { test('evaluates %', async () => {
expect(evaluate(await first('10 % 3'))) expect(evaluate(await expression('10 % 3')))
.to.deep.include({value: 1}); .to.deep.include({value: 1});
}); });
test('evaluates *', async () => { test('evaluates *', async () => {
expect(evaluate(await first('10 * 2'))) expect(evaluate(await expression('10 * 2')))
.to.deep.include({value: 20}); .to.deep.include({value: 20});
}); });
test('evaluates >', async () => { test('evaluates >', async () => {
expect(evaluate(await first('10 > 2'))) expect(evaluate(await expression('10 > 2')))
.to.deep.include({value: true}); .to.deep.include({value: true});
}); });
test('evaluates <', async () => { test('evaluates <', async () => {
expect(evaluate(await first('10 < 2'))) expect(evaluate(await expression('10 < 2')))
.to.deep.include({value: false}); .to.deep.include({value: false});
}); });
test('evaluates in', async () => { test('evaluates in', async () => {
expect(evaluate(await first('"i" in {i: 69}'))) expect(evaluate(await expression('"i" in {i: 69}')))
.to.deep.include({value: true}); .to.deep.include({value: true});
expect(evaluate(await first('"j" in {i: 69}'))) expect(evaluate(await expression('"j" in {i: 69}')))
.to.deep.include({value: false}); .to.deep.include({value: false});
}); });
test('evaluates >=', async () => { test('evaluates >=', async () => {
expect(evaluate(await first('10 >= 2'))) expect(evaluate(await expression('10 >= 2')))
.to.deep.include({value: true}); .to.deep.include({value: true});
expect(evaluate(await first('2 >= 2'))) expect(evaluate(await expression('2 >= 2')))
.to.deep.include({value: true}); .to.deep.include({value: true});
expect(evaluate(await first('1 >= 2'))) expect(evaluate(await expression('1 >= 2')))
.to.deep.include({value: false}); .to.deep.include({value: false});
}); });
test('evaluates <=', async () => { test('evaluates <=', async () => {
expect(evaluate(await first('10 <= 2'))) expect(evaluate(await expression('10 <= 2')))
.to.deep.include({value: false}); .to.deep.include({value: false});
expect(evaluate(await first('2 <= 2'))) expect(evaluate(await expression('2 <= 2')))
.to.deep.include({value: true}); .to.deep.include({value: true});
expect(evaluate(await first('1 <= 2'))) expect(evaluate(await expression('1 <= 2')))
.to.deep.include({value: true}); .to.deep.include({value: true});
}); });
test('evaluates **', async () => { test('evaluates **', async () => {
expect(evaluate(await first('2 ** 16'))) expect(evaluate(await expression('2 ** 16')))
.to.deep.include({value: 65536}); .to.deep.include({value: 65536});
}); });
test('evaluates ===', async () => { test('evaluates ===', async () => {
expect(evaluate(await first('10 === "10"'))) expect(evaluate(await expression('10 === "10"')))
.to.deep.include({value: false}); .to.deep.include({value: false});
expect(evaluate(await first('10 === 10'))) expect(evaluate(await expression('10 === 10')))
.to.deep.include({value: true}); .to.deep.include({value: true});
}); });
test('evaluates !==', async () => { test('evaluates !==', async () => {
expect(evaluate(await first('10 !== "10"'))) expect(evaluate(await expression('10 !== "10"')))
.to.deep.include({value: true}); .to.deep.include({value: true});
expect(evaluate(await first('10 !== 10'))) expect(evaluate(await expression('10 !== 10')))
.to.deep.include({value: false}); .to.deep.include({value: false});
}); });
test('evaluates ^', async () => { test('evaluates ^', async () => {
expect(evaluate(await first('7 ^ 2'))) expect(evaluate(await expression('7 ^ 2')))
.to.deep.include({value: 5}); .to.deep.include({value: 5});
}); });
test('evaluates &', async () => { test('evaluates &', async () => {
expect(evaluate(await first('5 & 3'))) expect(evaluate(await expression('5 & 3')))
.to.deep.include({value: 1}); .to.deep.include({value: 1});
}); });
test('evaluates |', async () => { test('evaluates |', async () => {
expect(evaluate(await first('1 | 2'))) expect(evaluate(await expression('1 | 2')))
.to.deep.include({value: 3}); .to.deep.include({value: 3});
}); });
test('evaluates >>', async () => { test('evaluates >>', async () => {
expect(evaluate(await first('8 >> 1'))) expect(evaluate(await expression('8 >> 1')))
.to.deep.include({value: 4}); .to.deep.include({value: 4});
}); });
test('evaluates <<', async () => { test('evaluates <<', async () => {
expect(evaluate(await first('2 << 1'))) expect(evaluate(await expression('2 << 1')))
.to.deep.include({value: 4}); .to.deep.include({value: 4});
}); });
test('evaluates >>>', async () => { test('evaluates >>>', async () => {
expect(evaluate(await first('-1 >>> 1'))) expect(evaluate(await expression('-1 >>> 1')))
.to.deep.include({value: 2147483647}); .to.deep.include({value: 2147483647});
}); });
test('evaluates ==', async () => { test('evaluates ==', async () => {
expect(evaluate(await first('10 == "10"'))) expect(evaluate(await expression('10 == "10"')))
.to.deep.include({value: true}); .to.deep.include({value: true});
expect(evaluate(await first('10 == 10'))) expect(evaluate(await expression('10 == 10')))
.to.deep.include({value: true}); .to.deep.include({value: true});
expect(evaluate(await first('10 == "ten"'))) expect(evaluate(await expression('10 == "ten"')))
.to.deep.include({value: false}); .to.deep.include({value: false});
}); });
test('evaluates !=', async () => { test('evaluates !=', async () => {
expect(evaluate(await first('10 != "10"'))) expect(evaluate(await expression('10 != "10"')))
.to.deep.include({value: false}); .to.deep.include({value: false});
expect(evaluate(await first('10 != 10'))) expect(evaluate(await expression('10 != 10')))
.to.deep.include({value: false}); .to.deep.include({value: false});
expect(evaluate(await first('10 != "ten"'))) expect(evaluate(await expression('10 != "ten"')))
.to.deep.include({value: true}); .to.deep.include({value: true});
}); });
test('evaluates ||', async () => { test('evaluates ||', async () => {
expect(evaluate(await first('true || true'))) expect(evaluate(await expression('true || true')))
.to.deep.include({value: true}); .to.deep.include({value: true});
expect(evaluate(await first('true || false'))) expect(evaluate(await expression('true || false')))
.to.deep.include({value: true}); .to.deep.include({value: true});
expect(evaluate(await first('false || false'))) expect(evaluate(await expression('false || false')))
.to.deep.include({value: false}); .to.deep.include({value: false});
}); });
test('evaluates &&', async () => { test('evaluates &&', async () => {
expect(evaluate(await first('true && true'))) expect(evaluate(await expression('true && true')))
.to.deep.include({value: true}); .to.deep.include({value: true});
expect(evaluate(await first('true && false'))) expect(evaluate(await expression('true && false')))
.to.deep.include({value: false}); .to.deep.include({value: false});
expect(evaluate(await first('false && false'))) expect(evaluate(await expression('false && false')))
.to.deep.include({value: false}); .to.deep.include({value: false});
}); });
@ -152,11 +152,11 @@ test('evaluates ??', async () => {
const scope = { const scope = {
get() { return undefined; }, get() { return undefined; },
}; };
expect(evaluate(await first('null ?? 1'))) expect(evaluate(await expression('null ?? 1')))
.to.deep.include({value: 1}); .to.deep.include({value: 1});
expect(evaluate(await first('undefined ?? 1'), {scope})) expect(evaluate(await expression('undefined ?? 1'), {scope}))
.to.deep.include({value: 1}); .to.deep.include({value: 1});
expect(evaluate(await first('2 ?? 1'))) expect(evaluate(await expression('2 ?? 1')))
.to.deep.include({value: 2}); .to.deep.include({value: 2});
}); });
@ -164,12 +164,12 @@ test('evaluates instanceof', async () => {
const scope = { const scope = {
get() { return Object; }, get() { return Object; },
}; };
expect(evaluate(await first('({}) instanceof Object'), {scope})) expect(evaluate(await expression('({}) instanceof Object'), {scope}))
.to.deep.include({value: true}); .to.deep.include({value: true});
}); });
test('evaluates promised expressions', async () => { test('evaluates promised expressions', async () => {
const evaluated = evaluate(await first('(await 1) + (await 2)')); const evaluated = evaluate(await expression('(await 1) + (await 2)'));
expect(evaluated.async) expect(evaluated.async)
.to.equal(true); .to.equal(true);
expect(await evaluated.value) expect(await evaluated.value)

View File

@ -1,7 +1,7 @@
import {expect, test} from 'vitest'; import {expect, test} from 'vitest';
import {first} from '@/swcx/builders.js';
import evaluate from '@/swcx/evaluate.js'; import evaluate from '@/swcx/evaluate.js';
import expression from '@/swcx/test/expression.js';
const scopeTest = test.extend({ const scopeTest = test.extend({
scope: async ({}, use) => { scope: async ({}, use) => {
@ -15,7 +15,7 @@ const scopeTest = test.extend({
scopeTest('evaluates calls', async ({scope}) => { scopeTest('evaluates calls', async ({scope}) => {
scope.set('f', (...args) => args.reduce((l, r) => l + r, 0)); scope.set('f', (...args) => args.reduce((l, r) => l + r, 0));
const evaluated = evaluate(await first('f(1, 2, 3)'), {scope}); const evaluated = evaluate(await expression('f(1, 2, 3)'), {scope});
expect(evaluated.value) expect(evaluated.value)
.to.equal(6); .to.equal(6);
}); });
@ -24,12 +24,12 @@ scopeTest('evaluates async calls', async ({scope}) => {
const f = (...args) => args.reduce((l, r) => l + r, 0); const f = (...args) => args.reduce((l, r) => l + r, 0);
scope.set('f', f); scope.set('f', f);
scope.set('O', {f}); scope.set('O', {f});
const evaluated = evaluate(await first('f(await 1, 2, 3)'), {scope}); const evaluated = evaluate(await expression('f(await 1, 2, 3)'), {scope});
expect(evaluated.async) expect(evaluated.async)
.to.equal(true); .to.equal(true);
expect(await evaluated.value) expect(await evaluated.value)
.to.equal(6); .to.equal(6);
const evaluatedOptional = evaluate(await first('O?.f(await 1, 2, 3)'), {scope}); const evaluatedOptional = evaluate(await expression('O?.f(await 1, 2, 3)'), {scope});
expect(evaluatedOptional.async) expect(evaluatedOptional.async)
.to.equal(true); .to.equal(true);
expect(await evaluatedOptional.value) expect(await evaluatedOptional.value)
@ -38,25 +38,25 @@ scopeTest('evaluates async calls', async ({scope}) => {
scopeTest('evaluates member calls', async ({scope}) => { scopeTest('evaluates member calls', async ({scope}) => {
scope.set('O', {f: (...args) => args.reduce((l, r) => l + r, 0)}); scope.set('O', {f: (...args) => args.reduce((l, r) => l + r, 0)});
expect(evaluate(await first('O.f(1, 2, 3)'), {scope}).value) expect(evaluate(await expression('O.f(1, 2, 3)'), {scope}).value)
.to.equal(6); .to.equal(6);
expect(evaluate(await first('O["f"](1, 2, 3)'), {scope}).value) expect(evaluate(await expression('O["f"](1, 2, 3)'), {scope}).value)
.to.equal(6); .to.equal(6);
}); });
scopeTest('evaluates optional calls', async ({scope}) => { scopeTest('evaluates optional calls', async ({scope}) => {
scope.set('O', {}); scope.set('O', {});
expect(evaluate(await first('g?.(1, 2, 3)'), {scope}).value) expect(evaluate(await expression('g?.(1, 2, 3)'), {scope}).value)
.to.equal(undefined); .to.equal(undefined);
expect(evaluate(await first('O?.g(1, 2, 3)'), {scope}).value) expect(evaluate(await expression('O?.g(1, 2, 3)'), {scope}).value)
.to.equal(undefined); .to.equal(undefined);
expect(evaluate(await first('O?.g?.(1, 2, 3)'), {scope}).value) expect(evaluate(await expression('O?.g?.(1, 2, 3)'), {scope}).value)
.to.equal(undefined); .to.equal(undefined);
}); });
scopeTest('evaluates async calls', async ({scope}) => { scopeTest('evaluates async calls', async ({scope}) => {
scope.set('O', {f: (...args) => args.reduce((l, r) => l + r, 0)}); scope.set('O', {f: (...args) => args.reduce((l, r) => l + r, 0)});
const evaluated = evaluate(await first('O.f(await 1, 2, 3)'), {scope}); const evaluated = evaluate(await expression('O.f(await 1, 2, 3)'), {scope});
expect(evaluated.async) expect(evaluated.async)
.to.equal(true); .to.equal(true);
expect(await evaluated.value) expect(await evaluated.value)

View File

@ -1,7 +1,7 @@
import {expect, test} from 'vitest'; import {expect, test} from 'vitest';
import {first} from '@/swcx/builders.js';
import evaluate from '@/swcx/evaluate.js'; import evaluate from '@/swcx/evaluate.js';
import expression from '@/swcx/test/expression.js';
const scopeTest = test.extend({ const scopeTest = test.extend({
scope: async ({}, use) => { scope: async ({}, use) => {
@ -16,11 +16,11 @@ const scopeTest = test.extend({
scopeTest('evaluates conditional expression', async ({scope}) => { scopeTest('evaluates conditional expression', async ({scope}) => {
scope.set('x', true); scope.set('x', true);
let evaluated; let evaluated;
evaluated = evaluate(await first('x ? 2 : 3'), {scope}); evaluated = evaluate(await expression('x ? 2 : 3'), {scope});
expect(evaluated.value) expect(evaluated.value)
.to.equal(2); .to.equal(2);
scope.set('x', false); scope.set('x', false);
evaluated = evaluate(await first('x ? 2 : 3'), {scope}); evaluated = evaluate(await expression('x ? 2 : 3'), {scope});
expect(evaluated.value) expect(evaluated.value)
.to.equal(3); .to.equal(3);
}); });
@ -28,13 +28,13 @@ scopeTest('evaluates conditional expression', async ({scope}) => {
scopeTest('evaluates async conditional expression', async ({scope}) => { scopeTest('evaluates async conditional expression', async ({scope}) => {
scope.set('x', true); scope.set('x', true);
let evaluated; let evaluated;
evaluated = evaluate(await first('(await x) ? 2 : 3'), {scope}); evaluated = evaluate(await expression('(await x) ? 2 : 3'), {scope});
expect(await evaluated.async) expect(await evaluated.async)
.to.equal(true); .to.equal(true);
expect(await evaluated.value) expect(await evaluated.value)
.to.equal(2); .to.equal(2);
scope.set('x', false); scope.set('x', false);
evaluated = evaluate(await first('(await x) ? 2 : 3'), {scope}); evaluated = evaluate(await expression('(await x) ? 2 : 3'), {scope});
expect(await evaluated.async) expect(await evaluated.async)
.to.equal(true); .to.equal(true);
expect(await evaluated.value) expect(await evaluated.value)

View File

@ -1,14 +1,14 @@
import {expect, test} from 'vitest'; import {expect, test} from 'vitest';
import {first} from '@/swcx/builders.js';
import evaluate from '@/swcx/evaluate.js'; import evaluate from '@/swcx/evaluate.js';
import expression from '@/swcx/test/expression.js';
test('evaluates numeric literals', async () => { test('evaluates numeric literals', async () => {
expect(evaluate(await first('1'))) expect(evaluate(await expression('1')))
.to.deep.include({value: 1}); .to.deep.include({value: 1});
}); });
test('evaluates string literals', async () => { test('evaluates string literals', async () => {
expect(evaluate(await first('"1"'))) expect(evaluate(await expression('"1"')))
.to.deep.include({value: '1'}); .to.deep.include({value: '1'});
}); });

View File

@ -1,7 +1,7 @@
import {expect, test} from 'vitest'; import {expect, test} from 'vitest';
import {first} from '@/swcx/builders.js';
import evaluate from '@/swcx/evaluate.js'; import evaluate from '@/swcx/evaluate.js';
import expression from '@/swcx/test/expression.js';
const scopeTest = test.extend({ const scopeTest = test.extend({
scope: async ({}, use) => { scope: async ({}, use) => {
@ -15,28 +15,28 @@ const scopeTest = test.extend({
scopeTest('evaluates member expression', async ({scope}) => { scopeTest('evaluates member expression', async ({scope}) => {
let evaluated; let evaluated;
evaluated = evaluate(await first('O.x'), {scope}); evaluated = evaluate(await expression('O.x'), {scope});
expect(evaluated.value) expect(evaluated.value)
.to.equal(32); .to.equal(32);
}); });
scopeTest('evaluates optional member expression', async ({scope}) => { scopeTest('evaluates optional member expression', async ({scope}) => {
let evaluated; let evaluated;
evaluated = evaluate(await first('O?.y'), {scope}); evaluated = evaluate(await expression('O?.y'), {scope});
expect(evaluated.value) expect(evaluated.value)
.to.equal(undefined); .to.equal(undefined);
}); });
scopeTest('evaluates computed member expression', async ({scope}) => { scopeTest('evaluates computed member expression', async ({scope}) => {
let evaluated; let evaluated;
evaluated = evaluate(await first('O["x"]'), {scope}); evaluated = evaluate(await expression('O["x"]'), {scope});
expect(evaluated.value) expect(evaluated.value)
.to.equal(32); .to.equal(32);
}); });
scopeTest('evaluates async member expression', async ({scope}) => { scopeTest('evaluates async member expression', async ({scope}) => {
let evaluated; let evaluated;
evaluated = evaluate(await first('O[await "x"]'), {scope}); evaluated = evaluate(await expression('O[await "x"]'), {scope});
expect(evaluated.async) expect(evaluated.async)
.to.equal(true); .to.equal(true);
expect(await evaluated.value) expect(await evaluated.value)

View File

@ -1,11 +1,11 @@
import {expect, test} from 'vitest'; import {expect, test} from 'vitest';
import {first} from '@/swcx/builders.js';
import evaluate from '@/swcx/evaluate.js'; import evaluate from '@/swcx/evaluate.js';
import expression from '@/swcx/test/expression.js';
test('evaluates object expression', async () => { test('evaluates object expression', async () => {
let evaluated; let evaluated;
evaluated = evaluate(await first(`({ evaluated = evaluate(await expression(`({
["foo"]: 16, ["foo"]: 16,
bar: 32, bar: 32,
'baz': 64, 'baz': 64,
@ -20,7 +20,7 @@ test('evaluates object expression', async () => {
test('evaluates async object expression', async () => { test('evaluates async object expression', async () => {
let evaluated; let evaluated;
evaluated = evaluate(await first(`({ evaluated = evaluate(await expression(`({
foo: await 32, foo: await 32,
})`)); })`));
expect(evaluated.async) expect(evaluated.async)
@ -33,7 +33,7 @@ test('evaluates async object expression', async () => {
test('evaluates object spread expression', async () => { test('evaluates object spread expression', async () => {
let evaluated; let evaluated;
evaluated = evaluate(await first(`({ evaluated = evaluate(await expression(`({
foo: 16, foo: 16,
...({bar: 32}), ...({bar: 32}),
})`)); })`));
@ -46,7 +46,7 @@ test('evaluates object spread expression', async () => {
test('evaluates async spread expression', async () => { test('evaluates async spread expression', async () => {
let evaluated; let evaluated;
evaluated = evaluate(await first(`({ evaluated = evaluate(await expression(`({
foo: 16, foo: 16,
...(await {bar: 32}), ...(await {bar: 32}),
})`)); })`));

View File

@ -1,40 +1,40 @@
import {expect, test} from 'vitest'; import {expect, test} from 'vitest';
import {first} from '@/swcx/builders.js';
import evaluate from '@/swcx/evaluate.js'; import evaluate from '@/swcx/evaluate.js';
import expression from '@/swcx/test/expression.js';
test('evaluates +', async () => { test('evaluates +', async () => {
expect(evaluate(await first('+1'))) expect(evaluate(await expression('+1')))
.to.deep.include({value: 1}); .to.deep.include({value: 1});
}); });
test('evaluates -', async () => { test('evaluates -', async () => {
expect(evaluate(await first('-1'))) expect(evaluate(await expression('-1')))
.to.deep.include({value: -1}); .to.deep.include({value: -1});
}); });
test('evaluates !', async () => { test('evaluates !', async () => {
expect(evaluate(await first('!true'))) expect(evaluate(await expression('!true')))
.to.deep.include({value: false}); .to.deep.include({value: false});
}); });
test('evaluates ~', async () => { test('evaluates ~', async () => {
expect(evaluate(await first('~1'))) expect(evaluate(await expression('~1')))
.to.deep.include({value: -2}); .to.deep.include({value: -2});
}); });
test('evaluates typeof', async () => { test('evaluates typeof', async () => {
expect(evaluate(await first('typeof "a"'))) expect(evaluate(await expression('typeof "a"')))
.to.deep.include({value: 'string'}); .to.deep.include({value: 'string'});
}); });
test('evaluates void', async () => { test('evaluates void', async () => {
expect(evaluate(await first('void 0'))) expect(evaluate(await expression('void 0')))
.to.deep.include({value: undefined}); .to.deep.include({value: undefined});
}); });
test('evaluates promised unary expression', async () => { test('evaluates promised unary expression', async () => {
const evaluated = evaluate(await first('-(await 4)')); const evaluated = evaluate(await expression('-(await 4)'));
expect(evaluated.async) expect(evaluated.async)
.to.equal(true); .to.equal(true);
expect(await evaluated.value) expect(await evaluated.value)

View File

@ -1,7 +1,7 @@
import {expect, test} from 'vitest'; import {expect, test} from 'vitest';
import {first} from '@/swcx/builders.js';
import evaluate from '@/swcx/evaluate.js'; import evaluate from '@/swcx/evaluate.js';
import expression from '@/swcx/test/expression.js';
const scopeTest = test.extend({ const scopeTest = test.extend({
scope: async ({}, use) => { scope: async ({}, use) => {
@ -15,14 +15,14 @@ const scopeTest = test.extend({
scopeTest('evaluates postfix updates', async ({scope}) => { scopeTest('evaluates postfix updates', async ({scope}) => {
scope.set('x', 4); scope.set('x', 4);
let evaluated = evaluate(await first('y = x++'), {scope}); let evaluated = evaluate(await expression('y = x++'), {scope});
expect(evaluated.value) expect(evaluated.value)
.to.equal(4); .to.equal(4);
expect(scope.get('x')) expect(scope.get('x'))
.to.equal(5); .to.equal(5);
expect(scope.get('y')) expect(scope.get('y'))
.to.equal(4); .to.equal(4);
evaluated = evaluate(await first('y = x--'), {scope}); evaluated = evaluate(await expression('y = x--'), {scope});
expect(evaluated.value) expect(evaluated.value)
.to.equal(5); .to.equal(5);
expect(scope.get('x')) expect(scope.get('x'))
@ -33,14 +33,14 @@ scopeTest('evaluates postfix updates', async ({scope}) => {
scopeTest('evaluates prefix updates', async ({scope}) => { scopeTest('evaluates prefix updates', async ({scope}) => {
scope.set('x', 4); scope.set('x', 4);
let evaluated = evaluate(await first('y = ++x'), {scope}); let evaluated = evaluate(await expression('y = ++x'), {scope});
expect(evaluated.value) expect(evaluated.value)
.to.equal(5); .to.equal(5);
expect(scope.get('x')) expect(scope.get('x'))
.to.equal(5); .to.equal(5);
expect(scope.get('y')) expect(scope.get('y'))
.to.equal(5); .to.equal(5);
evaluated = evaluate(await first('y = --x'), {scope}); evaluated = evaluate(await expression('y = --x'), {scope});
expect(evaluated.value) expect(evaluated.value)
.to.equal(4); .to.equal(4);
expect(scope.get('x')) expect(scope.get('x'))

View File

@ -10,6 +10,7 @@ import {
isIdentifier, isIdentifier,
isIfStatement, isIfStatement,
isObjectPattern, isObjectPattern,
isReturnStatement,
isVariableDeclarator, isVariableDeclarator,
isWhileStatement, isWhileStatement,
} from '@/swcx/types.js'; } from '@/swcx/types.js';
@ -206,7 +207,10 @@ export default class Sandbox {
if (!child) { if (!child) {
continue; continue;
} }
yield* this.execute(child, node); const result = yield* this.execute(child, node);
if (result) {
return result;
}
} }
// Loops... // Loops...
if (isForStatement(node)) { if (isForStatement(node)) {
@ -230,6 +234,10 @@ export default class Sandbox {
} }
} }
} while (loop); } while (loop);
// Top-level return.
if (isReturnStatement(node)) {
return !node.argument ? {value: undefined} : this.evaluate(node.argument);
}
if (isExpressionStatement(node)) { if (isExpressionStatement(node)) {
yield this.evaluate(node.expression); yield this.evaluate(node.expression);
} }

View File

@ -151,7 +151,7 @@ test('evaluates loops', async () => {
}); });
}); });
test('retuns undefined for nonexistent variables in scope', async () => { test('evaluates undefined for nonexistent variables in scope', async () => {
const sandbox = new Sandbox( const sandbox = new Sandbox(
await parse(` await parse(`
const x = y const x = y
@ -164,6 +164,36 @@ test('retuns undefined for nonexistent variables in scope', async () => {
}); });
}); });
test('returns values at the top level', async () => {
let sandbox;
sandbox = new Sandbox(
await parse(`
x = 16
y = 4
return [x * 3, y * 3]
x = 32
y = 8
`, {allowReturnOutsideFunction: true}),
);
const {value: {value}} = sandbox.run();
expect(value)
.to.deep.equal([48, 12]);
expect(sandbox.context)
.to.deep.equal({x: 16, y: 4});
sandbox = new Sandbox(
await parse(`
x = 16
y = 4
return
x = 32
y = 8
`, {allowReturnOutsideFunction: true}),
);
sandbox.run();
expect(sandbox.context)
.to.deep.equal({x: 16, y: 4});
});
test('sets variables in global scope', async () => { test('sets variables in global scope', async () => {
const sandbox = new Sandbox( const sandbox = new Sandbox(
await parse(` await parse(`

View File

@ -1,4 +1,4 @@
export async function first(code) { export default async function(code) {
const {parse} = await import('@swc/core'); const {parse} = await import('@swc/core');
const ast = await parse(code); const ast = await parse(code);
return ast.body[0].expression; return ast.body[0].expression;

View File

@ -29,6 +29,7 @@ export const TRAVERSAL_PATH = {
OptionalChainingExpression: ['base'], OptionalChainingExpression: ['base'],
ParenthesisExpression: ['expression'], ParenthesisExpression: ['expression'],
RegExpLiteral: [], RegExpLiteral: [],
ReturnStatement: ['argument'],
StringLiteral: [], StringLiteral: [],
UnaryExpression: ['argument'], UnaryExpression: ['argument'],
UpdateExpression: ['argument'], UpdateExpression: ['argument'],

View File

@ -92,6 +92,14 @@ export function isObjectPattern(node) {
return true; return true;
} }
export function isReturnStatement(node) {
/* v8 ignore next 3 */
if (!node || node.type !== 'ReturnStatement') {
return false;
}
return true;
}
export function isSpreadElement(node) { export function isSpreadElement(node) {
/* v8 ignore next 3 */ /* v8 ignore next 3 */
if (!node || node.type !== 'SpreadElement') { if (!node || node.type !== 'SpreadElement') {

View File

@ -80,7 +80,9 @@ async function remakeServer(Engine) {
createOnConnect(await createEngine(Engine)); createOnConnect(await createEngine(Engine));
} }
await remakeServer(Engine); (async () => {
await remakeServer(Engine);
})();
if (import.meta.hot) { if (import.meta.hot) {
import.meta.hot.accept('./engine/engine.js', async ({default: Engine}) => { import.meta.hot.accept('./engine/engine.js', async ({default: Engine}) => {

227
package-lock.json generated
View File

@ -19,6 +19,7 @@
"idb-keyval": "^6.2.1", "idb-keyval": "^6.2.1",
"isbot": "^4.1.0", "isbot": "^4.1.0",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"pixi.js": "^7.4.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"ws": "^8.17.0" "ws": "^8.17.0"
@ -3184,11 +3185,20 @@
"node": "^14.17.0 || ^16.13.0 || >=18.0.0" "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
} }
}, },
"node_modules/@pixi/accessibility": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/accessibility/-/accessibility-7.4.2.tgz",
"integrity": "sha512-R6VEolm8uyy1FB1F2qaLKxVbzXAFTZCF2ka8fl9lsz7We6ZfO4QpXv9ur7DvzratjCQUQVCKo0/V7xL5q1EV/g==",
"peerDependencies": {
"@pixi/core": "7.4.2",
"@pixi/display": "7.4.2",
"@pixi/events": "7.4.2"
}
},
"node_modules/@pixi/app": { "node_modules/@pixi/app": {
"version": "7.4.2", "version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/app/-/app-7.4.2.tgz", "resolved": "https://registry.npmjs.org/@pixi/app/-/app-7.4.2.tgz",
"integrity": "sha512-ugkH3kOgjT8P1mTMY29yCOgEh+KuVMAn8uBxeY0aMqaUgIMysfpnFv+Aepp2CtvI9ygr22NC+OiKl+u+eEaQHw==", "integrity": "sha512-ugkH3kOgjT8P1mTMY29yCOgEh+KuVMAn8uBxeY0aMqaUgIMysfpnFv+Aepp2CtvI9ygr22NC+OiKl+u+eEaQHw==",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@pixi/core": "7.4.2", "@pixi/core": "7.4.2",
"@pixi/display": "7.4.2" "@pixi/display": "7.4.2"
@ -3198,7 +3208,6 @@
"version": "7.4.2", "version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/assets/-/assets-7.4.2.tgz", "resolved": "https://registry.npmjs.org/@pixi/assets/-/assets-7.4.2.tgz",
"integrity": "sha512-anxho59H9egZwoaEdM5aLvYyxoz6NCy3CaQIvNHD1bbGg8L16Ih0e26QSBR5fu53jl8OjT6M7s+p6n7uu4+fGA==", "integrity": "sha512-anxho59H9egZwoaEdM5aLvYyxoz6NCy3CaQIvNHD1bbGg8L16Ih0e26QSBR5fu53jl8OjT6M7s+p6n7uu4+fGA==",
"peer": true,
"dependencies": { "dependencies": {
"@types/css-font-loading-module": "^0.0.12" "@types/css-font-loading-module": "^0.0.12"
}, },
@ -3210,7 +3219,6 @@
"version": "7.4.2", "version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/color/-/color-7.4.2.tgz", "resolved": "https://registry.npmjs.org/@pixi/color/-/color-7.4.2.tgz",
"integrity": "sha512-av1LOvhHsiaW8+T4n/FgnOKHby55/w7VcA1HzPIHRBtEcsmxvSCDanT1HU2LslNhrxLPzyVx18nlmalOyt5OBg==", "integrity": "sha512-av1LOvhHsiaW8+T4n/FgnOKHby55/w7VcA1HzPIHRBtEcsmxvSCDanT1HU2LslNhrxLPzyVx18nlmalOyt5OBg==",
"peer": true,
"dependencies": { "dependencies": {
"@pixi/colord": "^2.9.6" "@pixi/colord": "^2.9.6"
} }
@ -3218,20 +3226,26 @@
"node_modules/@pixi/colord": { "node_modules/@pixi/colord": {
"version": "2.9.6", "version": "2.9.6",
"resolved": "https://registry.npmjs.org/@pixi/colord/-/colord-2.9.6.tgz", "resolved": "https://registry.npmjs.org/@pixi/colord/-/colord-2.9.6.tgz",
"integrity": "sha512-nezytU2pw587fQstUu1AsJZDVEynjskwOL+kibwcdxsMBFqPsFFNA7xl0ii/gXuDi6M0xj3mfRJj8pBSc2jCfA==", "integrity": "sha512-nezytU2pw587fQstUu1AsJZDVEynjskwOL+kibwcdxsMBFqPsFFNA7xl0ii/gXuDi6M0xj3mfRJj8pBSc2jCfA=="
"peer": true },
"node_modules/@pixi/compressed-textures": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/compressed-textures/-/compressed-textures-7.4.2.tgz",
"integrity": "sha512-VJrt7el6O4ZJSWkeOGXwrhJaiLg1UBhHB3fj42VR4YloYkAxpfd9K6s6IcbcVz7n9L48APKBMgHyaB2pX2Ck/A==",
"peerDependencies": {
"@pixi/assets": "7.4.2",
"@pixi/core": "7.4.2"
}
}, },
"node_modules/@pixi/constants": { "node_modules/@pixi/constants": {
"version": "7.4.2", "version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/constants/-/constants-7.4.2.tgz", "resolved": "https://registry.npmjs.org/@pixi/constants/-/constants-7.4.2.tgz",
"integrity": "sha512-N9vn6Wpz5WIQg7ugUg2+SdqD2u2+NM0QthE8YzLJ4tLH2Iz+/TrnPKUJzeyIqbg3sxJG5ZpGGPiacqIBpy1KyA==", "integrity": "sha512-N9vn6Wpz5WIQg7ugUg2+SdqD2u2+NM0QthE8YzLJ4tLH2Iz+/TrnPKUJzeyIqbg3sxJG5ZpGGPiacqIBpy1KyA=="
"peer": true
}, },
"node_modules/@pixi/core": { "node_modules/@pixi/core": {
"version": "7.4.2", "version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/core/-/core-7.4.2.tgz", "resolved": "https://registry.npmjs.org/@pixi/core/-/core-7.4.2.tgz",
"integrity": "sha512-UbMtgSEnyCOFPzbE6ThB9qopXxbZ5GCof2ArB4FXOC5Xi/83MOIIYg5kf5M8689C5HJMhg2SrJu3xLKppF+CMg==", "integrity": "sha512-UbMtgSEnyCOFPzbE6ThB9qopXxbZ5GCof2ArB4FXOC5Xi/83MOIIYg5kf5M8689C5HJMhg2SrJu3xLKppF+CMg==",
"peer": true,
"dependencies": { "dependencies": {
"@pixi/color": "7.4.2", "@pixi/color": "7.4.2",
"@pixi/constants": "7.4.2", "@pixi/constants": "7.4.2",
@ -3251,22 +3265,84 @@
"version": "7.4.2", "version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/display/-/display-7.4.2.tgz", "resolved": "https://registry.npmjs.org/@pixi/display/-/display-7.4.2.tgz",
"integrity": "sha512-DaD0J7gIlNlzO0Fdlby/0OH+tB5LtCY6rgFeCBKVDnzmn8wKW3zYZRenWBSFJ0Psx6vLqXYkSIM/rcokaKviIw==", "integrity": "sha512-DaD0J7gIlNlzO0Fdlby/0OH+tB5LtCY6rgFeCBKVDnzmn8wKW3zYZRenWBSFJ0Psx6vLqXYkSIM/rcokaKviIw==",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@pixi/core": "7.4.2" "@pixi/core": "7.4.2"
} }
}, },
"node_modules/@pixi/events": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/events/-/events-7.4.2.tgz",
"integrity": "sha512-Jw/w57heZjzZShIXL0bxOvKB+XgGIevyezhGtfF2ZSzQoSBWo+Fj1uE0QwKd0RIaXegZw/DhSmiMJSbNmcjifA==",
"peerDependencies": {
"@pixi/core": "7.4.2",
"@pixi/display": "7.4.2"
}
},
"node_modules/@pixi/extensions": { "node_modules/@pixi/extensions": {
"version": "7.4.2", "version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/extensions/-/extensions-7.4.2.tgz", "resolved": "https://registry.npmjs.org/@pixi/extensions/-/extensions-7.4.2.tgz",
"integrity": "sha512-Hmx2+O0yZ8XIvgomHM9GZEGcy9S9Dd8flmtOK5Aa3fXs/8v7xD08+ANQpN9ZqWU2Xs+C6UBlpqlt2BWALvKKKA==", "integrity": "sha512-Hmx2+O0yZ8XIvgomHM9GZEGcy9S9Dd8flmtOK5Aa3fXs/8v7xD08+ANQpN9ZqWU2Xs+C6UBlpqlt2BWALvKKKA=="
"peer": true },
"node_modules/@pixi/extract": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/extract/-/extract-7.4.2.tgz",
"integrity": "sha512-JOX27TRWjVEjauGBbF8PU7/g6LYXnivehdgqS5QlVDv1CNHTOrz/j3MdKcVWOhyZPbH5c9sh7lxyRxvd9AIuTQ==",
"peerDependencies": {
"@pixi/core": "7.4.2"
}
},
"node_modules/@pixi/filter-alpha": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/filter-alpha/-/filter-alpha-7.4.2.tgz",
"integrity": "sha512-9OsKJ+yvY2wIcQXwswj5HQBiwNGymwmqdxfp7mo+nZSBoDmxUqvMZzE9UNJ3eUlswuNvNRO8zNOsQvwdz7WFww==",
"peerDependencies": {
"@pixi/core": "7.4.2"
}
},
"node_modules/@pixi/filter-blur": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/filter-blur/-/filter-blur-7.4.2.tgz",
"integrity": "sha512-gOXBbIUx6CRZP1fmsis2wLzzSsofrqmIHhbf1gIkZMIQaLsc9T7brj+PaLTTiOiyJgnvGN5j20RZnkERWWKV0Q==",
"peerDependencies": {
"@pixi/core": "7.4.2"
}
},
"node_modules/@pixi/filter-color-matrix": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/filter-color-matrix/-/filter-color-matrix-7.4.2.tgz",
"integrity": "sha512-ykZiR59Gvj80UKs9qm7jeUTKvn+wWk6HBVJOmJbK9jFK5juakDWp7BbH26U78Q61EWj97kI1FdfcbMkuQ7rqkA==",
"peerDependencies": {
"@pixi/core": "7.4.2"
}
},
"node_modules/@pixi/filter-displacement": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/filter-displacement/-/filter-displacement-7.4.2.tgz",
"integrity": "sha512-QS/eWp/ivsxef3xapNeGwpPX7vrqQQeo99Fux4k5zsvplnNEsf91t6QYJLG776AbZEu/qh8VYRBA5raIVY/REw==",
"peerDependencies": {
"@pixi/core": "7.4.2"
}
},
"node_modules/@pixi/filter-fxaa": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/filter-fxaa/-/filter-fxaa-7.4.2.tgz",
"integrity": "sha512-U/ptJgDsfs/r8y2a6gCaiPfDu2IFAxpQ4wtfmBpz6vRhqeE4kI8yNIUx5dZbui57zlsJaW0BNacOQxHU0vLkyQ==",
"peerDependencies": {
"@pixi/core": "7.4.2"
}
},
"node_modules/@pixi/filter-noise": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/filter-noise/-/filter-noise-7.4.2.tgz",
"integrity": "sha512-Vy9ViBFhZEGh6xKkd3kFWErolZTwv1Y5Qb1bV7qPIYbvBECYsqzlR4uCrrjBV6KKm0PufpG/+NKC5vICZaqKzg==",
"peerDependencies": {
"@pixi/core": "7.4.2"
}
}, },
"node_modules/@pixi/graphics": { "node_modules/@pixi/graphics": {
"version": "7.4.2", "version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/graphics/-/graphics-7.4.2.tgz", "resolved": "https://registry.npmjs.org/@pixi/graphics/-/graphics-7.4.2.tgz",
"integrity": "sha512-jH4/Tum2RqWzHGzvlwEr7HIVduoLO57Ze705N2zQPkUD57TInn5911aGUeoua7f/wK8cTLGzgB9BzSo2kTdcHw==", "integrity": "sha512-jH4/Tum2RqWzHGzvlwEr7HIVduoLO57Ze705N2zQPkUD57TInn5911aGUeoua7f/wK8cTLGzgB9BzSo2kTdcHw==",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@pixi/core": "7.4.2", "@pixi/core": "7.4.2",
"@pixi/display": "7.4.2", "@pixi/display": "7.4.2",
@ -3276,14 +3352,12 @@
"node_modules/@pixi/math": { "node_modules/@pixi/math": {
"version": "7.4.2", "version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/math/-/math-7.4.2.tgz", "resolved": "https://registry.npmjs.org/@pixi/math/-/math-7.4.2.tgz",
"integrity": "sha512-7jHmCQoYk6e0rfSKjdNFOPl0wCcdgoraxgteXJTTHv3r0bMNx2pHD9FJ0VvocEUG7XHfj55O3+u7yItOAx0JaQ==", "integrity": "sha512-7jHmCQoYk6e0rfSKjdNFOPl0wCcdgoraxgteXJTTHv3r0bMNx2pHD9FJ0VvocEUG7XHfj55O3+u7yItOAx0JaQ=="
"peer": true
}, },
"node_modules/@pixi/mesh": { "node_modules/@pixi/mesh": {
"version": "7.4.2", "version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/mesh/-/mesh-7.4.2.tgz", "resolved": "https://registry.npmjs.org/@pixi/mesh/-/mesh-7.4.2.tgz",
"integrity": "sha512-mEkKyQvvMrYXC3pahvH5WBIKtrtB63WixRr91ANFI7zXD+ESG6Ap6XtxMCJmXDQPwBDNk7SWVMiCflYuchG7kA==", "integrity": "sha512-mEkKyQvvMrYXC3pahvH5WBIKtrtB63WixRr91ANFI7zXD+ESG6Ap6XtxMCJmXDQPwBDNk7SWVMiCflYuchG7kA==",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@pixi/core": "7.4.2", "@pixi/core": "7.4.2",
"@pixi/display": "7.4.2" "@pixi/display": "7.4.2"
@ -3293,23 +3367,59 @@
"version": "7.4.2", "version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/mesh-extras/-/mesh-extras-7.4.2.tgz", "resolved": "https://registry.npmjs.org/@pixi/mesh-extras/-/mesh-extras-7.4.2.tgz",
"integrity": "sha512-vNR/7wjxjs7sv9fGoKkHyU91ZAD+7EnMHBS5F3CVISlOIFxLi96NNZCB81oUIdky/90pHw40johd/4izR5zTyw==", "integrity": "sha512-vNR/7wjxjs7sv9fGoKkHyU91ZAD+7EnMHBS5F3CVISlOIFxLi96NNZCB81oUIdky/90pHw40johd/4izR5zTyw==",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@pixi/core": "7.4.2", "@pixi/core": "7.4.2",
"@pixi/mesh": "7.4.2" "@pixi/mesh": "7.4.2"
} }
}, },
"node_modules/@pixi/mixin-cache-as-bitmap": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/mixin-cache-as-bitmap/-/mixin-cache-as-bitmap-7.4.2.tgz",
"integrity": "sha512-6dgthi2ruUT/lervSrFDQ7vXkEsHo6CxdgV7W/wNdW1dqgQlKfDvO6FhjXzyIMRLSooUf5FoeluVtfsjkUIYrw==",
"peerDependencies": {
"@pixi/core": "7.4.2",
"@pixi/display": "7.4.2",
"@pixi/sprite": "7.4.2"
}
},
"node_modules/@pixi/mixin-get-child-by-name": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/mixin-get-child-by-name/-/mixin-get-child-by-name-7.4.2.tgz",
"integrity": "sha512-0Cfw8JpQhsixprxiYph4Lj+B5n83Kk4ftNMXgM5xtZz+tVLz5s91qR0MqcdzwTGTJ7utVygiGmS4/3EfR/duRQ==",
"peerDependencies": {
"@pixi/display": "7.4.2"
}
},
"node_modules/@pixi/mixin-get-global-position": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/mixin-get-global-position/-/mixin-get-global-position-7.4.2.tgz",
"integrity": "sha512-LcsahbVdX4DFS2IcGfNp4KaXuu7SjAwUp/flZSGIfstyKOKb5FWFgihtqcc9ZT4coyri3gs2JbILZub/zPZj1w==",
"peerDependencies": {
"@pixi/core": "7.4.2",
"@pixi/display": "7.4.2"
}
},
"node_modules/@pixi/particle-container": { "node_modules/@pixi/particle-container": {
"version": "7.4.2", "version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/particle-container/-/particle-container-7.4.2.tgz", "resolved": "https://registry.npmjs.org/@pixi/particle-container/-/particle-container-7.4.2.tgz",
"integrity": "sha512-B78Qq86kt0lEa5WtB2YFIm3+PjhKfw9La9R++GBSgABl+g13s2UaZ6BIPxvY3JxWMdxPm4iPrQPFX1QWRN68mw==", "integrity": "sha512-B78Qq86kt0lEa5WtB2YFIm3+PjhKfw9La9R++GBSgABl+g13s2UaZ6BIPxvY3JxWMdxPm4iPrQPFX1QWRN68mw==",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@pixi/core": "7.4.2", "@pixi/core": "7.4.2",
"@pixi/display": "7.4.2", "@pixi/display": "7.4.2",
"@pixi/sprite": "7.4.2" "@pixi/sprite": "7.4.2"
} }
}, },
"node_modules/@pixi/prepare": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/prepare/-/prepare-7.4.2.tgz",
"integrity": "sha512-PugyMzReCHXUzc3so9PPJj2OdHwibpUNWyqG4mWY2UUkb6c8NAGK1AnAPiscOvLilJcv/XQSFoNhX+N1jrvJEg==",
"peerDependencies": {
"@pixi/core": "7.4.2",
"@pixi/display": "7.4.2",
"@pixi/graphics": "7.4.2",
"@pixi/text": "7.4.2"
}
},
"node_modules/@pixi/react": { "node_modules/@pixi/react": {
"version": "7.1.2", "version": "7.1.2",
"resolved": "https://registry.npmjs.org/@pixi/react/-/react-7.1.2.tgz", "resolved": "https://registry.npmjs.org/@pixi/react/-/react-7.1.2.tgz",
@ -3346,14 +3456,12 @@
"node_modules/@pixi/runner": { "node_modules/@pixi/runner": {
"version": "7.4.2", "version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/runner/-/runner-7.4.2.tgz", "resolved": "https://registry.npmjs.org/@pixi/runner/-/runner-7.4.2.tgz",
"integrity": "sha512-LPBpwym4vdyyDY5ucF4INQccaGyxztERyLTY1YN6aqJyyMmnc7iqXlIKt+a0euMBtNoLoxy6MWMvIuZj0JfFPA==", "integrity": "sha512-LPBpwym4vdyyDY5ucF4INQccaGyxztERyLTY1YN6aqJyyMmnc7iqXlIKt+a0euMBtNoLoxy6MWMvIuZj0JfFPA=="
"peer": true
}, },
"node_modules/@pixi/settings": { "node_modules/@pixi/settings": {
"version": "7.4.2", "version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/settings/-/settings-7.4.2.tgz", "resolved": "https://registry.npmjs.org/@pixi/settings/-/settings-7.4.2.tgz",
"integrity": "sha512-pMN+L6aWgvUbwhFIL/BTHKe2ShYGPZ8h9wlVBnFHMtUcJcFLMF1B3lzuvCayZRepOphs6RY0TqvnDvVb585JhQ==", "integrity": "sha512-pMN+L6aWgvUbwhFIL/BTHKe2ShYGPZ8h9wlVBnFHMtUcJcFLMF1B3lzuvCayZRepOphs6RY0TqvnDvVb585JhQ==",
"peer": true,
"dependencies": { "dependencies": {
"@pixi/constants": "7.4.2", "@pixi/constants": "7.4.2",
"@types/css-font-loading-module": "^0.0.12", "@types/css-font-loading-module": "^0.0.12",
@ -3364,7 +3472,6 @@
"version": "7.4.2", "version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/sprite/-/sprite-7.4.2.tgz", "resolved": "https://registry.npmjs.org/@pixi/sprite/-/sprite-7.4.2.tgz",
"integrity": "sha512-Ccf/OVQsB+HQV0Fyf5lwD+jk1jeU7uSIqEjbxenNNssmEdB7S5qlkTBV2EJTHT83+T6Z9OMOHsreJZerydpjeg==", "integrity": "sha512-Ccf/OVQsB+HQV0Fyf5lwD+jk1jeU7uSIqEjbxenNNssmEdB7S5qlkTBV2EJTHT83+T6Z9OMOHsreJZerydpjeg==",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@pixi/core": "7.4.2", "@pixi/core": "7.4.2",
"@pixi/display": "7.4.2" "@pixi/display": "7.4.2"
@ -3374,7 +3481,6 @@
"version": "7.4.2", "version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/sprite-animated/-/sprite-animated-7.4.2.tgz", "resolved": "https://registry.npmjs.org/@pixi/sprite-animated/-/sprite-animated-7.4.2.tgz",
"integrity": "sha512-QPT6yxCUGOBN+98H3pyIZ1ZO6Y7BN1o0Q2IMZEsD1rNfZJrTYS3Q8VlCG5t2YlFlcB8j5iBo24bZb6FUxLOmsQ==", "integrity": "sha512-QPT6yxCUGOBN+98H3pyIZ1ZO6Y7BN1o0Q2IMZEsD1rNfZJrTYS3Q8VlCG5t2YlFlcB8j5iBo24bZb6FUxLOmsQ==",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@pixi/core": "7.4.2", "@pixi/core": "7.4.2",
"@pixi/sprite": "7.4.2" "@pixi/sprite": "7.4.2"
@ -3384,7 +3490,6 @@
"version": "7.4.2", "version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/sprite-tiling/-/sprite-tiling-7.4.2.tgz", "resolved": "https://registry.npmjs.org/@pixi/sprite-tiling/-/sprite-tiling-7.4.2.tgz",
"integrity": "sha512-Z8PP6ewy3nuDYL+NeEdltHAhuucVgia33uzAitvH3OqqRSx6a6YRBFbNLUM9Sx+fBO2Lk3PpV1g6QZX+NE5LOg==", "integrity": "sha512-Z8PP6ewy3nuDYL+NeEdltHAhuucVgia33uzAitvH3OqqRSx6a6YRBFbNLUM9Sx+fBO2Lk3PpV1g6QZX+NE5LOg==",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@pixi/core": "7.4.2", "@pixi/core": "7.4.2",
"@pixi/display": "7.4.2", "@pixi/display": "7.4.2",
@ -3404,7 +3509,6 @@
"version": "7.4.2", "version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/text/-/text-7.4.2.tgz", "resolved": "https://registry.npmjs.org/@pixi/text/-/text-7.4.2.tgz",
"integrity": "sha512-rZZWpJNsIQ8WoCWrcVg8Gi6L/PDakB941clo6dO3XjoII2ucoOUcnpe5HIkudxi2xPvS/8Bfq990gFEx50TP5A==", "integrity": "sha512-rZZWpJNsIQ8WoCWrcVg8Gi6L/PDakB941clo6dO3XjoII2ucoOUcnpe5HIkudxi2xPvS/8Bfq990gFEx50TP5A==",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@pixi/core": "7.4.2", "@pixi/core": "7.4.2",
"@pixi/sprite": "7.4.2" "@pixi/sprite": "7.4.2"
@ -3414,7 +3518,6 @@
"version": "7.4.2", "version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/text-bitmap/-/text-bitmap-7.4.2.tgz", "resolved": "https://registry.npmjs.org/@pixi/text-bitmap/-/text-bitmap-7.4.2.tgz",
"integrity": "sha512-lPBMJ83JnpFVL+6ckQ8KO8QmwdPm0z9Zs/M0NgFKH2F+BcjelRNnk80NI3O0qBDYSEDQIE+cFbKoZ213kf7zwA==", "integrity": "sha512-lPBMJ83JnpFVL+6ckQ8KO8QmwdPm0z9Zs/M0NgFKH2F+BcjelRNnk80NI3O0qBDYSEDQIE+cFbKoZ213kf7zwA==",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@pixi/assets": "7.4.2", "@pixi/assets": "7.4.2",
"@pixi/core": "7.4.2", "@pixi/core": "7.4.2",
@ -3423,11 +3526,21 @@
"@pixi/text": "7.4.2" "@pixi/text": "7.4.2"
} }
}, },
"node_modules/@pixi/text-html": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/text-html/-/text-html-7.4.2.tgz",
"integrity": "sha512-duOu8oDYeDNuyPozj2DAsQ5VZBbRiwIXy78Gn7H2pCiEAefw/Uv5jJYwdgneKME0e1tOxz1eOUGKPcI6IJnZjw==",
"peerDependencies": {
"@pixi/core": "7.4.2",
"@pixi/display": "7.4.2",
"@pixi/sprite": "7.4.2",
"@pixi/text": "7.4.2"
}
},
"node_modules/@pixi/ticker": { "node_modules/@pixi/ticker": {
"version": "7.4.2", "version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/ticker/-/ticker-7.4.2.tgz", "resolved": "https://registry.npmjs.org/@pixi/ticker/-/ticker-7.4.2.tgz",
"integrity": "sha512-cAvxCh/KI6IW4m3tp2b+GQIf+DoSj9NNmPJmsOeEJ7LzvruG8Ps7SKI6CdjQob5WbceL1apBTDbqZ/f77hFDiQ==", "integrity": "sha512-cAvxCh/KI6IW4m3tp2b+GQIf+DoSj9NNmPJmsOeEJ7LzvruG8Ps7SKI6CdjQob5WbceL1apBTDbqZ/f77hFDiQ==",
"peer": true,
"dependencies": { "dependencies": {
"@pixi/extensions": "7.4.2", "@pixi/extensions": "7.4.2",
"@pixi/settings": "7.4.2", "@pixi/settings": "7.4.2",
@ -3443,7 +3556,6 @@
"version": "7.4.2", "version": "7.4.2",
"resolved": "https://registry.npmjs.org/@pixi/utils/-/utils-7.4.2.tgz", "resolved": "https://registry.npmjs.org/@pixi/utils/-/utils-7.4.2.tgz",
"integrity": "sha512-aU/itcyMC4TxFbmdngmak6ey4kC5c16Y5ntIYob9QnjNAfD/7GTsYIBnP6FqEAyO1eq0MjkAALxdONuay1BG3g==", "integrity": "sha512-aU/itcyMC4TxFbmdngmak6ey4kC5c16Y5ntIYob9QnjNAfD/7GTsYIBnP6FqEAyO1eq0MjkAALxdONuay1BG3g==",
"peer": true,
"dependencies": { "dependencies": {
"@pixi/color": "7.4.2", "@pixi/color": "7.4.2",
"@pixi/constants": "7.4.2", "@pixi/constants": "7.4.2",
@ -6714,8 +6826,7 @@
"node_modules/@types/css-font-loading-module": { "node_modules/@types/css-font-loading-module": {
"version": "0.0.12", "version": "0.0.12",
"resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.12.tgz", "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.12.tgz",
"integrity": "sha512-x2tZZYkSxXqWvTDgveSynfjq/T2HyiZHXb00j/+gy19yp70PHCizM48XFdjBCWH7eHBD0R5i/pw9yMBP/BH5uA==", "integrity": "sha512-x2tZZYkSxXqWvTDgveSynfjq/T2HyiZHXb00j/+gy19yp70PHCizM48XFdjBCWH7eHBD0R5i/pw9yMBP/BH5uA=="
"peer": true
}, },
"node_modules/@types/debug": { "node_modules/@types/debug": {
"version": "4.1.12", "version": "4.1.12",
@ -6747,8 +6858,7 @@
"node_modules/@types/earcut": { "node_modules/@types/earcut": {
"version": "2.1.4", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.4.tgz", "resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.4.tgz",
"integrity": "sha512-qp3m9PPz4gULB9MhjGID7wpo3gJ4bTGXm7ltNDsmOvsPduTeHp8wSW9YckBj3mljeOh4F0m2z/0JKAALRKbmLQ==", "integrity": "sha512-qp3m9PPz4gULB9MhjGID7wpo3gJ4bTGXm7ltNDsmOvsPduTeHp8wSW9YckBj3mljeOh4F0m2z/0JKAALRKbmLQ=="
"peer": true
}, },
"node_modules/@types/ejs": { "node_modules/@types/ejs": {
"version": "3.1.5", "version": "3.1.5",
@ -9171,8 +9281,7 @@
"node_modules/earcut": { "node_modules/earcut": {
"version": "2.2.4", "version": "2.2.4",
"resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz",
"integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ=="
"peer": true
}, },
"node_modules/eastasianwidth": { "node_modules/eastasianwidth": {
"version": "0.2.0", "version": "0.2.0",
@ -10200,8 +10309,7 @@
"node_modules/eventemitter3": { "node_modules/eventemitter3": {
"version": "4.0.7", "version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
"peer": true
}, },
"node_modules/execa": { "node_modules/execa": {
"version": "5.1.1", "version": "5.1.1",
@ -12059,8 +12167,7 @@
"node_modules/ismobilejs": { "node_modules/ismobilejs": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/ismobilejs/-/ismobilejs-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ismobilejs/-/ismobilejs-1.1.1.tgz",
"integrity": "sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw==", "integrity": "sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw=="
"peer": true
}, },
"node_modules/isobject": { "node_modules/isobject": {
"version": "3.0.1", "version": "3.0.1",
@ -14723,6 +14830,47 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/pixi.js": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-7.4.2.tgz",
"integrity": "sha512-TifqgHGNofO7UCEbdZJOpUu7dUnpu4YZ0o76kfCqxDa4RS8ITc9zjECCbtalmuNXkVhSEZmBKQvE7qhHMqw/xg==",
"dependencies": {
"@pixi/accessibility": "7.4.2",
"@pixi/app": "7.4.2",
"@pixi/assets": "7.4.2",
"@pixi/compressed-textures": "7.4.2",
"@pixi/core": "7.4.2",
"@pixi/display": "7.4.2",
"@pixi/events": "7.4.2",
"@pixi/extensions": "7.4.2",
"@pixi/extract": "7.4.2",
"@pixi/filter-alpha": "7.4.2",
"@pixi/filter-blur": "7.4.2",
"@pixi/filter-color-matrix": "7.4.2",
"@pixi/filter-displacement": "7.4.2",
"@pixi/filter-fxaa": "7.4.2",
"@pixi/filter-noise": "7.4.2",
"@pixi/graphics": "7.4.2",
"@pixi/mesh": "7.4.2",
"@pixi/mesh-extras": "7.4.2",
"@pixi/mixin-cache-as-bitmap": "7.4.2",
"@pixi/mixin-get-child-by-name": "7.4.2",
"@pixi/mixin-get-global-position": "7.4.2",
"@pixi/particle-container": "7.4.2",
"@pixi/prepare": "7.4.2",
"@pixi/sprite": "7.4.2",
"@pixi/sprite-animated": "7.4.2",
"@pixi/sprite-tiling": "7.4.2",
"@pixi/spritesheet": "7.4.2",
"@pixi/text": "7.4.2",
"@pixi/text-bitmap": "7.4.2",
"@pixi/text-html": "7.4.2"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/pixijs"
}
},
"node_modules/pkg-dir": { "node_modules/pkg-dir": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz",
@ -17905,7 +18053,6 @@
"version": "0.11.3", "version": "0.11.3",
"resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz",
"integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==",
"peer": true,
"dependencies": { "dependencies": {
"punycode": "^1.4.1", "punycode": "^1.4.1",
"qs": "^6.11.2" "qs": "^6.11.2"
@ -17914,14 +18061,12 @@
"node_modules/url/node_modules/punycode": { "node_modules/url/node_modules/punycode": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="
"peer": true
}, },
"node_modules/url/node_modules/qs": { "node_modules/url/node_modules/qs": {
"version": "6.12.1", "version": "6.12.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz",
"integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==",
"peer": true,
"dependencies": { "dependencies": {
"side-channel": "^1.0.6" "side-channel": "^1.0.6"
}, },

View File

@ -26,6 +26,7 @@
"idb-keyval": "^6.2.1", "idb-keyval": "^6.2.1",
"isbot": "^4.1.0", "isbot": "^4.1.0",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"pixi.js": "^7.4.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"ws": "^8.17.0" "ws": "^8.17.0"