Summary
Bridge registers a model by calling IBackend::registerModel, which is synchronous — it
must have the server-assigned ModelId before it returns. QtWebSocketBackend implements
that by parking a nested QEventLoop until the reply arrives (sendSync).
On a WASM main thread Qt refuses to do this. The nested loop trips
WaitForMoreEvents is not supported on the main thread without asyncify
and aborts the module. So a Qt/QML client compiled to WebAssembly cannot register a single
model against a remote backend — the first registerModel kills the page.
Why the existing escape hatches don't cover it
waitForConnected() has the same problem: blocking the browser event loop hangs the page.
- Building with asyncify is a heavy, whole-program cost (size and speed) to work around one
blocking call on one code path.
- Deferring registration until "later" doesn't help — the call is synchronous whenever it runs.
Suggested fix
Give IBackend an optional asynchronous registration path, defaulting to "unsupported" so
every current backend is unaffected:
/// Returns false if this backend has no async path; Bridge then uses registerModel().
virtual bool registerModelAsync(
const std::string& typeId,
std::function<std::unique_ptr<::morph::model::detail::IModelHolder>()> factory,
const std::string& contextKey,
std::function<void(::morph::exec::detail::ModelId)> onRegistered,
std::function<void(const std::string&)> onError)
{
return false; // default: no async path here
}
A socket backend overrides it to send the register envelope and settle later. No protocol
change is needed — the server already echoes callId on register replies, so the reply can
be matched through the same pending-call map that execute uses.
Bridge then prefers the async path when the backend offers one and falls back to the
synchronous call otherwise, which keeps every existing embedder on today's behaviour.
Exactly one of onRegistered / onError should be invoked, on the backend's own thread,
unless the backend is destroyed first.
I have this working locally against a WASM client and am happy to open a PR.
Summary
Bridgeregisters a model by callingIBackend::registerModel, which is synchronous — itmust have the server-assigned
ModelIdbefore it returns.QtWebSocketBackendimplementsthat by parking a nested
QEventLoopuntil the reply arrives (sendSync).On a WASM main thread Qt refuses to do this. The nested loop trips
and aborts the module. So a Qt/QML client compiled to WebAssembly cannot register a single
model against a remote backend — the first
registerModelkills the page.Why the existing escape hatches don't cover it
waitForConnected()has the same problem: blocking the browser event loop hangs the page.blocking call on one code path.
Suggested fix
Give
IBackendan optional asynchronous registration path, defaulting to "unsupported" soevery current backend is unaffected:
A socket backend overrides it to send the
registerenvelope and settle later. No protocolchange is needed — the server already echoes
callIdon register replies, so the reply canbe matched through the same pending-call map that
executeuses.Bridgethen prefers the async path when the backend offers one and falls back to thesynchronous call otherwise, which keeps every existing embedder on today's behaviour.
Exactly one of
onRegistered/onErrorshould be invoked, on the backend's own thread,unless the backend is destroyed first.
I have this working locally against a WASM client and am happy to open a PR.