This project explores how modern C++ coroutines can be used to control game object behavior, and evaluates their performance and practicality in a real-time game-like environment.
- Coroutines can be a very expressive and clean way to write control logic.
- Performance measurements can vary significantly when other systems (e.g. rendering) run in parallel.
→ It is more meaningful to evaluate impact on total frame time rather than isolated execution time. - The size of a coroutine frame cannot be queried, making it difficult to implement a proper custom allocator.
- Accessing the coroutine's
promiseobject from within the coroutine body requires non-trivial workarounds. - The Visual Studio 2022 + C++20 coroutine implementation is slow. There is clear room for improvement (see CoroGoto approach).
- Using a coroutine introduces roughly ~40 ns overhead per invocation (hardware dependent).
- As of 2026, the C++ coroutine ecosystem still feels immature and underdeveloped.
Baseline implementation using traditional control flow.
- Fastest solution, but not necessarily the cleanest (though trivial in this case)
- No per-object control function calls
- Logic is expanded inside a single loop
BasicTargetsis a separate persistent array (state survives between ticks)BasicTargets[Index].Xis used as a flag to indicate initialization
Coroutine-based implementation with the cleanest logic.
- No need to store per-object state manually
- The coroutine system preserves state automatically between calls
- Downsides:
- Coroutine state is ~320 bytes even for trivial logic
- Allocated dynamically on the heap by default
Coroutine simulation using goto.
- Manually implemented → fragile and hard to maintain
- Performance close to the Basic implementation
- In an ideal world, this is roughly how a compiler could implement coroutines
- If compiler-generated, it would remove fragility and make coroutines far more attractive
- State size is minimal (similar to Basic:
status:int + Target)
Coroutine simulation using setjmp.
- Similar conceptually to the
gotosolution - Tested so others don't have to 🙂
- Extremely slow, not practical
Tests coroutine behavior in a more complex system with dynamic creation.
- Simulates real-world usage with dynamically spawned coroutines
- introduces additional overhead
Platform:
i5 @ 3200 MHz, RTX 2070, Visual Studio 2022, C++20 mode, UE 5.7, Shipping build
1,000,000 position calculations per frame, first 10,000 objects rendered
| Method | CalcPosition (ms) | Frame (ms) |
|---|---|---|
| Basic | 15.4 | 18.1 |
| CoroAllInOne | 53.5 | 56.1 |
| CoroGoto | 18.53 | 21.12 |
1,000,000 position calculations per frame, first 80,000 objects rendered
| Method | CalcPosition (ms) | Frame (ms) | Additonal time (ms) |
|---|---|---|---|
| Basic | 23.1 | 38.9 | 0 |
| CoroAllInOne | 62.6 | 76.4 | 37.5 |
| CoroGoto | 26.25 | 41.0 | 2.1 |
| CoroSetJump | ~1280 | ~1320 | too many. |
| CoroTwoLevel | 88.1 | 102.0 | 63.1 |
While C++ coroutines provide a clean and expressive abstraction, their current implementations come with significant performance and usability drawbacks in performance-critical environments like games.
For now:
- They are great for clarity
- But questionable for high-performance gameplay systems
The gap between ideal coroutine behavior and current compiler implementations remains substantial.