World
world/world.ts:280 Extends AbstractComponentHost<World>
The root container for an arcade2d simulation. A World owns the set of
live WorldObjects, drives the per-frame update loop that animates
them, and itself hosts world-scoped Components — i.e. components
that conceptually belong to the simulation as a whole rather than to any
single object (a Scene graphics root, a physics broadphase, an
input sampler, an audio mixer, and so on). It also optionally resolves
named prefab lookups against a PrefabRegistry passed at
construction.
The class is designed so a typical game's "main loop" is one line: construct the world, then call World.update once per animation frame. Everything else — when components run, what order they run in, how spawns/destroys interact with the running tick — is the responsibility of this class and is described below.
The update tick
Each call to World.update executes the following schedule, in this exact order. Understanding the schedule is the single most important thing about working with arcade2d, because everything else (spawn semantics, destroy semantics, cross-component wiring, ordering pitfalls) is downstream of it.
┌──────────────────────────────────────────────────────────────────┐
│ Phase 1 — Pre-update │
│ 1a. World components, in insertion order, call `onPreUpdate` │
│ 1b. Each live WorldObject, in spawn order, has every component │
│ (in component insertion order) call `onPreUpdate` │
├──────────────────────────────────────────────────────────────────┤
│ Phase 2 — Update │
│ 2a. World components, in insertion order, call `onUpdate` │
│ 2b. Each live WorldObject, in spawn order, has every component │
│ (in component insertion order) call `onUpdate` │
├──────────────────────────────────────────────────────────────────┤
│ Phase 3 — Post-update │
│ 3a. World components, in insertion order, call `onPostUpdate` │
│ 3b. Each live WorldObject, in spawn order, has every component │
│ (in component insertion order) call `onPostUpdate` │
├──────────────────────────────────────────────────────────────────┤
│ Phase 4 — Sweep + flush │
│ 4a. Run `onDestroy` on every object marked destroyed this tick │
│ (or earlier) and remove them from the live set + id map │
│ 4b. Promote objects spawned mid-tick into the live set so they │
│ participate in the *next* tick │
└──────────────────────────────────────────────────────────────────┘Two ordering rules that follow from the schedule
Phases are strict. Every component on every host finishes its
onPreUpdatebefore any component anywhere startsonUpdate. EveryonUpdatefinishes before anyonPostUpdatestarts. This is the contract you rely on to make camera-follows-player and similar "I need everyone else's results" patterns work without race conditions.Within a phase, world components run before object components. A world-scoped input sampler in Phase 1a will have finished polling by the time a controller component in Phase 1b reads it. A world-scoped physics step in Phase 2a will have resolved collisions by the time a per-object damage handler in Phase 2b decides what to do about them.
Within each of those two sub-orderings (world components among
themselves; object components on a single host among themselves), the
order is insertion order — i.e. the order they were registered via
addComponents (or the order they appear in a Prefab's
component map). There is no priority or weight system; if A must run
before B, register A first.
Choosing the right phase
The three update hooks aren't "early, middle, late" so much as three specific roles. Picking the right one is mostly about asking what does this code need to be true when it runs?
onPreUpdate — sample and prepare
Use when you produce state that other components will consume during
onUpdate. The hook is optional: components that don't have prep work
to do should omit it. Canonical uses:
- Input polling. A world-scoped
InputSystemreads keyboard/mouse state once per tick inonPreUpdateand exposes it via getters. Every controller component then queries it inonUpdateand sees a consistent snapshot. - Per-frame buffer clears. A world-scoped collision broadphase
clears the previous tick's overlap set in
onPreUpdateso per-object colliders can populate it during their ownonUpdatewithout stepping on the previous frame's results. - Interpolation snapshots. A graphics component caches its pre-update transform so any other system that wants to interpolate between "before" and "after" positions during the post phase has a clean snapshot to work from.
onUpdate — do the work
The main per-frame body of behaviour. Required on every component — even if it's empty. Use when the work doesn't depend on having seen the results of everyone else's update this frame. The vast majority of components only need this hook. Canonical uses:
- Movement and behaviour. Controllers reading input and translating
it into changes to the host's
position, AI components evaluating their decision logic, projectile components advancing themselves. - World simulation steps. A world-scoped physics system stepping its solver, a particle emitter advancing emission timers, a wave manager spawning enemies on a cadence.
- Lifetime accounting. A bullet decrementing its remaining lifetime and self-destructing when it hits zero.
onPostUpdate — react to everyone else's updates
Use when your work requires that every other component has finished
its onUpdate first. Optional; skip when not needed. Canonical uses:
- Camera follow. A world- or object-scoped camera component reads
the player's already-moved position in
onPostUpdateand centres the viewport on it. If you did this inonUpdateand the player's controller ran later in the iteration order, you'd lag by a frame. - Transform sync to a renderer. A graphics component copying
host.positioninto a PIXI display object inonPostUpdateguarantees the final visual reflects every behaviour change that happened this tick, including ones from late-running components. - Late audits and assertions. A debug overlay that needs to inspect the world's settled state, count active enemies, etc.
Rule of thumb
If the code reads state set by other components: pre-update produces,
update consumes, post-update reacts. If you only set state, you almost
certainly want onUpdate. If you're not sure, start with onUpdate
and move to a different phase only if you see a one-frame-lag bug.
Spawn and destroy semantics
Both spawns (createFromPrefab, createFromPrefabName,
createEmpty) and destroys (WorldObject.destroy) are
deferred and re-entrancy-safe so that running components can't
observe inconsistent state mid-tick.
Spawning
- Outside a tick (e.g. during world setup before the first
update()call), spawned objects join the live set immediately and participate in the very next tick from Phase 1 onward. - During a tick, spawned objects are queued and only join the live set during Phase 4b. They first participate in the next tick. This keeps Phase 2/3 iteration order stable even if a component spawns ten new objects mid-update.
- Either way, the new object is findable via World.findById
immediately — the id map is updated synchronously on spawn — so
cross-references in the same tick are valid even if the spawned
object hasn't yet been driven by an update. Bulk tag queries are
deliberately the other way: World.findByTag and
World.findOneByTag exclude pending objects by default,
because iterating over a mix of "fully ticked" and "exists but has
never ticked" objects silently produces wrong answers (an AOE
damaging enemies that have not initialised, a counter reporting
non-yet-rendered units). Opt back into the same-tick view with
{ includePending: true }when you genuinely want it.
Destroying
WorldObject.destroy only marks the object. The real removal happens in Phase 4a. Consequences:
- An object destroyed mid-tick is skipped for all remaining phases. If a controller destroys itself in Phase 1, neither Phase 2 nor Phase 3 will touch it. This is the "no final tick" rule and exists so destroyed objects can't observe state from after their own death.
onDestroyis idempotent. Callingdestroy()twice, or destroying an already-cleaned object during teardown, is a no-op. Components on a destroyed object also have theironDestroyrun exactly once during sweep.- Spawned-and-destroyed-in-the-same-tick objects never enter the
live set but do receive
onDestroyduring Phase 4b, so component cleanup is honoured.
Enable/disable: per-component and per-host
Two gates control whether update hooks run, layered from most specific to most general:
- Per-component — each Component carries an optional
enabledflag. When explicitlyfalse, the engine skips that component'sonPreUpdate,onUpdate, andonPostUpdate. - Per-host — every host (the World itself, and every
WorldObject) carries an AbstractComponentHost.enabled
field. When
false, every component on that host has all three update phases skipped at a single early-return — the cheap way to freeze an entire object during a cutscene or pause a UI widget while a menu is up. The world-level toggle gates the world's own components only; object iteration is controlled by whetherupdate()is called.
Neither gate touches onAdded or onDestroy. Both fire regardless so
a component is never left half-attached and a destroyed host is always
cleanly torn down.
Error isolation
Every component hook (onPreUpdate, onUpdate, onPostUpdate,
onDestroy) is wrapped in a try/catch by the engine. A thrown error in
one component does not abort the tick — the rest of the host's
components, and every other host, continue to run. Errors are routed
through World.reportError, which forwards to the optional
WorldOptions.onError handler (defaulting to console.error).
If you want fail-fast, throw from inside onError and the engine's
try/finally will let the exception propagate out of update().
Cross-references
- Component — the per-host lifecycle interface.
- WorldObject — the per-object host driven by Phases 1b/2b/3b.
- Prefab — the declarative template used to build objects.
- PrefabRegistry — name-keyed prefab lookup, attachable to a world for World.createFromPrefabName.
- WorldErrorContext — payload handed to
onErrorwhen a component callback throws.
Example
// The blessed path: bootstrap a Game, then ask it for a World.
const game = await Game.bootstrap({ canvas: { fill: 'window' } });
const world = game.createWorld({
components: (world) => ({
// Phase 1a (pre-update) — registered first so it runs first.
input: () => new InputSystem(world),
// Phase 2a (update) — runs second, sees fresh input.
physics: () => new PhysicsSystem(world),
// Phase 3a (post-update) — runs third, sees settled positions.
camera: () => new CameraSystem(world),
}),
prefabs: prefabRegistry,
});Constructors
constructor(game: Game, options: WorldOptions): World Parameters
-
gameGame -
optionsWorldOptions
Properties
enabled: boolean Master gate on every component update phase this host runs. When
false, the host's onPreUpdate, onUpdate, and onPostUpdate
iterations short-circuit at a single check — useful for freezing a
single object during a cutscene, pausing a UI widget while a menu is
up, or temporarily disabling a debug overlay without tearing the
components down.
The flag is not propagated to onAdded or onDestroy. Those
always fire so a host can never end up with half-attached components,
and a disabled object is still cleanly torn down when destroyed.
Defaults to true (active). Flip back to true and the host resumes
ticking from its preserved state on the next world update().
Accessors
camera: Camera The world's auto-attached Camera. Always present — the engine
registers a camera during construction before any user components run,
so this getter never returns null and game code can treat the
camera's existence as an invariant.
game: Game The Game this world belongs to. Always present — passed positionally at construction and held as a non-null invariant. Test/headless callers that don't want to bootstrap a real renderer mint one via Game.createHeadless.
prefabs: null | PrefabRegistry The PrefabRegistry this world resolves prefabs against by name,
or null if no registry was attached at construction. Exposed so callers
can introspect or share the registry with other systems (e.g. editor
tools).
Methods
_createDependencyResolver(component: Component<World>, key: string): WorldComponentDependencyResolver Subclass hook that produces the concrete dependency resolver the host
hands to a component's resolveDependencies. The World hosts a
resolver scoped to siblings only; a WorldObject hosts one that
also exposes cross-tier lookups against the parent world.
Engine-internal — never called by user code.
Returns
WorldComponentDependencyResolver _handleComponentDestroyError(error: unknown, key: string): void Hook for subclasses to intercept errors thrown by a component's
onDestroy during AbstractComponentHost.removeAllComponents.
Default behaviour is to log and swallow — a single bad component must
not prevent the rest of the host's components from being torn down.
Subclasses may override to route errors through their own reporting
channel.
Parameters
-
errorunknown -
keystring
Returns
void addComponent(key: string, component: Component<World>, options: AddComponentOptions): Component<World> Adds a new component to the host object. Throws if a component with the
specified key already exists. Calls onAdded() on the component once
registered with its host.
Parameters
-
keystring -
componentComponent<World> -
optionsAddComponentOptions
addComponentFromFactory(key: string, factory: ComponentFactory<World>, options: AddComponentOptions): Component<World> Adds a new component to the host object using a factory function.
Internally produces the new component using the factory function, then
calls addComponent() with the result.
The advantage of using this method over addComponent() is that the
factory function is provided with the host.
Parameters
-
keystring -
factoryComponentFactory<World> -
optionsAddComponentOptions
addComponents(components: ComponentMap<World>, options: AddComponentOptions): ComponentMap<World> Adds a new set of components to the host object. Throws if a component with
the specified key already exists. Calls onAdded() on each component
after they are all registered with the host, rather than one by one.
This is important for components that may want to reference each other
during the addition phase via host.getComponent() or similar methods.
It is recommended to use this method rather than addComponent() in
situations like initialization of a new host object.
Parameters
-
componentsComponentMap<World> -
optionsAddComponentOptions
Returns
ComponentMap<World> addComponentsFromFactories(map: ComponentFactoryMap<World>, options?: AddComponentOptions): ComponentMap<World> Adds a new set of components to the host object based on an input map of
component keys to factory functions. Behavious is equivalent to
addComponents() using the key and output of each factory function.
Parameters
-
mapComponentFactoryMap<World> -
options?AddComponentOptions
Returns
ComponentMap<World> createEmpty(position: PointPrimitive, tags?: readonly string[]): WorldObject Creates a new empty world object. Useful for creating one-off objects that don't necessarily need to be based on a prefab definition.
Parameters
-
positionPointPrimitive -
tags?readonly string[]
Returns
WorldObject createFromPrefab(prefab: Prefab, position: PointPrimitive): WorldObject Creates a new world object from a target prefab. The object is added to
the world's live set immediately if called outside a tick, or queued
into the pending set if called from within an onUpdate handler — see
the World class docs for the full spawn-timing contract.
Parameters
-
prefabPrefab -
positionPointPrimitive
Returns
WorldObject createFromPrefabName(name: string, position: PointPrimitive): WorldObject Creates a new world object from a prefab looked up by name in the world's attached PrefabRegistry. Throws if no registry was passed at construction, or if the registry has no prefab under the given name.
This is the entry point intended for deserialised world state — saved data refers to prefabs by name, and this method is how the engine rehydrates them.
Parameters
-
namestring -
positionPointPrimitive
Returns
WorldObject destroy(): void Immediately destroys the world, all of its objects (including any spawned during the current tick that have not yet been flushed into the live set), and any included components.
Safe to call against objects that have already been marked destroyed but not yet swept — WorldObject.onDestroy is idempotent, so cleanup runs exactly once regardless of prior state.
Returns
void findById(id: string): null | WorldObject Finds an object in the world using its ID. Newly spawned objects are findable from the moment they are created, even before they are promoted into the live iteration set at the end of the current tick — this is the "spawn and immediately wire references by id" pattern. Bulk tag queries are deliberately not symmetric with this behaviour; see World.findByTag for the reasoning.
Parameters
-
idstring
findByTag(tag: string, options: FindByTagOptions): readonly WorldObject[] Finds all live objects in the world with the given tag. By default
excludes objects that were spawned during the current tick and are
still awaiting promotion into the live set — bulk iteration that
mixes fully-ticked objects with ones whose components have never run
is the canonical source of "I damaged the enemy before it existed"
bugs, so the engine defaults to live-only. Use
includePending to opt back
into the same-tick view (debug overlays, editor tooling, the rare
gameplay system that genuinely wants both).
Parameters
-
tagstring -
optionsFindByTagOptions
findOneByTag(tag: string, options: FindByTagOptions): null | WorldObject Finds a single live object in the world with the given tag. Same
default exclusion rule as World.findByTag — pending objects
are skipped unless FindByTagOptions.includePending is set.
Returns the first live match in spawn order, or the first pending
match (when opted in) if no live object matches.
Parameters
-
tagstring -
optionsFindByTagOptions
getComponent(key: string): T Gets a component from the host object using the key it was registered with.
Throws if the component does not exist. Performs an efficient lookup on a
local Map instance.
Parameters
-
keystring
Returns
T getComponentByType(type: ComponentHostConstructor<T>): T Gets a component from the host object using its type. Throws if no
component of the type exists, or if more than one component of the type
exists — in the multi-match case, getComponentByType deliberately
does not pick one for you. Use ComponentHost.getComponentsByType
when you genuinely expect multiple matches, or look the component up by
its string key.
Performs an O(n) lookup once per type initially, then caches the resolved key for O(1) lookups on subsequent calls. The cache is invalidated whenever a component is removed.
Parameters
-
typeComponentHostConstructor<T>
Returns
T getComponentsByType(type: ComponentHostConstructor<T>): readonly T[] Gets every component on the host of the given type, in the order they were originally registered. Returns an empty array if no components match.
Unlike ComponentHost.getComponentByType, this method never throws on multiplicity — it is the explicit "I expect more than one" accessor.
Parameters
-
typeComponentHostConstructor<T>
Returns
readonly T[] getHostReference(): World Gets a reference to the host object that this component is attached to. Required for the compiler to be able to resolve the type of the host object correctly in some internal function calls (e.g. when resolving the type of the host object from a factory function).
Returns
World getKeyboardState(): KeyboardState Returns the current keyboard state — the set of physical keys held at
tick-snapshot time, plus an isDown(code) predicate for membership
tests. Sourced from the parent Game's Keyboard
component; this method is a thin pass-through that exists for symmetry
with World.getMouseState (keyboards have no camera-projected
variant of their own — the same held-key set is meaningful in screen
space and world space alike).
See KeyboardState for the KeyboardEvent.code convention used
to identify physical keys.
Returns
KeyboardState Example
onUpdate() {
const keys = this.host.world.getKeyboardState();
if (keys.isDown('KeyW')) this.host.position.y -= 5;
if (keys.isDown('KeyS')) this.host.position.y += 5;
}getMouseState(): MouseState Returns the current mouse state — cursor position (both in world space, with the camera transform inverted, and in raw canvas-local pixels) and the held/released state of the three standard buttons.
The returned MouseState is a fresh snapshot per call; the engine deliberately never hands back a live reference, so a caller stashing the value for a frame won't see it change mid-tick.
The screen-space snapshot is sourced from the parent Game's
Mouse component; this method then projects the screen
coordinate through the world's Scene (which knows the camera
transform) to populate the world-space position. Worlds constructed
via Game.createWorld have both components attached automatically.
Returns
MouseState Throws
EngineError with code ErrorCode.COMPONENT_NOT_FOUND when the world has no Scene component registered (so screen→world projection is not possible).
Example
onUpdate() {
const { position, buttons } = this.host.world.getMouseState();
if (buttons.left) {
this.host.position.moveTowards(position, 5);
}
}getNullableComponent(key: string): null | T Gets a component from the host object using the key it was registered with.
Returns null if the component does not exist, rather than throwing an
error. Useful for referencing transient or optional components without
manually handling errors.
Parameters
-
keystring
Returns
null | T getNullableComponentByType(type: ComponentHostConstructor<T>): null | T Gets a component from the host object using its type. Returns null if
the component does not exist or if more than one component of the type
is registered (i.e. the lookup is ambiguous) — the nullable variant
collapses both "not found" and "ambiguous" into a single null. Use
ComponentHost.getComponentsByType when you need to distinguish
them.
Parameters
-
typeComponentHostConstructor<T>
Returns
null | T hasComponent(key: string): boolean Checks if the host object has a component with the specified key.
Parameters
-
keystring
Returns
boolean hasComponentByType(type: ComponentHostConstructor<T>): boolean Checks if the host object has a component with the specified type.
Parameters
-
typeComponentHostConstructor<T>
Returns
boolean removeAllComponents(): void Removes all components from the host object. Typically called internally
when the lifecycle of the host object is terminated. Differs from
individually removing components in that it first calls onDestroy() on
each component, then removes references from the host object in a separate
step. This allows cleaner teardown of components that may reference each
other.
Returns
void removeComponent(key: string): void Removes a component from the host object. Care should be taken when
manually removing components, as methods like getComponent() will throw
if components do not exist. Removal is idempotent and will do nothing if
the component does not exist i.e. was already removed, or never existed.
Parameters
-
keystring
Returns
void reportError(context: WorldErrorContext): void Routes a runtime component error through the configured handler, or to
console.error when no handler is supplied. Called by the engine
whenever a component's onUpdate or onDestroy throws. Safe to call
from user code too if you want to surface your own errors through the
same channel.
Parameters
-
contextWorldErrorContext
Returns
void update(): WorldUpdate Returns
WorldUpdate