diff --git a/app/ecs-components/index.js b/app/ecs-components/index.js index 3f6fb8d..362857a 100644 --- a/app/ecs-components/index.js +++ b/app/ecs-components/index.js @@ -8,14 +8,14 @@ const specificationsOrClasses = gather( ); const Components = {}; -for (const name in specificationsOrClasses) { - const specificationOrClass = specificationsOrClasses[name]; +for (const componentName in specificationsOrClasses) { + const specificationOrClass = specificationsOrClasses[componentName]; if (specificationOrClass instanceof Base) { - Components[name] = specificationOrClass; + Components[componentName] = specificationOrClass; } else { - Components[name] = class Component extends Arbitrary { - static name = name; + Components[componentName] = class Component extends Arbitrary { + static name = componentName; static schema = new Schema({ type: 'object', properties: specificationOrClass, diff --git a/app/ecs/ecs.js b/app/ecs/ecs.js index 72fbcdd..74dd58c 100644 --- a/app/ecs/ecs.js +++ b/app/ecs/ecs.js @@ -10,22 +10,22 @@ export default class Ecs { $$caret = 1; + Components = {}; + diff = {}; Systems = {}; - Types = {}; - $$entities = {}; $$entityFactory = new EntityFactory(); - constructor({Systems, Types} = {}) { - for (const name in Types) { - this.Types[name] = new Types[name](this); + constructor({Systems, Components} = {}) { + for (const componentName in Components) { + this.Components[componentName] = new Components[componentName](this); } - for (const name in Systems) { - this.Systems[name] = new Systems[name](this); + for (const systemName in Systems) { + this.Systems[systemName] = new Systems[systemName](this); } } @@ -42,12 +42,12 @@ export default class Ecs { } const componentsToRemove = []; const componentsToUpdate = {}; - for (const i in components) { - if (false === components[i]) { - componentsToRemove.push(i); + for (const componentName in components) { + if (false === components[componentName]) { + componentsToRemove.push(componentName); } else { - componentsToUpdate[i] = components[i]; + componentsToUpdate[componentName] = components[componentName]; } } if (componentsToRemove.length > 0) { @@ -84,24 +84,24 @@ export default class Ecs { const creating = {}; for (let i = 0; i < specificsList.length; i++) { const [entityId, components] = specificsList[i]; - const componentKeys = []; - for (const name in components) { - if (this.Types[name]) { - componentKeys.push(name); + const componentNames = []; + for (const componentName in components) { + if (this.Components[componentName]) { + componentNames.push(componentName); } } entityIds.push(entityId); - this.rebuild(entityId, () => componentKeys); - for (const component of componentKeys) { - if (!creating[component]) { - creating[component] = []; + this.rebuild(entityId, () => componentNames); + for (const componentName of componentNames) { + if (!creating[componentName]) { + creating[componentName] = []; } - creating[component].push([entityId, components[component]]); + creating[componentName].push([entityId, components[componentName]]); } this.markChange(entityId, components); } for (const i in creating) { - this.Types[i].createMany(creating[i]); + this.Components[i].createMany(creating[i]); } this.reindex(entityIds); return entityIds; @@ -112,13 +112,13 @@ export default class Ecs { } deindex(entityIds) { - for (const name in this.Systems) { - this.Systems[name].deindex(entityIds); + for (const systemName in this.Systems) { + this.Systems[systemName].deindex(entityIds); } } static deserialize(ecs, view) { - const types = Object.keys(ecs.Types); + const componentNames = Object.keys(ecs.Components); const {entities, systems} = decoder.decode(view.buffer); for (const system of systems) { ecs.system(system).active = true; @@ -129,7 +129,10 @@ export default class Ecs { max = Math.max(max, parseInt(id)); specifics.push([ parseInt(id), - Object.fromEntries(Object.entries(entities[id]).filter(([type]) => types.includes(type))), + Object.fromEntries( + Object.entries(entities[id]) + .filter(([componentName]) => componentNames.includes(componentName)), + ), ]); } ecs.$$caret = max + 1; @@ -152,17 +155,17 @@ export default class Ecs { if (!this.$$entities[entityId]) { throw new Error(`can't destroy non-existent entity ${entityId}`); } - for (const component of this.$$entities[entityId].constructor.types) { - if (!destroying[component]) { - destroying[component] = []; + for (const componentName of this.$$entities[entityId].constructor.componentNames) { + if (!destroying[componentName]) { + destroying[componentName] = []; } - destroying[component].push(entityId); + destroying[componentName].push(entityId); } this.$$entities[entityId] = undefined; this.diff[entityId] = false; } for (const i in destroying) { - this.Types[i].destroyMany(destroying[i]); + this.Components[i].destroyMany(destroying[i]); } } @@ -197,20 +200,20 @@ export default class Ecs { const inserting = {}; const unique = new Set(); for (const [entityId, components] of entities) { - this.rebuild(entityId, (types) => [...new Set(types.concat(Object.keys(components)))]); + this.rebuild(entityId, (componentNames) => [...new Set(componentNames.concat(Object.keys(components)))]); const diff = {}; - for (const component in components) { - if (!inserting[component]) { - inserting[component] = []; + for (const componentName in components) { + if (!inserting[componentName]) { + inserting[componentName] = []; } - diff[component] = {}; - inserting[component].push([entityId, components[component]]); + diff[componentName] = {}; + inserting[componentName].push([entityId, components[componentName]]); } unique.add(entityId); this.markChange(entityId, diff); } - for (const component in inserting) { - this.Types[component].insertMany(inserting[component]); + for (const componentName in inserting) { + this.Components[componentName].insertMany(inserting[componentName]); } this.reindex(unique.values()); } @@ -223,38 +226,38 @@ export default class Ecs { // Created? else if (!this.diff[entityId]) { const filtered = {}; - for (const name in components) { - filtered[name] = false === components[name] + for (const componentName in components) { + filtered[componentName] = false === components[componentName] ? false - : this.Types[name].constructor.filterDefaults(components[name]); + : this.Components[componentName].constructor.filterDefaults(components[componentName]); } this.diff[entityId] = filtered; } // Otherwise, merge. else { - for (const name in components) { - this.diff[entityId][name] = false === components[name] + for (const componentName in components) { + this.diff[entityId][componentName] = false === components[componentName] ? false - : this.Types[name].mergeDiff( - this.diff[entityId][name] || {}, - components[name], + : this.Components[componentName].mergeDiff( + this.diff[entityId][componentName] || {}, + components[componentName], ); } } } - rebuild(entityId, types) { + rebuild(entityId, componentNames) { let existing = []; if (this.$$entities[entityId]) { - existing.push(...this.$$entities[entityId].constructor.types); + existing.push(...this.$$entities[entityId].constructor.componentNames); } - const Class = this.$$entityFactory.makeClass(types(existing), this.Types); + const Class = this.$$entityFactory.makeClass(componentNames(existing), this.Components); this.$$entities[entityId] = new Class(entityId); } reindex(entityIds) { - for (const name in this.Systems) { - this.Systems[name].reindex(entityIds); + for (const systemName in this.Systems) { + this.Systems[systemName].reindex(entityIds); } } @@ -268,18 +271,18 @@ export default class Ecs { for (const [entityId, components] of entities) { unique.add(entityId); const diff = {}; - for (const component of components) { - diff[component] = false; - if (!removing[component]) { - removing[component] = []; + for (const componentName of components) { + diff[componentName] = false; + if (!removing[componentName]) { + removing[componentName] = []; } - removing[component].push(entityId); + removing[componentName].push(entityId); } this.markChange(entityId, diff); - this.rebuild(entityId, (types) => types.filter((type) => !components.includes(type))); + this.rebuild(entityId, (componentNames) => componentNames.filter((type) => !components.includes(type))); } - for (const component in removing) { - this.Types[component].destroyMany(removing[component]); + for (const componentName in removing) { + this.Components[componentName].destroyMany(removing[componentName]); } this.reindex(unique.values()); } @@ -301,7 +304,7 @@ export default class Ecs { let size = 0; // # of components. size += 2; - for (const type in this.Types) { + for (const type in this.Components) { size += Schema.sizeOf(type, {type: 'string'}); } // # of entities. @@ -312,19 +315,19 @@ export default class Ecs { return size; } - system(name) { - return this.Systems[name]; + system(systemName) { + return this.Systems[systemName]; } tick(elapsed) { - for (const name in this.Systems) { - if (this.Systems[name].active) { - this.Systems[name].tick(elapsed); + for (const systemName in this.Systems) { + if (this.Systems[systemName].active) { + this.Systems[systemName].tick(elapsed); } } - for (const name in this.Systems) { - if (this.Systems[name].active) { - this.Systems[name].finalize(elapsed); + for (const systemName in this.Systems) { + if (this.Systems[systemName].active) { + this.Systems[systemName].finalize(elapsed); } } this.tickDestruction(); @@ -332,8 +335,8 @@ export default class Ecs { tickDestruction() { const unique = new Set(); - for (const name in this.Systems) { - const System = this.Systems[name]; + for (const systemName in this.Systems) { + const System = this.Systems[systemName]; if (System.active) { for (let j = 0; j < System.destroying.length; j++) { unique.add(System.destroying[j]); @@ -352,9 +355,9 @@ export default class Ecs { entities[id] = this.$$entities[id].toJSON(); } const systems = []; - for (const name in this.Systems) { - if (this.Systems[name].active) { - systems.push(name); + for (const systemName in this.Systems) { + if (this.Systems[systemName].active) { + systems.push(systemName); } } return { diff --git a/app/ecs/ecs.test.js b/app/ecs/ecs.test.js index a1cb056..554b6bc 100644 --- a/app/ecs/ecs.test.js +++ b/app/ecs/ecs.test.js @@ -68,14 +68,14 @@ test('activates and deactivates systems at runtime', () => { }); test('creates entities with components', () => { - const ecs = new Ecs({Types: {Empty, Position}}); + const ecs = new Ecs({Components: {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", () => { - const ecs = new Ecs({Types: {Empty, Position}}); + const ecs = new Ecs({Components: {Empty, Position}}); const entity = ecs.create({Empty: {}, Position: {y: 128}}); ecs.remove(entity, ['Position']); expect(JSON.stringify(ecs.get(entity))) @@ -83,14 +83,14 @@ test("removes entities' components", () => { }); test('gets entities', () => { - const ecs = new Ecs({Types: {Empty, Position}}); + const ecs = new Ecs({Components: {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', () => { - const ecs = new Ecs({Types: {Empty, Position}}); + const ecs = new Ecs({Components: {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}})); @@ -106,7 +106,7 @@ test('destroys entities', () => { }); test('inserts components into entities', () => { - const ecs = new Ecs({Types: {Empty, Position}}); + const ecs = new Ecs({Components: {Empty, Position}}); const entity = ecs.create({Empty: {}}); ecs.insert(entity, {Position: {y: 128}}); expect(JSON.stringify(ecs.get(entity))) @@ -123,6 +123,7 @@ test('ticks systems', () => { z: {type: 'int32'}, }); const ecs = new Ecs({ + Components: {Momentum, Position}, Systems: { Physics: class Physics extends System { @@ -142,7 +143,6 @@ test('ticks systems', () => { }, }, - Types: {Momentum, Position}, }); ecs.system('Physics').active = true; const entity = ecs.create({Momentum: {}, Position: {y: 128}}); @@ -220,6 +220,7 @@ test('schedules entities to be deleted when ticking systems', () => { test('adds components to and remove components from entities when ticking systems', () => { let addLength, removeLength; const ecs = new Ecs({ + Components: {Foo: wrapSpecification('Foo', {bar: {type: 'uint8'}})}, Systems: { AddComponent: class extends System { static queries() { @@ -248,7 +249,6 @@ test('adds components to and remove components from entities when ticking system } }, }, - Types: {Foo: wrapSpecification('Foo', {bar: {type: 'uint8'}})}, }); ecs.system('AddComponent').active = true; ecs.create(); @@ -275,7 +275,7 @@ test('generates coalesced diffs for entity creation', () => { }); test('generates diffs for adding and removing components', () => { - const ecs = new Ecs({Types: {Position}}); + const ecs = new Ecs({Components: {Position}}); let entity; entity = ecs.create(); ecs.setClean(); @@ -291,7 +291,7 @@ test('generates diffs for adding and removing components', () => { }); test('generates diffs for empty components', () => { - const ecs = new Ecs({Types: {Empty}}); + const ecs = new Ecs({Components: {Empty}}); let entity; entity = ecs.create({Empty}); expect(ecs.diff) @@ -303,7 +303,7 @@ test('generates diffs for empty components', () => { }); test('generates diffs for entity mutations', () => { - const ecs = new Ecs({Types: {Position}}); + const ecs = new Ecs({Components: {Position}}); let entity; entity = ecs.create({Position: {}}); ecs.setClean(); @@ -316,7 +316,7 @@ test('generates diffs for entity mutations', () => { }); test('generates coalesced diffs for components', () => { - const ecs = new Ecs({Types: {Position}}); + const ecs = new Ecs({Components: {Position}}); let entity; entity = ecs.create({Position}); ecs.remove(entity, ['Position']); @@ -328,7 +328,7 @@ test('generates coalesced diffs for components', () => { }); test('generates coalesced diffs for mutations', () => { - const ecs = new Ecs({Types: {Position}}); + const ecs = new Ecs({Components: {Position}}); let entity; entity = ecs.create({Position}); ecs.setClean(); @@ -350,7 +350,7 @@ test('generates diffs for deletions', () => { }); test('applies creation patches', () => { - const ecs = new Ecs({Types: {Position}}); + const ecs = new Ecs({Components: {Position}}); ecs.apply({16: {Position: {x: 64}}}); expect(Array.from(ecs.entities).length) .to.equal(1); @@ -359,7 +359,7 @@ test('applies creation patches', () => { }); test('applies update patches', () => { - const ecs = new Ecs({Types: {Position}}); + const ecs = new Ecs({Components: {Position}}); ecs.createSpecific(16, {Position: {x: 64}}); ecs.apply({16: {Position: {x: 128}}}); expect(Array.from(ecs.entities).length) @@ -369,7 +369,7 @@ test('applies update patches', () => { }); test('applies entity deletion patches', () => { - const ecs = new Ecs({Types: {Position}}); + const ecs = new Ecs({Components: {Position}}); ecs.createSpecific(16, {Position: {x: 64}}); ecs.apply({16: false}); expect(Array.from(ecs.entities).length) @@ -377,17 +377,17 @@ test('applies entity deletion patches', () => { }); test('applies component deletion patches', () => { - const ecs = new Ecs({Types: {Empty, Position}}); + const ecs = new Ecs({Components: {Empty, Position}}); ecs.createSpecific(16, {Empty: {}, Position: {x: 64}}); - expect(ecs.get(16).constructor.types) + expect(ecs.get(16).constructor.componentNames) .to.deep.equal(['Empty', 'Position']); ecs.apply({16: {Empty: false}}); - expect(ecs.get(16).constructor.types) + expect(ecs.get(16).constructor.componentNames) .to.deep.equal(['Position']); }); test('calculates entity size', () => { - const ecs = new Ecs({Types: {Empty, Position}}); + const ecs = new Ecs({Components: {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 @@ -396,7 +396,7 @@ test('calculates entity size', () => { }); test('serializes and deserializes', () => { - const ecs = new Ecs({Types: {Empty, Name, Position}}); + const ecs = new Ecs({Components: {Empty, Name, Position}}); ecs.createSpecific(1, {Empty: {}, Position: {x: 64}}); ecs.createSpecific(16, {Name: {name: 'foobar'}, Position: {x: 128}}); expect(ecs.toJSON()) @@ -409,14 +409,14 @@ test('serializes and deserializes', () => { }); const view = Ecs.serialize(ecs); const deserialized = Ecs.deserialize( - new Ecs({Types: {Empty, Name, Position}}), + new Ecs({Components: {Empty, Name, Position}}), view, ); expect(Array.from(deserialized.entities).length) .to.equal(2); - expect(deserialized.get(1).constructor.types) + expect(deserialized.get(1).constructor.componentNames) .to.deep.equal(['Empty', 'Position']); - expect(deserialized.get(16).constructor.types) + expect(deserialized.get(16).constructor.componentNames) .to.deep.equal(['Name', 'Position']); expect(JSON.stringify(deserialized.get(1))) .to.equal(JSON.stringify({Empty: {}, Position: {x: 64}})) @@ -429,12 +429,12 @@ test('serializes and deserializes', () => { }); test('deserializes from compatible ECS', () => { - const ecs = new Ecs({Types: {Empty, Name, Position}}); + const ecs = new Ecs({Components: {Empty, Name, Position}}); ecs.createSpecific(1, {Empty: {}, Position: {x: 64}}); ecs.createSpecific(16, {Name: {name: 'foobar'}, Position: {x: 128}}); const view = Ecs.serialize(ecs); const deserialized = Ecs.deserialize( - new Ecs({Types: {Empty, Name}}), + new Ecs({Components: {Empty, Name}}), view, ); expect(deserialized.get(1).toJSON()) diff --git a/app/ecs/entity-factory.js b/app/ecs/entity-factory.js index dbf2a6b..243b421 100644 --- a/app/ecs/entity-factory.js +++ b/app/ecs/entity-factory.js @@ -7,8 +7,8 @@ export default class EntityFactory { $$tries = new Node(); - makeClass(types, Types) { - const sorted = types.toSorted(); + makeClass(componentNames, Components) { + const sorted = componentNames.toSorted(); let walk = this.$$tries; let i = 0; while (i < sorted.length) { @@ -20,14 +20,14 @@ export default class EntityFactory { } if (!walk.class) { class Entity { - static types = sorted; + static componentNames = sorted; constructor(id) { this.id = id; } size() { let size = 0; - for (const component of this.constructor.types) { - const instance = Types[component]; + for (const componentName of this.constructor.componentNames) { + const instance = Components[componentName]; size += 2 + 4 + instance.constructor.schema.sizeOf(instance.get(this.id)); } // ID + # of components. @@ -37,7 +37,7 @@ export default class EntityFactory { const properties = {}; for (const type of sorted) { properties[type] = {}; - const get = Types[type].get.bind(Types[type]); + const get = Components[type].get.bind(Components[type]); properties[type].get = function() { return get(this.id); }; diff --git a/app/ecs/query.js b/app/ecs/query.js index c377abc..a988e03 100644 --- a/app/ecs/query.js +++ b/app/ecs/query.js @@ -4,15 +4,15 @@ export default class Query { $$index = new Set(); - constructor(parameters, Types) { + constructor(parameters, Components) { for (let i = 0; i < parameters.length; ++i) { const parameter = parameters[i]; switch (parameter.charCodeAt(0)) { case '!'.charCodeAt(0): - this.$$criteria.without.push(Types[parameter.slice(1)]); + this.$$criteria.without.push(Components[parameter.slice(1)]); break; default: - this.$$criteria.with.push(Types[parameter]); + this.$$criteria.with.push(Components[parameter]); break; } } diff --git a/app/ecs/query.test.js b/app/ecs/query.test.js index 13614f7..82dac5d 100644 --- a/app/ecs/query.test.js +++ b/app/ecs/query.test.js @@ -18,13 +18,13 @@ const A = new (wrapSpecification('A', {a: {type: 'int32', defaultValue: 420}})); const B = new (wrapSpecification('B', {b: {type: 'int32', defaultValue: 69}})); const C = new (wrapSpecification('C', {c: {type: 'int32'}})); -const Types = {A, B, C}; -Types.A.createMany([[2], [3]]); -Types.B.createMany([[1], [2]]); -Types.C.createMany([[2], [4]]); +const Components = {A, B, C}; +Components.A.createMany([[2], [3]]); +Components.B.createMany([[1], [2]]); +Components.C.createMany([[2], [4]]); function testQuery(parameters, expected) { - const query = new Query(parameters, Types); + const query = new Query(parameters, Components); query.reindex([1, 2, 3]); expect(query.count) .to.equal(expected.length); @@ -51,7 +51,7 @@ test('can query excluding', () => { }); test('can deindex', () => { - const query = new Query(['A'], Types); + const query = new Query(['A'], Components); query.reindex([1, 2, 3]); expect(query.count) .to.equal(2); @@ -74,7 +74,7 @@ test('can reindex', () => { }); test('can select', () => { - const query = new Query(['A'], Types); + const query = new Query(['A'], Components); query.reindex([1, 2, 3]); const it = query.select(); const result = it.next(); diff --git a/app/ecs/system.js b/app/ecs/system.js index ff9bea2..ef67acf 100644 --- a/app/ecs/system.js +++ b/app/ecs/system.js @@ -15,7 +15,7 @@ export default class System { this.ecs = ecs; const queries = this.constructor.queries(); for (const i in queries) { - this.queries[i] = new Query(queries[i], ecs.Types); + this.queries[i] = new Query(queries[i], ecs.Components); } this.reindex(ecs.entities); } diff --git a/app/engine/engine.js b/app/engine/engine.js index 9cc0232..2c8792c 100644 --- a/app/engine/engine.js +++ b/app/engine/engine.js @@ -5,8 +5,8 @@ import { TPS, } from '@/constants.js'; import Ecs from '@/ecs/ecs.js'; +import Components from '@/ecs-components/index.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 { @@ -60,7 +60,7 @@ export default class Engine { } createEcs() { - return new Ecs({Systems, Types}); + return new Ecs({Components, Systems}); } async createHomestead(id) { diff --git a/app/react-components/ecs.jsx b/app/react-components/ecs.jsx index b3c965d..92cef71 100644 --- a/app/react-components/ecs.jsx +++ b/app/react-components/ecs.jsx @@ -3,15 +3,15 @@ import {useState} from 'react'; import {RESOLUTION} from '@/constants.js'; import Ecs from '@/ecs/ecs.js'; +import Components from '@/ecs-components/index.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({Systems, Types})); + const [ecs] = useState(new Ecs({Components, Systems})); const [entities, setEntities] = useState({}); const [mainEntity, setMainEntity] = useState(); usePacket('Tick', (payload) => {