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