# Crash Course: events, signals and everything in between # Table of Contents * [Introduction](#introduction) * [Delegate](#delegate) * [Signals](#signals) * [Event dispatcher](#event-dispatcher) * [Event emitter](#event-emitter) # Introduction Signals are usually a core part of games and software architectures in general.
Roughly speaking, they help to decouple the various parts of a system while allowing them to communicate with each other somehow. The so called _modern C++_ comes with a tool that can be useful in these terms, the `std::function`. As an example, it can be used to create delegates.
However, there is no guarantee that an `std::function` does not perform allocations under the hood and this could be problematic sometimes. Furthermore, it solves a problem but may not adapt well to other requirements that may arise from time to time. In case that the flexibility and potential of an `std::function` are not required or where you are looking for something different, `EnTT` offers a full set of classes to solve completely different problems. # Delegate A delegate can be used as a general purpose invoker with no memory overhead for free functions and members provided along with an instance on which to invoke them.
It does not claim to be a drop-in replacement for an `std::function`, so do not expect to use it whenever an `std::function` fits well. However, it can be used to send opaque delegates around to be used to invoke functions as needed. The interface is trivial. It offers a default constructor to create empty delegates: ```cpp entt::delegate delegate{}; ``` All what is needed to create an instance is to specify the type of the function the delegate will _contain_, that is the signature of the free function or the member function one wants to assign to it. Attempting to use an empty delegate by invoking its function call operator results in undefined behavior or most likely a crash. Before to use a delegate, it must be initialized.
There exists a bunch of overloads of the `connect` member function to do that. As an example of use: ```cpp int f(int i) { return i; } struct my_struct { int f(const int &i) { return i } }; // bind a free function to the delegate delegate.connect<&f>(); // bind a member function to the delegate my_struct instance; delegate.connect<&my_struct::f>(&instance); ``` The delegate class accepts also data members, if needed. In this case, the function type of the delegate is such that the parameter list is empty and the value of the data member is at least convertible to the return type.
Functions having type equivalent to `void(T *, args...)` are accepted as well. In this case, `T *` is considered a payload and the function will receive it back every time it's invoked. In other terms, this works just fine with the above definition: ```cpp void g(const char *c, int i) { /* ... */ } const char c = 'c'; delegate.connect<&g>(&c); delegate(42); ``` The function `g` will be invoked with a pointer to `c` and `42`. However, the function type of the delegate is still `void(int)`, mainly because this is also the signature of its function call operator. To create and initialize a delegate at once, there are also some specialized constructors. Because of the rules of the language, the listener is provided by means of the `entt::connect_arg` variable template: ```cpp entt::delegate func{entt::connect_arg<&f>}; ``` Aside `connect`, a `disconnect` counterpart isn't provided. Instead, there exists a `reset` member function to use to clear a delegate.
To know if a delegate is empty, it can be used explicitly in every conditional statement: ```cpp if(delegate) { // ... } ``` Finally, to invoke a delegate, the function call operator is the way to go as usual: ```cpp auto ret = delegate(42); ``` As shown above, listeners do not have to strictly follow the signature of the delegate. As long as a listener can be invoked with the given arguments to yield a result that is convertible to the given result type, everything works just fine. Probably too much small and pretty poor of functionalities, but the delegate class can help in a lot of cases and it has shown that it is worth keeping it within the library. # Signals Signal handlers work with naked pointers, function pointers and pointers to member functions. Listeners can be any kind of objects and users are in charge of connecting and disconnecting them from a signal to avoid crashes due to different lifetimes. On the other side, performance shouldn't be affected that much by the presence of such a signal handler.
A signal handler can be used as a private data member without exposing any _publish_ functionality to the clients of a class. The basic idea is to impose a clear separation between the signal itself and its _sink_ class, that is a tool to be used to connect and disconnect listeners on the fly. The API of a signal handler is straightforward. The most important thing is that it comes in two forms: with and without a collector. In case a signal is associated with a collector, all the values returned by the listeners can be literally _collected_ and used later by the caller. Otherwise it works just like a plain signal that emits events from time to time.
**Note**: collectors are allowed only in case of function types whose the return type isn't `void` for obvious reasons. To create instances of signal handlers there exist mainly two ways: ```cpp // no collector type entt::sigh signal; // explicit collector type entt::sigh> collector; ``` As expected, they offer all the basic functionalities required to know how many listeners they contain (`size`) or if they contain at least a listener (`empty`) and even to swap two signal handlers (`swap`). Besides them, there are member functions to use both to connect and disconnect listeners in all their forms by means of a sink: ```cpp void foo(int, char) { /* ... */ } struct listener { void bar(const int &, char) { /* ... */ } }; // ... listener instance; signal.sink().connect<&foo>(); signal.sink().connect<&listener::bar>(&instance); // ... // disconnects a free function signal.sink().disconnect<&foo>(); // disconnect a member function of an instance signal.sink().disconnect<&listener::bar>(&instance); // discards all the listeners at once signal.sink().disconnect(); ``` As shown above, listeners do not have to strictly follow the signature of the signal. As long as a listener can be invoked with the given arguments to yield a result that is convertible to the given result type, everything works just fine. Once listeners are attached (or even if there are no listeners at all), events and data in general can be published through a signal by means of the `publish` member function: ```cpp signal.publish(42, 'c'); ``` To collect data, the `collect` member function should be used instead. Below is a minimal example to show how to use it: ```cpp struct my_collector { std::vector vec{}; bool operator()(int v) noexcept { vec.push_back(v); return true; } }; int f() { return 0; } int g() { return 1; } // ... entt::sigh> signal; signal.sink().connect<&f>(); signal.sink().connect<&g>(); my_collector collector = signal.collect(); assert(collector.vec[0] == 0); assert(collector.vec[1] == 1); ``` A collector must expose a function operator that accepts as an argument a type to which the return type of the listeners can be converted. Moreover, it has to return a boolean value that is false to stop collecting data, true otherwise. This way one can avoid calling all the listeners in case it isn't necessary. # Event dispatcher The event dispatcher class is designed so as to be used in a loop. It allows users both to trigger immediate events or to queue events to be published all together once per tick.
This class shares part of its API with the one of the signal handler, but it doesn't require that all the types of events are specified when declared: ```cpp // define a general purpose dispatcher that works with naked pointers entt::dispatcher dispatcher{}; ``` In order to register an instance of a class to a dispatcher, its type must expose one or more member functions the arguments of which are such that `const E &` can be converted to them for each type of event `E`, no matter what the return value is.
The name of the member function aimed to receive the event must be provided to the `connect` member function of the sink in charge for the specific event: ```cpp struct an_event { int value; }; struct another_event {}; struct listener { void receive(const an_event &) { /* ... */ } void method(const another_event &) { /* ... */ } }; // ... listener listener; dispatcher.sink().connect<&listener::receive>(&listener); dispatcher.sink().connect<&listener::method>(&listener); ``` The `disconnect` member function follows the same pattern and can be used to selectively remove listeners: ```cpp dispatcher.sink().disconnect<&listener::receive>(&listener); dispatcher.sink().disconnect<&listener::method>(&listener); ``` The `trigger` member function serves the purpose of sending an immediate event to all the listeners registered so far. It offers a convenient approach that relieves users from having to create the event itself. Instead, it's enough to specify the type of event and provide all the parameters required to construct it.
As an example: ```cpp dispatcher.trigger(42); dispatcher.trigger(); ``` Listeners are invoked immediately, order of execution isn't guaranteed. This method can be used to push around urgent messages like an _is terminating_ notification on a mobile app. On the other hand, the `enqueue` member function queues messages together and allows to maintain control over the moment they are sent to listeners. The signature of this method is more or less the same of `trigger`: ```cpp dispatcher.enqueue(42); dispatcher.enqueue(); ``` Events are stored aside until the `update` member function is invoked, then all the messages that are still pending are sent to the listeners at once: ```cpp // emits all the events of the given type at once dispatcher.update(); // emits all the events queued so far at once dispatcher.update(); ``` This way users can embed the dispatcher in a loop and literally dispatch events once per tick to their systems. # Event emitter A general purpose event emitter thought mainly for those cases where it comes to working with asynchronous stuff.
Originally designed to fit the requirements of [`uvw`](https://github.com/skypjack/uvw) (a wrapper for `libuv` written in modern C++), it was adapted later to be included in this library. To create a custom emitter type, derived classes must inherit directly from the base class as: ```cpp struct my_emitter: emitter { // ... } ``` The full list of accepted types of events isn't required. Handlers are created internally on the fly and thus each type of event is accepted by default. Whenever an event is published, an emitter provides the listeners with a reference to itself along with a const reference to the event. Therefore listeners have an handy way to work with it without incurring in the need of capturing a reference to the emitter itself.
In addition, an opaque object is returned each time a connection is established between an emitter and a listener, allowing the caller to disconnect them at a later time.
The opaque object used to handle connections is both movable and copyable. On the other side, an event emitter is movable but not copyable by default. To create new instances of an emitter, no arguments are required: ```cpp my_emitter emitter{}; ``` Listeners must be movable and callable objects (free functions, lambdas, functors, `std::function`s, whatever) whose function type is: ```cpp void(const Event &, my_emitter &) ``` Where `Event` is the type of event they want to listen.
There are two ways to attach a listener to an event emitter that differ slightly from each other: * To register a long-lived listener, use the `on` member function. It is meant to register a listener designed to be invoked more than once for the given event type.
As an example: ```cpp auto conn = emitter.on([](const my_event &event, my_emitter &emitter) { // ... }); ``` The connection object can be freely discarded. Otherwise, it can be used later to disconnect the listener if required. * To register a short-lived listener, use the `once` member function. It is meant to register a listener designed to be invoked only once for the given event type. The listener is automatically disconnected after the first invocation.
As an example: ```cpp auto conn = emitter.once([](const my_event &event, my_emitter &emitter) { // ... }); ``` The connection object can be freely discarded. Otherwise, it can be used later to disconnect the listener if required. In both cases, the connection object can be used with the `erase` member function: ```cpp emitter.erase(conn); ``` There are also two member functions to use either to disconnect all the listeners for a given type of event or to clear the emitter: ```cpp // removes all the listener for the specific event emitter.clear(); // removes all the listeners registered so far emitter.clear(); ``` To send an event to all the listeners that are interested in it, the `publish` member function offers a convenient approach that relieves users from having to create the event: ```cpp struct my_event { int i; }; // ... emitter.publish(42); ``` Finally, the `empty` member function tests if there exists at least either a listener registered with the event emitter or to a given type of event: ```cpp bool empty; // checks if there is any listener registered for the specific event empty = emitter.empty(); // checks it there are listeners registered with the event emitter empty = emitter.empty(); ``` In general, the event emitter is a handy tool when the derived classes _wrap_ asynchronous operations, because it introduces a _nice-to-have_ model based on events and listeners that kindly hides the complexity behind the scenes. However it is not limited to such uses.