OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
qml_udp_inbound_socket.hpp
1#pragma once
2#include <ossia/network/context.hpp>
3#include <ossia/network/sockets/udp_socket.hpp>
4
5#include <ossia-qt/protocols/utils.hpp>
6
7#include <QJSValue>
8#include <QObject>
9#include <QQmlEngine>
10
11#include <nano_observer.hpp>
12
13#include <verdigris>
14
15namespace ossia::qt
16{
17
18// State extracted so both qml_udp_sender and qml_udp_inbound_socket can reference it
19struct qml_udp_inbound_state
20{
21 ossia::net::udp_receive_socket socket;
22 std::atomic_bool alive{true};
23 ossia::net::encoding enc{ossia::net::encoding::none};
24
25 qml_udp_inbound_state(
26 const ossia::net::inbound_socket_configuration& conf,
27 boost::asio::io_context& ctx,
28 ossia::net::encoding e = ossia::net::encoding::none)
29 : socket{conf, ctx}
30 {
31 enc = e;
32 }
33};
34
35class qml_udp_inbound_socket;
36
37// Cached sender object — reused across onMessage calls (no per-message allocation).
38// Updated with the sender's endpoint before each onMessage dispatch.
39class qml_udp_sender : public QObject
40{
41 W_OBJECT(qml_udp_sender)
42public:
43 explicit qml_udp_sender(QObject* parent = nullptr)
44 : QObject(parent)
45 {
46 }
47
48 QString host() const { return m_host; }
49 W_PROPERTY(QString, host READ host)
50
51 int port() const { return m_port; }
52 W_PROPERTY(int, port READ port)
53
54 void reply(QByteArray data)
55 {
56 if(!m_state || !m_state->alive)
57 return;
58 auto st = m_state;
59 if(st->enc != ossia::net::encoding::none)
60 data = apply_encoding(st->enc, data);
61 auto ep = m_endpoint;
62 boost::asio::dispatch(st->socket.m_context, [st, ep, data] {
63 if(st->alive)
64 {
65 boost::system::error_code ec;
66 st->socket.m_socket.send_to(
67 boost::asio::const_buffer(data.data(), data.size()), ep, 0, ec);
68 }
69 });
70 }
71 W_SLOT(reply)
72
73private:
74 friend class qml_udp_inbound_socket;
75 boost::asio::ip::udp::endpoint m_endpoint;
76 std::shared_ptr<qml_udp_inbound_state> m_state;
77 QString m_host;
78 int m_port = 0;
79};
80
81class qml_udp_inbound_socket
82 : public QObject
83 , public Nano::Observer
84{
85 W_OBJECT(qml_udp_inbound_socket)
86public:
87 using state = qml_udp_inbound_state;
88
89 qml_udp_inbound_socket()
90 {
91 QQmlEngine::setObjectOwnership(&m_sender, QQmlEngine::CppOwnership);
92 }
93
94 ~qml_udp_inbound_socket()
95 {
96 if(m_state)
97 {
98 m_state->alive = false;
99 close();
100 }
101 }
102
103 bool isOpen() const noexcept { return m_state != nullptr; }
104
105 void open(
106 const ossia::net::inbound_socket_configuration& conf,
107 boost::asio::io_context& ctx,
108 ossia::net::encoding e = ossia::net::encoding::none)
109 {
110 m_state = std::make_shared<state>(conf, ctx, e);
111
112 if(onClose.isCallable())
113 m_state->socket.on_close.connect<&qml_udp_inbound_socket::on_close>(*this);
114
115 m_state->socket.open();
116 if(onOpen.isCallable())
117 onOpen.call({qjsEngine(this)->newQObject(this)});
118
119 auto st = m_state;
120 auto self = QPointer{this};
121 st->socket.receive([st, self](const char* data, std::size_t sz) {
122 if(!st->alive)
123 return;
124 // Capture sender endpoint on asio thread (valid right now,
125 // m_endpoint was populated by async_receive_from)
126 auto sender_ep = st->socket.m_endpoint;
127 ossia::qt::run_async(
128 self.get(),
129 [self, st, arg = apply_decoding(st->enc, data, sz), sender_ep] {
130 if(!self.get())
131 return;
132
133 // Update cached sender object (Qt thread, no race)
134 // Only rebuild host string if endpoint changed
135 if(self->m_sender.m_endpoint != sender_ep)
136 {
137 self->m_sender.m_endpoint = sender_ep;
138 self->m_sender.m_host
139 = QString::fromStdString(sender_ep.address().to_string());
140 self->m_sender.m_port = sender_ep.port();
141 }
142 self->m_sender.m_state = st;
143
144 if(self->onMessage.isCallable())
145 {
146 auto engine = qjsEngine(self.get());
147 if(engine)
148 self->onMessage.call(
149 {engine->toScriptValue(arg), engine->newQObject(&self->m_sender)});
150 }
151 },
152 Qt::AutoConnection);
153 });
154 }
155
156 void on_close()
157 {
158 if(!m_state || !m_state->alive)
159 return;
160 ossia::qt::run_async(this, [=, this] { onClose.call(); }, Qt::AutoConnection);
161 }
162
163 void close()
164 {
165 if(!m_state)
166 return;
167 if(!m_state->socket.m_socket.is_open())
168 return;
169 auto st = m_state;
170 boost::asio::post(st->socket.m_context, [st] {
171 try
172 {
173 st->socket.m_socket.shutdown(boost::asio::ip::udp::socket::shutdown_both);
174 }
175 catch(...)
176 {
177 }
178 st->socket.m_socket.close();
179 st->socket.on_close();
180 });
181 }
182 W_SLOT(close)
183
184 QJSValue onOpen;
185 QJSValue onClose;
186 QJSValue onError;
187 QJSValue onMessage;
188
189private:
190 std::shared_ptr<state> m_state;
191 qml_udp_sender m_sender{this};
192};
193
194}
Definition qml_device.cpp:43