-
Hello! Thanks everyone for emscripten, it's a crazy/amazing thing. I'm using an existing coroutine framework extensively, so I'm trying to avoid propagating I've looked through @davits and @RReverser 's work (particularly in this issue/PR #23653 here and tried to use the coro tests here. I'm stuck on creating this " I'd much appreciate any help, thanks! using namespace emscripten;
EM_JS(EM_VAL, jsDoAsyncStuffImpl, (), {
const p = new Promise((resolve) => { setTimeout(async () => { resolve(49); }, 10) });
const h = Emval.toHandle(p);
return h;
});
val jsDoAsyncStuff () {
return val::take_ownership(jsDoAsyncStuffImpl());
}
// Based on https://github.com/emscripten-core/emscripten/blob/main/test/embind/test_val_coro.cpp#L39
template <typename T>
struct GluePromise : public val {
GluePromise (val&& promise) :
val(std::move(promise))
{}
struct GlueAwaiter : public val::awaiter {
void await_suspend (std::coroutine_handle<void> handle) {
// ??? How to call val::awaiter::await_suspend(std::coroutine_handle<val::promise_type>) ???
}
T await_resume () {
return val::awaiter::await_resume().template as<T>();
}
};
auto operator co_await() const {
return GlueAwaiter(*this);
}
};
concurrencpp::result<int> doSomething () {
int valueIsFortyNine = co_await GluePromise{jsDoAsyncStuff()};
// In reality this returns to a load of deep business logic using concurrencpp:
co_return valueIsFortyNine;
}
val exportedFnCalledByJs () {
// In reality this calls in to a load of business logic using concurrencpp:
int result = co_await doSomething();
co_return int;
}
EMSCRIPTEN_BINDINGS (wasm) {
function("exportedFnCalledByJs", &exportedFnCalledByJs);
} |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 1 reply
-
Ok this works: It's basically a reimplementation of Probably has issues I only just got it working, exception path not tried yet, but the principle stands to allow interop with other coroutine frameworks. struct GlueReceiver {
enum class Status {
PENDING = 0x0,
RESOLVED,
REJECTED,
};
Status status{Status::PENDING};
std::optional<std::coroutine_handle<>> handle{};
val result{val::undefined()};
void setHandle (
std::coroutine_handle<> handle
) {
this->handle = handle;
switch (this->status) {
case Status::RESOLVED: {
this->handle->resume();
} break;
case Status::REJECTED: {
this->handle->destroy();
} break;
case Status::PENDING:
default: {
} break;
}
}
void reject (
val error
) {
this->result = error;
this->status = Status::REJECTED;
if (this->handle) {
this->handle->destroy();
}
}
void resolve (
val value
) {
this->result = value;
this->status = Status::RESOLVED;
if (this->handle) {
this->handle->resume();
}
}
};
struct GlueAwaiter {
val receiver;
GlueAwaiter (
const val& jsPromise
) :
receiver{GlueReceiver{}}
{
EM_ASM({
const promise = Emval.toValue($0);
const receiver = Emval.toValue($1);
promise
.then((v) => { receiver.resolve(v); })
.catch((e) => { receiver.reject(e); })
;
}, jsPromise.as_handle(), this->receiver.as_handle());
}
GlueReceiver& getReceiver () {
return this->receiver.template as<GlueReceiver&>();
}
bool await_ready () {
return false;
}
void await_suspend (
std::coroutine_handle<> handle
) {
this->getReceiver().setHandle(handle);
}
val await_resume () {
return this->getReceiver().result;
}
};
struct GluePromise {
using promise_type = val::promise_type;
val jsPromise;
GluePromise (
val&& tJsPromise
) :
jsPromise{std::move(tJsPromise)}
{}
GlueAwaiter operator co_await() const {
return {this->jsPromise};
}
};
EMSCRIPTEN_BINDINGS(wasm_helpers) {
class_<GlueReceiver>("GlueReceiver")
.function("reject", &GlueReceiver::reject)
.function("resolve", &GlueReceiver::resolve)
;
} |
Beta Was this translation helpful? Give feedback.
-
So does everyone else 😅 I definitely designed val's awaiter to be somewhat overly restricted at first, only accepting JS promise - in - JS promise await, as I didn't want to try and do any premature abstraction without concrete usecases, but if your solution works well for integrating with 3rd-party libraries, I'm happy to review a PR. |
Beta Was this translation helpful? Give feedback.
Ok this works: It's basically a reimplementation of
val::awaiter
+emval_coro_suspend
, without being restricted to eitherval::promise_type
orval::awaiter
respectively. It binds itself to the promise that you're going to await just the same. No doubt it is less terse/efficient but I'm pretty likely to forget how this works so verbosity it is!Probably has issues I only just got it working, exception path not tried yet, but the principle stands to allow interop with other coroutine frameworks.