Event system
Note
This feature is not yet released and is planned for Ark v0.6.0.
You can try it out on the main
branch.
Ark provides an an event system with observers that allow an application to react on events, such as adding and removing components and entities.
Observers can filter for the events they are interested in, in several ways. A callback function with is executed for the affected entity whenever an observer’s filter matches.
Example
// Create an observer.
ecs.Observe1[Position](ecs.OnCreateEntity).
Do(func(e ecs.Entity, pos *Position) {
fmt.Printf("%#v\n", pos)
}).
Register(&world)
// Create an entity that triggers the observer's callback.
builder := ecs.NewMap1[Position](&world)
builder.NewEntity(&Position{X: 10, Y: 11})
Event types
Observers are specific for different event types, and each observer can react only to one type. See below for how to react on multiple different types.
- OnCreateEntity — Fires after a new entity is created.
- OnRemoveEntity — Fires before an entity is removed.
- OnAddComponents — Fires after components are added to an existing entity.
- OnRemoveComponents — Fires before components are removed from an entity.
- OnSetComponents — Fires after existing components are set from an entity.
If multiple components are added/removed/set for an entity, one event is emitted for the entire operation.
Combining multiple types
Observers can be combined to react to multiple event types in a single callback function. Below is a combination of observers to react on component addition as well as removal. The callback is set up to be able to distinguish between these event types (if needed).
// Common callback.
fn := func(evt ecs.EventType, entity ecs.Entity, pos *Position) {
if evt == ecs.OnAddComponents {
// do something
}
if evt == ecs.OnRemoveComponents {
// do something
}
}
// Observer for adding components.
ecs.Observe1[Position](ecs.OnAddComponents).
Do(func(e ecs.Entity, pos *Position) { fn(ecs.OnAddComponents, e, pos) }).
Register(&world)
// Observer for removing components.
ecs.Observe1[Position](ecs.OnRemoveComponents).
Do(func(e ecs.Entity, pos *Position) { fn(ecs.OnRemoveComponents, e, pos) }).
Register(&world)
Filters
Observers filter for the components specified by their generic parameters.
Additional components can be specified using ecs.Observer.For
,
but they are not directly accessible in the callback.
If multiple components are specified (in parameters and in For
),
all these components must be affected (added, removed, created, …)
at the same time for the observer to trigger.
Further, events can be filtered by the composition of the affected entity via
ecs.Observer.With
, ecs.Observer.Without
and ecs.Observer.Exclusive
, just like queries.
Examples (leaving out observer registration):
Both observers are triggered when an entity with Position
is created.
The first one has direct access to the component in the callback while the second does not:
ecs.Observe1[Position](ecs.OnCreateEntity).
Do(func(e ecs.Entity, p *Position) { /* ... */ })
ecs.Observe(ecs.OnCreateEntity).
With(ecs.C[Position]()).
Do(func(e ecs.Entity) { /* ... */ })
Both observers are triggered when an entity with Position
as well as Velocity
is created:
ecs.Observe2[Position, Velocity](ecs.OnCreateEntity).
Do(func(e ecs.Entity, p *Position, v *Velocity) { /* ... */ })
ecs.Observe1[Position](ecs.OnCreateEntity).
With(ecs.C[Velocity]()).
Do(func(e ecs.Entity, p *Position) { /* ... */ })
An observer that is triggered when any entity is created, irrespective of its components:
ecs.Observe(ecs.OnCreateEntity).
Do(func(e ecs.Entity) { /* ... */ })
An observer that is triggered when a Position
component is added to an existing entity:
ecs.Observe1[Position](ecs.OnAddComponents).
Do(func(e ecs.Entity, p *Position) { /* ... */ })
An observer that is triggered when a Position
component is added to an entity
that has Velocity
, but not Altitude
(or rather, had before the operation):
ecs.Observe1[Position](ecs.OnAddComponents).
With(ecs.C[Velocity]()).
Without(ecs.C[Altitude]()).
Do(func(e ecs.Entity, p *Position) { /* ... */ })
Event timing
The time an event is fired relative to the operation it is related to depends on the event’s type. The observer callbacks are executed immediately by any fired event.
Events for entity creation and for adding or setting components are fired after the operation. Hence, the new or changed components can be inspected in the observer’s callback. If emitted from individual operations, the world is in an unlocked state when the callback is executed. Contrary, when emitted from a batch operation, the world is locked.
Events for entity or component removal are fired before the operation. This way, the entity or component to be removed can be inspected in the observer’s callback. In this case, the world is locked when the callback is executed.
For batch operations, all events are fired before or after the entire batch, respectively. For batch creation or addition, events are fired after the potential batch callback is executed for all entities, allowing to inspect the result.
Note that observer order is undefined. Observers are not necessarily triggered in the same order as they were registered.