Events

Events allow objects to expose events that may trigger during execution. External objects interested in those events can then register callbacks with those events and be notified when they happen. They are useful because they allow two objects to communicate without necessarily knowing about each other's types, which can reduce class coupling and improve design.

They're represented with the Event class.

Creating events

When creating an event, all you need to do is specify a format of the callback it sends out, for example:

class MyPlayerController
{
public:
Event<void()> onPlayerJumped; // Event that triggers a callback with no parameters
Event<void(UINT32)> onPlayerCollectedCoins; // Event that triggers a callback with an UINT32 parameter
};

The format of the callback method signature is the same format as followed by C++ std::function: returnType(param1Type, param2Type, ...).

Triggering events

When an object is ready to trigger an event simply call it like a function:

class MyPlayerController
{
public:
// Assume this is a function called every frame
void update()
{
bool spacePressed = /*... check input system for button press ...*/;
UINT32 grabbedCoins = /*... use physics system to see if player is colliding with any coin objects ...*/;
if(spacePressed)
onPlayerJumped(); // Trigger event
if(grabbedCoins > 0)
onPlayerCollectedCoins(grabbedCoins); // Trigger event
}
Event<void()> onPlayerJumped;
Event<void(UINT32)> onPlayerCollectedCoins;
};

Subscribing to events

An external object can register itself with an event by calling Event::connect.

// Define a couple of methods that trigger when events are triggered
auto playerJumpedCallback = [&]()
{
gDebug().logDebug("Player jumped!");
};
auto playerCollectedCoinsCallback = [&](UINT32 numCoins)
{
gDebug().logDebug("Player collected: " + toString(numCoins) + " coins!");
};
MyPlayerController playerController;
// Subscribe to events
playerController.onPlayerJumped.connect(&playerJumpedCallback);
playerController.onPlayerCollectedCoins.connect(&playerCollectedCoinsCallback);
// ... run player logic every frame ...

Subscribing to an event will return an HEvent handle. You can use this handle to manually disconnect from the event by calling HEvent::disconnect. For example:

HEvent eventHandle = playerController.onPlayerJumped.connect(&playerJumpedCallback);
// ... do something and decide event is no longer required ...
eventHandle.disconnect();

Class methods as event callbacks

In the example above we used lambda functions as event callbacks, and it would have also worked for global functions, but if you wish to use class methods as event callbacks, some additional logic is required.

The main difference between lambda/global functions and class methods is that class methods have an implicit parameter, the this pointer. This is not something you see when you define a function, but it is always there "under the hood".

For example:

class MyEventSubscriber
{
void eventCallback(); // What you see
// void eventCallback(MyEventSubscriber* thisPtr); // What the compiler sees for the above method
};

When setting up event callbacks we must therefore provide the this pointer manually. However since the event owner has no idea which class will subscribe to its event, it cannot provide the this pointer to the object of that class. By using std::bind the event subscriber can instead permanently bind its this pointer when subscribing to an event.

If you are unfamiliar with std::bind, it basically transforms a function signature by permanently binding one of its parameters. Here's a simple example:

auto myFunc = [&](UINT32 param)
{ };
// Permanently binds 5 as the function parameter
auto myFuncNoParams = std::bind(myFunc, 5);
// Call the function with parameter 5
myFuncNoParams();

Using this same logic, we can permanently bind the this pointer when subscribing to an event:

class MyEventSubscriber
{
void playerJumpedCallback() // Has a hidden "this" pointer parameter
{
gDebug().logDebug("Player jumped!");
}
// Assuming the same player controller class we defined earlier
void subscribeToEvents(MyPlayerController& playerController)
{
// Bind the "this" pointer
auto callback = std::bind(&MyEventSubscriber::playerJumpedCallback, this);
// Register the callback without the event needing to know about "this" pointer parameter
playerController.onPlayerJumped.connect(callback);
}
};