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:
hypercross 2026-06-01 14:34:51 +08:00
parent 9e788b135b
commit 2fe9203be9
1 changed files with 27 additions and 4 deletions

View File

@ -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,