From 95a8e5f13eab4aac5efa7ea7f76fec1639afc145 Mon Sep 17 00:00:00 2001 From: cha0s Date: Sat, 15 Jun 2024 18:23:10 -0500 Subject: [PATCH] refactor: ecs --- app/ecs/ecs.js | 10 +-- app/ecs/ecs.test.js | 160 ++++++++++++----------------------- app/engine/ecs.js | 10 --- app/engine/engine.js | 20 +++-- app/react-components/ecs.jsx | 6 +- 5 files changed, 71 insertions(+), 135 deletions(-) delete mode 100644 app/engine/ecs.js diff --git a/app/ecs/ecs.js b/app/ecs/ecs.js index c7f27c0..72fbcdd 100644 --- a/app/ecs/ecs.js +++ b/app/ecs/ecs.js @@ -12,20 +12,15 @@ export default class Ecs { diff = {}; - static Systems = {}; - Systems = {}; - static Types = {}; - Types = {}; $$entities = {}; $$entityFactory = new EntityFactory(); - constructor() { - const {Systems, Types} = this.constructor; + constructor({Systems, Types} = {}) { for (const name in Types) { this.Types[name] = new Types[name](this); } @@ -122,8 +117,7 @@ export default class Ecs { } } - static deserialize(view) { - const ecs = new this(); + static deserialize(ecs, view) { const types = Object.keys(ecs.Types); const {entities, systems} = decoder.decode(view.buffer); for (const system of systems) { diff --git a/app/ecs/ecs.test.js b/app/ecs/ecs.test.js index a9fc57e..a1cb056 100644 --- a/app/ecs/ecs.test.js +++ b/app/ecs/ecs.test.js @@ -30,8 +30,8 @@ const Position = wrapSpecification('Position', { test('activates and deactivates systems at runtime', () => { let oneCount = 0; let twoCount = 0; - class SystemToggle extends Ecs { - static Systems = { + const ecs = new Ecs({ + Systems: { OneSystem: class extends System { tick() { oneCount += 1; @@ -42,9 +42,8 @@ test('activates and deactivates systems at runtime', () => { twoCount += 1; } }, - } - } - const ecs = new SystemToggle(); + }, + }); ecs.tick(); expect(oneCount) .to.equal(0); @@ -69,20 +68,14 @@ test('activates and deactivates systems at runtime', () => { }); test('creates entities with components', () => { - class CreateEcs extends Ecs { - static Types = {Empty, Position}; - } - const ecs = new CreateEcs(); + const ecs = new Ecs({Types: {Empty, Position}}); const entity = ecs.create({Empty: {}, Position: {y: 128}}); expect(JSON.stringify(ecs.get(entity))) .to.deep.equal(JSON.stringify({Empty: {}, Position: {y: 128}})); }); test("removes entities' components", () => { - class RemoveEcs extends Ecs { - static Types = {Empty, Position}; - } - const ecs = new RemoveEcs(); + const ecs = new Ecs({Types: {Empty, Position}}); const entity = ecs.create({Empty: {}, Position: {y: 128}}); ecs.remove(entity, ['Position']); expect(JSON.stringify(ecs.get(entity))) @@ -90,20 +83,14 @@ test("removes entities' components", () => { }); test('gets entities', () => { - class GetEcs extends Ecs { - static Types = {Empty, Position}; - } - const ecs = new GetEcs(); + const ecs = new Ecs({Types: {Empty, Position}}); const entity = ecs.create({Empty: {}, Position: {y: 128}}); expect(JSON.stringify(ecs.get(entity))) .to.deep.equal(JSON.stringify({Empty: {}, Position: {y: 128}})); }); test('destroys entities', () => { - class DestroyEcs extends Ecs { - static Types = {Empty, Position}; - } - const ecs = new DestroyEcs(); + const ecs = new Ecs({Types: {Empty, Position}}); const entity = ecs.create({Empty: {}, Position: {y: 128}}); expect(JSON.stringify(ecs.get(entity))) .to.deep.equal(JSON.stringify({Empty: {}, Position: {y: 128}})); @@ -119,10 +106,7 @@ test('destroys entities', () => { }); test('inserts components into entities', () => { - class InsertEcs extends Ecs { - static Types = {Empty, Position}; - } - const ecs = new InsertEcs(); + const ecs = new Ecs({Types: {Empty, Position}}); const entity = ecs.create({Empty: {}}); ecs.insert(entity, {Position: {y: 128}}); expect(JSON.stringify(ecs.get(entity))) @@ -138,8 +122,8 @@ test('ticks systems', () => { y: {type: 'int32'}, z: {type: 'int32'}, }); - class TickEcs extends Ecs { - static Systems = { + const ecs = new Ecs({ + Systems: { Physics: class Physics extends System { static queries() { @@ -157,10 +141,9 @@ test('ticks systems', () => { } }, - } - static Types = {Momentum, Position}; - } - const ecs = new TickEcs(); + }, + Types: {Momentum, Position}, + }); ecs.system('Physics').active = true; const entity = ecs.create({Momentum: {}, Position: {y: 128}}); const position = JSON.stringify(ecs.get(entity).Position); @@ -174,16 +157,15 @@ test('ticks systems', () => { }); test('creates many entities when ticking systems', () => { - class TickingSystemEcs extends Ecs { - static Systems = { + const ecs = new Ecs({ + Systems: { Spawn: class extends System { tick() { this.createManyEntities(Array.from({length: 5}).map(() => [])); } }, - }; - } - const ecs = new TickingSystemEcs(); + }, + }); ecs.system('Spawn').active = true; ecs.create(); expect(ecs.get(5)) @@ -194,16 +176,15 @@ test('creates many entities when ticking systems', () => { }); test('creates entities when ticking systems', () => { - class TickingSystemEcs extends Ecs { - static Systems = { + const ecs = new Ecs({ + Systems: { Spawn: class extends System { tick() { this.createEntity(); } }, - }; - } - const ecs = new TickingSystemEcs(); + }, + }); ecs.system('Spawn').active = true; ecs.create(); expect(ecs.get(2)) @@ -215,8 +196,8 @@ test('creates entities when ticking systems', () => { test('schedules entities to be deleted when ticking systems', () => { let entity; - class TickingSystemEcs extends Ecs { - static Systems = { + const ecs = new Ecs({ + Systems: { Despawn: class extends System { finalize() { entity = ecs.get(1); @@ -225,9 +206,8 @@ test('schedules entities to be deleted when ticking systems', () => { this.destroyEntity(1); } }, - }; - } - const ecs = new TickingSystemEcs(); + }, + }); ecs.system('Despawn').active = true; ecs.create(); ecs.tick(1); @@ -239,8 +219,8 @@ test('schedules entities to be deleted when ticking systems', () => { test('adds components to and remove components from entities when ticking systems', () => { let addLength, removeLength; - class TickingSystemEcs extends Ecs { - static Systems = { + const ecs = new Ecs({ + Systems: { AddComponent: class extends System { static queries() { return { @@ -267,10 +247,9 @@ test('adds components to and remove components from entities when ticking system removeLength = Array.from(this.select('default')).length; } }, - }; - static Types = {Foo: wrapSpecification('Foo', {bar: {type: 'uint8'}})}; - } - const ecs = new TickingSystemEcs(); + }, + Types: {Foo: wrapSpecification('Foo', {bar: {type: 'uint8'}})}, + }); ecs.system('AddComponent').active = true; ecs.create(); ecs.tick(1); @@ -296,10 +275,7 @@ test('generates coalesced diffs for entity creation', () => { }); test('generates diffs for adding and removing components', () => { - class DiffedEcs extends Ecs { - static Types = {Position}; - } - const ecs = new DiffedEcs(); + const ecs = new Ecs({Types: {Position}}); let entity; entity = ecs.create(); ecs.setClean(); @@ -315,10 +291,7 @@ test('generates diffs for adding and removing components', () => { }); test('generates diffs for empty components', () => { - class DiffedEcs extends Ecs { - static Types = {Empty}; - } - const ecs = new DiffedEcs(); + const ecs = new Ecs({Types: {Empty}}); let entity; entity = ecs.create({Empty}); expect(ecs.diff) @@ -330,10 +303,7 @@ test('generates diffs for empty components', () => { }); test('generates diffs for entity mutations', () => { - class DiffedEcs extends Ecs { - static Types = {Position}; - } - const ecs = new DiffedEcs(); + const ecs = new Ecs({Types: {Position}}); let entity; entity = ecs.create({Position: {}}); ecs.setClean(); @@ -346,10 +316,7 @@ test('generates diffs for entity mutations', () => { }); test('generates coalesced diffs for components', () => { - class DiffedEcs extends Ecs { - static Types = {Position}; - } - const ecs = new DiffedEcs(); + const ecs = new Ecs({Types: {Position}}); let entity; entity = ecs.create({Position}); ecs.remove(entity, ['Position']); @@ -361,10 +328,7 @@ test('generates coalesced diffs for components', () => { }); test('generates coalesced diffs for mutations', () => { - class DiffedEcs extends Ecs { - static Types = {Position}; - } - const ecs = new DiffedEcs(); + const ecs = new Ecs({Types: {Position}}); let entity; entity = ecs.create({Position}); ecs.setClean(); @@ -386,10 +350,7 @@ test('generates diffs for deletions', () => { }); test('applies creation patches', () => { - class PatchedEcs extends Ecs { - static Types = {Position}; - } - const ecs = new PatchedEcs(); + const ecs = new Ecs({Types: {Position}}); ecs.apply({16: {Position: {x: 64}}}); expect(Array.from(ecs.entities).length) .to.equal(1); @@ -398,10 +359,7 @@ test('applies creation patches', () => { }); test('applies update patches', () => { - class PatchedEcs extends Ecs { - static Types = {Position}; - } - const ecs = new PatchedEcs(); + const ecs = new Ecs({Types: {Position}}); ecs.createSpecific(16, {Position: {x: 64}}); ecs.apply({16: {Position: {x: 128}}}); expect(Array.from(ecs.entities).length) @@ -411,10 +369,7 @@ test('applies update patches', () => { }); test('applies entity deletion patches', () => { - class PatchedEcs extends Ecs { - static Types = {Position}; - } - const ecs = new PatchedEcs(); + const ecs = new Ecs({Types: {Position}}); ecs.createSpecific(16, {Position: {x: 64}}); ecs.apply({16: false}); expect(Array.from(ecs.entities).length) @@ -422,10 +377,7 @@ test('applies entity deletion patches', () => { }); test('applies component deletion patches', () => { - class PatchedEcs extends Ecs { - static Types = {Empty, Position}; - } - const ecs = new PatchedEcs(); + const ecs = new Ecs({Types: {Empty, Position}}); ecs.createSpecific(16, {Empty: {}, Position: {x: 64}}); expect(ecs.get(16).constructor.types) .to.deep.equal(['Empty', 'Position']); @@ -435,10 +387,7 @@ test('applies component deletion patches', () => { }); test('calculates entity size', () => { - class SizingEcs extends Ecs { - static Types = {Empty, Position}; - } - const ecs = new SizingEcs(); + const ecs = new Ecs({Types: {Empty, Position}}); ecs.createSpecific(1, {Empty: {}, Position: {}}); // ID + # of components + Empty + Position + x + y + z // 4 + 2 + 2 + 4 + 2 + 4 + 4 + 4 + 4 = 30 @@ -447,10 +396,7 @@ test('calculates entity size', () => { }); test('serializes and deserializes', () => { - class SerializingEcs extends Ecs { - static Types = {Empty, Name, Position}; - } - const ecs = new SerializingEcs(); + const ecs = new Ecs({Types: {Empty, Name, Position}}); ecs.createSpecific(1, {Empty: {}, Position: {x: 64}}); ecs.createSpecific(16, {Name: {name: 'foobar'}, Position: {x: 128}}); expect(ecs.toJSON()) @@ -461,8 +407,11 @@ test('serializes and deserializes', () => { }, systems: [], }); - const view = SerializingEcs.serialize(ecs); - const deserialized = SerializingEcs.deserialize(view); + const view = Ecs.serialize(ecs); + const deserialized = Ecs.deserialize( + new Ecs({Types: {Empty, Name, Position}}), + view, + ); expect(Array.from(deserialized.entities).length) .to.equal(2); expect(deserialized.get(1).constructor.types) @@ -480,17 +429,14 @@ test('serializes and deserializes', () => { }); test('deserializes from compatible ECS', () => { - class DeserializingEcs extends Ecs { - static Types = {Empty, Name}; - } - class SerializingEcs extends Ecs { - static Types = {Empty, Name, Position}; - } - const ecs = new SerializingEcs(); + const ecs = new Ecs({Types: {Empty, Name, Position}}); ecs.createSpecific(1, {Empty: {}, Position: {x: 64}}); ecs.createSpecific(16, {Name: {name: 'foobar'}, Position: {x: 128}}); - const view = SerializingEcs.serialize(ecs); - const deserialized = DeserializingEcs.deserialize(view); + const view = Ecs.serialize(ecs); + const deserialized = Ecs.deserialize( + new Ecs({Types: {Empty, Name}}), + view, + ); expect(deserialized.get(1).toJSON()) .to.deep.equal({Empty: {}}); expect(deserialized.get(16).toJSON()) diff --git a/app/engine/ecs.js b/app/engine/ecs.js deleted file mode 100644 index 9ec4c60..0000000 --- a/app/engine/ecs.js +++ /dev/null @@ -1,10 +0,0 @@ -import Ecs from '@/ecs/ecs.js'; -import Systems from '@/ecs-systems/index.js'; -import Types from '@/ecs-components/index.js'; - -class EngineEcs extends Ecs { - static Systems = Systems; - static Types = Types; -} - -export default EngineEcs; diff --git a/app/engine/engine.js b/app/engine/engine.js index 72314ce..9cc0232 100644 --- a/app/engine/engine.js +++ b/app/engine/engine.js @@ -4,7 +4,9 @@ import { MOVE_MAP, TPS, } from '@/constants.js'; -import Ecs from '@/engine/ecs.js'; +import Ecs from '@/ecs/ecs.js'; +import Systems from '@/ecs-systems/index.js'; +import Types from '@/ecs-components/index.js'; import {decode, encode} from '@/packets/index.js'; export default class Engine { @@ -58,7 +60,11 @@ export default class Engine { } createEcs() { - const ecs = new Ecs(); + return new Ecs({Systems, Types}); + } + + async createHomestead(id) { + const ecs = this.createEcs(); const area = {x: 100, y: 60}; ecs.create({ AreaSize: {x: area.x * 16, y: area.y * 16}, @@ -87,11 +93,6 @@ export default class Engine { defaultSystems.forEach((defaultSystem) => { ecs.system(defaultSystem).active = true; }); - return ecs; - } - - async createHomestead(id) { - const ecs = this.createEcs(); const view = Ecs.serialize(ecs); await this.server.writeData( join('homesteads', `${id}`), @@ -137,7 +138,10 @@ export default class Engine { } async loadEcs(path) { - this.ecses[path] = Ecs.deserialize(await this.server.readData(path)); + this.ecses[path] = Ecs.deserialize( + this.createEcs(), + await this.server.readData(path), + ); } async loadPlayer(id) { diff --git a/app/react-components/ecs.jsx b/app/react-components/ecs.jsx index 72c67bf..b3c965d 100644 --- a/app/react-components/ecs.jsx +++ b/app/react-components/ecs.jsx @@ -2,14 +2,16 @@ import {Container} from '@pixi/react'; import {useState} from 'react'; import {RESOLUTION} from '@/constants.js'; -import Ecs from '@/engine/ecs.js'; +import Ecs from '@/ecs/ecs.js'; +import Systems from '@/ecs-systems/index.js'; +import Types from '@/ecs-components/index.js'; import usePacket from '@/hooks/use-packet.js'; import Entities from './entities.jsx'; import TileLayer from './tile-layer.jsx'; export default function EcsComponent() { - const [ecs] = useState(new Ecs()); + const [ecs] = useState(new Ecs({Systems, Types})); const [entities, setEntities] = useState({}); const [mainEntity, setMainEntity] = useState(); usePacket('Tick', (payload) => {