Entity

Entities are unique numerical identifiers (eids) that group components together.

Overview

In bitECS, entities are just numbers. They have no inherent data or behavior - they're simply unique IDs that you use to associate components. This makes entities extremely lightweight.

const eid = addEntity(world)
console.log(eid)        // 1
console.log(typeof eid) // 'number'
i
Entity IDs Start at 1
Entity IDs begin at 1, not 0. This reserves 0 as a "null" entity for ZAII (Zero As Initial Initialization) - uninitialized entity references naturally mean "no entity".

Lightweight

Entities are just integers. No object overhead.

No Data

All data lives in components, not entities.

Recyclable

IDs are reused after removal for efficiency.

Creating Entities

Create entities with addEntity(). Each call returns a unique ID:

create.ts
import { createWorld, addEntity } from 'bitecs'

const world = createWorld()

// Create entities one at a time
const player = addEntity(world)
const enemy = addEntity(world)

console.log(player, enemy)  // 1, 2

Removing Entities

Remove entities with removeEntity(). This also removes all attached components automatically:

remove.ts
import { removeEntity, addComponent } from 'bitecs'

const player = addEntity(world)

// Add some components
addComponent(world, player, Position)
addComponent(world, player, Velocity)
addComponent(world, player, Health)

// Remove entity - all components are cleaned up automatically
removeEntity(world, player)

// Entity no longer exists
entityExists(world, player)  // false
Component Cleanup

When you remove an entity, bitECS automatically removes all its components. You don't need to manually detach components first.

Entity Utilities

Helper functions for working with entities:

FunctionDescription
entityExists(world, eid)Check if an entity exists
getEntityComponents(world, eid)Get all attached components
getAllEntities(world)Get all entity IDs in world
utilities.ts
import { entityExists, getEntityComponents, getAllEntities } from 'bitecs'

const eid = addEntity(world)
addComponent(world, eid, Position)
addComponent(world, eid, Velocity)

// Check if entity exists
if (entityExists(world, eid)) {
  console.log('Entity is valid')
}

// Get all components for an entity
const components = getEntityComponents(world, eid)
console.log(components.length)  // 2

// Get all entities in world
const allEntities = getAllEntities(world)
console.log(allEntities)  // [1]

ID Recycling

Entity IDs are recycled immediately after removal. This is memory-efficient but requires awareness:

const world = createWorld()
const eid1 = addEntity(world)
const eid2 = addEntity(world)
console.log(eid1, eid2)  // 1, 2

removeEntity(world, eid1)
const eid3 = addEntity(world)

console.log(eid3)        // 1 (recycled!)
console.log(eid1 === eid3)  // true
!
Stale References
If you store entity IDs and the entity is removed, that ID may be recycled for a new entity. Always verify with entityExists() before using stored IDs, or use versioning.

Query Removal Behavior

When you call removeEntity() or removeComponent(), the component data is removed immediately. However, the entity's removal from query results is queued until the next query call. This prevents issues when removing entities while iterating over query results.

safe-iteration.ts
// Safe: removals don't affect current iteration
for (const eid of query(world, [Health])) {
  if (Health.value[eid] <= 0) {
    removeEntity(world, eid)  // Queued, won't skip entities
  }
}

// Next query call commits the removals
const alive = query(world, [Health])  // Removed entities now excluded
Nested Queries

If you need to run a query inside another query's loop, use the isNestedmodifier to prevent the inner query from committing removals. See the Query documentation for details onisNested and commit options.

ID Versioning

For safer ID recycling, enable versioning. Each entity ID embeds a version number that increments when the ID is recycled:

versioning.ts
import { createEntityIndex, withVersioning, getId, getVersion } from 'bitecs'

// Create entity index with 8-bit versioning
const entityIndex = createEntityIndex(withVersioning(8))
const world = createWorld(entityIndex)

const eid1 = addEntity(world)
console.log('ID:', getId(entityIndex, eid1))       // 1
console.log('Version:', getVersion(entityIndex, eid1))  // 0

removeEntity(world, eid1)
const eid2 = addEntity(world)  // Reuses base ID 1

console.log('ID:', getId(entityIndex, eid2))       // 1 (same base)
console.log('Version:', getVersion(entityIndex, eid2))  // 1 (incremented)

// The full entity ID is different!
console.log('Same eid?', eid1 === eid2)  // false

Version Bit Trade-offs

More version bits = more recycling safety, but fewer maximum entities:

Version BitsMax EntitiesRecycles Before Wrap
8 (default)16,777,216256
104,194,3041,024
121,048,5764,096
1665,53665,536
!
Large Entity IDs
With versioning, entity IDs can be large numbers (e.g., 257, 513, 769...). If using versioned IDs as TypedArray indices, ensure arrays are sized appropriately or extract the base ID with getId().

Next Steps

Continue Learning