Overview of Event Messaging Systems

Event messaging systems are essential to a game engine because they allow systems to communicate with each other. Open 3D Engine (O3DE) uses a few types of communication methods, which are separated into the following modules:

  • Event Bus (EBus): A single global bus that all modules can use to invoke requests and dispatch messages, respectively known as request and notification buses.
  • AZ::Interface: A C++ template class for a global interface that other components can invoke requests from, equivalent to the EBus sytem’s request bus.
  • AZ::Event: A C++ template class that can publish single-value messages, which other components can subscribe to, equivalent to the EBus system’s notification bus.

The functionality of these three modules overlaps in some ways: AZ::Interface provides a request interface, AZ::Event provides a publish-subscribe interface, and EBus provides both. However, all three modules have strengths and weaknesses that make them more suitable for different scenarios.

The following table compares some key characteristics of EBus, AZ::Interface, and AZ::Event. You can find all three event messaging modules throughout O3DE’s code base.

Communication modelEBus is a general purpose event messaging system whose design focuses on abstraction and decoupling systems. It allows many source components to provide requests or publish messages. It also allows many listener components to invoke those requests or subscribe to those messages—all without the components needing to know about each other. Unlike AZ::Event, a listener can subscribe to an EBus specialization even if the EBus doesn’t exist yet.An AZ::Interface<T> template specialization defines a global singleton-like request interface that other systems can invoke. To access the request interface, other systems can call AZ::Interface<T>.Get().AZ::Event events are defined as members of a component. As such, other components must have a reference to the component to subscribe to the event. To listen and process an event, subscriber components must implement a handler. A single handler can connect to only one event, but an event can have multiple handlers.

Some patterns for cross-entity communication include:
  • Exposing the AZ::Event to an AZ::Interface.
  • Calling SceneQuery() and AZ::IVisibility to get a reference to the entity.
LifetimeEBus is a global singleton that initializes when the application launches and gets destroyed when the application terminates.There can be only a single instance of an AZ::Interface<T> specialization at a time. An instance initializes when the application launches and gets destroyed when the application terminates.There can be many instances of an AZ::Event<T> specialization. Each instance must be attached to a component. An instance initializes when the component is created and gets destroyed when the component is destroyed.
ScriptingEBus supports script binding for both its notification bus (like AZ::Event) and request bus (unlike AZ::Interface).AZ::Interface doesn’t support script binding. You can still use AZ::Interface, but you must also use an EBus to expose it to scripting modules.AZ::Event supports script binding.
AdvantagesEBus is O3DE’s most flexible and powerful event messaging system. Any system can connect to it and communicate with any other system by simply providing a system’s EBus name. EBus is generally used for situations in which event flow is more important than the source of the event.Compared to EBus, AZ::Interface has improved runtime performance, improved debuggability, and compatibility with code autocompletion.Compared to EBus, AZ::Event has improved runtime performance, allows simpler syntax to implement, uses fewer files, and removes aggregate interfaces where a handler cares only about a subset of events.
ExamplesEBus examples are similar to AZ::Interface and AZ::Event. In most cases, using those two is preferable due to their optimizations. However, you must use EBus to add script binding support for request buses.A spawner-spawnee system. The spawner interface contains functions to manage spawnees. If you want another class to spawn something, that class can make a request via AZ::Interface<ISpawner>. By calling AZ::Interface<ISpawner>.Get(), other components can invoke requests from the spawner.In the networking layer, when a remote procedure call (RPC) is sent, it signals an AZ::Event<NetworkEntityRpcMessage&>. Then, a connected handler listens for that signal change and processes the NetworkEntityRpcMessage.