AutoAPMS
Resilient Robot Mission Management
Loading...
Searching...
No Matches
tree_document.cpp
1// Copyright 2024 Robin Müller
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include "auto_apms_behavior_tree_core/tree/tree_document.hpp"
16
17#include "auto_apms_behavior_tree_core/builder.hpp"
18#include "auto_apms_behavior_tree_core/exceptions.hpp"
19#include "auto_apms_behavior_tree_core/node/node_model_type.hpp"
20#include "auto_apms_behavior_tree_core/tree/tree_resource.hpp"
21#include "auto_apms_util/container.hpp"
22#include "auto_apms_util/logging.hpp"
23#include "auto_apms_util/string.hpp"
24#include "behaviortree_cpp/xml_parsing.h"
25
27{
28
29std::vector<std::string> getAllTreeNamesImpl(const tinyxml2::XMLDocument & doc)
30{
31 std::vector<std::string> names;
32 if (const tinyxml2::XMLElement * root = doc.RootElement()) {
33 if (strcmp(root->Name(), TreeDocument::ROOT_ELEMENT_NAME) == 0) {
34 // Found root element
35 for (const tinyxml2::XMLElement * child = root->FirstChildElement(); child != nullptr;
36 child = child->NextSiblingElement()) {
37 if (strcmp(TreeDocument::TREE_ELEMENT_NAME, child->Name()) == 0) {
38 if (const char * name = child->Attribute(TreeDocument::TREE_NAME_ATTRIBUTE_NAME)) {
39 names.push_back(name);
40 } else {
41 throw exceptions::TreeDocumentError(
42 "Cannot get tree name, because required attribute '" +
43 std::string(TreeDocument::TREE_NAME_ATTRIBUTE_NAME) + "' is missing.");
44 }
45 }
46 }
47 } else if (strcmp(root->Name(), TreeDocument::TREE_ELEMENT_NAME) == 0) {
48 // Found behavior tree element as root
49 if (const char * name = root->Attribute(TreeDocument::TREE_NAME_ATTRIBUTE_NAME)) names.push_back(name);
50 }
51 }
52 return names;
53}
54
56: doc_ptr_(doc_ptr), ele_ptr_(ele_ptr)
57{
58 if (!doc_ptr) {
59 throw exceptions::TreeDocumentError("Cannot create an instance of NodeElement with doc_ptr=nullptr.");
60 }
61 if (!ele_ptr) {
62 throw exceptions::TreeDocumentError("Cannot create an instance of NodeElement with ele_ptr=nullptr.");
63 }
64 const NodeModelMap model = doc_ptr_->getNodeModel(true);
65 NodeModelMap::const_iterator it = model.find(ele_ptr_->Name());
66 std::vector<NodePortInfo> port_infos_str_vec;
67 if (it != model.end()) port_infos_str_vec = it->second.port_infos;
68
69 // There should always be a corresponding element in the nodes model. However, if the constructor is called e.g.
70 // before the document registered the respective node, that's not the case and setPorts() won't work as expected.
71 // Therefore, we must ensure that all factory methods for a NodeElement verify that the node is known to the document
72 // before creating an instance. NOTE: We must not throw, since the constructor must also succeed for the derived
73 // TreeElement class for which there is no node model and setPorts() is deleted anyways.
74 if (!port_infos_str_vec.empty()) {
75 for (const NodePortInfo & port_info : port_infos_str_vec) {
76 port_names_.push_back(port_info.port_name);
77 if (!port_info.port_default.empty()) port_default_values_[port_info.port_name] = port_info.port_default;
78 }
79
80 // Set the node's attributes according to the default values for all the ports that have a default value and have
81 // not been set previously.
82 PortValues existing_port_values = getPorts();
83 for (const auto & [name, val] : port_default_values_) {
84 if (existing_port_values.find(name) == existing_port_values.end()) {
85 ele_ptr_->SetAttribute(name.c_str(), val.c_str());
86 }
87 }
88 }
89}
90
92{
93 XMLElement * other_ele = other.ele_ptr_;
94 if (ele_ptr_ != other_ele) {
95 // Copy the other element and all its children
96 other_ele = other_ele->DeepClone(doc_ptr_)->ToElement();
97 // Insert new element in place of this
98 XMLElement * prev = ele_ptr_->PreviousSiblingElement();
99 if (prev) {
100 ele_ptr_->Parent()->InsertAfterChild(ele_ptr_->PreviousSibling(), other_ele);
101 } else {
102 ele_ptr_->Parent()->InsertFirstChild(other_ele);
103 }
104 // Delete this element
105 doc_ptr_->DeleteNode(ele_ptr_);
106 // Update the internal pointer
107 ele_ptr_ = other_ele;
108 }
109 port_names_ = other.port_names_;
110 port_default_values_ = other.port_default_values_;
111 return *this;
112}
113
114bool TreeDocument::NodeElement::operator==(const NodeElement & other) const { return ele_ptr_ == other.ele_ptr_; }
115
116bool TreeDocument::NodeElement::operator!=(const NodeElement & other) const { return !this->operator==(other); }
117
119 const std::string & name, const NodeElement * before_this)
120{
121 if (const std::set<std::string> names = doc_ptr_->getRegisteredNodeNames(true); names.find(name) == names.end()) {
122 throw exceptions::TreeDocumentError(
123 "Cannot insert unkown node <" + name +
124 ">. Before inserting a new node, the associated document must register the corresponding behavior tree "
125 "node. Consider using a signature of insertNode() that does this automatically.");
126 }
127 XMLElement * ele = doc_ptr_->NewElement(name.c_str());
128 return insertBeforeImpl(before_this, ele);
129}
130
132 const std::string & name, const NodeRegistrationOptions & registration_options, const NodeElement * before_this)
133{
134 // Check if the node name is known. If the user tries to register a node with the same name as one of the native
135 // nodes, we want registerNodes to throw since those names are reserved (Therefore we set include_native to false).
136 if (const std::set<std::string> names = doc_ptr_->getRegisteredNodeNames(false); names.find(name) == names.end()) {
137 doc_ptr_->registerNodes(NodeManifest({{name, registration_options}}));
138 }
139 return insertNode(name, before_this);
140}
141
143 const std::string & tree_name, const NodeElement * before_this)
144{
145 if (!doc_ptr_->hasTreeName(tree_name)) {
146 throw exceptions::TreeDocumentError(
147 "Cannot insert subtree node for tree '" + tree_name + "' because no tree with that name exists.");
148 }
149 NodeElement ele = insertNode(SUBTREE_ELEMENT_NAME, before_this);
150 ele.ele_ptr_->SetAttribute(TREE_NAME_ATTRIBUTE_NAME, tree_name.c_str());
151 return model::SubTree(ele.doc_ptr_, ele.ele_ptr_);
152}
153
155{
156 const std::string tree_name = tree.getName();
157 if (doc_ptr_->hasTreeName(tree_name)) {
158 // We don't allow inserting a subtree node for a tree with the same name of another existing tree
159 if (doc_ptr_->getTree(tree_name) != tree) {
160 throw exceptions::TreeDocumentError(
161 "Cannot insert subtree node using the provided tree element, because another tree with name '" + tree_name +
162 "' already exists.");
163 }
164 } else {
165 // Add the tree provided as argument to the document
166 doc_ptr_->mergeTree(tree, false);
167 }
168 return insertSubTreeNode(tree_name, before_this);
169}
170
172 const TreeElement & tree, const NodeElement * before_this)
173{
174 const XMLElement * root_child = tree.ele_ptr_->FirstChildElement();
175 if (!root_child) {
176 throw exceptions::TreeDocumentError(
177 "Cannot insert tree element '" + tree.getFullyQualifiedName() + "' because it has no child nodes.");
178 }
179 if (root_child->NextSibling()) {
180 throw exceptions::TreeDocumentError(
181 "Cannot insert tree element '" + tree.getFullyQualifiedName() + "' because it has more than one child node.");
182 }
183 doc_ptr_->registerNodes(tree.getRequiredNodeManifest());
184 return insertTreeFromDocument(*tree.doc_ptr_, tree.getName(), before_this);
185}
186
188 const TreeDocument & doc, const std::string & tree_name, const NodeElement * before_this)
189{
190 const std::vector<std::string> other_tree_names = doc.getAllTreeNames();
191 if (other_tree_names.empty()) {
192 throw exceptions::TreeDocumentError(
193 "Cannot insert tree '" + tree_name + "' because document has no <" + TREE_ELEMENT_NAME + "> elements.");
194 }
195 if (!auto_apms_util::contains(other_tree_names, tree_name)) {
196 throw exceptions::TreeDocumentError(
197 "Cannot insert tree '" + tree_name + "' because document doesn't specify a tree with that name.");
198 }
199
200 // List including the target tree name and the names of its dependencies (Trees required by SubTree nodes)
201 std::set<std::string> required_tree_names = {tree_name};
202
203 // Find all subtree nodes and push back the associated tree to required_tree_names.
204 // NOTE: All tree elements within a TreeDocument instance always have exactly one child, so we don't have to verify
205 // that here again
206 TreeDocument temp_doc(doc_ptr_->format_version_, doc_ptr_->tree_node_loader_ptr_); // Temporary working document
207 doc.DeepCopy(&temp_doc);
208 const TreeElement other_tree(&temp_doc, temp_doc.getXMLElementForTreeWithName(tree_name));
209 ConstDeepApplyCallback collect_dependency_tree_names;
210 collect_dependency_tree_names = [&required_tree_names, &collect_dependency_tree_names](const NodeElement & ele) {
211 if (ele.getRegistrationName() == SUBTREE_ELEMENT_NAME) {
212 if (const char * name = ele.ele_ptr_->Attribute(TREE_NAME_ATTRIBUTE_NAME)) {
213 // Add the tree name to the list
214 required_tree_names.insert(name);
215
216 // Search for more tree dependencies under the tree pointed by this subtree node
217 ele.doc_ptr_->getTree(name).deepApplyConst(collect_dependency_tree_names);
218 } else {
219 throw exceptions::TreeDocumentError("Subtree element has no name attribute.");
220 };
221 }
222 // The return value doesn't matter here
223 return false;
224 };
225 other_tree.deepApplyConst(collect_dependency_tree_names);
226
227 // Verify that all child nodes of the trees listed in required_tree_names are known to the builder
228 ConstDeepApplyCallback apply = [available_names = doc_ptr_->getRegisteredNodeNames(true)](const NodeElement & ele) {
229 return available_names.find(ele.getRegistrationName()) == available_names.end();
230 };
231 for (const std::string & name : required_tree_names) {
232 const NodeElement ele(&temp_doc, temp_doc.getXMLElementForTreeWithName(name)->FirstChildElement());
233 if (const std::vector<NodeElement> found = ele.deepApplyConst(apply); !found.empty()) {
234 std::vector<std::string> names;
235 for (const NodeElement & ele : found) names.push_back(ele.getFullyQualifiedName());
236 throw exceptions::TreeDocumentError(
237 "Cannot insert tree '" + tree_name + "' because the following nodes found in tree '" + name +
238 "' are unkown to the builder:\n\t- " + auto_apms_util::join(names, "\n\t- "));
239 }
240 }
241
242 // Remove the data from each tree that the target tree doesn't directly depend on and merge the working document. This
243 // approach ensures, that the verification logic inside merge() is applied to the working document.
244 required_tree_names.erase(tree_name); // Don't add the target tree twice
245 for (const std::string & name : other_tree_names) {
246 if (required_tree_names.find(name) == required_tree_names.end()) {
247 // Remove if not required
248 temp_doc.removeTree(name);
249 }
250 }
251 doc_ptr_->mergeTreeDocument(static_cast<const XMLDocument &>(temp_doc), false);
252
253 // Clone and insert the target tree
254 return insertBeforeImpl(
255 before_this, doc.getXMLElementForTreeWithName(tree_name)->FirstChildElement()->DeepClone(doc_ptr_)->ToElement());
256}
257
259 const TreeDocument & doc, const NodeElement * before_this)
260{
261 return insertTreeFromDocument(doc, doc.getRootTreeName(), before_this);
262}
263
265 const std::string & tree_str, const std::string & tree_name, const NodeElement * before_this)
266{
267 TreeDocument insert_doc(doc_ptr_->format_version_, doc_ptr_->tree_node_loader_ptr_);
268 insert_doc.mergeString(tree_str);
269 return insertTreeFromDocument(insert_doc, tree_name, before_this);
270}
271
273 const std::string & tree_str, const NodeElement * before_this)
274{
275 TreeDocument insert_doc(doc_ptr_->format_version_, doc_ptr_->tree_node_loader_ptr_);
276 insert_doc.mergeString(tree_str);
277 return insertTreeFromDocument(insert_doc, before_this);
278}
279
281 const std::string & path, const std::string & tree_name, const NodeElement * before_this)
282{
283 TreeDocument insert_doc(doc_ptr_->format_version_, doc_ptr_->tree_node_loader_ptr_);
284 insert_doc.mergeFile(path);
285 return insertTreeFromDocument(insert_doc, tree_name, before_this);
286}
287
289 const std::string & path, const NodeElement * before_this)
290{
291 TreeDocument insert_doc(doc_ptr_->format_version_, doc_ptr_->tree_node_loader_ptr_);
292 insert_doc.mergeFile(path);
293 return insertTreeFromDocument(insert_doc, before_this);
294}
295
297 const TreeResource & resource, const std::string & tree_name, const NodeElement * before_this)
298{
299 TreeDocument insert_doc(doc_ptr_->format_version_, doc_ptr_->tree_node_loader_ptr_);
300 insert_doc.mergeFile(resource.tree_file_path_);
301
302 // We register all associated node plugins beforehand, so that the user doesn't have to do that manually. This means,
303 // that also potentially unused nodes are available and registered with the factory. This seems unnecessary, but it's
304 // very convenient and the performance probably doesn't suffer too much.
305 doc_ptr_->registerNodes(resource.getNodeManifest(), false);
306
307 return insertTreeFromDocument(insert_doc, tree_name, before_this);
308}
309
311 const TreeResource & resource, const NodeElement * before_this)
312{
313 return insertTreeFromResource(resource, resource.getRootTreeName(), before_this);
314}
315
316bool TreeDocument::NodeElement::hasChildren() const { return ele_ptr_->FirstChild() == nullptr ? false : true; }
317
319 const std::string & registration_name, const std::string & instance_name) const
320{
321 if (registration_name.empty() && instance_name.empty()) return NodeElement(doc_ptr_, ele_ptr_->FirstChildElement());
322
323 // If name is given, recursively search for the first node with this name
324 ConstDeepApplyCallback apply = [&registration_name, &instance_name](const NodeElement & ele) {
325 if (registration_name.empty()) return ele.getName() == instance_name;
326 if (instance_name.empty()) return ele.getRegistrationName() == registration_name;
327 return ele.getRegistrationName() == registration_name && ele.getName() == instance_name;
328 };
329 if (const std::vector<NodeElement> found = deepApplyConst(apply); !found.empty()) return found[0];
330
331 // Cannot find node in children of this
332 throw exceptions::TreeDocumentError(
333 "Cannot find a child node that matches the search arguments (registration_name: '" + registration_name +
334 "' - instance_name: '" + instance_name + "') in parent element '" + getFullyQualifiedName() + "'.");
335}
336
338 const std::string & registration_name, const std::string & instance_name)
339{
340 ele_ptr_->DeleteChild(getFirstNode(registration_name, instance_name).ele_ptr_);
341 return *this;
342}
343
345{
346 ele_ptr_->DeleteChildren();
347 return *this;
348}
349
350const std::vector<std::string> & TreeDocument::NodeElement::getPortNames() const { return port_names_; }
351
353{
354 PortValues values;
355 for (const tinyxml2::XMLAttribute * attr = ele_ptr_->FirstAttribute(); attr != nullptr; attr = attr->Next()) {
356 if (const std::string attr_name = attr->Name(); auto_apms_util::contains(port_names_, attr_name)) {
357 values[attr_name] = attr->Value();
358 }
359 }
360 return values;
361}
362
364{
365 // Verify port_values
366 std::vector<std::string> unkown_keys;
367 for (const auto & [key, _] : port_values) {
368 if (!auto_apms_util::contains(port_names_, key)) unkown_keys.push_back(key);
369 }
370 if (!unkown_keys.empty()) {
371 throw exceptions::TreeDocumentError(
372 "Cannot set ports. According to the node model, the following ports are not implemented by '" +
373 std::string(ele_ptr_->Name()) + "': [ " + auto_apms_util::join(unkown_keys, ", ") + " ].");
374 }
375
376 // Populate attributes according to the content of port_values
377 for (const auto & [key, val] : port_values) {
378 ele_ptr_->SetAttribute(key.c_str(), val.c_str());
379 }
380 return *this;
381}
382
384{
385 for (const std::string & name : port_names_) {
386 PortValues::const_iterator it = port_default_values_.find(name);
387 if (it != port_default_values_.end()) {
388 ele_ptr_->SetAttribute(it->first.c_str(), it->second.c_str());
389 } else {
390 ele_ptr_->DeleteAttribute(name.c_str());
391 }
392 }
393 return *this;
394}
395
397{
398 ele_ptr_->SetAttribute(BT::toStr(type).c_str(), script.str().c_str());
399 return *this;
400}
401
403{
404 ele_ptr_->SetAttribute(BT::toStr(type).c_str(), script.str().c_str());
405 return *this;
406}
407
409{
410 ele_ptr_->SetAttribute(NODE_INSTANCE_NAME_ATTRIBUTE_NAME, instance_name.c_str());
411 return *this;
412}
413
414std::string TreeDocument::NodeElement::getRegistrationName() const { return ele_ptr_->Name(); }
415
417{
418 if (const char * name = ele_ptr_->Attribute(NODE_INSTANCE_NAME_ATTRIBUTE_NAME)) return name;
419 return getRegistrationName();
420}
421
423{
424 std::string registration_name = getRegistrationName();
425 std::string instance_name = getName();
426 if (registration_name == instance_name) return registration_name;
427 return instance_name + " (" + registration_name + ")";
428}
429
431
432const std::vector<TreeDocument::NodeElement> TreeDocument::NodeElement::deepApplyConst(
433 ConstDeepApplyCallback apply_callback) const
434{
435 std::vector<NodeElement> found;
436 deepApplyImpl(*this, apply_callback, found);
437 return found;
438}
439
440std::vector<TreeDocument::NodeElement> TreeDocument::NodeElement::deepApply(DeepApplyCallback apply_callback)
441{
442 std::vector<NodeElement> found;
443 deepApplyImpl(*this, apply_callback, found);
444 return found;
445}
446
447TreeDocument::NodeElement TreeDocument::NodeElement::insertBeforeImpl(
448 const NodeElement * before_this, XMLElement * add_this)
449{
450 if (before_this) {
451 XMLElement * prev = nullptr;
452 XMLElement * curr = ele_ptr_->FirstChildElement();
453
454 // Traverse through siblings of first child to find the before_this node.
455 // If there are no siblings, add the first child to this;
456 bool found = false;
457 while (curr) {
458 if (curr == before_this->ele_ptr_) {
459 found = true;
460 break;
461 }
462 prev = curr;
463 curr = curr->NextSiblingElement();
464 }
465 if (prev) {
466 if (found) {
467 ele_ptr_->InsertAfterChild(prev, add_this);
468 } else {
469 throw exceptions::TreeDocumentError(
470 "NodeElement before_this (" + before_this->getFullyQualifiedName() + ") is not a child of " +
471 getFullyQualifiedName() + ".");
472 }
473 } else {
474 ele_ptr_->InsertFirstChild(add_this);
475 }
476 } else {
477 ele_ptr_->InsertEndChild(add_this);
478 }
479 return NodeElement(doc_ptr_, add_this);
480}
481
482void TreeDocument::NodeElement::deepApplyImpl(
483 const NodeElement & parent, ConstDeepApplyCallback apply_callback, std::vector<NodeElement> & vec)
484{
485 for (XMLElement * child = parent.ele_ptr_->FirstChildElement(); child != nullptr;
486 child = child->NextSiblingElement()) {
487 const NodeElement child_ele(parent.doc_ptr_, child);
488
489 // Apply on current child element
490 if (apply_callback(child_ele)) vec.push_back(child_ele);
491
492 // Search children of child before evaluating the siblings
493 deepApplyImpl(child_ele, apply_callback, vec);
494 }
495}
496
497void TreeDocument::NodeElement::deepApplyImpl(
498 NodeElement & parent, DeepApplyCallback apply_callback, std::vector<NodeElement> & vec)
499{
500 for (XMLElement * child = parent.ele_ptr_->FirstChildElement(); child != nullptr;
501 child = child->NextSiblingElement()) {
502 NodeElement child_ele(parent.doc_ptr_, child);
503
504 // Apply on current child element
505 if (apply_callback(child_ele)) vec.push_back(child_ele);
506
507 // Search children of child before evaluating the siblings
508 deepApplyImpl(child_ele, apply_callback, vec);
509 }
510}
511
512TreeDocument::TreeElement::TreeElement(TreeDocument * doc_ptr, XMLElement * ele_ptr) : NodeElement(doc_ptr, ele_ptr)
513{
514 if (!ele_ptr->Attribute(TREE_NAME_ATTRIBUTE_NAME)) {
515 throw exceptions::TreeDocumentError(
516 "Cannot create tree element without a '" + std::string(TREE_NAME_ATTRIBUTE_NAME) + "' attribute.");
517 }
518}
519
521{
522 const std::string other_tree_name = other.getName();
523 // For a tree replacement to be allowed, the other tree name must be the same or at least not already taken by another
524 // tree inside this document.
525 if (getName() != other_tree_name && doc_ptr_->hasTreeName(other.getName())) {
526 throw exceptions::TreeDocumentError(
527 "Cannot copy tree '" + other.getName() + "' because another tree with this name already exists.");
528 }
530 return *this;
531}
532
534{
535 ele_ptr_->SetAttribute(TREE_NAME_ATTRIBUTE_NAME, tree_name.c_str());
536 return *this;
537}
538
540{
541 if (const char * name = ele_ptr_->Attribute(TREE_NAME_ATTRIBUTE_NAME)) return name;
542 return "unkown";
543}
544
546{
547 doc_ptr_->setRootTreeName(getName());
548 return *this;
549}
550
552{
553 NodeManifest m;
554 deepApplyConst([this, &m](const NodeElement & node) {
555 const std::string name = node.getRegistrationName();
556 const bool is_native_node = doc_ptr_->native_node_names_.find(name) != doc_ptr_->native_node_names_.end();
557 if (!is_native_node && !m.contains(name)) {
558 if (!doc_ptr_->registered_nodes_manifest_.contains(name)) {
559 throw exceptions::NodeManifestError(
560 "Cannot assemble the required node manifest for tree '" + getName() +
561 "' since there are no registration options for node '" + name + "'.");
562 }
563 m.add(name, doc_ptr_->registered_nodes_manifest_[name]);
564 }
565 return false;
566 });
567 return m;
568}
569
571{
572 TreeDocument doc(doc_ptr_->format_version_, doc_ptr_->tree_node_loader_ptr_);
573 return doc.mergeTree(*this, true).verify();
574}
575
577{
578 XMLDocument tree_doc;
579 tree_doc.InsertEndChild(ele_ptr_->DeepClone(&tree_doc));
580 tinyxml2::XMLPrinter printer;
581 tree_doc.Print(&printer);
582 return printer.CStr();
583}
584
586 const std::string & registration_name, const std::string & instance_name)
587{
588 NodeElement::removeFirstChild(registration_name, instance_name);
589 return *this;
590}
591
597
598TreeDocument::TreeDocument(const std::string & format_version, NodeRegistrationLoader::SharedPtr tree_node_loader)
599// It's important to initialize XMLDocument using PRESERVE_WHITESPACE, since encoded port data (like
600// NodeRegistrationOptions) may be sensitive to changes in the whitespaces (think of the YAML format).
601: XMLDocument(true, tinyxml2::PRESERVE_WHITESPACE),
602 all_node_classes_package_map_(auto_apms_behavior_tree::core::NodeRegistrationLoader().getClassPackageMap()),
603 native_node_names_(BT::BehaviorTreeFactory().builtinNodes()),
604 format_version_(format_version),
605 tree_node_loader_ptr_(tree_node_loader),
606 registered_nodes_manifest_(),
607 factory_(),
608 logger_(rclcpp::get_logger(LOGGER_NAME))
609{
611 reset();
612}
613
614TreeDocument & TreeDocument::mergeTreeDocument(const XMLDocument & other, bool adopt_root_tree)
615{
616 const XMLElement * other_root = other.RootElement();
617 if (!other_root) {
618 throw exceptions::TreeDocumentError("Cannot merge tree documents: other_root is nullptr.");
619 };
620
621 auto verify_tree_structure = [](const XMLElement * tree_ele) {
622 const char * tree_id = tree_ele->Attribute(TREE_NAME_ATTRIBUTE_NAME);
623 if (!tree_id) {
624 throw exceptions::TreeDocumentError(
625 "Cannot merge tree document: Found a <" + std::string(TREE_ELEMENT_NAME) +
626 "> element that doesn't specify the required attribute '" + TREE_NAME_ATTRIBUTE_NAME + "'.");
627 }
628 const XMLElement * tree_root_child = tree_ele->FirstChildElement();
629 if (!tree_root_child) {
630 throw exceptions::TreeDocumentError(
631 "Cannot merge tree document: Tree '" + std::string(tree_id) + "' has no child nodes.");
632 }
633 if (tree_root_child->NextSibling()) {
634 throw exceptions::TreeDocumentError(
635 "Cannot merge tree document: Tree '" + std::string(tree_id) + "' has more than one child node.");
636 }
637 };
638
639 const std::vector<std::string> other_tree_names = getAllTreeNamesImpl(other);
640
641 // Verify that there are no duplicate tree names
642 const auto common_tree_names = auto_apms_util::getCommonElements(getAllTreeNames(), other_tree_names);
643 if (!common_tree_names.empty()) {
644 throw exceptions::TreeDocumentError(
645 "Cannot merge tree document: The following trees are already defined: [ " +
646 auto_apms_util::join(common_tree_names, ", ") + " ].");
647 }
648
649 if (strcmp(other_root->Name(), ROOT_ELEMENT_NAME) == 0) {
650 // Verify format
651 if (const char * ver = other_root->Attribute(BTCPP_FORMAT_ATTRIBUTE_NAME)) {
652 if (std::string(ver) != format_version_) {
653 throw exceptions::TreeDocumentError(
654 "Cannot merge tree document: Format of other document (" + std::string(BTCPP_FORMAT_ATTRIBUTE_NAME) + ": " +
655 ver + ") is not compatible with this document (" + std::string(BTCPP_FORMAT_ATTRIBUTE_NAME) + ": " +
656 format_version_ + ").");
657 }
658 } else {
659 throw exceptions::TreeDocumentError(
660 "Cannot merge tree document: Root element of other document doesn't have required attribute '" +
661 std::string(BTCPP_FORMAT_ATTRIBUTE_NAME) + "'.");
662 }
663
664 // Iterate over all the children of other root
665 for (const XMLElement * child = other_root->FirstChildElement(); child != nullptr;
666 child = child->NextSiblingElement()) {
667 // Disregard tree node model elements
668 if (strcmp(child->Name(), TREE_NODE_MODEL_ELEMENT_NAME) == 0) continue;
669
670 // Verify the structure of the behavior tree element
671 if (strcmp(child->Name(), TREE_ELEMENT_NAME) == 0) {
672 verify_tree_structure(child);
673 }
674
675 // If tree element is valid, append to this document
676 RootElement()->InsertEndChild(child->DeepClone(this));
677 }
678
679 if (adopt_root_tree) {
680 // After (!) all tree elements have been inserted, adopt the name specified in the corresponding attribute of the
681 // root element
682 if (const char * name = other_root->Attribute(ROOT_TREE_ATTRIBUTE_NAME)) setRootTreeName(name);
683 }
684 } else if (strcmp(other_root->Name(), TREE_ELEMENT_NAME) == 0) {
685 // Allow a single behavior tree without <root> element.
686 // We assume that the the format complies with the version configured at construction time
687 verify_tree_structure(other_root);
688
689 // If tree element is valid, append to this document
690 RootElement()->InsertEndChild(other_root->DeepClone(this));
691 } else {
692 throw exceptions::TreeDocumentError(
693 "Cannot merge tree document: Root element of other document must either be <" + std::string(ROOT_ELEMENT_NAME) +
694 "> or <" + TREE_ELEMENT_NAME + ">.");
695 }
696
697 if (adopt_root_tree && other_tree_names.size() == 1) {
698 // If there is only one tree element, we change the root tree to the respective tree after (!) all tree elements
699 // have been inserted
700 setRootTreeName(other_tree_names[0]);
701 }
702 return *this;
703}
704
705TreeDocument & TreeDocument::mergeTreeDocument(const TreeDocument & other, bool adopt_root_tree)
706{
708 return mergeTreeDocument(static_cast<const XMLDocument &>(other), adopt_root_tree);
709}
710
711TreeDocument & TreeDocument::mergeString(const std::string & tree_str, bool adopt_root_tree)
712{
713 XMLDocument other_doc;
714 if (other_doc.Parse(tree_str.c_str()) != tinyxml2::XMLError::XML_SUCCESS) {
715 throw exceptions::TreeDocumentError("Cannot merge tree document from string: " + std::string(other_doc.ErrorStr()));
716 }
717 return mergeTreeDocument(other_doc, adopt_root_tree);
718}
719
720TreeDocument & TreeDocument::mergeFile(const std::string & path, bool adopt_root_tree)
721{
722 XMLDocument other_doc;
723 if (other_doc.LoadFile(path.c_str()) != tinyxml2::XMLError::XML_SUCCESS) {
724 throw exceptions::TreeDocumentError("Cannot create tree document from file " + path + ": " + other_doc.ErrorStr());
725 }
726 return mergeTreeDocument(other_doc, adopt_root_tree);
727}
728
729TreeDocument & TreeDocument::mergeResource(const TreeResource & resource, bool adopt_root_tree)
730{
731 registerNodes(resource.getNodeManifest(), false);
732 return mergeFile(resource.tree_file_path_, adopt_root_tree);
733}
734
735TreeDocument & TreeDocument::mergeTree(const TreeElement & tree, bool make_root_tree)
736{
737 XMLDocument tree_doc;
738 tree_doc.InsertEndChild(tree.ele_ptr_->DeepClone(&tree_doc));
740 mergeTreeDocument(tree_doc, make_root_tree);
741 return *this;
742}
743
745{
746 // This function is implemented to allow users access to the API for building XML
747 // documents, not instantiating trees (like TreeBuilder does). We pass a mock object of TreeBuilder as it's required
748 // for constructing TreeElement instances, however, the mock builder was initialized without valid pointers to the ROS
749 // objects, so as soon as a node plugin is instantiated, the program will fail. However, it is impossible to do so,
750 // because the user is not granted access to the builder object which implements the method for instantiating a tree.
751 if (tree_name.empty()) {
752 throw exceptions::TreeDocumentError("Cannot create a new tree with an empty name");
753 }
754 if (hasTreeName(tree_name)) {
755 throw exceptions::TreeDocumentError(
756 "Cannot create a new tree with name '" + tree_name + "' because it already exists.");
757 }
758 TreeDocument::XMLElement * new_ele = RootElement()->InsertNewChildElement(TreeDocument::TREE_ELEMENT_NAME);
759 new_ele->SetAttribute(TreeDocument::TREE_NAME_ATTRIBUTE_NAME, tree_name.c_str());
760 return TreeElement(this, new_ele);
761}
762
764{
765 return mergeTree(other_tree).getTree(other_tree.getName());
766}
767
769{
770 std::string name(tree_name);
771 if (name.empty()) {
772 if (other.hasRootTreeName()) {
773 name = other.getRootTreeName();
774 } else if (const std::vector<std::string> names = other.getAllTreeNames(); names.size() == 1) {
775 name = names[0];
776 } else {
777 throw exceptions::TreeDocumentError(
778 "Failed to create new tree element from another document because argument tree_name was omitted and it was not "
779 "possible to determine the root tree automatically.");
780 }
781 }
782 TreeElement tree_ele = newTree(name);
783 tree_ele.insertTreeFromDocument(other, name);
784 return tree_ele;
785}
786
787TreeDocument::TreeElement TreeDocument::newTreeFromString(const std::string & tree_str, const std::string & tree_name)
788{
789 TreeDocument new_doc(format_version_, tree_node_loader_ptr_);
790 new_doc.mergeString(tree_str, true);
791 return newTreeFromDocument(new_doc, tree_name);
792}
793
794TreeDocument::TreeElement TreeDocument::newTreeFromFile(const std::string & path, const std::string & tree_name)
795{
796 TreeDocument new_doc(format_version_, tree_node_loader_ptr_);
797 new_doc.mergeFile(path, true);
798 return newTreeFromDocument(new_doc, tree_name);
799}
800
802 const TreeResource & resource, const std::string & tree_name)
803{
804 TreeDocument new_doc(format_version_, tree_node_loader_ptr_);
805 new_doc.mergeFile(resource.tree_file_path_);
806 if (resource.hasRootTreeName()) new_doc.setRootTreeName(resource.getRootTreeName());
807 registerNodes(resource.getNodeManifest(), false);
808 return newTreeFromDocument(new_doc, tree_name);
809}
810
811bool TreeDocument::hasTreeName(const std::string & tree_name) const
812{
813 if (auto_apms_util::contains(getAllTreeNames(), tree_name)) return true;
814 return false;
815}
816
818{
819 return TreeElement(this, getXMLElementForTreeWithName(tree_name));
820}
821
822TreeDocument & TreeDocument::setRootTreeName(const std::string & tree_name)
823{
824 if (tree_name.empty()) {
825 throw exceptions::TreeDocumentError("Cannot set root tree name with empty string.");
826 }
827 if (!hasTreeName(tree_name)) {
828 throw exceptions::TreeDocumentError(
829 "Cannot make tree with name '" + tree_name + "' the root tree because it doesn't exist.");
830 }
831 RootElement()->SetAttribute(ROOT_TREE_ATTRIBUTE_NAME, tree_name.c_str());
832 return *this;
833}
834
836{
837 if (RootElement()->Attribute(ROOT_TREE_ATTRIBUTE_NAME)) return true;
838 return false;
839}
840
842{
843 if (const auto tree_name = RootElement()->Attribute(ROOT_TREE_ATTRIBUTE_NAME)) return tree_name;
844 throw exceptions::TreeDocumentError(
845 "Cannot get root tree name because the document's root element has no attribute '" +
846 std::string(ROOT_TREE_ATTRIBUTE_NAME) + "'.");
847}
848
850
851TreeDocument & TreeDocument::removeTree(const std::string & tree_name)
852{
853 RootElement()->DeleteChild(getXMLElementForTreeWithName(tree_name));
854 return *this;
855}
856
858
859std::vector<std::string> TreeDocument::getAllTreeNames() const { return getAllTreeNamesImpl(*this); }
860
861TreeDocument & TreeDocument::registerNodes(const NodeManifest & tree_node_manifest, bool override)
862{
863 // Make sure that there are no node names that are reserved for native nodes
864 std::set<std::string> all_registration_names;
865 for (const auto & [name, _] : tree_node_manifest.map()) all_registration_names.insert(name);
866 if (const std::set<std::string> common =
867 auto_apms_util::getCommonElements(all_registration_names, native_node_names_);
868 !common.empty()) {
869 throw exceptions::TreeDocumentError(
870 "Found reserved node registration names in the node manifest. The following names are not allowed, because "
871 "they refer to native behavior tree nodes: [ " +
872 auto_apms_util::join(std::vector<std::string>(common.begin(), common.end()), ", ") + " ].");
873 }
874
875 for (const auto & [node_name, params] : tree_node_manifest.map()) {
876 // If the node is already registered
877 if (registered_nodes_manifest_.contains(node_name)) {
878 // Check whether the specified node class is different from the currently registered one by comparing the
879 // respective plugin class names.
880 if (registered_nodes_manifest_[node_name].class_name == params.class_name) {
881 // We assume that the manifest entry refers to the exact same node plugin, because all NodeRegistrationLoader
882 // instances verify that there are no ambiguous class names during initialization. Since the node is already
883 // registered, we may skip registering it as there's nothing new to do.
884 continue;
885 } else if (override) {
886 // If it's actually a different class and override is true, register the new node plugin instead of the
887 // current one.
888 factory_.unregisterBuilder(node_name);
889 } else {
890 // If it's actually a different class and override is false, we must throw.
891 throw exceptions::TreeDocumentError(
892 "Tried to register node '" + node_name + "' (Class : " + params.class_name +
893 ") which is already known to the builder, but under a different class name (" +
894 registered_nodes_manifest_[node_name].class_name +
895 "). You must explicitly set override=true to allow for overriding previously registered nodes.");
896 }
897 }
898
899 // Check if the class we search for is actually available with the loader.
900 if (!tree_node_loader_ptr_->isClassAvailable(params.class_name)) {
901 if (all_node_classes_package_map_.find(params.class_name) == all_node_classes_package_map_.end()) {
902 throw exceptions::TreeDocumentError(
903 "Node '" + node_name + " (" + params.class_name +
904 ")' cannot be registered, because the class name is not known to the class loader. "
905 "Make sure that it's spelled correctly and registered by calling "
906 "auto_apms_behavior_tree_declare_nodes() in the CMakeLists.txt of the "
907 "corresponding package.");
908 }
909 throw exceptions::TreeDocumentError(
910 "Node '" + node_name + " (" + params.class_name +
911 ")' cannot be registered, because the corresponding resource belongs to excluded package '" +
912 all_node_classes_package_map_.at(params.class_name) + "'.");
913 }
914
915 pluginlib::UniquePtr<NodeRegistrationInterface> plugin_instance;
916 try {
917 plugin_instance = tree_node_loader_ptr_->createUniqueInstance(params.class_name);
918 } catch (const pluginlib::CreateClassException & e) {
919 throw pluginlib::CreateClassException(
920 "Failed to create an instance of node '" + node_name + " (" + params.class_name +
921 ")'. Remember that the AUTO_APMS_BEHAVIOR_TREE_DECLARE_NODE "
922 "macro must be called in the source file for the node class to be discoverable. "
923 "Error message: " +
924 e.what() + ".");
925 }
926
927 try {
928 if (plugin_instance->requiresRosNodeContext()) {
929 if (only_non_ros_nodes_) {
930 throw exceptions::NodeRegistrationError(
931 "Node '" + node_name +
932 "' relies on ROS 2 functionality but this instance only allows to use non-ROS nodes.");
933 }
934 RosNodeContext ros_node_context(
935 ros_node_wptr_.lock(), tree_node_waitables_callback_group_wptr_.lock(),
936 tree_node_waitables_executor_wptr_.lock(), params);
937 plugin_instance->registerWithBehaviorTreeFactory(factory_, node_name, &ros_node_context);
938 } else {
939 plugin_instance->registerWithBehaviorTreeFactory(factory_, node_name);
940 }
941 } catch (const std::exception & e) {
942 throw exceptions::NodeRegistrationError(
943 "Cannot register node '" + node_name + " (" + params.class_name + ")': " + e.what() + ".");
944 }
945 registered_nodes_manifest_.add(node_name, params);
946 }
947 return *this;
948}
949
950std::set<std::string> TreeDocument::getRegisteredNodeNames(bool include_native) const
951{
952 std::set<std::string> names;
953 if (include_native) names = native_node_names_;
954 for (const auto & [name, _] : registered_nodes_manifest_.map()) names.insert(name);
955 return names;
956}
957
959{
960 NodeManifest m;
961 TreeDocument * doc = const_cast<TreeDocument *>(this);
962 for (const std::string & tree_name : getAllTreeNames()) {
963 XMLElement * ptr = const_cast<XMLElement *>(getXMLElementForTreeWithName(tree_name));
964 const TreeElement ele(doc, ptr);
965 m.merge(ele.getRequiredNodeManifest(), true);
966 }
967 return m;
968}
969
971{
972 tinyxml2::XMLDocument model_doc;
973 if (
974 model_doc.Parse(BT::writeTreeNodesModelXML(factory_, include_native).c_str()) != tinyxml2::XMLError::XML_SUCCESS) {
975 throw exceptions::TreeDocumentError(
976 "Error parsing the model of the currently registered nodes: " + std::string(model_doc.ErrorStr()));
977 }
978
979 // Verify format of document and discard the return value (We add a model element even if empty)
980 getNodeModel(model_doc);
981
982 const tinyxml2::XMLElement * model_child =
983 model_doc.RootElement()->FirstChildElement(TreeDocument::TREE_NODE_MODEL_ELEMENT_NAME);
984
985 // Clone the memory of the node model element to the builder document
986 tinyxml2::XMLNode * copied_child = model_child->DeepClone(this);
987
988 // Append the copied child to the root of the builder document
989 RootElement()->InsertEndChild(copied_child);
990 return *this;
991}
992
994{
995 const tinyxml2::XMLElement * root = doc.RootElement();
996 if (!root) {
997 throw exceptions::TreeDocumentError("Node model document has no root element.");
998 }
999 if (const char * ver = root->Attribute(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME)) {
1000 const std::string expected_format = TreeDocument::BTCPP_FORMAT_DEFAULT_VERSION;
1001 if (std::string(ver) != expected_format) {
1002 throw exceptions::TreeDocumentError(
1003 "Cannot parse node model document: Format of model document (" +
1004 std::string(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME) + ": " + ver +
1005 ") doesn't comply with the expected format (" + std::string(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME) + ": " +
1006 expected_format + ").");
1007 }
1008 } else {
1009 throw exceptions::TreeDocumentError(
1010 "Cannot parse node model document: Root element of model document doesn't have required attribute '" +
1011 std::string(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME) + "'.");
1012 }
1013 tinyxml2::XMLElement * model_ele = doc.RootElement()->FirstChildElement(TreeDocument::TREE_NODE_MODEL_ELEMENT_NAME);
1014 if (!model_ele) {
1015 throw exceptions::TreeDocumentError(
1016 "Element <" + std::string(TreeDocument::TREE_NODE_MODEL_ELEMENT_NAME) +
1017 "> doesn't exist in node model document.");
1018 }
1019
1020 NodeModelMap model_map;
1021 for (tinyxml2::XMLElement * ele = model_ele->FirstChildElement(); ele != nullptr; ele = ele->NextSiblingElement()) {
1022 const char * node_name = ele->Attribute("ID");
1023 if (!node_name) {
1024 throw exceptions::TreeDocumentError(
1025 "Element '" + std::string(ele->Name()) + "' in node model document is missing the required attribute 'ID'");
1026 }
1027 NodeModel & model = model_map[node_name];
1028 model.type = BT::convertFromString<BT::NodeType>(ele->Name());
1029 for (const tinyxml2::XMLElement * port_ele = ele->FirstChildElement(); port_ele != nullptr;
1030 port_ele = port_ele->NextSiblingElement()) {
1031 const std::string direction = port_ele->Name();
1032 NodePortInfo port_info;
1033 if (direction == "input_port") {
1034 port_info.port_direction = BT::PortDirection::INPUT;
1035 } else if (direction == "output_port") {
1036 port_info.port_direction = BT::PortDirection::OUTPUT;
1037 } else if (direction == "inout_port") {
1038 port_info.port_direction = BT::PortDirection::INOUT;
1039 } else {
1040 throw exceptions::TreeDocumentError(
1041 "Unkown port direction in node model for '" + std::string(node_name) + "': " + direction);
1042 }
1043 if (const char * c = port_ele->Attribute("name")) {
1044 port_info.port_name = c;
1045 }
1046 if (const char * c = port_ele->Attribute("type")) {
1047 port_info.port_type = c;
1048 }
1049 if (const char * c = port_ele->Attribute("default")) {
1050 port_info.port_default = c;
1051 }
1052 if (const char * c = port_ele->GetText()) {
1053 port_info.port_description = c;
1054 }
1055 model.port_infos.push_back(std::move(port_info));
1056 }
1057 // Port infos may be empty if there are no ports
1058 }
1059 return model_map;
1060}
1061
1063{
1064 tinyxml2::XMLDocument model_doc;
1065 if (
1066 model_doc.Parse(BT::writeTreeNodesModelXML(factory_, include_native).c_str()) != tinyxml2::XMLError::XML_SUCCESS) {
1067 throw exceptions::TreeDocumentError(
1068 "Error parsing the model of the currently registered nodes: " + std::string(model_doc.ErrorStr()));
1069 }
1070 return getNodeModel(model_doc);
1071}
1072
1073BT::Result TreeDocument::verify() const
1074{
1075 NodeModelMap model_map = getNodeModel(true);
1076 std::unordered_map<std::string, BT::NodeType> registered_nodes;
1077 for (const auto & [node_name, model] : model_map) {
1078 registered_nodes[node_name] = model.type;
1079 }
1080 try {
1081 BT::VerifyXML(writeToString(), registered_nodes);
1082 } catch (const BT::RuntimeError & e) {
1083 return nonstd::make_unexpected(e.what());
1084 }
1085 return {};
1086}
1087
1089{
1090 tinyxml2::XMLPrinter printer;
1091 Print(&printer);
1092 return printer.CStr();
1093}
1094
1095void TreeDocument::writeToFile(const std::string & path) const
1096{
1097 XMLDocument doc;
1098 DeepCopy(&doc);
1099 tinyxml2::XMLError result = doc.SaveFile(path.c_str());
1100 if (result != tinyxml2::XML_SUCCESS) {
1101 throw exceptions::TreeDocumentError(
1102 "Failed to write tree document to file. Error ID: " + std::string(doc.ErrorIDToName(result)));
1103 }
1104}
1105
1107{
1108 Clear();
1109 tinyxml2::XMLElement * root_ele = NewElement(TreeDocument::ROOT_ELEMENT_NAME);
1110 root_ele->SetAttribute(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME, format_version_.c_str());
1111 InsertFirstChild(root_ele);
1112 return *this;
1113}
1114
1115const TreeDocument::XMLElement * TreeDocument::getXMLElementForTreeWithName(const std::string & tree_name) const
1116{
1117 return getXMLElementForTreeWithNameImpl<const XMLElement *>(*this, tree_name);
1118}
1119
1120TreeDocument::XMLElement * TreeDocument::getXMLElementForTreeWithName(const std::string & tree_name)
1121{
1122 return getXMLElementForTreeWithNameImpl<XMLElement *>(*this, tree_name);
1123}
1124
1125template <typename ReturnT, typename DocumentT>
1126ReturnT TreeDocument::getXMLElementForTreeWithNameImpl(DocumentT & doc, const std::string & tree_name)
1127{
1128 if (tree_name.empty()) {
1129 throw exceptions::TreeDocumentError("Cannot get tree with an empty name.");
1130 }
1131 if (!doc.hasTreeName(tree_name)) {
1132 throw exceptions::TreeDocumentError("Cannot get tree with name '" + tree_name + "' because it doesn't exist.");
1133 }
1134 auto child = doc.RootElement()->FirstChildElement(TreeDocument::TREE_ELEMENT_NAME);
1135 while (child && !child->Attribute(TreeDocument::TREE_NAME_ATTRIBUTE_NAME, tree_name.c_str())) {
1136 child = child->NextSiblingElement();
1137 }
1138 if (!child) {
1139 throw std::logic_error(
1140 "Unexpected error trying to get tree element with name '" + tree_name +
1141 "'. Since hasTreeName() returned true, there MUST be a corresponding element.");
1142 }
1143 return child;
1144}
1145
1146} // namespace auto_apms_behavior_tree::core
Data structure for information about which behavior tree node plugin to load and how to configure the...
NodeManifest & add(const std::string &node_name, const RegistrationOptions &opt)
Add registration options for a behavior tree node to the manifest.
bool contains(const std::string &node_name) const
Determine if a behavior tree node has been added to the manifest.
NodeManifest & merge(const NodeManifest &other, bool replace=false)
Merges another NodeManifest with this one.
const Map & map() const
Get a view of the internal map.
A pluginlib::ClassLoader specifically for loading installed behavior tree node plugins.
Additional parameters specific to ROS 2 determined at runtime by TreeBuilder.
Class that encapsulates behavior tree script expressions.
Definition script.hpp:30
std::string str() const
Concatenate all expressions of this instance to a single string.
Definition script.cpp:41
Handle for a single node of a TreeDocument.
const TreeDocument & getParentDocument() const
Get a const view of this node's parent tree document.
NodeElement insertTreeFromResource(const TreeResource &resource, const std::string &tree_name, const NodeElement *before_this=nullptr)
Concatenate a tree from one of the installed package's behavior tree resources and add its first chil...
const std::vector< std::string > & getPortNames() const
Get the names of the data ports implemented by the node represented by this element.
virtual std::string getRegistrationName() const
Get the name of this node given during registration representing its dynamic type.
std::function< bool(const NodeElement &)> ConstDeepApplyCallback
Callback invoked for every node found under another node. It cannot modify the current node.
NodeElement & resetPorts()
Delete all currently specified port values and reset with the defaults.
std::function< bool(NodeElement &)> DeepApplyCallback
Callback invoked for every node found under another node. It may modify the current node.
NodeElement & removeFirstChild(const std::string &registration_name="", const std::string &instance_name="")
Recursively visit this node's children in execution order and remove the first node with a particular...
NodeElement & setPorts(const PortValues &port_values)
Populate the the node's data ports.
virtual NodeElement & setName(const std::string &instance_name)
Assign a name for this specific node instance.
NodeElement getFirstNode(const std::string &registration_name="", const std::string &instance_name="") const
Recursively visit this node's children in execution order and get the first node with a particular re...
NodeElement(TreeDocument *doc_ptr, XMLElement *ele_ptr)
Protected constructor intended for internal use only.
std::map< std::string, std::string > PortValues
Mapping of port names and its respective value encoded as string.
const std::vector< NodeElement > deepApplyConst(ConstDeepApplyCallback apply_callback) const
Recursively apply a callback to this node's children.
TreeDocument * doc_ptr_
Pointer to the tree document that created the tree this node belongs to.
virtual std::string getName() const
Get the name of this node given to this specific instance by the developer.
PortValues getPorts() const
Assemble the values given to each data port implemented by this node.
NodeElement & setConditionalScript(BT::PreCond type, const Script &script)
Specify a script that is evaluated before this node is ticked.
NodeElement insertTreeFromString(const std::string &tree_str, const std::string &tree_name, const NodeElement *before_this=nullptr)
Concatenate a tree from a document created from a string and add its first child node to the children...
tinyxml2::XMLElement * ele_ptr_
Pointer to the corresponding XMLElement of the base document.
NodeElement insertTree(const TreeElement &tree, const NodeElement *before_this=nullptr)
Concatenate a tree and add its first child node to the children of this node.
std::string getFullyQualifiedName() const
Create a string that uniquely identifies this node considering its registration and its instance name...
NodeElement insertTreeFromDocument(const TreeDocument &doc, const std::string &tree_name, const NodeElement *before_this=nullptr)
Concatenate a tree from a document and add its first child node to the children of this node.
model::SubTree insertSubTreeNode(const std::string &tree_name, const NodeElement *before_this=nullptr)
Add a subtree node for a specific tree element to the children of this node.
bool hasChildren() const
Determine whether any children have been given to this node.
bool operator==(const NodeElement &other) const
Determine if two node elements refer to the same node.
NodeElement insertNode(const std::string &name, const NodeElement *before_this=nullptr)
Add a new node to the children of this node.
NodeElement insertTreeFromFile(const std::string &path, const std::string &tree_name, const NodeElement *before_this=nullptr)
Concatenate a tree from a document created from a file and add its first child node to the children o...
NodeElement & removeChildren()
Recursively remove all children of this node element.
NodeElement & operator=(const NodeElement &other)
Replace this node with another.
std::vector< NodeElement > deepApply(DeepApplyCallback apply_callback)
Recursively apply a callback to this node's children.
Handle for a single behavior tree of a TreeDocument.
TreeElement(TreeDocument *doc_ptr, XMLElement *ele_ptr)
Protected constructor intended to be used only by certain factory methods of TreeDocument.
std::string getName() const override
Get the name of the behavior tree.
BT::Result verify() const
Verify that this behavior tree is structured correctly and can be created successfully.
NodeManifest getRequiredNodeManifest() const
Assemble the node manifest that is required for successfully creating an instance of this tree.
TreeElement & makeRoot()
Set this behavior tree as the root tree of the parent document.
TreeElement & removeFirstChild(const std::string &registration_name="", const std::string &instance_name="")
TreeElement & operator=(const TreeElement &other)
Replace the behavior tree represented by this element with another.
TreeElement & setName(const std::string &tree_name) override
Set the name of the behavior tree.
std::string writeToString() const
Write this behavior tree to an XML encoded in a string.
TreeElement getRootTree()
Get the corresponding behavior tree element for the root tree of this document.
TreeDocument & mergeResource(const TreeResource &resource, bool adopt_root_tree=false)
Merge the behavior trees from one of the installed package's behavior tree resources.
std::string getRootTreeName() const
Get the name of this document's root tree.
TreeDocument & mergeTreeDocument(const XMLDocument &other, bool adopt_root_tree=false)
Merge another tree document with this one.
TreeDocument & mergeString(const std::string &tree_str, bool adopt_root_tree=false)
Create a tree document from a string and merge it with this one.
TreeElement newTreeFromDocument(const TreeDocument &other, const std::string &tree_name="")
Create a new behavior tree inside this document with the content of one found inside another tree doc...
void writeToFile(const std::string &path) const
Write the XML of this tree document to a file.
TreeElement newTreeFromFile(const std::string &path, const std::string &tree_name="")
Create a new behavior tree inside this document with the content of one found inside the XML file.
BT::Result verify() const
Verify that all behavior trees of this document are structured correctly and can be created successfu...
NodeManifest getRequiredNodeManifest() const
Assemble the node manifest that is required for successfully creating an instance of any of the docum...
std::map< std::string, NodeModel > NodeModelMap
Mapping of node registration names and their implementation details.
std::set< std::string > getRegisteredNodeNames(bool include_native=true) const
Get the names of all nodes that are known to this document.
TreeElement newTreeFromString(const std::string &tree_str, const std::string &tree_name="")
Create a new behavior tree inside this document with the content of one found inside the XML string.
TreeElement newTreeFromResource(const TreeResource &resource, const std::string &tree_name="")
Create a new behavior tree inside this document with the content of one the trees found inside a part...
TreeDocument & reset()
Clear this document and reset it to its initial state.
TreeElement getTree(const std::string &tree_name)
Get the corresponding behavior tree element for a tree inside this document.
TreeDocument & addNodeModel(bool include_native=false)
Add an behavior tree node model element to the document.
virtual TreeDocument & registerNodes(const NodeManifest &tree_node_manifest, bool override=false)
Load behavior tree node plugins and register them with the internal behavior tree factory.
TreeDocument & setRootTreeName(const std::string &tree_name)
Define the root tree of this document.
bool hasTreeName(const std::string &tree_name) const
Determine if this document specifies a behavior tree with a particular name.
static NodeModelMap getNodeModel(tinyxml2::XMLDocument &doc)
Convert a behavior tree node model document to the corresponding data structure.
TreeDocument & mergeFile(const std::string &path, bool adopt_root_tree=false)
Create a tree document from a file and merge it with this one.
std::vector< std::string > getAllTreeNames() const
Get the names of all behavior trees inside this document.
TreeDocument & mergeTree(const TreeElement &tree, bool make_root_tree=false)
Merge an existing behavior tree with this tree document.
std::string writeToString() const
Write the XML of this tree document to a string.
bool hasRootTreeName() const
Determine if this document specifies which of its trees is the root tree.
TreeDocument & removeTree(const std::string &tree_name)
Remove a particular behavior tree from this document.
TreeDocument(const std::string &format_version=BTCPP_FORMAT_DEFAULT_VERSION, NodeRegistrationLoader::SharedPtr tree_node_loader=NodeRegistrationLoader::make_shared())
Create a an empty tree document.
TreeElement newTree(const std::string &tree_name)
Create a new behavior tree inside this document.
Class containing behavior tree resource data.
std::string getRootTreeName() const
Get the name of the root tree of this behavior tree resource.
NodeManifest getNodeManifest() const
Get the node manifest associated with this resource.
bool hasRootTreeName() const
Determine if this behavior tree resource specifies a root tree.
Subtree behavior tree node model.
bool contains(const ContainerT< ValueT, AllocatorT > &c, const ValueT &val)
Check whether a particular container structure contains a value.
Definition container.hpp:36
std::set< KeyT, CompareT, AllocatorT > getCommonElements(std::set< KeyT, CompareT, AllocatorT > c1, std::set< KeyT, CompareT, AllocatorT > c2)
Assemble common elements of two sets.
Definition container.hpp:53
void exposeToGlobalDebugLogging(const rclcpp::Logger &logger)
Enable ROS 2 debug logging, if the C preprocessor flag _AUTO_APMS_DEBUG_LOGGING is set.
Definition logging.cpp:25
Core API for AutoAPMS's behavior tree implementation.
Definition builder.hpp:30
Models for all available behavior tree nodes.
Useful tooling for incorporating behavior trees for task development.
Definition builder.hpp:30
Parameters for loading and registering a behavior tree node class from a shared library using e....
Data structure encapsulating the information of all ports implemented by a behavior tree node.
Implementation details of a single data port.
std::string port_default
Default value of the port encoded as string.
BT::PortDirection port_direction
Direction of the port.
std::string port_type
String representation of the C++ type given to the port.