EnTT  2.7.3
docs/process.md
1 # Crash Course: cooperative scheduler
2 
3 <!--
4 @cond TURN_OFF_DOXYGEN
5 -->
6 # Table of Contents
7 
8 * [Introduction](#introduction)
9 * [The process](#the-process)
10  * [Adaptor](#adaptor)
11 * [The scheduler](#the-scheduler)
12 <!--
13 @endcond TURN_OFF_DOXYGEN
14 -->
15 
16 # Introduction
17 
18 Sometimes processes are a useful tool to work around the strict definition of a
19 system and introduce logic in a different way, usually without resorting to the
20 introduction of other components.
21 
22 `EnTT` offers a minimal support to this paradigm by introducing a few classes
23 that users can use to define and execute cooperative processes.
24 
25 # The process
26 
27 A typical process must inherit from the `Process` class template that stays true
28 to the CRTP idiom. Moreover, derived classes must specify what's the intended
29 type for elapsed times.
30 
31 A process should expose publicly the following member functions whether
32 required (note that it isn't required to define a function unless the derived
33 class wants to _override_ the default behavior):
34 
35 * `void update(Delta, void *);`
36 
37  It's invoked once per tick until a process is explicitly aborted or it
38  terminates either with or without errors. Even though it's not mandatory to
39  declare this member function, as a rule of thumb each process should at
40  least define it to work properly. The `void *` parameter is an opaque pointer
41  to user data (if any) forwarded directly to the process during an update.
42 
43 * `void init(void *);`
44 
45  It's invoked at the first tick, immediately before an update. The `void *`
46  parameter is an opaque pointer to user data (if any) forwarded directly to the
47  process during an update.
48 
49 * `void succeeded();`
50 
51  It's invoked in case of success, immediately after an update and during the
52  same tick.
53 
54 * `void failed();`
55 
56  It's invoked in case of errors, immediately after an update and during the
57  same tick.
58 
59 * `void aborted();`
60 
61  It's invoked only if a process is explicitly aborted. There is no guarantee
62  that it executes in the same tick, this depends solely on whether the
63  process is aborted immediately or not.
64 
65 Derived classes can also change the internal state of a process by invoking
66 `succeed` and `fail`, as well as `pause` and `unpause` the process itself. All
67 these are protected member functions made available to be able to manage the
68 life cycle of a process from a derived class.
69 
70 Here is a minimal example for the sake of curiosity:
71 
72 ```cpp
73 struct MyProcess: entt::Process<MyProcess, std::uint32_t> {
74  using delta_type = std::uint32_t;
75 
76  void update(delta_type delta, void *) {
77  remaining = delta > remaining ? delta_type{] : (remaining - delta);
78 
79  // ...
80 
81  if(!remaining) {
82  succeed();
83  }
84  }
85 
86  void init(void *data) {
87  remaining = *static_cast<delta_type *>(data);
88  }
89 
90 private:
91  delta_type remaining;
92 };
93 ```
94 
95 ## Adaptor
96 
97 Lambdas and functors can't be used directly with a scheduler for they are not
98 properly defined processes with managed life cycles.<br/>
99 This class helps in filling the gap and turning lambdas and functors into
100 full featured processes usable by a scheduler.
101 
102 The function call operator has a signature similar to the one of the `update`
103 function of a process but for the fact that it receives two extra arguments to
104 call whenever a process is terminated with success or with an error:
105 
106 ```cpp
107 void(Delta delta, void *data, auto succeed, auto fail);
108 ```
109 
110 Parameters have the following meaning:
111 
112 * `delta` is the elapsed time.
113 * `data` is an opaque pointer to user data if any, `nullptr` otherwise.
114 * `succeed` is a function to call when a process terminates with success.
115 * `fail` is a function to call when a process terminates with errors.
116 
117 Both `succeed` and `fail` accept no parameters at all.
118 
119 Note that usually users shouldn't worry about creating adaptors at all. A
120 scheduler creates them internally each and every time a lambda or a functor is
121 used as a process.
122 
123 # The scheduler
124 
125 A cooperative scheduler runs different processes and helps managing their life
126 cycles.
127 
128 Each process is invoked once per tick. If it terminates, it's removed
129 automatically from the scheduler and it's never invoked again. Otherwise it's
130 a good candidate to run once more the next tick.<br/>
131 A process can also have a child. In this case, the process is replaced with
132 its child when it terminates if it returns with success. In case of errors,
133 both the process and its child are discarded. This way, it's easy to create
134 chain of processes to run sequentially.
135 
136 Using a scheduler is straightforward. To create it, users must provide only the
137 type for the elapsed times and no arguments at all:
138 
139 ```cpp
140 Scheduler<std::uint32_t> scheduler;
141 ```
142 
143 It has member functions to query its internal data structures, like `empty` or
144 `size`, as well as a `clear` utility to reset it to a clean state:
145 
146 ```cpp
147 // checks if there are processes still running
148 const auto empty = scheduler.empty();
149 
150 // gets the number of processes still running
151 Scheduler<std::uint32_t>::size_type size = scheduler.size();
152 
153 // resets the scheduler to its initial state and discards all the processes
154 scheduler.clear();
155 ```
156 
157 To attach a process to a scheduler there are mainly two ways:
158 
159 * If the process inherits from the `Process` class template, it's enough to
160  indicate its type and submit all the parameters required to construct it to
161  the `attach` member function:
162 
163  ```cpp
164  scheduler.attach<MyProcess>("foobar");
165  ```
166 
167 * Otherwise, in case of a lambda or a functor, it's enough to provide an
168  instance of the class to the `attach` member function:
169 
170  ```cpp
171  scheduler.attach([](auto...){ /* ... */ });
172  ```
173 
174 In both cases, the return value is an opaque object that offers a `then` member
175 function to use to create chains of processes to run sequentially.<br/>
176 As a minimal example of use:
177 
178 ```cpp
179 // schedules a task in the form of a lambda function
180 scheduler.attach([](auto delta, void *, auto succeed, auto fail) {
181  // ...
182 })
183 // appends a child in the form of another lambda function
184 .then([](auto delta, void *, auto succeed, auto fail) {
185  // ...
186 })
187 // appends a child in the form of a process class
188 .then<MyProcess>();
189 ```
190 
191 To update a scheduler and thus all its processes, the `update` member function
192 is the way to go:
193 
194 ```cpp
195 // updates all the processes, no user data are provided
196 scheduler.update(delta);
197 
198 // updates all the processes and provides them with custom data
199 scheduler.update(delta, &data);
200 ```
201 
202 In addition to these functions, the scheduler offers an `abort` member function
203 that can be used to discard all the running processes at once:
204 
205 ```cpp
206 // aborts all the processes abruptly ...
207 scheduler.abort(true);
208 
209 // ... or gracefully during the next tick
210 scheduler.abort();
211 ```