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:
+27
-4
@@ -21,6 +21,8 @@ export class World {
|
||||
// ── Entity pools ──────────────────────────────────
|
||||
private _generations: number[] = [];
|
||||
private _free: number[] = [];
|
||||
private _componentCounts: number[] = [];
|
||||
private _relCounts: number[] = [];
|
||||
|
||||
// ── Component storage ─────────────────────────────
|
||||
private _components = new Map<symbol, SparseSet<any>>();
|
||||
@@ -52,6 +54,8 @@ export class World {
|
||||
if (this._free.length > 0) {
|
||||
const idx = this._free.pop()!;
|
||||
const gen = this._generations[idx];
|
||||
this._componentCounts[idx] = 0;
|
||||
this._relCounts[idx] = 0;
|
||||
const e = makeEntity(idx, gen);
|
||||
this._emit({ type: "spawned", entity: e });
|
||||
return e;
|
||||
@@ -59,6 +63,8 @@ export class World {
|
||||
|
||||
const idx = this._generations.length;
|
||||
this._generations.push(1);
|
||||
this._componentCounts.push(0);
|
||||
this._relCounts.push(0);
|
||||
const e = makeEntity(idx, 1);
|
||||
this._emit({ type: "spawned", entity: e });
|
||||
return e;
|
||||
@@ -69,6 +75,14 @@ export class World {
|
||||
const idx = entityIndex(entity);
|
||||
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
|
||||
for (const [key] of this._relForward) {
|
||||
// Entity as source
|
||||
@@ -99,6 +113,8 @@ export class World {
|
||||
dirty.delete(idx);
|
||||
}
|
||||
|
||||
this._componentCounts[idx] = 0;
|
||||
this._relCounts[idx] = 0;
|
||||
this._generations[idx]++;
|
||||
this._free.push(idx);
|
||||
|
||||
@@ -114,10 +130,7 @@ export class World {
|
||||
hasAnyComponent(entity: Entity): boolean {
|
||||
const idx = entityIndex(entity);
|
||||
if (!this._isAlive(idx, entity)) return false;
|
||||
for (const store of this._components.values()) {
|
||||
if (store.has(idx)) return true;
|
||||
}
|
||||
return false;
|
||||
return this._componentCounts[idx] > 0;
|
||||
}
|
||||
|
||||
// ── Component operations ──────────────────────────
|
||||
@@ -131,9 +144,12 @@ export class World {
|
||||
this._assertAlive(idx, entity);
|
||||
|
||||
const store = this._getOrCreateStore(def);
|
||||
const existed = store.has(idx);
|
||||
const value = { ...def.defaults, ...init };
|
||||
store.set(idx, value);
|
||||
|
||||
if (!existed) this._componentCounts[idx]++;
|
||||
|
||||
this._emit({ type: "componentAdded", entity, component: def });
|
||||
return value;
|
||||
}
|
||||
@@ -149,6 +165,7 @@ export class World {
|
||||
this._dirty.get(def._key)?.delete(idx);
|
||||
|
||||
if (removed) {
|
||||
this._componentCounts[idx]--;
|
||||
this._emit({ type: "componentRemoved", entity, component: def });
|
||||
}
|
||||
}
|
||||
@@ -265,6 +282,9 @@ export class World {
|
||||
}
|
||||
rev.add(si);
|
||||
|
||||
this._relCounts[si]++;
|
||||
this._relCounts[ti]++;
|
||||
|
||||
this._emit({
|
||||
type: "relationshipAdded",
|
||||
source,
|
||||
@@ -570,6 +590,9 @@ export class World {
|
||||
}
|
||||
}
|
||||
|
||||
this._relCounts[si]--;
|
||||
this._relCounts[ti]--;
|
||||
|
||||
this._emit({
|
||||
type: "relationshipRemoved",
|
||||
source,
|
||||
|
||||
Reference in New Issue
Block a user