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

Components

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

A component is data associated with an entity, differentiated by its schema, a template. Or, more specifically, that template's integer id.

Schema

A schema is defined using Harmony's Schema.make function:

import { Format, Schema } from "harmony-ecs"

const Health = Schema.make(world, Format.float64)

Schema.make returns an integer, the id of the newly created template.

Harmony exports a formats object that contains multiple numeric types, e.g. uint8, int16, float64, that are used to define the shape of schemas.

There are two types of component schemas: native and binary.

Native Components

The term "native" here is misleading; as we will see, the other kind of component that Harmony can store is powered by a native, although esoteric, language feature.

Native components store plain built-in JavaScript values. This currently includes numbers and objects, and will include arrays, maps, and sets in the near future.

We have already defined a number component in the Health component example above. Any of Harmony's numeric formats (uint, int, float) do not matter here: the component is simply stored in an array of built-in, IEEE 754 numbers.

Native object components are created by providing an object to Schema.make, where each key is a fixed property name, and its value is either a format or a nested object that adheres to the same rules:

const Attributes = Schema.make(world, {
  strength: Format.uint8,
  dexterity: Format.uint8,
  // ...
  modifiers: {
    strength: Format.int8,
    // ...
  },
})

A component can be added to, or removed from, an entity using Entity.set and Entity.unset, respectively. Components are queried and mutated using queries.

Binary Components

Binary components are specialized components stored in a struct-of-arrays architecture. An instance of a native Attributes component would look something like the following:

{
  strength: 18,
  dexterity: 14,
}

This object is stored in an array, where the value of each index of the array corresponds to the component of a unique entity:

[{ strength }, { strength }, { strength }]

A struct-of-arrays architecture looks like the following, where the values of each component property exist in their own array:

{
  strength: [18, 8, 8],
  dexterity: [14, 18, 8],
  // ...
}

Like with the array of objects, the index of each array of numbers corresponds to a unique entity.

In Harmony, each array that constitutes a binary component is a TypedArray – an array-like view, or proxy, to a binary data buffer called an ArrayBuffer. ArrayBuffers are very fast to read and write. They can be iterated, mutated, and serialized performantly relative to objects. However, they have many rules and drawbacks (that will not be discussed here) when compared to built-in arrays.

A binary component is defined using the Schema.makeBinary function. This function follows similar rules to native schema, although it does not support nested objects (yet):

const Attributes = Schema.makeBinary(world, {
  strength: Format.float64,
})

A single-array storage can also be defined binary component, almost identical to the original Health example above:

const Health = Schema.makeBinary(world, Format.float64)

Querying Binary Components

Binary components are queried the same way as native components. However, due to the difference in storage, component values per-entity are read and modified a bit differently.

Reading and updating the value of a binary component for an entity inverts the accessor. Instead of reading the property intuitively (as with with native components):

attrs[i].strength++

One would need to access the property of interest first, and then access the index of the target entity:

attrs.strength[i]++
Clone this wiki locally