import {expect} from 'chai'; import Bundle from '../src/bundle'; import Ecs from '../src/ecs'; import System from '../src/system'; const Empty = {}; const Position = { x: {type: 'int32', defaultValue: 32}, y: 'int32', z: 'int32', }; it('can add and remove systems at runtime', () => { const ecs = new Ecs(); let oneCount = 0; let twoCount = 0; const oneSystem = () => { oneCount++; }; ecs.addSystem(oneSystem); ecs.tick(); expect(oneCount) .to.equal(1); const twoSystem = () => { twoCount++; }; ecs.addSystem(twoSystem); ecs.tick(); expect(oneCount) .to.equal(2); expect(twoCount) .to.equal(1); ecs.removeSystem(oneSystem); ecs.tick(); expect(oneCount) .to.equal(2); expect(twoCount) .to.equal(2); }); it('can create entities with components', () => { const ecs = new Ecs({Empty, Position}); const entity = ecs.create({Empty: {}, Position: {y: 420}}); expect(JSON.stringify(ecs.get(entity))) .to.deep.equal(JSON.stringify({Empty: {}, Position: {x: 32, y: 420, z: 0}})); }); it("can remove entities' components", () => { const ecs = new Ecs({Empty, Position}); const entity = ecs.create({Empty: {}, Position: {y: 420}}); ecs.remove(entity, ['Position']); expect(JSON.stringify(ecs.get(entity))) .to.deep.equal(JSON.stringify({Empty: {}})); }); it('can get entities unsafely', () => { const ecs = new Ecs({Empty, Position}); const entity = ecs.create({Empty: {}, Position: {y: 420}}); expect(JSON.stringify(ecs.getUnsafe(entity))) .to.deep.equal(JSON.stringify({Empty: {}, Position: {x: 32, y: 420, z: 0}})); }); it('can insert components into entities', () => { const ecs = new Ecs({Empty, Position}); const entity = ecs.create({Empty: {}}); ecs.insert(entity, {Position: {y: 420}}); expect(JSON.stringify(ecs.get(entity))) .to.deep.equal(JSON.stringify({Empty: {}, Position: {x: 32, y: 420, z: 0}})); ecs.insert(entity, {Position: {y: 69}}); expect(JSON.stringify(ecs.get(entity))) .to.deep.equal(JSON.stringify({Empty: {}, Position: {x: 32, y: 69, z: 0}})); }); it('can tick systems', () => { const Momentum = { x: 'int32', y: 'int32', z: 'int32', }; const ecs = new Ecs({Momentum, Position}); class Physics extends System { static queries() { return { default: ['Position', 'Momentum'], }; } tick(elapsed) { expect(elapsed) .to.equal(1); // eslint-disable-next-line no-restricted-syntax for (const [position, momentum] of this.select('default')) { position.x += momentum.x * elapsed; position.y += momentum.y * elapsed; position.z += momentum.z * elapsed; } } } ecs.addSystem(Physics); const entity = ecs.create({Momentum: {}, Position: {y: 420}}); const position = JSON.stringify(ecs.get(entity).Position); ecs.tick(1); expect(JSON.stringify(ecs.get(entity).Position)) .to.deep.equal(position); ecs.get(1).Momentum.y = 30; ecs.tick(1); expect(JSON.stringify(ecs.get(entity).Position)) .to.deep.equal(JSON.stringify({x: 32, y: 450, z: 0})); }); it('can create entities when ticking systems', () => { const ecs = new Ecs(); class Spawn extends System { tick() { this.createEntity(); } } ecs.addSystem(Spawn); ecs.create(); expect(ecs.get(2)) .to.be.undefined; ecs.tick(1); expect(ecs.get(2)) .to.not.be.undefined; }); it('can schedule entities to be deleted when ticking systems', () => { const ecs = new Ecs(); class Despawn extends System { tick() { this.destroyEntity(1); } } ecs.addSystem(Despawn); ecs.createExact(1); ecs.tick(1); expect(ecs.get(1)) .to.not.be.undefined; ecs.tickFinalize(); expect(ecs.get(1)) .to.be.undefined; }); it('can add components to and remove components from entities when ticking systems', () => { const Foo = {bar: 'uint8'}; const ecs = new Ecs({Foo}); class AddComponent extends System { tick() { this.insertComponents(1n, {Foo: {}}); } } class RemoveComponent extends System { tick() { this.removeComponents(1n, ['Foo']); } } ecs.addSystem(AddComponent); ecs.createExact(1); ecs.tick(1); expect(ecs.get(1).Foo) .to.not.be.undefined; ecs.removeSystem(AddComponent); ecs.addSystem(RemoveComponent); ecs.tick(1); expect(ecs.get(1).Foo) .to.be.undefined; }); it('can encode and decode an ecs', () => { const ecs = new Ecs({Empty, Position}); const entity = ecs.create({Empty: {}, Position: {y: 420}}); const view = new DataView(new ArrayBuffer(1024)); ecs.encode([[entity, true]], view); const newEcs = new Ecs({Empty, Position}); newEcs.decode(view); expect(JSON.stringify(newEcs.get(entity))) .to.deep.equal(JSON.stringify(ecs.get(entity))); ecs.setClean(); ecs.encode([[entity, true]], view); const newEcs2 = new Ecs({Empty, Position}); newEcs2.decode(view); expect(newEcs2.get(entity)) .to.be.undefined; ecs.encode([entity], view); newEcs2.decode(view); expect(newEcs2.get(entity)) .to.not.be.undefined; }); it('can add bundles', () => { const Height = {height: 'uint32'}; const Width = {width: 'uint32'}; const Area = ['Height', 'Width']; class ConfiguredArea extends Bundle { static configure({height, width}) { return {Height: {height}, Width: {width}}; } static Components = ['Height', 'Width']; } const ecs = new Ecs({ Area, ConfiguredArea, Height, Width, }); const entity = ecs.get(ecs.create({Area: {Width: {width: 420}}})); expect(entity.Height.height) .to.equal(0); expect(entity.Width.width) .to.equal(420); const configuredEntity = ecs.get(ecs.create({ConfiguredArea: {width: 420}})); expect(configuredEntity.Height.height) .to.equal(0); expect(configuredEntity.Width.width) .to.equal(420); });