Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
BehaviorTreeDebugWindow.cpp
Go to the documentation of this file.
1/**
2 * @file BehaviorTreeDebugWindow.cpp
3 * @brief Implementation of behavior tree runtime debugger
4 */
5
7#include "../World.h"
8#include "../GameEngine.h"
9#include "../ECS_Components.h"
10#include "../ECS_Components_AI.h"
11#include "../json_helper.h"
12#include "../third_party/imgui/imgui.h"
13#include "../third_party/imnodes/imnodes.h"
14#include "../third_party/imgui/backends/imgui_impl_sdl3.h"
15#include "../third_party/imgui/backends/imgui_impl_sdlrenderer3.h"
16#include <SDL3/SDL.h>
17#include <algorithm>
18#include <cstring>
19#include <cmath>
20#include <ctime>
21#include <unordered_set>
22#include <set>
23
24namespace Olympe
25{
27 : m_separateWindow(nullptr)
28 , m_separateRenderer(nullptr)
29 , m_windowCreated(false)
30 , m_separateImGuiContext(nullptr)
31 {
32 }
33
38
40 {
42 return;
43
45 {
46 ImNodes::CreateContext();
47 ImNodes::GetStyle().GridSpacing = 32.0f;
48 ImNodes::GetStyle().NodeCornerRounding = 8.0f;
49 ImNodes::GetStyle().NodePadding = ImVec2(8, 8);
51 }
52
53 m_isInitialized = true;
54
55 // Set up autosave timing only. The per-save lambda overload of
56 // ScheduleSave() is used at each change site so that serialization
57 // happens on the UI thread and the background task only does I/O.
58 m_autosave.Init(nullptr, 1.5f, 60.0f);
59
60 // Initialize NodeGraph debug panel (Blueprint Editor pipeline, Runtime mode)
62
63 std::cout << "[BTDebugger] Initialized (window will be created on first F10)" << std::endl;
64 }
65
67 {
68 // 1. Flush autosave first
70
71 // 2. Shutdown NodeGraph BEFORE destroying window (needs ImGui/ImNodes contexts alive)
73
74 // 3. Destroy ImNodes context BEFORE destroying ImGui context
76 {
77 ImNodes::DestroyContext();
79 }
80
81 // 4. Destroy window and ImGui context LAST
83
84 m_isInitialized = false;
85 }
86
88 {
90
91 if (m_isVisible)
92 {
93 if (!m_isInitialized)
94 {
95 Initialize();
96 }
97
98 if (!m_windowCreated)
99 {
101 }
102
103 std::cout << "[BTDebugger] F10: Debugger window opened (separate window)" << std::endl;
104 }
105 else
106 {
108
109 std::cout << "[BTDebugger] F10: Debugger window closed" << std::endl;
110 }
111 }
112
114 {
115 if (m_windowCreated)
116 {
117 std::cout << "[BTDebugger] Separate window already exists" << std::endl;
118 return;
119 }
120
121 ImGuiContext* previousContext = ImGui::GetCurrentContext();
122
123 const int windowWidth = 1200;
124 const int windowHeight = 720;
125
127 "Behavior Tree Runtime Debugger - Independent Window",
133 {
134 std::cout << "[BTDebugger] ERROR: Failed to create separate window: "
135 << SDL_GetError() << std::endl;
136 return;
137 }
138
139 m_separateImGuiContext = ImGui::CreateContext();
140 ImGui::SetCurrentContext(m_separateImGuiContext);
141
142 ImGuiIO& io = ImGui::GetIO();
143 (void)io;
144 ImGui::StyleColorsDark();
145
148
149 m_windowCreated = true;
150
151 ImGui::SetCurrentContext(previousContext);
152
153 std::cout << "[BTDebugger] ✅ Separate window created successfully!" << std::endl;
154 std::cout << "[BTDebugger] Window can be moved to second monitor" << std::endl;
155 }
156
158 {
159 if (!m_windowCreated)
160 return;
161
162 ImGuiContext* previousContext = ImGui::GetCurrentContext();
163
164 if (m_separateImGuiContext != nullptr)
165 {
166 ImGui::SetCurrentContext(m_separateImGuiContext);
167
168 // Shutdown backends BEFORE destroying context
171
172 // Destroy ImGui context
173 ImGui::DestroyContext(m_separateImGuiContext);
174 m_separateImGuiContext = nullptr;
175 }
176
177 // Restore context BEFORE destroying SDL resources
179 ImGui::SetCurrentContext(previousContext);
180
181 // Destroy SDL resources LAST
182 if (m_separateRenderer != nullptr)
183 {
185 m_separateRenderer = nullptr;
186 }
187
188 if (m_separateWindow != nullptr)
189 {
191 m_separateWindow = nullptr;
192 }
193
194 m_windowCreated = false;
195
196 std::cout << "[BTDebugger] Separate window destroyed" << std::endl;
197 }
198
200 {
202 return;
203
205 {
206 if (event->window.windowID == SDL_GetWindowID(m_separateWindow))
207 {
209 return;
210 }
211 }
212
213 ImGuiContext* previousContext = ImGui::GetCurrentContext();
214
215 ImGui::SetCurrentContext(m_separateImGuiContext);
217
218 ImGui::SetCurrentContext(previousContext);
219 }
220
222 {
224 return;
225
226 ImGuiContext* previousContext = ImGui::GetCurrentContext();
227
228 ImGui::SetCurrentContext(m_separateImGuiContext);
229
232 ImGui::NewFrame();
233
235
236 ImGui::Render();
241
242 ImGui::SetCurrentContext(previousContext);
243 }
244
246 {
247 static float accumulatedTime = 0.0f;
249
251 {
253 accumulatedTime = 0.0f;
254 }
255
256 for (auto& entry : m_executionLog)
257 {
258 entry.timeAgo += GameEngine::fDt;
259 }
260
261 ImGui::SetNextWindowPos(ImVec2(0, 0));
262 ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize);
263
271
272 if (!ImGui::Begin("Behavior Tree Runtime Debugger##Main", nullptr, windowFlags))
273 {
274 ImGui::End();
275 return;
276 }
277
278 if (ImGui::BeginMenuBar())
279 {
280 if (ImGui::BeginMenu("View"))
281 {
282 ImGui::SliderFloat("Auto Refresh (s)", &m_autoRefreshInterval, 0.1f, 5.0f);
283 ImGui::SliderFloat("Entity List Width", &m_entityListWidth, 150.0f, 400.0f);
284 ImGui::SliderFloat("Inspector Width", &m_inspectorWidth, 250.0f, 500.0f);
285
286 ImGui::Separator();
287 ImGui::Text("Window Mode: Separate (Independent)");
288 ImGui::Text("Press F10 to close window");
289
290 ImGui::EndMenu();
291 }
292
293 if (ImGui::BeginMenu("Actions"))
294 {
295 if (ImGui::MenuItem("Refresh Now (F5)"))
296 {
298 }
299 if (ImGui::MenuItem("Clear Execution Log"))
300 {
301 m_executionLog.clear();
302 }
303
304 ImGui::EndMenu();
305 }
306
307 ImGui::EndMenuBar();
308 }
309
310 // Keyboard shortcuts
311 if (ImGui::IsKeyPressed(ImGuiKey_F5))
312 {
314 }
315
316 float windowWidth = ImGui::GetContentRegionAvail().x;
317 float windowHeight = ImGui::GetContentRegionAvail().y;
318
319 ImGui::BeginChild("EntityListPanel", ImVec2(m_entityListWidth, windowHeight), true);
321 ImGui::EndChild();
322
323 ImGui::SameLine();
324
326 ImGui::BeginChild("NodeGraphPanel", ImVec2(centerWidth, windowHeight), true);
328 ImGui::EndChild();
329
330 ImGui::SameLine();
331
332 ImGui::BeginChild("InspectorPanel", ImVec2(m_inspectorWidth, windowHeight), true);
334 ImGui::EndChild();
335
336 ImGui::End();
337 }
338
340 {
341 m_entities.clear();
342
343 auto& world = World::Get();
344 const auto& allEntities = world.GetAllEntities();
345
346 for (EntityID entity : allEntities)
347 {
348 if (!world.HasComponent<BehaviorTreeRuntime_data>(entity))
349 continue;
350
352 info.entityId = entity;
353
354 if (world.HasComponent<Identity_data>(entity))
355 {
356 const auto& identity = world.GetComponent<Identity_data>(entity);
357 info.entityName = identity.name;
358 }
359 else
360 {
361 info.entityName = "Entity " + std::to_string(entity);
362 }
363
364 const auto& btRuntime = world.GetComponent<BehaviorTreeRuntime_data>(entity);
366 info.isActive = btRuntime.isActive;
367 info.currentNodeId = btRuntime.AICurrentNodeIndex;
368 info.lastStatus = static_cast<BTStatus>(btRuntime.lastStatus);
369
371 if (tree)
372 {
373 info.treeName = tree->name;
374 }
375 else
376 {
377 std::string path = BehaviorTreeManager::Get().GetTreePathFromId(info.treeId);
378
379 if (!path.empty())
380 {
381 info.treeName = "Not Loaded: " + path;
382 }
383 else
384 {
385 info.treeName = "Unknown (ID=" + std::to_string(info.treeId) + ")";
386 }
387
388 static std::set<EntityID> debuggedEntities;
389 if (debuggedEntities.find(entity) == debuggedEntities.end())
390 {
391 debuggedEntities.insert(entity);
392 std::cout << "[BTDebugger] WARNING: Entity " << entity << " (" << info.entityName
393 << ") has unknown tree ID=" << info.treeId << std::endl;
395 }
396 }
397
398 if (world.HasComponent<AIState_data>(entity))
399 {
400 const auto& aiState = world.GetComponent<AIState_data>(entity);
401 switch (aiState.currentMode)
402 {
403 case AIMode::Idle: info.aiMode = "Idle"; break;
404 case AIMode::Patrol: info.aiMode = "Patrol"; break;
405 case AIMode::Combat: info.aiMode = "Combat"; break;
406 case AIMode::Flee: info.aiMode = "Flee"; break;
407 case AIMode::Investigate: info.aiMode = "Investigate"; break;
408 case AIMode::Dead: info.aiMode = "Dead"; break;
409 default: info.aiMode = "Unknown"; break;
410 }
411 }
412 else
413 {
414 info.aiMode = "N/A";
415 }
416
417 if (world.HasComponent<AIBlackboard_data>(entity))
418 {
419 const auto& blackboard = world.GetComponent<AIBlackboard_data>(entity);
420 info.hasTarget = blackboard.hasTarget;
421 }
422
423 info.lastUpdateTime = 0.0f;
424
425 m_entities.push_back(info);
426 }
427
430 }
431
433 {
434 m_filteredEntities.clear();
435
436 for (const auto& info : m_entities)
437 {
438 if (m_filterText[0] != '\0')
439 {
440 std::string lowerName = info.entityName;
441 std::string lowerFilter = m_filterText;
442 std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower);
443 std::transform(lowerFilter.begin(), lowerFilter.end(), lowerFilter.begin(), ::tolower);
444
445 if (lowerName.find(lowerFilter) == std::string::npos)
446 continue;
447 }
448
449 if (m_filterActiveOnly && !info.isActive)
450 continue;
451
452 if (m_filterHasTarget && !info.hasTarget)
453 continue;
454
455 m_filteredEntities.push_back(info);
456 }
457 }
458
460 {
461 std::sort(m_filteredEntities.begin(), m_filteredEntities.end(),
462 [this](const EntityDebugInfo& a, const EntityDebugInfo& b) {
463 bool result = false;
464 switch (m_sortMode)
465 {
466 case SortMode::Name:
467 result = a.entityName < b.entityName;
468 break;
469 case SortMode::TreeName:
470 result = a.treeName < b.treeName;
471 break;
472 case SortMode::LastUpdate:
473 result = a.lastUpdateTime > b.lastUpdateTime;
474 break;
475 case SortMode::AIMode:
476 result = a.aiMode < b.aiMode;
477 break;
478 }
479 return m_sortAscending ? result : !result;
480 });
481 }
482
483 void BehaviorTreeDebugWindow::RenderEntityListPanel()
484 {
485 ImGui::Text("Entities with Behavior Trees");
486 ImGui::Separator();
487
488 ImGui::InputText("Search", m_filterText, sizeof(m_filterText));
489 if (ImGui::IsItemEdited())
490 {
491 UpdateEntityFiltering();
492 UpdateEntitySorting();
493 }
494
495 if (ImGui::Checkbox("Active Only", &m_filterActiveOnly))
496 {
497 UpdateEntityFiltering();
498 UpdateEntitySorting();
499 }
500 ImGui::SameLine();
501 if (ImGui::Checkbox("Has Target", &m_filterHasTarget))
502 {
503 UpdateEntityFiltering();
504 UpdateEntitySorting();
505 }
506
507 ImGui::Separator();
508
509 ImGui::Text("Sort by:");
510 const char* sortModes[] = { "Name", "Tree Name", "Last Update", "AI Mode" };
511 int currentSort = static_cast<int>(m_sortMode);
512 if (ImGui::Combo("##SortMode", &currentSort, sortModes, IM_ARRAYSIZE(sortModes)))
513 {
514 m_sortMode = static_cast<SortMode>(currentSort);
515 UpdateEntitySorting();
516 }
517 ImGui::SameLine();
518 if (ImGui::Button(m_sortAscending ? "Asc" : "Desc"))
519 {
520 m_sortAscending = !m_sortAscending;
521 UpdateEntitySorting();
522 }
523
524 ImGui::Separator();
525
526 ImGui::Text("Entities: %d / %d", (int)m_filteredEntities.size(), (int)m_entities.size());
527
528 ImGui::BeginChild("EntityList", ImVec2(0, 0), false);
529 for (const auto& info : m_filteredEntities)
530 {
531 RenderEntityEntry(info);
532 }
533 ImGui::EndChild();
534 }
535
536 void BehaviorTreeDebugWindow::RenderEntityEntry(const EntityDebugInfo& info)
537 {
538 ImGui::PushID((unsigned int)info.entityId);
539
540 const char* statusIcon = info.isActive ? "●" : "○";
541 ImVec4 statusColor = info.isActive ? ImVec4(0.0f, 1.0f, 0.0f, 1.0f) : ImVec4(0.5f, 0.5f, 0.5f, 1.0f);
542
543 const char* resultIcon = "▶";
544 ImVec4 resultColor = ImVec4(1.0f, 1.0f, 0.0f, 1.0f);
545 if (info.lastStatus == BTStatus::Success)
546 {
547 resultIcon = "✓";
548 resultColor = ImVec4(0.0f, 1.0f, 0.0f, 1.0f);
549 }
550 else if (info.lastStatus == BTStatus::Failure)
551 {
552 resultIcon = "✗";
553 resultColor = ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
554 }
555
556 bool isSelected = (m_selectedEntity == info.entityId);
557 ImGui::TextColored(statusColor, "%s", statusIcon);
558 ImGui::SameLine();
559 ImGui::TextColored(resultColor, "%s", resultIcon);
560 ImGui::SameLine();
561
562 if (ImGui::Selectable(info.entityName.c_str(), isSelected))
563 {
564 m_selectedEntity = info.entityId;
565 }
566
567 if (ImGui::IsItemHovered())
568 {
569 ImGui::BeginTooltip();
570 ImGui::Text("Entity ID: %u", info.entityId);
571 ImGui::Text("Tree: %s", info.treeName.c_str());
572 ImGui::Text("AI Mode: %s", info.aiMode.c_str());
573 ImGui::Text("Active: %s", info.isActive ? "Yes" : "No");
574 ImGui::Text("Has Target: %s", info.hasTarget ? "Yes" : "No");
575 ImGui::EndTooltip();
576 }
577
578 ImGui::Indent();
579 ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "%s", info.treeName.c_str());
580 ImGui::Unindent();
581
582 ImGui::PopID();
583 }
584
585 void BehaviorTreeDebugWindow::RenderInspectorPanel()
586 {
587 if (m_selectedEntity == 0)
588 {
589 ImGui::Text("No entity selected");
590 return;
591 }
592
593 ImGui::Text("Inspector");
594 ImGui::Separator();
595
596 if (ImGui::CollapsingHeader("Runtime Info", ImGuiTreeNodeFlags_DefaultOpen))
597 {
598 RenderRuntimeInfo();
599 }
600
601 if (ImGui::CollapsingHeader("Blackboard", ImGuiTreeNodeFlags_DefaultOpen))
602 {
603 RenderBlackboardSection();
604 }
605
606 if (ImGui::CollapsingHeader("Execution Log", ImGuiTreeNodeFlags_DefaultOpen))
607 {
608 RenderExecutionLog();
609 }
610 }
611
612 void BehaviorTreeDebugWindow::RenderRuntimeInfo()
613 {
614 auto& world = World::Get();
615
616 if (!world.HasComponent<BehaviorTreeRuntime_data>(m_selectedEntity))
617 return;
618
619 const auto& btRuntime = world.GetComponent<BehaviorTreeRuntime_data>(m_selectedEntity);
620
622
623 ImGui::Text("Tree ID: %u", btRuntime.AITreeAssetId);
624
625 if (tree)
626 {
627 ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "Tree Name: %s", tree->name.c_str());
628 ImGui::Text("Node Count: %zu", tree->nodes.size());
629 }
630 else
631 {
632 ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Tree: NOT FOUND");
633
634 std::string path = BehaviorTreeManager::Get().GetTreePathFromId(btRuntime.AITreeAssetId);
635 if (!path.empty())
636 {
637 ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), "Expected: %s", path.c_str());
638 }
639
640 if (ImGui::Button("Debug: List All Trees"))
641 {
643 }
644 }
645
646 ImGui::Separator();
647
648 ImGui::Text("Current Node ID: %u", btRuntime.AICurrentNodeIndex);
649
650 if (tree)
651 {
652 const BTNode* currentNode = tree->GetNode(btRuntime.AICurrentNodeIndex);
653 if (currentNode)
654 {
655 ImGui::Text("Node Name: %s", currentNode->name.c_str());
656 }
657 }
658
659 const char* statusStr = "Running";
660 ImVec4 statusColor = ImVec4(1.0f, 1.0f, 0.0f, 1.0f);
661 BTStatus status = static_cast<BTStatus>(btRuntime.lastStatus);
662 if (status == BTStatus::Success)
663 {
664 statusStr = "Success";
665 statusColor = ImVec4(0.0f, 1.0f, 0.0f, 1.0f);
666 }
667 else if (status == BTStatus::Failure)
668 {
669 statusStr = "Failure";
670 statusColor = ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
671 }
672 ImGui::TextColored(statusColor, "Last Status: %s", statusStr);
673
674 ImGui::Text("Active: %s", btRuntime.isActive ? "Yes" : "No");
675
676 if (world.HasComponent<AIState_data>(m_selectedEntity))
677 {
678 const auto& aiState = world.GetComponent<AIState_data>(m_selectedEntity);
679 const char* modeStr = "Unknown";
680 switch (aiState.currentMode)
681 {
682 case AIMode::Idle: modeStr = "Idle"; break;
683 case AIMode::Patrol: modeStr = "Patrol"; break;
684 case AIMode::Combat: modeStr = "Combat"; break;
685 case AIMode::Flee: modeStr = "Flee"; break;
686 case AIMode::Investigate: modeStr = "Investigate"; break;
687 case AIMode::Dead: modeStr = "Dead"; break;
688 }
689 ImGui::Text("AI Mode: %s", modeStr);
690 ImGui::Text("Time in Mode: %.2f s", aiState.timeInCurrentMode);
691 }
692 }
693
694 void BehaviorTreeDebugWindow::RenderBlackboardSection()
695 {
696 auto& world = World::Get();
697
698 if (!world.HasComponent<AIBlackboard_data>(m_selectedEntity))
699 {
700 ImGui::Text("No blackboard data");
701 return;
702 }
703
704 const auto& blackboard = world.GetComponent<AIBlackboard_data>(m_selectedEntity);
705
706 if (ImGui::TreeNode("Target"))
707 {
708 ImGui::Text("Has Target: %s", blackboard.hasTarget ? "Yes" : "No");
709 ImGui::Text("Target Entity: %u", blackboard.targetEntity);
710 ImGui::Text("Target Visible: %s", blackboard.targetVisible ? "Yes" : "No");
711 ImGui::Text("Distance: %.2f", blackboard.distanceToTarget);
712 ImGui::Text("Time Since Seen: %.2f s", blackboard.timeSinceTargetSeen);
713 ImGui::Text("Last Known Pos: (%.1f, %.1f)",
714 blackboard.lastKnownTargetPosition.x,
715 blackboard.lastKnownTargetPosition.y);
716 ImGui::TreePop();
717 }
718
719 if (ImGui::TreeNode("Movement"))
720 {
721 ImGui::Text("Has Move Goal: %s", blackboard.hasMoveGoal ? "Yes" : "No");
722 ImGui::Text("Goal Position: (%.1f, %.1f)",
723 blackboard.moveGoal.x,
724 blackboard.moveGoal.y);
725 ImGui::TreePop();
726 }
727
728 if (ImGui::TreeNode("Patrol"))
729 {
730 ImGui::Text("Has Patrol Path: %s", blackboard.hasPatrolPath ? "Yes" : "No");
731 ImGui::Text("Current Point: %d", blackboard.currentPatrolPoint);
732 ImGui::Text("Point Count: %d", blackboard.patrolPointCount);
733 ImGui::TreePop();
734 }
735
736 if (ImGui::TreeNode("Combat"))
737 {
738 ImGui::Text("Can Attack: %s", blackboard.canAttack ? "Yes" : "No");
739 ImGui::Text("Attack Cooldown: %.2f s", blackboard.attackCooldown);
740
741 if (blackboard.lastAttackTime > 0.0f)
742 {
743 ImGui::Text("Last Attack Time: %.2f", blackboard.lastAttackTime);
744 }
745 else
746 {
747 ImGui::Text("Last Attack: Never");
748 }
749
750 ImGui::TreePop();
751 }
752
753 if (ImGui::TreeNode("Stimuli"))
754 {
755 ImGui::Text("Heard Noise: %s", blackboard.heardNoise ? "Yes" : "No");
756 ImGui::Text("Last Damage: %.2f", blackboard.damageAmount);
757 ImGui::TreePop();
758 }
759
760 if (ImGui::TreeNode("Wander"))
761 {
762 ImGui::Text("Has Destination: %s", blackboard.hasWanderDestination ? "Yes" : "No");
763 ImGui::Text("Destination: (%.1f, %.1f)",
764 blackboard.wanderDestination.x,
765 blackboard.wanderDestination.y);
766 ImGui::Text("Wait Timer: %.2f / %.2f s",
767 blackboard.wanderWaitTimer,
768 blackboard.wanderTargetWaitTime);
769 ImGui::TreePop();
770 }
771 }
772
773 void BehaviorTreeDebugWindow::RenderExecutionLog()
774 {
775 if (ImGui::Button("Clear Log"))
776 {
777 m_executionLog.clear();
778 }
779
780 ImGui::Separator();
781
782 ImGui::BeginChild("ExecutionLogScroll", ImVec2(0, 0), false);
783
784 for (auto it = m_executionLog.rbegin(); it != m_executionLog.rend(); ++it)
785 {
786 const auto& entry = *it;
787
788 if (entry.entity != m_selectedEntity)
789 continue;
790
791 ImVec4 color = ImVec4(1.0f, 1.0f, 0.0f, 1.0f);
792 const char* icon = "▶";
793 if (entry.status == BTStatus::Success)
794 {
795 color = ImVec4(0.0f, 1.0f, 0.0f, 1.0f);
796 icon = "✓";
797 }
798 else if (entry.status == BTStatus::Failure)
799 {
800 color = ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
801 icon = "✗";
802 }
803
804 ImGui::TextColored(color, "[%.2fs ago] %s Node %u (%s)",
805 entry.timeAgo, icon, entry.nodeId, entry.nodeName.c_str());
806 }
807
808 ImGui::EndChild();
809 }
810
811 void BehaviorTreeDebugWindow::AddExecutionEntry(EntityID entity, uint32_t nodeId, const std::string& nodeName, BTStatus status)
812 {
814 entry.timeAgo = 0.0f;
815 entry.entity = entity;
816 entry.nodeId = nodeId;
817 entry.nodeName = nodeName;
818 entry.status = status;
819
820 m_executionLog.push_back(entry);
821
822 while (m_executionLog.size() > MAX_LOG_ENTRIES)
823 {
824 m_executionLog.pop_front();
825 }
826 }
827
828}
Runtime debugger for behavior tree visualization and inspection.
BTStatus
Behavior tree node execution status.
@ Success
Node completed successfully.
@ Failure
Node failed.
@ Investigate
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
std::uint64_t EntityID
Definition ECS_Entity.h:21
std::string GetTreePathFromId(uint32_t treeId) const
void DebugPrintLoadedTrees() const
static BehaviorTreeManager & Get()
const BehaviorTreeAsset * GetTreeByAnyId(uint32_t treeId) const
static float fDt
Delta time between frames in seconds.
Definition GameEngine.h:120
void Initialize()
Initialize the debug window.
std::vector< EntityDebugInfo > m_filteredEntities
void ToggleVisibility()
Toggle window visibility (creates/destroys separate window)
std::deque< ExecutionLogEntry > m_executionLog
void Render()
Render the debug window (in separate SDL3 window)
std::vector< EntityDebugInfo > m_entities
void ProcessEvent(SDL_Event *event)
Process SDL events for separate window.
void Init(std::function< void()> saveFn, float debounceSec=1.5f, float periodicIntervalSec=60.0f)
Set the timing parameters and an optional legacy save callback.
void Flush()
Block until any running async save finishes.
static World & Get()
Get singleton instance (short form)
Definition World.h:232
< Provides AssetID and INVALID_ASSET_ID
Represents a single node in a behavior tree.
Identity component for entity identification.
std::string name
Entity name identifier.
Cached debug information for a single entity.
Single entry in the execution log.
float timeAgo
Time since entry (seconds)