I have implemented a c++ coroutine framework so that I can coawait my own coroutines and script sequences like so:
co_await obj->move_to(....)
obj->face(LEFT);
co_await wait(15);
This would be a coroutine that I update one frame at a time from my main program, and would execute the directions in order as if they are blocking.
This is the implementation of my coroutines for reference:
struct Script {
struct suspend_maybe {
bool suspend;
bool await_ready() noexcept { return !suspend; }
void await_suspend(std::coroutine_handle<> c) noexcept {}
void await_resume() noexcept { }
};
struct promise_type {
std::coroutine_handle<promise_type> child;
std::coroutine_handle<promise_type> caller;
bool cancelled = false;
void cancel() { cancelled = true; }
Script get_return_object() {
return { std::coroutine_handle<Script::promise_type>::from_promise(*this) };
}
suspend_maybe initial_suspend() noexcept {
return { !caller };
}
std::suspend_always final_suspend() noexcept {
return {};
}
void unhandled_exception() {}
void return_void() {}
};
bool await_ready() noexcept { return false; }
bool await_suspend(std::coroutine_handle<Script::promise_type> c) noexcept {
c.promise().child = h_;
h_.promise().caller = c;
return true;
}
void await_resume() noexcept { }
std::coroutine_handle<Script::promise_type> h_;
Script(std::coroutine_handle<Script::promise_type> h) :h_{ h } { }
operator std::coroutine_handle<Script::promise_type>() const { return h_; }
// Step update the coroutine, managing its control flow
void update() {
auto& child = h_.promise().child;
if (child && !(child.done() || child.promise().cancelled)) {
child.promise().get_return_object().update();
}
else {
if (child && (child.done() || child.promise().cancelled)) {
child.destroy();
child = nullptr;
}
h_.resume();
}
}
};
typedef std::coroutine_handle<Script::promise_type> CHandle;
And this is how I update the coroutines that are spawned by the script, in the main loop per every frame of rendering:
CHandle ScriptPlayer::spawn(CHandle script) {
scripts.push_back(script);
return script;
}
void ScriptPlayer::update() {
for (int i = 0; i < static_cast<int>(scripts.size()); ) {
Script{ scripts[i] }.update();
if (play_next_script) break;
// Check completion
if (scripts[i].done() || scripts[i].promise().cancelled) {
scripts[i].destroy();
scripts.erase(scripts.begin() + i);
}
else i++;
}
}
Then somewhere I have a lambda that I use to generate a Script and spawn it to the vector of coroutines that I update every frame:
auto terminate = [](ScriptPlayer* player, Object* fx) -> Script {
co_await fx->wait_animation();
player->destroy_object(fx);
}(player, fx);
player->spawn(terminate);
If I use the lambda function parameters like I did above, everything works perfectly fine. On the other hand, if I try to use the captures like so:
auto terminate = [player, fx]() -> Script {
co_await fx->wait_animation();
player->destroy_object(fx);
}();
player->spawn(terminate);
The whole thing crashes within "wait_animation" during runtime with a completely corrupt coroutine stack frame.
Why? I thought passing pointers by value through the captures would be the same as passing the same pointers through function arguments?