Skip to content

Commit 6eb7692

Browse files
author
Damian Nowakowski
committed
readme patches and more examples
1 parent 42f3427 commit 6eb7692

6 files changed

+192
-80
lines changed

README.md

+30-28
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@ Normally the for-loop would simply set the material alpha to 0, but because this
4343

4444
## Coroutines in C++
4545
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.
4951

5052
[Back to index](#index)
5153

@@ -105,9 +107,9 @@ CoroTest After Resume
105107
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:
106108

107109
## 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:
109111
* `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();`)
111113
* `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.
112114
> Note: You can also write `void await_ready() {}` and it will behave the same as if it return `false`.
113115
* `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
116118
The Promise structure contains a configuration of the coroutine, which defines how it should behave. The absolute minimum that must be defined here are:
117119
* `get_return_object` - function which constructs the coroutine handle.
118120
* `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.
121123
* `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.
124126
> 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.
125127
* `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.
126128
> Note: A Promise can also implement `return_value`. How to implement it is described **[here](#co_return-value)**.
127129
* `unhandled_exception` - used to catch any exception. To get more details about the catched extepction use `std::current_exception()` function.
128130

129131
## 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!
131133

132134
[Back to index](#index)
133135

134136
# 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.
136138

137139
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.
138140

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.
140142
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)
142144

143145
```c++
144146
#include <iostream>
@@ -221,7 +223,7 @@ As you can see in this example we can call different Awaiters inside the same co
221223
[Back to index](#index)
222224

223225
# 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.
225227
Let's write a generic Generator and then use it to write some simple counting coroutine.
226228

227229
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
326328
327329
# Fibonacci Generator
328330
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)
330333
331334
```c++
332335
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
391394
std::cout << "Returned Value: " << handle.promise().ReturnValue << "\n";
392395
```
393396

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!
397+
Important! Remember, that the `final_suspend()` of this awaiter must return `std::suspend_always`, otherwise the Handle after `co_return` will be invalid!
395398

396399
## 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`.
398401
```cpp
399402
template<std::convertible_to<T> From>
400403
void return_value(From&& from)
@@ -407,9 +410,9 @@ The difference between `return_value` and `yield_value` is that `yield_value` wi
407410
[Back to index](#index)
408411
409412
# 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.
411414
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)
413416
414417
```c++
415418
#include <coroutine>
@@ -430,15 +433,15 @@ struct CoroPromise
430433
void unhandled_exception() {}
431434
};
432435
433-
class WaitSecondsTask
436+
class WaitSeconds
434437
{
435438
private:
436439
float TimeRemaining;
437440
std::coroutine_handle<CoroPromise> Handle;
438441
FTSTicker::FDelegateHandle TickerHandle;
439442
440443
public:
441-
WaitSecondsTask(float Time) : TimeRemaining(Time) {}
444+
WaitSeconds(float Time) : TimeRemaining(Time) {}
442445
443446
void await_resume() {}
444447
bool await_ready() { return TimeRemaining <= 0.f; }
@@ -469,21 +472,21 @@ CoroHandle CoroFadeOut()
469472
CameraManager->SetManualCameraFade((float)Fade * .01f, FColor::Black, false);
470473
}
471474
}
472-
co_await WaitSecondsTask(.1f);
475+
co_await WaitSeconds(.1f);
473476
}
474477
}
475478
```
476479

477-
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.
478481

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.
481484
The time to wait is passed as an argument in the constructor.
482485
The coroutine Handle is obtained from the `await_suspend` function.
483486
The `await_suspend` function starts the Unreal Engine ticker which will resume the suspended coroutine using the received coroutine Handle.
484487

485488
## 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.
487490

488491
In order to use this function, simply call it in your project.
489492

@@ -500,11 +503,10 @@ When using coroutines it is important to be aware that the coroutine doesn't tra
500503

501504
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.
502505

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.
504507

505508
For pure c++ solutions it would require some sort of objects lifetime tracker.
506509

507510
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.
508511

509-
510512
[Back to index](#index)

Samples/01_CoroSimpleExample.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ struct CoroPromise
3737
// Do not suspend when the coroutine ends
3838
std::suspend_never final_suspend() noexcept { return {}; }
3939

40-
// Called when co_return is used
40+
// Called when co_return void is used
4141
void return_void() {}
4242

4343
// Called when exception occurs

Samples/02_CoroTasks.cpp renamed to Samples/02_CoroAwaiters.cpp

+18-18
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright (c) 2024 Damian Nowakowski. All rights reserved.
22

3-
// This is the example of c++ coroutine tasks.
3+
// This is the example of c++ coroutine Awaiters.
44
// For more details check: https://github.com/zompi2/cppcorosample
55

66
#include <iostream>
@@ -35,9 +35,9 @@ struct CoroPromise
3535
void unhandled_exception() {}
3636
};
3737

38-
// Definition of the base coroutine Task
39-
// It should contain the common part of every task we need
40-
class CoroTaskBase
38+
// Definition of the base coroutine Awaiter
39+
// It should contain the common part of every Awaiter we need
40+
class CoroAwaiterBase
4141
{
4242
public:
4343

@@ -48,27 +48,27 @@ class CoroTaskBase
4848
bool await_ready() { return false; }
4949
};
5050

51-
// Definition of the coroutine Task A
52-
class CoroTaskA : public CoroTaskBase
51+
// Definition of the coroutine Awaiter A
52+
class CoroAwaiterA : public CoroAwaiterBase
5353
{
5454
public:
5555

56-
// Called when the coroutine has been suspended using this Task
56+
// Called when the coroutine has been suspended using this Awaiter
5757
void await_suspend(std::coroutine_handle<CoroPromise> Handle)
5858
{
59-
std::cout << "Suspended Using Task A\n";
59+
std::cout << "Suspended Using Awaiter A\n";
6060
};
6161
};
6262

63-
// Definition of the coroutine Task B
64-
class CoroTaskB : public CoroTaskBase
63+
// Definition of the coroutine Awaiter B
64+
class CoroAwaiterB : public CoroAwaiterBase
6565
{
6666
public:
6767

68-
// Called when the coroutine has been suspended using this Task
68+
// Called when the coroutine has been suspended using this Awaiter
6969
void await_suspend(std::coroutine_handle<CoroPromise> Handle)
7070
{
71-
std::cout << "Suspended Using Task B\n";
71+
std::cout << "Suspended Using Awaiter B\n";
7272
};
7373
};
7474

@@ -77,13 +77,13 @@ CoroHandle CoroTest()
7777
{
7878
std::cout << "CoroTest Before Suspend\n";
7979

80-
// Suspending the function using Task A
81-
co_await CoroTaskA();
80+
// Suspending the function using Awaiter A
81+
co_await CoroAwaiterA();
8282

8383
std::cout << "CoroTest After First Resume\n";
8484

85-
// Suspending the function using Task B
86-
co_await CoroTaskB();
85+
// Suspending the function using Awaiter B
86+
co_await CoroAwaiterB();
8787

8888
std::cout << "CoroTest After Second Resume\n";
8989
}
@@ -111,10 +111,10 @@ int main()
111111
The program should output:
112112
113113
CoroTest Before Suspend
114-
Suspended Using Task A
114+
Suspended Using Awaiter A
115115
CoroTest First Resuming
116116
CoroTest After First Resume
117-
Suspended Using Task B
117+
Suspended Using Awaiter B
118118
CoroTest Second Resuming
119119
CoroTest After Second Resume
120120
*/

Samples/03_CoroGenerators.cpp

+8-26
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ struct CoroGenerator
2727

2828
// Called in order to construct the Generator
2929
CoroGenerator get_return_object() { return { CoroGenerator(CoroHandle::from_promise(*this)) }; }
30-
30+
3131
// Suspend the Generator at the beginning
3232
std::suspend_always initial_suspend() noexcept { return {}; }
3333

@@ -73,38 +73,20 @@ struct CoroGenerator
7373
~CoroGenerator() { Handle.destroy(); }
7474
};
7575

76-
// Fibonacci Sequence Generator. Yields every next value of the sequence and suspends it's execution.
77-
CoroGenerator<int> FibonacciGenerator(const int Amount)
76+
// Generator that will count to three
77+
CoroGenerator<int> CountToThree()
7878
{
79-
if (Amount <= 0)
80-
{
81-
co_return;
82-
}
83-
84-
int n1 = 1;
85-
int n2 = 1;
86-
for (int i = 1; i <= Amount; i++)
87-
{
88-
if (i < 3)
89-
{
90-
co_yield 1;
91-
}
92-
else
93-
{
94-
const int tmp = n2;
95-
n2 = n1 + n2;
96-
n1 = tmp;
97-
co_yield n2;
98-
}
99-
}
79+
co_yield 1;
80+
co_yield 2;
81+
co_yield 3;
10082
}
10183

10284
// Main program
10385
int main()
10486
{
10587
// Constructs the Generator. Because initial_suspend is set to suspend_always the defined function
10688
// will not start immediately.
107-
auto generator = FibonacciGenerator(10);
89+
auto generator = CountToThree();
10890

10991
// Every time a bool() operator is called the Generator resumes it's execution and checks if the coroutine has finished.
11092
// When the coroutine has finished the wile loop will finish.
@@ -124,5 +106,5 @@ int main()
124106
/**
125107
The program should output:
126108
127-
1 1 2 3 5 8 13 21 34 55
109+
1 2 3
128110
*/

0 commit comments

Comments
 (0)