Events
The event aggregator lets one part of the game announce something without knowing who listens. A publisher sends a message; every subscriber to that message type is called. The engine provides it as IEventAggregator, registered in the service container.
A message is any class or record that implements IEvent. The interface has no members, so it only marks the type:
using BSGameLib.Game.Events;
public sealed record ScoreChanged(int Score) : IEvent;
Publishing and subscribing
private IEventAggregator _events;
private IEventSubscriptionToken _scoreToken;
protected override void Initialize()
{
_events = GetService<IEventAggregator>();
_scoreToken = _events.Subscribe<ScoreChanged>(message =>
Console.WriteLine($"Score is now {message.Score}"));
base.Initialize();
}
// Anywhere in the game:
private void AnnounceScore()
{
_events.Publish(new ScoreChanged(10));
}
Subscribe<T> returns an IEventSubscriptionToken, which is the handle for
removing the subscription. Publish<T> calls every subscriber to T, in the order they subscribed, and
returns when they have all run.
To remove a subscription, pass its token to Unsubscribe. Clear removes every subscription, and
Subscriptions lists the current tokens.
_events.Unsubscribe(_scoreToken);
Disposing the token unsubscribes it too, so _scoreToken.Dispose(); does the same. Doing either more
than once is harmless.
Messages from the engine
The engine publishes some messages of its own. MouseClickedEvent is one: it
is published on each left-button click, with Position in the game's logical coordinates, the same ones
the mouse manager reports. See Input.
Lifetime
The aggregator holds each subscriber's handler until it is unsubscribed. A subscriber therefore stays alive, and keeps receiving messages, until someone removes it. Nothing removes it when the object that subscribed is no longer used.
When an object that subscribes goes away, unsubscribe it. Keep its token, and pass the token to
Unsubscribe or dispose it, as in the example above. A view that closes, or a component that is removed, should do
this in the same place it releases its other resources.
Threads and failures
Publish runs the subscribers on the thread that called it, one after another, and holds the
aggregator's lock while they run. Two rules follow:
- A subscriber must not wait for another thread that publishes a message. That thread would be waiting for the lock the subscriber holds, and the two would wait on each other.
- A subscriber may subscribe or unsubscribe from inside a handler. The change applies from the next message. A subscriber removed during a publish may still receive the message that is being sent.
A subscriber that throws does not stop the others. The exception is logged, and Publish returns
normally.