Skip to content

Commit 22f1b62

Browse files
committed
add timestamp and sequence to blackboard entries
1 parent 720cf63 commit 22f1b62

File tree

4 files changed

+130
-42
lines changed

4 files changed

+130
-42
lines changed

include/behaviortree_cpp/basic_types.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,14 @@ using Optional = nonstd::expected<T, std::string>;
332332
* */
333333
using Result = Expected<std::monostate>;
334334

335+
struct Timestamp
336+
{
337+
uint64_t seq = 0;
338+
std::chrono::nanoseconds stamp = std::chrono::nanoseconds(0);
339+
};
340+
341+
using ResultStamped = Expected<Timestamp>;
342+
335343
[[nodiscard]] bool IsAllowedPortName(StringView str);
336344

337345
class TypeInfo

include/behaviortree_cpp/blackboard.h

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <memory>
55
#include <unordered_map>
66
#include <mutex>
7+
#include <optional>
78

89
#include "behaviortree_cpp/basic_types.h"
910
#include "behaviortree_cpp/contrib/json.hpp"
@@ -40,8 +41,14 @@ class Blackboard
4041
StringConverter string_converter;
4142
mutable std::mutex entry_mutex;
4243

44+
uint64_t sequence_id = 0;
45+
// timestamp since epoch
46+
std::chrono::nanoseconds stamp = std::chrono::nanoseconds{ 0 };
47+
4348
Entry(const TypeInfo& _info) : info(_info)
4449
{}
50+
51+
Entry& operator=(const Entry& other);
4552
};
4653

4754
/** Use this static method to create an instance of the BlackBoard
@@ -75,12 +82,18 @@ class Blackboard
7582
template <typename T>
7683
[[nodiscard]] bool get(const std::string& key, T& value) const;
7784

85+
template <typename T>
86+
std::optional<Timestamp> getStamped(const std::string& key, T& value) const;
87+
7888
/**
7989
* Version of get() that throws if it fails.
8090
*/
8191
template <typename T>
8292
[[nodiscard]] T get(const std::string& key) const;
8393

94+
template <typename T>
95+
std::pair<T, Timestamp> getStamped(const std::string& key) const;
96+
8497
/// Update the entry with the given key
8598
template <typename T>
8699
void set(const std::string& key, const T& value);
@@ -155,10 +168,7 @@ inline T Blackboard::get(const std::string& key) const
155168
}
156169
return any_ref.get()->cast<T>();
157170
}
158-
else
159-
{
160-
throw RuntimeError("Blackboard::get() error. Missing key [", key, "]");
161-
}
171+
throw RuntimeError("Blackboard::get() error. Missing key [", key, "]");
162172
}
163173

164174
inline void Blackboard::unset(const std::string& key)
@@ -203,6 +213,8 @@ inline void Blackboard::set(const std::string& key, const T& value)
203213
lock.lock();
204214

205215
entry->value = new_value;
216+
entry->sequence_id++;
217+
entry->stamp = std::chrono::steady_clock::now().time_since_epoch();
206218
}
207219
else
208220
{
@@ -212,14 +224,15 @@ inline void Blackboard::set(const std::string& key, const T& value)
212224
std::scoped_lock scoped_lock(entry.entry_mutex);
213225

214226
Any& previous_any = entry.value;
215-
216227
Any new_value(value);
217228

218229
// special case: entry exists but it is not strongly typed... yet
219230
if(!entry.info.isStronglyTyped())
220231
{
221232
// Use the new type to create a new entry that is strongly typed.
222233
entry.info = TypeInfo::Create<T>();
234+
entry.sequence_id++;
235+
entry.stamp = std::chrono::steady_clock::now().time_since_epoch();
223236
previous_any = std::move(new_value);
224237
return;
225238
}
@@ -273,6 +286,8 @@ inline void Blackboard::set(const std::string& key, const T& value)
273286
// copy only if the type is compatible
274287
new_value.copyInto(previous_any);
275288
}
289+
entry.sequence_id++;
290+
entry.stamp = std::chrono::steady_clock::now().time_since_epoch();
276291
}
277292
}
278293

@@ -281,10 +296,47 @@ inline bool Blackboard::get(const std::string& key, T& value) const
281296
{
282297
if(auto any_ref = getAnyLocked(key))
283298
{
299+
if(any_ref.get()->empty())
300+
{
301+
return false;
302+
}
284303
value = any_ref.get()->cast<T>();
285304
return true;
286305
}
287306
return false;
288307
}
289308

309+
template <typename T>
310+
inline std::optional<Timestamp> Blackboard::getStamped(const std::string& key,
311+
T& value) const
312+
{
313+
if(auto entry = getEntry(key))
314+
{
315+
std::unique_lock lk(entry->entry_mutex);
316+
if(entry->value.empty())
317+
{
318+
return std::nullopt;
319+
}
320+
value = entry->value.cast<T>();
321+
return Timestamp{ entry->sequence_id, entry->stamp };
322+
}
323+
return std::nullopt;
324+
}
325+
326+
template <typename T>
327+
inline std::pair<T, Timestamp> Blackboard::getStamped(const std::string& key) const
328+
{
329+
if(auto entry = getEntry(key))
330+
{
331+
std::unique_lock lk(entry->entry_mutex);
332+
if(entry->value.empty())
333+
{
334+
throw RuntimeError("Blackboard::get() error. Entry [", key,
335+
"] hasn't been initialized, yet");
336+
}
337+
return { entry->value.cast<T>(), Timestamp{ entry->sequence_id, entry->stamp } };
338+
}
339+
throw RuntimeError("Blackboard::get() error. Missing key [", key, "]");
340+
}
341+
290342
} // namespace BT

include/behaviortree_cpp/tree_node.h

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,9 @@ class TreeNode
223223
template <typename T>
224224
Result getInput(const std::string& key, T& destination) const;
225225

226+
template <typename T>
227+
ResultStamped getInputStamped(const std::string& key, T& destination) const;
228+
226229
/** Same as bool getInput(const std::string& key, T& destination)
227230
* but using optional.
228231
*
@@ -352,6 +355,9 @@ class TreeNode
352355
PreScripts& preConditionsScripts();
353356
PostScripts& postConditionsScripts();
354357

358+
template <typename T>
359+
T parseString(const std::string& str) const;
360+
355361
private:
356362
struct PImpl;
357363
std::unique_ptr<PImpl> _p;
@@ -365,32 +371,31 @@ class TreeNode
365371
};
366372

367373
//-------------------------------------------------------
374+
368375
template <typename T>
369-
inline Result TreeNode::getInput(const std::string& key, T& destination) const
376+
T TreeNode::parseString(const std::string& str) const
370377
{
371-
// address the special case where T is an enum
372-
auto ParseString = [this](const std::string& str) -> T {
373-
(void)this; // maybe unused
374-
if constexpr(std::is_enum_v<T> && !std::is_same_v<T, NodeStatus>)
378+
if constexpr(std::is_enum_v<T> && !std::is_same_v<T, NodeStatus>)
379+
{
380+
auto it = config().enums->find(str);
381+
// conversion available
382+
if(it != config().enums->end())
375383
{
376-
auto it = config().enums->find(str);
377-
// conversion available
378-
if(it != config().enums->end())
379-
{
380-
return static_cast<T>(it->second);
381-
}
382-
else
383-
{
384-
// hopefully str contains a number that can be parsed. May throw
385-
return static_cast<T>(convertFromString<int>(str));
386-
}
384+
return static_cast<T>(it->second);
387385
}
388386
else
389387
{
390-
return convertFromString<T>(str);
388+
// hopefully str contains a number that can be parsed. May throw
389+
return static_cast<T>(convertFromString<int>(str));
391390
}
392-
};
391+
}
392+
return convertFromString<T>(str);
393+
}
393394

395+
template <typename T>
396+
inline ResultStamped TreeNode::getInputStamped(const std::string& key,
397+
T& destination) const
398+
{
394399
std::string port_value_str;
395400

396401
auto input_port_it = config().input_ports.find(key);
@@ -406,8 +411,7 @@ inline Result TreeNode::getInput(const std::string& key, T& destination) const
406411
{
407412
return nonstd::make_unexpected(StrCat("getInput() of node '", fullPath(),
408413
"' failed because the manifest doesn't "
409-
"contain"
410-
"the key: [",
414+
"contain the key: [",
411415
key, "]"));
412416
}
413417
const auto& port_info = port_manifest_it->second;
@@ -416,8 +420,7 @@ inline Result TreeNode::getInput(const std::string& key, T& destination) const
416420
{
417421
return nonstd::make_unexpected(StrCat("getInput() of node '", fullPath(),
418422
"' failed because nor the manifest or the "
419-
"XML contain"
420-
"the key: [",
423+
"XML contain the key: [",
421424
key, "]"));
422425
}
423426
if(port_info.defaultValue().isString())
@@ -427,68 +430,81 @@ inline Result TreeNode::getInput(const std::string& key, T& destination) const
427430
else
428431
{
429432
destination = port_info.defaultValue().cast<T>();
430-
return {};
433+
return Timestamp{};
431434
}
432435
}
433436

434-
auto remapped_res = getRemappedKey(key, port_value_str);
437+
auto blackboard_ptr = getRemappedKey(key, port_value_str);
435438
try
436439
{
437440
// pure string, not a blackboard key
438-
if(!remapped_res)
441+
if(!blackboard_ptr)
439442
{
440443
try
441444
{
442-
destination = ParseString(port_value_str);
445+
destination = parseString<T>(port_value_str);
443446
}
444447
catch(std::exception& ex)
445448
{
446449
return nonstd::make_unexpected(StrCat("getInput(): ", ex.what()));
447450
}
448-
return {};
451+
return Timestamp{};
449452
}
450-
const auto& remapped_key = remapped_res.value();
453+
const auto& blackboard_key = blackboard_ptr.value();
451454

452455
if(!config().blackboard)
453456
{
454457
return nonstd::make_unexpected("getInput(): trying to access "
455458
"an invalid Blackboard");
456459
}
457460

458-
if(auto any_ref = config().blackboard->getAnyLocked(std::string(remapped_key)))
461+
if(auto entry = config().blackboard->getEntry(std::string(blackboard_key)))
459462
{
460-
auto val = any_ref.get();
463+
std::unique_lock lk(entry->entry_mutex);
464+
auto& any_value = entry->value;
465+
461466
// support getInput<Any>()
462467
if constexpr(std::is_same_v<T, Any>)
463468
{
464-
destination = *val;
469+
destination = any_value;
465470
return {};
466471
}
467472

468-
if(!val->empty())
473+
if(!entry->value.empty())
469474
{
470-
if(!std::is_same_v<T, std::string> && val->isString())
475+
if(!std::is_same_v<T, std::string> && any_value.isString())
471476
{
472-
destination = ParseString(val->cast<std::string>());
477+
destination = parseString<T>(any_value.cast<std::string>());
473478
}
474479
else
475480
{
476-
destination = val->cast<T>();
481+
destination = any_value.cast<T>();
477482
}
478-
return {};
483+
return Timestamp{ entry->sequence_id, entry->stamp };
479484
}
480485
}
481486

482487
return nonstd::make_unexpected(StrCat("getInput() failed because it was unable to "
483488
"find the key [",
484-
key, "] remapped to [", remapped_key, "]"));
489+
key, "] remapped to [", blackboard_key, "]"));
485490
}
486491
catch(std::exception& err)
487492
{
488493
return nonstd::make_unexpected(err.what());
489494
}
490495
}
491496

497+
template <typename T>
498+
inline Result TreeNode::getInput(const std::string& key, T& destination) const
499+
{
500+
auto res = getInputStamped(key, destination);
501+
if(!res)
502+
{
503+
return nonstd::make_unexpected(res.error());
504+
}
505+
return {};
506+
}
507+
492508
template <typename T>
493509
inline Result TreeNode::setOutput(const std::string& key, const T& value)
494510
{

src/blackboard.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@ void Blackboard::cloneInto(Blackboard& dst) const
191191
dst_entry->string_converter = src_entry->string_converter;
192192
dst_entry->value = src_entry->value;
193193
dst_entry->info = src_entry->info;
194+
dst_entry->sequence_id++;
195+
dst_entry->stamp = std::chrono::steady_clock::now().time_since_epoch();
194196
}
195197
else
196198
{
@@ -297,4 +299,14 @@ void ImportBlackboardFromJSON(const nlohmann::json& json, Blackboard& blackboard
297299
}
298300
}
299301

302+
Blackboard::Entry& Blackboard::Entry::operator=(const Entry& other)
303+
{
304+
value = other.value;
305+
info = other.info;
306+
string_converter = other.string_converter;
307+
sequence_id = other.sequence_id;
308+
stamp = other.stamp;
309+
return *this;
310+
}
311+
300312
} // namespace BT

0 commit comments

Comments
 (0)