std/ecs
The data layer of the entity-component system - Entity and its allocator, dense Column storage, the Store trait that lets a system name a component without knowing the world, queries of one to four components, the parent hierarchy, Events.
Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite
Die generierte Referenz von std/ecs zeigt jede öffentliche Deklaration mit Signatur, Doc-Kommentar und Beispielen.
std/ecs is the data layer of the entity-component design: it stores components and answers queries over them, and it
has no scheduler and no scene format. An Entity is an index with a generation, Entities hands them out and checks
that one is alive, and a Column<Component> keeps the values of one component type densely, with a sparse index from
entity to row. A world is not a type of this package. It is an ordinary type of the program whose fields are columns
and an Entities, so it is a value: a snapshot is a binding and a rollback is an assignment. A system names the
components it needs with the Store trait and is generic over the group of columns that holds them. The package is not
in the prelude.
Import#
use Column, Entities, Entity, Store, query from "std/ecs"use Column, Entities, Entity, Store, query from "std/ecs"
type Position with Equals, Show {
x: Int
y: Int
}
type Places {
var positions: Column<Position> = Column()
}
extend Places with Store<Position> {
fn column(): Column<Position> {
positions
}
var fn attach(entity: Entity, value: Position) {
positions.put entity, value
}
var fn detach(entity: Entity) {
positions.remove entity
}
}
fn total<Space>(space: Space): Int where Space: Store<Position> {
var sum = 0
for (_, position) in query(space) {
sum = sum + position.x
}
sum
}
var entities = Entities()
var places = Places()
places.attach entities.spawn(), Position(2, 0)
places.attach entities.spawn(), Position(5, 1)
print total(places)Declarations#
Entity and Entities#
type Entity with Equals, Hash, Show {
index: Int
generation: Int = 0
}
type Entities with Equals, Show {
protected var generations: List<Int> = []
protected var living: List<Bool> = []
protected var freed: List<Int> = []
protected var live: Int = 0
var fn spawn(): Entity
var fn despawn(entity: Entity): Bool
fn isAlive(entity: Entity): Bool
fn length(): Int
}An Entity is a value: two equal entities are the same entity, and one whose slot was reused is not equal to the one
that left it. Entities is the allocator and the one place that says whether an entity is alive.
Column#
type Column<Component> {
protected var owners: List<Entity> = []
protected var values: List<Component> = []
protected var rows: Map<Entity, Int> = [:]
protected var stamps: List<Int> = []
protected var clock: Int = 0
var fn put(entity: Entity, value: Component)
var fn remove(entity: Entity)
var fn each(body: (var value: Component) => Void)
fn get(entity: Entity): Component?
fn has(entity: Entity): Bool
fn length(): Int
fn ownerAt(row: Int): Entity
fn valueAt(row: Int): Component
fn version(): Int
fn changedSince(stamp: Int): Iterate<Entity>
}A column keeps its values in insertion order and removes by swapping the last row in, so a removal is constant time and
moves one row. version and changedSince are how a system asks what changed since it last looked.
Store and ComponentKey#
trait Store<Component> {
fn column(): Column<Component>
var fn attach(entity: Entity, value: Component)
var fn detach(entity: Entity)
}
type ComponentKey with Equals, Hash, Show {
name: String
}A group of columns implements Store<Component> once per component it owns. Inside a generic system the component type
is inferred from the bound; on a group that implements several Stores it is written out, as in
query<Motions, Velocity>(motions). detach takes no value, so it is ambiguous on such a group; attach resolves by
the type of its value.
Queries#
fn query<Space, Component>(space: Space): Iterate<(Entity, Component)> where Space: Store<Component>
fn pairs<FirstSpace, SecondSpace, First, Second>(first: FirstSpace, second: SecondSpace): Iterate<(Entity, First, Second)> where FirstSpace: Store<First>, SecondSpace: Store<Second>
fn triples<FirstSpace, SecondSpace, ThirdSpace, First, Second, Third>(first: FirstSpace, second: SecondSpace, third: ThirdSpace): Iterate<(Entity, First, Second, Third)> where FirstSpace: Store<First>, SecondSpace: Store<Second>, ThirdSpace: Store<Third>
fn quadruples<FirstSpace, SecondSpace, ThirdSpace, FourthSpace, First, Second, Third, Fourth>(first: FirstSpace, second: SecondSpace, third: ThirdSpace, fourth: FourthSpace): Iterate<(Entity, First, Second, Third, Fourth)> where FirstSpace: Store<First>, SecondSpace: Store<Second>, ThirdSpace: Store<Third>, FourthSpace: Store<Fourth>A query holds a copy of the columns it was made from and walks the first one in insertion order, so a body that spawns, attaches or detaches changes the group and never the iteration in progress.
Parent and depthOrder#
type Parent with Equals, Show {
entity: Entity
}
fn depthOrder(parents: Column<Parent>, entities: Iterate<Entity>): List<Entity>depthOrder lists the entities so that every parent comes before its children. It panics on a cycle in the parents.
Events and EventCursor#
type Events<Event> {
protected var older: List<Event> = []
protected var newer: List<Event> = []
protected var dropped: Int = 0
var fn send(event: Event)
fn read(var cursor: EventCursor): List<Event>
var fn advance()
fn length(): Int
}
type EventCursor with Equals, Show {
var seen: Int = 0
}An event survives exactly two advance calls. The consumer holds its own EventCursor, so reading changes the cursor
and never the events.
Pitfalls#
- A world is a value, so a copy of it is a second world: a system that takes its group by value changes nothing the
caller sees. A system that attaches or detaches takes it as a
var. - Component inference stops at a group with several
Stores; write the type arguments there. - A query sees the columns as they were when it began. An entity spawned by its body is visited by the next query.
depthOrderpanics on a cycle instead of returning a partial order.
Related#
- ECS.md - the design, the decision against an open type-keyed store and the slices.
- std/linear - the vectors and quaternions a transform component is made of.