Skip to content

Commit

Permalink
Allow subclassing coroutines (emscripten-core#20682)
Browse files Browse the repository at this point in the history
Slight refactoring of coroutine glue to allow subclassing via the public API.

This doesn't change how `co_await some_val;` behaves, but makes it possible to create your own subclasses that can also be awaited.
  • Loading branch information
RReverser authored Nov 18, 2023
1 parent 28a09f6 commit c017fc2
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 12 deletions.
22 changes: 11 additions & 11 deletions system/include/emscripten/val.h
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,10 @@ class val {
constexpr nullptr_t end() const { return nullptr; }

#if __cplusplus >= 202002L
struct promise_type;
class awaiter;
awaiter operator co_await() const;

class promise_type;
#endif

private:
Expand Down Expand Up @@ -660,12 +663,11 @@ inline val::iterator val::begin() const {
}

#if __cplusplus >= 202002L
namespace internal {
// Awaiter defines a set of well-known methods that compiler uses
// to drive the argument of the `co_await` operator (regardless
// of the type of the parent coroutine).
// This one is used for Promises represented by the `val` type.
class val_awaiter {
class val::awaiter {
// State machine holding awaiter's current state. One of:
// - initially created with promise
// - waiting with a given coroutine handle
Expand All @@ -677,11 +679,11 @@ class val_awaiter {
constexpr static std::size_t STATE_RESULT = 2;

public:
val_awaiter(val&& promise)
: state(std::in_place_index<STATE_PROMISE>, std::move(promise)) {}
awaiter(const val& promise)
: state(std::in_place_index<STATE_PROMISE>, promise) {}

// just in case, ensure nobody moves / copies this type around
val_awaiter(val_awaiter&&) = delete;
awaiter(awaiter&&) = delete;

// Promises don't have a synchronously accessible "ready" state.
bool await_ready() { return false; }
Expand All @@ -705,6 +707,9 @@ class val_awaiter {
// of the `co_await ...` expression - in our case, the stored value.
val await_resume() { return std::move(std::get<STATE_RESULT>(state)); }
};

inline val::awaiter val::operator co_await() const {
return {*this};
}

// `promise_type` is a well-known subtype with well-known method names
Expand Down Expand Up @@ -742,11 +747,6 @@ class val::promise_type {
void return_value(T&& value) {
resolve(std::forward<T>(value));
}

// Return our awaiter on `co_await promise`.
internal::val_awaiter await_transform(val promise) {
return {std::move(promise)};
}
};
#endif

Expand Down
2 changes: 1 addition & 1 deletion system/lib/embind/bind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ void _embind_register_bindings(InitFunc* f) {
init_funcs = f;
}

void _emval_coro_resume(val_awaiter* awaiter, EM_VAL result) {
void _emval_coro_resume(val::awaiter* awaiter, EM_VAL result) {
awaiter->resume_with(val::take_ownership(result));
}

Expand Down
20 changes: 20 additions & 0 deletions test/embind/test_val_coro.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,32 @@ val promise_sleep(int ms, int result = 0) {
return val::take_ownership(promise_sleep_impl(ms, result));
}

// Test that we can subclass and make custom awaitable types.
template <typename T>
class typed_promise: public val {
public:
typed_promise(val&& promise): val(std::move(promise)) {}

auto operator co_await() const {
struct typed_awaiter: public val::awaiter {
T await_resume() {
return val::awaiter::await_resume().template as<T>();
}
};

return typed_awaiter(*this);
}
};

val asyncCoro() {
// check that just sleeping works
co_await promise_sleep(1);
// check that sleeping and receiving value works
val v = co_await promise_sleep(1, 12);
assert(v.as<int>() == 12);
// check that awaiting a subclassed promise works and returns the correct type
int x = co_await typed_promise<int>(promise_sleep(1, 23));
assert(x == 23);
// check that returning value works (checked by JS in tests)
co_return 34;
}
Expand Down

0 comments on commit c017fc2

Please sign in to comment.