Entities

An entity identifies a discrete game unit. You can think of an entity as a pointer to a collection of components that can grow and shrink during gameplay.

An entity can represent anything from a player or enemy, to a spawn point, or even a remotely connected client.

Javelin supports up to around one-million (2^20) active entities, and around four-billion (2^32) total entities over the lifetime of the game. Entities are technically unsigned integers, but they should be treated as opaque values to keep your code robust to API changes.

Entity Creation

Entities are created using world.create.

world.create()

Entities can be created with a single component.

let Position = j.value<Vector2>()

world.create(Position, {x: 0, y: 0})

But most often you will need to create entities from a set of components. This is accomplished using types:

let Position = j.value<Vector2>()
let Velocity = j.value<Vector2>()
let Kinetic = j.type(Position, Velocity)

world.create(Kinetic, {x: 0, y: 0}, {x: 1, y: -1})

Component values cannot be provided to tag components during entity creation, since tags are stateless.

let Burning = j.tag()

world.create(j.type(Burning, Position), {x: 0, y: 0})

Components defined with a schema are auto-initialized if a value is not provided.

let Position = j.value({x: "f32", y: "f32"})

world.create(Position) // automatically adds {x: 0, y: 0}

Entity Reconfiguration

Components are added to entities using world.add.

world.add(entity, Velocity, {x: 1, y: -1})
world.add(entity, j.type(Burning, Position))

Components are removed from entities using world.remove.

world.remove(entity, Kinetic)

Entity Deletion

Entities are deleted using world.delete.

world.delete(entity)

Entity Transaction

Entity operations are deferred until the end of each step. Take the following example where a systemB downstream of systemA fails to locate a newly created entity within a single step.

app
  // systemA
  .addSystem(world => {
    world.create(Hippo)
  })
  // systemB
  .addSystem(world => {
    world.of(Hippo).each(hippo => {
      // (not called, even though a hippo was created)
    })
  })
  .step()

All changes made to entities are accumulated into a transction that is applied after the last system executes. This allows Javelin to performanly move changed entities within it’s internal data structures at most one time per step. This behavior also reduces the potential for bugs where systems that occur early in the pipeline can “miss” entities that are created and deleted within the same step.