ecspresso
Version:
A minimal Entity-Component-System library for typescript and javascript.
47 lines (46 loc) • 2.24 kB
TypeScript
import type { Entity } from "./types";
import { type MatchHost } from "./query-match";
/**
* Host interface that QueryCache uses to read EntityManager state.
* Decouples QueryCache from EntityManager's private fields.
*/
export interface QueryCacheHost<ComponentTypes> extends MatchHost<ComponentTypes> {
getChildren(parentId: number): readonly number[];
allEntities(): IterableIterator<Entity<ComponentTypes>>;
componentIndex(component: keyof ComponentTypes): Set<number> | undefined;
}
/**
* Maintains incrementally-updated Maps of entity id → Entity matching the
* static portion of registered query shapes (with / without / parentHas).
* EntityManager calls the on* hooks on component add/remove, entity
* removal, and parent change. The `changed` filter is applied as a
* post-pass by the caller, since its threshold advances each tick.
*/
export default class QueryCache<ComponentTypes> {
private readonly host;
private readonly caches;
private readonly byComp;
private readonly byParentComp;
constructor(host: QueryCacheHost<ComponentTypes>);
/**
* Returns the Map of entity id → Entity matching the (with, without,
* parentHas) shape. Caches are interned by canonical shape — identical
* shapes share a single Map across systems. Cold-start populates by
* iterating the smallest matching component index.
*/
getOrCreate(withC: ReadonlyArray<keyof ComponentTypes>, withoutC: ReadonlyArray<keyof ComponentTypes>, parentHas: ReadonlyArray<keyof ComponentTypes>): Map<number, Entity<ComponentTypes>>;
/** Test-only: returns the number of distinct interned shapes. */
get cacheCount(): number;
private populate;
private matches;
private reeval;
onComponentChanged(entityId: number, componentName: keyof ComponentTypes): void;
onParentChanged(childId: number): void;
/**
* Drops the entity from every cache. For parentHas caches, also drops
* direct children — once this entity is gone, the parent-link severs
* and children stop matching. Cascade-removed children fire their own
* beforeRemoved first, so the extra delete is a harmless no-op there.
*/
onEntityRemoved(entityId: number): void;
}