Event System

Arche features an event system that can be used to get notifications about world modifications, namely:

  • Entity creation and removal
  • Component addition, removal and exchange
  • Changes of entity relation targets

The event system is particularly useful for automating the management of supplementary data structures that store entities. It can be used to automatically insert entities into these structures on creation or component addition, and to remove them on entity or component removal.

The types of interest here are ecs.Listener and ecs.EntityEvent .

Subscriptions

A listener must subscribe to certain event types. These are constants of type ecs.event.Subscription :

1_ = event.EntityCreated
2_ = event.EntityRemoved
3_ = event.ComponentAdded
4_ = event.ComponentRemoved
5_ = event.RelationChanged
6_ = event.TargetChanged

Multiple event types can be combined using bit-wise OR (|):

1subs := event.EntityCreated | event.EntityRemoved
2_ = subs

Some combinations of event types are already defined as ecs.event.Subscription . E.g., to subscribe to all event types, use event.All.

Besides subscribing to event types, subscriptions can be restricted to certain component types that must be affected by the event. Component type subscriptions are realized using the same ecs.Mask mechanics that filters use. Create masks with ecs.All :

1world := ecs.NewWorld()
2posID := ecs.ComponentID[Position](&world)
3headID := ecs.ComponentID[Heading](&world)
4
5compSubs := ecs.All(posID, headID)
6_ = compSubs

A listener with this component subscription would be notified on changes that are related to the Position or to the Heading component.

Builtin listener

An ecs.World can have at most one ecs.Listener . If required, this listener can be used to dispatch events to sub-listeners. Such a listener is provided by listener.Dispatch . In conjunction with listener.Callback , it is already possible to build a sophisticated event system.

In the following example, we compose a listener.Dispatch from two listener.Callback . The first one listens to all entity creation and entity removal events. The second one listens to events where a Position or a Heading is added to an entity.

 1world := ecs.NewWorld()
 2posID := ecs.ComponentID[Position](&world)
 3headID := ecs.ComponentID[Heading](&world)
 4
 5// Listener for all entity creation and entity removal events.
 6entityListener := listener.NewCallback(
 7	// A function we want to call on notification.
 8	func(w *ecs.World, ee ecs.EntityEvent) { /* Do something here */ },
 9	// Subscription to event types.
10	event.EntityCreated|event.EntityRemoved,
11)
12
13posOrHeadAddedListener := listener.NewCallback(
14	// A function we want to call on notification.
15	func(w *ecs.World, ee ecs.EntityEvent) { /* Do something here */ },
16	// Subscription to event types.
17	event.ComponentAdded,
18	// Subscription is restricted to these component types.
19	posID, headID,
20)
21
22// Create the dispatch listener from both sub-listeners.
23dispatch := listener.NewDispatch(
24	&entityListener,
25	&posOrHeadAddedListener,
26)
27// Set it as the world's listener.
28world.SetListener(&dispatch)

Custom listeners

Custom listeners can be created by implementing the interface ecs.Listener . Here is an example of a listener that listens to additions of a Position component:

 1package main
 2
 3import (
 4	"fmt"
 5
 6	"github.com/mlange-42/arche/ecs"
 7	"github.com/mlange-42/arche/ecs/event"
 8)
 9
10// PositionAddedListener listens to additions of a Position component.
11type PositionAddedListener struct {
12	subs  event.Subscription
13	comps ecs.Mask
14}
15
16// NewPositionAddedListener creates a new PositionAddedListener.
17func NewPositionAddedListener(world *ecs.World) PositionAddedListener {
18	posID := ecs.ComponentID[Position](world)
19	return PositionAddedListener{
20		subs:  event.ComponentAdded,
21		comps: ecs.All(posID),
22	}
23}
24
25// Notify the listener about a subscribed event.
26func (l *PositionAddedListener) Notify(world *ecs.World, evt ecs.EntityEvent) {
27	fmt.Println("Position component added to entity ", evt.Entity)
28}
29
30// Subscriptions to one or more event types.
31func (l *PositionAddedListener) Subscriptions() event.Subscription {
32	return l.subs
33}
34
35// Components the listener subscribes to. Listening to all components indicated by nil.
36func (l *PositionAddedListener) Components() *ecs.Mask {
37	return &l.comps
38}

EntityEvent

In Listener.Notify, as well as in the callback for listener.Callback , we get an ecs.EntityEvent as argument. It provides all sorts of information about the event, like the affected ecs.Entity , event types covered, components added and removed, and more. See the API docs of ecs.EntityEvent for details.