Ember
Loading...
Searching...
No Matches
BehaviorTree.cpp
Go to the documentation of this file.
1#include "Core/BehaviorTree.h"
2#include "Utils/Logger.h"
3#include <algorithm>
4#include <queue>
5#include <sstream>
6#include <stack>
7
8namespace EmberCore {
9
12 Logger::GetInstance().Info("BehaviorTree", "Created behavior tree: " + name);
13}
14
15BehaviorTree::~BehaviorTree() { Logger::GetInstance().Info("BehaviorTree", "Destroying behavior tree: " + name_); }
16
17void BehaviorTree::SetRootNode(std::unique_ptr<Node> root) {
18 root_node_ = std::move(root);
19 root_node_shared_.reset(); // Clear shared version
21 NotifyTreeChange("root_changed");
22}
23
24void BehaviorTree::SetRootNode(std::shared_ptr<Node> root) {
25 root_node_shared_ = root;
26 root_node_.reset(); // Clear unique version
28 NotifyTreeChange("root_changed");
29}
30
32 Node *root = GetRootNode();
33 if (!root)
34 return 0;
35 return root->GetSubtreeNodeCount();
36}
37
39 Node *root = GetRootNode();
40 if (!root)
41 return 0;
42 return root->GetDepth();
43}
44
45std::vector<Node *> BehaviorTree::GetAllNodes() {
46 std::vector<Node *> nodes;
47 Node *root = GetRootNode();
48 if (root) {
49 CollectNodesRecursive(root, nodes);
50 }
51 return nodes;
52}
53
54std::vector<const Node *> BehaviorTree::GetAllNodes() const {
55 std::vector<const Node *> nodes;
56 const Node *root = GetRootNode();
57 if (root) {
58 CollectNodesRecursive(root, nodes);
59 }
60 return nodes;
61}
62
64 auto it = node_index_.find(id);
65 return (it != node_index_.end()) ? it->second : nullptr;
66}
67
69 return FindNodeRecursive(GetRootNode(), [&name](const Node *node) { return node->GetName() == name; });
70}
71
72std::vector<Node *> BehaviorTree::FindNodesByType(Node::Type type) const {
73 std::vector<Node *> result;
74 // Use const version to avoid const-correctness issues
75 TraverseNodes([&result, type](const Node *node) {
76 if (node->GetType() == type) {
77 result.push_back(const_cast<Node *>(node));
78 }
79 });
80 return result;
81}
82
83bool BehaviorTree::AddNode(std::unique_ptr<Node> node, Node *parent) {
84 if (!node) {
85 Logger::GetInstance().Error("BehaviorTree", "Cannot add null node to behavior tree");
86 return false;
87 }
88
89 if (!parent) {
90 if (root_node_) {
91 Logger::GetInstance().Error("BehaviorTree", "Cannot add root node - tree already has a root");
92 return false;
93 }
94 SetRootNode(std::move(node));
95 return true;
96 }
97
98 // Find parent in our tree
99 Node *found_parent = FindNodeById(parent->GetId());
100 if (!found_parent) {
101 Logger::GetInstance().Error("BehaviorTree", "Parent node not found in tree");
102 return false;
103 }
104
105 Node *node_ptr = node.get();
106 found_parent->AddChild(std::move(node));
108 NotifyNodeChange(node_ptr, "added");
109 return true;
110}
111
112bool BehaviorTree::RemoveNode(size_t node_id) {
113 Node *node = FindNodeById(node_id);
114 if (!node) {
115 Logger::GetInstance().Error("BehaviorTree", "Node not found for removal");
116 return false;
117 }
118
119 if (node == root_node_.get()) {
120 root_node_.reset();
121 node_index_.clear();
122 NotifyTreeChange("root_removed");
123 return true;
124 }
125
126 // Find parent and remove child
127 auto all_nodes = GetAllNodes();
128 for (Node *potential_parent : all_nodes) {
129 for (size_t i = 0; i < potential_parent->GetChildCount(); ++i) {
130 if (potential_parent->GetChild(i)->GetId() == node_id) {
131 potential_parent->RemoveChild(i);
133 NotifyNodeChange(node, "removed");
134 return true;
135 }
136 }
137 }
138
139 Logger::GetInstance().Error("BehaviorTree", "Could not find parent of node to remove");
140 return false;
141}
142
143bool BehaviorTree::MoveNode(size_t node_id, Node *new_parent) {
144 // Move node to a new parent
145 // This is a placeholder implementation for step 2
146 return false;
147}
148
150 ValidationResult result;
151
152 // Check for root node (use both unique_ptr and shared_ptr)
153 Node *root = GetRootNode();
154 if (!root) {
155 result.AddError("Tree has no root node");
156 return result;
157 }
158
159 // Check for cycles
160 if (HasCycles()) {
161 result.AddError("Tree contains cycles");
162 }
163
164 // Validate individual nodes recursively
165 std::vector<const Node *> visited;
166 auto node_result = ValidateNode(root, visited);
167
168 // Merge results from node validation
169 for (const auto &error : node_result.errors) {
170 result.errors.push_back(error);
171 result.is_valid = false;
172 }
173 for (const auto &warning : node_result.warnings) {
174 result.warnings.push_back(warning);
175 }
176
177 return result;
178}
179
181 if (!root_node_)
182 return false;
183 return root_node_->HasCycles();
184}
185
187 root_node_.reset();
188 node_index_.clear();
190 NotifyTreeChange("cleared");
191}
192
193std::unique_ptr<BehaviorTree> BehaviorTree::Clone() const {
194 auto cloned_tree = std::make_unique<BehaviorTree>(name_ + " (Copy)");
195 cloned_tree->description_ = description_;
196 cloned_tree->metadata_ = metadata_;
197
198 if (root_node_) {
199 // Simple clone for now - just create a basic action node with same name
200 auto cloned_root = NodeFactory::CreateActionNode(root_node_->GetName());
201 cloned_tree->SetRootNode(std::move(cloned_root));
202 }
203
204 return cloned_tree;
205}
206
215
216void BehaviorTree::TraverseNodes(std::function<void(Node *)> visitor) {
217 if (root_node_) {
218 root_node_->Accept(visitor);
219 }
220}
221
222void BehaviorTree::TraverseNodes(std::function<void(const Node *)> visitor) const {
223 if (root_node_) {
224 // Use explicit cast to resolve ambiguity
225 const Node *const_root = root_node_.get();
226 const_root->Accept(visitor);
227 }
228}
229
230void BehaviorTree::TraversePreOrder(std::function<void(Node *)> visitor) {
231 if (!root_node_)
232 return;
233
234 std::stack<Node *> stack;
235 stack.push(root_node_.get());
236
237 while (!stack.empty()) {
238 Node *current = stack.top();
239 stack.pop();
240
241 visitor(current);
242
243 // Add children in reverse order for proper pre-order traversal
244 for (int i = current->GetChildCount() - 1; i >= 0; --i) {
245 stack.push(current->GetChild(i));
246 }
247 }
248}
249
250void BehaviorTree::TraversePostOrder(std::function<void(Node *)> visitor) {
251 if (!root_node_)
252 return;
253
254 std::stack<Node *> stack1, stack2;
255 stack1.push(root_node_.get());
256
257 while (!stack1.empty()) {
258 Node *current = stack1.top();
259 stack1.pop();
260 stack2.push(current);
261
262 for (size_t i = 0; i < current->GetChildCount(); ++i) {
263 stack1.push(current->GetChild(i));
264 }
265 }
266
267 while (!stack2.empty()) {
268 visitor(stack2.top());
269 stack2.pop();
270 }
271}
272
274 if (root_node_) {
275 Logger::GetInstance().Info("BehaviorTree", "=== Behavior Tree: " + name_ + " ===");
276 root_node_->PrintTree();
277 } else {
278 Logger::GetInstance().Info("BehaviorTree", "Behavior Tree '" + name_ + "' is empty");
279 }
280}
281
283 if (!root_node_)
284 return "Empty tree";
285
286 std::ostringstream oss;
287 oss << "Tree: " << name_ << "\n";
288 oss << "Nodes: " << GetNodeCount() << "\n";
289 oss << "Depth: " << GetMaxDepth() << "\n";
290 oss << "Structure:\n" << root_node_->ToString();
291
292 return EmberCore::String(oss.str());
293}
294
295std::unordered_map<Node::Type, size_t> BehaviorTree::GetNodeTypeStatistics() const {
296 std::unordered_map<Node::Type, size_t> stats;
297
298 // Use const version to avoid const-correctness issues
299 TraverseNodes([&stats](const Node *node) { stats[node->GetType()]++; });
300
301 return stats;
302}
303
305 metadata_[key] = value;
306 NotifyTreeChange("metadata_changed");
307}
308
310 auto it = metadata_.find(key);
311 return (it != metadata_.end()) ? it->second : EmberCore::String("");
312}
313
315 metadata_.erase(key);
316 NotifyTreeChange("metadata_changed");
317}
318
321 node_change_callback_(node, change_type);
322 }
323}
324
327 tree_change_callback_(change_type);
328 }
329}
330
332 node_index_.clear();
333 if (root_node_) {
335 }
336}
337
338Node *BehaviorTree::FindNodeRecursive(Node *current, std::function<bool(const Node *)> predicate) const {
339 if (!current)
340 return nullptr;
341
342 if (predicate(current)) {
343 return current;
344 }
345
346 for (size_t i = 0; i < current->GetChildCount(); ++i) {
347 Node *result = FindNodeRecursive(current->GetChild(i), predicate);
348 if (result)
349 return result;
350 }
351
352 return nullptr;
353}
354
355void BehaviorTree::CollectNodesRecursive(Node *current, std::vector<Node *> &nodes) {
356 if (!current)
357 return;
358
359 nodes.push_back(current);
360 for (size_t i = 0; i < current->GetChildCount(); ++i) {
361 CollectNodesRecursive(current->GetChild(i), nodes);
362 }
363}
364
365void BehaviorTree::CollectNodesRecursive(const Node *current, std::vector<const Node *> &nodes) const {
366 if (!current)
367 return;
368
369 nodes.push_back(current);
370 for (size_t i = 0; i < current->GetChildCount(); ++i) {
371 CollectNodesRecursive(current->GetChild(i), nodes);
372 }
373}
374
375BehaviorTree::ValidationResult BehaviorTree::ValidateNode(const Node *node, std::vector<const Node *> &visited) const {
376 ValidationResult result;
377
378 if (!node) {
379 result.AddError("Null node found in tree");
380 return result;
381 }
382
383 // Check for circular references
384 if (std::find(visited.begin(), visited.end(), node) != visited.end()) {
385 result.AddError("Circular reference detected at node: " + node->GetName());
386 return result;
387 }
388 visited.push_back(node);
389
390 // Basic validity check
391 if (!node->IsValid()) {
392 result.AddError("Invalid node: " + node->GetName());
393 }
394
395 // Validate based on node type
396 size_t child_count = node->GetChildCount();
397
398 switch (node->GetType()) {
400 // Decorators must have exactly 1 child
401 if (child_count == 0) {
402 result.AddError("Decorator node '" + node->GetName() + "' has no children (must have exactly 1)");
403 } else if (child_count > 1) {
404 result.AddWarning("Decorator node '" + node->GetName() + "' has " + std::to_string(child_count) +
405 " children (typically should have exactly 1)");
406 }
407 break;
408
410 // Control nodes typically should have at least 1 child
411 if (child_count == 0) {
412 result.AddWarning("Control node '" + node->GetName() + "' has no children");
413 }
414 break;
415
418 // Leaf nodes typically should not have children
419 if (child_count > 0) {
420 result.AddWarning("Leaf node '" + node->GetName() + "' has " + std::to_string(child_count) +
421 " children (leaf nodes typically have no children)");
422 }
423 break;
424
425 default:
426 break;
427 }
428
429 // Check for required attributes (name should not be empty)
430 if (node->GetName().empty()) {
431 result.AddError("Node has empty name");
432 }
433
434 // Recursively validate children
435 for (size_t i = 0; i < child_count; ++i) {
436 const Node *child = node->GetChild(i);
437 if (child) {
438 auto child_result = ValidateNode(child, visited);
439
440 // Merge child results
441 for (const auto &error : child_result.errors) {
442 result.errors.push_back(error);
443 result.is_valid = false;
444 }
445 for (const auto &warning : child_result.warnings) {
446 result.warnings.push_back(warning);
447 }
448 } else {
449 result.AddError("Node '" + node->GetName() + "' has null child at index " + std::to_string(i));
450 }
451 }
452
453 return result;
454}
455
457 if (!node)
458 return;
459
460 node_index_[node->GetId()] = node;
461 for (size_t i = 0; i < node->GetChildCount(); ++i) {
462 BuildNodeIndex(node->GetChild(i));
463 }
464}
465
466// Utility functions
467namespace BehaviorTreeUtils {
468
469std::unique_ptr<BehaviorTree> CreateSampleTree() {
470 auto tree = std::make_unique<BehaviorTree>("Sample Tree");
471 tree->SetDescription("A sample behavior tree for demonstration");
472
473 // Create a simple tree structure using existing factory methods
474 auto root = NodeFactory::CreateControlNode("Root Sequence");
475 auto condition = NodeFactory::CreateConditionNode("Check Condition");
476 auto action1 = NodeFactory::CreateActionNode("Action 1");
477 auto action2 = NodeFactory::CreateActionNode("Action 2");
478
479 root->AddChild(std::move(condition));
480 root->AddChild(std::move(action1));
481 root->AddChild(std::move(action2));
482
483 tree->SetRootNode(std::move(root));
484 return tree;
485}
486
487std::unique_ptr<BehaviorTree> CreateEmptyTree(const EmberCore::String &name) {
488 auto tree = std::make_unique<BehaviorTree>(name);
489 tree->SetDescription("Empty behavior tree");
490 return tree;
491}
492
493} // namespace BehaviorTreeUtils
494
495// Blackboard management implementation
496void BehaviorTree::AddBlackboard(std::unique_ptr<Blackboard> blackboard) {
497 if (!blackboard) {
498 LOG_ERROR("BehaviorTree", "Cannot add null blackboard to behavior tree");
499 return;
500 }
501
502 EmberCore::String id = blackboard->GetId();
503 if (id.empty()) {
504 LOG_ERROR("BehaviorTree", "Cannot add blackboard with empty ID to behavior tree");
505 return;
506 }
507
508 blackboards_[id] = std::move(blackboard);
509 LOG_INFO("BehaviorTree", "Added blackboard '" + id + "' to behavior tree '" + name_ + "'");
510}
511
513 auto it = blackboards_.find(id);
514 return (it != blackboards_.end()) ? it->second.get() : nullptr;
515}
516
518 auto it = blackboards_.find(id);
519 return (it != blackboards_.end()) ? it->second.get() : nullptr;
520}
521
523 return blackboards_.find(id) != blackboards_.end();
524}
525
527 auto it = blackboards_.find(id);
528 if (it != blackboards_.end()) {
529 blackboards_.erase(it);
530 LOG_INFO("BehaviorTree", "Removed blackboard '" + id + "' from behavior tree '" + name_ + "'");
531 }
532}
533
535 size_t count = blackboards_.size();
536 blackboards_.clear();
537 LOG_INFO("BehaviorTree", "Cleared " + std::to_string(count) + " blackboards from behavior tree '" + name_ + "'");
538}
539
540} // namespace EmberCore
#define LOG_ERROR(category, message)
Definition Logger.h:116
#define LOG_INFO(category, message)
Definition Logger.h:114
Blackboard * GetBlackboard(const EmberCore::String &id)
std::map< EmberCore::String, std::unique_ptr< Blackboard > > blackboards_
std::shared_ptr< Node > root_node_shared_
std::unique_ptr< Node > root_node_
void TraverseNodes(std::function< void(Node *)> visitor)
size_t GetNodeCount() const
std::vector< Node * > GetAllNodes()
Node * GetRootNode() const
std::unordered_map< size_t, Node * > node_index_
void TraversePostOrder(std::function< void(Node *)> visitor)
EmberCore::String GetMetadata(const EmberCore::String &key) const
EmberCore::String description_
bool HasBlackboard(const EmberCore::String &id) const
std::unordered_map< Node::Type, size_t > GetNodeTypeStatistics() const
void SetMetadata(const EmberCore::String &key, const EmberCore::String &value)
size_t GetMaxDepth() const
void AddBlackboard(std::unique_ptr< Blackboard > blackboard)
EmberCore::String name_
void BuildNodeIndex(Node *node)
ExecutionState
Tree execution states.
bool RemoveNode(size_t node_id)
EmberCore::String GetTreeStructure() const
void CollectNodesRecursive(Node *current, std::vector< Node * > &nodes)
ValidationResult ValidateNode(const Node *node, std::vector< const Node * > &visited) const
void RemoveBlackboard(const EmberCore::String &id)
bool MoveNode(size_t node_id, Node *new_parent)
BehaviorTree(const EmberCore::String &name="Behavior Tree")
Constructor.
ValidationResult Validate() const
bool AddNode(std::unique_ptr< Node > node, Node *parent=nullptr)
std::unique_ptr< BehaviorTree > Clone() const
void TraversePreOrder(std::function< void(Node *)> visitor)
void SetRootNode(std::unique_ptr< Node > root)
ExecutionState execution_state_
std::unordered_map< EmberCore::String, EmberCore::String > metadata_
TreeChangeCallback tree_change_callback_
std::vector< Node * > FindNodesByType(Node::Type type) const
void NotifyTreeChange(const EmberCore::String &change_type)
Node * FindNodeById(size_t id) const
NodeChangeCallback node_change_callback_
Node * FindNodeByName(const EmberCore::String &name) const
void NotifyNodeChange(Node *node, const EmberCore::String &change_type)
void RemoveMetadata(const EmberCore::String &key)
Node * FindNodeRecursive(Node *current, std::function< bool(const Node *)> predicate) const
Represents a blackboard containing multiple entries.
void Error(const EmberCore::String &category, const EmberCore::String &message)
Definition Logger.cpp:113
static Logger & GetInstance()
Definition Logger.cpp:251
void Info(const EmberCore::String &category, const EmberCore::String &message)
Definition Logger.cpp:105
static std::unique_ptr< Node > CreateConditionNode(const String &name)
Definition Node.cpp:408
static std::unique_ptr< Node > CreateActionNode(const String &name)
Definition Node.cpp:400
static std::unique_ptr< Node > CreateControlNode(const String &name)
Definition Node.cpp:404
Represents a node in a behavior tree structure.
Definition Node.h:20
void SetVisualState(VisualState state)
Definition Node.h:95
size_t GetSubtreeNodeCount() const
Definition Node.cpp:246
Node * GetChild(size_t index) const
Definition Node.cpp:175
Type
Node types for behavior tree classification.
Definition Node.h:25
void Accept(std::function< void(Node *)> visitor)
Definition Node.cpp:254
size_t GetId() const
Definition Node.h:80
Type GetType() const
Definition Node.h:84
const String & GetName() const
Definition Node.h:81
bool IsValid() const
Definition Node.cpp:205
void AddChild(std::unique_ptr< Node > child)
Definition Node.cpp:77
size_t GetChildCount() const
Definition Node.h:76
void SetStatus(Status status)
Definition Node.h:88
size_t GetDepth() const
Definition Node.cpp:236
std::unique_ptr< BehaviorTree > CreateSampleTree()
Create a sample behavior tree for testing.
std::unique_ptr< BehaviorTree > CreateEmptyTree(const EmberCore::String &name="New Tree")
Create an empty behavior tree with basic structure.
Main types header for EmberCore.
std::string String
Framework-agnostic string type.
Definition String.h:14
void AddError(const EmberCore::String &error)
void AddWarning(const EmberCore::String &warning)
std::vector< EmberCore::String > errors
std::vector< EmberCore::String > warnings