Note that you will need a C++ compiler with partial C++14 support.
What is an event?
To simply put it, an event is when something happens, usually of importance.
When programming events are used to do certain tasks when something happens, e.g:
When a player dies, show the death screen.
How do they work?
Somewhere in some part of the logic code you take the Event object and you raise an event, this will simply call all the event handlers (functions) from the current thread. The Event object will store a bunch of function objects (Actually function pointers), when the event is raised it will loop through all of its internal function objects and invoke(call) each one of them.
Event implementation
Function object!
A simple class that wraps the std::function class, we make sure to use std::function
because writing type erasure and other very complicated things are unnecessary.
Code:
template<typename Fty> // Fty is the signature of the function, like so: return(arguments...)
class function
{
private:
using callable = std::function<Fty>; // Type alias for std::function
callable callable_; // Internal callable object
public:
template<typename _Callable>
function( _Callable &&callable ) // Take callable as a universal reference
: callable_( std::forward<_Callable>( callable ) ) // Use std::forward to forward the callable
{}
// No initialization
function( )
: callable_( )
{}
// Invoke
template<typename..._Ax>
void invoke( _Ax&&...args ) // Take arguments as universal references
{
if ( callable_ )
callable_( std::forward<_Ax>( args )... ); // Forward away &&
}
// Simple getter
callable& target( ) { return callable_; }
};
That wasn't so hard, it might be hard to understand because of some slightly new C++ features including
variadic templates, its simple really. If you dont know what universal references are check out one of
Scott Meyers talks about, he is great at explaining them, certainly better than me.
The event handler!
The important thing to note about the eventhandler class is thats its sole purpose
is to be used by the Event class (see further) for storing a callback, it can be stored
with a name or without a name, the name is specified so that it can also be removed.
It has 2 constructors so that you can construct the handler only with a function, I'll show what I mean later.
Code:
template<typename Fty> // Function signature
class EventHandler
{
private:
function<Fty> f_;
String name_; // Possible name, defaults to unknown
public:
template<typename _Callable>
EventHandler( _Callable&&callable )
: name_( "unknown" ), f_( std::forward<_Callable>( callable ) )
{}
template<typename _Callable>
EventHandler( String name, _Callable&&callable )
: name_( std::move( name ) ), f_( std::forward<_Callable>( callable ) )
{}
template<typename..._Ax>
void invoke( _Ax&&...args )
{
f_.invoke( std::forward<_Ax>( args )... );
}
const auto& name( ) const { return name_; }
auto& name( ) { return name_; }
const auto& fty( ) const { return f_; }
auto &fty( ) { return f_; }
};
Event class!
Code:
template<typename Fty>
class Event
{
private:
std::vector<EventHandler<Fty>> handlers_; // The event handlers
public:
Event( )
: handlers_( )
{ }
auto &getHandlers( ) { return handlers_; }
void clear( ) { handlers_.clear( ); }
template<typename..._Ax>
void Invoke( _Ax&&...args ) // Same as EventHandler::invoke
{
for ( auto &x : handlers_ ) // Call all stored event handlers
x.invoke( std::forward<_Ax>( args )... );
}
void remove_handler( const String &name )
{
for ( auto it = handlers_.begin( ), end = handlers_.end( );
it < end;
++it )
{
if ( it->name( ) == name ) // If found
{
handlers_.erase( it ); // Erase it
break; // Break out of loop, otherwise we'll have undefined behavious
}
}
}
// Here's the magic, the sole purpose of the entire EventHandler class
// Because EventHandlers constructor can take one parameter or two
// The += operator can be used with a single function, but also with a name.
// See example below.
Event& operator+=( EventHandler<Fty> lhs )
{
handlers_.push_back( std::move( lhs ) );
return *this;
}
// Remove handler
Event& operator-=( const String &name )
{
remove_handler( name );
return *this;
}
};
Example of how the Constructor of the EventHandler class is useful:
Code:
Event<void()> event;
// Now, if we removed the first constructor of EventHandler, the below line would cause an error:
event += []() { std::cout << "Hello" << std::endl };
// When using the += operator like so above, we are constructing a temporary EventHandler object
// that is later moved into the events handler vector.
// If we however removed the first constructor, we would always have to write the handlers
// explicitly, like so:
event += EventHandler( "name", []( ) { std::cout << "Hello" << std::endl; } );
// That is good when you need a name, but if you dont need a name its simply
// more typing than needed.
How can I use them?
Simply when creating cheats, you can have a backend, lets call the backend engine.
The backend will handle everything related to reading the games memory and keeping track of everything that changes, when the backend sees that something has changed it will raise an event that relates to that, e.g if the health changes, the turn ends or simply our player jumps.
Lets say we are inside some function, after engine has been initialized, we create some Eventhandlers:
Code:
engine.OnPlayerLand( ) += []( ) // Maybe OnPlayerLand takes a Player* to represent the player as an argument.
{
// Ok player landed, lets say we were trying to bhop
// In here we'd just check if bhop is enabled, if it
// Then we jump
};
engine.OnPlayerAir( ) += []( )
{
// Ok player jumped or somehow got air,
// so if we are trying to bhop we'd check if bhop is enabled
// and then we'd just do -jump or similiar.
};
// Lets say that, we have a class, in our constructor we create event handlers:
Constructor::Constructor()
{
// Lets say we have access to a engine variable
// Notice that the this pointer is captured
engine.OnPlayerDeath( ) += [&this](){ std::cout << this->name( ) << " : " << "OnPlayerDeath"; }
// Now, the problem that will come to play is, when the instance of this object
// is destroyed, OnPlayerDeath will still have access to this handler, and the captured
// this pointer is a dangling pointer, this is bad.
// This is why you should name the event handlers, that way when this object is destroyed
// you can remove the handler in the destructor.
}
This is simple, and if you are working in a group it gives your program a flow that can easily be understood and followed.
If you wanna see how Events are useful for GUI's and such, I use them in my framework:
dxLib/UI/