Skip to content
This repository has been archived by the owner on Dec 13, 2022. It is now read-only.

Entities

Eric McDaniel edited this page Oct 26, 2021 · 12 revisions

Entities in Harmony are fully defined by their component makeup. They are integers that act as "pointers" to a specific row in an archetype table.

The component makeup of an entity is called its type. A type is an array of schema ids (integers) that identifies the component signature of an entity. For example, if your program had Health (1), Transform (4), and Velocity (9) schemas, an entity with that exact set of components would have a type of [1,4,9].

Creating and Destroying Entities

An entity is created using the Entity.make function, which requires a world and a type:

import { Entity } from "harmony-ecs"

const entity = Entity.make(world, [Health])

The variable entity in the above example holds an integer value that references a row of components in an archetype table. Component data will automatically be created for the entity using the provided type.

Initial values for component data can be provided using a third argument to Entity.make:

Entity.make(world, [Health], 100)
Entity.make(world, [Position, Velocity], [{ x: 0, y: 0 }, { x: 0, y: 0 }])

To destroy an entity, call Entity.destroy with an entity pointer:

Entity.destroy(world, entity)

Conceptually, using Entity.destroy to destroy an entity is identical to removing of that entity's components. The entity will still technically exist in the world, albeit with no component data.

Adding and Removing Components

One or more components can be added to an entity using Entity.set:

Entity.set(world, entity, [Faction], [15])

Components can be removed from an entity using Entity.unset:

Entity.unset(world, entity, [Faction])

Checking Components

You can do a quick check to see if an entity has one or more components with Entity.has:

Entity.has(world, entity, [Faction, Treasury])

Common Pitfalls

Changing the components of an entity is conceptually similar to splicing an element out of an array. This can lead to familiar pitfalls while iterating.

Take the following example, where the author's intent was to splice every element from an array of numbers:

const arr = [1, 2, 3]
for (let i = 0; i < arr.length; i++) {
  arr.splice(i, 1)
}

Index 1 is skipped after the first iteration, leaving the final state of the array as [2]. A simple way to resolve this bug is to move backwards through the array (of course .pop() would be more efficient here):

const arr = [1, 2, 3]
for (let i = arr.length - 1; i >= 0; i--) {
  arr.splice(i, 1)
}

This strategy also works when modifying an entity's components inside of a query loop:

for (let i = 0; i < query.length; i++) {
  const [e] = query[i]
  for (let j = e.length - 1; j >= 0; j--) {
    if (logic) Entity.unset(world, e[j], [Faction])
  }
}

Cache

Most ECS have some way to defer an update until the next loop to avoid problems when adding and removing components. Harmony is no exception, and handles this with a cache.

A cache is created using the Cache.make function:

const cache = Cache.make()

Deferred operations (like adding/removing components) are stored inside of a cache to be applied to a world at a later time.

Cache.set and Cache.unset will store an add and remove operation in the entity manager, respectively:

Cache.set(cache, entity, [Faction])
Cache.unset(cache, entity, [Faction])

Accumulated operations are then applied to a world using Cache.apply:

Cache.apply(world, manager)

You'll generally want to call this function at the beginning (or end) of each game tick.

Applied operations are not automatically cleared from the cache. A cache can be cleared using Cache.clear:

Cache.clear(cache)
Clone this wiki locally