OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
qml_tcp_inbound_socket.hpp
1#pragma once
2#include <ossia/network/context.hpp>
3#include <ossia/network/sockets/tcp_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{
17class qml_tcp_connection
18 : public QObject
19 , public Nano::Observer
20{
21 W_OBJECT(qml_tcp_connection)
22public:
23 struct state
24 {
25 boost::asio::io_context& context;
26 ossia::net::tcp_listener listener;
27 char data[4096];
28 std::atomic_bool alive{true};
29
30 state(ossia::net::tcp_listener l, boost::asio::io_context& ctx)
31 : context{ctx}
32 , listener{std::move(l)}
33 {
34 }
35 };
36
37 explicit qml_tcp_connection(
38 ossia::net::tcp_listener listener, boost::asio::io_context& ctx)
39 : m_state{std::make_shared<state>(std::move(listener), ctx)}
40 {
41 }
42
43 ~qml_tcp_connection() { m_state->alive = false; }
44 inline boost::asio::io_context& context() noexcept { return m_state->context; }
45
46 void write(QByteArray buffer)
47 {
48 auto st = m_state;
49 boost::asio::dispatch(st->context, [st, buffer] {
50 if(st->alive)
51 st->listener.write(boost::asio::const_buffer(buffer.data(), buffer.size()));
52 });
53 }
54 W_SLOT(write)
55
56 void close(QByteArray)
57 {
58 auto st = m_state;
59 boost::asio::dispatch(st->context, [st] {
60 if(st->alive)
61 st->listener.close();
62 });
63 }
64 W_SLOT(close)
65
66 void receive(QJSValue v)
67 {
68 onBytes = v;
69 receive_impl(m_state, QPointer{this});
70 }
71 W_SLOT(receive)
72
73 QJSValue onBytes;
74 QJSValue onClose;
75 W_PROPERTY(QJSValue, onClose W_MEMBER onClose);
76
77private:
78 static void receive_impl(
79 std::shared_ptr<state> st, QPointer<qml_tcp_connection> self)
80 {
81 st->listener.m_socket.async_read_some(
82 boost::asio::buffer(st->data, sizeof(st->data)),
83 [self, st](boost::system::error_code ec, std::size_t bytes_transferred) {
84 if(!st->alive)
85 return;
86 if(!ec)
87 {
88 auto buf = QByteArray(st->data, bytes_transferred);
89 ossia::qt::run_async(
90 self.get(),
91 [self, buf] {
92 if(!self.get())
93 return;
94 if(self->onBytes.isCallable())
95 {
96 auto engine = qjsEngine(self.get());
97 if(engine)
98 self->onBytes.call({engine->toScriptValue(buf)});
99 }
100 },
101 Qt::AutoConnection);
102 receive_impl(st, self);
103 }
104 else
105 {
106 ossia::qt::run_async(
107 self.get(),
108 [self] {
109 if(self && self->onClose.isCallable())
110 self->onClose.call();
111 },
112 Qt::AutoConnection);
113 }
114 });
115 }
116
117 std::shared_ptr<state> m_state;
118};
119
120class qml_tcp_inbound_socket
121 : public QObject
122 , public Nano::Observer
123{
124 W_OBJECT(qml_tcp_inbound_socket)
125public:
126 struct state
127 {
128 ossia::net::tcp_server server;
129 std::atomic_bool alive{true};
130 std::atomic_bool open{false};
131
132 state(
133 const ossia::net::inbound_socket_configuration& conf,
134 boost::asio::io_context& ctx)
135 : server{conf, ctx}
136 {
137 }
138 };
139
140 qml_tcp_inbound_socket(
141 const ossia::net::inbound_socket_configuration& conf, boost::asio::io_context& ctx)
142 : m_state{std::make_shared<state>(conf, ctx)}
143 {
144 }
145
146 ~qml_tcp_inbound_socket() { m_state->alive = false; }
147
148 inline boost::asio::io_context& context() noexcept { return m_state->server.m_context; }
149
150 void open()
151 {
152 m_state->open = true;
153 accept_impl(m_state, QPointer{this});
154 if(onOpen.isCallable())
155 onOpen.call({qjsEngine(this)->newQObject(this)});
156 }
157
158 void close()
159 {
160 m_state->open = false;
161 m_state->server.m_acceptor.close();
162 if(onClose.isCallable())
163 onClose.call();
164 }
165 W_SLOT(close)
166
167 void on_close()
168 {
169 m_state->open = false;
170 ossia::qt::run_async(this, [=, this] { onClose.call(); }, Qt::AutoConnection);
171 }
172
173 QJSValue onOpen;
174 QJSValue onClose;
175 QJSValue onError;
176 QJSValue onConnection;
177
178private:
179 static void accept_impl(
180 std::shared_ptr<state> st, QPointer<qml_tcp_inbound_socket> self)
181 {
182 st->server.m_acceptor.async_accept(
183 [self, st](
184 boost::system::error_code ec, ossia::net::tcp_server::proto::socket socket) {
185 if(!st->alive || !st->open)
186 return;
187 if(!ec)
188 {
189 ossia::qt::run_async(
190 self.get(),
191 [self, st, socket = std::move(socket)]() mutable {
192 if(!self.get())
193 return;
194 auto conn = new qml_tcp_connection{
195 ossia::net::tcp_listener{std::move(socket)}, st->server.m_context};
196
197 if(self->onConnection.isCallable())
198 {
199 self->onConnection.call({qjsEngine(self.get())->newQObject(conn)});
200 }
201 },
202 Qt::AutoConnection);
203 accept_impl(st, self);
204 }
205 });
206 }
207
208 std::shared_ptr<state> m_state;
209};
210
211}
Definition qml_device.cpp:43