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 ──────────────────────────────────
|
// ── 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,
|
||||||
|
|||||||
Reference in New Issue
Block a user