Loading...
Searching...
No Matches
Teleplot.hpp
1#pragma once
2#include <ossia/network/resolve.hpp>
3#include <ossia/network/sockets/udp_socket.hpp>
4
5#include <boost/asio/ip/basic_resolver.hpp>
6#include <boost/asio/ip/udp.hpp>
7
8#include <QDebug>
9
10#include <AvndProcesses/AddressTools.hpp>
11namespace avnd_tools
12{
16{
17 halp_meta(name, "Teleplot")
18 halp_meta(author, "ossia team")
19 halp_meta(category, "Monitoring")
20 halp_meta(description, "Forwards a set of device explorer messages to ")
21 halp_meta(c_name, "avnd_teleplot")
22 halp_meta(manual_url, "https://ossia.io/score-docs/processes/teleplot.html")
23 halp_meta(uuid, "e1d5b9a0-4df9-4281-87a6-9f427dfb6e31")
24
25 // Populated automatically from Executor
26 boost::asio::io_context* io_context{};
27
28 struct inputs_t
29 {
30 PatternSelector pattern;
31 struct : halp::lineedit<"Host", "">
32 {
33 void update(Teleplot& self) { self.update(); }
34 } host;
35 } inputs;
36
37 struct
38 {
39 } outputs;
40
41 ~Teleplot() { clear(); }
42
43 std::pair<std::string, uint16_t> resolve_ip(const std::string& url)
44 {
45 auto [host, port] = ossia::url_to_host_and_port(url);
46 if(auto resolved = ossia::resolve_sync_v4<boost::asio::ip::udp>(host, port))
47 {
48 return {resolved->host, std::stoi(resolved->port)};
49 }
50 return {};
51 }
52
53 void clear()
54 {
55 for(auto& [param, cb] : params)
56 {
57 param->remove_callback(cb);
58 }
59 params.clear();
60 }
61
62 void update()
63 {
64 // 1. Remove existing callbacks
65 clear();
66
67 // 2. recreate socket
68 {
69 // auto split = QString::fromStdString(inputs.host.value).split(':');
70 auto [ip, port] = resolve_ip(inputs.host.value);
71 if(!ip.empty() && port > 1)
72 {
73 socket = std::make_shared<ossia::net::udp_send_socket>(
74 ossia::net::outbound_socket_configuration{.host = ip, .port = port},
75 *io_context);
76 socket->connect();
77 }
78 }
79
80 // 3. Recreate callbacks
81 for(auto nodes : this->roots)
82 {
83 if(auto p = nodes->get_parameter(); p && !params.contains(p))
84 {
85 auto it = p->add_callback(
86 [p, socket = socket](const ossia::value& v) { push(*socket, *p, v); });
87 params.emplace(p, it);
88 }
89 }
90 }
91
92 // NOTE: this function can be called from any thread
93 static void push(
94 ossia::net::udp_send_socket& socket, const ossia::net::parameter_base& param,
95 const ossia::value& v)
96 {
97 using clk = std::chrono::system_clock;
98
99 thread_local fmt::memory_buffer buf;
100 buf.clear();
101 buf.reserve(512);
102
103 struct
104 {
105 const std::string& addr;
106 int64_t t = std::chrono::duration_cast<std::chrono::milliseconds>(
107 clk::now().time_since_epoch())
108 .count();
109
110 void operator()(ossia::impulse v)
111 {
112 fmt::format_to(fmt::appender(buf), "{}:{}:1\n", addr, t);
113 }
114 void operator()(int v)
115 {
116 fmt::format_to(fmt::appender(buf), "{}:{}:{}\n", addr, t, v);
117 }
118 void operator()(float v)
119 {
120 fmt::format_to(fmt::appender(buf), "{}:{}:{}\n", addr, t, v);
121 }
122 void operator()(std::string v)
123 {
124 fmt::format_to(fmt::appender(buf), "{}:{}:{}\n", addr, t, v);
125 }
126 void operator()(bool v)
127 {
128 fmt::format_to(fmt::appender(buf), "{}:{}:{}\n", addr, t, v ? 1 : 0);
129 }
130 void operator()(ossia::vec2f v)
131 {
132 fmt::format_to(
133 fmt::appender(buf), "{}.x:{}:{}\n{}.y:{}:{}\n", addr, t, v[0], addr, t,
134 v[1]);
135 }
136 void operator()(ossia::vec3f v)
137 {
138 fmt::format_to(
139 fmt::appender(buf), "{}.x:{}:{}\n{}.y:{}:{}\n{}.z:{}:{}\n", addr, t, v[0],
140 addr, t, v[1], addr, t, v[2]);
141 }
142 void operator()(ossia::vec4f v)
143 {
144 fmt::format_to(
145 fmt::appender(buf), "{}.x:{}:{}\n{}.y:{}:{}\n{}.z:{}:{}\n{}.w:{}:{}\n", addr,
146 t, v[0], addr, t, v[1], addr, t, v[2], addr, t, v[3]);
147 }
148 void operator()(const std::vector<ossia::value>& v)
149 {
150 int i = 0;
151 for(auto& val : v)
152 {
153 // FIXME this does not handle multidimensional arrays / recursivity.
154 fmt::format_to(
155 fmt::appender(buf), "{}[{}]:{}:{}\n", addr, i, t,
156 ossia::convert<double>(val));
157 i++;
158 }
159 }
160 void operator()(const ossia::value_map_type& v)
161 {
162 for(auto& [k, val] : v)
163 {
164 // FIXME this does not handle multidimensional arrays / recursivity.
165 fmt::format_to(
166 fmt::appender(buf), "{}[{}]:{}:{}\n", addr, k, t,
167 ossia::convert<double>(val));
168 }
169 }
170 void operator()() { }
171
172 } vis{.addr = param.get_node().osc_address()};
173 v.apply(vis);
174
175 socket.write(buf.begin(), buf.size());
176 }
177
178 void operator()()
179 {
180 if(!socket)
181 {
182 socket = std::make_shared<ossia::net::udp_send_socket>(
183 ossia::net::outbound_socket_configuration{.host = "127.0.0.1", .port = 47269},
184 *io_context);
185 socket->connect();
186 }
187
188 if(!m_path)
189 return;
190
191 // FIXME do this in an update callback instead
192 // Create callbacks for added nodes
193 for(auto nodes : this->roots)
194 {
195 if(auto p = nodes->get_parameter(); p && !params.contains(p))
196 {
197 auto it = p->add_callback(
198 [p, socket = socket](const ossia::value& v) { push(*socket, *p, v); });
199 params.emplace(p, it);
200 }
201 }
202
203 // Remove callbacks for removed nodes
204 for(auto it = params.begin(); it != params.end();)
205 {
206 if(!ossia::contains(this->roots, &(it->first)->get_node()))
207 {
208 it->first->remove_callback(it->second);
209 it = params.erase(it);
210 }
211 else
212 {
213 ++it;
214 }
215 }
216 }
217
218 boost::container::flat_map<
219 ossia::net::parameter_base*,
220 ossia::callback_container<ossia::value_callback>::iterator>
221 params;
222 std::shared_ptr<ossia::net::udp_send_socket> socket;
223};
224}
Definition AddressTools.hpp:21
Definition AddressTools.hpp:28
Definition Teleplot.hpp:29
Definition Teleplot.hpp:16