Entities

Deep dive into entities: the fundamental building blocks of the ECS architecture. Learn about entity lifecycle, recycling, hierarchies, and metadata management.

In Web Engine's ECS architecture, entities are the simplest concept: they're just unsigned 32-bit integers that serve as unique identifiers. An entity has no data and no behavior on its own — it's merely an ID that ties components together.

What is an Entity?#

Think of an entity as a database primary key. It's a number that identifies a game object, but all the actual data lives in component arrays indexed by that number.

import { addEntity, addComponent } from 'bitecs';
import { Transform, RigidBody, Mesh } from '@web-engine/core';
// Create a new entity
const eid = addEntity(world);
// eid is just a number
console.log(eid); // 42
// Add components to give it data and behavior
addComponent(world, Transform, eid);
addComponent(world, RigidBody, eid);
addComponent(world, Mesh, eid);
// Now set component data
Transform.position.x[eid] = 10.0;
RigidBody.mass[eid] = 50.0;

Entity IDs are Sequential

bitECS assigns entity IDs sequentially starting from 0. The first entity is 0, second is 1, and so on. When entities are removed, their IDs are recycled to keep the arrays compact.

Entity Lifecycle#

Entities go through a well-defined lifecycle from creation to destruction:

Creation#

import { addEntity, addComponent } from 'bitecs';
import { Transform, setEntityDisplayName } from '@web-engine/core';
// Create a new entity
const eid = addEntity(world);
// Always add Transform (required for all game objects)
addComponent(world, Transform, eid);
// Set default transform values
Transform.position.x[eid] = 0;
Transform.position.y[eid] = 0;
Transform.position.z[eid] = 0;
Transform.scale.x[eid] = 1;
Transform.scale.y[eid] = 1;
Transform.scale.z[eid] = 1;
// Set a display name (stored in EntityMetadataStore)
setEntityDisplayName(eid, 'Player');

Destruction#

When removing an entity, Web Engine ensures all components and metadata are cleaned up properly. The EntityCleanupSystem handles this at the end of each frame.

import { removeEntity } from 'bitecs';
import { clearEntityMetadata } from '@web-engine/core';
// Remove an entity
removeEntity(world, eid);
// Clean up metadata (done automatically by EntityCleanupSystem)
clearEntityMetadata(eid);
// The entity ID is now recycled and can be reused

Deferred Cleanup

Web Engine uses deferred entity cleanup to avoid issues with systems still processing entities. When you call removeEntity, the entity is marked for removal but not immediately destroyed. The EntityCleanupSystem runs at the end of the frame to finalize cleanup.

Entity Recycling#

bitECS automatically recycles entity IDs to prevent unbounded growth of internal arrays. When an entity is removed, its ID goes into a free list and will be reused for the next entity creation.

// Create first entity
const eid1 = addEntity(world); // ID: 0
// Create second entity
const eid2 = addEntity(world); // ID: 1
// Remove first entity
removeEntity(world, eid1);
// Create third entity - reuses ID 0
const eid3 = addEntity(world); // ID: 0 (recycled!)
console.log(eid3 === eid1); // true

Benefits of Recycling#

  • Compact arrays — Component arrays stay dense, improving cache efficiency
  • Bounded memory — Maximum entity count is fixed (default: 1 million)
  • No fragmentation — Gaps are immediately filled by new entities
  • Predictable performance — No allocation spikes from array growth

Stale Entity References

Because entity IDs are recycled, storing entity IDs long-term can be dangerous. If you hold a reference to entity 42, destroy it, then create a new entity, ID 42 might refer to a completely different object. Always validate entity references before use.

Finding Entities with Queries#

Queries let you find all entities with a specific set of components. Web Engine uses bitECS queries, which are automatically cached for O(1) performance after the first call.

import { defineQuery } from 'bitecs';
import { Transform, RigidBody, Mesh } from '@web-engine/core';
// Define a query for all entities with Transform and RigidBody
const physicsQuery = defineQuery([Transform, RigidBody]);
// Execute the query (cached after first call)
const entities = physicsQuery(world);
// Iterate over results
for (let i = 0; i < entities.length; i++) {
const eid = entities[i];
// Access components
const mass = RigidBody.mass[eid];
const x = Transform.position.x[eid];
console.log(`Entity ${eid} at (${x}, ...) with mass ${mass}`);
}

For more details on queries, see the Queries documentation.

Entity Hierarchies#

Web Engine supports parent-child relationships between entities using the Parent and LocalTransform components. This enables scene graphs where child entities inherit their parent's transform.

import { addComponent } from 'bitecs';
import { Transform, LocalTransform, Parent } from '@web-engine/core';
// Create parent entity
const parentEid = addEntity(world);
addComponent(world, Transform, parentEid);
Transform.position.x[parentEid] = 10.0;
// Create child entity
const childEid = addEntity(world);
addComponent(world, Transform, childEid);
addComponent(world, LocalTransform, childEid);
addComponent(world, Parent, childEid);
// Set parent reference
Parent.eid[childEid] = parentEid;
// Set local transform (relative to parent)
LocalTransform.position.x[childEid] = 5.0;
// Child's world position will be 15.0 (parent 10.0 + local 5.0)

Hierarchy Components#

  • Transform — World-space transform (final calculated position)
  • LocalTransform — Local-space transform (relative to parent)
  • Parent — Stores parent entity ID
  • SiblingIndex — Order among siblings (for editor)

Transform Propagation

Web Engine uses WASM-accelerated transform propagation to update world transforms from local transforms efficiently. The WasmTransformSystem processes entire hierarchies in parallel.

Entity Metadata#

Not all entity data fits in TypedArrays. For strings, objects, and complex data, Web Engine uses the EntityMetadataStore — a unified Map that stores non-primitive data.

import {
setEntityDisplayName,
getEntityDisplayName,
setMetadata,
getMetadata,
} from '@web-engine/core';
// Set display name
setEntityDisplayName(eid, 'Player 1');
// Get display name
const name = getEntityDisplayName(eid); // "Player 1"
// Store custom metadata
setMetadata(eid, 'playerInfo', {
name: 'Alice',
avatarId: 'avatar_001',
});
// Retrieve custom metadata
const info = getMetadata(eid, 'playerInfo');
console.log(info.name); // "Alice"

Common Metadata Types#

  • name — Display name for editor and debugging
  • billboard — Billboard widget metadata
  • interactable — Interaction prompt and action
  • playerInfo — Player display info
  • inventory — Inventory items array
  • animationRuntime — Animation graph state
  • logicRuntime — Logic graph execution state

Memory Efficiency

EntityMetadataStore only allocates memory for entities that actually have metadata. Empty metadata objects are automatically cleaned up to prevent memory leaks.

Prefabs and Templates#

Web Engine supports entity prefabs — reusable templates that can be instantiated multiple times with different configurations.

import { spawnPrefab, PrefabRegistry } from '@web-engine/core';
// Define a prefab template
PrefabRegistry.register({
name: 'Crate',
components: [
{ type: 'Transform', data: { scale: [1, 1, 1] } },
{ type: 'RigidBody', data: { mass: 10.0, friction: 0.5 } },
{ type: 'Collider', data: { type: 0, size: [0.5, 0.5, 0.5] } },
{ type: 'Mesh', data: { geometryType: 'box' } },
],
});
// Spawn instances of the prefab
const crate1 = spawnPrefab(world, 'Crate', { position: [0, 1, 0] });
const crate2 = spawnPrefab(world, 'Crate', { position: [5, 1, 0] });
const crate3 = spawnPrefab(world, 'Crate', { position: [10, 1, 0] });

Best Practices#

  • Always add Transform — Every game object should have Transform as a base
  • Use display names — Set meaningful names for debugging and editor display
  • Clean up metadata — Always call clearEntityMetadata when destroying entities
  • Validate references — Check if an entity still has required components before use
  • Use prefabs — Define common entity types as prefabs for consistency
  • Batch creation — Create multiple entities in one frame for better performance
Entities | Web Engine Docs | Web Engine Docs