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
Copy file name to clipboardExpand all lines: README.md
+30-28
Original file line number
Diff line number
Diff line change
@@ -43,9 +43,11 @@ Normally the for-loop would simply set the material alpha to 0, but because this
43
43
44
44
## Coroutines in C++
45
45
Function is a coroutine in C++ when there is one of the following keywords inside:
46
-
*`co_await` - it simply suspends the function
47
-
*`co_yield` - it suspends the function and returns a value
48
-
*`co_return` - it completes the function with or without returning a value
46
+
*`co_await` - it suspends the coroutine
47
+
*`co_yield` - it suspends the coroutine and returns a value to the coroutine's caller
48
+
*`co_return` - it completes the function with or without returning a value to the coroutine's caller
49
+
50
+
Coroutine must return a coroutine handle which must satisfies a number of requirements, which will be explained below.
49
51
50
52
[Back to index](#index)
51
53
@@ -105,9 +107,9 @@ CoroTest After Resume
105
107
Now let's go through all of this from top to bottom. First of all we have to define the coroutine. The coroutine is made of two parts:
106
108
107
109
## Coroutine Handle
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:
110
+
This is a structure based on the `std::coroutine_handle`. It allows to control the flow of the coroutine. It is always returned by the coroutine to it's caller. It will be used to resume the suspended coroutine later. The absolute minimum which needs to be defined inside the Handle is:
109
111
*`promise_type` - this is a type of the coroutine Promise. What is a Promise will be explained later.
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();`)
112
+
*`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 can be obtained when calling `co_await` (e.g. `auto Value = co_await CoroHandle();`)
111
113
*`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.
112
114
> Note: You can also write `void await_ready() {}` and it will behave the same as if it return `false`.
113
115
*`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.
@@ -116,29 +118,29 @@ This is a structure based on the `std::coroutine_handle`. It controls the flow o
116
118
The Promise structure contains a configuration of the coroutine, which defines how it should behave. The absolute minimum that must be defined here are:
117
119
*`get_return_object` - function which constructs the coroutine handle.
118
120
*`initial_suspend` - can return:
119
-
*`suspend_never` - the function will not be suspended right at the beginning and it will be suspended when the `co_await` or `co_yield` is used.
120
-
*`suspend_always` - the function will be suspended right at the beginning and it must be resumed to even start.
121
+
*`suspend_never` - the coroutine will not be suspended right at the beginning and it will be suspended when the `co_await` or `co_yield` is used.
122
+
*`suspend_always` - the coroutine will be suspended right at the beginning and it must be resumed to even start.
121
123
*`final_suspend` - can return:
122
-
*`suspend_never` - the function will not be suspended at the end of the function. The coroutine will be destroyed right after the function has finished.
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.
124
+
*`suspend_never` - the coroutine will not be suspended at the end of the coroutine. The coroutine will be destroyed right after it's finished.
125
+
*`suspend_always` - the coroutine 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.
124
126
> 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.
125
127
*`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
128
> Note: A Promise can also implement `return_value`. How to implement it is described **[here](#co_return-value)**.
127
129
*`unhandled_exception` - used to catch any exception. To get more details about the catched extepction use `std::current_exception()` function.
128
130
129
131
## Using the coroutine
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!
132
+
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 get the `handle` to it. Later, we can use the `resume()` function on that handle to resume the suspended coroutine. And that's all!
131
133
132
134
[Back to index](#index)
133
135
134
136
# 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.
137
+
Awaiters are an elegant way to define different awaiting logics using the same Handle and Promise, reducing the amount of boilerplate code you need to write.
136
138
137
139
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.
138
140
139
-
> Note: You can notice that in the previous example the Handle was also the Awaiter.
141
+
> Note: You can notice that in the previous example the Handle was also an Awaiter.
140
142
141
-
This code with comments is also inside the `Samples` directory here: [02_CoroTasks.cpp](Samples/02_CoroTasksExample.cpp)
143
+
This code with comments is also inside the `Samples` directory here: [02_CoroAwaiters.cpp](Samples/02_CoroAwaiters.cpp)
142
144
143
145
```c++
144
146
#include<iostream>
@@ -221,7 +223,7 @@ As you can see in this example we can call different Awaiters inside the same co
221
223
[Back to index](#index)
222
224
223
225
# Generators - coroutines returning values
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.
226
+
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, which can be used later outside of the coroutine.
225
227
Let's write a generic Generator and then use it to write some simple counting coroutine.
226
228
227
229
This code with comments is also inside the `Samples` directory here: [03_CoroGenerators.cpp](Samples/03_CoroGenerators.cpp)
@@ -326,7 +328,8 @@ When Generator is constructed it automatically suspends, because of the `initial
326
328
327
329
# Fibonacci Generator
328
330
329
-
We can write a slightly more advanced Generator. This one will yield every next number in the Fibonacci sequence.
331
+
We can write a slightly more advanced Generator. This one will yield every next number in the Fibonacci sequence.
332
+
This code with comments is also inside the `Samples` directory here: [04_CoroFibonacciGenerator.cpp](Samples/04_CoroFibonacciGenerator.cpp)
330
333
331
334
```c++
332
335
CoroGenerator<int> FibonacciGenerator(const int Amount)
@@ -391,10 +394,10 @@ To get this value, get a Promise from the Handle and then get this value
Important! Remember, that the `final_suspend()` of this awaiter must return `std::suspend_always`, otherwise the Handle after `co_return` will not be valid!
397
+
Important! Remember, that the `final_suspend()` of this awaiter must return `std::suspend_always`, otherwise the Handle after `co_return` will be invalid!
395
398
396
399
## co_return value in Generator
397
-
In Generator defining `return_value` is very similar to defining `yield_value`.
400
+
In Generators defining `return_value` is very similar to defining `yield_value`.
398
401
```cpp
399
402
template<std::convertible_to<T> From>
400
403
voidreturn_value(From&& from)
@@ -407,9 +410,9 @@ The difference between `return_value` and `yield_value` is that `yield_value` wi
407
410
[Back to index](#index)
408
411
409
412
# Camera Fade Out for Unreal Engine 5
410
-
[At the beginning of this document](#coroutines-in-other-languages) I showed the example of the coroutine used in Unity game engine to fade out the camera. Let's write the same functionality but for Unreal Engine 5.3 which officially supports C++20, so it should be possible to implement it.
413
+
[At the beginning of this document](#coroutines-in-other-languages) I showed the example of the coroutine used in Unity game engine to fade out the camera. Let's write the same functionality but for Unreal Engine 5.3 which officially supports C++20, so it is possible to implement it.
411
414
412
-
This code with comments is also inside the `Samples` directory here: [04_CoroUE5FadeOut.cpp](Samples/04_CoroUE5FadeOut.cpp)
415
+
This code with comments is also inside the `Samples` directory here: [05_CoroUE5FadeOut.cpp](Samples/05_CoroUE5FadeOut.cpp)
The Handle and the Promise are the same as usual. To make it work we need a coroutine Task, which will suspend the coroutine for a given amount of time and the actual coroutine which will fade out the camera.
480
+
The Handle and the Promise are the same as usual. To make it work we need a coroutine Awaiter, which will suspend the coroutine for a given amount of time and the actual coroutine which will fade out the camera.
478
481
479
-
## Wait Seconds Task
480
-
This is a specific case of a coroutine Task which resumes itself. It must somehow receive the coroutine Handle and the amount of seconds we want to wait.
482
+
## Wait Seconds
483
+
This is a specific case of a coroutine Awaiter which resumes itself. It must somehow receive the coroutine Handle and the amount of seconds we want to wait.
481
484
The time to wait is passed as an argument in the constructor.
482
485
The coroutine Handle is obtained from the `await_suspend` function.
483
486
The `await_suspend` function starts the Unreal Engine ticker which will resume the suspended coroutine using the received coroutine Handle.
484
487
485
488
## Fade Out function
486
-
The fade out function changes the camera fade in a for loop in 10 steps every 0.1 second. You can notice the `co_await WaitSecondsTask(.1f)` after every loop iteration, which triggers our waiting coroutine Task.
489
+
The fade out function changes the camera fade in a for loop in 10 steps every 0.1 second. You can notice the `co_await WaitSeconds(.1f)` after every loop iteration, which triggers our waiting coroutine Awaiter.
487
490
488
491
In order to use this function, simply call it in your project.
489
492
@@ -500,11 +503,10 @@ When using coroutines it is important to be aware that the coroutine doesn't tra
500
503
501
504
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
505
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.
506
+
The solution for game engines like Unreal Engine is to keep a soft reference to the owner inside the Awaiter and simply do not resume the coroutine if the reference becomes invalid.
504
507
505
508
For pure c++ solutions it would require some sort of objects lifetime tracker.
506
509
507
510
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