std/ecs/column

std/ecs/src/column.trb

type Column

type Column<Component>

One component type's dense column: the values in insertion order, the entity each row belongs to, and the row each entity occupies.

A column is where a component lives, and nothing in it knows what the component is. Every member is generic over Component, so each one is monomorphized for the type it holds and no value is ever kept under a type that was forgotten. Every write stamps its row from a clock the column keeps, which is all change detection is (changedSince).

Examples

var names: Column<String> = Column()
const first = Entity 0
const second = Entity 1
names.put first, "ada"
names.put second, "grace"
assert(names.get(second) == Some("grace"))
names.remove first
assert(names.length() == 1)
assert(names.ownerAt(0) == second)

Pitfalls

  • Removing swaps the last row into the hole, so a system must not remember a row number across a remove.
  • A column answers for the entity handle it was given and knows nothing about Entities: a despawned entity is gone from a column only because the program took it out.

field owners

protected var owners: List<Entity> = []

The entity each row belongs to.

field values

protected var values: List<Component> = []

The values, dense and in insertion order.

field rows

protected var rows: Map<Entity, Int> = [:]

The row each entity occupies.

field stamps

protected var stamps: List<Int> = []

What the clock stood at when each row was last written.

field clock

protected var clock: Int = 0

Counts the writes.

fn put

var fn put(entity: Entity, value: Component)

Puts value under entity, replacing whatever was there. A new entity gets the row after the last one.

fn remove

var fn remove(entity: Entity)

Takes entity's value out, swapping the last row into the hole. An entity without a value changes nothing.

fn each

var fn each(body: (var value: Component) => Void)

Changes every value in place, in row order. The rows it visits count as written: they are stamped with one tick of the clock, as Bevy marks mutable access.

fn get

fn get(entity: Entity): Component?

The value of entity, or None when it has none.

fn has

fn has(entity: Entity): Bool

Whether entity has a value in this column.

fn length

fn length(): Int

How many rows there are.

fn ownerAt

fn ownerAt(row: Int): Entity

The entity row row belongs to. It panics outside 0..length().

fn valueAt

fn valueAt(row: Int): Component

The value in row row. It panics outside 0..length().

fn version

fn version(): Int

What the clock stood at after the last write of any row.

fn changedSince

fn changedSince(stamp: Int): Iterate<Entity>

The entities whose value was written after stamp, in row order. Change detection, without a second data structure: a system remembers the version it last looked at and asks for what is newer.

Examples

var counts: Column<Int> = Column()
counts.put Entity(0), 1
const seen = counts.version()
counts.put Entity(1), 2
assert(counts.changedSince(seen).toList() == [Entity(1)])