Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
BehaviorTreeExecutor.cpp
Go to the documentation of this file.
1/**
2 * @file BehaviorTreeExecutor.cpp
3 * @brief Native BehaviorTree execution implementation
4 * @author Olympe Engine
5 * @date 2026-03-24
6 */
7
9#include "../system/system_utils.h"
10#include <functional>
11
12namespace Olympe {
13
15 : m_maxDepth(0), m_executedNodes(0)
16{
17}
18
22
24{
25 outTracer.Reset();
26 m_maxDepth = 0;
28
29 if (btAsset.rootNodeId == 0)
30 {
31 SYSTEM_LOG << "[BehaviorTreeExecutor] Tree has no root node\n";
32 outTracer.RecordError(-1, "", "Tree has no root node", "Critical");
33 outTracer.RecordExecutionCompleted(false, "No root node");
34 return BTStatus::Failure;
35 }
36
37 SYSTEM_LOG << "[BehaviorTreeExecutor] Starting execution of tree: " << btAsset.name << "\n";
38
39 // Execute the root node
40 BTStatus result = ExecuteNode(btAsset.rootNodeId, btAsset, outTracer);
41
42 bool success = (result == BTStatus::Success);
43 outTracer.RecordExecutionCompleted(success,
44 success ? "Tree executed successfully" : "Tree execution failed");
45
46 SYSTEM_LOG << "[BehaviorTreeExecutor] Tree execution completed: "
47 << StatusToString(result)
48 << " (executed " << m_executedNodes << " nodes)\n";
49
50 return result;
51}
52
54{
55 m_maxDepth++;
56 if (m_maxDepth > 100)
57 {
58 SYSTEM_LOG << "[BehaviorTreeExecutor] Recursion limit exceeded (cycle detected?)\n";
59 return BTStatus::Failure;
60 }
61
63
64 const BTNode* node = btAsset.GetNode(nodeId);
65 if (!node)
66 {
67 SYSTEM_LOG << "[BehaviorTreeExecutor] Node " << nodeId << " not found in tree\n";
68 return BTStatus::Failure;
69 }
70
71 // Record node entry
72 std::string nodeTypeName;
73 switch (node->type)
74 {
75 case BTNodeType::Selector: nodeTypeName = "Selector"; break;
76 case BTNodeType::Sequence: nodeTypeName = "Sequence"; break;
77 case BTNodeType::Condition: nodeTypeName = "Condition"; break;
78 case BTNodeType::Action: nodeTypeName = "Action"; break;
79 case BTNodeType::Inverter: nodeTypeName = "Inverter"; break;
80 case BTNodeType::Repeater: nodeTypeName = "Repeater"; break;
81 case BTNodeType::Root: nodeTypeName = "Root"; break;
82 case BTNodeType::OnEvent: nodeTypeName = "OnEvent"; break;
83 case BTNodeType::SubGraph: nodeTypeName = "SubGraph"; break; ///< Phase 39
84 default: nodeTypeName = "Unknown"; break;
85 }
86
87 outTracer.RecordNodeEntered(static_cast<int32_t>(nodeId), node->name, nodeTypeName);
88
90
91 switch (node->type)
92 {
95 break;
96
99 break;
100
102 status = ExecuteCondition(*node, outTracer);
103 break;
104
106 status = ExecuteAction(*node, outTracer);
107 break;
108
112 break;
113
114 case BTNodeType::Root:
115 // Root node: execute first child (usually a Selector or Sequence)
116 if (!node->childIds.empty())
117 {
118 status = ExecuteNode(node->childIds[0], btAsset, outTracer);
119 }
120 else
121 {
122 status = BTStatus::Success; // Empty root = success
123 }
124 break;
125
127 // OnEvent nodes only execute when triggered by events
128 // For simulation, treat as success (no event triggered in offline sim)
129 status = BTStatus::Success;
130 break;
131
133 // Phase 39: SubGraph - execute external graph (BT or ATS)
135 break;
136
137 default:
138 status = BTStatus::Failure;
139 break;
140 }
141
142 m_maxDepth--;
143 return status;
144}
145
147{
148 // Selector (OR): tries children in order, returns SUCCESS on first success
149 // Records which branch was taken
150 int childIndex = 0;
151 for (uint32_t childId : node.childIds)
152 {
154
156 {
157 // Record branch taken
158 outTracer.RecordBranchTaken(static_cast<int32_t>(node.id),
159 "Selector child " + std::to_string(childIndex),
160 static_cast<int32_t>(childId));
161 return BTStatus::Success;
162 }
163
164 childIndex++;
165 }
166
167 // All children failed
168 outTracer.RecordBranchTaken(static_cast<int32_t>(node.id), "Selector failed", -1);
169 return BTStatus::Failure;
170}
171
173{
174 // Sequence (AND): runs all children, returns FAILURE on first failure
175 // Records which child failed (if any)
176 int childIndex = 0;
177 for (uint32_t childId : node.childIds)
178 {
180
182 {
183 // Record failure
184 outTracer.RecordBranchTaken(static_cast<int32_t>(node.id),
185 "Sequence failed at child " + std::to_string(childIndex),
186 static_cast<int32_t>(childId));
187 return BTStatus::Failure;
188 }
189
190 childIndex++;
191 }
192
193 // All children succeeded
194 outTracer.RecordBranchTaken(static_cast<int32_t>(node.id), "Sequence succeeded", -1);
195 return BTStatus::Success;
196}
197
199{
200 // Simulate condition evaluation
201 // In offline simulation, we make reasonable assumptions:
202 // - Most conditions succeed (optimistic)
203 // - Some conditions based on entity state fail
204
205 std::string conditionName = node.conditionTypeString;
206 if (conditionName.empty())
207 {
208 switch (node.conditionType)
209 {
210 case BTConditionType::TargetVisible: conditionName = "TargetVisible"; break;
211 case BTConditionType::TargetInRange: conditionName = "TargetInRange"; break;
212 case BTConditionType::HealthBelow: conditionName = "HealthBelow"; break;
213 case BTConditionType::HasMoveGoal: conditionName = "HasMoveGoal"; break;
214 case BTConditionType::CanAttack: conditionName = "CanAttack"; break;
215 case BTConditionType::HeardNoise: conditionName = "HeardNoise"; break;
216 case BTConditionType::IsWaitTimerExpired: conditionName = "IsWaitTimerExpired"; break;
217 case BTConditionType::HasNavigableDestination: conditionName = "HasNavigableDestination"; break;
218 case BTConditionType::HasValidPath: conditionName = "HasValidPath"; break;
219 case BTConditionType::HasReachedDestination: conditionName = "HasReachedDestination"; break;
220 default: conditionName = "Unknown"; break;
221 }
222 }
223
224 // In simulation: conditions optimistically succeed
225 bool result = true; // Assume condition passes
226
227 outTracer.RecordConditionEvaluated(static_cast<int32_t>(node.id),
228 conditionName, result,
229 "Parameter: " + std::to_string(node.conditionParam));
230
231 return result ? BTStatus::Success : BTStatus::Failure;
232}
233
235{
236 // Simulate action execution
237 // In offline simulation, actions always succeed (no runtime effects)
238
239 std::string actionName = "Action";
240 switch (node.actionType)
241 {
242 case BTActionType::SetMoveGoalToLastKnownTargetPos: actionName = "SetMoveGoalToLastKnownTargetPos"; break;
243 case BTActionType::SetMoveGoalToTarget: actionName = "SetMoveGoalToTarget"; break;
244 case BTActionType::SetMoveGoalToPatrolPoint: actionName = "SetMoveGoalToPatrolPoint"; break;
245 case BTActionType::MoveToGoal: actionName = "MoveToGoal"; break;
246 case BTActionType::AttackIfClose: actionName = "AttackIfClose"; break;
247 case BTActionType::PatrolPickNextPoint: actionName = "PatrolPickNextPoint"; break;
248 case BTActionType::ClearTarget: actionName = "ClearTarget"; break;
249 case BTActionType::Idle: actionName = "Idle"; break;
250 case BTActionType::WaitRandomTime: actionName = "WaitRandomTime"; break;
251 case BTActionType::ChooseRandomNavigablePoint: actionName = "ChooseRandomNavigablePoint"; break;
252 case BTActionType::RequestPathfinding: actionName = "RequestPathfinding"; break;
253 case BTActionType::FollowPath: actionName = "FollowPath"; break;
254 case BTActionType::SendMessage: actionName = "SendMessage"; break;
255 default: actionName = "Unknown"; break;
256 }
257
258 outTracer.RecordDataPinResolved(static_cast<int32_t>(node.id), actionName,
259 "Params: " + std::to_string(node.actionParam1) +
260 ", " + std::to_string(node.actionParam2));
261
262 // Actions always succeed in simulation
263 return BTStatus::Success;
264}
265
267{
268 if (node.decoratorChildId == 0)
269 {
270 return BTStatus::Failure; // Decorator with no child = failure
271 }
272
274
275 if (node.type == BTNodeType::Inverter)
276 {
277 // Inverter: flip the result
279 }
280 else if (node.type == BTNodeType::Repeater)
281 {
282 // Repeater: repeat child N times, return success if last iteration succeeds
283 for (int i = 1; i < node.repeatCount; ++i)
284 {
285 childStatus = ExecuteNode(node.decoratorChildId, btAsset, outTracer);
286 }
287 return childStatus;
288 }
289
290 return childStatus;
291}
292
293// Phase 39b: Execute SubGraph node with file loading and parameter binding
295{
296 // 1. Validate SubGraph has a path
297 if (node.subgraphPath.empty())
298 {
299 std::string error = "SubGraph node '" + node.name + "' has no path specified";
300 SYSTEM_LOG << "[BehaviorTreeExecutor] " << error << "\n";
301 outTracer.RecordError(static_cast<int32_t>(node.id), node.name, error, "Error");
302 return BTStatus::Failure;
303 }
304
305 // 2. Cycle detection: check if this path is already on the call stack
306 if (m_callStack.Contains(node.subgraphPath))
307 {
308 std::string error = "Circular reference detected: " + node.subgraphPath;
309 SYSTEM_LOG << "[BehaviorTreeExecutor] " << error << "\n";
310 outTracer.RecordError(static_cast<int32_t>(node.id), node.name, error, "Error");
311 return BTStatus::Failure;
312 }
313
314 // 3. Depth limit check
315 if (m_callStack.IsFull())
316 {
317 std::string error = "SubGraph recursion depth limit (" + std::to_string(SubGraphCallStack::MAX_DEPTH) + ") exceeded";
318 SYSTEM_LOG << "[BehaviorTreeExecutor] " << error << "\n";
319 outTracer.RecordError(static_cast<int32_t>(node.id), node.name, error, "Error");
320 return BTStatus::Failure;
321 }
322
323 // 4. Log execution start
324 SYSTEM_LOG << "[BehaviorTreeExecutor] Executing SubGraph: " << node.subgraphPath
325 << " (depth=" << m_callStack.GetDepth() + 1 << ")\n";
326
327 // 5. Push onto call stack
328 m_callStack.Push(node.subgraphPath);
329
330 // 6. Load external BehaviorTreeAsset from file
331 // Attempt ID generation (simple hash-based for phase 39b)
332 uint32_t subgraphId = std::hash<std::string>{}(node.subgraphPath) & 0x7FFFFFFF;
333
334 // Try to load the tree from file
335 // TODO (Phase 39c): Should cache loaded graphs to avoid repeated file I/O
337 {
338 std::string error = "Failed to load SubGraph: '" + node.subgraphPath + "'";
339 SYSTEM_LOG << "[BehaviorTreeExecutor] ERROR: " << error << "\n";
340 outTracer.RecordError(static_cast<int32_t>(node.id), node.name, error, "Error");
342 return BTStatus::Failure;
343 }
344
345 // 7. Get the loaded tree
347 if (subgraph == nullptr)
348 {
349 std::string error = "SubGraph loaded but not found in manager: '" + node.subgraphPath + "'";
350 SYSTEM_LOG << "[BehaviorTreeExecutor] ERROR: " << error << "\n";
351 outTracer.RecordError(static_cast<int32_t>(node.id), node.name, error, "Error");
353 return BTStatus::Failure;
354 }
355
356 // 8. Apply input parameter bindings (parent -> child)
357 // TODO (Phase 39c): Implement actual parameter passing via blackboard
358 // For now, we just log what would be bound
359 for (const auto& inputBinding : node.subgraphInputs)
360 {
361 const std::string& childParamName = inputBinding.first; // Child graph parameter
362 const std::string& parentParamName = inputBinding.second; // Parent graph parameter
363 SYSTEM_LOG << "[BehaviorTreeExecutor] Input binding: " << childParamName
364 << " <- " << parentParamName << " (Phase 39c)\n";
365 }
366
367 // 9. Execute the loaded subgraph recursively
369
370 SYSTEM_LOG << "[BehaviorTreeExecutor] SubGraph execution completed with status: "
371 << StatusToString(result) << "\n";
372
373 // 10. Apply output parameter bindings (child -> parent)
374 // TODO (Phase 39c): Implement actual parameter collection via blackboard
375 // For now, we just log what would be bound
376 for (const auto& outputBinding : node.subgraphOutputs)
377 {
378 const std::string& parentParamName = outputBinding.first; // Parent graph parameter
379 const std::string& childParamName = outputBinding.second; // Child graph parameter
380 SYSTEM_LOG << "[BehaviorTreeExecutor] Output binding: " << parentParamName
381 << " <- " << childParamName << " (Phase 39c)\n";
382 }
383
384 // 11. Pop from call stack
386
387 return result;
388}
389
391{
392 switch (status)
393 {
394 case BTStatus::Idle: return "Idle";
395 case BTStatus::Running: return "Running";
396 case BTStatus::Success: return "Success";
397 case BTStatus::Failure: return "Failure";
398 case BTStatus::Aborted: return "Aborted";
399 default: return "Unknown";
400 }
401}
402
403} // namespace Olympe
Native BehaviorTree execution for simulation and tracing.
@ SetMoveGoalToTarget
Move towards current target.
@ SetMoveGoalToLastKnownTargetPos
Move to last seen target position.
@ WaitRandomTime
Initialize random timer (param1=min, param2=max)
@ MoveToGoal
Execute movement to goal.
@ ClearTarget
Clear current target.
@ SendMessage
Emit event to EventQueue (param1=EventType enum)
@ PatrolPickNextPoint
Select next patrol point.
@ ChooseRandomNavigablePoint
Choose navigable point (param1=searchRadius, param2=maxAttempts)
@ AttackIfClose
Attack if in range.
@ Idle
Do nothing.
@ SetMoveGoalToPatrolPoint
Move to next patrol waypoint.
@ RequestPathfinding
Request pathfinding to moveGoal via MoveIntent.
@ FollowPath
Follow the path (check progression)
BTStatus
Behavior tree node execution status.
@ Success
Node completed successfully.
@ Running
Node is currently executing.
@ Aborted
Node execution interrupted (e.g., entity destroyed)
@ Failure
Node failed.
@ Idle
Node waiting for execution (not yet started)
@ 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.
@ SubGraph
Phase 39: SubGraph - external graph reference (recursive BT or ATS)
@ 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)
@ HasValidPath
Valid path calculated?
@ CanAttack
Attack is available.
@ IsWaitTimerExpired
Wait timer expired?
@ HeardNoise
Detected noise.
@ TargetVisible
Can see target entity.
@ HasMoveGoal
Movement goal is set.
@ HasNavigableDestination
Navigable destination chosen?
@ HealthBelow
Health below threshold.
@ HasReachedDestination
Reached destination?
@ TargetInRange
Target within specified range.
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
bool LoadTreeFromFile(const std::string &filepath, uint32_t treeId)
static BehaviorTreeManager & Get()
const BehaviorTreeAsset * GetTreeByAnyId(uint32_t treeId) const
BTStatus ExecuteSelector(const BTNode &node, const BehaviorTreeAsset &btAsset, GraphExecutionTracer &outTracer)
Execute a Selector (OR) composite node.
static const char * StatusToString(BTStatus status)
Convert BTStatus to string for logging.
SubGraphCallStack m_callStack
Phase 39: Call stack for SubGraph recursion tracking.
BTStatus ExecuteCondition(const BTNode &node, GraphExecutionTracer &outTracer)
Execute a Condition leaf node.
BTStatus ExecuteTree(const BehaviorTreeAsset &btAsset, GraphExecutionTracer &outTracer)
Execute a BehaviorTree and collect trace information.
int m_executedNodes
Count of executed nodes.
BTStatus ExecuteNode(uint32_t nodeId, const BehaviorTreeAsset &btAsset, GraphExecutionTracer &outTracer)
Recursively execute a single BehaviorTree node.
BTStatus ExecuteAction(const BTNode &node, GraphExecutionTracer &outTracer)
Execute an Action leaf node.
int m_maxDepth
Track recursion depth to detect cycles.
BTStatus ExecuteSubGraph(const BTNode &node, const BehaviorTreeAsset &btAsset, GraphExecutionTracer &outTracer)
Phase 39: Execute a SubGraph reference node.
BTStatus ExecuteSequence(const BTNode &node, const BehaviorTreeAsset &btAsset, GraphExecutionTracer &outTracer)
Execute a Sequence (AND) composite node.
BTStatus ExecuteDecorator(const BTNode &node, const BehaviorTreeAsset &btAsset, GraphExecutionTracer &outTracer)
Apply a Decorator (Inverter, Repeater) to a child node result.
Records execution trace during graph simulation.
< Provides AssetID and INVALID_ASSET_ID
Represents a single node in a behavior tree.
bool Contains(const std::string &path) const
void Push(const std::string &path)
static const int MAX_DEPTH
Maximum recursion depth limit.
#define SYSTEM_LOG