Skip to content

World

The World is a sparse-set ECS keyed by integer entity ids. Components are stored per-key; queries walk the smallest store first.

import { world, component, pos_c, type Id } from "@f0rbit/forge";
const vel_c = component<{ dx: number; dy: number }>("vel");
const w = world();
const e: Id = w.spawn(
[pos_c, { x: 0, y: 0 }],
[vel_c, { dx: 1, dy: 0 }],
);
w.has(e, vel_c); // → true
const r = w.get(e, pos_c); // → Result<{ x, y }, EngineError>
if (r.ok) r.value; // → { x: 0, y: 0 }
w.set(e, pos_c, { x: 5, y: 0 });
w.remove(e, vel_c);
w.despawn(e);
w.count(); // → 0
MethodSignatureNotes
spawn(...c: [Component<T>, T][]) => IdReturns a fresh integer id.
spawn_many`(count, factory)(specs[]) => readonly Id[]`
despawn(id) => Result<void, EngineError>Removes from every store.
despawn_marked(...markers) => numberBulk-despawn every entity with ALL given markers. Returns count.
has(id, c) => booleanPure check, no allocation.
get(id, c) => Result<T, EngineError>kind: "component_missing" if absent.
set(id, c, data) => Result<void, EngineError>Adds the component if not present and bumps the version. Errors entity_not_found if despawned.
remove(id, c) => Result<void, EngineError>Errors component_missing if not present.
query(cs, opts?) => Query<C>Marker components are auto-elided from the tuple. See Queries.
count() => numberLive entity count.
clear() => voidDespawns every entity. Resources are NOT cleared.

spawn_at is exposed via world[internal].spawn_at — used by snapshot restore to preserve ids across round-trips. Not part of the public surface.

Two forms. Use the count + factory form for grid-style fills, the array form when you already have an array of specs (one entity per element):

// (count, factory) — best when entities share a common shape parameterised by index
const grid_ids = w.spawn_many(100, (i) => [
[pos_c, { x: (i % 20) * 16, y: Math.floor(i / 20) * 16 }],
[floor_c, true],
]);
// (specs) — best when you already have an array of inputs
const floor_ids = w.spawn_many(
[...floor_keys].map(k => {
const cell = g.unkey(k);
const wp = g.cell_to_world(cell.x, cell.y);
return [[pos_c, { x: wp.x, y: wp.y }], [floor_c, true]] as const;
}),
);

Signature:

world.spawn_many: {
(count: number, factory: (i: number) => readonly [Component<any>, any][]): readonly Id[];
(specs: readonly (readonly [Component<any>, any][])[]): readonly Id[];
};

Both forms return ids in spawn order. The runtime branches on Array.isArray(arg0). count must be a non-negative integer; the factory is called count times with i = 0..count-1.

For level transitions, restarts, or “kill every X”, despawn_marked despawns every entity that has all the listed marker components:

w.despawn_marked(floor_c); // every entity with floor_c
w.despawn_marked(enemy_c, alive_c); // every alive enemy
w.despawn_marked(); // no-op, returns 0

Signature:

world.despawn_marked(...markers: readonly Component<any>[]): number;

Returns the number of entities despawned. Snapshots the matching id set before mutating, so it’s safe to call mid-iteration of unrelated queries.

For a hard reset (clear everything regardless of markers), use world.clear() instead.