Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
DebugController.cpp
Go to the documentation of this file.
1/**
2 * @file DebugController.cpp
3 * @brief Implementation of the ATS VS debug controller (Phase 5).
4 * @author Olympe Engine
5 * @date 2026-03-09
6 *
7 * @details C++14 compliant — no std::optional, structured bindings, std::filesystem.
8 */
9
10#include "DebugController.h"
11
12#include <chrono>
13#include <thread>
14#include <iostream>
15
16namespace Olympe {
17
18// ============================================================================
19// Singleton
20// ============================================================================
21
27
31
35
36// ============================================================================
37// Internal helpers
38// ============================================================================
39
40int DebugController::MakeBreakpointKey(int graphID, int nodeID) const
41{
42 // Combine graphID and nodeID into a single integer key.
43 // Assumes nodeID < 100000 and graphID < 100000.
44 return graphID * 100000 + nodeID;
45}
46
47bool DebugController::HasBreakpointLocked(int graphID, int nodeID) const
48{
49 int key = MakeBreakpointKey(graphID, nodeID);
50 auto it = m_breakpoints.find(key);
51 if (it == m_breakpoints.end())
52 return false;
53 return it->second.enabled;
54}
55
56// ============================================================================
57// Breakpoints
58// ============================================================================
59
60void DebugController::SetBreakpoint(int graphID, int nodeID,
61 const std::string& graphName,
62 const std::string& nodeName)
63{
64 std::lock_guard<std::mutex> lock(m_mutex);
65 int key = MakeBreakpointKey(graphID, nodeID);
67 bp.graphID = graphID;
68 bp.nodeID = nodeID;
69 bp.graphName = graphName;
70 bp.nodeName = nodeName;
71 bp.enabled = true;
73}
74
75void DebugController::ClearBreakpoint(int graphID, int nodeID)
76{
77 std::lock_guard<std::mutex> lock(m_mutex);
78 int key = MakeBreakpointKey(graphID, nodeID);
79 m_breakpoints.erase(key);
80}
81
82void DebugController::ToggleBreakpoint(int graphID, int nodeID,
83 const std::string& graphName,
84 const std::string& nodeName)
85{
86 std::lock_guard<std::mutex> lock(m_mutex);
87 int key = MakeBreakpointKey(graphID, nodeID);
88 auto it = m_breakpoints.find(key);
89 if (it != m_breakpoints.end())
90 {
91 m_breakpoints.erase(it);
92 }
93 else
94 {
96 bp.graphID = graphID;
97 bp.nodeID = nodeID;
98 bp.graphName = graphName;
99 bp.nodeName = nodeName;
100 bp.enabled = true;
102 }
103}
104
105bool DebugController::HasBreakpoint(int graphID, int nodeID) const
106{
107 std::lock_guard<std::mutex> lock(m_mutex);
108 return HasBreakpointLocked(graphID, nodeID);
109}
110
111void DebugController::SetBreakpointEnabled(int graphID, int nodeID, bool enabled)
112{
113 std::lock_guard<std::mutex> lock(m_mutex);
114 int key = MakeBreakpointKey(graphID, nodeID);
115 auto it = m_breakpoints.find(key);
116 if (it != m_breakpoints.end())
117 {
118 it->second.enabled = enabled;
119 }
120}
121
122std::vector<BreakpointInfo> DebugController::GetBreakpoints(int graphID) const
123{
124 std::lock_guard<std::mutex> lock(m_mutex);
125 std::vector<BreakpointInfo> result;
126 for (auto& kv : m_breakpoints)
127 {
128 if (kv.second.graphID == graphID)
129 result.push_back(kv.second);
130 }
131 return result;
132}
133
134std::vector<BreakpointInfo> DebugController::GetAllBreakpoints() const
135{
136 std::lock_guard<std::mutex> lock(m_mutex);
137 std::vector<BreakpointInfo> result;
138 for (auto& kv : m_breakpoints)
139 {
140 result.push_back(kv.second);
141 }
142 return result;
143}
144
146{
147 std::lock_guard<std::mutex> lock(m_mutex);
148 m_breakpoints.clear();
149}
150
151// ============================================================================
152// Debug flow
153// ============================================================================
154
156{
157 std::lock_guard<std::mutex> lock(m_mutex);
159 m_currentGraphID = graphID;
160 m_currentNodeID = -1;
161 m_callStack.clear();
162}
163
165{
166 std::lock_guard<std::mutex> lock(m_mutex);
168 m_currentGraphID = -1;
169 m_currentNodeID = -1;
170 m_callStack.clear();
171}
172
174{
175 std::lock_guard<std::mutex> lock(m_mutex);
177 {
179 }
180}
181
183{
184 std::lock_guard<std::mutex> lock(m_mutex);
186 {
188 }
189}
190
192{
193 std::lock_guard<std::mutex> lock(m_mutex);
195 {
197 }
198}
199
201{
202 std::lock_guard<std::mutex> lock(m_mutex);
204 {
206 }
207}
208
210{
211 std::lock_guard<std::mutex> lock(m_mutex);
213 {
215 }
216}
217
218// ============================================================================
219// Runtime state accessors
220// ============================================================================
221
223{
224 std::lock_guard<std::mutex> lock(m_mutex);
225 return m_state;
226}
227
229{
230 std::lock_guard<std::mutex> lock(m_mutex);
232}
233
235{
236 std::lock_guard<std::mutex> lock(m_mutex);
237 return m_currentGraphID;
238}
239
241{
242 std::lock_guard<std::mutex> lock(m_mutex);
243 return m_currentNodeID;
244}
245
247{
248 std::lock_guard<std::mutex> lock(m_mutex);
249 return m_bbSnapshot;
250}
251
252std::vector<SubGraphStackFrame> DebugController::GetCallStack() const
253{
254 std::lock_guard<std::mutex> lock(m_mutex);
255 return m_callStack;
256}
257
258// ============================================================================
259// Hooks
260// ============================================================================
261
262void DebugController::OnNodeExecuting(int graphID, int nodeID,
263 const LocalBlackboard* bb)
264{
265 // Update current node and blackboard snapshot under lock, then check
266 // whether to pause.
268 bool shouldPause = false;
269
270 {
271 std::lock_guard<std::mutex> lock(m_mutex);
272
274 return;
275
276 m_currentGraphID = graphID;
277 m_currentNodeID = nodeID;
278
279 if (bb != nullptr)
280 m_bbSnapshot = *bb;
281
283
284 // Breakpoint hit in Running state
286 HasBreakpointLocked(graphID, nodeID))
287 {
289 shouldPause = true;
290 }
291 // StepNext: pause immediately after this call
293 {
295 shouldPause = true;
296 }
297 // StepInto: pause (entering a SubGraph will be handled separately)
299 {
301 shouldPause = true;
302 }
303 }
304
305 // Busy-wait loop — only runs when not in headless/test mode
307 {
308 while (true)
309 {
311 {
312 std::lock_guard<std::mutex> lock(m_mutex);
313 cur = m_state;
314 }
315 if (cur != DebugState::Paused)
316 break;
317 std::this_thread::sleep_for(std::chrono::milliseconds(10));
318 }
319 }
320}
321
322void DebugController::PushCallFrame(int graphID, int nodeID,
323 const std::string& graphName,
324 const std::string& nodeName)
325{
326 std::lock_guard<std::mutex> lock(m_mutex);
327
328 // Mark previous top frame as non-current
329 if (!m_callStack.empty())
330 m_callStack.back().isCurrent = false;
331
332 SubGraphStackFrame frame;
333 frame.graphID = graphID;
334 frame.nodeID = nodeID;
335 frame.graphName = graphName;
336 frame.nodeName = nodeName;
337 frame.isCurrent = true;
338 m_callStack.push_back(frame);
339}
340
342{
343 std::lock_guard<std::mutex> lock(m_mutex);
344
345 if (!m_callStack.empty())
346 m_callStack.pop_back();
347
348 if (!m_callStack.empty())
349 m_callStack.back().isCurrent = true;
350
351 // If we were stepping out and we've now popped back, pause.
353 {
355 }
356}
357
358// ============================================================================
359// Headless mode
360// ============================================================================
361
363{
364 std::lock_guard<std::mutex> lock(m_mutex);
366}
367
368} // namespace Olympe
Runtime debug controller for ATS Visual Scripting (Phase 5).
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
Singleton that manages runtime debugging of VS graphs.
void StartDebugging(int graphID)
Starts a debug session for the given graph.
int GetCurrentNodeID() const
Returns the node being executed (-1 if none).
LocalBlackboard GetCurrentBlackboard() const
Returns a copy of the blackboard snapshot from the last OnNodeExecuting() call.
void Continue()
Resumes execution from Paused state.
void ClearBreakpoint(int graphID, int nodeID)
Removes the breakpoint at (graphID, nodeID) if it exists.
std::vector< BreakpointInfo > GetBreakpoints(int graphID) const
Returns all registered breakpoints for graphID.
void StepNext()
Executes the next node then pauses.
std::vector< SubGraphStackFrame > m_callStack
Call stack (index 0 = bottom / entry, back = current)
void PopCallFrame()
Pop the top SubGraph frame from the call stack.
int MakeBreakpointKey(int graphID, int nodeID) const
std::vector< SubGraphStackFrame > GetCallStack() const
Returns the current SubGraph call stack (most recent at front).
void StepOut()
Runs until the current SubGraph returns.
void ToggleBreakpoint(int graphID, int nodeID, const std::string &graphName="", const std::string &nodeName="")
Toggles the breakpoint at (graphID, nodeID).
void SetBreakpointEnabled(int graphID, int nodeID, bool enabled)
Enables or disables an existing breakpoint.
void SetHeadlessMode(bool headless)
When true, OnNodeExecuting() never busy-waits on Paused.
bool HasBreakpointLocked(int graphID, int nodeID) const
void ClearAllBreakpoints()
Removes all breakpoints.
static DebugController & Get()
Returns the singleton instance (Meyers pattern).
std::vector< BreakpointInfo > GetAllBreakpoints() const
Returns all registered breakpoints across all graphs.
void StopDebugging()
Stops the current debug session.
DebugState GetState() const
Returns the current debug state.
int GetCurrentGraphID() const
Returns the currently debugged graphID (-1 if none).
void Pause()
Pauses execution.
void PushCallFrame(int graphID, int nodeID, const std::string &graphName="", const std::string &nodeName="")
Push a SubGraph frame onto the call stack.
std::unordered_map< int, BreakpointInfo > m_breakpoints
Breakpoints keyed by (graphID * 100000 + nodeID)
void StepInto()
Steps into a SubGraph if the next node is a SubGraph node.
bool IsDebugging() const
Returns true when a debug session is active.
bool HasBreakpoint(int graphID, int nodeID) const
Returns true if an enabled breakpoint exists at (graphID, nodeID).
void SetBreakpoint(int graphID, int nodeID, const std::string &graphName="", const std::string &nodeName="")
Registers a breakpoint at (graphID, nodeID).
void OnNodeExecuting(int graphID, int nodeID, const LocalBlackboard *bb)
Called by VSGraphExecutor before executing a node.
LocalBlackboard m_bbSnapshot
Simple map-based blackboard for task graph runtime state.
< Provides AssetID and INVALID_ASSET_ID
DebugState
States of the debug controller state machine.
@ NotDebugging
No active debug session.
@ StepNext
Execute next node then pause.
@ StepOut
Run until the current SubGraph returns.
@ Running
Normal execution.
@ StepInto
Step into a SubGraph on next SubGraph node.
@ Paused
Stopped at a breakpoint or manual pause.
Describes a single breakpoint.
A single frame in the debugger's SubGraph call stack.
bool isCurrent
true for the top-most (active) frame