Skip to content

TestWorld

Defined in: src/test-utils.ts:15

Convenience subclass of EcsWorld intended for unit tests. Provides no extra behaviour — it exists so test code can new TestWorld() without building a project-specific subclass just to exercise the ECS primitives.

Register components and tags on the instance like you would on any other world: world.registerComponent(PositionDef).

new TestWorld(): TestWorld

TestWorld

EcsWorld.constructor

readonly clock: ChangeClock

Defined in: src/world.ts:60

Change-detection clock shared by every registered store. Stores stamp added / changed ticks from it; queries with added / changed filters advance it. See Query.changed.

EcsWorld.clock


readonly lifecycle: EventBus<LifecycleEvent, { ComponentAdded: { component: string; id: EntityId; type: "ComponentAdded"; value: unknown; }; ComponentRemoved: { component: string; id: EntityId; type: "ComponentRemoved"; }; EntityCreated: { id: EntityId; type: "EntityCreated"; }; EntityDestroyed: { id: EntityId; type: "EntityDestroyed"; }; TagAdded: { id: EntityId; tag: string; type: "TagAdded"; }; TagRemoved: { id: EntityId; tag: string; type: "TagRemoved"; }; }>

Defined in: src/world.ts:75

Engine-internal lifecycle bus. Emits EntityCreated, EntityDestroyed, ComponentAdded, ComponentRemoved, TagAdded, TagRemoved. Queue-based like any EventBus — call lifecycle.flush() (typically once per tick) to dispatch. Subscribers are not preserved across world swaps.

An event is only built and queued while its type has a subscriber, so an unobserved world pays nothing per mutation. A handler therefore sees only the changes made after it subscribed.

EcsWorld.lifecycle

clearAll(): void

Defined in: src/world.ts:150

Reset the world to an empty state — clears every registered component store, tag store, the command queue, and the spatial index (if enabled), then resets id allocation so the next entity is index 0, generation 0. Component and tag registrations are preserved; only their contents are wiped. Ids held from before the reset may match entities created after it.

Intended for “restart the game” / “respawn” paths in prototypes that tear down and rebuild mid-session. Silent by design — does not emit EntityDestroyed lifecycle events for the cleared entities, to avoid a reset-time event storm. Callers that need per-entity cleanup observation should destroy entities individually before calling this.

Pending lifecycle events are dropped with the queue clear.

void

EcsWorld.clearAll


createEntity(): EntityId

Defined in: src/world.ts:169

Create an empty live entity. Its id may reuse a destroyed entity’s index, with a newer generation, so ids held for destroyed entities stay dead.

EntityId

EcsWorld.createEntity


destroyEntity(id): void

Defined in: src/world.ts:182

Remove every component and tag from id and mark it dead, freeing its index for reuse. A no-op unless id is alive, so destroying twice, or through a stale id whose index was reused, never touches another entity.

EntityId

void

EcsWorld.destroyEntity


enableSpatial<T, S>(def, structure, options?): S

Defined in: src/world.ts:206

Index a component’s values in structure (e.g. HashGrid2D or HashGrid3D from @pierre/ecs/modules/spatial), keeping it in sync with the component’s store — and, with options.withTag, restricted to entities holding that tag. Entities that already qualify are indexed immediately.

May be called any number of times: several components, or several indexes over one component split by tag. Each call needs its own structure. Returns structure with its own type, so backend-specific extras (a grid’s getAt, say) stay typed on the returned handle — keep it to query the index.

Once a component is indexed, change its values through move so the indexes see the old and new position.

T

S extends SpatialStructure<T>

ComponentDef<T>

S

SpatialOptions = {}

S

EcsWorld.enableSpatial


endOfTick(): void

Defined in: src/world.ts:260

End-of-tick convenience: flushCommands() then lifecycle.flush().

Ordering invariant: structural commands (destroys included) apply first so lifecycle subscribers see the final entity set — any EntityDestroyed / ComponentRemoved / ComponentAdded events they emit are dispatched in the same flush pass.

Prefer this over calling both manually in game loops that do not use tick-runner!TickRunner (which already sequences these internally).

void

EcsWorld.endOfTick


entityCount(): number

Defined in: src/world.ts:266

Number of live entities (created and not yet destroyed).

number

EcsWorld.entityCount


flushCommands(): void

Defined in: src/world.ts:277

Apply every queued structural change (queueSpawn / queueDestroy / queueAdd / queueRemove / queueAddTag / queueRemoveTag) in insertion order. Safe to call after a system iteration loop — the loop enqueues, this applies once, so stores are never mutated mid-iteration. A repeated destroy of the same id within one flush collapses to a single destruction.

void

EcsWorld.flushCommands


getColumnStore<T>(def): ColumnStore<T>

Defined in: src/world.ts:345

Fast-path accessor for a columnar (all-numeric) component’s Structure-of-Arrays store, exposing column() / slotOf() for zero-allocation hot loops. Throws if the component uses object storage.

T

ComponentDef<T>

ColumnStore<T>

EcsWorld.getColumnStore


getStore<T>(def): ComponentStoreLike<T>

Defined in: src/world.ts:352

T

ComponentDef<T>

ComponentStoreLike<T>

EcsWorld.getStore


getStoreByName(name): ComponentStoreLike<unknown> | undefined

Defined in: src/world.ts:359

string

ComponentStoreLike<unknown> | undefined

EcsWorld.getStoreByName


getTag(def): TagStore

Defined in: src/world.ts:363

TagDef

TagStore

EcsWorld.getTag


getTagByName(name): TagStore | undefined

Defined in: src/world.ts:370

string

TagStore | undefined

EcsWorld.getTagByName


hasPlugin(name): boolean

Defined in: src/world.ts:375

Whether a plugin with the given name has been installed via use.

string

boolean

EcsWorld.hasPlugin


isAlive(id): boolean

Defined in: src/world.ts:380

Whether id refers to a live entity — created and not yet destroyed.

EntityId

boolean

EcsWorld.isAlive


liveEntities(): IterableIterator<EntityId>

Defined in: src/world.ts:385

Iterate the live entity ids. Order is unspecified.

IterableIterator<EntityId>

EcsWorld.liveEntities


loadJSON(data): void

Defined in: src/world.ts:395

In-place load — clears existing stores and repopulates from the serialized payload, restoring id allocation so saved ids, live or stale, keep their meaning. Throws when a component or tag row names an entity that is not live in the saved allocation.

unknown

void

EcsWorld.loadJSON


move<T>(def, id, to): void

Defined in: src/world.ts:437

Move an entity’s indexed component to to: every index enableSpatial bound to def that holds the entity moves it from the current value, then to’s fields are written into the stored value and the change is stamped. A no-op when the entity lacks the component. Throws when def has no index.

T extends object

ComponentDef<T>

EntityId

T

void

EcsWorld.move


query<D>(…defs): Query<ComponentValues<D>>

Defined in: src/world.ts:459

Build a Query yielding [id, ...values] for every entity holding all of defs. Build it once and iterate it every tick: the query caches its archetype resolution across passes. world.query() with no defs is the entry point for tag-only or filter-only queries.

D extends AnyComponentDef[]

…D

Query<ComponentValues<D>>

EcsWorld.query


queueAdd<T>(def, id, value): void

Defined in: src/world.ts:474

Enqueue a component add on the next flushCommands() call. Safe during system iteration — the store is not mutated until the queue is drained. Throws now if def is not registered.

T

ComponentDef<T>

EntityId

T

void

EcsWorld.queueAdd


queueAddTag(def, id): void

Defined in: src/world.ts:479

Enqueue a tag add on the next flushCommands() call. Throws now if def is not registered.

TagDef

EntityId

void

EcsWorld.queueAddTag


queueDestroy(id): void

Defined in: src/world.ts:489

Enqueue an entity for destruction on the next flushCommands() call. Safe to call during system iteration — destroyEntity is not invoked until the queue is drained, so in-flight queries aren’t mutated. Repeated enqueues of the same id collapse to a single destruction per flush.

EntityId

void

EcsWorld.queueDestroy


queueRemove<T>(def, id): void

Defined in: src/world.ts:497

Enqueue a component remove on the next flushCommands() call. Safe during system iteration. Throws now if def is not registered.

T

ComponentDef<T>

EntityId

void

EcsWorld.queueRemove


queueRemoveTag(def, id): void

Defined in: src/world.ts:502

Enqueue a tag remove on the next flushCommands() call. Throws now if def is not registered.

TagDef

EntityId

void

EcsWorld.queueRemoveTag


queueSpawn(template?, overrides?): EntityId

Defined in: src/world.ts:516

Reserve an entity id now and enqueue its creation (and optional template population) for the next flushCommands() call. The id is returned immediately so a caller can queueAdd(def, id, …) against it inside the same loop; the entity becomes alive and emits EntityCreated at flush.

If template references a component name that has not been registered, the error is thrown at flush time (inside _populateEntity), not here. An error mid-flush aborts the remaining commands in that flush batch.

EntityTemplate

Record<string, unknown>

EntityId

EcsWorld.queueSpawn


registerComponent<T>(def, options?): ComponentStoreLike<T>

Defined in: src/world.ts:522

T

ComponentDef<T>

ColumnStoreOptions = {}

ComponentStoreLike<T>

EcsWorld.registerComponent


registerTag(def): TagStore

Defined in: src/world.ts:567

TagDef

TagStore

EcsWorld.registerTag


spawn(template, overrides?): EntityId

Defined in: src/world.ts:590

Create an entity from a template, merging per-component overrides (shallow merge per component).

EntityTemplate

Record<string, unknown>

EntityId

EcsWorld.spawn


spawnBatch(entries): EntityId[]

Defined in: src/world.ts:612

Spawn many entities at once, suppressing per-entity DEV validation until the whole batch is attached. All entities validate together after all template components have been set, which means cross-entity requirements (if ever introduced) see a consistent world, and validation overhead is paid once instead of per call. Behaviour matches calling spawn in a loop apart from the deferred validation.

readonly object[]

EntityId[]

EcsWorld.spawnBatch


toJSON(): Record<string, unknown>

Defined in: src/world.ts:630

Record<string, unknown>

EcsWorld.toJSON


transferEntity(id, from, componentNames?): void

Defined in: src/world.ts:657

Copy an entity’s components from another world into this one, preserving its EntityId. Used during level transitions and any scenario where an entity must survive a world swap.

  • Components are iterated in registration order. Each value is structuredClone-d on copy so the two worlds never share references.
  • Tags are not transferred — tags are application-semantic (which tags follow the entity depends on the game). Callers own tag handling.
  • id becomes live here with the same index and generation. Throws when this world’s entity at that index is a different one, when the index is retired, or when it is free at a newer generation than id’s.
  • If componentNames is given, only those components are transferred. Names must be registered on this world; unknown names throw.
  • Values already present on this world for id are overwritten.

EntityId

EcsWorld

readonly string[]

void

EcsWorld.transferEntity


use(…plugins): this

Defined in: src/world.ts:690

Install one or more plugin!Plugins, calling each plugin’s build with this world exactly once. A plugin’s name must be unique per world — re-installing the same name throws. Installed names survive clearAll (the registrations a plugin’s build created do too), so a plugin cannot be re-used after a reset. Returns this for chaining.

…Plugin[]

this

EcsWorld.use