feat: async creation

This commit is contained in:
cha0s 2024-06-27 06:28:00 -05:00
parent 76f18e09c7
commit 74ec36dfa8
5 changed files with 101 additions and 48 deletions

View File

@ -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}})
}

View File

@ -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());
}

View File

@ -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}});
});

View File

@ -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: {

View File

@ -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: {},