5#include <QSslConfiguration>
12#include <core/async/StopToken.hpp>
16#include <morph/attributes.hpp>
17#include <morph/core/backend.hpp>
19#include <morph/core/registry.hpp>
20#include <morph/core/wire.hpp>
24#include <unordered_map>
25#include <unordered_set>
111 ::morph::model::detail::ActionDispatcher& dispatcher = ::morph::model::detail::defaultDispatcher(),
112 ::morph::model::detail::ModelRegistryFactory& registry = ::morph::model::detail::defaultRegistry(),
114 std::optional<QSslConfiguration> tls = std::nullopt,
136 :
QtWebSocketBackend(std::move(serverUrl), ::morph::model::detail::defaultDispatcher(),
137 ::morph::model::detail::defaultRegistry(), std::move(tls), cfg) {}
149 :
QtWebSocketBackend(std::move(serverUrl), ::morph::model::detail::defaultDispatcher(),
150 ::morph::model::detail::defaultRegistry(),
209 const std::string& typeId,
210 std::function<std::unique_ptr<::morph::model::detail::IModelHolder>()> factory)
override;
257 void assignPrimary(::morph::exec::detail::ModelId mid,
const std::string& typeId,
258 std::string_view primary)
override;
333 ::morph::backend::detail::ActionCall call,
395 struct PendingControl {
397 std::function<void(const ::morph::wire::Envelope&)> reply;
399 std::function<void(
const std::exception_ptr&)> fail;
401 bool keepSession =
false;
411 void onTextMessage(
const QString& message);
416 void routeReply(const ::morph::wire::Envelope& env);
421 bool tryRouteExecuteReply(const ::morph::wire::Envelope& env);
426 bool tryRouteControlReply(const ::morph::wire::Envelope& env);
431 bool tryRouteDeregisterReply(const ::morph::wire::Envelope& env);
434 void scheduleReconnect();
437 void attemptReconnect();
455 void flushQueuedRegistrations();
466 void sendCancel(uint64_t target, const ::morph::session::Context& callerSession);
475 void checkThread(
char const* site)
const noexcept;
479 std::optional<QSslConfiguration> _tls;
483 QTimer _reconnectTimer;
488 std::chrono::milliseconds _currentReconnectDelay;
489 bool _connected{
false};
490 bool _everConnected{
false};
491 bool _shuttingDown{
false};
492 std::function<void()> _reconnectHandler;
493 ::morph::exec::IExecutor* _reconnectExec{
nullptr};
494 std::function<void()> _connectHandler;
495 std::function<void()> _disconnectHandler;
498 std::mutex _eventMutex;
499 std::shared_ptr<std::function<void(::morph::wire::EventBody)>> _eventHandler;
500 ::morph::exec::IExecutor* _eventExec{
nullptr};
501 ::morph::session::Context _session;
503 bool _negotiate{
false};
506 bool _cancelAdvertised{
false};
512 struct CancelOnStop {
513 std::shared_ptr<std::atomic<bool>> requested;
514 QTimer* wake{
nullptr};
515 void operator()() const noexcept;
518 struct PendingExecute {
519 std::shared_ptr<::morph::async::detail::CompletionState<std::shared_ptr<void>>> state;
520 std::function<std::shared_ptr<void>(std::string_view)> deserialize;
521 ::morph::exec::IExecutor* cbExec{
nullptr};
523 std::shared_ptr<::core::async::StopSource> stop;
525 ::morph::session::Context
session;
528 std::shared_ptr<std::atomic<bool>> stopRequested;
531 std::shared_ptr<::core::async::StopCallback<CancelOnStop>> watch;
533 uint64_t _nextCallId{0};
537 std::unordered_map<uint64_t, PendingExecute> _pending;
545 std::unordered_map<uint64_t, PendingControl> _pendingControl;
552 struct QueuedRegistration {
554 std::string contextKey;
555 ::morph::async::Completion<::morph::exec::detail::ModelId>::Promise promise;
557 std::vector<QueuedRegistration> _queuedRegistrations;
561 struct QueuedRequest {
562 ::morph::wire::Envelope envelope;
563 PendingControl pending;
565 std::vector<QueuedRequest> _queuedRequests;
570 std::unordered_set<uint64_t> _pendingDeregisters;
AnyHandle session(std::string const &name)
A session attribute, {"session": name}.
Producer-side handle paired with a Completion<T> by makeSettleable().
Move-only handle representing the eventual result of an asynchronous operation.
What a wire transport offers beside IBackend: one request, one reply, matched by callId.
IBackend implementation that communicates with a RemoteServer over WebSocket.
QtWebSocketBackend(QUrl serverUrl, ::morph::model::detail::ActionDispatcher &dispatcher=::morph::model::detail::defaultDispatcher(), ::morph::model::detail::ModelRegistryFactory ®istry=::morph::model::detail::defaultRegistry(), std::optional< QSslConfiguration > tls=std::nullopt, Config cfg=Config{})
Constructs the backend and opens a WebSocket connection to serverUrl.
::morph::async::Completion<::morph::wire::Envelope > request(::morph::wire::Envelope envelope, ::morph::exec::IExecutor &cbExec) override
Sends one request envelope of a kind the other verbs do not build (IEnvelopeTransport::request) and s...
::morph::async::Completion<::morph::wire::ProtocolNegotiationResult > negotiateProtocolVersion(::morph::exec::IExecutor &replyExec)
Sends a "hello" envelope to the server and classifies its reply.
void assignPrimary(::morph::exec::detail::ModelId mid, const std::string &typeId, std::string_view primary) override
Refuses: this backend promotes only through promoteModel.
QtWebSocketBackend(QUrl serverUrl, std::optional< QSslConfiguration > tls, Config cfg=Config{})
Constructs the backend without naming the dispatcher/registry pair.
::morph::async::Completion< std::shared_ptr< void > > execute(::morph::exec::detail::ModelId mid, ::morph::backend::detail::ActionCall call, ::morph::exec::IExecutor *cbExec) override
Sends an execute message and returns a Completion that resolves on reply.
QtWebSocketBackend(QUrl serverUrl, Config cfg)
Constructs the backend without naming the dispatcher/registry pair or TLS.
::morph::async::Completion< std::vector< std::string > > instances(const std::string &typeId, ::morph::exec::IExecutor &cbExec) override
Asks the server for the live shared primary keys of typeId.
::morph::exec::detail::ModelId registerModel(const std::string &typeId, std::function< std::unique_ptr<::morph::model::detail::IModelHolder >()> factory) override
Refuses: this backend registers only through bindModel.
std::vector< std::string > listInstances(const std::string &typeId) override
Refuses: this backend lists instances only through instances.
bool waitForConnected(int timeoutMs=5000)
Pumps the Qt event loop until the socket is connected or timeoutMs elapses.
void setDisconnectHandler(const std::function< void()> &handler) override
Installs a handler invoked whenever the socket drops.
void notifyBackendChanged() override
No-op — this backend holds no local model objects.
void setSession(::morph::session::Context session) override
Installs the session stamped onto every control envelope this backend subsequently builds (register,...
void setEventHandler(std::function< void(::morph::wire::EventBody)> handler, ::morph::exec::IExecutor *exec) override
Installs the handler each server-initiated event is posted to. See IBackend::setEventHandler.
void setReconnectHandler(std::function< void()> handler, ::morph::exec::IExecutor *exec) override
Installs the handler Bridge uses to re-register handlers after a reconnect.
void deregisterModel(::morph::exec::detail::ModelId mid) override
Sends a deregister message fire-and-forget (does not wait for a reply).
void cancelPending(const std::exception_ptr &exc) override
Rejects every in-flight request's Completion with exc — executes, binds (queued ones included),...
void setConnectHandler(const std::function< void()> &handler) override
Installs a handler invoked on every successful connect, including the first.
QtWebSocketBackendConfig Config
Alias for the reconnect configuration struct.
::morph::async::Completion<::morph::exec::detail::ModelId > promoteModel(::morph::backend::detail::PromoteRequest request, ::morph::exec::IExecutor &cbExec) override
Files a live server-side instance under a key, settled by the server's reply.
~QtWebSocketBackend() override
Closes the socket and cleans up pending operations.
::morph::async::Completion<::morph::exec::detail::ModelId > bindModel(::morph::backend::detail::BindRequest request, ::morph::exec::IExecutor &cbExec) override
Acquires a model instance over the wire, settled by the server's reply.
IEnvelopeTransport: a transport that sends a request envelope of a kind its backend verbs do not buil...
Abstract executor interface.
Reconnect tuning for QtWebSocketBackend.
std::chrono::milliseconds maxReconnectDelay
Upper bound on the exponential backoff between reconnect attempts.
std::chrono::milliseconds initialReconnectDelay
Delay before the first reconnect attempt after a disconnect.
double backoffMultiplier
Multiplier applied to the delay after each failed attempt.
bool reconnectEnabled
Whether to attempt automatic reconnect after an unsolicited disconnect.
Per-call session context carried through the bridge to the model.
JSON wire envelope used between any client and RemoteServer.
The decoded body of an event envelope.