perf: detached entities

This commit is contained in:
cha0s 2024-08-03 15:25:30 -05:00
parent 097bf9505f
commit 172b457a8c
4 changed files with 122 additions and 22 deletions

View File

@ -73,7 +73,7 @@ export default class Component {
}
destroy(entityId) {
this.destroyMany([entityId]);
this.destroyMany(new Set([entityId]));
}
destroyMany(entityIds) {

View File

@ -25,6 +25,8 @@ export default class Ecs {
$$destructionDependencies = new Map();
$$detached = new Set();
diff = {};
Systems = {};
@ -58,7 +60,7 @@ export default class Ecs {
async apply(patch) {
const creating = [];
const destroying = [];
const destroying = new Set();
const inserting = [];
const removing = [];
const updating = [];
@ -66,7 +68,7 @@ export default class Ecs {
const entityId = parseInt(entityIdString);
const components = patch[entityId];
if (false === components) {
destroying.push(entityId);
destroying.add(entityId);
continue;
}
const componentsToRemove = [];
@ -105,7 +107,7 @@ export default class Ecs {
creating.push([entityId, componentsToUpdate]);
}
}
if (destroying.length > 0) {
if (destroying.size > 0) {
this.destroyMany(destroying);
}
const promises = [];
@ -124,6 +126,13 @@ export default class Ecs {
}
}
attach(entityIds) {
for (const entityId of entityIds) {
this.$$detached.delete(entityId);
}
this.reindex(entityIds);
}
changed(criteria) {
const it = Object.entries(this.diff).values();
return {
@ -158,10 +167,26 @@ export default class Ecs {
return entityId;
}
async createDetached(components = {}) {
const [entityId] = await this.createManyDetached([components]);
return entityId;
}
async createMany(componentsList) {
const specificsList = [];
for (const components of componentsList) {
specificsList.push([this.$$caret++, components]);
specificsList.push([this.$$caret, components]);
this.$$caret += 1;
}
return this.createManySpecific(specificsList);
}
async createManyDetached(componentsList) {
const specificsList = [];
for (const components of componentsList) {
specificsList.push([this.$$caret, components]);
this.$$detached.add(this.$$caret);
this.$$caret += 1;
}
return this.createManySpecific(specificsList);
}
@ -170,18 +195,20 @@ export default class Ecs {
if (0 === specificsList.length) {
return;
}
const entityIds = [];
const entityIds = new Set();
const creating = {};
for (let i = 0; i < specificsList.length; i++) {
const [entityId, components] = specificsList[i];
if (!this.$$detached.has(entityId)) {
this.deferredChanges[entityId] = [];
}
const componentNames = [];
for (const componentName in components) {
if (this.Components[componentName]) {
componentNames.push(componentName);
}
}
entityIds.push(entityId);
entityIds.add(entityId);
this.rebuild(entityId, () => componentNames);
for (const componentName of componentNames) {
if (!creating[componentName]) {
@ -198,6 +225,9 @@ export default class Ecs {
await Promise.all(promises);
for (let i = 0; i < specificsList.length; i++) {
const [entityId] = specificsList[i];
if (this.$$detached.has(entityId)) {
continue;
}
const changes = this.deferredChanges[entityId];
delete this.deferredChanges[entityId];
for (const components of changes) {
@ -209,16 +239,26 @@ export default class Ecs {
}
async createSpecific(entityId, components) {
return this.createManySpecific([[entityId, components]]);
const [created] = await this.createManySpecific([[entityId, components]]);
return created;
}
deindex(entityIds) {
// Stage 4 Draft / July 6, 2024
// const attached = entityIds.difference(this.$$detached);
const attached = new Set(entityIds);
for (const detached of this.$$detached) {
attached.delete(detached);
}
if (0 === attached.size) {
return;
}
for (const systemName in this.Systems) {
const System = this.Systems[systemName];
if (!System.active) {
continue;
}
System.deindex(entityIds);
System.deindex(attached);
}
}
@ -259,10 +299,6 @@ export default class Ecs {
return dependencies.resolvers.promise;
}
destroyAll() {
this.destroyMany(this.entities);
}
destroyMany(entityIds) {
const destroying = {};
this.deindex(entityIds);
@ -286,6 +322,13 @@ export default class Ecs {
}
}
detach(entityIds) {
this.deindex(entityIds);
for (const entityId of entityIds) {
this.$$detached.add(entityId);
}
}
get entities() {
const ids = [];
for (const entity of Object.values(this.$$entities)) {
@ -327,6 +370,9 @@ export default class Ecs {
}
markChange(entityId, components) {
if (this.$$detached.has(entityId)) {
return;
}
if (this.deferredChanges[entityId]) {
this.deferredChanges[entityId].push(components);
return;
@ -415,12 +461,21 @@ export default class Ecs {
}
reindex(entityIds) {
// Stage 4 Draft / July 6, 2024
// const attached = entityIds.difference(this.$$detached);
const attached = new Set(entityIds);
for (const detached of this.$$detached) {
attached.delete(detached);
}
if (0 === attached.size) {
return;
}
for (const systemName in this.Systems) {
const System = this.Systems[systemName];
if (!System.active) {
continue;
}
System.reindex(entityIds);
System.reindex(attached);
}
}

View File

@ -110,11 +110,11 @@ test('destroys entities', async () => {
.to.deep.equal(JSON.stringify({Empty: {}, Position: {y: 128}}));
expect(ecs.get(entity))
.to.not.be.undefined;
ecs.destroyAll();
ecs.destroyMany(new Set([entity]));
expect(ecs.get(entity))
.to.be.undefined;
expect(() => {
ecs.destroyMany([entity]);
ecs.destroyMany(new Set([entity]));
})
.to.throw();
});
@ -122,10 +122,10 @@ test('destroys entities', async () => {
test('inserts components into entities', async () => {
const ecs = new Ecs({Components: {Empty, Position}});
const entity = await ecs.create({Empty: {}});
ecs.insert(entity, {Position: {y: 128}});
await ecs.insert(entity, {Position: {y: 128}});
expect(JSON.stringify(ecs.get(entity)))
.to.deep.equal(JSON.stringify({Empty: {}, Position: {y: 128}}));
ecs.insert(entity, {Position: {y: 64}});
await ecs.insert(entity, {Position: {y: 64}});
expect(JSON.stringify(ecs.get(entity)))
.to.deep.equal(JSON.stringify({Empty: {}, Position: {y: 64}}));
});
@ -197,6 +197,35 @@ test('schedules entities to be deleted when ticking systems', async () => {
.to.be.undefined;
});
test('skips indexing detached entities', async () => {
const ecs = new Ecs({
Components: {Empty},
Systems: {
Indexer: class extends System {
static queries() {
return {
default: ['Empty'],
};
}
},
},
});
const {$$index: index} = ecs.system('Indexer').queries.default;
ecs.system('Indexer').active = true;
const attached = await ecs.create({Empty: {}});
expect(Array.from(index))
.to.deep.equal([attached]);
ecs.destroyMany(new Set([attached]));
expect(Array.from(index))
.to.deep.equal([]);
const detached = await ecs.createDetached({Empty: {}});
expect(Array.from(index))
.to.deep.equal([]);
ecs.destroyMany(new Set([detached]));
expect(Array.from(index))
.to.deep.equal([]);
});
test('generates diffs for entity creation', async () => {
const ecs = new Ecs();
let entity;
@ -210,7 +239,7 @@ test('generates diffs for adding and removing components', async () => {
let entity;
entity = await ecs.create();
ecs.setClean();
ecs.insert(entity, {Position: {x: 64}});
await ecs.insert(entity, {Position: {x: 64}});
expect(ecs.diff)
.to.deep.equal({[entity]: {Position: {x: 64}}});
ecs.setClean();
@ -246,6 +275,23 @@ test('generates diffs for entity mutations', async () => {
.to.deep.equal({});
});
test('generates no diffs for detached entities', async () => {
const ecs = new Ecs({Components: {Position}});
let entity;
entity = await ecs.createDetached();
expect(ecs.diff)
.to.deep.equal({});
await ecs.insert(entity, {Position: {x: 64}});
expect(ecs.diff)
.to.deep.equal({});
ecs.get(entity).Position.x = 128;
expect(ecs.diff)
.to.deep.equal({});
ecs.remove(entity, ['Position']);
expect(ecs.diff)
.to.deep.equal({});
});
test('generates coalesced diffs for components', async () => {
const ecs = new Ecs({Components: {Position}});
let entity;
@ -253,7 +299,7 @@ test('generates coalesced diffs for components', async () => {
ecs.remove(entity, ['Position']);
expect(ecs.diff)
.to.deep.equal({[entity]: {Position: false}});
ecs.insert(entity, {Position: {}});
await ecs.insert(entity, {Position: {}});
expect(ecs.diff)
.to.deep.equal({[entity]: {Position: {}}});
});

View File

@ -20,7 +20,6 @@ export default class System {
for (const i in queries) {
this.queries[i] = new Query(queries[i], ecs);
}
this.reindex(ecs.entities);
}
deindex(entityIds) {