/src/behaviortreecpp/src/blackboard.cpp
Line | Count | Source |
1 | | #include "behaviortree_cpp/blackboard.h" |
2 | | |
3 | | #include "behaviortree_cpp/json_export.h" |
4 | | |
5 | | #include <tuple> |
6 | | #include <unordered_set> |
7 | | |
8 | | namespace BT |
9 | | { |
10 | | |
11 | | namespace |
12 | | { |
13 | | bool IsPrivateKey(StringView str) |
14 | 0 | { |
15 | 0 | return str.size() >= 1 && str.data()[0] == '_'; |
16 | 0 | } |
17 | | } // namespace |
18 | | |
19 | | void Blackboard::enableAutoRemapping(bool remapping) |
20 | 0 | { |
21 | 0 | autoremapping_ = remapping; |
22 | 0 | } |
23 | | |
24 | | AnyPtrLocked Blackboard::getAnyLocked(const std::string& key) |
25 | 40.8k | { |
26 | 40.8k | if(auto entry = getEntry(key)) |
27 | 39.8k | { |
28 | 39.8k | return AnyPtrLocked(&entry->value, &entry->entry_mutex); |
29 | 39.8k | } |
30 | 1.02k | return {}; |
31 | 40.8k | } |
32 | | |
33 | | AnyPtrLocked Blackboard::getAnyLocked(const std::string& key) const |
34 | 0 | { |
35 | 0 | if(auto entry = getEntry(key)) |
36 | 0 | { |
37 | 0 | return AnyPtrLocked(&entry->value, const_cast<std::mutex*>(&entry->entry_mutex)); |
38 | 0 | } |
39 | 0 | return {}; |
40 | 0 | } |
41 | | |
42 | | const Any* Blackboard::getAny(const std::string& key) const |
43 | 0 | { |
44 | 0 | return getAnyLocked(key).get(); |
45 | 0 | } |
46 | | |
47 | | Any* Blackboard::getAny(const std::string& key) |
48 | 0 | { |
49 | | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) |
50 | 0 | return const_cast<Any*>(getAnyLocked(key).get()); |
51 | 0 | } |
52 | | |
53 | | const std::shared_ptr<Blackboard::Entry> |
54 | | Blackboard::getEntry(const std::string& key) const |
55 | 64.0k | { |
56 | | // special syntax: "@" will always refer to the root BB |
57 | 64.0k | if(StartWith(key, '@')) |
58 | 2.60k | { |
59 | 2.60k | return rootBlackboard()->getEntry(key.substr(1, key.size() - 1)); |
60 | 2.60k | } |
61 | | |
62 | 61.4k | { |
63 | 61.4k | const std::unique_lock<std::mutex> storage_lock(storage_mutex_); |
64 | 61.4k | auto it = storage_.find(key); |
65 | 61.4k | if(it != storage_.end()) |
66 | 57.6k | { |
67 | 57.6k | return it->second; |
68 | 57.6k | } |
69 | 61.4k | } |
70 | | // not found. Try autoremapping |
71 | 3.76k | if(auto parent = parent_bb_.lock()) |
72 | 0 | { |
73 | 0 | auto remap_it = internal_to_external_.find(key); |
74 | 0 | if(remap_it != internal_to_external_.cend()) |
75 | 0 | { |
76 | 0 | auto const& new_key = remap_it->second; |
77 | 0 | return parent->getEntry(new_key); |
78 | 0 | } |
79 | 0 | if(autoremapping_ && !IsPrivateKey(key)) |
80 | 0 | { |
81 | 0 | return parent->getEntry(key); |
82 | 0 | } |
83 | 0 | } |
84 | 3.76k | return {}; |
85 | 3.76k | } |
86 | | |
87 | | std::shared_ptr<Blackboard::Entry> Blackboard::getEntry(const std::string& key) |
88 | 61.4k | { |
89 | 61.4k | return static_cast<const Blackboard&>(*this).getEntry(key); |
90 | 61.4k | } |
91 | | |
92 | | const TypeInfo* Blackboard::entryInfo(const std::string& key) |
93 | 1.41k | { |
94 | 1.41k | auto entry = getEntry(key); |
95 | 1.41k | return (!entry) ? nullptr : &(entry->info); |
96 | 1.41k | } |
97 | | |
98 | | void Blackboard::addSubtreeRemapping(StringView internal, StringView external) |
99 | 0 | { |
100 | 0 | internal_to_external_.insert( |
101 | 0 | { static_cast<std::string>(internal), static_cast<std::string>(external) }); |
102 | 0 | } |
103 | | |
104 | | void Blackboard::debugMessage() const |
105 | 0 | { |
106 | 0 | for(const auto& [key, entry] : storage_) |
107 | 0 | { |
108 | 0 | auto port_type = entry->info.type(); |
109 | 0 | if(port_type == typeid(void)) |
110 | 0 | { |
111 | 0 | port_type = entry->value.type(); |
112 | 0 | } |
113 | |
|
114 | 0 | std::cout << key << " (" << BT::demangle(port_type) << ")" << std::endl; |
115 | 0 | } |
116 | |
|
117 | 0 | for(const auto& [from, to] : internal_to_external_) |
118 | 0 | { |
119 | 0 | std::cout << "[" << from << "] remapped to port of parent tree [" << to << "]" |
120 | 0 | << std::endl; |
121 | 0 | } |
122 | 0 | } |
123 | | |
124 | | std::vector<StringView> Blackboard::getKeys() const |
125 | 0 | { |
126 | 0 | if(storage_.empty()) |
127 | 0 | { |
128 | 0 | return {}; |
129 | 0 | } |
130 | 0 | std::vector<StringView> out; |
131 | 0 | out.reserve(storage_.size()); |
132 | 0 | for(const auto& entry_it : storage_) |
133 | 0 | { |
134 | 0 | out.push_back(entry_it.first); |
135 | 0 | } |
136 | 0 | return out; |
137 | 0 | } |
138 | | |
139 | | void Blackboard::clear() |
140 | 0 | { |
141 | 0 | const std::unique_lock<std::mutex> storage_lock(storage_mutex_); |
142 | 0 | storage_.clear(); |
143 | 0 | } |
144 | | |
145 | | std::recursive_mutex& Blackboard::entryMutex() const |
146 | 0 | { |
147 | 0 | return entry_mutex_; |
148 | 0 | } |
149 | | |
150 | | void Blackboard::createEntry(const std::string& key, const TypeInfo& info) |
151 | 2.56k | { |
152 | 2.56k | if(StartWith(key, '@')) |
153 | 342 | { |
154 | 342 | if(key.find('@', 1) != std::string::npos) |
155 | 0 | { |
156 | 0 | throw LogicError("Character '@' used multiple times in the key"); |
157 | 0 | } |
158 | 342 | rootBlackboard()->createEntryImpl(key.substr(1, key.size() - 1), info); |
159 | 342 | } |
160 | 2.21k | else |
161 | 2.21k | { |
162 | 2.21k | createEntryImpl(key, info); |
163 | 2.21k | } |
164 | 2.56k | } |
165 | | |
166 | | void Blackboard::cloneInto(Blackboard& dst) const |
167 | 0 | { |
168 | | // Lock both mutexes without risking lock-order inversion. |
169 | 0 | std::unique_lock<std::mutex> lk1(storage_mutex_, std::defer_lock); |
170 | 0 | std::unique_lock<std::mutex> lk2(dst.storage_mutex_, std::defer_lock); |
171 | 0 | std::lock(lk1, lk2); |
172 | | |
173 | | // keys that are not updated must be removed. |
174 | 0 | std::unordered_set<std::string> keys_to_remove; |
175 | 0 | auto& dst_storage = dst.storage_; |
176 | 0 | for(const auto& [key, entry] : dst_storage) |
177 | 0 | { |
178 | 0 | std::ignore = entry; // unused in this loop |
179 | 0 | keys_to_remove.insert(key); |
180 | 0 | } |
181 | | |
182 | | // update or create entries in dst_storage |
183 | 0 | for(const auto& [src_key, src_entry] : storage_) |
184 | 0 | { |
185 | 0 | keys_to_remove.erase(src_key); |
186 | |
|
187 | 0 | auto it = dst_storage.find(src_key); |
188 | 0 | if(it != dst_storage.end()) |
189 | 0 | { |
190 | | // overwrite |
191 | 0 | auto& dst_entry = it->second; |
192 | 0 | dst_entry->string_converter = src_entry->string_converter; |
193 | 0 | dst_entry->value = src_entry->value; |
194 | 0 | dst_entry->info = src_entry->info; |
195 | 0 | dst_entry->sequence_id++; |
196 | 0 | dst_entry->stamp = std::chrono::steady_clock::now().time_since_epoch(); |
197 | 0 | } |
198 | 0 | else |
199 | 0 | { |
200 | | // create new |
201 | 0 | auto new_entry = std::make_shared<Entry>(src_entry->info); |
202 | 0 | new_entry->value = src_entry->value; |
203 | 0 | new_entry->string_converter = src_entry->string_converter; |
204 | 0 | dst_storage.insert({ src_key, new_entry }); |
205 | 0 | } |
206 | 0 | } |
207 | |
|
208 | 0 | for(const auto& key : keys_to_remove) |
209 | 0 | { |
210 | 0 | dst_storage.erase(key); |
211 | 0 | } |
212 | 0 | } |
213 | | |
214 | | Blackboard::Ptr Blackboard::parent() |
215 | 0 | { |
216 | 0 | if(auto parent = parent_bb_.lock()) |
217 | 0 | { |
218 | 0 | return parent; |
219 | 0 | } |
220 | 0 | return {}; |
221 | 0 | } |
222 | | |
223 | | std::shared_ptr<Blackboard::Entry> Blackboard::createEntryImpl(const std::string& key, |
224 | | const TypeInfo& info) |
225 | 87.2k | { |
226 | 87.2k | const std::unique_lock<std::mutex> storage_lock(storage_mutex_); |
227 | | // This function might be called recursively, when we do remapping, because we move |
228 | | // to the top scope to find already existing entries |
229 | | |
230 | | // search if exists already |
231 | 87.2k | auto storage_it = storage_.find(key); |
232 | 87.2k | if(storage_it != storage_.end()) |
233 | 0 | { |
234 | 0 | const auto& prev_info = storage_it->second->info; |
235 | 0 | if(prev_info.type() != info.type() && prev_info.isStronglyTyped() && |
236 | 0 | info.isStronglyTyped()) |
237 | 0 | { |
238 | 0 | auto msg = StrCat("Blackboard entry [", key, |
239 | 0 | "]: once declared, the type of a port" |
240 | 0 | " shall not change. Previously declared type [", |
241 | 0 | BT::demangle(prev_info.type()), "], current type [", |
242 | 0 | BT::demangle(info.type()), "]"); |
243 | |
|
244 | 0 | throw LogicError(msg); |
245 | 0 | } |
246 | 0 | return storage_it->second; |
247 | 0 | } |
248 | | |
249 | | // manual remapping first |
250 | 87.2k | auto remapping_it = internal_to_external_.find(key); |
251 | 87.2k | if(remapping_it != internal_to_external_.end()) |
252 | 0 | { |
253 | 0 | const auto& remapped_key = remapping_it->second; |
254 | 0 | if(auto parent = parent_bb_.lock()) |
255 | 0 | { |
256 | 0 | return parent->createEntryImpl(remapped_key, info); |
257 | 0 | } |
258 | 0 | throw RuntimeError("Missing parent blackboard"); |
259 | 0 | } |
260 | | // autoremapping second (excluding private keys) |
261 | 87.2k | if(autoremapping_ && !IsPrivateKey(key)) |
262 | 0 | { |
263 | 0 | if(auto parent = parent_bb_.lock()) |
264 | 0 | { |
265 | 0 | return parent->createEntryImpl(key, info); |
266 | 0 | } |
267 | 0 | throw RuntimeError("Missing parent blackboard"); |
268 | 0 | } |
269 | | // not remapped, not found. Create locally. |
270 | | |
271 | 87.2k | auto entry = std::make_shared<Entry>(info); |
272 | | // even if empty, let's assign to it a default type |
273 | 87.2k | entry->value = Any(info.type()); |
274 | 87.2k | storage_.insert({ key, entry }); |
275 | 87.2k | return entry; |
276 | 87.2k | } |
277 | | |
278 | | nlohmann::json ExportBlackboardToJSON(const Blackboard& blackboard) |
279 | 0 | { |
280 | 0 | nlohmann::json dest; |
281 | 0 | for(auto entry_name : blackboard.getKeys()) |
282 | 0 | { |
283 | 0 | const std::string name(entry_name); |
284 | 0 | if(auto any_ref = blackboard.getAnyLocked(name)) |
285 | 0 | { |
286 | 0 | if(auto any_ptr = any_ref.get()) |
287 | 0 | { |
288 | 0 | JsonExporter::get().toJson(*any_ptr, dest[name]); |
289 | 0 | } |
290 | 0 | } |
291 | 0 | } |
292 | 0 | return dest; |
293 | 0 | } |
294 | | |
295 | | void ImportBlackboardFromJSON(const nlohmann::json& json, Blackboard& blackboard) |
296 | 0 | { |
297 | 0 | for(auto it = json.begin(); it != json.end(); ++it) |
298 | 0 | { |
299 | 0 | if(auto res = JsonExporter::get().fromJson(it.value())) |
300 | 0 | { |
301 | 0 | auto entry = blackboard.getEntry(it.key()); |
302 | 0 | if(!entry) |
303 | 0 | { |
304 | 0 | blackboard.createEntry(it.key(), res->second); |
305 | 0 | entry = blackboard.getEntry(it.key()); |
306 | 0 | } |
307 | 0 | entry->value = res->first; |
308 | 0 | } |
309 | 0 | } |
310 | 0 | } |
311 | | |
312 | | Blackboard* BT::Blackboard::rootBlackboard() |
313 | 342 | { |
314 | 342 | auto bb = static_cast<const Blackboard&>(*this).rootBlackboard(); |
315 | | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) |
316 | 342 | return const_cast<Blackboard*>(bb); |
317 | 342 | } |
318 | | |
319 | | const Blackboard* BT::Blackboard::rootBlackboard() const |
320 | 2.94k | { |
321 | 2.94k | const Blackboard* bb = this; |
322 | 2.94k | Blackboard::Ptr prev = parent_bb_.lock(); |
323 | 2.94k | while(prev) |
324 | 0 | { |
325 | 0 | bb = prev.get(); |
326 | 0 | prev = bb->parent_bb_.lock(); |
327 | 0 | } |
328 | 2.94k | return bb; |
329 | 2.94k | } |
330 | | |
331 | | } // namespace BT |