OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
qml_tcp_inbound_socket.hpp
1#pragma once
2#include <ossia/detail/variant.hpp>
3#include <ossia/network/context.hpp>
4#include <ossia/network/sockets/configuration.hpp>
5#include <ossia/network/sockets/line_framing.hpp>
6#include <ossia/network/sockets/no_framing.hpp>
7#include <ossia/network/sockets/size_prefix_framing.hpp>
8#include <ossia/network/sockets/slip_framing.hpp>
9#include <ossia/network/sockets/tcp_socket.hpp>
10
11#include <ossia-qt/protocols/utils.hpp>
12
13#include <QJSValue>
14#include <QObject>
15#include <QQmlEngine>
16
17#include <nano_observer.hpp>
18
19#include <verdigris>
20
21namespace ossia::qt
22{
23class qml_tcp_connection
24 : public QObject
25 , public Nano::Observer
26{
27 W_OBJECT(qml_tcp_connection)
28public:
29 using socket_t = boost::asio::ip::tcp::socket;
30 using decoder_type = ossia::slow_variant<
31 ossia::net::no_framing::decoder<socket_t>,
32 ossia::net::slip_decoder<socket_t>,
33 ossia::net::size_prefix_decoder<socket_t>,
34 ossia::net::line_framing_decoder<socket_t>>;
35
36 struct state
37 {
38 boost::asio::io_context& context;
39 ossia::net::tcp_listener listener;
40 std::atomic_bool alive{true};
41 ossia::net::framing framing{ossia::net::framing::none};
42 char line_delimiter[8] = {};
43 decoder_type decoder;
44
45 state(
46 ossia::net::tcp_listener l, boost::asio::io_context& ctx,
47 ossia::net::framing f = ossia::net::framing::none,
48 const std::string& delim = {})
49 : context{ctx}
50 , listener{std::move(l)}
51 , decoder{ossia::in_place_index<0>, listener.m_socket}
52 {
53 framing = f;
54 if(!delim.empty())
55 {
56 auto sz = std::min(delim.size(), (size_t)7);
57 std::copy_n(delim.begin(), sz, line_delimiter);
58 }
59
60 switch(f)
61 {
62 default:
63 case ossia::net::framing::none:
64 break;
65 case ossia::net::framing::slip:
66 decoder.template emplace<1>(listener.m_socket);
67 break;
68 case ossia::net::framing::size_prefix:
69 decoder.template emplace<2>(listener.m_socket);
70 break;
71 case ossia::net::framing::line_delimiter:
72 decoder.template emplace<3>(listener.m_socket);
73 {
74 auto& dec = ossia::get<3>(decoder);
75 std::copy_n(line_delimiter, 8, dec.delimiter);
76 }
77 break;
78 }
79 }
80
81 void write_encoded(const char* data, std::size_t sz)
82 {
83 switch(framing)
84 {
85 default:
86 case ossia::net::framing::none:
87 listener.write(boost::asio::const_buffer(data, sz));
88 break;
89 case ossia::net::framing::slip: {
90 ossia::net::slip_encoder<socket_t> enc{listener.m_socket};
91 enc.write(data, sz);
92 break;
93 }
94 case ossia::net::framing::size_prefix: {
95 ossia::net::size_prefix_encoder<socket_t> enc{listener.m_socket};
96 enc.write(data, sz);
97 break;
98 }
99 case ossia::net::framing::line_delimiter: {
100 ossia::net::line_framing_encoder<socket_t> enc{listener.m_socket};
101 std::copy_n(line_delimiter, 8, enc.delimiter);
102 enc.write(data, sz);
103 break;
104 }
105 }
106 }
107 };
108
109 struct receive_callback
110 {
111 std::shared_ptr<state> st;
112 QPointer<qml_tcp_connection> self;
113
114 void operator()(const unsigned char* data, std::size_t sz) const
115 {
116 if(!st->alive)
117 return;
118 auto buf = QByteArray((const char*)data, sz);
119 ossia::qt::run_async(
120 self.get(),
121 [self = self, buf] {
122 if(!self.get())
123 return;
124 if(self->onBytes.isCallable())
125 {
126 auto engine = qjsEngine(self.get());
127 if(engine)
128 self->onBytes.call({engine->toScriptValue(buf)});
129 }
130 },
131 Qt::AutoConnection);
132 }
133
134 bool validate_stream(boost::system::error_code ec) const
135 {
136 if(ec == boost::asio::error::operation_aborted)
137 return false;
138 if(ec == boost::asio::error::eof)
139 {
140 ossia::qt::run_async(
141 self.get(),
142 [self = self] {
143 if(self && self->onClose.isCallable())
144 self->onClose.call();
145 },
146 Qt::AutoConnection);
147 return false;
148 }
149 return true;
150 }
151 };
152
153 explicit qml_tcp_connection(
154 ossia::net::tcp_listener listener, boost::asio::io_context& ctx,
155 ossia::net::framing f = ossia::net::framing::none,
156 const std::string& delim = {})
157 : m_state{std::make_shared<state>(std::move(listener), ctx, f, delim)}
158 {
159 }
160
161 ~qml_tcp_connection()
162 {
163 if(m_state)
164 {
165 m_state->alive = false;
166 close({});
167 }
168 }
169
170 bool isOpen() const noexcept { return m_state && m_state->alive; }
171
172 inline boost::asio::io_context& context() noexcept { return m_state->context; }
173
174 void write(QByteArray buffer)
175 {
176 if(!m_state)
177 return;
178 auto st = m_state;
179 boost::asio::dispatch(st->context, [st, buffer] {
180 if(st->alive)
181 st->write_encoded(buffer.data(), buffer.size());
182 });
183 }
184 W_SLOT(write)
185
186 void close(QByteArray)
187 {
188 if(!m_state)
189 return;
190 auto st = m_state;
191 boost::asio::dispatch(st->context, [st] { st->listener.close(); });
192 }
193 W_SLOT(close)
194
195 void receive(QJSValue v)
196 {
197 onBytes = v;
198 auto st = m_state;
199 auto self = QPointer{this};
200 ossia::visit(
201 [cb = receive_callback{st, self}](auto& decoder) mutable {
202 decoder.receive(std::move(cb));
203 },
204 st->decoder);
205 }
206 W_SLOT(receive)
207
208 QJSValue onBytes;
209 QJSValue onClose;
210 W_PROPERTY(QJSValue, onClose W_MEMBER onClose);
211
212private:
213 std::shared_ptr<state> m_state;
214};
215
216class qml_tcp_inbound_socket
217 : public QObject
218 , public Nano::Observer
219{
220 W_OBJECT(qml_tcp_inbound_socket)
221public:
222 struct state
223 {
224 ossia::net::tcp_server server;
225 std::atomic_bool alive{true};
226 std::atomic_bool open{false};
227 ossia::net::framing framing{ossia::net::framing::none};
228 std::string framing_delimiter;
229
230 state(
231 const ossia::net::inbound_socket_configuration& conf,
232 boost::asio::io_context& ctx,
233 ossia::net::framing f = ossia::net::framing::none,
234 std::string delim = {})
235 : server{conf, ctx}
236 , framing{f}
237 , framing_delimiter{std::move(delim)}
238 {
239 }
240 };
241
242 qml_tcp_inbound_socket() { }
243
244 ~qml_tcp_inbound_socket()
245 {
246 if(m_state)
247 {
248 m_state->alive = false;
249 close();
250 }
251 }
252
253 bool isOpen() const noexcept { return m_state && m_state->open; }
254
255 void open(
256 const ossia::net::inbound_socket_configuration& conf,
257 boost::asio::io_context& ctx,
258 ossia::net::framing f = ossia::net::framing::none,
259 const std::string& delim = {})
260 {
261 m_state = std::make_shared<state>(conf, ctx, f, delim);
262 m_state->open = true;
263 accept_impl(m_state, QPointer{this});
264 if(onOpen.isCallable())
265 onOpen.call({qjsEngine(this)->newQObject(this)});
266 }
267
268 void close()
269 {
270 if(!m_state)
271 return;
272 m_state->open = false;
273 m_state->server.m_acceptor.close();
274 if(onClose.isCallable())
275 onClose.call();
276 }
277 W_SLOT(close)
278
279 void on_close()
280 {
281 if(!m_state)
282 return;
283 m_state->open = false;
284 ossia::qt::run_async(this, [=, this] { onClose.call(); }, Qt::AutoConnection);
285 }
286
287 QJSValue onOpen;
288 QJSValue onClose;
289 QJSValue onError;
290 QJSValue onConnection;
291
292private:
293 static void accept_impl(
294 std::shared_ptr<state> st, QPointer<qml_tcp_inbound_socket> self)
295 {
296 st->server.m_acceptor.async_accept(
297 [self, st](
298 boost::system::error_code ec, ossia::net::tcp_server::proto::socket socket) {
299 if(!st->alive || !st->open)
300 return;
301 if(!ec)
302 {
303 ossia::qt::run_async(
304 self.get(),
305 [self, st, socket = std::move(socket)]() mutable {
306 if(!self.get())
307 return;
308 auto conn = new qml_tcp_connection{
309 ossia::net::tcp_listener{std::move(socket)}, st->server.m_context,
310 st->framing, st->framing_delimiter};
311
312 if(self->onConnection.isCallable())
313 {
314 self->onConnection.call({qjsEngine(self.get())->newQObject(conn)});
315 }
316 },
317 Qt::AutoConnection);
318 accept_impl(st, self);
319 }
320 });
321 }
322
323 std::shared_ptr<state> m_state;
324};
325
326}
Definition qml_device.cpp:43
Definition git_info.h:7