Problem
With QtWebSocketBackendConfig::asyncRegistrationEnabled (required for a WASM client, whose main thread cannot spin the nested QEventLoop the blocking registration uses), a freshly constructed BridgeHandler is unbound until the server replies. BridgeHandler::execute() on a plain (non-keyed) action then fails fast with "handler not bound".
whenBound() (#60) makes this gateable, but every call site has to do it. In practice an embedder ends up writing the same wrapper around every handler it owns, because view models typically construct a handler and dispatch their first action in the same breath (e.g. on screen open):
template <typename Model>
class BoundHandler final : public morph::bridge::BridgeHandler<Model> {
using Base = morph::bridge::BridgeHandler<Model>;
public:
using Base::Base;
template <typename Action>
auto execute(Action action) -> morph::async::Completion<typename morph::model::ActionTraits<Action>::Result> {
if (Base::isBound()) return Base::execute(std::move(action));
// makeSettleable + whenBound().then(Base::execute(...)) + a liveness token
}
};
The payload- and result-keyed branches of execute() already defer through attachHandlerAsync / ensureBoundAsync, so plain actions are the only route without a deferred path.
Proposal
An opt-in way for plain actions to wait for an in-flight registration instead of failing fast, e.g. one of:
- a
BridgeHandler constructor option / policy (UnboundPolicy::Defer vs the current FailFast), or
- a separate
executeWhenBound(action) on BridgeHandler.
Semantics: if isBound(), dispatch as today; if a registration is in flight, chain on whenBound() and dispatch on success; reject with the registration error (or a "not bound" error when whenBound() resolves false); drop the continuation if the handler is destroyed first.
The default stays fail-fast, so the documented trade in asyncRegistrationEnabled is unchanged for anyone relying on it.
Why in morph
The wrapper needs a liveness guard against the handler being destroyed before the bind settles, which morph can provide more cheaply and correctly from inside BridgeHandler (it already tracks binding lifetime) than each embedder can from outside.
Problem
With
QtWebSocketBackendConfig::asyncRegistrationEnabled(required for a WASM client, whose main thread cannot spin the nestedQEventLoopthe blocking registration uses), a freshly constructedBridgeHandleris unbound until the server replies.BridgeHandler::execute()on a plain (non-keyed) action then fails fast with"handler not bound".whenBound()(#60) makes this gateable, but every call site has to do it. In practice an embedder ends up writing the same wrapper around every handler it owns, because view models typically construct a handler and dispatch their first action in the same breath (e.g. on screen open):The payload- and result-keyed branches of
execute()already defer throughattachHandlerAsync/ensureBoundAsync, so plain actions are the only route without a deferred path.Proposal
An opt-in way for plain actions to wait for an in-flight registration instead of failing fast, e.g. one of:
BridgeHandlerconstructor option / policy (UnboundPolicy::Defervs the currentFailFast), orexecuteWhenBound(action)onBridgeHandler.Semantics: if
isBound(), dispatch as today; if a registration is in flight, chain onwhenBound()and dispatch on success; reject with the registration error (or a "not bound" error whenwhenBound()resolvesfalse); drop the continuation if the handler is destroyed first.The default stays fail-fast, so the documented trade in
asyncRegistrationEnabledis unchanged for anyone relying on it.Why in morph
The wrapper needs a liveness guard against the handler being destroyed before the bind settles, which morph can provide more cheaply and correctly from inside
BridgeHandler(it already tracks binding lifetime) than each embedder can from outside.