diff --git a/app/ecs/component.js b/app/ecs/component.js index d2dc2d0..6a4b56d 100644 --- a/app/ecs/component.js +++ b/app/ecs/component.js @@ -28,22 +28,20 @@ export default class Component { return results; } - create(entityId, values) { + async create(entityId, values) { this.createMany([[entityId, values]]); } - createMany(entries) { + async createMany(entries) { if (entries.length > 0) { const allocated = this.allocateMany(entries.length); const {properties} = this.constructor.schema.specification; const keys = Object.keys(properties); + const promises = []; for (let i = 0; i < entries.length; ++i) { const [entityId, values = {}] = entries[i]; this.map[entityId] = allocated[i]; this.data[allocated[i]].entity = entityId; - if (false === values) { - continue; - } for (let k = 0; k < keys.length; ++k) { const j = keys[k]; const {defaultValue} = properties[j]; @@ -54,7 +52,9 @@ export default class Component { this.data[allocated[i]][j] = defaultValue; } } + promises.push(this.load(this.data[allocated[i]])); } + await Promise.all(promises); } } @@ -108,7 +108,7 @@ export default class Component { return this.data[this.map[entityId]]; } - insertMany(entities) { + async insertMany(entities) { const creating = []; for (let i = 0; i < entities.length; i++) { const [entityId, values] = entities[i]; @@ -122,7 +122,7 @@ export default class Component { } } } - this.createMany(creating); + await this.createMany(creating); } instanceFromSchema() { @@ -170,6 +170,10 @@ export default class Component { return Instance; } + async load(instance) { + return instance; + } + markChange(entityId, key, value) { this.ecs.markChange(entityId, {[this.constructor.componentName]: {[key]: value}}) } diff --git a/app/ecs/ecs.js b/app/ecs/ecs.js index 60845f7..1b81931 100644 --- a/app/ecs/ecs.js +++ b/app/ecs/ecs.js @@ -28,7 +28,7 @@ export default class Ecs { } } - apply(patch) { + async apply(patch) { const creating = []; const destroying = []; const removing = []; @@ -63,7 +63,7 @@ export default class Ecs { this.destroyMany(destroying); this.insertMany(updating); this.removeMany(removing); - this.createManySpecific(creating); + await this.createManySpecific(creating); } changed(criteria) { @@ -91,12 +91,12 @@ export default class Ecs { }; } - create(components = {}) { - const [entityId] = this.createMany([components]); + async create(components = {}) { + const [entityId] = await this.createMany([components]); return entityId; } - createMany(componentsList) { + async createMany(componentsList) { const specificsList = []; for (const components of componentsList) { specificsList.push([this.$$caret++, components]); @@ -104,7 +104,7 @@ export default class Ecs { return this.createManySpecific(specificsList); } - createManySpecific(specificsList) { + async createManySpecific(specificsList) { const entityIds = []; const creating = {}; for (let i = 0; i < specificsList.length; i++) { @@ -125,14 +125,16 @@ export default class Ecs { } this.markChange(entityId, components); } + const promises = []; for (const i in creating) { - this.Components[i].createMany(creating[i]); + promises.push(this.Components[i].createMany(creating[i])); } + await Promise.all(promises); this.reindex(entityIds); return entityIds; } - createSpecific(entityId, components) { + async createSpecific(entityId, components) { return this.createManySpecific([[entityId, components]]); } @@ -142,7 +144,7 @@ export default class Ecs { } } - static deserialize(ecs, view) { + static async deserialize(ecs, view) { const componentNames = Object.keys(ecs.Components); const {entities, systems} = decoder.decode(view.buffer); for (const system of systems) { @@ -164,7 +166,7 @@ export default class Ecs { ]); } ecs.$$caret = max + 1; - ecs.createManySpecific(specifics); + await ecs.createManySpecific(specifics); return ecs; } @@ -220,11 +222,11 @@ export default class Ecs { return this.$$entities[entityId]; } - insert(entityId, components) { - this.insertMany([[entityId, components]]); + async insert(entityId, components) { + return this.insertMany([[entityId, components]]); } - insertMany(entities) { + async insertMany(entities) { const inserting = {}; const unique = new Set(); for (const [entityId, components] of entities) { @@ -240,9 +242,11 @@ export default class Ecs { unique.add(entityId); this.markChange(entityId, diff); } + const promises = []; for (const componentName in inserting) { - this.Components[componentName].insertMany(inserting[componentName]); + promises.push(this.Components[componentName].insertMany(inserting[componentName])); } + await Promise.all(promises); this.reindex(unique.values()); } diff --git a/app/ecs/ecs.test.js b/app/ecs/ecs.test.js index 317cca2..5809a32 100644 --- a/app/ecs/ecs.test.js +++ b/app/ecs/ecs.test.js @@ -23,6 +23,24 @@ const Position = wrapProperties('Position', { z: {type: 'int32'}, }); +function asyncTimesTwo(x) { + return new Promise((resolve) => { + setTimeout(() => { + resolve(x * 2) + }, 5); + }); +} + +class Async extends Component { + static componentName = 'Async'; + static properties = { + foo: {type: 'uint8'}, + }; + async load(instance) { + instance.foo = await asyncTimesTwo(instance.foo); + } +} + test('activates and deactivates systems at runtime', () => { let oneCount = 0; let twoCount = 0; @@ -63,31 +81,31 @@ test('activates and deactivates systems at runtime', () => { .to.equal(2); }); -test('creates entities with components', () => { +test('creates entities with components', async () => { const ecs = new Ecs({Components: {Empty, Position}}); - const entity = ecs.create({Empty: {}, Position: {y: 128}}); + const entity = await 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", () => { +test("removes entities' components", async () => { const ecs = new Ecs({Components: {Empty, Position}}); - const entity = ecs.create({Empty: {}, Position: {y: 128}}); + const entity = await ecs.create({Empty: {}, Position: {y: 128}}); ecs.remove(entity, ['Position']); expect(JSON.stringify(ecs.get(entity))) .to.deep.equal(JSON.stringify({Empty: {}})); }); -test('gets entities', () => { +test('gets entities', async () => { const ecs = new Ecs({Components: {Empty, Position}}); - const entity = ecs.create({Empty: {}, Position: {y: 128}}); + const entity = await ecs.create({Empty: {}, Position: {y: 128}}); expect(JSON.stringify(ecs.get(entity))) .to.deep.equal(JSON.stringify({Empty: {}, Position: {y: 128}})); }); -test('destroys entities', () => { +test('destroys entities', async () => { const ecs = new Ecs({Components: {Empty, Position}}); - const entity = ecs.create({Empty: {}, Position: {y: 128}}); + const entity = await ecs.create({Empty: {}, Position: {y: 128}}); expect(JSON.stringify(ecs.get(entity))) .to.deep.equal(JSON.stringify({Empty: {}, Position: {y: 128}})); expect(ecs.get(entity)) @@ -101,9 +119,9 @@ test('destroys entities', () => { .to.throw(); }); -test('inserts components into entities', () => { +test('inserts components into entities', async () => { const ecs = new Ecs({Components: {Empty, Position}}); - const entity = ecs.create({Empty: {}}); + const entity = await ecs.create({Empty: {}}); ecs.insert(entity, {Position: {y: 128}}); expect(JSON.stringify(ecs.get(entity))) .to.deep.equal(JSON.stringify({Empty: {}, Position: {y: 128}})); @@ -112,7 +130,7 @@ test('inserts components into entities', () => { .to.deep.equal(JSON.stringify({Empty: {}, Position: {y: 64}})); }); -test('ticks systems', () => { +test('ticks systems', async () => { const Momentum = wrapProperties('Momentum', { x: {type: 'int32'}, y: {type: 'int32'}, @@ -141,7 +159,7 @@ test('ticks systems', () => { }, }); ecs.system('Physics').active = true; - const entity = ecs.create({Momentum: {}, Position: {y: 128}}); + const entity = await ecs.create({Momentum: {}, Position: {y: 128}}); const position = JSON.stringify(ecs.get(entity).Position); ecs.tick(1); expect(JSON.stringify(ecs.get(entity).Position)) @@ -262,10 +280,10 @@ test('adds components to and remove components from entities when ticking system .to.be.undefined; }); -test('generates coalesced diffs for entity creation', () => { +test('generates coalesced diffs for entity creation', async () => { const ecs = new Ecs(); let entity; - entity = ecs.create(); + entity = await ecs.create(); expect(ecs.diff) .to.deep.equal({[entity]: {}}); }); @@ -286,10 +304,10 @@ test('generates diffs for adding and removing components', () => { .to.deep.equal({[entity]: {Position: false}}); }); -test('generates diffs for empty components', () => { +test('generates diffs for empty components', async () => { const ecs = new Ecs({Components: {Empty}}); let entity; - entity = ecs.create({Empty: {}}); + entity = await ecs.create({Empty: {}}); expect(ecs.diff) .to.deep.equal({[entity]: {Empty: {}}}); ecs.setClean(); @@ -298,10 +316,10 @@ test('generates diffs for empty components', () => { .to.deep.equal({[entity]: {Empty: false}}); }); -test('generates diffs for entity mutations', () => { +test('generates diffs for entity mutations', async () => { const ecs = new Ecs({Components: {Position}}); let entity; - entity = ecs.create({Position: {}}); + entity = await ecs.create({Position: {}}); ecs.setClean(); ecs.get(entity).Position.x = 128; expect(ecs.diff) @@ -311,10 +329,10 @@ test('generates diffs for entity mutations', () => { .to.deep.equal({}); }); -test('generates coalesced diffs for components', () => { +test('generates coalesced diffs for components', async () => { const ecs = new Ecs({Components: {Position}}); let entity; - entity = ecs.create({Position}); + entity = await ecs.create({Position}); ecs.remove(entity, ['Position']); expect(ecs.diff) .to.deep.equal({[entity]: {Position: false}}); @@ -323,10 +341,10 @@ test('generates coalesced diffs for components', () => { .to.deep.equal({[entity]: {Position: {}}}); }); -test('generates coalesced diffs for mutations', () => { +test('generates coalesced diffs for mutations', async () => { const ecs = new Ecs({Components: {Position}}); let entity; - entity = ecs.create({Position}); + entity = await ecs.create({Position}); ecs.setClean(); ecs.get(entity).Position.x = 128; ecs.get(entity).Position.x = 256; @@ -335,10 +353,10 @@ test('generates coalesced diffs for mutations', () => { .to.deep.equal({[entity]: {Position: {x: 512}}}); }); -test('generates diffs for deletions', () => { +test('generates diffs for deletions', async () => { const ecs = new Ecs(); let entity; - entity = ecs.create(); + entity = await ecs.create(); ecs.setClean(); ecs.destroy(entity); expect(ecs.diff) @@ -438,3 +456,30 @@ test('deserializes from compatible ECS', () => { expect(deserialized.get(16).toJSON()) .to.deep.equal({Name: {name: 'foobar'}}); }); + +test('creates entities asynchronously', async () => { + const ecs = new Ecs({Components: {Async}}); + const entity = await ecs.create({Async: {foo: 64}}); + expect(ecs.get(entity).toJSON()) + .to.deep.equal({Async: {foo: 128}}); +}); + +test('inserts components asynchronously', async () => { + const ecs = new Ecs({Components: {Async}}); + const entity = await ecs.create(); + await ecs.insert(entity, {Async: {foo: 64}}); + expect(ecs.get(entity).toJSON()) + .to.deep.equal({Async: {foo: 128}}); +}); + +test('deserializes asynchronously', async () => { + const ecs = new Ecs({Components: {Async}}); + await ecs.createSpecific(16, {Async: {foo: 16}}); + const view = Ecs.serialize(ecs); + const deserialized = await Ecs.deserialize( + new Ecs({Components: {Async}}), + view, + ); + expect(deserialized.get(16).toJSON()) + .to.deep.equal({Async: {foo: 64}}); +}); diff --git a/app/engine.js b/app/engine.js index 84f7413..2ae6729 100644 --- a/app/engine.js +++ b/app/engine.js @@ -99,7 +99,7 @@ export default class Engine { await this.loadEcs(entityJson.Ecs.path); } const ecs = this.ecses[entityJson.Ecs.path]; - const entity = ecs.create(entityJson); + const entity = await ecs.create(entityJson); this.connections.push(connection); this.connectedPlayers.set( connection, @@ -118,7 +118,7 @@ export default class Engine { async createHomestead(id) { const ecs = this.createEcs(); const area = {x: 100, y: 60}; - ecs.create({ + await ecs.create({ AreaSize: {x: area.x * 16, y: area.y * 16}, Engine: {}, TileLayers: { diff --git a/app/engine.test.js b/app/engine.test.js index 95e875b..b8eb5c6 100644 --- a/app/engine.test.js +++ b/app/engine.test.js @@ -29,7 +29,7 @@ test('visibility-based updates', async () => { await engine.connectPlayer(0, 0); const ecs = engine.ecses['homesteads/0']; // Create an entity. - const entity = ecs.get(ecs.create({ + const entity = ecs.get(await ecs.create({ Forces: {forceX: 1}, Position: {x: (RESOLUTION.x * 1.5) + 32 - 3, y: 20}, VisibleAabb: {},