BehaviorTree
Core Library to create and execute Behavior Trees
Loading...
Searching...
No Matches
blackboard.h
1#pragma once
2
3#include "behaviortree_cpp/basic_types.h"
4#ifdef BTCPP_SYSTEM_JSON
5#include <nlohmann/json.hpp>
6#else
7#include "behaviortree_cpp/contrib/json.hpp"
8#endif
9#include "behaviortree_cpp/exceptions.h"
10#include "behaviortree_cpp/utils/locked_reference.hpp"
11#include "behaviortree_cpp/utils/polymorphic_cast_registry.hpp"
12#include "behaviortree_cpp/utils/safe_any.hpp"
13
14#include <memory>
15#include <mutex>
16#include <shared_mutex>
17#include <string>
18#include <unordered_map>
19
20namespace BT
21{
22
23/// This type contains a pointer to Any, protected
24/// with a locked mutex as long as the object is in scope
25using AnyPtrLocked = LockedPtr<Any>;
26
27template <typename T>
28struct StampedValue
29{
30 T value;
31 Timestamp stamp;
32};
33
34/**
35 * @brief The Blackboard is the mechanism used by BehaviorTrees to exchange
36 * typed data.
37 */
38class Blackboard
39{
40public:
41 using Ptr = std::shared_ptr<Blackboard>;
42
43protected:
44 // This is intentionally protected. Use Blackboard::create instead
45 Blackboard(Blackboard::Ptr parent) : parent_bb_(parent)
46 {}
47
48public:
49 Blackboard(const Blackboard&) = delete;
50 Blackboard& operator=(const Blackboard&) = delete;
51 Blackboard(Blackboard&&) = delete;
52 Blackboard& operator=(Blackboard&&) = delete;
53
54 struct Entry
55 {
56 Any value;
57 TypeInfo info;
58 StringConverter string_converter;
59 mutable std::mutex entry_mutex;
60
61 uint64_t sequence_id = 0;
62 // timestamp since epoch
63 std::chrono::nanoseconds stamp = std::chrono::nanoseconds{ 0 };
64
65 Entry(const TypeInfo& _info) : info(_info)
66 {}
67
68 ~Entry() = default;
69 Entry(const Entry&) = delete;
70 Entry& operator=(const Entry&) = delete;
71 Entry(Entry&&) = delete;
72 Entry& operator=(Entry&&) = delete;
73 };
74
75 /** Use this static method to create an instance of the BlackBoard
76 * to share among all your NodeTrees.
77 */
78 static Blackboard::Ptr create(Blackboard::Ptr parent = {})
79 {
80 return std::shared_ptr<Blackboard>(new Blackboard(parent));
81 }
82
83 virtual ~Blackboard() = default;
84
85 void enableAutoRemapping(bool remapping);
86
87 [[nodiscard]] const std::shared_ptr<Entry> getEntry(const std::string& key) const;
88
89 [[nodiscard]] std::shared_ptr<Blackboard::Entry> getEntry(const std::string& key);
90
91 [[nodiscard]] AnyPtrLocked getAnyLocked(const std::string& key);
92
93 [[nodiscard]] AnyPtrLocked getAnyLocked(const std::string& key) const;
94
95 [[deprecated("Use getAnyLocked instead")]] const Any*
96 getAny(const std::string& key) const;
97
98 [[deprecated("Use getAnyLocked instead")]] Any* getAny(const std::string& key);
99
100 /** Return true if the entry with the given key was found.
101 * Note that this method may throw an exception if the cast to T failed.
102 */
103 template <typename T>
104 [[nodiscard]] bool get(const std::string& key, T& value) const;
105
106 template <typename T>
107 [[nodiscard]] Expected<Timestamp> getStamped(const std::string& key, T& value) const;
108
109 /**
110 * Version of get() that throws if it fails.
111 */
112 template <typename T>
113 [[nodiscard]] T get(const std::string& key) const;
114
115 template <typename T>
116 [[nodiscard]] Expected<StampedValue<T>> getStamped(const std::string& key) const;
117
118 /// Update the entry with the given key
119 template <typename T>
120 void set(const std::string& key, const T& value);
121
122 void unset(const std::string& key);
123
124 [[nodiscard]] const TypeInfo* entryInfo(const std::string& key);
125
126 void addSubtreeRemapping(StringView internal, StringView external);
127
128 void debugMessage() const;
129
130 /// The names of all the entries stored in this blackboard (copies).
131 [[nodiscard]] std::vector<std::string> getKeyNames() const;
132
133 // The views point into the storage: they dangle as soon as another thread
134 // removes the entry.
135 [[deprecated(
136 "The views may dangle: use getKeyNames")]] [[nodiscard]] std::vector<StringView>
137 getKeys() const;
138
139 [[deprecated("This command is unsafe. Consider using Backup/Restore instead")]] void
140 clear();
141
142 void createEntry(const std::string& key, const TypeInfo& info);
143
144 /**
145 * @brief cloneInto copies the values of the entries
146 * into another blackboard. Known limitations:
147 *
148 * - it doesn't update the remapping in dst
149 * - it doesn't change the parent blackboard os dst
150 *
151 * @param dst destination, i.e. blackboard to be updated
152 */
153 void cloneInto(Blackboard& dst) const;
154
155 Blackboard::Ptr parent();
156
157 // recursively look for parent Blackboard, until you find the root
158 Blackboard* rootBlackboard();
159
160 const Blackboard* rootBlackboard() const;
161
162 /**
163 * @brief Set the polymorphic cast registry for this blackboard.
164 *
165 * The registry enables polymorphic shared_ptr conversions during get().
166 * This is typically set automatically when creating trees via BehaviorTreeFactory.
167 */
168 void setPolymorphicCastRegistry(std::shared_ptr<PolymorphicCastRegistry> registry)
169 {
170 polymorphic_registry_ = std::move(registry);
171 }
172
173 /**
174 * @brief Get the polymorphic cast registry (may be null).
175 */
177 {
178 return polymorphic_registry_.get();
179 }
180
181 /**
182 * @brief Cast Any value with polymorphic fallback for shared_ptr types.
183 *
184 * First attempts a direct cast. If that fails and T is a shared_ptr type,
185 * tries a polymorphic cast via the registry. Returns Expected with error on failure.
186 */
187 template <typename T>
188 [[nodiscard]] Expected<T> tryCastWithPolymorphicFallback(const Any* any) const;
189
190private:
191 mutable std::shared_mutex storage_mutex_;
192 std::unordered_map<std::string, std::shared_ptr<Entry>> storage_;
193 std::weak_ptr<Blackboard> parent_bb_;
194 std::unordered_map<std::string, std::string> internal_to_external_;
195
196 std::shared_ptr<Entry> createEntryImpl(const std::string& key, const TypeInfo& info);
197
198 bool autoremapping_ = false;
199
200 // Optional registry for polymorphic shared_ptr conversions
201 std::shared_ptr<PolymorphicCastRegistry> polymorphic_registry_;
202};
203
204/**
205 * @brief ExportBlackboardToJSON will create a JSON
206 * that contains the current values of the blackboard.
207 * Complex types must be registered with JsonExporter::get()
208 */
210
211/**
212 * @brief ImportBlackboardFromJSON will append elements to the blackboard,
213 * using the values parsed from the JSON file created using ExportBlackboardToJSON.
214 * Complex types must be registered with JsonExporter::get()
215 */
216void ImportBlackboardFromJSON(const nlohmann::json& json, Blackboard& blackboard);
217
218//------------------------------------------------------
219
220template <typename T>
222{
223 // Try direct cast first
224 auto result = any->tryCast<T>();
225 if(result)
226 {
227 return result.value();
228 }
229
230 // For shared_ptr types, try polymorphic cast via registry (Issue #943)
231 if constexpr(is_shared_ptr<T>::value)
232 {
233 if(polymorphic_registry_)
234 {
235 auto poly_result = any->tryCastWithRegistry<T>(*polymorphic_registry_);
236 if(poly_result)
237 {
238 return poly_result.value();
239 }
240 }
241 }
242
243 return nonstd::make_unexpected(result.error());
244}
245
246template <typename T>
247inline T Blackboard::get(const std::string& key) const
248{
249 if(auto any_ref = getAnyLocked(key))
250 {
251 const auto& any = any_ref.get();
252 if(any->empty())
253 {
254 throw RuntimeError("Blackboard::get() error. Entry [", key,
255 "] hasn't been initialized, yet");
256 }
257 auto result = tryCastWithPolymorphicFallback<T>(any);
258 if(!result)
259 {
260 throw std::runtime_error(result.error());
261 }
262 return result.value();
263 }
264 throw RuntimeError("Blackboard::get() error. Missing key [", key, "]");
265}
266
267inline void Blackboard::unset(const std::string& key)
268{
269 // the entry (i.e. the stored value) is destroyed outside the lock
270 std::shared_ptr<Entry> removed;
271 std::unique_lock storage_lock(storage_mutex_);
272
273 // check local storage
274 auto it = storage_.find(key);
275 if(it == storage_.end())
276 {
277 // No entry, nothing to do.
278 return;
279 }
280
281 removed = std::move(it->second);
282 storage_.erase(it);
283}
284
285template <typename T>
286inline void Blackboard::set(const std::string& key, const T& value)
287{
288 if(StartWith(key, '@'))
289 {
290 rootBlackboard()->set(key.substr(1, key.size() - 1), value);
291 return;
292 }
293 // getEntry() follows the remapping to the parent blackboard, exactly like
294 // the readers do. A key that resolves to an existing entry (local or
295 // remapped) must go through the type check below: createEntryImpl() would
296 // follow the same remapping and return the pre-existing entry, and writing
297 // to it blindly bypasses the type lock and the string conversion.
298 auto entry_ptr = getEntry(key);
299 if(!entry_ptr)
300 {
301 // create a new entry
302 Any new_value(value);
303 std::shared_ptr<Blackboard::Entry> entry;
304 // if a new generic port is created with a string, it's type should be AnyTypeAllowed
305 if constexpr(std::is_same_v<std::string, T>)
306 {
307 entry = createEntryImpl(key, PortInfo(PortDirection::INOUT));
308 }
309 else
310 {
311 PortInfo new_port(PortDirection::INOUT, new_value.type(),
312 GetAnyFromStringFunctor<T>());
313 entry = createEntryImpl(key, new_port);
314 }
315
316 // Lock entry_mutex before writing to prevent data races with
317 // concurrent readers (BUG-1/BUG-8 fix).
318 std::scoped_lock entry_lock(entry->entry_mutex);
319 entry->value = new_value;
320 entry->sequence_id++;
321 entry->stamp = std::chrono::steady_clock::now().time_since_epoch();
322 }
323 else
324 {
325 // this is not the first time we set this entry, we need to check
326 // if the type is the same or not.
327 Entry& entry = *entry_ptr;
328
329 std::scoped_lock scoped_lock(entry.entry_mutex);
330
331 Any& previous_any = entry.value;
332 Any new_value(value);
333
334 // special case: entry exists but it is not strongly typed... yet
335 if(!entry.info.isStronglyTyped())
336 {
337 // Use the new type to create a new entry that is strongly typed.
338 entry.info = TypeInfo::Create<T>();
339 entry.sequence_id++;
340 entry.stamp = std::chrono::steady_clock::now().time_since_epoch();
341 previous_any = std::move(new_value);
342 return;
343 }
344
345 std::type_index previous_type = entry.info.type();
346
347 // check type mismatch
348 if(previous_type != std::type_index(typeid(T)) && previous_type != new_value.type())
349 {
350 bool mismatching = true;
351 if(std::is_constructible<StringView, T>::value)
352 {
353 Any any_from_string = entry.info.parseString(value);
354 if(any_from_string.empty() == false)
355 {
356 mismatching = false;
357 new_value = std::move(any_from_string);
358 }
359 }
360 // check if we are doing a safe cast between numbers
361 // for instance, it is safe to use int(100) to set
362 // a uint8_t port, but not int(-42) or int(300)
363 if constexpr(std::is_arithmetic_v<T>)
364 {
365 if(mismatching && isCastingSafe(previous_type, value))
366 {
367 mismatching = false;
368 }
369 }
370
371 if(mismatching)
372 {
373 debugMessage();
374
375 auto msg = StrCat("Blackboard::set(", key,
376 "): once declared, "
377 "the type of a port shall not change. "
378 "Previously declared type [",
379 BT::demangle(previous_type), "], current type [",
380 BT::demangle(typeid(T)), "]");
381 throw LogicError(msg);
382 }
383 }
384 // if doing set<BT::Any>, skip type check
385 if constexpr(std::is_same_v<Any, T>)
386 {
387 previous_any = new_value;
388 }
389 else
390 {
391 // copy only if the type is compatible
392 new_value.copyInto(previous_any);
393 }
394 entry.sequence_id++;
395 entry.stamp = std::chrono::steady_clock::now().time_since_epoch();
396 }
397}
398
399template <typename T>
400inline bool Blackboard::get(const std::string& key, T& value) const
401{
402 if(auto any_ref = getAnyLocked(key))
403 {
404 const auto& any = any_ref.get();
405 if(any->empty())
406 {
407 return false;
408 }
409 auto result = tryCastWithPolymorphicFallback<T>(any);
410 if(!result)
411 {
412 throw std::runtime_error(result.error());
413 }
414 value = result.value();
415 return true;
416 }
417 return false;
418}
419
420template <typename T>
421inline Expected<Timestamp> Blackboard::getStamped(const std::string& key, T& value) const
422{
423 if(auto entry = getEntry(key))
424 {
425 std::unique_lock lk(entry->entry_mutex);
426 if(entry->value.empty())
427 {
428 return nonstd::make_unexpected(StrCat("Blackboard::getStamped() error. Entry [",
429 key, "] hasn't been initialized, yet"));
430 }
431 auto result = tryCastWithPolymorphicFallback<T>(&entry->value);
432 if(result)
433 {
434 value = result.value();
435 return Timestamp{ entry->sequence_id, entry->stamp };
436 }
437 return nonstd::make_unexpected(result.error());
438 }
439 return nonstd::make_unexpected(
440 StrCat("Blackboard::getStamped() error. Missing key [", key, "]"));
441}
442
443template <typename T>
444inline Expected<StampedValue<T>> Blackboard::getStamped(const std::string& key) const
445{
446 StampedValue<T> out;
447 if(auto res = getStamped<T>(key, out.value))
448 {
449 out.stamp = *res;
450 return out;
451 }
452 else
453 {
454 return nonstd::make_unexpected(res.error());
455 }
456}
457
458} // namespace BT
Definition: safe_any.hpp:57
The Blackboard is the mechanism used by BehaviorTrees to exchange typed data.
Definition: blackboard.h:39
void set(const std::string &key, const T &value)
Update the entry with the given key.
Definition: blackboard.h:286
T get(const std::string &key) const
Definition: blackboard.h:247
const PolymorphicCastRegistry * polymorphicCastRegistry() const
Get the polymorphic cast registry (may be null).
Definition: blackboard.h:176
void setPolymorphicCastRegistry(std::shared_ptr< PolymorphicCastRegistry > registry)
Set the polymorphic cast registry for this blackboard.
Definition: blackboard.h:168
std::vector< std::string > getKeyNames() const
The names of all the entries stored in this blackboard (copies).
Expected< T > tryCastWithPolymorphicFallback(const Any *any) const
Cast Any value with polymorphic fallback for shared_ptr types.
Definition: blackboard.h:221
bool get(const std::string &key, T &value) const
Definition: blackboard.h:400
static Blackboard::Ptr create(Blackboard::Ptr parent={})
Definition: blackboard.h:78
void cloneInto(Blackboard &dst) const
cloneInto copies the values of the entries into another blackboard. Known limitations:
The LockedPtr class is used to share a pointer to an object and a mutex that protects the read/write ...
Definition: locked_reference.hpp:22
Registry for polymorphic shared_ptr cast relationships.
Definition: polymorphic_cast_registry.hpp:47
Definition: basic_types.h:424
Definition: basic_types.h:377
Definition: action_node.h:24
void ImportBlackboardFromJSON(const nlohmann::json &json, Blackboard &blackboard)
ImportBlackboardFromJSON will append elements to the blackboard, using the values parsed from the JSO...
nlohmann::json ExportBlackboardToJSON(const Blackboard &blackboard)
ExportBlackboardToJSON will create a JSON that contains the current values of the blackboard....
Definition: blackboard.h:55
Definition: blackboard.h:29
Definition: basic_types.h:357
Definition: safe_any.hpp:46