refactor: lifetimes

This commit is contained in:
cha0s 2024-07-21 03:54:03 -05:00
parent 8be6c23711
commit 1a7d55f6d3
5 changed files with 27 additions and 150 deletions

View File

@ -22,6 +22,8 @@ export default class Ecs {
deferredChanges = {} deferredChanges = {}
destroying = new Set();
diff = {}; diff = {};
Systems = {}; Systems = {};
@ -206,6 +208,10 @@ export default class Ecs {
} }
destroy(entityId) { destroy(entityId) {
this.destroying.add(entityId);
}
destroyImmediately(entityId) {
this.destroyMany([entityId]); this.destroyMany([entityId]);
} }
@ -427,31 +433,24 @@ export default class Ecs {
} }
tick(elapsed) { tick(elapsed) {
const destroying = new Set();
for (const systemName in this.Systems) { for (const systemName in this.Systems) {
const System = this.Systems[systemName]; const System = this.Systems[systemName];
if (System.active) { if (!System.active) {
if (System.frequency) { continue;
System.elapsed += elapsed; }
if (System.elapsed < System.frequency) { if (!System.frequency) {
continue; System.tick(elapsed);
} continue;
} }
while (!System.frequency || System.elapsed >= System.frequency) { System.elapsed += elapsed;
System.tick(System.frequency ? System.elapsed : elapsed); while (System.elapsed >= System.frequency) {
for (let j = 0; j < System.destroying.length; j++) { System.tick(System.frequency);
destroying.add(System.destroying[j]); System.elapsed -= System.frequency;
}
System.tickDestruction();
if (!System.frequency) {
break;
}
System.elapsed -= System.frequency;
}
} }
} }
if (destroying.size > 0) { if (this.destroying.size > 0) {
this.destroyMany(destroying.values()); this.destroyMany(this.destroying);
this.destroying.clear();
} }
} }

View File

@ -114,7 +114,7 @@ test('destroys entities', async () => {
expect(ecs.get(entity)) expect(ecs.get(entity))
.to.be.undefined; .to.be.undefined;
expect(() => { expect(() => {
ecs.destroy(entity); ecs.destroyImmediately(entity);
}) })
.to.throw(); .to.throw();
}); });
@ -170,50 +170,14 @@ test('ticks systems', async () => {
.to.deep.equal(JSON.stringify({y: 128 + 30})); .to.deep.equal(JSON.stringify({y: 128 + 30}));
}); });
test('creates many entities when ticking systems', () => {
const ecs = new Ecs({
Systems: {
Spawn: class extends System {
tick() {
this.createManyEntities(Array.from({length: 5}).map(() => []));
}
},
},
});
ecs.system('Spawn').active = true;
ecs.create();
expect(ecs.get(5))
.to.be.undefined;
ecs.tick(1);
expect(ecs.get(5))
.to.not.be.undefined;
});
test('creates entities when ticking systems', () => {
const ecs = new Ecs({
Systems: {
Spawn: class extends System {
tick() {
this.createEntity();
}
},
},
});
ecs.system('Spawn').active = true;
ecs.create();
expect(ecs.get(2))
.to.be.undefined;
ecs.tick(1);
expect(ecs.get(2))
.to.not.be.undefined;
});
test('schedules entities to be deleted when ticking systems', () => { test('schedules entities to be deleted when ticking systems', () => {
const ecs = new Ecs({ const ecs = new Ecs({
Systems: { Systems: {
Despawn: class extends System { Despawn: class extends System {
tick() { tick() {
this.destroyEntity(1); this.ecs.destroy(1);
expect(ecs.get(1))
.to.not.be.undefined;
} }
}, },
}, },
@ -225,50 +189,6 @@ test('schedules entities to be deleted when ticking systems', () => {
.to.be.undefined; .to.be.undefined;
}); });
test('adds components to and remove components from entities when ticking systems', async () => {
let promise;
const ecs = new Ecs({
Components: {Foo: wrapProperties('Foo', {bar: {type: 'uint8'}})},
Systems: {
AddComponent: class extends System {
static queries() {
return {
default: ['Foo'],
};
}
tick() {
promise = this.insertComponents(1, {Foo: {}});
}
},
RemoveComponent: class extends System {
static queries() {
return {
default: ['Foo'],
};
}
tick() {
this.removeComponents(1, ['Foo']);
}
},
},
});
ecs.system('AddComponent').active = true;
ecs.create();
ecs.tick(1);
await promise;
expect(Array.from(ecs.system('AddComponent').select('default')).length)
.to.equal(1);
expect(ecs.get(1).Foo)
.to.not.be.undefined;
ecs.system('AddComponent').active = false;
ecs.system('RemoveComponent').active = true;
ecs.tick(1);
expect(Array.from(ecs.system('RemoveComponent').select('default')).length)
.to.equal(0);
expect(ecs.get(1).Foo)
.to.be.undefined;
});
test('generates diffs for entity creation', async () => { test('generates diffs for entity creation', async () => {
const ecs = new Ecs(); const ecs = new Ecs();
let entity; let entity;
@ -347,7 +267,7 @@ test('generates diffs for deletions', async () => {
let entity; let entity;
entity = await ecs.create(); entity = await ecs.create();
ecs.setClean(); ecs.setClean();
ecs.destroy(entity); ecs.destroyImmediately(entity);
expect(ecs.diff) expect(ecs.diff)
.to.deep.equal({[entity]: false}); .to.deep.equal({[entity]: false});
}); });

View File

@ -6,8 +6,6 @@ export default class System {
active = false; active = false;
destroying = [];
ecs; ecs;
elapsed = 0; elapsed = 0;
@ -25,38 +23,12 @@ export default class System {
this.reindex(ecs.entities); this.reindex(ecs.entities);
} }
createEntity(components) {
return this.ecs.create(components);
}
createManyEntities(componentsList) {
return this.ecs.createMany(componentsList);
}
deindex(entityIds) { deindex(entityIds) {
for (const i in this.queries) { for (const i in this.queries) {
this.queries[i].deindex(entityIds); this.queries[i].deindex(entityIds);
} }
} }
destroyEntity(entityId) {
this.destroyManyEntities([entityId]);
}
destroyManyEntities(entityIds) {
for (let i = 0; i < entityIds.length; i++) {
this.destroying.push(entityIds[i]);
}
}
async insertComponents(entityId, components) {
return this.ecs.insert(entityId, components);
}
async insertManyComponents(components) {
return this.ecs.insertMany(components);
}
static get priority() { static get priority() {
return { return {
phase: 'normal', phase: 'normal',
@ -73,14 +45,6 @@ export default class System {
} }
} }
removeComponents(entityId, components) {
this.ecs.remove(entityId, components);
}
removeManyComponents(entityIds) {
this.ecs.removeMany(entityIds);
}
select(query) { select(query) {
return this.queries[query].select(); return this.queries[query].select();
} }
@ -117,13 +81,6 @@ export default class System {
); );
} }
tickDestruction() {
if (this.destroying.length > 0) {
this.deindex(this.destroying);
}
this.destroying = [];
}
tick() {} tick() {}
} }

View File

@ -322,6 +322,7 @@ export default class Engine {
stop() { stop() {
clearTimeout(this.handle); clearTimeout(this.handle);
this.handle = undefined; this.handle = undefined;
this.tick(0);
} }
tick(elapsed) { tick(elapsed) {

View File

@ -45,8 +45,8 @@ const engine = new Engine(WorkerServer);
onmessage = async (event) => { onmessage = async (event) => {
if (0 === event.data) { if (0 === event.data) {
engine.stop();
await engine.disconnectPlayer(0); await engine.disconnectPlayer(0);
engine.stop();
await engine.saveEcses(); await engine.saveEcses();
postMessage(0); postMessage(0);
return; return;