You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
*[Camera Fade Out for Unreal Engine 5](#camera-fade-out-for-unreal-engine-5)
17
+
*[More coroutines for UE5](#more-coroutines-for-ue5)
18
+
*[Stability concerns](#stability-concerns)
15
19
16
20
# What is a coroutine?
17
21
@@ -103,7 +107,7 @@ Now let's go through all of this from top to bottom. First of all we have to def
103
107
## Coroutine Handle
104
108
This is a structure based on the `std::coroutine_handle`. It controls the flow of the coroutine and will be used to resume the suspended coroutine later. The absolute minimum which needs to be defined inside the Handle are:
105
109
*`promise_type` - this is a type of the coroutine Promise. What is a Promise will be explained later.
106
-
*`await_resume` - this function is called when the coroutine is resumed.
110
+
*`await_resume` - this function is called when the coroutine is resumed. It can return `void` or a value of any type. When returning a value, the value will be obtained when calling `co_await` (e.g. `auto Value = co_await CoroHandle();`)
107
111
*`await_ready` - this function is called before the coroutine suspension. If it returns `false` the coroutine will be suspended. If it returns `true` it will ignore the suspension.
108
112
> Note: You can also write `void await_ready() {}` and it will behave the same as if it return `false`.
109
113
*`await_suspend` - this function is called right after the suspension. It contains the actual instance of the handle of the suspended coroutine which might be stored and used later.
@@ -119,19 +123,20 @@ The Promise structure contains a configuration of the coroutine, which defines h
119
123
*`suspend_always` - the function will be suspended at the end. You must call `destroy()` function on the handle manually. Only with this setting you can check if the coroutine has finished by using the `done()` function on the handle.
120
124
> Note: `initial_suspend` and `final_suspend` are one of the most important functions here, as they can control when the coroutine can be safely destroyed. We will utilize these functions later in more advanced coroutines.
121
125
*`return_void` - this function is called when the `co_return` is used. The `co_return` is called by default when the function reaches the end.
126
+
> Note: A Promise can also implement `return_value`. How to implement it is described **[here](#co_return-value)**.
122
127
*`unhandled_exception` - used to catch any exception. To get more details about the catched extepction use `std::current_exception()` function.
123
128
124
129
## Using the coroutine
125
130
Ok, we have a very simple coroutine defined, but how do we use it? In our example we have a function, which returns our coroutine handle, the `CoroHandle`. It uses `co_await` keyword to suspend it's execution. In the `main` program we call this function and gets the `handle` to it. Later, we can use the `resume()` function on that handle to resume the suspended coroutine. And that's all!
126
131
127
132
[Back to index](#index)
128
133
129
-
# Coroutine Tasks
130
-
Coroutine Tasks are the elegant way to define different coroutine Handles using the same Handle base and Promise, reducing the amount of boilerplate code you need to write.
134
+
# Awaiters
135
+
Awaiters are the elegant way to define different awaiting logic using the same Handle and Promise, reducing the amount of boilerplate code you need to write.
131
136
132
-
The code below implements two different tasks: `CoroTaskA` and `CoroTaskB`. They can be called when using `co_await` in the same coroutine which returns the same type of Handle.
137
+
The code below implements two different awaiters: `AwaiterA` and `AwaiterB`. They can be called when using `co_await` in the same coroutine which returns the same type of Handle.
133
138
134
-
Tasks are defined using the class instead of structs, so they allow better code encapsulation.
139
+
> Note: You can notice that in the previous example the Handle was also the Awaiter.
135
140
136
141
This code with comments is also inside the `Samples` directory here: [02_CoroTasks.cpp](Samples/02_CoroTasksExample.cpp)
@@ -203,24 +208,21 @@ The output of this code will be:
203
208
204
209
```
205
210
CoroTest Before Suspend
206
-
Suspended Using Task A
211
+
Suspended Using Awaiter A
207
212
CoroTest First Resuming
208
213
CoroTest After First Resume
209
-
Suspended Using Task B
214
+
Suspended Using Awaiter B
210
215
CoroTest Second Resuming
211
216
CoroTest After Second Resume
212
217
```
213
218
214
-
The Promise definition is the same as previously. The coroutine Handle has been split into two parts:
215
-
* the definition of the coroutine Handle which uses the defined Promise
216
-
* the definition of Tasks which implements the Handle functions such as `await_resume`, `await_ready` and `await_suspend`.
217
-
218
-
As you can see in this example we can call different Tasks inside the same coroutine function.
219
+
As you can see in this example we can call different Awaiters inside the same coroutine function.
219
220
220
221
[Back to index](#index)
221
222
222
223
# Generators - coroutines returning values
223
-
Just suspending a running function is neat, but we can do more. Coroutines can be written in such way that they can store values which can be used later. Such coroutines are called Generators. Let's write a generic Generator and then use it to write a coroutine which will give us every value of the beloved Fibonacci sequence one by one.
224
+
Just suspending a running function is neat, but we can do more. Generators are a special type of Awaiters which, when suspended, can store a value. This value can be used later.
225
+
Let's write a generic Generator and then use it to write some simple counting coroutine.
224
226
225
227
This code with comments is also inside the `Samples` directory here: [03_CoroGenerators.cpp](Samples/03_CoroGenerators.cpp)
226
228
@@ -271,6 +273,62 @@ struct CoroGenerator
271
273
~CoroGenerator() { Handle.destroy(); }
272
274
};
273
275
276
+
CoroGenerator<int> CountToThree()
277
+
{
278
+
co_yield 1;
279
+
co_yield 2;
280
+
co_yield 3;
281
+
}
282
+
283
+
int main()
284
+
{
285
+
auto generator = CountToThree();
286
+
while (generator)
287
+
{
288
+
std::cout << generator() << " ";
289
+
}
290
+
return 0;
291
+
}
292
+
```
293
+
294
+
The output of this code will be:
295
+
296
+
```
297
+
1 2 3
298
+
```
299
+
300
+
Once again, there is a lot to cover. Let's go through this step by step.
301
+
302
+
## Coroutine Promise for Generator
303
+
The Promise for a Generator is slightly different. You can notice few differences:
304
+
* `T Value` - Promise stores a value of a generic type. This value can be obtained later by a Generator.
305
+
* `get_return_object` - doesn't return coroutine Handle, but a Generator with a coroutine Handle passed as an argument to the constructor.
306
+
* `initial_suspend` and `final_suspend` returns `suspend_always`. This is important, because with such setup we have the full control over the coroutine flow.
307
+
* `yield_value` - this function is called every time when `co_yield` is used. It stores the given value to the `Value` variable and returns `suspend_always` in order to suspend the function.
308
+
309
+
The Promise is defined inside the Generator struct in order to keep everything in one place and to avoid declaration loop.
310
+
311
+
## Generator
312
+
The generator itself has few interesting parts as well:
313
+
* `Handle` - this is the coroutine Handle saved from the Generator constructor.
314
+
* `operator bool()` - is very handy for resuming the coroutine and checking if the coroutine has finished. To check if the coroutine is done we use `done()` function on the coroutine Handle. We can use it safely, because the `final_suspend` is set to `suspend_always`, so the coroutine Handle will not be destroyed automatically when the function is finished.
315
+
* `operator()` - will be used to get a stored value from the Promise.
316
+
* Constructor - receives and remembers the coroutine Handle. The Generator construcor is used in `get_return_object` function in the Promise.
317
+
* Destructor - explicitly destroys the coroutine Handle using `destroy()` function. It must be used, because the `final_suspend` is set to `suspend_always`, so it won't be destroyed automatically.
318
+
319
+
## The Counter
320
+
`CountToThree` coroutine returns the Generator which stores the `int` value. Every `co_yield` stores the given value inside of the Generator, which can be obtained later.
321
+
322
+
## Using the Counter
323
+
When Generator is constructed it automatically suspends, because of the `initial_suspend` set to `suspend_always`. Inside the while loop the `operator bool()` override is used to resume the Generator and check if the coroutine has already finished. Inside the while loop the `operator()` override is used to get lastly yielded value.
324
+
325
+
[Back to index](#index)
326
+
327
+
# Fibonacci Generator
328
+
329
+
We can write a slightly more advanced Generator. This one will yield every next number in the Fibonacci sequence.
330
+
331
+
```c++
274
332
CoroGenerator<int> FibonacciGenerator(const int Amount)
275
333
{
276
334
if (Amount <= 0)
@@ -313,30 +371,38 @@ The output of this code will be:
313
371
1 1 2 3 5 8 13 21 34 55
314
372
```
315
373
316
-
Once again, there is a lot to cover. Let's go through this step by step.
374
+
[Back to index](#index)
317
375
318
-
## Coroutine Promise for Generator
319
-
The Promise for a Generator is slightly different. You can notice few differences:
320
-
* `T Value` - Promise stores a value of a generic type. This value can be obtained later by a Generator.
321
-
* `get_return_object` - doesn't return coroutine Handle, but a Generator with a coroutine Handle passed as an argument to the constructor.
322
-
* `initial_suspend` and `final_suspend` returns `suspend_always`. This is important, because with such setup we have the full control over the coroutine flow.
323
-
* `yield_value` - this function is called every time when `co_yield` is used. It stores the given value to the `Value` variable and returns `suspend_always` in order to suspend the function.
376
+
# co_return value
377
+
A Coroutine Promise can also define `return_value` function, which will be called when `co_return` with a value is used. `return_value` and `return_void` can't be defined at the same time, only one of these functions can be used in the same Promise.
324
378
325
-
The Promise is defined inside the Generator struct in order to keep everything in one place and to avoid declaration loop.
379
+
## co_return value in Awaiter
380
+
In Awaiter the `return_value` must be defined with the value type it can return. For `int` it will be:
381
+
```cpp
382
+
int ReturnValue;
383
+
voidreturn_value(int Value)
384
+
{
385
+
ReturnValue = Value;
386
+
}
387
+
```
326
388
327
-
## Generator
328
-
The generator itself has few interesting parts as well:
329
-
* `Handle` - this is the coroutine Handle saved from the Generator constructor.
330
-
* `operator bool()` - is very handy for resuming the coroutine and checking if the coroutine has finished. To check if the coroutine is done we use `done()` function on the coroutine Handle. We can use it safely, because the `final_suspend` is set to `suspend_always`, so the coroutine Handle will not be destroyed automatically when the function is finished.
331
-
* `operator()` - will be used to get a stored value from the Promise.
332
-
* Constructor - receives and remembers the coroutine Handle. The Generator construcor is used in `get_return_object` function in the Promise.
333
-
* Destructor - explicitly destroys the coroutine Handle using `destroy()` function. It must be used, because the `final_suspend` is set to `suspend_always`, so it won't be destroyed automatically.
389
+
To get this value, get a Promise from the Handle and then get this value
`FibonacciGenerator` function returns the Generator which stores the `int` value. Every next value of the Fibonacci sequence is yielded, which means the function is suspended and the value is stored.
394
+
Important! Remember, that the `final_suspend()` of this awaiter must return `std::suspend_always`, otherwise the Handle after `co_return` will not be valid!
337
395
338
-
## Using Fibonacci Generator
339
-
When our Generator is constructed it automatically suspends, because of the `initial_suspend` set to `suspend_always`. Inside the while loop the `operator bool()` override is used to resume the Generator and check if the coroutine has already finished. Inside the while loop the `operator()` override is used to get lastly yielded value.
396
+
## co_return value in Generator
397
+
In Generator defining `return_value` is very similar to defining `yield_value`.
398
+
```cpp
399
+
template<std::convertible_to<T> From>
400
+
voidreturn_value(From&& from)
401
+
{
402
+
Value = std::forward<From>(from);
403
+
}
404
+
```
405
+
The difference between `return_value` and `yield_value` is that `yield_value` will suspend the coroutine, while `return_value` will finish it.
@@ -422,7 +487,24 @@ The fade out function changes the camera fade in a for loop in 10 steps every 0.
422
487
423
488
In order to use this function, simply call it in your project.
424
489
425
-
## More coroutines for UE5
490
+
[Back to index](#index)
491
+
492
+
# More coroutines for UE5
426
493
If you are interested witch coroutines implementation for Unreal Engine 5 check out this amazing plugin [UE5Coro](https://github.com/landelare/ue5coro). Coroutines are also implemented in my UE plugin [Enhanced Code Flow](https://github.com/zompi2/UE4EnhancedCodeFlow) as well :)
427
494
495
+
[Back to index](#index)
496
+
497
+
# Stability concerns
498
+
499
+
When using coroutines it is important to be aware that the coroutine doesn't track the lifetime of it's owner.
500
+
501
+
If the owner of the coroutine has been destroyed while the coroutine is suspended, when something resumes the coroutine we will end up in an invalid place in memory.
502
+
503
+
The solution for game engines like Unreal Engine is to keep a soft reference to the owner which calls the coroutine inside the Awaiter and simply do not resume the coroutine if the reference becomes invalid.
504
+
505
+
For pure c++ solutions it would require some sort of objects lifetime tracker.
506
+
507
+
There is no bad side effect of keeping coroutines suspended forever, because they are stackless and all data required for their resume is stored inside a Handle, which is destroyed when going out of scope.
0 commit comments