Goby3 3.7.0
2026.11.12
Loading...
Searching...
No Matches
pubsub_entry.h
Go to the documentation of this file.
1// Copyright 2020-2026:
2// GobySoft, LLC (2013-)
3// Community contributors (see AUTHORS file)
4// File authors:
5// Toby Schneider <toby@gobysoft.org>
6//
7//
8// This file is part of the Goby Underwater Autonomy Project Libraries
9// ("The Goby Libraries").
10//
11// The Goby Libraries are free software: you can redistribute them and/or modify
12// them under the terms of the GNU Lesser General Public License as published by
13// the Free Software Foundation, either version 2.1 of the License, or
14// (at your option) any later version.
15//
16// The Goby Libraries are distributed in the hope that they will be useful,
17// but WITHOUT ANY WARRANTY; without even the implied warranty of
18// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19// GNU Lesser General Public License for more details.
20//
21// You should have received a copy of the GNU Lesser General Public License
22// along with Goby. If not, see <http://www.gnu.org/licenses/>.
23
24#ifndef GOBY_MIDDLEWARE_TOOL_INTERFACE_PUBSUB_ENTRY_H
25#define GOBY_MIDDLEWARE_TOOL_INTERFACE_PUBSUB_ENTRY_H
26
27#include <cstdint>
28#include <ostream>
29#include <set>
30#include <string>
31
32#include <yaml-cpp/yaml.h>
33
36
37namespace goby
38{
39namespace middleware
40{
41namespace tool
42{
43namespace interface
44{
45enum class Layer
46{
47 UNKNOWN = -1,
48 INTERTHREAD = 0,
49 INTERPROCESS = 10,
50 INTERMODULE = 20,
51 INTERVEHICLE = 30
52};
53
54inline std::string layer_name(Layer layer)
55{
56 switch (layer)
57 {
58 case Layer::INTERTHREAD: return "interthread";
59 case Layer::INTERPROCESS: return "interprocess";
60 case Layer::INTERMODULE: return "intermodule";
61 case Layer::INTERVEHICLE: return "intervehicle";
62 case Layer::UNKNOWN: break;
63 }
64 return "unknown";
65}
66
67inline char layer_code(Layer layer)
68{
69 switch (layer)
70 {
71 case Layer::INTERTHREAD: return 'T';
72 case Layer::INTERPROCESS: return 'P';
73 case Layer::INTERMODULE: return 'M';
74 case Layer::INTERVEHICLE: return 'V';
75 case Layer::UNKNOWN: break;
76 }
77 return 'X';
78}
79
81{
82 switch (n)
83 {
84 case goby::middleware::Necessity::REQUIRED: return "required";
85 case goby::middleware::Necessity::RECOMMENDED: return "recommended";
87 }
88 return "optional";
89}
90
92{
93 if (s == "required")
95 else if (s == "recommended")
97 else
99}
100
103{
104 enum class Direction
105 {
106 UNKNOWN,
107 PUBLISH,
109 };
110
111 PubSubEntry() = default;
112
113 PubSubEntry(Layer l, Direction dir, const YAML::Node& yaml, const std::string& th = "")
114 : layer(l), direction(dir)
115 {
116 auto thread_node = yaml["thread"];
117 thread = thread_node ? thread_node.as<std::string>() : th;
118
119 group = yaml["group"].as<std::string>();
120 scheme = yaml["scheme"].as<std::string>();
121 type = yaml["type"].as<std::string>();
122
123 if (dir == Direction::SUBSCRIBE && yaml["necessity"])
124 necessity = necessity_from_name(yaml["necessity"].as<std::string>());
125
126 auto inner_node = yaml["inner"];
127 is_inner_pub = inner_node && inner_node.as<bool>();
128
129 auto is_regex_node = yaml["is_regex"];
130 is_regex = is_regex_node && is_regex_node.as<bool>();
131
132 auto dynamic_node = yaml["dynamic"];
133 dynamic = dynamic_node && dynamic_node.as<bool>();
134 auto numeric_node = yaml["numeric"];
135 numeric_runtime = numeric_node && numeric_node.as<std::string>() == "runtime";
136 if (auto expr_node = yaml["expr"])
137 expr = expr_node.as<std::string>();
138 }
139
140 PubSubEntry(Layer l, Direction d, std::string th, std::string g, std::string s, std::string t,
141 bool tk, goby::middleware::Necessity n, bool r)
142 : layer(l),
143 direction(d),
144 thread(std::move(th)),
145 group(std::move(g)),
146 scheme(std::move(s)),
147 type(std::move(t)),
148 thread_is_known(tk),
149 necessity(n),
150 is_regex(r)
151 {
152 }
153
156
157 std::string thread;
158 std::string group;
159 std::string scheme;
160 std::string type;
161 bool thread_is_known{true};
163 bool is_regex{false};
164 // publication was added to this layer from an outer layer's publisher
165 bool is_inner_pub{false};
166
167 // made with publish_dynamic()/subscribe_dynamic() on a DynamicGroup
168 bool dynamic{false};
169 // the group's string is known but its numeric part is set at run time
170 bool numeric_runtime{false};
171 // the C++ expression for a group goby_clang_tool could not resolve (group is "*")
172 std::string expr;
173 // numeric values given to a numeric_runtime group by a deployment's bind:
174 std::set<std::uint32_t> bound_numerics;
175 // necessity was set by the deployment rather than the code
177
179 bool unresolved() const { return dynamic && group == "*"; }
180
182 bool unbound() const { return numeric_runtime && bound_numerics.empty(); }
183
185 std::string display_group() const;
186 // assigned when a deployment is loaded and used only to label diagrams; not part of the
187 // ordering, so it may be set on an entry already held in a std::set
188 mutable int publish_index{-1};
189
190 std::string publish_index_str() const
191 {
192 return std::string(1, layer_code(layer)) + std::to_string(publish_index);
193 }
194
195 void write_yaml_map(YAML::Emitter& yaml_out, bool include_thread = true, bool inner_pub = false,
196 bool include_necessity = true) const
197 {
198 goby::yaml::YMap entry_map(yaml_out, false);
199 entry_map.add("group", group);
200 entry_map.add("scheme", scheme);
201 entry_map.add("type", type);
202 if (include_necessity)
203 entry_map.add("necessity", necessity_name(necessity));
204 if (include_thread)
205 entry_map.add("thread", thread);
206 if (inner_pub)
207 entry_map.add("inner", "true");
208 if (is_regex)
209 entry_map.add("is_regex", "true");
210 if (dynamic)
211 entry_map.add("dynamic", "true");
212 if (numeric_runtime)
213 entry_map.add("numeric", "runtime");
214 if (!expr.empty())
215 entry_map.add("expr", expr);
216 }
217};
218
220inline std::string compact_numerics(const std::set<std::uint32_t>& values)
221{
222 std::string out;
223 for (auto it = values.begin(); it != values.end();)
224 {
225 auto first = *it, last = *it;
226 for (++it; it != values.end() && *it == last + 1; ++it) last = *it;
227 if (!out.empty())
228 out += ",";
229 out += std::to_string(first);
230 if (last != first)
231 out += "-" + std::to_string(last);
232 }
233 return out;
234}
235
236inline std::string PubSubEntry::display_group() const
237{
238 if (unresolved())
239 return expr.empty() ? group : "* (" + expr + ")";
240 if (!bound_numerics.empty())
241 return group + ";" + compact_numerics(bound_numerics);
242 if (numeric_runtime)
243 return group + ";?";
244 return group;
245}
246
248enum class GroupMatch
249{
250 NONE,
251 EXACT,
252 // a numeric part known only at run time might match (an unbound dynamic group)
253 LOOSE
254};
255
256namespace detail
257{
258// a group's string and numeric parts: "name;5" from a static Group, or a dynamic group's numerics
260{
261 std::string name;
262 enum class Numeric
263 {
264 NONE,
265 VALUES,
266 // a static Group's numeric written as an expression goby_clang_tool did not evaluate
267 TEXT,
268 RUNTIME
269 } numeric{Numeric::NONE};
270 std::set<std::uint32_t> values;
271 std::string text;
272};
273
275{
276 GroupKey key;
277 if (e.numeric_runtime)
278 {
279 key.name = e.group;
280 key.numeric =
281 e.bound_numerics.empty() ? GroupKey::Numeric::RUNTIME : GroupKey::Numeric::VALUES;
282 key.values = e.bound_numerics;
283 return key;
284 }
285
286 auto semicolon = e.group.rfind(';');
287 if (semicolon == std::string::npos)
288 {
289 key.name = e.group;
290 return key;
291 }
292 key.name = e.group.substr(0, semicolon);
293 auto numeric = e.group.substr(semicolon + 1);
294 if (!numeric.empty() && numeric.find_first_not_of("0123456789") == std::string::npos)
295 {
296 key.numeric = GroupKey::Numeric::VALUES;
297 key.values.insert(std::stoul(numeric));
298 }
299 else
300 {
301 key.numeric = GroupKey::Numeric::TEXT;
302 key.text = numeric;
303 }
304 return key;
305}
306} // namespace detail
307
309{
310 if (a.unresolved() || b.unresolved())
311 return GroupMatch::NONE;
312 if (!a.numeric_runtime && !b.numeric_runtime)
314
315 using Numeric = detail::GroupKey::Numeric;
316 auto ka = detail::group_key(a), kb = detail::group_key(b);
317 if (ka.name != kb.name)
318 return GroupMatch::NONE;
319 // a numeric set at run time is part of the group string, so it never reaches a group without one
320 if (ka.numeric == Numeric::NONE || kb.numeric == Numeric::NONE)
321 return GroupMatch::NONE;
322 if (ka.numeric == Numeric::RUNTIME || kb.numeric == Numeric::RUNTIME)
323 return GroupMatch::LOOSE;
324 if (ka.numeric == Numeric::TEXT || kb.numeric == Numeric::TEXT)
325 return ka.numeric == kb.numeric && ka.text == kb.text ? GroupMatch::EXACT
327 for (auto v : ka.values)
328 {
329 if (kb.values.count(v))
330 return GroupMatch::EXACT;
331 }
332 return GroupMatch::NONE;
333}
334
335inline std::ostream& operator<<(std::ostream& os, const PubSubEntry& e)
336{
337 return os << "layer: " << static_cast<int>(e.layer) << ", thread: " << e.thread
338 << ", group: " << e.group << ", scheme: " << e.scheme << ", type: " << e.type;
339}
340
341inline bool operator<(const PubSubEntry& a, const PubSubEntry& b)
342{
343 if (a.layer != b.layer)
344 return a.layer < b.layer;
345 if (a.thread != b.thread)
346 return a.thread < b.thread;
347 if (a.group != b.group)
348 return a.group < b.group;
349 if (a.scheme != b.scheme)
350 return a.scheme < b.scheme;
351
352 return a.type < b.type;
353}
354
357{
358 if (a.layer != b.layer)
359 return GroupMatch::NONE;
360 auto match = group_match(a, b);
361 if (match == GroupMatch::NONE)
362 return match;
363
364 // a type regex is given at run time, so a regex subscription takes any type in its group
365 bool types_match = a.type == b.type || a.is_regex || b.is_regex;
366 if ((a.scheme == b.scheme || a.scheme == "CXX_OBJECT" || b.scheme == "CXX_OBJECT") &&
367 types_match)
368 return match;
369
370 // mavlink_message_t can be decoded as any MAVLink type
371 if (a.scheme == "MAVLINK" && b.scheme == "MAVLINK" &&
372 (a.type == "mavlink::__mavlink_message" || b.type == "mavlink::__mavlink_message"))
373 return match;
374
375 return GroupMatch::NONE;
376}
377
378inline bool connects(const PubSubEntry& a, const PubSubEntry& b)
379{
380 return connection(a, b) != GroupMatch::NONE;
381}
382
383} // namespace interface
384} // namespace tool
385} // namespace middleware
386} // namespace goby
387
388#endif
void add(A key, B value)
Definition yaml_raii.h:75
detail namespace with internal helper functions
Definition json.hpp:263
GroupKey group_key(const PubSubEntry &e)
bool operator<(const PubSubEntry &a, const PubSubEntry &b)
std::string layer_name(Layer layer)
goby::middleware::Necessity necessity_from_name(const std::string &s)
GroupMatch
How a publication's group reaches a subscription's.
std::string necessity_name(goby::middleware::Necessity n)
GroupMatch connection(const PubSubEntry &a, const PubSubEntry &b)
Would a publication a reach subscription b (or vice versa) if they share a scope?
bool connects(const PubSubEntry &a, const PubSubEntry &b)
std::string compact_numerics(const std::set< std::uint32_t > &values)
e.g. {1, 2, 3, 7} -> "1-3,7"
GroupMatch group_match(const PubSubEntry &a, const PubSubEntry &b)
Necessity
Used to tag subscriptions based on their necessity (e.g. required for correct functioning,...
Definition interface.h:222
The global namespace for the Goby project.
STL namespace.
One publication or subscription as written to (and read from) an interface file.
std::string display_group() const
The group as a deployment sees it, with any bound numeric values.
bool unresolved() const
A dynamic group whose string goby_clang_tool could not determine.
bool unbound() const
A dynamic group whose numeric part is still unknown.
void write_yaml_map(YAML::Emitter &yaml_out, bool include_thread=true, bool inner_pub=false, bool include_necessity=true) const
PubSubEntry(Layer l, Direction dir, const YAML::Node &yaml, const std::string &th="")
PubSubEntry(Layer l, Direction d, std::string th, std::string g, std::string s, std::string t, bool tk, goby::middleware::Necessity n, bool r)
enum goby::middleware::tool::interface::detail::GroupKey::Numeric NONE