event Bus
Socially Distant has a bunch of moving parts that make up its game play. The game is split into three main parts - the UI, the game hypervisor, and the network simulation. These three layers need to stay decoupled from each other, but they do need to communicate with each other. In some cases, they need to pass messages along to each other across threads. The Event Bus facilitates all of that.
Posting an Event
When you want to tell the rest of the game that your code did something, you do so by posting a new Event to the Event Bus.
Consider this example.
You want to fire an event every time a command is executed on a computer.
First, create a new CommandRunEvent
class inheriting DeviceEvent
.
public sealed class CommandRunEvent : DeviceEvent
{
public string Name { get;
public string[] Arguments { get; }
public CommandRunEvent(IComputer computer, string name, string[] args) : base(computer)
{
Name = name;
Arguments = args;
}
}
Then, simply create an instance of the CommandRunEvent
and then:
EventBus.Post(myCommandRunEvent);
It's that simple!
Receiving Events
Most of the time, you'll be listening to events on the bus.
To listen for an event, call EventBus.Listen<T>(callback)
.
IDisposable EventBus.Listen<T>(Action<T> callback)
(where T
is any Event
)
This method takes a callback, and a type of Socially Distant event. It creates an event listener that calls the callback any time an event of matching type is posted. The event listener is returned as an IDisposable
. When you want to stop receiving events, call Dispose()
on the event listener.
Here's an example of how to receive an event every time a device is pinged on the Internet.
private void OnPing(PingEvent pingEvent)
{
Log.Information("Pong!");
}
using var listener = EventBus.Listen<PingEvent>(OnPing);
// ...
listener.Dispose();
What are Events?
An Event is just a plain C# object containing data about something that just happened in the game. Any time something happens in-game, an Event is posted to the Event Bus containing information about what just happened.
For example, any time the files on an in-game computer are accessed, the game posts a FileSystemEvent
object to the Event Bus. In this case, information about what computer was accessed and what happened on disk is sent with the event. Other parts of the game, like the mission system, can then listen for FileSystemEvent
events on the Event Bus, and do what they will with the information. This can be anything, like completing an objective when a specific file is deleted.
Event Hierarchy
Any object inheriting from Event
can be posted to the event bus. You can also inherit subclasses of Event
. This forms the Event Hierarchy.
Consider the following example situation:
The Mission System fires three kinds of events.
MissionFailEvent
: when a mission is failed.MissionCompletedEvent
: when a mission is completed.MissionAbandonedEvent
: When a mission is abandoned.
These three events may have unique information about what specifically happened. A fail event may carry info about why the mission failed. However, all mission events carry a mission with them.
Another part of the game, like DocumentAdapter
, may want to receive all mission events so it can redraw any mission info boxes in an email. Without Event Hierarchy, DocumentAdapter
would need to listen for mission fails, completions, and abandons. This works fine until we add a MissionStartEvent
!
EventBus.Listen<MissionFailEvent>(OnMissionFail);
EventBus.Listen<MissionCompleteEvent>(OnMissionComplete);
EventBus.Listen<MissionAbandonEvent>(OnMissionAbandon);
Since we know all types of mission event will have a mission associated with them, we can define a common MissionEvent
class they all inherit from.
public abstract class MissionEvent : Event
{
public IMission Mission { get; }
protected MissionEvent(IMission mission)
{
Mission = mission;
}
}
Then, DocumentAdapter
can simplify to just listening for mission events.
EventBus.Listen<MissionEvent>(OnMissionEvent);
You can use C#'s pattern matching features to see more specific information about an Event
instance, including checking what kind of event it is.
Thread-safety and performance notes
Posting an event with EventBus.Post()
is thread-safe. Events are posted to a ConcurrentQueue
to be dispatched on the next available frame.
All events on the Event Bus are dispatched on the main UI thread. Do not do slow work in an event listener.
The game only dispatches 129 Event
instances per frame, due to how clogged the event bus can get when the player's moving fast.