|
1 | 1 | // Copyright (c) 2009, Willow Garage, Inc. |
| 2 | +// Copyright (c) 2024, Lennart Nachtigall |
2 | 3 | // |
3 | 4 | // Redistribution and use in source and binary forms, with or without |
4 | 5 | // modification, are permitted provided that the following conditions are met: |
|
27 | 28 | // POSSIBILITY OF SUCH DAMAGE. |
28 | 29 |
|
29 | 30 | // Author: Stuart Glaser |
| 31 | +// Author: Lennart Nachtigall |
30 | 32 |
|
31 | | -#ifndef REALTIME_TOOLS__REALTIME_BOX_H__ |
32 | | -#define REALTIME_TOOLS__REALTIME_BOX_H__ |
| 33 | +#ifndef REALTIME_TOOLS__REALTIME_BOX_H_ |
| 34 | +#define REALTIME_TOOLS__REALTIME_BOX_H_ |
33 | 35 |
|
| 36 | +#include <functional> |
| 37 | +#include <initializer_list> |
34 | 38 | #include <mutex> |
35 | | -#include <string> |
| 39 | +#include <optional> |
| 40 | +#include <utility> |
| 41 | + |
| 42 | +#include <rcpputils/pointer_traits.hpp> |
36 | 43 |
|
37 | 44 | namespace realtime_tools |
38 | 45 | { |
39 | | -/*! |
40 | 46 |
|
41 | | - Strongly suggested that you use an std::shared_ptr in this box to |
42 | | - guarantee realtime safety. |
| 47 | +template <typename T> |
| 48 | +constexpr auto is_ptr_or_smart_ptr = rcpputils::is_pointer<T>::value; |
| 49 | + |
| 50 | +/*! |
| 51 | + A Box that ensures thread safe access to the boxed contents. |
| 52 | + Access is best effort. If it can not lock it will return. |
43 | 53 |
|
44 | | - */ |
45 | | -template <class T> |
46 | | -class RealtimeBox |
| 54 | + NOTE about pointers: |
| 55 | + You can use pointers with this box but the access will be different. |
| 56 | + Only use the get/set methods that take function pointer for accessing the internal value. |
| 57 | +*/ |
| 58 | +template <class T, typename mutex_type = std::mutex> |
| 59 | +class RealtimeBoxBase |
47 | 60 | { |
| 61 | + static_assert(std::is_copy_constructible_v<T>, "Passed type must be copy constructible"); |
| 62 | + |
48 | 63 | public: |
49 | | - explicit RealtimeBox(const T & initial = T()) : thing_(initial) {} |
| 64 | + using mutex_t = mutex_type; |
| 65 | + using type = T; |
| 66 | + // Provide various constructors |
| 67 | + constexpr explicit RealtimeBoxBase(const T & init = T{}) : value_(init) {} |
| 68 | + constexpr explicit RealtimeBoxBase(const T && init) : value_(std::move(init)) {} |
50 | 69 |
|
51 | | - void set(const T & value) |
| 70 | + // Copy constructor |
| 71 | + constexpr RealtimeBoxBase(const RealtimeBoxBase & o) |
52 | 72 | { |
53 | | - std::lock_guard<std::mutex> guard(thing_lock_RT_); |
54 | | - thing_ = value; |
| 73 | + // Lock the other box mutex |
| 74 | + std::unique_lock<mutex_t> lock(o.lock_); |
| 75 | + // We do not need to lock our own mutex because we are currently in the process of being created |
| 76 | + value_ = o.value_; |
55 | 77 | } |
| 78 | + // Copy assignment constructor |
| 79 | + constexpr RealtimeBoxBase & operator=(const RealtimeBoxBase & o) |
| 80 | + { |
| 81 | + // Check for self assignment (and a potential deadlock) |
| 82 | + if (&o != this) { |
| 83 | + // Lock the other box mutex |
| 84 | + std::unique_lock<mutex_t> lock_other(o.lock_); |
| 85 | + std::unique_lock<mutex_t> lock_self(lock_); |
56 | 86 |
|
57 | | - void get(T & ref) |
| 87 | + value_ = o.value_; |
| 88 | + } |
| 89 | + return *this; |
| 90 | + } |
| 91 | + constexpr RealtimeBoxBase(RealtimeBoxBase && o) |
58 | 92 | { |
59 | | - std::lock_guard<std::mutex> guard(thing_lock_RT_); |
60 | | - ref = thing_; |
| 93 | + // Lock the other box mutex |
| 94 | + std::unique_lock<mutex_t> lock(o.lock_); |
| 95 | + // We do not need to lock our own mutex because we are currently in the process of being created |
| 96 | + value_ = std::move(o.value_); |
61 | 97 | } |
| 98 | + // Only enabled for types that can be constructed from an initializer list |
| 99 | + template <typename U = T> |
| 100 | + constexpr RealtimeBoxBase( |
| 101 | + const std::initializer_list<U> & init, |
| 102 | + std::enable_if_t<std::is_constructible_v<U, std::initializer_list<U>>>) |
| 103 | + : value_(init) |
| 104 | + { |
| 105 | + } |
| 106 | + constexpr RealtimeBoxBase & operator=(RealtimeBoxBase && o) |
| 107 | + { |
| 108 | + // Check for self assignment (and a potential deadlock) |
| 109 | + if (&o != this) { |
| 110 | + // Lock the other box mutex |
| 111 | + std::unique_lock<mutex_t> lock_other(o.lock_); |
| 112 | + std::unique_lock<mutex_t> lock_self(lock_); |
| 113 | + |
| 114 | + value_ = std::move(o.value_); |
| 115 | + } |
| 116 | + return *this; |
| 117 | + } |
| 118 | + |
| 119 | + /** |
| 120 | + * @brief set a new content with best effort |
| 121 | + * @return false if mutex could not be locked |
| 122 | + * @note disabled for pointer types |
| 123 | + */ |
| 124 | + template <typename U = T> |
| 125 | + typename std::enable_if_t<!is_ptr_or_smart_ptr<U>, bool> try_set(const T & value) |
| 126 | + { |
| 127 | + std::unique_lock<mutex_t> guard(lock_, std::defer_lock); |
| 128 | + if (!guard.try_lock()) { |
| 129 | + return false; |
| 130 | + } |
| 131 | + value_ = value; |
| 132 | + return true; |
| 133 | + } |
| 134 | + /** |
| 135 | + * @brief access the content readable with best effort |
| 136 | + * @return false if the mutex could not be locked |
| 137 | + * @note only safe way to access pointer type content (rw) |
| 138 | + */ |
| 139 | + bool try_set(const std::function<void(T &)> & func) |
| 140 | + { |
| 141 | + std::unique_lock<mutex_t> guard(lock_, std::defer_lock); |
| 142 | + if (!guard.try_lock()) { |
| 143 | + return false; |
| 144 | + } |
| 145 | + |
| 146 | + func(value_); |
| 147 | + return true; |
| 148 | + } |
| 149 | + /** |
| 150 | + * @brief get the content with best effort |
| 151 | + * @return std::nullopt if content could not be access, otherwise the content is returned |
| 152 | + */ |
| 153 | + template <typename U = T> |
| 154 | + [[nodiscard]] typename std::enable_if_t<!is_ptr_or_smart_ptr<U>, std::optional<U>> try_get() const |
| 155 | + { |
| 156 | + std::unique_lock<mutex_t> guard(lock_, std::defer_lock); |
| 157 | + if (!guard.try_lock()) { |
| 158 | + return std::nullopt; |
| 159 | + } |
| 160 | + return value_; |
| 161 | + } |
| 162 | + /** |
| 163 | + * @brief access the content (r) with best effort |
| 164 | + * @return false if the mutex could not be locked |
| 165 | + * @note only safe way to access pointer type content (r) |
| 166 | + */ |
| 167 | + bool try_get(const std::function<void(const T &)> & func) |
| 168 | + { |
| 169 | + std::unique_lock<mutex_t> guard(lock_, std::defer_lock); |
| 170 | + if (!guard.try_lock()) { |
| 171 | + return false; |
| 172 | + } |
| 173 | + |
| 174 | + func(value_); |
| 175 | + return true; |
| 176 | + } |
| 177 | + |
| 178 | + /** |
| 179 | + * @brief set the content and wait until the mutex could be locked (RealtimeBox behavior) |
| 180 | + * @return true |
| 181 | + */ |
| 182 | + template <typename U = T> |
| 183 | + typename std::enable_if_t<!is_ptr_or_smart_ptr<U>, void> set(const T & value) |
| 184 | + { |
| 185 | + std::lock_guard<mutex_t> guard(lock_); |
| 186 | + // cppcheck-suppress missingReturn |
| 187 | + value_ = value; |
| 188 | + } |
| 189 | + /** |
| 190 | + * @brief access the content (rw) and wait until the mutex could locked |
| 191 | + */ |
| 192 | + void set(const std::function<void(T &)> & func) |
| 193 | + { |
| 194 | + std::lock_guard<mutex_t> guard(lock_); |
| 195 | + func(value_); |
| 196 | + } |
| 197 | + |
| 198 | + /** |
| 199 | + * @brief get the content and wait until the mutex could be locked (RealtimeBox behaviour) |
| 200 | + * @return copy of the value |
| 201 | + */ |
| 202 | + template <typename U = T> |
| 203 | + [[nodiscard]] typename std::enable_if_t<!is_ptr_or_smart_ptr<U>, U> get() const |
| 204 | + { |
| 205 | + std::lock_guard<mutex_t> guard(lock_); |
| 206 | + return value_; |
| 207 | + } |
| 208 | + /** |
| 209 | + * @brief get the content and wait until the mutex could be locked |
| 210 | + * @note same signature as in the existing RealtimeBox<T> |
| 211 | + */ |
| 212 | + template <typename U = T> |
| 213 | + typename std::enable_if_t<!is_ptr_or_smart_ptr<U>, void> get(T & in) const |
| 214 | + { |
| 215 | + std::lock_guard<mutex_t> guard(lock_); |
| 216 | + // cppcheck-suppress missingReturn |
| 217 | + in = value_; |
| 218 | + } |
| 219 | + /** |
| 220 | + * @brief access the content (r) and wait until the mutex could be locked |
| 221 | + * @note only safe way to access pointer type content (r) |
| 222 | + * @note same signature as in the existing RealtimeBox<T> |
| 223 | + */ |
| 224 | + void get(const std::function<void(const T &)> & func) |
| 225 | + { |
| 226 | + std::lock_guard<mutex_t> guard(lock_); |
| 227 | + func(value_); |
| 228 | + } |
| 229 | + |
| 230 | + /** |
| 231 | + * @brief provide a custom assignment operator for easier usage |
| 232 | + * @note only to be used from non-RT! |
| 233 | + */ |
| 234 | + template <typename U = T> |
| 235 | + typename std::enable_if_t<!is_ptr_or_smart_ptr<U>, void> operator=(const T & value) |
| 236 | + { |
| 237 | + set(value); |
| 238 | + } |
| 239 | + |
| 240 | + /** |
| 241 | + * @brief provide a custom conversion operator |
| 242 | + * @note Can only be used from non-RT! |
| 243 | + */ |
| 244 | + template <typename U = T, typename = typename std::enable_if_t<!is_ptr_or_smart_ptr<U>>> |
| 245 | + [[nodiscard]] operator T() const |
| 246 | + { |
| 247 | + // Only makes sense with the getNonRT method otherwise we would return an std::optional |
| 248 | + return get(); |
| 249 | + } |
| 250 | + /** |
| 251 | + * @brief provide a custom conversion operator |
| 252 | + * @note Can be used from non-RT and RT contexts |
| 253 | + */ |
| 254 | + template <typename U = T, typename = typename std::enable_if_t<!is_ptr_or_smart_ptr<U>>> |
| 255 | + [[nodiscard]] operator std::optional<T>() const |
| 256 | + { |
| 257 | + return try_get(); |
| 258 | + } |
| 259 | + |
| 260 | + // In case one wants to actually use a pointer |
| 261 | + // in this implementation we allow accessing the lock directly. |
| 262 | + // Note: Be careful with lock.unlock(). |
| 263 | + // It may only be called from the thread that locked the mutex! |
| 264 | + [[nodiscard]] const mutex_t & get_mutex() const { return lock_; } |
| 265 | + [[nodiscard]] mutex_t & get_mutex() { return lock_; } |
62 | 266 |
|
63 | 267 | private: |
64 | | - // The thing that's in the box. |
65 | | - T thing_; |
| 268 | + T value_; |
66 | 269 |
|
67 | 270 | // Protects access to the thing in the box. This mutex is |
68 | 271 | // guaranteed to be locked for no longer than the duration of the |
69 | 272 | // copy, so as long as the copy is realtime safe and the OS has |
70 | 273 | // priority inheritance for mutexes, this lock can be safely locked |
71 | 274 | // from within realtime. |
72 | | - std::mutex thing_lock_RT_; |
| 275 | + mutable mutex_t lock_; |
73 | 276 | }; |
74 | 277 |
|
| 278 | +// Introduce some easier to use names |
| 279 | + |
| 280 | +// Only kept for compatibility reasons |
| 281 | +template <typename T, typename mutex_type = std::mutex> |
| 282 | +using RealtimeBoxBestEffort [[deprecated("Use RealtimeBox instead")]] = |
| 283 | + RealtimeBoxBase<T, mutex_type>; |
| 284 | + |
| 285 | +// Provide specialisations for different mutex types |
| 286 | +template <typename T> |
| 287 | +using RealtimeBoxStandard = RealtimeBoxBase<T, std::mutex>; |
| 288 | + |
| 289 | +template <typename T> |
| 290 | +using RealtimeBoxRecursive = RealtimeBoxBase<T, std::recursive_mutex>; |
| 291 | + |
| 292 | +// This is the specialisation we recommend to use in the end |
| 293 | +template <typename T> |
| 294 | +using RealtimeBox = RealtimeBoxStandard<T>; |
| 295 | + |
75 | 296 | } // namespace realtime_tools |
76 | 297 |
|
77 | 298 | #endif // REALTIME_TOOLS__REALTIME_BOX_H_ |
0 commit comments