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"
21#include <unordered_set>
29 , m_windowCreated(
false)
30 , m_separateImGuiContext(
nullptr)
46 ImNodes::CreateContext();
47 ImNodes::GetStyle().GridSpacing = 32.0f;
48 ImNodes::GetStyle().NodeCornerRounding = 8.0f;
49 ImNodes::GetStyle().NodePadding =
ImVec2(8, 8);
63 std::cout <<
"[BTDebugger] Initialized (window will be created on first F10)" << std::endl;
77 ImNodes::DestroyContext();
103 std::cout <<
"[BTDebugger] F10: Debugger window opened (separate window)" << std::endl;
109 std::cout <<
"[BTDebugger] F10: Debugger window closed" << std::endl;
117 std::cout <<
"[BTDebugger] Separate window already exists" << std::endl;
127 "Behavior Tree Runtime Debugger - Independent Window",
134 std::cout <<
"[BTDebugger] ERROR: Failed to create separate window: "
144 ImGui::StyleColorsDark();
153 std::cout <<
"[BTDebugger] ✅ Separate window created successfully!" << std::endl;
154 std::cout <<
"[BTDebugger] Window can be moved to second monitor" << std::endl;
196 std::cout <<
"[BTDebugger] Separate window destroyed" << std::endl;
261 ImGui::SetNextWindowPos(
ImVec2(0, 0));
262 ImGui::SetNextWindowSize(ImGui::GetIO().
DisplaySize);
272 if (!ImGui::Begin(
"Behavior Tree Runtime Debugger##Main",
nullptr,
windowFlags))
278 if (ImGui::BeginMenuBar())
280 if (ImGui::BeginMenu(
"View"))
287 ImGui::Text(
"Window Mode: Separate (Independent)");
288 ImGui::Text(
"Press F10 to close window");
293 if (ImGui::BeginMenu(
"Actions"))
295 if (ImGui::MenuItem(
"Refresh Now (F5)"))
299 if (ImGui::MenuItem(
"Clear Execution Log"))
316 float windowWidth = ImGui::GetContentRegionAvail().x;
361 info.entityName =
"Entity " + std::to_string(entity);
381 info.treeName =
"Not Loaded: " + path;
385 info.treeName =
"Unknown (ID=" + std::to_string(
info.treeId) +
")";
392 std::cout <<
"[BTDebugger] WARNING: Entity " << entity <<
" (" <<
info.entityName
393 <<
") has unknown tree ID=" <<
info.treeId << std::endl;
409 default:
info.aiMode =
"Unknown";
break;
423 info.lastUpdateTime = 0.0f;
467 result = a.entityName < b.entityName;
469 case SortMode::TreeName:
470 result = a.treeName < b.treeName;
472 case SortMode::LastUpdate:
473 result = a.lastUpdateTime > b.lastUpdateTime;
475 case SortMode::AIMode:
476 result = a.aiMode < b.aiMode;
483 void BehaviorTreeDebugWindow::RenderEntityListPanel()
485 ImGui::Text(
"Entities with Behavior Trees");
488 ImGui::InputText(
"Search", m_filterText,
sizeof(m_filterText));
489 if (ImGui::IsItemEdited())
491 UpdateEntityFiltering();
492 UpdateEntitySorting();
495 if (ImGui::Checkbox(
"Active Only", &m_filterActiveOnly))
497 UpdateEntityFiltering();
498 UpdateEntitySorting();
501 if (ImGui::Checkbox(
"Has Target", &m_filterHasTarget))
503 UpdateEntityFiltering();
504 UpdateEntitySorting();
509 ImGui::Text(
"Sort by:");
510 const char*
sortModes[] = {
"Name",
"Tree Name",
"Last Update",
"AI Mode" };
515 UpdateEntitySorting();
518 if (ImGui::Button(m_sortAscending ?
"Asc" :
"Desc"))
520 m_sortAscending = !m_sortAscending;
521 UpdateEntitySorting();
526 ImGui::Text(
"Entities: %d / %d", (
int)m_filteredEntities.size(), (
int)m_entities.size());
528 ImGui::BeginChild(
"EntityList",
ImVec2(0, 0),
false);
529 for (
const auto&
info : m_filteredEntities)
531 RenderEntityEntry(
info);
538 ImGui::PushID((
unsigned int)
info.entityId);
556 bool isSelected = (m_selectedEntity ==
info.entityId);
562 if (ImGui::Selectable(
info.entityName.c_str(), isSelected))
564 m_selectedEntity =
info.entityId;
567 if (ImGui::IsItemHovered())
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");
579 ImGui::TextColored(
ImVec4(0.7f, 0.7f, 0.7f, 1.0f),
"%s",
info.treeName.c_str());
585 void BehaviorTreeDebugWindow::RenderInspectorPanel()
587 if (m_selectedEntity == 0)
589 ImGui::Text(
"No entity selected");
593 ImGui::Text(
"Inspector");
603 RenderBlackboardSection();
608 RenderExecutionLog();
612 void BehaviorTreeDebugWindow::RenderRuntimeInfo()
623 ImGui::Text(
"Tree ID: %u",
btRuntime.AITreeAssetId);
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());
632 ImGui::TextColored(
ImVec4(1.0f, 0.0f, 0.0f, 1.0f),
"Tree: NOT FOUND");
637 ImGui::TextColored(
ImVec4(1.0f, 0.5f, 0.0f, 1.0f),
"Expected: %s", path.c_str());
640 if (ImGui::Button(
"Debug: List All Trees"))
648 ImGui::Text(
"Current Node ID: %u",
btRuntime.AICurrentNodeIndex);
655 ImGui::Text(
"Node Name: %s",
currentNode->name.c_str());
674 ImGui::Text(
"Active: %s",
btRuntime.isActive ?
"Yes" :
"No");
679 const char*
modeStr =
"Unknown";
689 ImGui::Text(
"AI Mode: %s",
modeStr);
690 ImGui::Text(
"Time in Mode: %.2f s",
aiState.timeInCurrentMode);
694 void BehaviorTreeDebugWindow::RenderBlackboardSection()
700 ImGui::Text(
"No blackboard data");
706 if (ImGui::TreeNode(
"Target"))
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)",
719 if (ImGui::TreeNode(
"Movement"))
721 ImGui::Text(
"Has Move Goal: %s",
blackboard.hasMoveGoal ?
"Yes" :
"No");
722 ImGui::Text(
"Goal Position: (%.1f, %.1f)",
728 if (ImGui::TreeNode(
"Patrol"))
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);
736 if (ImGui::TreeNode(
"Combat"))
738 ImGui::Text(
"Can Attack: %s",
blackboard.canAttack ?
"Yes" :
"No");
739 ImGui::Text(
"Attack Cooldown: %.2f s",
blackboard.attackCooldown);
743 ImGui::Text(
"Last Attack Time: %.2f",
blackboard.lastAttackTime);
747 ImGui::Text(
"Last Attack: Never");
753 if (ImGui::TreeNode(
"Stimuli"))
755 ImGui::Text(
"Heard Noise: %s",
blackboard.heardNoise ?
"Yes" :
"No");
756 ImGui::Text(
"Last Damage: %.2f",
blackboard.damageAmount);
760 if (ImGui::TreeNode(
"Wander"))
762 ImGui::Text(
"Has Destination: %s",
blackboard.hasWanderDestination ?
"Yes" :
"No");
763 ImGui::Text(
"Destination: (%.1f, %.1f)",
766 ImGui::Text(
"Wait Timer: %.2f / %.2f s",
773 void BehaviorTreeDebugWindow::RenderExecutionLog()
775 if (ImGui::Button(
"Clear Log"))
777 m_executionLog.clear();
782 ImGui::BeginChild(
"ExecutionLogScroll",
ImVec2(0, 0),
false);
784 for (
auto it = m_executionLog.rbegin();
it != m_executionLog.rend(); ++
it)
788 if (
entry.entity != m_selectedEntity)
792 const char* icon =
"▶";
795 color =
ImVec4(0.0f, 1.0f, 0.0f, 1.0f);
800 color =
ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
804 ImGui::TextColored(color,
"[%.2fs ago] %s Node %u (%s)",
815 entry.entity = entity;
816 entry.nodeId = nodeId;
817 entry.nodeName = nodeName;
818 entry.status = status;
820 m_executionLog.push_back(
entry);
822 while (m_executionLog.size() > MAX_LOG_ENTRIES)
824 m_executionLog.pop_front();
Runtime debugger for behavior tree visualization and inspection.
BTStatus
Behavior tree node execution status.
@ Success
Node completed successfully.
ComponentTypeID GetComponentTypeID_Static()
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.
~BehaviorTreeDebugWindow()
float m_autoRefreshInterval
EditorAutosaveManager m_autosave
void Initialize()
Initialize the debug window.
void RenderNodeGraphDebugPanel()
SDL_Renderer * m_separateRenderer
ImGuiContext * m_separateImGuiContext
void RenderInspectorPanel()
void RenderEntityListPanel()
std::vector< EntityDebugInfo > m_filteredEntities
void DestroySeparateWindow()
void ToggleVisibility()
Toggle window visibility (creates/destroys separate window)
std::deque< ExecutionLogEntry > m_executionLog
void UpdateEntitySorting()
BehaviorTreeDebugWindow()
void UpdateEntityFiltering()
SDL_Window * m_separateWindow
void ShutdownNodeGraphDebugMode()
void CreateSeparateWindow()
void Shutdown()
Shutdown and cleanup.
void Render()
Render the debug window (in separate SDL3 window)
bool m_imnodesInitialized
std::vector< EntityDebugInfo > m_entities
void RenderInSeparateWindow()
void InitNodeGraphDebugMode()
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)
< 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)