🛠️🐜 Antkeeper superbuild with dependencies included https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

447 lines
14 KiB

  1. # Crash Course: events, signals and everything in between
  2. <!--
  3. @cond TURN_OFF_DOXYGEN
  4. -->
  5. # Table of Contents
  6. * [Introduction](#introduction)
  7. * [Delegate](#delegate)
  8. * [Signals](#signals)
  9. * [Event dispatcher](#event-dispatcher)
  10. * [Event emitter](#event-emitter)
  11. <!--
  12. @endcond TURN_OFF_DOXYGEN
  13. -->
  14. # Introduction
  15. Signals are usually a core part of games and software architectures in
  16. general.<br/>
  17. Roughly speaking, they help to decouple the various parts of a system while
  18. allowing them to communicate with each other somehow.
  19. The so called _modern C++_ comes with a tool that can be useful in these terms,
  20. the `std::function`. As an example, it can be used to create delegates.<br/>
  21. However, there is no guarantee that an `std::function` does not perform
  22. allocations under the hood and this could be problematic sometimes. Furthermore,
  23. it solves a problem but may not adapt well to other requirements that may arise
  24. from time to time.
  25. In case that the flexibility and potential of an `std::function` are not
  26. required or where you are looking for something different, `EnTT` offers a full
  27. set of classes to solve completely different problems.
  28. # Delegate
  29. A delegate can be used as a general purpose invoker with no memory overhead for
  30. free functions and members provided along with an instance on which to invoke
  31. them.<br/>
  32. It does not claim to be a drop-in replacement for an `std::function`, so do not
  33. expect to use it whenever an `std::function` fits well. However, it can be used
  34. to send opaque delegates around to be used to invoke functions as needed.
  35. The interface is trivial. It offers a default constructor to create empty
  36. delegates:
  37. ```cpp
  38. entt::delegate<int(int)> delegate{};
  39. ```
  40. All what is needed to create an instance is to specify the type of the function
  41. the delegate will _contain_, that is the signature of the free function or the
  42. member function one wants to assign to it.
  43. Attempting to use an empty delegate by invoking its function call operator
  44. results in undefined behavior or most likely a crash. Before to use a delegate,
  45. it must be initialized.<br/>
  46. There exists a bunch of overloads of the `connect` member function to do that.
  47. As an example of use:
  48. ```cpp
  49. int f(int i) { return i; }
  50. struct my_struct {
  51. int f(const int &i) { return i }
  52. };
  53. // bind a free function to the delegate
  54. delegate.connect<&f>();
  55. // bind a member function to the delegate
  56. my_struct instance;
  57. delegate.connect<&my_struct::f>(&instance);
  58. ```
  59. The delegate class accepts also data members, if needed. In this case, the
  60. function type of the delegate is such that the parameter list is empty and the
  61. value of the data member is at least convertible to the return type.<br/>
  62. Functions having type equivalent to `void(T *, args...)` are accepted as well.
  63. In this case, `T *` is considered a payload and the function will receive it
  64. back every time it's invoked. In other terms, this works just fine with the
  65. above definition:
  66. ```cpp
  67. void g(const char *c, int i) { /* ... */ }
  68. const char c = 'c';
  69. delegate.connect<&g>(&c);
  70. delegate(42);
  71. ```
  72. The function `g` will be invoked with a pointer to `c` and `42`. However, the
  73. function type of the delegate is still `void(int)`, mainly because this is also
  74. the signature of its function call operator.
  75. To create and initialize a delegate at once, there are also some specialized
  76. constructors. Because of the rules of the language, the listener is provided by
  77. means of the `entt::connect_arg` variable template:
  78. ```cpp
  79. entt::delegate<int(int)> func{entt::connect_arg<&f>};
  80. ```
  81. Aside `connect`, a `disconnect` counterpart isn't provided. Instead, there
  82. exists a `reset` member function to use to clear a delegate.<br/>
  83. To know if a delegate is empty, it can be used explicitly in every conditional
  84. statement:
  85. ```cpp
  86. if(delegate) {
  87. // ...
  88. }
  89. ```
  90. Finally, to invoke a delegate, the function call operator is the way to go as
  91. usual:
  92. ```cpp
  93. auto ret = delegate(42);
  94. ```
  95. As shown above, listeners do not have to strictly follow the signature of the
  96. delegate. As long as a listener can be invoked with the given arguments to yield
  97. a result that is convertible to the given result type, everything works just
  98. fine.
  99. Probably too much small and pretty poor of functionalities, but the delegate
  100. class can help in a lot of cases and it has shown that it is worth keeping it
  101. within the library.
  102. # Signals
  103. Signal handlers work with naked pointers, function pointers and pointers to
  104. member functions. Listeners can be any kind of objects and users are in charge
  105. of connecting and disconnecting them from a signal to avoid crashes due to
  106. different lifetimes. On the other side, performance shouldn't be affected that
  107. much by the presence of such a signal handler.<br/>
  108. A signal handler can be used as a private data member without exposing any
  109. _publish_ functionality to the clients of a class. The basic idea is to impose a
  110. clear separation between the signal itself and its _sink_ class, that is a tool
  111. to be used to connect and disconnect listeners on the fly.
  112. The API of a signal handler is straightforward. The most important thing is that
  113. it comes in two forms: with and without a collector. In case a signal is
  114. associated with a collector, all the values returned by the listeners can be
  115. literally _collected_ and used later by the caller. Otherwise it works just like
  116. a plain signal that emits events from time to time.<br/>
  117. **Note**: collectors are allowed only in case of function types whose the return
  118. type isn't `void` for obvious reasons.
  119. To create instances of signal handlers there exist mainly two ways:
  120. ```cpp
  121. // no collector type
  122. entt::sigh<void(int, char)> signal;
  123. // explicit collector type
  124. entt::sigh<void(int, char), my_collector<bool>> collector;
  125. ```
  126. As expected, they offer all the basic functionalities required to know how many
  127. listeners they contain (`size`) or if they contain at least a listener (`empty`)
  128. and even to swap two signal handlers (`swap`).
  129. Besides them, there are member functions to use both to connect and disconnect
  130. listeners in all their forms by means of a sink:
  131. ```cpp
  132. void foo(int, char) { /* ... */ }
  133. struct listener {
  134. void bar(const int &, char) { /* ... */ }
  135. };
  136. // ...
  137. listener instance;
  138. signal.sink().connect<&foo>();
  139. signal.sink().connect<&listener::bar>(&instance);
  140. // ...
  141. // disconnects a free function
  142. signal.sink().disconnect<&foo>();
  143. // disconnect a member function of an instance
  144. signal.sink().disconnect<&listener::bar>(&instance);
  145. // discards all the listeners at once
  146. signal.sink().disconnect();
  147. ```
  148. As shown above, listeners do not have to strictly follow the signature of the
  149. signal. As long as a listener can be invoked with the given arguments to yield a
  150. result that is convertible to the given result type, everything works just fine.
  151. Once listeners are attached (or even if there are no listeners at all), events
  152. and data in general can be published through a signal by means of the `publish`
  153. member function:
  154. ```cpp
  155. signal.publish(42, 'c');
  156. ```
  157. To collect data, the `collect` member function should be used instead. Below is
  158. a minimal example to show how to use it:
  159. ```cpp
  160. struct my_collector {
  161. std::vector<int> vec{};
  162. bool operator()(int v) noexcept {
  163. vec.push_back(v);
  164. return true;
  165. }
  166. };
  167. int f() { return 0; }
  168. int g() { return 1; }
  169. // ...
  170. entt::sigh<int(), my_collector<int>> signal;
  171. signal.sink().connect<&f>();
  172. signal.sink().connect<&g>();
  173. my_collector collector = signal.collect();
  174. assert(collector.vec[0] == 0);
  175. assert(collector.vec[1] == 1);
  176. ```
  177. A collector must expose a function operator that accepts as an argument a type
  178. to which the return type of the listeners can be converted. Moreover, it has to
  179. return a boolean value that is false to stop collecting data, true otherwise.
  180. This way one can avoid calling all the listeners in case it isn't necessary.
  181. # Event dispatcher
  182. The event dispatcher class is designed so as to be used in a loop. It allows
  183. users both to trigger immediate events or to queue events to be published all
  184. together once per tick.<br/>
  185. This class shares part of its API with the one of the signal handler, but it
  186. doesn't require that all the types of events are specified when declared:
  187. ```cpp
  188. // define a general purpose dispatcher that works with naked pointers
  189. entt::dispatcher dispatcher{};
  190. ```
  191. In order to register an instance of a class to a dispatcher, its type must
  192. expose one or more member functions the arguments of which are such that
  193. `const E &` can be converted to them for each type of event `E`, no matter what
  194. the return value is.<br/>
  195. The name of the member function aimed to receive the event must be provided to
  196. the `connect` member function of the sink in charge for the specific event:
  197. ```cpp
  198. struct an_event { int value; };
  199. struct another_event {};
  200. struct listener
  201. {
  202. void receive(const an_event &) { /* ... */ }
  203. void method(const another_event &) { /* ... */ }
  204. };
  205. // ...
  206. listener listener;
  207. dispatcher.sink<an_event>().connect<&listener::receive>(&listener);
  208. dispatcher.sink<another_event>().connect<&listener::method>(&listener);
  209. ```
  210. The `disconnect` member function follows the same pattern and can be used to
  211. selectively remove listeners:
  212. ```cpp
  213. dispatcher.sink<an_event>().disconnect<&listener::receive>(&listener);
  214. dispatcher.sink<another_event>().disconnect<&listener::method>(&listener);
  215. ```
  216. The `trigger` member function serves the purpose of sending an immediate event
  217. to all the listeners registered so far. It offers a convenient approach that
  218. relieves users from having to create the event itself. Instead, it's enough to
  219. specify the type of event and provide all the parameters required to construct
  220. it.<br/>
  221. As an example:
  222. ```cpp
  223. dispatcher.trigger<an_event>(42);
  224. dispatcher.trigger<another_event>();
  225. ```
  226. Listeners are invoked immediately, order of execution isn't guaranteed. This
  227. method can be used to push around urgent messages like an _is terminating_
  228. notification on a mobile app.
  229. On the other hand, the `enqueue` member function queues messages together and
  230. allows to maintain control over the moment they are sent to listeners. The
  231. signature of this method is more or less the same of `trigger`:
  232. ```cpp
  233. dispatcher.enqueue<an_event>(42);
  234. dispatcher.enqueue<another_event>();
  235. ```
  236. Events are stored aside until the `update` member function is invoked, then all
  237. the messages that are still pending are sent to the listeners at once:
  238. ```cpp
  239. // emits all the events of the given type at once
  240. dispatcher.update<my_event>();
  241. // emits all the events queued so far at once
  242. dispatcher.update();
  243. ```
  244. This way users can embed the dispatcher in a loop and literally dispatch events
  245. once per tick to their systems.
  246. # Event emitter
  247. A general purpose event emitter thought mainly for those cases where it comes to
  248. working with asynchronous stuff.<br/>
  249. Originally designed to fit the requirements of
  250. [`uvw`](https://github.com/skypjack/uvw) (a wrapper for `libuv` written in
  251. modern C++), it was adapted later to be included in this library.
  252. To create a custom emitter type, derived classes must inherit directly from the
  253. base class as:
  254. ```cpp
  255. struct my_emitter: emitter<my_emitter> {
  256. // ...
  257. }
  258. ```
  259. The full list of accepted types of events isn't required. Handlers are created
  260. internally on the fly and thus each type of event is accepted by default.
  261. Whenever an event is published, an emitter provides the listeners with a
  262. reference to itself along with a const reference to the event. Therefore
  263. listeners have an handy way to work with it without incurring in the need of
  264. capturing a reference to the emitter itself.<br/>
  265. In addition, an opaque object is returned each time a connection is established
  266. between an emitter and a listener, allowing the caller to disconnect them at a
  267. later time.<br/>
  268. The opaque object used to handle connections is both movable and copyable. On
  269. the other side, an event emitter is movable but not copyable by default.
  270. To create new instances of an emitter, no arguments are required:
  271. ```cpp
  272. my_emitter emitter{};
  273. ```
  274. Listeners must be movable and callable objects (free functions, lambdas,
  275. functors, `std::function`s, whatever) whose function type is:
  276. ```cpp
  277. void(const Event &, my_emitter &)
  278. ```
  279. Where `Event` is the type of event they want to listen.<br/>
  280. There are two ways to attach a listener to an event emitter that differ
  281. slightly from each other:
  282. * To register a long-lived listener, use the `on` member function. It is meant
  283. to register a listener designed to be invoked more than once for the given
  284. event type.<br/>
  285. As an example:
  286. ```cpp
  287. auto conn = emitter.on<my_event>([](const my_event &event, my_emitter &emitter) {
  288. // ...
  289. });
  290. ```
  291. The connection object can be freely discarded. Otherwise, it can be used later
  292. to disconnect the listener if required.
  293. * To register a short-lived listener, use the `once` member function. It is
  294. meant to register a listener designed to be invoked only once for the given
  295. event type. The listener is automatically disconnected after the first
  296. invocation.<br/>
  297. As an example:
  298. ```cpp
  299. auto conn = emitter.once<my_event>([](const my_event &event, my_emitter &emitter) {
  300. // ...
  301. });
  302. ```
  303. The connection object can be freely discarded. Otherwise, it can be used later
  304. to disconnect the listener if required.
  305. In both cases, the connection object can be used with the `erase` member
  306. function:
  307. ```cpp
  308. emitter.erase(conn);
  309. ```
  310. There are also two member functions to use either to disconnect all the
  311. listeners for a given type of event or to clear the emitter:
  312. ```cpp
  313. // removes all the listener for the specific event
  314. emitter.clear<my_event>();
  315. // removes all the listeners registered so far
  316. emitter.clear();
  317. ```
  318. To send an event to all the listeners that are interested in it, the `publish`
  319. member function offers a convenient approach that relieves users from having to
  320. create the event:
  321. ```cpp
  322. struct my_event { int i; };
  323. // ...
  324. emitter.publish<my_event>(42);
  325. ```
  326. Finally, the `empty` member function tests if there exists at least either a
  327. listener registered with the event emitter or to a given type of event:
  328. ```cpp
  329. bool empty;
  330. // checks if there is any listener registered for the specific event
  331. empty = emitter.empty<my_event>();
  332. // checks it there are listeners registered with the event emitter
  333. empty = emitter.empty();
  334. ```
  335. In general, the event emitter is a handy tool when the derived classes _wrap_
  336. asynchronous operations, because it introduces a _nice-to-have_ model based on
  337. events and listeners that kindly hides the complexity behind the scenes. However
  338. it is not limited to such uses.