-
Notifications
You must be signed in to change notification settings - Fork 715
/
Copy pathblackboard.h
362 lines (302 loc) · 9.87 KB
/
blackboard.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
#pragma once
#include <string>
#include <memory>
#include <unordered_map>
#include <mutex>
#include "behaviortree_cpp/basic_types.h"
#include "behaviortree_cpp/contrib/json.hpp"
#include "behaviortree_cpp/utils/safe_any.hpp"
#include "behaviortree_cpp/exceptions.h"
#include "behaviortree_cpp/utils/locked_reference.hpp"
namespace BT
{
/// This type contains a pointer to Any, protected
/// with a locked mutex as long as the object is in scope
using AnyPtrLocked = LockedPtr<Any>;
template <typename T>
struct StampedValue
{
T value;
Timestamp stamp;
};
/**
* @brief The Blackboard is the mechanism used by BehaviorTrees to exchange
* typed data.
*/
class Blackboard
{
public:
using Ptr = std::shared_ptr<Blackboard>;
protected:
// This is intentionally protected. Use Blackboard::create instead
Blackboard(Blackboard::Ptr parent) : parent_bb_(parent)
{}
public:
struct Entry
{
Any value;
TypeInfo info;
StringConverter string_converter;
mutable std::mutex entry_mutex;
uint64_t sequence_id = 0;
// timestamp since epoch
std::chrono::nanoseconds stamp = std::chrono::nanoseconds{ 0 };
Entry(const TypeInfo& _info) : info(_info)
{}
Entry& operator=(const Entry& other);
};
/** Use this static method to create an instance of the BlackBoard
* to share among all your NodeTrees.
*/
static Blackboard::Ptr create(Blackboard::Ptr parent = {})
{
return std::shared_ptr<Blackboard>(new Blackboard(parent));
}
virtual ~Blackboard() = default;
void enableAutoRemapping(bool remapping);
[[nodiscard]] const std::shared_ptr<Entry> getEntry(const std::string& key) const;
[[nodiscard]] std::shared_ptr<Blackboard::Entry> getEntry(const std::string& key);
[[nodiscard]] AnyPtrLocked getAnyLocked(const std::string& key);
[[nodiscard]] AnyPtrLocked getAnyLocked(const std::string& key) const;
[[deprecated("Use getAnyLocked instead")]] const Any*
getAny(const std::string& key) const;
[[deprecated("Use getAnyLocked instead")]] Any* getAny(const std::string& key);
/** Return true if the entry with the given key was found.
* Note that this method may throw an exception if the cast to T failed.
*/
template <typename T>
[[nodiscard]] bool get(const std::string& key, T& value) const;
template <typename T>
[[nodiscard]] Expected<Timestamp> getStamped(const std::string& key, T& value) const;
/**
* Version of get() that throws if it fails.
*/
template <typename T>
[[nodiscard]] T get(const std::string& key) const;
template <typename T>
[[nodiscard]] Expected<StampedValue<T>> getStamped(const std::string& key) const;
/// Update the entry with the given key
template <typename T>
void set(const std::string& key, const T& value);
void unset(const std::string& key);
[[nodiscard]] const TypeInfo* entryInfo(const std::string& key);
void addSubtreeRemapping(StringView internal, StringView external);
void debugMessage() const;
[[nodiscard]] std::vector<StringView> getKeys() const;
[[deprecated("This command is unsafe. Consider using Backup/Restore instead")]] void
clear();
[[deprecated("Use getAnyLocked to access safely an Entry")]] std::recursive_mutex&
entryMutex() const;
void createEntry(const std::string& key, const TypeInfo& info);
/**
* @brief cloneInto copies the values of the entries
* into another blackboard. Known limitations:
*
* - it doesn't update the remapping in dst
* - it doesn't change the parent blackboard os dst
*
* @param dst destination, i.e. blackboard to be updated
*/
void cloneInto(Blackboard& dst) const;
Blackboard::Ptr parent();
// recursively look for parent Blackboard, until you find the root
Blackboard* rootBlackboard();
const Blackboard* rootBlackboard() const;
private:
mutable std::mutex mutex_;
mutable std::recursive_mutex entry_mutex_;
std::unordered_map<std::string, std::shared_ptr<Entry>> storage_;
std::weak_ptr<Blackboard> parent_bb_;
std::unordered_map<std::string, std::string> internal_to_external_;
std::shared_ptr<Entry> createEntryImpl(const std::string& key, const TypeInfo& info);
bool autoremapping_ = false;
};
/**
* @brief ExportBlackboardToJSON will create a JSON
* that contains the current values of the blackboard.
* Complex types must be registered with JsonExporter::get()
*/
nlohmann::json ExportBlackboardToJSON(const Blackboard& blackboard);
/**
* @brief ImportBlackboardFromJSON will append elements to the blackboard,
* using the values parsed from the JSON file created using ExportBlackboardToJSON.
* Complex types must be registered with JsonExporter::get()
*/
void ImportBlackboardFromJSON(const nlohmann::json& json, Blackboard& blackboard);
//------------------------------------------------------
template <typename T>
inline T Blackboard::get(const std::string& key) const
{
if(auto any_ref = getAnyLocked(key))
{
const auto& any = any_ref.get();
if(any->empty())
{
throw RuntimeError("Blackboard::get() error. Entry [", key,
"] hasn't been initialized, yet");
}
return any_ref.get()->cast<T>();
}
throw RuntimeError("Blackboard::get() error. Missing key [", key, "]");
}
inline void Blackboard::unset(const std::string& key)
{
std::unique_lock lock(mutex_);
// check local storage
auto it = storage_.find(key);
if(it == storage_.end())
{
// No entry, nothing to do.
return;
}
storage_.erase(it);
}
template <typename T>
inline void Blackboard::set(const std::string& key, const T& value)
{
if(StartWith(key, '@'))
{
rootBlackboard()->set(key.substr(1, key.size() - 1), value);
return;
}
std::unique_lock lock(mutex_);
// check local storage
auto it = storage_.find(key);
if(it == storage_.end())
{
// create a new entry
Any new_value(value);
lock.unlock();
std::shared_ptr<Blackboard::Entry> entry;
// if a new generic port is created with a string, it's type should be AnyTypeAllowed
if constexpr(std::is_same_v<std::string, T>)
{
entry = createEntryImpl(key, PortInfo(PortDirection::INOUT));
}
else
{
PortInfo new_port(PortDirection::INOUT, new_value.type(),
GetAnyFromStringFunctor<T>());
entry = createEntryImpl(key, new_port);
}
lock.lock();
entry->value = new_value;
entry->sequence_id++;
entry->stamp = std::chrono::steady_clock::now().time_since_epoch();
}
else
{
// this is not the first time we set this entry, we need to check
// if the type is the same or not.
Entry& entry = *it->second;
std::scoped_lock scoped_lock(entry.entry_mutex);
Any& previous_any = entry.value;
Any new_value(value);
// special case: entry exists but it is not strongly typed... yet
if(!entry.info.isStronglyTyped())
{
// Use the new type to create a new entry that is strongly typed.
entry.info = TypeInfo::Create<T>();
entry.sequence_id++;
entry.stamp = std::chrono::steady_clock::now().time_since_epoch();
previous_any = std::move(new_value);
return;
}
std::type_index previous_type = entry.info.type();
// allow matching if any is of the same base
const auto current_type = TypeInfo::Create<T>().type();
// check type mismatch
if(previous_type != current_type && previous_type != new_value.type())
{
bool mismatching = true;
if(std::is_constructible<StringView, T>::value)
{
Any any_from_string = entry.info.parseString(value);
if(any_from_string.empty() == false)
{
mismatching = false;
new_value = std::move(any_from_string);
}
}
// check if we are doing a safe cast between numbers
// for instance, it is safe to use int(100) to set
// a uint8_t port, but not int(-42) or int(300)
if constexpr(std::is_arithmetic_v<T>)
{
if(mismatching && isCastingSafe(previous_type, value))
{
mismatching = false;
}
}
if(mismatching)
{
debugMessage();
auto msg = StrCat("Blackboard::set(", key,
"): once declared, "
"the type of a port shall not change. "
"Previously declared type [",
BT::demangle(previous_type), "], current type [",
BT::demangle(typeid(T)), "]");
throw LogicError(msg);
}
}
// if doing set<BT::Any>, skip type check
if constexpr(std::is_same_v<Any, T>)
{
previous_any = new_value;
}
else
{
// copy only if the type is compatible
new_value.copyInto(previous_any);
}
entry.sequence_id++;
entry.stamp = std::chrono::steady_clock::now().time_since_epoch();
}
}
template <typename T>
inline bool Blackboard::get(const std::string& key, T& value) const
{
if(auto any_ref = getAnyLocked(key))
{
if(any_ref.get()->empty())
{
return false;
}
value = any_ref.get()->cast<T>();
return true;
}
return false;
}
template <typename T>
inline Expected<Timestamp> Blackboard::getStamped(const std::string& key, T& value) const
{
if(auto entry = getEntry(key))
{
std::unique_lock lk(entry->entry_mutex);
if(entry->value.empty())
{
return nonstd::make_unexpected(StrCat("Blackboard::getStamped() error. Entry [",
key, "] hasn't been initialized, yet"));
}
value = entry->value.cast<T>();
return Timestamp{ entry->sequence_id, entry->stamp };
}
return nonstd::make_unexpected(
StrCat("Blackboard::getStamped() error. Missing key [", key, "]"));
}
template <typename T>
inline Expected<StampedValue<T>> Blackboard::getStamped(const std::string& key) const
{
StampedValue<T> out;
if(auto res = getStamped<T>(key, out.value))
{
out.stamp = *res;
return out;
}
else
{
return nonstd::make_unexpected(res.error());
}
}
} // namespace BT