OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
qml_unix_outbound_socket.hpp
1#pragma once
2#include <ossia/network/context.hpp>
3#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
4#include <ossia/detail/variant.hpp>
5#include <ossia/network/sockets/configuration.hpp>
6#include <ossia/network/sockets/cobs_framing.hpp>
7#include <ossia/network/sockets/fixed_length_framing.hpp>
8#include <ossia/network/sockets/line_framing.hpp>
9#include <ossia/network/sockets/no_framing.hpp>
10#include <ossia/network/sockets/size_prefix_framing.hpp>
11#include <ossia/network/sockets/slip_framing.hpp>
12#include <ossia/network/sockets/stx_etx_framing.hpp>
13#include <ossia/network/sockets/unix_socket.hpp>
14#include <ossia/network/sockets/var_size_prefix_framing.hpp>
15
16#include <ossia-qt/protocols/utils.hpp>
17
18#include <QJSValue>
19#include <QObject>
20#include <QQmlEngine>
21
22#include <nano_observer.hpp>
23
24#include <verdigris>
25
26namespace ossia::qt
27{
28class qml_unix_datagram_outbound_socket
29 : public QObject
30 , public Nano::Observer
31 , public protocols_sender
32{
33 W_OBJECT(qml_unix_datagram_outbound_socket)
34public:
35 struct state
36 {
37 ossia::net::unix_datagram_socket socket;
38 std::atomic_bool alive{true};
39 ossia::net::encoding enc{ossia::net::encoding::none};
40
41 state(
42 const ossia::net::fd_configuration& conf, boost::asio::io_context& ctx,
43 ossia::net::encoding e = ossia::net::encoding::none)
44 : socket{conf, ctx}
45 {
46 enc = e;
47 }
48 };
49
50 ossia::net::unix_datagram_socket* socket = nullptr;
51
52 qml_unix_datagram_outbound_socket() { }
53
54 ~qml_unix_datagram_outbound_socket()
55 {
56 if(m_state)
57 {
58 m_state->alive = false;
59 close();
60 }
61 }
62
63 bool isOpen() const noexcept { return m_state != nullptr; }
64
65 void open(
66 const ossia::net::fd_configuration& conf, boost::asio::io_context& ctx,
67 ossia::net::encoding e = ossia::net::encoding::none)
68 {
69 m_state = std::make_shared<state>(conf, ctx, e);
70 socket = &m_state->socket;
71
72 if(onClose.isCallable())
73 m_state->socket.on_close.connect<&qml_unix_datagram_outbound_socket::on_close>(this);
74
75 m_state->socket.connect();
76
77 if(onOpen.isCallable())
78 onOpen.call({qjsEngine(this)->newQObject(this)});
79 }
80
81 void close()
82 {
83 if(!m_state)
84 return;
85 if(!m_state->socket.m_socket.is_open())
86 return;
87 auto st = m_state;
88 boost::asio::post(st->socket.m_context, [st] {
89 try
90 {
91 st->socket.m_socket.shutdown(boost::asio::ip::udp::socket::shutdown_both);
92 }
93 catch(...)
94 {
95 }
96 st->socket.m_socket.close();
97 st->socket.on_close();
98 });
99 }
100 W_SLOT(close)
101
102 void on_close()
103 {
104 if(!m_state || !m_state->alive)
105 return;
106 ossia::qt::run_async(this, [=, this] { onClose.call(); }, Qt::AutoConnection);
107 }
108
109 void write(QByteArray buffer)
110 {
111 if(!m_state)
112 return;
113 auto st = m_state;
114 if(st->enc != ossia::net::encoding::none)
115 buffer = apply_encoding(st->enc, buffer);
116 boost::asio::dispatch(st->socket.m_context, [st, buffer = std::move(buffer)] {
117 if(st->alive)
118 st->socket.write(buffer.data(), buffer.size());
119 });
120 }
121 W_SLOT(write)
122
123 void osc(QByteArray address, QJSValueList values)
124 {
125 if(socket)
126 this->send_osc(address, values);
127 }
128 W_SLOT(osc)
129
130 QJSValue onOpen;
131 QJSValue onClose;
132 QJSValue onError;
133
134private:
135 std::shared_ptr<state> m_state;
136};
137
138class qml_unix_stream_outbound_socket
139 : public QObject
140 , public Nano::Observer
141{
142 W_OBJECT(qml_unix_stream_outbound_socket)
143public:
144 using socket_t = boost::asio::local::stream_protocol::socket;
145 using decoder_type = ossia::slow_variant<
146 ossia::net::no_framing::decoder<socket_t>,
147 ossia::net::slip_decoder<socket_t>,
148 ossia::net::size_prefix_decoder<socket_t>,
149 ossia::net::line_framing_decoder<socket_t>,
150 ossia::net::cobs_decoder<socket_t>,
151 ossia::net::stx_etx_framing::decoder<socket_t>,
152 ossia::net::size_prefix_1byte_framing::decoder<socket_t>,
153 ossia::net::size_prefix_2byte_be_framing::decoder<socket_t>,
154 ossia::net::size_prefix_2byte_le_framing::decoder<socket_t>,
155 ossia::net::size_prefix_4byte_le_framing::decoder<socket_t>,
156 ossia::net::fixed_length_decoder<socket_t>>;
157
158 struct state
159 {
160 ossia::net::unix_stream_client socket;
161 std::atomic_bool alive{true};
162 ossia::net::framing framing{ossia::net::framing::none};
163 ossia::net::encoding enc{ossia::net::encoding::none};
164 char line_delimiter[8] = {};
165 decoder_type decoder;
166
167 state(
168 const ossia::net::fd_configuration& conf, boost::asio::io_context& ctx,
169 ossia::net::framing f = ossia::net::framing::none,
170 const std::string& delim = {},
171 ossia::net::encoding e = ossia::net::encoding::none)
172 : socket{conf, ctx}
173 , decoder{ossia::in_place_index<0>, socket.m_socket}
174 {
175 framing = f;
176 enc = e;
177 if(!delim.empty())
178 {
179 auto sz = std::min(delim.size(), (size_t)7);
180 std::copy_n(delim.begin(), sz, line_delimiter);
181 }
182 switch(f)
183 {
184 default:
185 case ossia::net::framing::none:
186 break;
187 case ossia::net::framing::slip:
188 decoder.template emplace<1>(socket.m_socket);
189 break;
190 case ossia::net::framing::size_prefix:
191 decoder.template emplace<2>(socket.m_socket);
192 break;
193 case ossia::net::framing::line_delimiter:
194 decoder.template emplace<3>(socket.m_socket);
195 {
196 auto& dec = ossia::get<3>(decoder);
197 std::copy_n(line_delimiter, 8, dec.delimiter);
198 }
199 break;
200 case ossia::net::framing::cobs:
201 decoder.template emplace<4>(socket.m_socket);
202 break;
203 case ossia::net::framing::stx_etx:
204 decoder.template emplace<5>(socket.m_socket);
205 break;
206 case ossia::net::framing::size_prefix_1byte:
207 decoder.template emplace<6>(socket.m_socket);
208 break;
209 case ossia::net::framing::size_prefix_2byte_be:
210 decoder.template emplace<7>(socket.m_socket);
211 break;
212 case ossia::net::framing::size_prefix_2byte_le:
213 decoder.template emplace<8>(socket.m_socket);
214 break;
215 case ossia::net::framing::size_prefix_4byte_le:
216 decoder.template emplace<9>(socket.m_socket);
217 break;
218 case ossia::net::framing::fixed_length:
219 decoder.template emplace<10>(socket.m_socket);
220 if(!delim.empty())
221 ossia::get<10>(decoder).frame_size = std::stoul(delim);
222 break;
223 }
224 }
225
226 void write_encoded(const char* data, std::size_t sz)
227 {
228 switch(framing)
229 {
230 default:
231 case ossia::net::framing::none:
232 socket.write(data, sz);
233 break;
234 case ossia::net::framing::slip:
235 ossia::net::slip_encoder<socket_t>{socket.m_socket}.write(data, sz);
236 break;
237 case ossia::net::framing::size_prefix:
238 ossia::net::size_prefix_encoder<socket_t>{socket.m_socket}.write(data, sz);
239 break;
240 case ossia::net::framing::line_delimiter: {
241 ossia::net::line_framing_encoder<socket_t> enc{socket.m_socket};
242 std::copy_n(line_delimiter, 8, enc.delimiter);
243 enc.write(data, sz);
244 break;
245 }
246 case ossia::net::framing::cobs:
247 ossia::net::cobs_encoder<socket_t>{socket.m_socket}.write(data, sz);
248 break;
249 case ossia::net::framing::stx_etx:
250 ossia::net::stx_etx_framing::encoder<socket_t>{socket.m_socket}.write(data, sz);
251 break;
252 case ossia::net::framing::size_prefix_1byte:
253 ossia::net::size_prefix_1byte_framing::encoder<socket_t>{socket.m_socket}.write(
254 data, sz);
255 break;
256 case ossia::net::framing::size_prefix_2byte_be:
257 ossia::net::size_prefix_2byte_be_framing::encoder<socket_t>{socket.m_socket}
258 .write(data, sz);
259 break;
260 case ossia::net::framing::size_prefix_2byte_le:
261 ossia::net::size_prefix_2byte_le_framing::encoder<socket_t>{socket.m_socket}
262 .write(data, sz);
263 break;
264 case ossia::net::framing::size_prefix_4byte_le:
265 ossia::net::size_prefix_4byte_le_framing::encoder<socket_t>{socket.m_socket}
266 .write(data, sz);
267 break;
268 case ossia::net::framing::fixed_length:
269 ossia::net::fixed_length_encoder<socket_t>{socket.m_socket}.write(data, sz);
270 break;
271 }
272 }
273 };
274
275 struct receive_callback
276 {
277 std::shared_ptr<state> st;
278 QPointer<qml_unix_stream_outbound_socket> self;
279 QJSValue* target;
280
281 void operator()(const unsigned char* data, std::size_t sz) const
282 {
283 if(!st->alive)
284 return;
285 auto buf = apply_decoding(st->enc, data, sz);
286 auto cb = target;
287 ossia::qt::run_async(
288 self.get(),
289 [self = self, buf, cb] {
290 if(!self.get())
291 return;
292 if(cb->isCallable())
293 {
294 auto engine = qjsEngine(self.get());
295 if(engine)
296 cb->call({engine->toScriptValue(buf)});
297 }
298 },
299 Qt::AutoConnection);
300 }
301
302 bool validate_stream(boost::system::error_code ec) const
303 {
304 if(ec == boost::asio::error::operation_aborted)
305 return false;
306 if(ec == boost::asio::error::eof)
307 return false;
308 return true;
309 }
310 };
311
312 qml_unix_stream_outbound_socket() { }
313
314 ~qml_unix_stream_outbound_socket()
315 {
316 if(m_state)
317 {
318 m_state->alive = false;
319 close();
320 }
321 }
322
323 bool isOpen() const noexcept { return m_state != nullptr; }
324
325 void open(
326 const ossia::net::fd_configuration& conf, boost::asio::io_context& ctx,
327 ossia::net::framing f = ossia::net::framing::none,
328 const std::string& delim = {},
329 ossia::net::encoding e = ossia::net::encoding::none)
330 {
331 m_state = std::make_shared<state>(conf, ctx, f, delim, e);
332
333 try
334 {
335 if(onOpen.isCallable())
336 m_state->socket.on_open.connect<&qml_unix_stream_outbound_socket::on_open>(this);
337 if(onClose.isCallable())
338 m_state->socket.on_close.connect<&qml_unix_stream_outbound_socket::on_close>(
339 this);
340 if(onError.isCallable())
341 m_state->socket.on_fail.connect<&qml_unix_stream_outbound_socket::on_fail>(this);
342 m_state->socket.connect();
343 }
344 catch(const std::exception& e)
345 {
346 if(onError.isCallable())
347 {
348 onError.call({QString::fromStdString(e.what())});
349 }
350 }
351 }
352
353 void write(QByteArray buffer)
354 {
355 if(!m_state)
356 return;
357 auto st = m_state;
358 if(st->enc != ossia::net::encoding::none)
359 buffer = apply_encoding(st->enc, buffer);
360 boost::asio::dispatch(st->socket.m_context, [st, buffer = std::move(buffer)] {
361 if(st->alive)
362 st->write_encoded(buffer.data(), buffer.size());
363 });
364 }
365 W_SLOT(write)
366
367 void close()
368 {
369 if(!m_state)
370 return;
371 auto st = m_state;
372 boost::asio::post(st->socket.m_context, [st] {
373 try
374 {
375 st->socket.m_socket.shutdown(boost::asio::ip::udp::socket::shutdown_both);
376 }
377 catch(...)
378 {
379 }
380 st->socket.m_socket.close();
381 st->socket.on_close();
382 });
383 }
384 W_SLOT(close)
385
386 void on_open()
387 {
388 if(!m_state || !m_state->alive)
389 return;
390
391 auto st = m_state;
392 auto self = QPointer{this};
393 if(onMessage.isCallable())
394 {
395 ossia::visit(
396 [cb = receive_callback{st, self, &self.data()->onMessage}](
397 auto& decoder) mutable { decoder.receive(std::move(cb)); },
398 st->decoder);
399 }
400 else if(onBytes.isCallable())
401 {
402 st->decoder.template emplace<0>(st->socket.m_socket);
403 ossia::get<0>(st->decoder)
404 .receive(receive_callback{st, self, &self.data()->onBytes});
405 }
406
407 ossia::qt::run_async(
408 this, [=, this] { onOpen.call({qjsEngine(this)->newQObject(this)}); },
409 Qt::AutoConnection);
410 }
411 void on_fail()
412 {
413 if(!m_state || !m_state->alive)
414 return;
415 ossia::qt::run_async(this, [=, this] { onError.call(); }, Qt::AutoConnection);
416 }
417 void on_close()
418 {
419 if(!m_state || !m_state->alive)
420 return;
421 ossia::qt::run_async(this, [=, this] { onClose.call(); }, Qt::AutoConnection);
422 }
423
424 void osc(QByteArray address, QJSValueList values)
425 {
426 if(!m_state)
427 return;
428 QByteArray packet;
429 buffer_writer bw{packet};
430 using send_visitor = ossia::net::osc_value_send_visitor<
431 ossia::net::full_parameter_data, ossia::net::osc_1_0_policy, buffer_writer>;
432
434 const std::string addr = address.toStdString();
435
436 switch(values.size())
437 {
438 case 0: {
439 ossia::value{ossia::impulse{}}.apply(send_visitor{p, addr, bw});
440 break;
441 }
442 case 1: {
443 auto v = ossia::qt::value_from_js(values[0]);
444 v.apply(send_visitor{p, addr, bw});
445 break;
446 }
447 default: {
448 std::vector<ossia::value> vec;
449 vec.reserve(values.size());
450 for(const auto& v : values)
451 vec.push_back(ossia::qt::value_from_js(v));
452 ossia::value vvec(std::move(vec));
453 vvec.apply(send_visitor{p, addr, bw});
454 }
455 }
456 write(packet);
457 }
458 W_SLOT(osc)
459
460 QJSValue onOpen;
461 QJSValue onClose;
462 QJSValue onError;
463 QJSValue onMessage;
464 QJSValue onBytes; // raw bytes, ignores Framing (backward compatible)
465
466private:
467 std::shared_ptr<state> m_state;
468};
469
470}
471#endif
The value class.
Definition value.hpp:173
Definition qml_device.cpp:43
Definition git_info.h:7
Full information about a parameter.
Definition parameter_data.hpp:61