feat: switch ecs
This commit is contained in:
parent
5fb34d66ac
commit
3d862d39d5
37
app/client-ecs.js
Normal file
37
app/client-ecs.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
import Ecs from '@/ecs/ecs.js';
|
||||
import Script from '@/util/script.js';
|
||||
|
||||
import {LRUCache} from 'lru-cache';
|
||||
|
||||
const cache = new LRUCache({
|
||||
max: 128,
|
||||
});
|
||||
|
||||
export default class ClientEcs extends Ecs {
|
||||
async readAsset(uri) {
|
||||
if (!cache.has(uri)) {
|
||||
let promise, resolve, reject;
|
||||
promise = new Promise((res, rej) => {
|
||||
resolve = res;
|
||||
reject = rej;
|
||||
});
|
||||
cache.set(uri, promise);
|
||||
fetch(new URL(uri, window.location.origin))
|
||||
.then(async (response) => {
|
||||
resolve(response.ok ? response.arrayBuffer() : new ArrayBuffer(0));
|
||||
})
|
||||
.catch(reject);
|
||||
}
|
||||
return cache.get(uri);
|
||||
}
|
||||
async readJson(uri) {
|
||||
const chars = await this.readAsset(uri);
|
||||
return chars.byteLength > 0 ? JSON.parse((new TextDecoder()).decode(chars)) : {};
|
||||
}
|
||||
async readScript(uri, context = {}) {
|
||||
const code = await this.readAsset(uri);
|
||||
if (code.byteLength > 0) {
|
||||
return Script.fromCode((new TextDecoder()).decode(code), context);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,6 @@ export function useEcs() {
|
|||
}
|
||||
|
||||
export function useEcsTick(fn, dependencies) {
|
||||
const ecs = useEcs();
|
||||
const [ecs] = useEcs();
|
||||
usePacket(':Ecs', fn, [ecs, ...dependencies]);
|
||||
}
|
|
@ -30,6 +30,7 @@ export default async function createHomestead(Ecs) {
|
|||
],
|
||||
collisionStartScript: '/assets/shit-shack/collision-start.js',
|
||||
},
|
||||
Ecs: {},
|
||||
Position: {x: 100, y: 100},
|
||||
Sprite: {
|
||||
anchor: {x: 0.5, y: 0.8},
|
||||
|
|
|
@ -17,5 +17,23 @@ export default async function createHouse(Ecs) {
|
|||
],
|
||||
},
|
||||
});
|
||||
await ecs.create({
|
||||
Collider: {
|
||||
bodies: [
|
||||
[
|
||||
{x: -8, y: -8},
|
||||
{x: 7, y: -8},
|
||||
{x: 7, y: 7},
|
||||
{x: -8, y: 7},
|
||||
],
|
||||
],
|
||||
collisionStartScript: '/assets/house/collision-start.js',
|
||||
},
|
||||
Ecs: {},
|
||||
Position: {
|
||||
x: 72,
|
||||
y: 320,
|
||||
},
|
||||
});
|
||||
return ecs;
|
||||
}
|
||||
|
|
134
app/engine.js
134
app/engine.js
|
@ -13,13 +13,11 @@ import createPlayer from './create-player.js';
|
|||
|
||||
export default class Engine {
|
||||
|
||||
connections = [];
|
||||
connectedPlayers = new Map();
|
||||
connectingPlayers = [];
|
||||
ecses = {};
|
||||
frame = 0;
|
||||
handle;
|
||||
incomingActions = [];
|
||||
incomingActions = new Map();
|
||||
last = Date.now();
|
||||
server;
|
||||
|
||||
|
@ -33,6 +31,7 @@ export default class Engine {
|
|||
super.transmit(connection, encode(packet));
|
||||
}
|
||||
}
|
||||
const engine = this;
|
||||
const server = this.server = new SilphiusServer();
|
||||
this.Ecs = class EngineEcs extends Ecs {
|
||||
async readAsset(uri) {
|
||||
|
@ -51,63 +50,102 @@ export default class Engine {
|
|||
return Script.fromCode((new TextDecoder()).decode(code), context);
|
||||
}
|
||||
}
|
||||
async switchEcs(entity, path, updates) {
|
||||
for (const [connection, connectedPlayer] of engine.connectedPlayers) {
|
||||
if (entity !== connectedPlayer.entity) {
|
||||
continue;
|
||||
}
|
||||
// remove entity link to connection to start queueing actions and pause updates
|
||||
delete connectedPlayer.entity;
|
||||
// forget previous state
|
||||
connectedPlayer.memory.clear();
|
||||
// inform client of the upcoming change
|
||||
server.send(
|
||||
connection,
|
||||
{
|
||||
type: 'EcsChange',
|
||||
payload: {},
|
||||
},
|
||||
);
|
||||
// dump entity state with updates for the transition
|
||||
const dumped = {
|
||||
...entity.toJSON(),
|
||||
Controlled: entity.Controlled,
|
||||
Ecs: {path},
|
||||
...updates,
|
||||
};
|
||||
// remove from old ECS
|
||||
this.destroy(entity.id);
|
||||
// load if necessary
|
||||
if (!engine.ecses[path]) {
|
||||
await engine.loadEcs(path);
|
||||
}
|
||||
// recreate the entity in the new ECS and again associate it with the connection
|
||||
connectedPlayer.entity = engine.ecses[path].get(await engine.ecses[path].create(dumped));
|
||||
}
|
||||
}
|
||||
}
|
||||
this.server.addPacketListener('Action', (connection, payload) => {
|
||||
this.incomingActions.push([connection, payload]);
|
||||
if (!this.incomingActions.has(connection)) {
|
||||
this.incomingActions.set(connection, []);
|
||||
}
|
||||
this.incomingActions.get(connection).push(payload);
|
||||
});
|
||||
}
|
||||
|
||||
acceptActions() {
|
||||
for (const [
|
||||
connection,
|
||||
payload,
|
||||
] of this.incomingActions) {
|
||||
for (const [connection, payloads] of this.incomingActions) {
|
||||
if (!this.connectedPlayers.get(connection)) {
|
||||
continue;
|
||||
}
|
||||
const {entity} = this.connectedPlayers.get(connection);
|
||||
if (!entity) {
|
||||
continue;
|
||||
}
|
||||
const {Controlled, Ecs, Interacts, Inventory, Wielder} = entity;
|
||||
switch (payload.type) {
|
||||
case 'changeSlot': {
|
||||
if (!Controlled.locked) {
|
||||
Wielder.activeSlot = payload.value - 1;
|
||||
for (const payload of payloads) {
|
||||
switch (payload.type) {
|
||||
case 'changeSlot': {
|
||||
if (!Controlled.locked) {
|
||||
Wielder.activeSlot = payload.value - 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'moveUp':
|
||||
case 'moveRight':
|
||||
case 'moveDown':
|
||||
case 'moveLeft': {
|
||||
Controlled[payload.type] = payload.value;
|
||||
break;
|
||||
}
|
||||
case 'swapSlots': {
|
||||
if (!Controlled.locked) {
|
||||
Inventory.swapSlots(...payload.value);
|
||||
case 'moveUp':
|
||||
case 'moveRight':
|
||||
case 'moveDown':
|
||||
case 'moveLeft': {
|
||||
Controlled[payload.type] = payload.value;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'use': {
|
||||
if (!Controlled.locked) {
|
||||
Wielder.useActiveItem(payload.value);
|
||||
case 'swapSlots': {
|
||||
if (!Controlled.locked) {
|
||||
Inventory.swapSlots(...payload.value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'interact': {
|
||||
if (!Controlled.locked) {
|
||||
if (payload.value) {
|
||||
if (Interacts.willInteractWith) {
|
||||
const ecs = this.ecses[Ecs.path];
|
||||
const subject = ecs.get(Interacts.willInteractWith);
|
||||
subject.Interactive.interact(entity);
|
||||
case 'use': {
|
||||
if (!Controlled.locked) {
|
||||
Wielder.useActiveItem(payload.value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'interact': {
|
||||
if (!Controlled.locked) {
|
||||
if (payload.value) {
|
||||
if (Interacts.willInteractWith) {
|
||||
const ecs = this.ecses[Ecs.path];
|
||||
const subject = ecs.get(Interacts.willInteractWith);
|
||||
subject.Interactive.interact(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.incomingActions.set(connection, []);
|
||||
}
|
||||
this.incomingActions = [];
|
||||
}
|
||||
|
||||
async connectPlayer(connection, id) {
|
||||
|
@ -117,7 +155,6 @@ export default class Engine {
|
|||
}
|
||||
const ecs = this.ecses[entityJson.Ecs.path];
|
||||
const entity = await ecs.create(entityJson);
|
||||
this.connections.push(connection);
|
||||
this.connectedPlayers.set(
|
||||
connection,
|
||||
{
|
||||
|
@ -138,7 +175,7 @@ export default class Engine {
|
|||
await this.savePlayer(id, entity);
|
||||
ecs.destroy(entity.id);
|
||||
this.connectedPlayers.delete(connection);
|
||||
this.connections.splice(this.connections.indexOf(connection), 1);
|
||||
this.incomingActions.delete(connection);
|
||||
}
|
||||
|
||||
async load() {
|
||||
|
@ -160,13 +197,17 @@ export default class Engine {
|
|||
if ('ENOENT' !== error.code) {
|
||||
throw error;
|
||||
}
|
||||
const homestead = await createHomestead(this.Ecs);
|
||||
homestead.get(2).Ecs.path = ['houses', `${id}`].join('/');
|
||||
await this.saveEcs(
|
||||
['homesteads', `${id}`].join('/'),
|
||||
await createHomestead(this.Ecs),
|
||||
homestead,
|
||||
);
|
||||
const house = await createHouse(this.Ecs);
|
||||
house.get(2).Ecs.path = ['homesteads', `${id}`].join('/');
|
||||
await this.saveEcs(
|
||||
['houses', `${id}`].join('/'),
|
||||
await createHouse(this.Ecs),
|
||||
house,
|
||||
);
|
||||
buffer = await createPlayer(id);
|
||||
await this.server.writeData(
|
||||
|
@ -228,7 +269,10 @@ export default class Engine {
|
|||
}
|
||||
|
||||
update(elapsed) {
|
||||
for (const connection of this.connections) {
|
||||
for (const [connection, {entity}] of this.connectedPlayers) {
|
||||
if (!entity) {
|
||||
continue;
|
||||
}
|
||||
this.server.send(
|
||||
connection,
|
||||
{
|
||||
|
|
|
@ -97,7 +97,9 @@ if (import.meta.hot) {
|
|||
await beforeResolver;
|
||||
delete engine.ecses['homesteads/0'];
|
||||
await engine.server.removeData('homesteads/0');
|
||||
await engine.saveEcs('homesteads/0', await createHomestead(engine.Ecs));
|
||||
const homestead = await createHomestead(engine.Ecs);
|
||||
homestead.get(2).Ecs.path = 'houses/0';
|
||||
await engine.saveEcs('homesteads/0', homestead);
|
||||
resolver.resolve();
|
||||
});
|
||||
import.meta.hot.on('vite:afterUpdate', async () => {
|
||||
|
|
3
app/packets/ecs-change.js
Normal file
3
app/packets/ecs-change.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import Packet from '@/net/packet.js';
|
||||
|
||||
export default class EcsChange extends Packet {}
|
|
@ -2,6 +2,7 @@ import {Container} from '@pixi/react';
|
|||
import {useState} from 'react';
|
||||
|
||||
import {RESOLUTION} from '@/constants.js';
|
||||
import {usePacket} from '@/context/client.js';
|
||||
import {useEcs, useEcsTick} from '@/context/ecs.js';
|
||||
import {useMainEntity} from '@/context/main-entity.js';
|
||||
|
||||
|
@ -15,6 +16,9 @@ export default function Ecs({scale}) {
|
|||
const [ecs] = useEcs();
|
||||
const [entities, setEntities] = useState({});
|
||||
const [mainEntity] = useMainEntity();
|
||||
usePacket('EcsChange', async () => {
|
||||
setEntities({});
|
||||
}, [setEntities]);
|
||||
useEcsTick((payload) => {
|
||||
if (!ecs) {
|
||||
return;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {useEffect, useState} from 'react';
|
||||
|
||||
import addKeyListener from '@/add-key-listener.js';
|
||||
import ClientEcs from '@/client-ecs';
|
||||
import {RESOLUTION} from '@/constants.js';
|
||||
import {useClient, usePacket} from '@/context/client.js';
|
||||
import {useDebug} from '@/context/debug.js';
|
||||
|
@ -24,12 +25,26 @@ export default function Ui({disconnected}) {
|
|||
const client = useClient();
|
||||
const [mainEntity, setMainEntity] = useMainEntity();
|
||||
const [debug, setDebug] = useDebug();
|
||||
const [ecs] = useEcs();
|
||||
const [ecs, setEcs] = useEcs();
|
||||
const [showDisconnected, setShowDisconnected] = useState(false);
|
||||
const [bufferSlot, setBufferSlot] = useState();
|
||||
const [hotbarSlots, setHotbarSlots] = useState(emptySlots());
|
||||
const [activeSlot, setActiveSlot] = useState(0);
|
||||
const [scale, setScale] = useState(2);
|
||||
const [Components, setComponents] = useState();
|
||||
const [Systems, setSystems] = useState();
|
||||
useEffect(() => {
|
||||
async function setEcsStuff() {
|
||||
const {default: Components} = await import('@/ecs-components/index.js');
|
||||
const {default: Systems} = await import('@/ecs-systems/index.js');
|
||||
setComponents(Components);
|
||||
setSystems(Systems);
|
||||
}
|
||||
setEcsStuff();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
setEcs(new ClientEcs({Components, Systems}));
|
||||
}, [Components, setEcs, Systems]);
|
||||
useEffect(() => {
|
||||
let handle;
|
||||
if (disconnected) {
|
||||
|
@ -165,6 +180,10 @@ export default function Ui({disconnected}) {
|
|||
}
|
||||
});
|
||||
}, [client, debug, setDebug, setScale]);
|
||||
usePacket('EcsChange', async () => {
|
||||
setMainEntity(undefined);
|
||||
setEcs(new ClientEcs({Components, Systems}));
|
||||
}, [Components, Systems, setEcs, setMainEntity]);
|
||||
usePacket('Tick', async (payload, client) => {
|
||||
if (0 === Object.keys(payload.ecs).length) {
|
||||
return;
|
||||
|
|
|
@ -7,45 +7,8 @@ import ClientContext from '@/context/client.js';
|
|||
import DebugContext from '@/context/debug.js';
|
||||
import EcsContext from '@/context/ecs.js';
|
||||
import MainEntityContext from '@/context/main-entity.js';
|
||||
import Ecs from '@/ecs/ecs.js';
|
||||
import Ui from '@/react-components/ui.jsx';
|
||||
import {juggleSession} from '@/session.server';
|
||||
import Script from '@/util/script.js';
|
||||
|
||||
import {LRUCache} from 'lru-cache';
|
||||
|
||||
const cache = new LRUCache({
|
||||
max: 128,
|
||||
});
|
||||
|
||||
class ClientEcs extends Ecs {
|
||||
async readAsset(uri) {
|
||||
if (!cache.has(uri)) {
|
||||
let promise, resolve, reject;
|
||||
promise = new Promise((res, rej) => {
|
||||
resolve = res;
|
||||
reject = rej;
|
||||
});
|
||||
cache.set(uri, promise);
|
||||
fetch(new URL(uri, window.location.origin))
|
||||
.then(async (response) => {
|
||||
resolve(response.ok ? response.arrayBuffer() : new ArrayBuffer(0));
|
||||
})
|
||||
.catch(reject);
|
||||
}
|
||||
return cache.get(uri);
|
||||
}
|
||||
async readJson(uri) {
|
||||
const chars = await this.readAsset(uri);
|
||||
return chars.byteLength > 0 ? JSON.parse((new TextDecoder()).decode(chars)) : {};
|
||||
}
|
||||
async readScript(uri, context = {}) {
|
||||
const code = await this.readAsset(uri);
|
||||
if (code.byteLength > 0) {
|
||||
return Script.fromCode((new TextDecoder()).decode(code), context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function loader({request}) {
|
||||
await juggleSession(request);
|
||||
|
@ -59,25 +22,10 @@ export default function PlaySpecific() {
|
|||
const mainEntityTuple = useState();
|
||||
const setMainEntity = mainEntityTuple[1];
|
||||
const debugTuple = useState(false);
|
||||
const [Components, setComponents] = useState();
|
||||
const [Systems, setSystems] = useState();
|
||||
const ecsTuple = useState();
|
||||
const setEcs = ecsTuple[1];
|
||||
const [disconnected, setDisconnected] = useState(false);
|
||||
const params = useParams();
|
||||
const [type, url] = params['*'].split('/');
|
||||
useEffect(() => {
|
||||
async function setEcsStuff() {
|
||||
const {default: Components} = await import('@/ecs-components/index.js');
|
||||
const {default: Systems} = await import('@/ecs-systems/index.js');
|
||||
setComponents(Components);
|
||||
setSystems(Systems);
|
||||
}
|
||||
setEcsStuff();
|
||||
});
|
||||
useEffect(() => {
|
||||
setEcs(new ClientEcs({Components, Systems}));
|
||||
}, [Components, setEcs, Systems]);
|
||||
useEffect(() => {
|
||||
if (!Client) {
|
||||
return;
|
||||
|
|
10
public/assets/house/collision-start.js
Normal file
10
public/assets/house/collision-start.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
ecs.switchEcs(
|
||||
other,
|
||||
entity.Ecs.path,
|
||||
{
|
||||
Position: {
|
||||
x: 74,
|
||||
y: 108,
|
||||
},
|
||||
},
|
||||
);
|
|
@ -1 +1,10 @@
|
|||
console.log("I'ma warp yo azz")
|
||||
ecs.switchEcs(
|
||||
other,
|
||||
entity.Ecs.path,
|
||||
{
|
||||
Position: {
|
||||
x: 72,
|
||||
y: 304,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue
Block a user