Coverage Report

Created: 2026-01-17 06:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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