OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
qml_tcp_outbound_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/encoding.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/tcp_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_tcp_outbound_socket
29 : public QObject
30 , public Nano::Observer
31{
32 W_OBJECT(qml_tcp_outbound_socket)
33public:
34 using socket_t = boost::asio::ip::tcp::socket;
35 using decoder_type = ossia::slow_variant<
36 ossia::net::no_framing::decoder<socket_t>,
37 ossia::net::slip_decoder<socket_t>,
38 ossia::net::size_prefix_decoder<socket_t>,
39 ossia::net::line_framing_decoder<socket_t>,
40 ossia::net::cobs_decoder<socket_t>,
41 ossia::net::stx_etx_framing::decoder<socket_t>,
42 ossia::net::size_prefix_1byte_framing::decoder<socket_t>,
43 ossia::net::size_prefix_2byte_be_framing::decoder<socket_t>,
44 ossia::net::size_prefix_2byte_le_framing::decoder<socket_t>,
45 ossia::net::size_prefix_4byte_le_framing::decoder<socket_t>,
46 ossia::net::fixed_length_decoder<socket_t>>;
47
48 struct state
49 {
50 ossia::net::tcp_client socket;
51 std::atomic_bool alive{true};
52 ossia::net::framing framing{ossia::net::framing::none};
53 ossia::net::encoding enc{ossia::net::encoding::none};
54 char line_delimiter[8] = {};
55 decoder_type decoder;
56
57 state(
58 const ossia::net::outbound_socket_configuration& conf,
59 boost::asio::io_context& ctx,
60 ossia::net::framing f = ossia::net::framing::none,
61 const std::string& delim = {},
62 ossia::net::encoding e = ossia::net::encoding::none)
63 : socket{conf, ctx}
64 , decoder{ossia::in_place_index<0>, socket.m_socket}
65 {
66 framing = f;
67 enc = e;
68 if(!delim.empty())
69 {
70 auto sz = std::min(delim.size(), (size_t)7);
71 std::copy_n(delim.begin(), sz, line_delimiter);
72 }
73
74 switch(f)
75 {
76 default:
77 case ossia::net::framing::none:
78 break;
79 case ossia::net::framing::slip:
80 decoder.template emplace<1>(socket.m_socket);
81 break;
82 case ossia::net::framing::size_prefix:
83 decoder.template emplace<2>(socket.m_socket);
84 break;
85 case ossia::net::framing::line_delimiter:
86 decoder.template emplace<3>(socket.m_socket);
87 {
88 auto& dec = ossia::get<3>(decoder);
89 std::copy_n(line_delimiter, 8, dec.delimiter);
90 }
91 break;
92 case ossia::net::framing::cobs:
93 decoder.template emplace<4>(socket.m_socket);
94 break;
95 case ossia::net::framing::stx_etx:
96 decoder.template emplace<5>(socket.m_socket);
97 break;
98 case ossia::net::framing::size_prefix_1byte:
99 decoder.template emplace<6>(socket.m_socket);
100 break;
101 case ossia::net::framing::size_prefix_2byte_be:
102 decoder.template emplace<7>(socket.m_socket);
103 break;
104 case ossia::net::framing::size_prefix_2byte_le:
105 decoder.template emplace<8>(socket.m_socket);
106 break;
107 case ossia::net::framing::size_prefix_4byte_le:
108 decoder.template emplace<9>(socket.m_socket);
109 break;
110 case ossia::net::framing::fixed_length:
111 decoder.template emplace<10>(socket.m_socket);
112 if(!delim.empty())
113 ossia::get<10>(decoder).frame_size = std::stoul(delim);
114 break;
115 }
116 }
117
118 void write_encoded(const char* data, std::size_t sz)
119 {
120 switch(framing)
121 {
122 default:
123 case ossia::net::framing::none:
124 socket.write(data, sz);
125 break;
126 case ossia::net::framing::slip:
127 ossia::net::slip_encoder<socket_t>{socket.m_socket}.write(data, sz);
128 break;
129 case ossia::net::framing::size_prefix:
130 ossia::net::size_prefix_encoder<socket_t>{socket.m_socket}.write(data, sz);
131 break;
132 case ossia::net::framing::line_delimiter: {
133 ossia::net::line_framing_encoder<socket_t> enc{socket.m_socket};
134 std::copy_n(line_delimiter, 8, enc.delimiter);
135 enc.write(data, sz);
136 break;
137 }
138 case ossia::net::framing::cobs:
139 ossia::net::cobs_encoder<socket_t>{socket.m_socket}.write(data, sz);
140 break;
141 case ossia::net::framing::stx_etx:
142 ossia::net::stx_etx_framing::encoder<socket_t>{socket.m_socket}.write(data, sz);
143 break;
144 case ossia::net::framing::size_prefix_1byte:
145 ossia::net::size_prefix_1byte_framing::encoder<socket_t>{socket.m_socket}.write(
146 data, sz);
147 break;
148 case ossia::net::framing::size_prefix_2byte_be:
149 ossia::net::size_prefix_2byte_be_framing::encoder<socket_t>{socket.m_socket}
150 .write(data, sz);
151 break;
152 case ossia::net::framing::size_prefix_2byte_le:
153 ossia::net::size_prefix_2byte_le_framing::encoder<socket_t>{socket.m_socket}
154 .write(data, sz);
155 break;
156 case ossia::net::framing::size_prefix_4byte_le:
157 ossia::net::size_prefix_4byte_le_framing::encoder<socket_t>{socket.m_socket}
158 .write(data, sz);
159 break;
160 case ossia::net::framing::fixed_length:
161 ossia::net::fixed_length_encoder<socket_t>{socket.m_socket}.write(data, sz);
162 break;
163 }
164 }
165 };
166
167 struct receive_callback
168 {
169 std::shared_ptr<state> st;
170 QPointer<qml_tcp_outbound_socket> self;
171 QJSValue* target; // points to onMessage or onBytes on the QObject
172
173 void operator()(const unsigned char* data, std::size_t sz) const
174 {
175 if(!st->alive)
176 return;
177 auto buf = apply_decoding(st->enc, data, sz);
178 auto cb = target;
179 ossia::qt::run_async(
180 self.get(),
181 [self = self, buf, cb] {
182 if(!self.get())
183 return;
184 if(cb->isCallable())
185 {
186 auto engine = qjsEngine(self.get());
187 if(engine)
188 cb->call({engine->toScriptValue(buf)});
189 }
190 },
191 Qt::AutoConnection);
192 }
193
194 bool validate_stream(boost::system::error_code ec) const
195 {
196 if(ec == boost::asio::error::operation_aborted)
197 return false;
198 if(ec == boost::asio::error::eof)
199 return false;
200 return true;
201 }
202 };
203
204 qml_tcp_outbound_socket() { }
205
206 ~qml_tcp_outbound_socket()
207 {
208 if(m_state)
209 {
210 m_state->alive = false;
211 close();
212 }
213 }
214
215 bool isOpen() const noexcept { return m_state != nullptr; }
216
217 void open(
218 const ossia::net::outbound_socket_configuration& conf,
219 boost::asio::io_context& ctx,
220 ossia::net::framing f = ossia::net::framing::none,
221 const std::string& delim = {},
222 ossia::net::encoding e = ossia::net::encoding::none)
223 {
224 m_state = std::make_shared<state>(conf, ctx, f, delim, e);
225
226 try
227 {
228 if(onOpen.isCallable())
229 m_state->socket.on_open.connect<&qml_tcp_outbound_socket::on_open>(this);
230 if(onClose.isCallable())
231 m_state->socket.on_close.connect<&qml_tcp_outbound_socket::on_close>(this);
232 if(onError.isCallable())
233 m_state->socket.on_fail.connect<&qml_tcp_outbound_socket::on_fail>(this);
234 m_state->socket.connect();
235 }
236 catch(const std::exception& e)
237 {
238 if(onError.isCallable())
239 {
240 onError.call({QString::fromStdString(e.what())});
241 }
242 }
243 }
244
245 void write(QByteArray buffer)
246 {
247 if(!m_state)
248 return;
249 auto st = m_state;
250 if(st->enc != ossia::net::encoding::none)
251 buffer = apply_encoding(st->enc, buffer);
252 boost::asio::dispatch(st->socket.m_context, [st, buffer = std::move(buffer)] {
253 if(st->alive)
254 st->write_encoded(buffer.data(), buffer.size());
255 });
256 }
257 W_SLOT(write)
258
259 void close()
260 {
261 if(!m_state)
262 return;
263 auto st = m_state;
264 boost::asio::post(st->socket.m_context, [st] {
265 try
266 {
267 st->socket.m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
268 }
269 catch(...)
270 {
271 }
272 st->socket.m_socket.close();
273 st->socket.on_close();
274 });
275 }
276 W_SLOT(close)
277
278 void on_open()
279 {
280 if(!m_state || !m_state->alive)
281 return;
282
283 // Start receive loop:
284 // - onMessage uses the configured framing decoder
285 // - onBytes always uses no_framing (raw bytes, backward compatible)
286 // - onMessage takes priority if both are set
287 auto st = m_state;
288 auto self = QPointer{this};
289 if(onMessage.isCallable())
290 {
291 ossia::visit(
292 [cb = receive_callback{st, self, &self.data()->onMessage}](
293 auto& decoder) mutable { decoder.receive(std::move(cb)); },
294 st->decoder);
295 }
296 else if(onBytes.isCallable())
297 {
298 // Override decoder to no_framing for raw bytes
299 st->decoder.template emplace<0>(st->socket.m_socket);
300 ossia::get<0>(st->decoder)
301 .receive(receive_callback{st, self, &self.data()->onBytes});
302 }
303
304 ossia::qt::run_async(
305 this, [=, this] { onOpen.call({qjsEngine(this)->newQObject(this)}); },
306 Qt::AutoConnection);
307 }
308 void on_fail()
309 {
310 if(!m_state || !m_state->alive)
311 return;
312 ossia::qt::run_async(this, [=, this] { onError.call(); }, Qt::AutoConnection);
313 }
314 void on_close()
315 {
316 if(!m_state || !m_state->alive)
317 return;
318 ossia::qt::run_async(this, [=, this] { onClose.call(); }, Qt::AutoConnection);
319 }
320
321 void osc(QByteArray address, QJSValueList values)
322 {
323 if(!m_state)
324 return;
325
326 QByteArray packet;
327 buffer_writer bw{packet};
328 using send_visitor = ossia::net::osc_value_send_visitor<
329 ossia::net::full_parameter_data, ossia::net::osc_1_0_policy, buffer_writer>;
330
332 const std::string addr = address.toStdString();
333
334 switch(values.size())
335 {
336 case 0: {
337 ossia::value{ossia::impulse{}}.apply(send_visitor{p, addr, bw});
338 break;
339 }
340 case 1: {
341 auto v = ossia::qt::value_from_js(values[0]);
342 v.apply(send_visitor{p, addr, bw});
343 break;
344 }
345 default: {
346 std::vector<ossia::value> vec;
347 vec.reserve(values.size());
348 for(const auto& v : values)
349 vec.push_back(ossia::qt::value_from_js(v));
350 ossia::value vvec(std::move(vec));
351 vvec.apply(send_visitor{p, addr, bw});
352 }
353 }
354
355 write(packet);
356 }
357 W_SLOT(osc)
358
359 QJSValue onOpen;
360 QJSValue onClose;
361 QJSValue onError;
362 QJSValue onMessage;
363 QJSValue onBytes; // raw bytes, ignores Framing (backward compatible)
364
365private:
366 std::shared_ptr<state> m_state;
367};
368
369}
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