Easy Navigation
Loading...
Searching...
No Matches
NavState.hpp
Go to the documentation of this file.
1// Copyright 2025 Intelligent Robotics Lab
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
21
22#ifndef EASYNAV_COMMON__TYPES__NAVSTATE_HPP_
23#define EASYNAV_COMMON__TYPES__NAVSTATE_HPP_
24
25#include <execinfo.h>
26#include <cxxabi.h>
27
28#include <algorithm>
29#include <string>
30#include <unordered_map>
31#include <unordered_set>
32#include <memory>
33#include <mutex>
34#include <optional>
35#include <stdexcept>
36#include <sstream>
37#include <type_traits>
38#include <iostream>
39#include <functional>
40#include <typeinfo>
41#include <vector>
42#include <set>
43
44namespace easynav
45{
46
50inline std::string demangle(const char * name)
51{
52 int status = 0;
53 char * p = abi::__cxa_demangle(name, nullptr, nullptr, &status);
54 std::string out = (status == 0 && p) ? p : name;
55 std::free(p);
56 return out;
57}
58
63inline std::string stacktrace(std::size_t skip = 1, std::size_t max_frames = 64)
64{
65 void * buf[128];
66 const int n = backtrace(buf, static_cast<int>(std::min<std::size_t>(max_frames, 128)));
67 char ** syms = backtrace_symbols(buf, n);
68 std::ostringstream oss;
69 for (int i = static_cast<int>(skip); i < n; ++i) {
70 oss << "#" << (i - static_cast<int>(skip)) << " " << syms[i] << "\n";
71 }
72 std::free(syms);
73 return oss.str();
74}
75
87{
88public:
94
96 virtual ~NavState() = default;
97
108 template<typename T>
109 void set(const std::string & key, const T & value)
110 {
111 std::lock_guard<std::mutex> lock(state_mutex_);
112 auto it = values_.find(key);
113
114 if (it == values_.end()) {
115 values_[key] = std::make_shared<T>(value);
116 types_[key] = typeid(T).hash_code();
117 type_names_[key] = demangle(typeid(T).name());
118 } else {
119 if (types_[key] != typeid(T).hash_code()) {
120 std::ostringstream oss;
121 oss << "Type mismatch in set(\"" << key << "\")\n"
122 << " stored type : " << type_names_[key] << "\n"
123 << " provided type: " << demangle(typeid(T).name()) << "\n"
124 << "Backtrace:\n" << stacktrace(1);
125 throw std::runtime_error(oss.str());
126 }
127
128 auto ptr = std::static_pointer_cast<T>(it->second);
129 *ptr = value;
130 }
131 }
132
143 template<typename T>
144 void set(const std::string & key, const std::shared_ptr<T> value_ptr)
145 {
146 std::lock_guard<std::mutex> lock(state_mutex_);
147 auto it = values_.find(key);
148
149 if (it == values_.end()) {
150 values_[key] = std::shared_ptr<T>(value_ptr);
151 types_[key] = typeid(T).hash_code();
152 type_names_[key] = demangle(typeid(T).name());
153 } else {
154 if (types_[key] != typeid(T).hash_code()) {
155 std::ostringstream oss;
156 oss << "Type mismatch in set(\"" << key << "\")\n"
157 << " stored type : " << type_names_[key] << "\n"
158 << " provided type: " << demangle(typeid(T).name()) << "\n"
159 << "Backtrace:\n" << stacktrace(1);
160 throw std::runtime_error(oss.str());
161 }
162
163 it->second = value_ptr;
164 }
165 }
166
177 void set_group(const std::string & key, const std::vector<std::string> & group_keys)
178 {
179 std::lock_guard<std::mutex> lock(group_mutex_);
180 groups_[key] = group_keys;
181
182 // Also store the group keys as a value for introspection/debugging purposes.
183 set<std::vector<std::string>>(key, group_keys);
184 }
185
194 template<typename T>
195 const T & get(const std::string & key) const
196 {
197 std::lock_guard<std::mutex> lock(state_mutex_);
198 auto it = values_.find(key);
199
200 if (it == values_.end()) {
201 throw std::runtime_error("Key not found in get: " + key);
202 }
203
204 if (types_.at(key) != typeid(T).hash_code()) {
205 std::ostringstream oss;
206 oss << "Type mismatch in get(\"" << key << "\")\n"
207 << " stored type : " << type_names_.at(key) << "\n"
208 << " requested type: " << demangle(typeid(T).name());
209 throw std::runtime_error(oss.str());
210 }
211
212 auto ptr = std::static_pointer_cast<T>(it->second);
213 return *ptr;
214 }
215
234 template<typename T>
235 T get_safe(const std::string & key) const
236 {
237 std::lock_guard<std::mutex> lock(state_mutex_);
238 auto it = values_.find(key);
239
240 if (it == values_.end()) {
241 throw std::runtime_error("Key not found in get_safe: " + key);
242 }
243
244 if (types_.at(key) != typeid(T).hash_code()) {
245 std::ostringstream oss;
246 oss << "Type mismatch in get_safe(\"" << key << "\")\n"
247 << " stored type : " << type_names_.at(key) << "\n"
248 << " requested type: " << demangle(typeid(T).name());
249 throw std::runtime_error(oss.str());
250 }
251
252 auto ptr = std::static_pointer_cast<T>(it->second);
253 return *ptr; // copy made while state_mutex_ is still held
254 }
255
264 template<typename T>
265 const std::shared_ptr<T> get_ptr(const std::string & key) const
266 {
267 std::lock_guard<std::mutex> lock(state_mutex_);
268 auto it = values_.find(key);
269
270 if (it == values_.end()) {
271 throw std::runtime_error("Key not found in get: " + key);
272 }
273
274 if (types_.at(key) != typeid(T).hash_code()) {
275 std::ostringstream oss;
276 oss << "Type mismatch in get_ptr(\"" << key << "\")\n"
277 << " stored type : " << type_names_.at(key) << "\n"
278 << " requested type: " << demangle(typeid(T).name());
279 throw std::runtime_error(oss.str());
280 }
281
282 return std::static_pointer_cast<T>(it->second);
283 }
284
293 template<typename T>
294 const std::vector<std::shared_ptr<T>> get_group(const std::string & group_key) const
295 {
296 std::lock_guard<std::mutex> lock(group_mutex_);
297 auto group_it = groups_.find(group_key);
298
299 if (group_it == groups_.end()) {
300 throw std::runtime_error("Group key not found in get_group: " + group_key);
301 }
302
303 const auto group_keys = group_it->second;
304
305 std::vector<std::shared_ptr<T>> out;
306 out.reserve(group_keys.size());
307
308 for (const auto & group_key : group_keys) {
309 try {
310 out.push_back(get_ptr<T>(group_key));
311 } catch (const std::runtime_error & e) {
312 std::cerr << "Error retrieving key '" << group_key << "' from group '" << group_key <<
313 "': " << e.what() << std::endl;
314 }
315 }
316
317 return out;
318 }
319
328 template<typename T>
329 std::vector<std::shared_ptr<T>> get_by_type() const
330 {
331 std::lock_guard<std::mutex> lock(state_mutex_);
332 std::vector<std::shared_ptr<T>> out;
333 const size_t target_hash = typeid(T).hash_code();
334 for (const auto & kv : values_) {
335 if (types_.at(kv.first) == target_hash) {
336 out.push_back(std::static_pointer_cast<T>(kv.second));
337 }
338 }
339 return out;
340 }
341
352 template<typename T>
353 std::vector<std::shared_ptr<T>> get_to_vector(const std::string & key) const
354 {
355 std::vector<std::shared_ptr<T>> out;
356 std::lock_guard<std::mutex> lock(state_mutex_);
357 auto it = values_.find(key);
358 if (it == values_.end()) {
359 return out;
360 }
361 if (types_.at(key) != typeid(T).hash_code()) {
362 return out;
363 }
364 out.push_back(std::static_pointer_cast<T>(it->second));
365 return out;
366 }
367
377 template<typename T>
378 std::vector<std::shared_ptr<T>> get_no_group() const
379 {
380 // Snapshot the set of all keys that belong to any group (under group lock).
381 std::unordered_set<std::string> grouped_keys;
382 {
383 std::lock_guard<std::mutex> glock(group_mutex_);
384 for (const auto & g : groups_) {
385 for (const auto & k : g.second) {
386 grouped_keys.insert(k);
387 }
388 }
389 }
390
391 // Iterate values and return those of type T whose key is not grouped.
392 std::lock_guard<std::mutex> slock(state_mutex_);
393 std::vector<std::shared_ptr<T>> out;
394 const size_t target_hash = typeid(T).hash_code();
395 for (const auto & kv : values_) {
396 if (types_.at(kv.first) == target_hash &&
397 grouped_keys.find(kv.first) == grouped_keys.end())
398 {
399 out.push_back(std::static_pointer_cast<T>(kv.second));
400 }
401 }
402 return out;
403 }
404
408 bool has(const std::string & key) const
409 {
410 return values_.find(key) != values_.end();
411 }
412
416 bool has_group(const std::string & key) const
417 {
418 return groups_.find(key) != groups_.end();
419 }
420
423 void add_to_group(const std::string & group_key, const std::string & key)
424 {
425 std::lock_guard<std::mutex> lock(group_mutex_);
426 auto & members = groups_[group_key];
427 if (std::find(members.begin(), members.end(), key) != members.end()) {
428 return;
429 }
430 members.push_back(key);
431 set<std::vector<std::string>>(group_key, members);
432 }
433
437 std::vector<std::string> get_group_keys(const std::string & group_key) const
438 {
439 std::lock_guard<std::mutex> lock(group_mutex_);
440 auto it = groups_.find(group_key);
441 if (it == groups_.end()) {
442 return {};
443 }
444 return it->second;
445 }
446
451 using AnyPrinter = std::function<std::string(std::shared_ptr<void>)>;
452
457 template<typename T>
458 static void register_printer(std::function<std::string(const T &)> printer)
459 {
460 auto wrapper = [printer](std::shared_ptr<void> base_ptr) -> std::string {
461 auto typed_ptr = std::static_pointer_cast<T>(base_ptr);
462 return printer(*typed_ptr);
463 };
464 auto & registry = printer_registry();
465 std::lock_guard<std::mutex> lock(registry.mutex);
466 registry.printers[typeid(T).hash_code()] = wrapper;
467 }
468
474 std::string debug_string() const
475 {
476 // Snapshot under the lock, format outside it (printers may be slow).
477 struct Entry
478 {
479 std::string key;
480 std::shared_ptr<void> ptr;
481 std::optional<size_t> type;
482 };
483 std::vector<Entry> entries;
484 {
485 std::lock_guard<std::mutex> lock(state_mutex_);
486 entries.reserve(values_.size());
487 for (const auto & [key, ptr] : values_) {
488 const auto type_it = types_.find(key);
489 entries.push_back(
490 {key, ptr, type_it != types_.end() ? std::optional(type_it->second) : std::nullopt});
491 }
492 }
493 // Sorted keys: stable, readable output.
494 std::sort(
495 entries.begin(), entries.end(),
496 [](const Entry & a, const Entry & b) {return a.key < b.key;});
497
498 std::stringstream ss;
499 for (const auto & [key, ptr, type] : entries) {
500 ss << key << " = ";
501 if (ptr) {
502 if (type) {
503 AnyPrinter printer;
504 {
505 auto & registry = printer_registry();
506 std::lock_guard<std::mutex> lock(registry.mutex);
507 const auto printer_it = registry.printers.find(*type);
508 if (printer_it != registry.printers.end()) {
509 printer = printer_it->second;
510 }
511 }
512 if (printer) {
513 ss << "[" << ptr.get() << "] : " << printer(ptr);
514 } else {
515 ss << "[" << ptr.get() << "] : " << *type << "]";
516 }
517 } else {
518 ss << "[" << ptr.get() << "] : unknown]";
519 }
520 } else {
521 ss << "[null]";
522 }
523 ss << std::endl;
524 }
525 return ss.str();
526 }
527
530 static void print_stacktrace()
531 {
532 void * array[50];
533 int size = backtrace(array, 50);
534 char ** strings = backtrace_symbols(array, size);
535 std::cerr << "\nStack trace:\n";
536 for (int i = 0; i < size; ++i) {
537 std::cerr << strings[i] << std::endl;
538 }
539 std::cerr << std::endl;
540 free(strings);
541 }
542
547 {
548 register_printer<int>([](const int & v) {return std::to_string(v);});
549 register_printer<float>([](const float & v) {return std::to_string(v);});
550 register_printer<double>([](const double & v) {return std::to_string(v);});
551 register_printer<std::string>([](const std::string & v) {return v;});
552 register_printer<bool>([](const bool & v) {return v ? "true" : "false";});
553 register_printer<char>([](const char & v) {return std::string(1, v);});
555 [](const std::vector<std::string> & v) {
556 std::ostringstream oss;
557 oss << " " << v.size() << " [";
558 for (std::size_t i = 0; i < v.size(); ++i) {
559 if (i > 0) {oss << ", ";}
560 oss << v[i];
561 }
562 oss << "]";
563 return oss.str();
564 });
565 }
566
567private:
568 mutable std::mutex state_mutex_;
569 mutable std::mutex group_mutex_;
570
572 mutable std::unordered_map<std::string, std::shared_ptr<void>> values_;
573
576 mutable std::unordered_map<std::string, std::vector<std::string>> groups_;
577
579 mutable std::unordered_map<std::string, size_t> types_;
580
582 mutable std::unordered_map<std::string, std::string> type_names_;
583
585 struct PrinterRegistry
586 {
587 std::mutex mutex;
588 std::unordered_map<size_t, AnyPrinter> printers;
589 };
590
597 static PrinterRegistry & printer_registry()
598 {
599 static PrinterRegistry * const registry = new PrinterRegistry();
600 return *registry;
601 }
602};
603
604} // namespace easynav
605
606#endif // EASYNAV_COMMON__TYPES__NAVSTATE_HPP_
std::vector< std::shared_ptr< T > > get_no_group() const
Retrieves all stored values of type T that are NOT a member of any group.
Definition NavState.hpp:378
void set_group(const std::string &key, const std::vector< std::string > &group_keys)
Sets a new group of values as a list of strings.
Definition NavState.hpp:177
const T & get(const std::string &key) const
Retrieves a const reference to the stored value of type T for key.
Definition NavState.hpp:195
std::vector< std::shared_ptr< T > > get_to_vector(const std::string &key) const
Wraps a single stored value of type T in a one-element vector.
Definition NavState.hpp:353
const std::shared_ptr< T > get_ptr(const std::string &key) const
Retrieves the shared_ptr to the stored value of type T for key.
Definition NavState.hpp:265
std::function< std::string(std::shared_ptr< void >)> AnyPrinter
Type alias for a generic printer functor used by debug_string().
Definition NavState.hpp:451
void set(const std::string &key, const std::shared_ptr< T > value_ptr)
Stores a value of type T associated with key (by shared pointer).
Definition NavState.hpp:144
static void print_stacktrace()
Prints the current C++ stack trace to std::cerr.
Definition NavState.hpp:530
static void register_basic_printers()
Registers default printers for common scalar types.
Definition NavState.hpp:546
void set(const std::string &key, const T &value)
Stores a value of type T associated with key (by copy).
Definition NavState.hpp:109
bool has_group(const std::string &key) const
Checks whether key exists in the state.
Definition NavState.hpp:416
std::vector< std::string > get_group_keys(const std::string &group_key) const
Member keys of group_key, as recorded by set_group() (without dereferencing them).
Definition NavState.hpp:437
void add_to_group(const std::string &group_key, const std::string &key)
Adds key to the group group_key (created if missing), atomically: safe when several threads add membe...
Definition NavState.hpp:423
static void register_printer(std::function< std::string(const T &)> printer)
Registers a pretty-printer for type T used by debug_string().
Definition NavState.hpp:458
std::vector< std::shared_ptr< T > > get_by_type() const
Retrieves all stored values of type T, regardless of their key.
Definition NavState.hpp:329
NavState()
Constructs an empty NavState and registers basic type printers.
Definition NavState.hpp:90
virtual ~NavState()=default
Destructor.
bool has(const std::string &key) const
Checks whether key exists in the state.
Definition NavState.hpp:408
std::string debug_string() const
Generates a human-readable dump of all stored keys and values.
Definition NavState.hpp:474
T get_safe(const std::string &key) const
Retrieves a snapshot copy of the stored value of type T for key.
Definition NavState.hpp:235
const std::vector< std::shared_ptr< T > > get_group(const std::string &group_key) const
Retrieves a vector of values (pointers) from a group by the group key.
Definition NavState.hpp:294
Definition CircularBuffer.hpp:24
std::string stacktrace(std::size_t skip=1, std::size_t max_frames=64)
Captures a C/C++ stack trace as a string.
Definition NavState.hpp:63
std::string demangle(const char *name)
Demangles a C++ RTTI type name if possible.
Definition NavState.hpp:50