perf: track component and relationship counts per entity
Introduce component and relationship counters to optimize entity destruction and component checks. This allows for short-circuiting the destruction process for bare entities and provides an O(1) check for `hasAnyComponent`.
This commit is contained in:
parent
9e788b135b
commit
2fe9203be9
31
src/world.ts
31
src/world.ts
|
|
@ -21,6 +21,8 @@ export class World {
|
||||||
// ── Entity pools ──────────────────────────────────
|
// ── Entity pools ──────────────────────────────────
|
||||||
private _generations: number[] = [];
|
private _generations: number[] = [];
|
||||||
private _free: number[] = [];
|
private _free: number[] = [];
|
||||||
|
private _componentCounts: number[] = [];
|
||||||
|
private _relCounts: number[] = [];
|
||||||
|
|
||||||
// ── Component storage ─────────────────────────────
|
// ── Component storage ─────────────────────────────
|
||||||
private _components = new Map<symbol, SparseSet<any>>();
|
private _components = new Map<symbol, SparseSet<any>>();
|
||||||
|
|
@ -52,6 +54,8 @@ export class World {
|
||||||
if (this._free.length > 0) {
|
if (this._free.length > 0) {
|
||||||
const idx = this._free.pop()!;
|
const idx = this._free.pop()!;
|
||||||
const gen = this._generations[idx];
|
const gen = this._generations[idx];
|
||||||
|
this._componentCounts[idx] = 0;
|
||||||
|
this._relCounts[idx] = 0;
|
||||||
const e = makeEntity(idx, gen);
|
const e = makeEntity(idx, gen);
|
||||||
this._emit({ type: "spawned", entity: e });
|
this._emit({ type: "spawned", entity: e });
|
||||||
return e;
|
return e;
|
||||||
|
|
@ -59,6 +63,8 @@ export class World {
|
||||||
|
|
||||||
const idx = this._generations.length;
|
const idx = this._generations.length;
|
||||||
this._generations.push(1);
|
this._generations.push(1);
|
||||||
|
this._componentCounts.push(0);
|
||||||
|
this._relCounts.push(0);
|
||||||
const e = makeEntity(idx, 1);
|
const e = makeEntity(idx, 1);
|
||||||
this._emit({ type: "spawned", entity: e });
|
this._emit({ type: "spawned", entity: e });
|
||||||
return e;
|
return e;
|
||||||
|
|
@ -69,6 +75,14 @@ export class World {
|
||||||
const idx = entityIndex(entity);
|
const idx = entityIndex(entity);
|
||||||
if (!this._isAlive(idx, entity)) return;
|
if (!this._isAlive(idx, entity)) return;
|
||||||
|
|
||||||
|
// Short-circuit: truly bare entities have nothing to clean up
|
||||||
|
if (this._componentCounts[idx] === 0 && this._relCounts[idx] === 0) {
|
||||||
|
this._generations[idx]++;
|
||||||
|
this._free.push(idx);
|
||||||
|
this._emit({ type: "destroyed", entity });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Clean relationships before components
|
// Clean relationships before components
|
||||||
for (const [key] of this._relForward) {
|
for (const [key] of this._relForward) {
|
||||||
// Entity as source
|
// Entity as source
|
||||||
|
|
@ -99,6 +113,8 @@ export class World {
|
||||||
dirty.delete(idx);
|
dirty.delete(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._componentCounts[idx] = 0;
|
||||||
|
this._relCounts[idx] = 0;
|
||||||
this._generations[idx]++;
|
this._generations[idx]++;
|
||||||
this._free.push(idx);
|
this._free.push(idx);
|
||||||
|
|
||||||
|
|
@ -114,10 +130,7 @@ export class World {
|
||||||
hasAnyComponent(entity: Entity): boolean {
|
hasAnyComponent(entity: Entity): boolean {
|
||||||
const idx = entityIndex(entity);
|
const idx = entityIndex(entity);
|
||||||
if (!this._isAlive(idx, entity)) return false;
|
if (!this._isAlive(idx, entity)) return false;
|
||||||
for (const store of this._components.values()) {
|
return this._componentCounts[idx] > 0;
|
||||||
if (store.has(idx)) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Component operations ──────────────────────────
|
// ── Component operations ──────────────────────────
|
||||||
|
|
@ -131,9 +144,12 @@ export class World {
|
||||||
this._assertAlive(idx, entity);
|
this._assertAlive(idx, entity);
|
||||||
|
|
||||||
const store = this._getOrCreateStore(def);
|
const store = this._getOrCreateStore(def);
|
||||||
|
const existed = store.has(idx);
|
||||||
const value = { ...def.defaults, ...init };
|
const value = { ...def.defaults, ...init };
|
||||||
store.set(idx, value);
|
store.set(idx, value);
|
||||||
|
|
||||||
|
if (!existed) this._componentCounts[idx]++;
|
||||||
|
|
||||||
this._emit({ type: "componentAdded", entity, component: def });
|
this._emit({ type: "componentAdded", entity, component: def });
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
@ -149,6 +165,7 @@ export class World {
|
||||||
this._dirty.get(def._key)?.delete(idx);
|
this._dirty.get(def._key)?.delete(idx);
|
||||||
|
|
||||||
if (removed) {
|
if (removed) {
|
||||||
|
this._componentCounts[idx]--;
|
||||||
this._emit({ type: "componentRemoved", entity, component: def });
|
this._emit({ type: "componentRemoved", entity, component: def });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -265,6 +282,9 @@ export class World {
|
||||||
}
|
}
|
||||||
rev.add(si);
|
rev.add(si);
|
||||||
|
|
||||||
|
this._relCounts[si]++;
|
||||||
|
this._relCounts[ti]++;
|
||||||
|
|
||||||
this._emit({
|
this._emit({
|
||||||
type: "relationshipAdded",
|
type: "relationshipAdded",
|
||||||
source,
|
source,
|
||||||
|
|
@ -570,6 +590,9 @@ export class World {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._relCounts[si]--;
|
||||||
|
this._relCounts[ti]--;
|
||||||
|
|
||||||
this._emit({
|
this._emit({
|
||||||
type: "relationshipRemoved",
|
type: "relationshipRemoved",
|
||||||
source,
|
source,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue