Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
BehaviorTreeGraphAdapter.cpp
Go to the documentation of this file.
1/**
2 * @file BehaviorTreeGraphAdapter.cpp
3 * @brief Implementation of BehaviorTree to TaskGraphTemplate conversion adapter
4 * @author Olympe Engine
5 * @date 2026-03-24
6 *
7 * @details
8 * Implements the BehaviorTreeGraphAdapter class methods for converting hierarchical
9 * BehaviorTree structures into flat TaskGraphTemplate format for generic simulation.
10 */
11
13#include <sstream>
14#include <algorithm>
15#include <iostream>
16
17namespace Olympe {
18
19// ============================================================================
20// PUBLIC: Main Conversion Methods
21// ============================================================================
22
23std::unique_ptr<TaskGraphTemplate> BehaviorTreeGraphAdapter::AdaptToTaskGraph(
25{
26 // Step 1: Validate tree structure before conversion
28 std::cerr << "[BehaviorTreeGraphAdapter] Failed to validate BehaviorTree structure. Conversion aborted.\n";
29 return nullptr;
30 }
31
32 // Step 2: Create output graph template
33 auto taskGraph = std::make_unique<TaskGraphTemplate>();
34 taskGraph->Name = btAsset.name;
35 taskGraph->Description = "Adapted from BehaviorTree: " + btAsset.name;
36 taskGraph->GraphType = "BehaviorTree";
37
38 // For BehaviorTree, the root node is the entry point (not EntryPointID)
39 // EntryPointID is only for VisualScript graphs
40 taskGraph->RootNodeID = static_cast<int32_t>(btAsset.rootNodeId);
41 taskGraph->EntryPointID = -1; // BehaviorTree doesn't use EntryPointID
42
43 // Step 3: Convert all BT nodes recursively, starting from root
44 std::map<uint32_t, int32_t> btToGraphIdMap; // BT nodeId -> Graph nodeId mapping
45
46 if (btAsset.rootNodeId > 0) {
47 const BTNode* rootNode = btAsset.GetNode(btAsset.rootNodeId);
48 if (rootNode) {
50 }
51 }
52
53 std::cout << "[BehaviorTreeGraphAdapter] Successfully adapted BehaviorTree '" << btAsset.name
54 << "' to TaskGraphTemplate (" << taskGraph->Nodes.size() << " nodes)\n";
55
56 return taskGraph;
57}
58
62{
63 std::ostringstream output;
64 output << "BehaviorTree Simulation Trace: " << btAsset.name << "\n";
65 output << "==================================================\n\n";
66
67 const auto& events = tracer.GetEvents();
68
69 if (events.empty()) {
70 output << "[No execution events recorded]\n";
71 return output.str();
72 }
73
74 // Build map of depth for each node (for indentation)
75 std::map<int32_t, int32_t> nodeDepthMap;
76 for (const auto& event : events) {
77 if (nodeDepthMap.find(event.nodeId) == nodeDepthMap.end()) {
78 int32_t depth = CalculateNodeDepth(static_cast<uint32_t>(event.nodeId), btAsset);
79 nodeDepthMap[event.nodeId] = depth;
80 }
81 }
82
83 // Process each event
84 for (const auto& event : events) {
85 // Find corresponding BT node for context
86 const BTNode* btNode = btAsset.GetNode(static_cast<uint32_t>(event.nodeId));
87 if (!btNode) {
88 continue; // Skip events for nodes we can't find
89 }
90
91 // Determine status symbol from event message/type
92 std::string statusSymbol = " "; // Default: neutral
93 if (event.message.find("SUCCESS") != std::string::npos) {
94 statusSymbol = "✓ ";
95 } else if (event.message.find("FAILURE") != std::string::npos) {
96 statusSymbol = "✗ ";
97 } else if (event.message.find("RUNNING") != std::string::npos ||
98 event.message.find("Running") != std::string::npos) {
99 statusSymbol = "⊙ ";
100 }
101
102 // Get indentation from node depth
103 int32_t depth = nodeDepthMap[event.nodeId];
104 std::string indent(depth * 2, ' ');
105
106 // Get BT-specific type name
107 std::string typeName = GetBTNodeTypeName(static_cast<uint8_t>(btNode->type));
108
109 // Build trace line
110 output << indent << statusSymbol << typeName << ": " << btNode->name;
111
112 // Add event message if present
113 if (!event.message.empty()) {
114 output << " → " << event.message;
115 }
116
117 output << "\n";
118 }
119
120 output << "\n==================================================\n";
121 output << "Total Events: " << events.size() << "\n";
122
123 return output.str();
124}
125
128{
129 // Check 1: Empty tree
130 if (btAsset.nodes.empty()) {
131 std::cout << "[BehaviorTreeGraphAdapter] Warning: BehaviorTree is empty (no nodes)\n";
132 return btAsset.rootNodeId == 0; // Valid if root is also 0
133 }
134
135 // Check 2: Node ID uniqueness
136 std::map<uint32_t, bool> seenIds;
137 for (const auto& node : btAsset.nodes) {
138 if (seenIds.find(node.id) != seenIds.end()) {
139 std::cerr << "[BehaviorTreeGraphAdapter] Error: Duplicate node ID: " << node.id << "\n";
140 return false;
141 }
142 seenIds[node.id] = true;
143 }
144
145 // Check 3: Root node exists
146 if (btAsset.rootNodeId != 0) {
147 if (btAsset.GetNode(btAsset.rootNodeId) == nullptr) {
148 std::cerr << "[BehaviorTreeGraphAdapter] Error: Root node ID " << btAsset.rootNodeId << " does not exist in tree\n";
149 return false;
150 }
151 }
152
153 // Check 4: Child references validity (all referenced children must exist)
154 for (const auto& node : btAsset.nodes) {
155 for (uint32_t childId : node.childIds) {
156 if (btAsset.GetNode(childId) == nullptr) {
157 std::cerr << "[BehaviorTreeGraphAdapter] Error: Node " << node.id << " references non-existent child " << childId << "\n";
158 return false;
159 }
160 }
161 }
162
163 // Check 5: Cycle detection
164 if (btAsset.rootNodeId != 0) {
165 if (btAsset.DetectCycle(btAsset.rootNodeId)) {
166 std::cerr << "[BehaviorTreeGraphAdapter] Error: Cycle detected in BehaviorTree\n";
167 return false;
168 }
169 }
170
171 return true;
172}
173
174// ============================================================================
175// PRIVATE: Helper Methods
176// ============================================================================
177
179 const BTNode& btNode,
182 std::map<uint32_t, int32_t>& btToGraphIdMap)
183{
184 // Check if we've already processed this node (cycle safety)
185 if (btToGraphIdMap.find(btNode.id) != btToGraphIdMap.end()) {
186 return; // Already processed
187 }
188
189 // Step 1: Create graph node from BT node
191 graphNode.NodeID = static_cast<int32_t>(outGraph.Nodes.size());
192 graphNode.NodeName = btNode.name;
193
194 // Map BTNodeType to TaskNodeType
195 switch (btNode.type) {
198 break;
201 break;
204 graphNode.AtomicTaskID = "BT_Condition_" + std::to_string(static_cast<uint8_t>(btNode.conditionType));
205 break;
208 graphNode.AtomicTaskID = "BT_Action_" + std::to_string(static_cast<uint8_t>(btNode.actionType));
209 break;
213 break;
214 case BTNodeType::Root:
217 break;
218 default:
220 }
221
222 // Store editor position
223 graphNode.EditorPosX = btNode.editorPosX;
224 graphNode.EditorPosY = btNode.editorPosY;
225 graphNode.HasEditorPos = true;
226
227 // Track the mapping
228 btToGraphIdMap[btNode.id] = graphNode.NodeID;
229 const int32_t currentGraphNodeId = graphNode.NodeID;
230
231 // Step 2: Add to graph
232 outGraph.Nodes.push_back(graphNode);
233
234 // Step 3: Recursively add children and create connections
235 for (uint32_t childBtId : btNode.childIds) {
236 const BTNode* childNode = btAsset.GetNode(childBtId);
237 if (!childNode) continue;
238
239 // Recursively process child (if not already done)
241
242 // Find the graph node ID for this child
244
245 // Create explicit connection (parent control-flow → child)
248 connection.SourcePinName = "Control"; // Output pin name
249 connection.TargetNodeID = childGraphId;
250 connection.TargetPinName = "In"; // Input pin name
251
252 outGraph.ExecConnections.push_back(connection);
253 }
254}
255
257 uint32_t nodeId,
259{
260 // Base case: root node
261 if (nodeId == btAsset.rootNodeId) {
262 return 0;
263 }
264
265 // Find parent node
266 uint32_t parentId = FindParentNodeId(nodeId, btAsset);
267 if (parentId == 0) {
268 return 0; // Root or orphaned node
269 }
270
271 // Recursive case: depth = parent_depth + 1
272 return CalculateNodeDepth(parentId, btAsset) + 1;
273}
274
278{
279 // Search all nodes to find one that has childNodeId in its children
280 for (const auto& node : btAsset.nodes) {
281 for (uint32_t childId : node.childIds) {
282 if (childId == childNodeId) {
283 return node.id; // Found parent
284 }
285 }
286 }
287 return 0; // No parent found (root or orphaned)
288}
289
291{
292 // Cast to BTNodeType for safety
293 BTNodeType nodeType = static_cast<BTNodeType>(type);
294
295 switch (nodeType) {
296 case BTNodeType::Selector: return "Selector";
297 case BTNodeType::Sequence: return "Sequence";
298 case BTNodeType::Condition: return "Condition";
299 case BTNodeType::Action: return "Action";
300 case BTNodeType::Inverter: return "Inverter";
301 case BTNodeType::Repeater: return "Repeater";
302 case BTNodeType::Root: return "Root";
303 case BTNodeType::OnEvent: return "OnEvent";
304 default: return "Unknown";
305 }
306}
307
308} // namespace Olympe
Adapter layer for converting BehaviorTree graphs to generic TaskGraphTemplate format.
BTNodeType
Behavior tree node types.
@ Action
Leaf node - performs an action.
@ Selector
OR node - succeeds if any child succeeds.
@ OnEvent
Phase 38b: OnEvent root - event-driven entry point (orange, event-triggered)
@ Sequence
AND node - succeeds if all children succeed.
@ Inverter
Decorator - inverts child result.
@ Condition
Leaf node - checks a condition.
@ Repeater
Decorator - repeats child N times.
@ Root
Phase 38b: Root node - entry point of behavior tree (green, fixed position)
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
static void AddNodeToGraph(const BTNode &btNode, const BehaviorTreeAsset &btAsset, TaskGraphTemplate &outGraph, std::map< uint32_t, int32_t > &btToGraphIdMap)
Helper: Recursively adds a node and its children to the graph.
static uint32_t FindParentNodeId(uint32_t childNodeId, const BehaviorTreeAsset &btAsset)
Helper: Finds parent node ID for a given child node.
static bool ValidateTreeStructure(const BehaviorTreeAsset &btAsset)
Validates BehaviorTree structure before conversion.
static int32_t CalculateNodeDepth(uint32_t nodeId, const BehaviorTreeAsset &btAsset)
Helper: Calculates depth of a node in BT hierarchy.
static std::string GetBTNodeTypeName(uint8_t type)
Helper: Gets BehaviorTree node type name.
static std::unique_ptr< TaskGraphTemplate > AdaptToTaskGraph(const BehaviorTreeAsset &btAsset)
Converts a BehaviorTreeAsset to TaskGraphTemplate format.
static std::string FormatTraceForBehaviorTree(const GraphExecutionTracer &tracer, const BehaviorTreeAsset &btAsset)
Formats generic execution trace back to BehaviorTree-specific context.
Records execution trace during graph simulation.
Immutable, shareable task graph asset.
< Provides AssetID and INVALID_ASSET_ID
@ Selector
Executes children in order; stops on first success.
@ AtomicTask
Leaf node that executes a single atomic task.
@ Sequence
Executes children in order; stops on first failure.
@ Decorator
Wraps a single child and modifies its behaviour.
@ Root
Entry point of the graph (exactly one per template)
Represents a single node in a behavior tree.
Explicit connection between a named exec-out pin of a source node and the exec-in pin of a target nod...
Full description of a single node in the task graph.
int32_t NodeID
Unique ID within this template.