Goby3 3.7.0
2026.11.12
Loading...
Searching...
No Matches
resolve.h
Go to the documentation of this file.
1// Copyright 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_RESOLVE_H
25#define GOBY_MIDDLEWARE_TOOL_INTERFACE_RESOLVE_H
26
27#include <map>
28#include <regex>
29#include <set>
30#include <string>
31#include <vector>
32
34
35namespace goby
36{
37namespace middleware
38{
39namespace tool
40{
41namespace interface
42{
45{
46 const Platform* platform{nullptr};
47 const Module* module{nullptr};
48 const Application* application{nullptr};
49 // the Thread whose interface lists the entry (interthread only); a base class's entries are
50 // drawn as its most derived class but still belong to the base Thread
51 const Thread* owner{nullptr};
52};
53
55{
60 // made only by assuming an unbound dynamic group's run-time numeric matches
61 bool loose{false};
62};
63
69
71{
72 std::vector<Connection> connections;
73 // publications that no subscription in scope receives
74 std::vector<Unmatched> unsubscribed;
75 // subscriptions that no publication in scope reaches
76 std::vector<Unmatched> unpublished;
77};
78
83inline bool in_scope(Layer layer, const Endpoint& pub, const Endpoint& sub)
84{
85 switch (layer)
86 {
87 case Layer::INTERTHREAD: return pub.application == sub.application;
88 case Layer::INTERPROCESS: return pub.module == sub.module;
89 case Layer::INTERMODULE: return pub.platform == sub.platform;
90 case Layer::INTERVEHICLE: return pub.platform != sub.platform;
91 case Layer::UNKNOWN: break;
92 }
93 return false;
94}
95
97inline void collect_entries(const Deployment& deployment, std::vector<Unmatched>& pubs,
98 std::vector<Unmatched>& subs)
99{
100 for (const auto& platform : deployment.platforms)
101 {
102 for (const auto& module : platform.modules)
103 {
104 for (const auto& app_p : module.applications)
105 {
106 const auto& app = app_p.second;
107 for (const auto& thread_p : app.threads)
108 {
109 Endpoint at{&platform, &module, &app, thread_p.second.get()};
110 for (const auto& e : thread_p.second->interthread_publishes)
111 pubs.push_back({e, at});
112 for (const auto& e : thread_p.second->interthread_subscribes)
113 subs.push_back({e, at});
114 }
115
116 Endpoint at{&platform, &module, &app, nullptr};
118 {
119 for (const auto& e : app.publishes_on(layer)) pubs.push_back({e, at});
120 for (const auto& e : app.subscribes_on(layer)) subs.push_back({e, at});
121 }
122 }
123 }
124 }
125}
126
128inline Resolution resolve(const Deployment& deployment)
129{
130 std::vector<Unmatched> pubs, subs;
131 collect_entries(deployment, pubs, subs);
132
133 Resolution resolution;
134 std::vector<bool> sub_matched(subs.size(), false);
135 for (const auto& pub : pubs)
136 {
137 bool pub_matched = false;
138 for (std::size_t i = 0, n = subs.size(); i < n; ++i)
139 {
140 const auto& sub = subs[i];
141 if (sub.entry.layer != pub.entry.layer || !in_scope(pub.entry.layer, pub.at, sub.at))
142 continue;
143 auto match = connection(pub.entry, sub.entry);
144 if (match != GroupMatch::NONE)
145 {
146 resolution.connections.push_back(
147 {pub.entry, pub.at, sub.entry, sub.at, match == GroupMatch::LOOSE});
148 pub_matched = true;
149 sub_matched[i] = true;
150 }
151 }
152 if (!pub_matched)
153 resolution.unsubscribed.push_back(pub);
154 }
155 for (std::size_t i = 0, n = subs.size(); i < n; ++i)
156 {
157 if (!sub_matched[i])
158 resolution.unpublished.push_back(subs[i]);
159 }
160 return resolution;
161}
162
165{
166 public:
167 struct Params
168 {
169 std::string omit_group_regex;
170 std::string omit_node_regex;
171 bool include_coroner{false};
172 bool include_terminate{false};
173 bool include_internal{false};
174 bool omit_interthread{false};
175 // draw Goby's own applications (gobyd, goby_*) with their threads
176 bool expand_goby_apps{false};
177 };
178
180 : params_(std::move(params)),
181 omit_group_regex_(params_.omit_group_regex),
182 omit_node_regex_(params_.omit_node_regex)
183 {
184 }
185
186 const Params& params() const { return params_; }
187
188 bool is_group_included(const std::string& group) const
189 {
190 static const std::set<std::string> internal_groups{
191 "goby::middleware::interprocess::to_portal",
192 "goby::middleware::interprocess::regex",
193 "goby::middleware::SerializationUnSubscribeAll",
194 "goby::middleware::Thread::joinable",
195 "goby::middleware::Thread::shutdown",
196 "goby::configuration",
197 "goby::datum_update",
198 "goby::middleware::intervehicle::modem_data_in",
199 "goby::middleware::intervehicle::modem_data_out",
200 "goby::middleware::intervehicle::metadata_request",
201 "goby::middleware::intervehicle::modem_ack_in",
202 "goby::middleware::intervehicle::modem_expire_in",
203 "goby::middleware::intervehicle::modem_subscription_forward_tx"};
204 static const std::set<std::string> terminate_groups{
205 "goby::terminate::request", "goby::terminate::response", "goby::terminate::result"};
206 static const std::set<std::string> coroner_groups{
207 "goby::health::request", "goby::health::response", "goby::health::report"};
208
209 if (!params_.omit_group_regex.empty() && std::regex_match(group, omit_group_regex_))
210 return false;
211 // with or without a numeric
212 auto name = group.substr(0, group.find(';'));
213 if (internal_groups.count(name) && !params_.include_internal)
214 return false;
215 if (terminate_groups.count(name) && !params_.include_terminate)
216 return false;
217 if (coroner_groups.count(name) && !params_.include_coroner)
218 return false;
219 return true;
220 }
221
222 bool is_node_included(const std::string& node) const
223 {
224 return params_.omit_node_regex.empty() || !std::regex_match(node, omit_node_regex_);
225 }
226
228 bool is_thread_included(const Application& app, const Thread& thread) const
229 {
230 if (!params_.omit_interthread)
231 return true;
232 auto name = thread.most_derived_name();
233 for (const auto* layer_map : {&app.publishes, &app.subscribes})
234 {
235 for (const auto& layer_p : *layer_map)
236 {
237 for (const auto& e : layer_p.second)
238 {
239 if (e.thread == name)
240 return true;
241 }
242 }
243 }
244 return false;
245 }
246
248 bool is_included(const Endpoint& at, const std::string& thread) const
249 {
250 if (!is_node_included(at.platform->name))
251 return false;
252 if (at.platform->has_modules() && !is_node_included(at.module->name))
253 return false;
254 if (!is_node_included(at.application->name) || !is_node_included(thread))
255 return false;
256 auto thread_it = at.application->threads.find(thread);
257 return thread_it == at.application->threads.end() ||
258 is_thread_included(*at.application, *thread_it->second);
259 }
260
261 bool is_included(const Connection& c) const
262 {
263 if (params_.omit_interthread && c.pub.layer == Layer::INTERTHREAD)
264 return false;
265 return is_group_included(c.pub.group) && is_group_included(c.sub.group) &&
266 is_included(c.pub_at, c.pub.thread) && is_included(c.sub_at, c.sub.thread);
267 }
268
269 bool is_included(const Unmatched& u) const
270 {
271 if (params_.omit_interthread && u.entry.layer == Layer::INTERTHREAD)
272 return false;
273 return is_group_included(u.entry.group) && is_included(u.at, u.entry.thread);
274 }
275
276 private:
277 Params params_;
278 std::regex omit_group_regex_;
279 std::regex omit_node_regex_;
280};
281
283inline bool is_goby_application(const Application& app)
284{
285 return app.name == "gobyd" || app.name.rfind("goby_", 0) == 0;
286}
287
289inline bool is_goby_thread(const std::string& name)
290{
291 static const std::regex goby_lambda(R"(\‍(lambda at .*/goby/.*\))");
292 return name.rfind("goby::", 0) == 0 || std::regex_match(name, goby_lambda);
293}
294
296inline std::string display_thread_name(const std::string& name)
297{
298 static const std::regex lambda(R"(\‍(lambda at (?:.*/)?([^/]+):(\d+):\d+\))");
299 std::smatch match;
300 if (std::regex_match(name, match, lambda))
301 return "lambda (" + match[1].str() + ":" + match[2].str() + ")";
302 auto lt = name.find('<');
303 return lt == std::string::npos ? name : name.substr(0, lt) + "<...>";
304}
305
308{
309 public:
310 DiagramView(const Deployment& deployment, const Resolution& resolution, ViewFilter filter,
311 bool omit_disconnected)
312 : filter_(std::move(filter)), omit_disconnected_(omit_disconnected)
313 {
314 // a deployment of only Goby's applications (such as one's interface file) is about them
315 for (const auto& platform : deployment.platforms)
316 {
317 for (const auto& module : platform.modules)
318 {
319 for (const auto& app_p : module.applications)
320 collapse_goby_apps_ |= !is_goby_application(app_p.second);
321 }
322 }
323 collapse_goby_apps_ &= !filter_.params().expand_goby_apps;
324
325 auto use = [&](const Endpoint& at, const std::string& thread)
326 { used_[at.application].insert(thread); };
327 for (const auto& c : resolution.connections)
328 {
329 if (drawn(c))
330 {
331 use(c.pub_at, c.pub.thread);
332 use(c.sub_at, c.sub.thread);
333 }
334 }
335 for (const auto& u : resolution.unsubscribed)
336 {
337 if (drawn_unsubscribed(u))
338 use(u.at, u.entry.thread);
339 }
340 for (const auto& u : resolution.unpublished)
341 {
342 if (drawn_unpublished(u))
343 use(u.at, u.entry.thread);
344 }
345
346 // gobyd is the module (its interprocess boundary), so unless something is drawn to it,
347 // it is shown by naming it on its container
348 for (const auto& platform : deployment.platforms)
349 {
350 for (const auto& module : platform.modules)
351 {
352 for (const auto& app_p : module.applications)
353 {
354 const auto& app = app_p.second;
355 if (app.name == "gobyd" && collapsed(app) && !used_.count(&app))
356 folded_.insert(&app);
357 }
358 }
359 }
360 }
361
362 const ViewFilter& filter() const { return filter_; }
363
364 bool collapsed(const Application& app) const
365 {
366 return collapse_goby_apps_ && is_goby_application(app);
367 }
368
369 bool folded(const Application& app) const { return folded_.count(&app); }
370
372 std::string container_label(const std::string& name, const Module& module) const
373 {
374 for (const auto& app_p : module.applications)
375 {
376 if (folded(app_p.second))
377 return name + " (gobyd)";
378 }
379 return name;
380 }
381
382 bool drawn(const Connection& c) const
383 {
384 if (c.pub.layer == Layer::INTERTHREAD && collapsed(*c.pub_at.application))
385 return false;
386 return filter_.is_included(c);
387 }
388
389 bool drawn_unsubscribed(const Unmatched& u) const
390 {
391 return !omit_disconnected_ && !u.entry.is_inner_pub && !collapsed(*u.at.application) &&
392 !goby_internal(u) && filter_.is_included(u);
393 }
394
396 bool drawn_unpublished(const Unmatched& u) const
397 {
399 if ((collapsed(*u.at.application) || goby_internal(u)) &&
400 (!required || u.entry.layer == Layer::INTERTHREAD))
401 return false;
402 if (omit_disconnected_ && !required)
403 return false;
404 return filter_.is_included(u);
405 }
406
409 bool thread_drawn(const Application& app, const Thread& thread) const
410 {
411 auto name = thread.most_derived_name();
412 if (!filter_.is_node_included(name) || !filter_.is_thread_included(app, thread))
413 return false;
414 if (is_goby_application(app) || !is_goby_thread(name))
415 return true;
416 auto it = used_.find(&app);
417 return it != used_.end() && it->second.count(name);
418 }
419
420 private:
421 // made by Goby's own thread within a project's application
422 bool goby_internal(const Unmatched& u) const
423 {
425 }
426
427 ViewFilter filter_;
428 bool omit_disconnected_;
429 bool collapse_goby_apps_{false};
430 std::map<const Application*, std::set<std::string>> used_;
431 std::set<const Application*> folded_;
432};
433
434} // namespace interface
435} // namespace tool
436} // namespace middleware
437} // namespace goby
438
439#endif
What a diagram draws of a resolved deployment, shared by the DOT and D2 writers.
Definition resolve.h:308
bool drawn_unsubscribed(const Unmatched &u) const
Definition resolve.h:389
DiagramView(const Deployment &deployment, const Resolution &resolution, ViewFilter filter, bool omit_disconnected)
Definition resolve.h:310
std::string container_label(const std::string &name, const Module &module) const
The container's name, with any gobyd folded into it.
Definition resolve.h:372
bool drawn_unpublished(const Unmatched &u) const
An unmet required subscription is drawn even when the rest are left out.
Definition resolve.h:396
bool thread_drawn(const Application &app, const Thread &thread) const
Goby's own threads within a project's application (such as its portal's forwarder) are drawn only whe...
Definition resolve.h:409
bool folded(const Application &app) const
Definition resolve.h:369
bool collapsed(const Application &app) const
Definition resolve.h:364
bool drawn(const Connection &c) const
Definition resolve.h:382
Which groups and nodes a diagram (or a filtered check) shows.
Definition resolve.h:165
bool is_group_included(const std::string &group) const
Definition resolve.h:188
bool is_thread_included(const Application &app, const Thread &thread) const
With omit_interthread, a thread is shown only if it uses an outer layer.
Definition resolve.h:228
bool is_included(const Endpoint &at, const std::string &thread) const
Is every part of the path to this endpoint (and its thread) shown?
Definition resolve.h:248
bool is_node_included(const std::string &node) const
Definition resolve.h:222
bool is_included(const Connection &c) const
Definition resolve.h:261
bool is_included(const Unmatched &u) const
Definition resolve.h:269
goby::util::logger::GroupSetter group(std::string n)
std::string display_thread_name(const std::string &name)
A thread's name as drawn: a lambda by its file and line, a template without its arguments.
Definition resolve.h:296
bool is_goby_thread(const std::string &name)
A thread from Goby itself: a goby:: class, or a lambda in one of Goby's headers.
Definition resolve.h:289
bool is_goby_application(const Application &app)
Goby's own applications, which a diagram draws as one node without their threads.
Definition resolve.h:283
GroupMatch connection(const PubSubEntry &a, const PubSubEntry &b)
Would a publication a reach subscription b (or vice versa) if they share a scope?
void collect_entries(const Deployment &deployment, std::vector< Unmatched > &pubs, std::vector< Unmatched > &subs)
Every publication and subscription in a deployment, with where it is made.
Definition resolve.h:97
bool in_scope(Layer layer, const Endpoint &pub, const Endpoint &sub)
Can a publication made at pub reach a subscription made at sub on layer?
Definition resolve.h:83
Resolution resolve(const Deployment &deployment)
Match every publication to the subscriptions it reaches.
Definition resolve.h:128
The global namespace for the Goby project.
STL namespace.
std::map< Layer, std::set< PubSubEntry > > publishes
Definition model.h:157
std::map< std::string, std::shared_ptr< Thread > > threads
Definition model.h:155
std::map< Layer, std::set< PubSubEntry > > subscribes
Definition model.h:158
Where in a deployment a publication or subscription is made.
Definition resolve.h:45
One publication or subscription as written to (and read from) an interface file.
std::vector< Unmatched > unsubscribed
Definition resolve.h:74
std::vector< Connection > connections
Definition resolve.h:72
std::vector< Unmatched > unpublished
Definition resolve.h:76
std::string most_derived_name() const
Definition model.h:136