OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
receiver.hpp
1#pragma once
3#include <ossia/detail/thread.hpp>
4
5#include <oscpack/ip/UdpSocket.h>
6#include <oscpack/osc/OscDebug.h>
7#include <oscpack/osc/OscPacketListener.h>
8
9#include <functional>
10#include <memory>
11#include <sstream>
12#include <thread>
13
14namespace oscpack
15{
16
17namespace detail
18{
19
20template <typename Impl_T>
21struct ClearListener : public oscpack::TimerListener
22{
23 ClearListener(UdpSocket<Impl_T>& s)
24 : socket{s}
25 {
26 }
27 UdpSocket<Impl_T>& socket;
28
29 void TimerExpired() override { socket.AsynchronousBreak(); }
30};
31
32template <typename Impl_T>
33class ReceiveSocket : public UdpSocket<Impl_T>
34{
35 SocketReceiveMultiplexer<Impl_T> mux_;
36 PacketListener* listener_;
37
38public:
39 ReceiveSocket(const IpEndpointName& localEndpoint, PacketListener* listener)
40 : listener_(listener)
41 {
42 this->Bind(localEndpoint);
43 mux_.AttachSocketListener(&this->impl_, listener_);
44 }
45
46 ~ReceiveSocket() { mux_.DetachSocketListener(&this->impl_, listener_); }
47
48 // see SocketReceiveMultiplexer above for the behaviour of these methods...
49 void Run() { mux_.Run(); }
50 void Break()
51 {
52 ClearListener<Impl_T> l{*this};
53 mux_.AttachPeriodicTimerListener(0, &l);
54 mux_.Break();
55 }
56 void AsynchronousBreak()
57 {
58 ClearListener<Impl_T> l{*this};
59 mux_.AttachPeriodicTimerListener(0, &l);
60 mux_.AsynchronousBreak();
61 }
62};
63}
64class ReceiveSocket
65 : public detail::UdpListeningReceiveSocket<detail::Implementation>
66{
67public:
68 using UdpListeningReceiveSocket::UdpListeningReceiveSocket;
69
72 unsigned int BoundPort()
73 {
74 sockaddr_in addr{};
75#if defined(_WIN32)
76 int len = sizeof(addr);
77#else
78 socklen_t len = sizeof(addr);
79#endif
80 if(getsockname(this->impl_.Socket(), reinterpret_cast<sockaddr*>(&addr), &len) != 0)
81 return 0;
82 return ntohs(addr.sin_port);
83 }
84};
85}
86namespace osc
87{
88
89template <typename MessageHandler>
95class listener final : public oscpack::OscPacketListener
96{
97public:
98 listener(MessageHandler msg)
99 : m_messageHandler{msg}
100 {
101 }
102
103 void ProcessMessage(
104 const oscpack::ReceivedMessage& m, const oscpack::IpEndpointName& ip) override
105 {
106 try
107 {
108 m_messageHandler(m, ip);
109 }
110 catch(std::exception& e)
111 {
112 std::stringstream s;
113 oscpack::debug(s, m);
114
115 ossia::logger().error(
116 "osc::listener::ProcessMessage error: '{}': {}", s.str(), e.what());
117 }
118 catch(...)
119 {
120 std::stringstream s;
121 oscpack::debug(s, m);
122 ossia::logger().error("osc::listener::ProcessMessage error: '{}'", s.str());
123 }
124 }
125
126 void ProcessPacket(
127 const char* data, int size, const oscpack::IpEndpointName& remoteEndpoint) override
128 {
129 try
130 {
131 oscpack::ReceivedPacket p(data, size);
132 if(p.IsBundle())
133 this->ProcessBundle(oscpack::ReceivedBundle(p), remoteEndpoint);
134 else
135 this->ProcessMessage(oscpack::ReceivedMessage(p), remoteEndpoint);
136 }
137 catch(std::exception& e)
138 {
139 ossia::logger().error("osc::listener::ProcessPacket error: {}", e.what());
140 }
141 catch(...)
142 {
143 ossia::logger().error("osc::listener::ProcessPacket error");
144 }
145 }
146
147private:
148 MessageHandler m_messageHandler;
149};
150
157class receiver
158{
159public:
160 template <typename Handler>
161 receiver(unsigned int port, Handler msg)
162 : m_impl{std::make_unique<listener<Handler>>(msg)}
163 {
164 setPort(port);
165 }
166
167 receiver() = default;
168 receiver(receiver&& other) noexcept
169 {
170 other.stop();
171 m_impl = std::move(other.m_impl);
172 m_socket = std::move(other.m_socket);
173 setPort(other.m_port);
174 }
175
176 receiver& operator=(receiver&& other) noexcept
177 {
178 stop();
179
180 m_impl = std::move(other.m_impl);
181 m_socket = std::move(other.m_socket);
182
183 setPort(other.m_port);
184
185 return *this;
186 }
187
188 ~receiver() { stop(); }
189
190 void run()
191 {
192 if(m_runThread.joinable())
193 stop();
194
195 m_runThread = ossia::thread([this] {
196 ossia::set_thread_name("ossia osc");
197 run_impl();
198 });
199 while(!m_running)
200 std::this_thread::sleep_for(std::chrono::microseconds(1));
201 }
202
203 void run_impl()
204 {
205 m_running = true;
206 // Retried while running only: once stop() has begun, a failure is the
207 // socket being shut down under the loop.
208 while(m_running)
209 {
210 try
211 {
212 m_socket->Run();
213 return;
214 }
215 catch(...)
216 {
217 }
218 }
219 }
220
221 void stop()
222 {
223 m_running = false;
224 if(m_socket)
225 {
226 if(m_runThread.joinable())
227 {
228 // The packet is a fallback wake-up only: the break pipe already ends
229 // the loop, and sending can fail (macOS refuses 127.0.0.1:0).
230 try
231 {
232 oscpack::UdpTransmitSocket send_socket(
233 oscpack::IpEndpointName("127.0.0.1", port()));
234 send_socket.Send("__stop_", 8);
235 }
236 catch(...)
237 {
238 }
239 m_socket->AsynchronousBreak();
240
241 try
242 {
243 m_runThread.join();
244 }
245 catch(...)
246 {
247 // The thread may still be in Run(): leak the socket rather than
248 // free it under the thread.
249 m_runThread.detach();
250 (void)m_socket.release();
251 return;
252 }
253 }
254
255 m_socket.reset();
256 }
257 else
258 {
259 if(m_runThread.joinable())
260 {
261 // Error somewhere: the thread is joinable, but there's no socket...
262 m_runThread.detach();
263 }
264 }
265 }
266
267 unsigned int port() const { return m_port; }
268
269 unsigned int setPort(unsigned int port)
270 {
271 m_port = port;
272
273 bool ok = false;
274 while(!ok)
275 {
276 try
277 {
278 m_socket = std::make_unique<oscpack::ReceiveSocket>(
279 oscpack::IpEndpointName(oscpack::IpEndpointName::ANY_ADDRESS, m_port),
280 m_impl.get());
281 if(m_port == 0)
282 m_port = m_socket->BoundPort();
283 ok = true;
284 }
285 catch(std::runtime_error&)
286 {
287 m_port++;
288 }
289 }
290
291 return m_port;
292 }
293
294private:
295 unsigned int m_port = 0;
296 std::unique_ptr<oscpack::OscPacketListener> m_impl;
297 std::unique_ptr<oscpack::ReceiveSocket> m_socket;
298
299 ossia::thread m_runThread;
300 std::atomic_bool m_running = false;
301};
302}
Definition thread.hpp:28
spdlog::logger & logger() noexcept
Where the errors will be logged. Default is stderr.
Definition context.cpp:120