perf: detached entities
This commit is contained in:
parent
097bf9505f
commit
172b457a8c
|
@ -73,7 +73,7 @@ export default class Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(entityId) {
|
destroy(entityId) {
|
||||||
this.destroyMany([entityId]);
|
this.destroyMany(new Set([entityId]));
|
||||||
}
|
}
|
||||||
|
|
||||||
destroyMany(entityIds) {
|
destroyMany(entityIds) {
|
||||||
|
|
|
@ -25,6 +25,8 @@ export default class Ecs {
|
||||||
|
|
||||||
$$destructionDependencies = new Map();
|
$$destructionDependencies = new Map();
|
||||||
|
|
||||||
|
$$detached = new Set();
|
||||||
|
|
||||||
diff = {};
|
diff = {};
|
||||||
|
|
||||||
Systems = {};
|
Systems = {};
|
||||||
|
@ -58,7 +60,7 @@ export default class Ecs {
|
||||||
|
|
||||||
async apply(patch) {
|
async apply(patch) {
|
||||||
const creating = [];
|
const creating = [];
|
||||||
const destroying = [];
|
const destroying = new Set();
|
||||||
const inserting = [];
|
const inserting = [];
|
||||||
const removing = [];
|
const removing = [];
|
||||||
const updating = [];
|
const updating = [];
|
||||||
|
@ -66,7 +68,7 @@ export default class Ecs {
|
||||||
const entityId = parseInt(entityIdString);
|
const entityId = parseInt(entityIdString);
|
||||||
const components = patch[entityId];
|
const components = patch[entityId];
|
||||||
if (false === components) {
|
if (false === components) {
|
||||||
destroying.push(entityId);
|
destroying.add(entityId);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const componentsToRemove = [];
|
const componentsToRemove = [];
|
||||||
|
@ -105,7 +107,7 @@ export default class Ecs {
|
||||||
creating.push([entityId, componentsToUpdate]);
|
creating.push([entityId, componentsToUpdate]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (destroying.length > 0) {
|
if (destroying.size > 0) {
|
||||||
this.destroyMany(destroying);
|
this.destroyMany(destroying);
|
||||||
}
|
}
|
||||||
const promises = [];
|
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) {
|
changed(criteria) {
|
||||||
const it = Object.entries(this.diff).values();
|
const it = Object.entries(this.diff).values();
|
||||||
return {
|
return {
|
||||||
|
@ -158,10 +167,26 @@ export default class Ecs {
|
||||||
return entityId;
|
return entityId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createDetached(components = {}) {
|
||||||
|
const [entityId] = await this.createManyDetached([components]);
|
||||||
|
return entityId;
|
||||||
|
}
|
||||||
|
|
||||||
async createMany(componentsList) {
|
async createMany(componentsList) {
|
||||||
const specificsList = [];
|
const specificsList = [];
|
||||||
for (const components of componentsList) {
|
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);
|
return this.createManySpecific(specificsList);
|
||||||
}
|
}
|
||||||
|
@ -170,18 +195,20 @@ export default class Ecs {
|
||||||
if (0 === specificsList.length) {
|
if (0 === specificsList.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const entityIds = [];
|
const entityIds = new Set();
|
||||||
const creating = {};
|
const creating = {};
|
||||||
for (let i = 0; i < specificsList.length; i++) {
|
for (let i = 0; i < specificsList.length; i++) {
|
||||||
const [entityId, components] = specificsList[i];
|
const [entityId, components] = specificsList[i];
|
||||||
|
if (!this.$$detached.has(entityId)) {
|
||||||
this.deferredChanges[entityId] = [];
|
this.deferredChanges[entityId] = [];
|
||||||
|
}
|
||||||
const componentNames = [];
|
const componentNames = [];
|
||||||
for (const componentName in components) {
|
for (const componentName in components) {
|
||||||
if (this.Components[componentName]) {
|
if (this.Components[componentName]) {
|
||||||
componentNames.push(componentName);
|
componentNames.push(componentName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entityIds.push(entityId);
|
entityIds.add(entityId);
|
||||||
this.rebuild(entityId, () => componentNames);
|
this.rebuild(entityId, () => componentNames);
|
||||||
for (const componentName of componentNames) {
|
for (const componentName of componentNames) {
|
||||||
if (!creating[componentName]) {
|
if (!creating[componentName]) {
|
||||||
|
@ -198,6 +225,9 @@ export default class Ecs {
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
for (let i = 0; i < specificsList.length; i++) {
|
for (let i = 0; i < specificsList.length; i++) {
|
||||||
const [entityId] = specificsList[i];
|
const [entityId] = specificsList[i];
|
||||||
|
if (this.$$detached.has(entityId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const changes = this.deferredChanges[entityId];
|
const changes = this.deferredChanges[entityId];
|
||||||
delete this.deferredChanges[entityId];
|
delete this.deferredChanges[entityId];
|
||||||
for (const components of changes) {
|
for (const components of changes) {
|
||||||
|
@ -209,16 +239,26 @@ export default class Ecs {
|
||||||
}
|
}
|
||||||
|
|
||||||
async createSpecific(entityId, components) {
|
async createSpecific(entityId, components) {
|
||||||
return this.createManySpecific([[entityId, components]]);
|
const [created] = await this.createManySpecific([[entityId, components]]);
|
||||||
|
return created;
|
||||||
}
|
}
|
||||||
|
|
||||||
deindex(entityIds) {
|
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) {
|
for (const systemName in this.Systems) {
|
||||||
const System = this.Systems[systemName];
|
const System = this.Systems[systemName];
|
||||||
if (!System.active) {
|
if (!System.active) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
System.deindex(entityIds);
|
System.deindex(attached);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,10 +299,6 @@ export default class Ecs {
|
||||||
return dependencies.resolvers.promise;
|
return dependencies.resolvers.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
destroyAll() {
|
|
||||||
this.destroyMany(this.entities);
|
|
||||||
}
|
|
||||||
|
|
||||||
destroyMany(entityIds) {
|
destroyMany(entityIds) {
|
||||||
const destroying = {};
|
const destroying = {};
|
||||||
this.deindex(entityIds);
|
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() {
|
get entities() {
|
||||||
const ids = [];
|
const ids = [];
|
||||||
for (const entity of Object.values(this.$$entities)) {
|
for (const entity of Object.values(this.$$entities)) {
|
||||||
|
@ -327,6 +370,9 @@ export default class Ecs {
|
||||||
}
|
}
|
||||||
|
|
||||||
markChange(entityId, components) {
|
markChange(entityId, components) {
|
||||||
|
if (this.$$detached.has(entityId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this.deferredChanges[entityId]) {
|
if (this.deferredChanges[entityId]) {
|
||||||
this.deferredChanges[entityId].push(components);
|
this.deferredChanges[entityId].push(components);
|
||||||
return;
|
return;
|
||||||
|
@ -415,12 +461,21 @@ export default class Ecs {
|
||||||
}
|
}
|
||||||
|
|
||||||
reindex(entityIds) {
|
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) {
|
for (const systemName in this.Systems) {
|
||||||
const System = this.Systems[systemName];
|
const System = this.Systems[systemName];
|
||||||
if (!System.active) {
|
if (!System.active) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
System.reindex(entityIds);
|
System.reindex(attached);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -110,11 +110,11 @@ test('destroys entities', async () => {
|
||||||
.to.deep.equal(JSON.stringify({Empty: {}, Position: {y: 128}}));
|
.to.deep.equal(JSON.stringify({Empty: {}, Position: {y: 128}}));
|
||||||
expect(ecs.get(entity))
|
expect(ecs.get(entity))
|
||||||
.to.not.be.undefined;
|
.to.not.be.undefined;
|
||||||
ecs.destroyAll();
|
ecs.destroyMany(new Set([entity]));
|
||||||
expect(ecs.get(entity))
|
expect(ecs.get(entity))
|
||||||
.to.be.undefined;
|
.to.be.undefined;
|
||||||
expect(() => {
|
expect(() => {
|
||||||
ecs.destroyMany([entity]);
|
ecs.destroyMany(new Set([entity]));
|
||||||
})
|
})
|
||||||
.to.throw();
|
.to.throw();
|
||||||
});
|
});
|
||||||
|
@ -122,10 +122,10 @@ test('destroys entities', async () => {
|
||||||
test('inserts components into entities', async () => {
|
test('inserts components into entities', async () => {
|
||||||
const ecs = new Ecs({Components: {Empty, Position}});
|
const ecs = new Ecs({Components: {Empty, Position}});
|
||||||
const entity = await ecs.create({Empty: {}});
|
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)))
|
expect(JSON.stringify(ecs.get(entity)))
|
||||||
.to.deep.equal(JSON.stringify({Empty: {}, Position: {y: 128}}));
|
.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)))
|
expect(JSON.stringify(ecs.get(entity)))
|
||||||
.to.deep.equal(JSON.stringify({Empty: {}, Position: {y: 64}}));
|
.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;
|
.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 () => {
|
test('generates diffs for entity creation', async () => {
|
||||||
const ecs = new Ecs();
|
const ecs = new Ecs();
|
||||||
let entity;
|
let entity;
|
||||||
|
@ -210,7 +239,7 @@ test('generates diffs for adding and removing components', async () => {
|
||||||
let entity;
|
let entity;
|
||||||
entity = await ecs.create();
|
entity = await ecs.create();
|
||||||
ecs.setClean();
|
ecs.setClean();
|
||||||
ecs.insert(entity, {Position: {x: 64}});
|
await ecs.insert(entity, {Position: {x: 64}});
|
||||||
expect(ecs.diff)
|
expect(ecs.diff)
|
||||||
.to.deep.equal({[entity]: {Position: {x: 64}}});
|
.to.deep.equal({[entity]: {Position: {x: 64}}});
|
||||||
ecs.setClean();
|
ecs.setClean();
|
||||||
|
@ -246,6 +275,23 @@ test('generates diffs for entity mutations', async () => {
|
||||||
.to.deep.equal({});
|
.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 () => {
|
test('generates coalesced diffs for components', async () => {
|
||||||
const ecs = new Ecs({Components: {Position}});
|
const ecs = new Ecs({Components: {Position}});
|
||||||
let entity;
|
let entity;
|
||||||
|
@ -253,7 +299,7 @@ test('generates coalesced diffs for components', async () => {
|
||||||
ecs.remove(entity, ['Position']);
|
ecs.remove(entity, ['Position']);
|
||||||
expect(ecs.diff)
|
expect(ecs.diff)
|
||||||
.to.deep.equal({[entity]: {Position: false}});
|
.to.deep.equal({[entity]: {Position: false}});
|
||||||
ecs.insert(entity, {Position: {}});
|
await ecs.insert(entity, {Position: {}});
|
||||||
expect(ecs.diff)
|
expect(ecs.diff)
|
||||||
.to.deep.equal({[entity]: {Position: {}}});
|
.to.deep.equal({[entity]: {Position: {}}});
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,7 +20,6 @@ export default class System {
|
||||||
for (const i in queries) {
|
for (const i in queries) {
|
||||||
this.queries[i] = new Query(queries[i], ecs);
|
this.queries[i] = new Query(queries[i], ecs);
|
||||||
}
|
}
|
||||||
this.reindex(ecs.entities);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deindex(entityIds) {
|
deindex(entityIds) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user