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