Files
EVEngine/src/engine/devtools/Debugger.hpp
T
西风逍遥游 4377e062dd feat: zeroerr parameter validation + Doxygen API documentation
Squashed onto current main. Adds src/engine/common/Assert.h (EV_PARAM_CHECK / EV_ASSERT backed by zeroerr), an EVENGINE_ENABLE_ASSERTS option (non-Debug builds strip ZEROERR_NO_ASSERT unless enabled), parameter checks at key entry points, Doxygen comments across headers, Doxyfile + make docs / CMake docs targets, and Windows setup + code conventions in AGENTS.md.
2026-08-19 09:01:12 +08:00

225 lines
8.7 KiB
C++

#pragma once
#include "common/Export.h"
#include "devtools/CallGraph.hpp"
#include <atomic>
#include <cstdint>
#include <functional>
#include <mutex>
#include <string>
#include <vector>
struct SQVM;
typedef struct SQVM* HSQUIRRELVM;
namespace eve::dev {
enum class PauseReason : uint8_t {
None = 0,
PauseKey,
Breakpoint,
Step,
Exception,
Snapshot,
};
enum class RunMode : uint8_t {
Running = 0,
Paused, // frame-level or script-level pause
StepFrame, // run one game frame then pause
StepInto, // next script line (any call depth) — DAP stepIn
StepOver, // next script line at ≤ start depth — DAP next
StepOut, // next script line at < start depth — DAP stepOut
};
struct EVENGINE_API Breakpoint {
std::string source; // normalized path or basename
int line = 0;
bool enabled = true;
bool verified = false;
std::string condition; // optional GDScript-style condition; empty = always
int id = 0;
};
struct EVENGINE_API WatchEntry {
std::string expression;
std::string value; // last evaluated (display)
bool ok = false;
};
struct EVENGINE_API StackFrameInfo {
int id = 0;
SourceLoc loc;
std::string name;
};
struct EVENGINE_API VariableInfo {
std::string name;
std::string value;
std::string type;
bool expensive = false;
bool expandable = false; // container / instance: children available
int childCount = -1; // -1 = unknown
};
/** @brief Where a variable tree node is rooted (used by containerChildren/resolvePath). */
enum class VarKind : uint8_t {
Locals = 0, // frame locals
Globals = 1, // roottable
};
/**
* @brief Script + frame debugger: pause/step, breakpoints, watches.
*
* Frame pause: game loop skips eve_update (see load.nut + shouldRunUpdate).
* Script pause: Squirrel line hook blocks until resume/step (waitWhilePaused).
*
* Stepping (script, when stopped on a line):
* - stepInto — stop on the next line event (enter calls)
* - stepOver — stop on the next line at the same / outer stack depth
* - stepOut — stop after returning to the caller
*/
class EVENGINE_API Debugger {
public:
static Debugger& instance();
Debugger(const Debugger&) = delete;
Debugger& operator=(const Debugger&) = delete;
void attach(HSQUIRRELVM vm);
void detach();
bool isAttached() const { return vm_ != nullptr; }
HSQUIRRELVM vm() const { return vm_; }
// ---- run control ----
void pause(PauseReason reason = PauseReason::PauseKey);
void resume();
void stepFrame();
/** @brief Enter calls: stop on the next script line at any depth. */
void stepInto();
/** @brief Skip calls: stop on the next line at ≤ current stack depth. */
void stepOver();
/** @brief Finish current function: stop when stack depth drops. */
void stepOut();
/** @brief Alias for stepInto (historical name). */
void stepLine() { stepInto(); }
/**
* @brief Convenience: script stepOver when mid-hook; otherwise one game frame.
* Prefer stepInto/stepOver/stepOut from DAP / UI.
*/
void step();
bool isPaused() const { return mode_.load() == RunMode::Paused; }
RunMode mode() const { return mode_.load(); }
PauseReason lastPauseReason() const { return reason_.load(); }
const SourceLoc& pauseLocation() const { return pauseLoc_; }
/** @brief Current Squirrel call depth (1 = topmost script frame). 0 if none. */
int scriptStackDepth() const;
/** @brief Frame loop: true ⇒ call eve_update this frame. Consumes StepFrame. */
bool shouldRunUpdate();
/** @brief After a frame when StepFrame was active → return to Paused. */
void notifyFrameDone();
/**
* @brief Called from Squirrel line debug hook.
* Returns true if execution should block (breakpoint / step).
*/
bool onScriptLine(const SourceLoc& loc);
/** @brief Block until resume/step/detach (processes external poll callbacks). */
void waitWhilePaused(const std::function<void()>& pump = {});
// ---- breakpoints ----
int setBreakpoint(std::string source, int line, bool enabled = true,
std::string condition = {});
bool clearBreakpoint(std::string source, int line);
void clearBreakpoints(const std::string& source = {});
std::vector<Breakpoint> breakpoints() const;
bool hasBreakpoint(const std::string& source, int line) const;
// ---- watches ----
void addWatch(std::string expression);
bool removeWatch(const std::string& expression);
void clearWatches();
std::vector<WatchEntry> watches() const;
/** @brief Re-evaluate all watches against current VM (paused preferred). */
void refreshWatches();
/**
* @brief Evaluate an expression in the given frame's scope.
* Understands plain names, `a.b` paths, and full Squirrel expressions
* (arithmetic / calls / indexing) compiled against a locals+roottable env.
*/
VariableInfo evaluate(const std::string& expression, int frameLevel = 0) const;
/** @brief Locals of the given call-stack level (0 = current script frame). */
std::vector<VariableInfo> locals(int stackLevel = 0) const;
/** @brief Root-table slots (globals). */
std::vector<VariableInfo> globals() const;
/**
* @brief Children of a container variable. `path` is the key chain that locates
* the container from its root (locals frame / roottable / upvalues).
* Empty `path` lists the root entries themselves.
*/
std::vector<VariableInfo> containerChildren(VarKind kind, int frame,
const std::vector<std::string>& path) const;
std::vector<StackFrameInfo> stackTrace(int maxFrames = 32) const;
// ---- error / breakpoint policy ----
/** @brief Break on script errors (Godot "Break on Error"). Default off. */
void setBreakOnError(bool on) { breakOnError_.store(on); }
bool breakOnError() const { return breakOnError_.load(); }
/** @brief Master switch for all breakpoints ("skip all breakpoints"). */
void setBreakpointsEnabled(bool on) { bpsEnabled_.store(on); }
bool breakpointsEnabled() const { return bpsEnabled_.load(); }
/** @brief Called once when a breakpoint line is first observed by the line hook. */
using BreakpointEventFn = std::function<void(int id, const std::string& source, int line,
bool verified)>;
void setBreakpointEventFn(BreakpointEventFn fn) { bpEventFn_ = std::move(fn); }
using PumpFn = std::function<void()>;
void setPump(PumpFn pump) { pump_ = std::move(pump); }
/** @brief Normalize source paths for breakpoint matching (basename fallback). */
static std::string normalizeSource(std::string source);
/** @brief Basename of a normalized path (empty-safe). */
static std::string sourceBasename(const std::string& source);
/**
* @brief True when two source paths refer to the same script file.
* Matches exact path, basename, or one path as a suffix of the other.
*/
static bool sourcesMatch(const std::string& a, const std::string& b);
private:
Debugger() = default;
bool matchBreakpoint(const std::string& source, int line) const;
bool matchBreakpointAny(const std::string& source, int line) const;
bool conditionHolds(const Breakpoint& bp) const;
bool pushPathValue(HSQUIRRELVM vm, VarKind kind, int frame,
const std::vector<std::string>& path) const;
VariableInfo readLocal(HSQUIRRELVM vm, unsigned level, const std::string& name) const;
VariableInfo readRoot(HSQUIRRELVM vm, const std::string& name) const;
VariableInfo evaluateLegacy(const std::string& expression) const;
bool pushLocalValue(HSQUIRRELVM vm, unsigned level, const std::string& name) const;
void beginScriptStep(RunMode mode);
HSQUIRRELVM vm_ = nullptr;
std::atomic<RunMode> mode_{RunMode::Running};
std::atomic<PauseReason> reason_{PauseReason::None};
std::atomic<bool> breakOnError_{false};
std::atomic<bool> bpsEnabled_{true};
SourceLoc pauseLoc_;
mutable std::mutex mu_;
std::vector<Breakpoint> bps_;
std::vector<std::string> watchExprs_;
std::vector<WatchEntry> watchCache_;
int nextBpId_ = 1;
PumpFn pump_;
bool stepFrameArmed_ = false;
int stepStartDepth_ = 0;
/** @brief While set, step filters ignore this exact source+line (multi-_OP_LINE). */
SourceLoc stepSkipLoc_;
BreakpointEventFn bpEventFn_;
};
} // namespace eve::dev