Table of Contents

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.