refactor: make getRelatedTo return an iterator
Convert `getRelatedTo` from returning an array to returning an `IterableIterator`. This improves memory efficiency by yielding entities lazily instead of allocating a new array on every call.
This commit is contained in:
parent
2469cdc7cb
commit
5d125167cc
|
|
@ -52,8 +52,8 @@ function clearSubtree(world: World, entity: Entity): void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function childrenOf(world: World, parent: Entity): Entity[] {
|
function* childrenOf(world: World, parent: Entity): IterableIterator<Entity> {
|
||||||
return world.getRelatedTo(parent, ChildOf);
|
yield* world.getRelatedTo(parent, ChildOf);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parentOf(world: World, child: Entity): Entity | null {
|
function parentOf(world: World, child: Entity): Entity | null {
|
||||||
|
|
@ -230,10 +230,9 @@ export class TaskRunner {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _executeRandom(entity: Entity): void {
|
private _executeRandom(entity: Entity): void {
|
||||||
const children = childrenOf(this._world, entity);
|
// Single pass: check terminals and collect eligible children
|
||||||
|
const eligible: Entity[] = [];
|
||||||
// Check if any child already reached a terminal
|
for (const child of childrenOf(this._world, entity)) {
|
||||||
for (const child of children) {
|
|
||||||
if (isTerminal(this._world, child)) {
|
if (isTerminal(this._world, child)) {
|
||||||
const status = terminalStatus(this._world, child)!;
|
const status = terminalStatus(this._world, child)!;
|
||||||
this._finish(
|
this._finish(
|
||||||
|
|
@ -246,13 +245,14 @@ export class TaskRunner {
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
!this._world.has(child, Running) &&
|
||||||
|
!this._world.has(child, Scheduled)
|
||||||
|
) {
|
||||||
|
eligible.push(child);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pick a random child that isn't already running
|
|
||||||
const eligible = children.filter(
|
|
||||||
(c) => !this._world.has(c, Running) && !this._world.has(c, Scheduled),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (eligible.length > 0) {
|
if (eligible.length > 0) {
|
||||||
const pick = eligible[Math.floor(Math.random() * eligible.length)];
|
const pick = eligible[Math.floor(Math.random() * eligible.length)];
|
||||||
this._world.add(pick, Scheduled);
|
this._world.add(pick, Scheduled);
|
||||||
|
|
@ -261,7 +261,7 @@ export class TaskRunner {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _executeRepeat(entity: Entity): void {
|
private _executeRepeat(entity: Entity): void {
|
||||||
const children = childrenOf(this._world, entity);
|
const children = [...childrenOf(this._world, entity)];
|
||||||
|
|
||||||
// Repeat expects exactly one child
|
// Repeat expects exactly one child
|
||||||
if (children.length === 0) return;
|
if (children.length === 0) return;
|
||||||
|
|
|
||||||
15
src/world.ts
15
src/world.ts
|
|
@ -388,17 +388,22 @@ export class World {
|
||||||
/**
|
/**
|
||||||
* Get all source entities that point to `target` via this relationship.
|
* Get all source entities that point to `target` via this relationship.
|
||||||
*/
|
*/
|
||||||
getRelatedTo(target: Entity, rel: RelationshipDef): Entity[] {
|
*getRelatedTo(
|
||||||
|
target: Entity,
|
||||||
|
rel: RelationshipDef,
|
||||||
|
): IterableIterator<Entity> {
|
||||||
const ti = entityIndex(target);
|
const ti = entityIndex(target);
|
||||||
if (!this._isAlive(ti, target)) return [];
|
if (!this._isAlive(ti, target)) return;
|
||||||
|
|
||||||
const rev = this._relReverse.get(rel._key);
|
const rev = this._relReverse.get(rel._key);
|
||||||
if (!rev) return [];
|
if (!rev) return;
|
||||||
|
|
||||||
const sources = rev.get(ti);
|
const sources = rev.get(ti);
|
||||||
if (!sources) return [];
|
if (!sources) return;
|
||||||
|
|
||||||
return [...sources].map((si) => makeEntity(si, this._generations[si]));
|
for (const si of sources) {
|
||||||
|
yield makeEntity(si, this._generations[si]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ describe("Relationships", () => {
|
||||||
world.relate(a, ChildOf, parent);
|
world.relate(a, ChildOf, parent);
|
||||||
world.relate(b, ChildOf, parent);
|
world.relate(b, ChildOf, parent);
|
||||||
|
|
||||||
const children = world.getRelatedTo(parent, ChildOf);
|
const children = [...world.getRelatedTo(parent, ChildOf)];
|
||||||
expect(children).toHaveLength(2);
|
expect(children).toHaveLength(2);
|
||||||
expect(children).toContain(a);
|
expect(children).toContain(a);
|
||||||
expect(children).toContain(b);
|
expect(children).toContain(b);
|
||||||
|
|
@ -63,7 +63,7 @@ describe("Relationships", () => {
|
||||||
|
|
||||||
it("getRelatedTo returns empty when no edges", () => {
|
it("getRelatedTo returns empty when no edges", () => {
|
||||||
const e = world.spawn();
|
const e = world.spawn();
|
||||||
expect(world.getRelatedTo(e, ChildOf)).toEqual([]);
|
expect([...world.getRelatedTo(e, ChildOf)]).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("unrelate removes the relationship", () => {
|
it("unrelate removes the relationship", () => {
|
||||||
|
|
@ -74,7 +74,7 @@ describe("Relationships", () => {
|
||||||
world.unrelate(child, ChildOf);
|
world.unrelate(child, ChildOf);
|
||||||
|
|
||||||
expect(world.getRelated(child, ChildOf)).toBeUndefined();
|
expect(world.getRelated(child, ChildOf)).toBeUndefined();
|
||||||
expect(world.getRelatedTo(parent, ChildOf)).toEqual([]);
|
expect([...world.getRelatedTo(parent, ChildOf)]).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("unrelate is idempotent", () => {
|
it("unrelate is idempotent", () => {
|
||||||
|
|
@ -89,13 +89,13 @@ describe("Relationships", () => {
|
||||||
|
|
||||||
world.relate(a, ChildOf, b);
|
world.relate(a, ChildOf, b);
|
||||||
expect(world.getRelated(a, ChildOf)).toBe(b);
|
expect(world.getRelated(a, ChildOf)).toBe(b);
|
||||||
expect(world.getRelatedTo(b, ChildOf)).toContain(a);
|
expect([...world.getRelatedTo(b, ChildOf)]).toContain(a);
|
||||||
|
|
||||||
world.relate(a, ChildOf, c);
|
world.relate(a, ChildOf, c);
|
||||||
expect(world.getRelated(a, ChildOf)).toBe(c);
|
expect(world.getRelated(a, ChildOf)).toBe(c);
|
||||||
// a should no longer point to b
|
// a should no longer point to b
|
||||||
expect(world.getRelatedTo(b, ChildOf)).toEqual([]);
|
expect([...world.getRelatedTo(b, ChildOf)]).toEqual([]);
|
||||||
expect(world.getRelatedTo(c, ChildOf)).toContain(a);
|
expect([...world.getRelatedTo(c, ChildOf)]).toContain(a);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -243,7 +243,7 @@ describe("Destroy cleanup", () => {
|
||||||
|
|
||||||
world.destroy(child);
|
world.destroy(child);
|
||||||
expect(world.getRelated(child, ChildOf)).toBeUndefined();
|
expect(world.getRelated(child, ChildOf)).toBeUndefined();
|
||||||
expect(world.getRelatedTo(parent, ChildOf)).toEqual([]);
|
expect([...world.getRelatedTo(parent, ChildOf)]).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("removes edges when target is destroyed", () => {
|
it("removes edges when target is destroyed", () => {
|
||||||
|
|
@ -252,7 +252,7 @@ describe("Destroy cleanup", () => {
|
||||||
world.relate(child, ChildOf, parent);
|
world.relate(child, ChildOf, parent);
|
||||||
|
|
||||||
world.destroy(parent);
|
world.destroy(parent);
|
||||||
expect(world.getRelatedTo(parent, ChildOf)).toEqual([]);
|
expect([...world.getRelatedTo(parent, ChildOf)]).toEqual([]);
|
||||||
expect(world.getRelated(child, ChildOf)).toBeUndefined();
|
expect(world.getRelated(child, ChildOf)).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -376,6 +376,6 @@ describe("Dead entity safety", () => {
|
||||||
it("getRelatedTo returns empty for dead entity", () => {
|
it("getRelatedTo returns empty for dead entity", () => {
|
||||||
const e = world.spawn();
|
const e = world.spawn();
|
||||||
world.destroy(e);
|
world.destroy(e);
|
||||||
expect(world.getRelatedTo(e, ChildOf)).toEqual([]);
|
expect([...world.getRelatedTo(e, ChildOf)]).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue