7#include <simplesquirrel/simplesquirrel.hpp>
21std::string truthyString(
bool v) {
return v ?
"true" :
"false"; }
24 const SQObjectType
t = sq_gettype(
vm,
idx);
30 sq_getinteger(
vm,
idx, &
v);
31 return std::to_string(
static_cast<long long>(
v));
36 return std::to_string(
static_cast<double>(
v));
41 return v ?
"true" :
"false";
44 const SQChar*
s =
nullptr;
46 return s ? std::string(
"\"") +
s +
"\"" :
"\"\"";
56 case OT_NATIVECLOSURE:
70 switch (sq_gettype(
vm,
idx)) {
89 case OT_NATIVECLOSURE:
107 const SQObjectType
t = sq_gettype(
vm,
idx);
115 sq_getinteger(
vm,
idx, &
v);
116 info.value = std::to_string(
static_cast<long long>(
v));
122 info.value = std::to_string(
static_cast<double>(
v));
128 info.value =
v ?
"true" :
"false";
132 const SQChar*
s =
nullptr;
133 sq_getstring(
vm,
idx, &
s);
134 info.value = std::string(
"\"") + (
s ?
s :
"") +
"\"";
139 const SQInteger
n = sq_getsize(
vm,
idx);
140 info.value = std::string(
t == OT_TABLE ?
"<table (" :
"<array (") +
141 std::to_string(static_cast<long long>(
n)) +
")>";
142 info.expandable =
true;
143 info.childCount =
static_cast<int>(
n);
147 info.value =
"<class>";
148 info.expandable =
true;
151 info.value =
"<instance>";
152 info.expandable =
true;
155 info.value =
"<userdata>";
158 info.value =
"<closure>";
159 info.expandable =
true;
161 case OT_NATIVECLOSURE:
162 info.value =
"<native>";
165 info.value =
"<thread>";
168 info.value =
"<generator>";
171 info.value =
"<other>";
179 std::vector<VariableInfo> out;
180 const SQObjectType
t = sq_gettype(
vm,
idx);
181 const SQInteger
top = sq_gettop(
vm);
182 const int absIdx = idx < 0 ? static_cast<int>(
top) +
static_cast<int>(
idx) + 1 : static_cast<int>(
idx);
185 const SQInteger
size = sq_getsize(
vm,
idx);
186 for (SQInteger i = 0; i <
size; ++i) {
187 const SQInteger before = sq_gettop(
vm);
188 sq_pushinteger(
vm, i);
189 if (SQ_FAILED(sq_get(
vm, absIdx))) {
190 sq_settop(
vm, before);
193 VariableInfo
info = describeAt(
vm, -1);
194 info.name = std::to_string(
static_cast<long long>(i));
195 sq_settop(
vm, before);
196 out.push_back(std::move(
info));
201 if (
t == OT_TABLE ||
t == OT_CLASS) {
202 sq_pushinteger(
vm, 0);
203 while (SQ_SUCCEEDED(sq_next(
vm, absIdx))) {
204 VariableInfo
info = describeAt(
vm, -1);
205 switch (sq_gettype(
vm, -2)) {
207 const SQChar*
s =
nullptr;
208 sq_getstring(
vm, -2, &
s);
214 sq_getinteger(
vm, -2, &k);
215 info.name = std::to_string(
static_cast<long long>(k));
220 sq_getfloat(
vm, -2, &k);
221 info.name = std::to_string(
static_cast<double>(k));
226 sq_getbool(
vm, -2, &k);
227 info.name = truthyString(k != SQFalse);
235 out.push_back(std::move(
info));
241 if (
t == OT_INSTANCE) {
242 sq_getclass(
vm, absIdx);
243 const int clsAbs =
static_cast<int>(sq_gettop(
vm));
244 sq_pushinteger(
vm, 0);
245 while (SQ_SUCCEEDED(sq_next(
vm, clsAbs))) {
246 const SQChar*
s =
nullptr;
247 if (sq_gettype(
vm, -2) == OT_STRING) sq_getstring(
vm, -2, &
s);
248 const std::string
name =
s ?
s :
"";
249 const SQInteger before = sq_gettop(
vm);
251 sq_pushstring(
vm,
name.c_str(), -1);
253 if (SQ_SUCCEEDED(sq_get(
vm, -2))) {
254 info = describeAt(
vm, -1);
257 info.value =
"not found";
260 sq_settop(
vm, before);
262 out.push_back(std::move(
info));
268 if (
t == OT_CLOSURE) {
269 SQInteger nparams = 0, nfree = 0;
270 if (SQ_SUCCEEDED(sq_getclosureinfo(
vm,
idx, &nparams, &nfree))) {
271 for (SQUnsignedInteger i = 0; i < static_cast<SQUnsignedInteger>(nfree); ++i) {
272 const SQChar*
n = sq_getfreevariable(
vm, absIdx, i);
273 VariableInfo
info = describeAt(
vm, -1);
274 info.name =
n ?
n : (
"upvalue_" + std::to_string(
static_cast<long long>(i)));
276 out.push_back(std::move(
info));
284bool valueTruthy(
const VariableInfo&
info) {
285 if (
info.type ==
"error")
return false;
286 if (
info.type ==
"null")
return false;
287 if (
info.type ==
"bool")
return info.value ==
"true";
288 if (
info.type ==
"integer") {
290 return std::stoll(
info.value) != 0;
295 if (
info.type ==
"float") {
297 return std::stod(
info.value) != 0.0;
305bool isGenericFuncName(
const char*
name) {
306 if (!
name || !
name[0] || std::strcmp(
name,
"?") == 0)
return true;
307 return std::strcmp(
name,
"unknown") == 0 || std::strcmp(
name,
"NATIVE") == 0 ||
308 std::strcmp(
name,
"anonymous") == 0;
311bool isGenericFuncName(
const std::string&
name) {
return isGenericFuncName(
name.c_str()); }
313std::string nthLine(
const std::string&
text,
int line) {
314 if (
line <= 0)
return {};
318 const size_t nl =
text.find(
'\n',
start);
319 if (nl == std::string::npos)
return {};
324 if (
end == std::string::npos)
end =
text.size();
326 if (!out.empty() && out.back() ==
'\r') out.pop_back();
330std::string inferNameFromDefinitionLine(std::string
line) {
331 while (!
line.empty() && std::isspace(
static_cast<unsigned char>(
line.front())))
line.erase(
line.begin());
332 if (
line.rfind(
"//", 0) == 0)
return {};
333 const auto isIdent = [](
unsigned char c) {
return std::isalnum(
c) ||
c ==
'_' ||
c ==
'.'; };
334 if (
line.rfind(
"function ", 0) == 0) {
336 while (i <
line.size() && std::isspace(
static_cast<unsigned char>(
line[i]))) ++i;
337 const size_t start = i;
338 while (i <
line.size() && isIdent(
static_cast<unsigned char>(
line[i]))) ++i;
343 if (i <
line.size() && (std::isalpha(
static_cast<unsigned char>(
line[i])) ||
line[i] ==
'_')) {
344 const size_t start = i;
345 while (i <
line.size() && isIdent(
static_cast<unsigned char>(
line[i]))) ++i;
347 while (i <
line.size() && std::isspace(
static_cast<unsigned char>(
line[i]))) ++i;
349 if (i + 1 <
line.size() &&
line[i] ==
'<' &&
line[i + 1] ==
'-') {
352 }
else if (i <
line.size() &&
line[i] ==
'=') {
356 if (!assign)
return {};
357 while (i <
line.size() && std::isspace(
static_cast<unsigned char>(
line[i]))) ++i;
358 if (
line.compare(i, 6,
"async ") == 0) {
360 while (i <
line.size() && std::isspace(
static_cast<unsigned char>(
line[i]))) ++i;
362 if (
line.compare(i, 8,
"function") == 0)
return ident;
367std::string readDefinitionLine(
const std::string&
source,
int line) {
371 const auto slash =
source.find_last_of(
"/\\");
372 if (slash != std::string::npos) in.open(
source.substr(slash + 1));
376 for (
int n = 1;
n <=
line && std::getline(in,
text); ++
n) {
378 if (!
text.empty() &&
text.back() ==
'\r')
text.pop_back();
395 if (
source.rfind(
"file://", 0) == 0) {
402 if (
c ==
'\\')
c =
'/';
411 const auto slash = norm.find_last_of(
'/');
412 return (slash == std::string::npos) ? norm : norm.substr(slash + 1);
418 if (na.empty() || nb.empty())
return false;
419 if (na == nb)
return true;
421 const std::string& longer = na.size() >= nb.size() ? na : nb;
422 const std::string& shorter = na.size() >= nb.size() ? nb : na;
423 if (longer.size() > shorter.size() &&
424 longer.compare(longer.size() - shorter.size(), shorter.size(), shorter) == 0) {
425 const auto idx = longer.size() - shorter.size();
426 if (
idx == 0 || longer[
idx - 1] ==
'/')
return true;
432 const bool aHasDir = na.find(
'/') != std::string::npos;
433 const bool bHasDir = nb.find(
'/') != std::string::npos;
434 if (aHasDir && bHasDir)
return false;
437 return !ba.empty() && ba == bb;
441 std::lock_guard<std::mutex> lock(mu_);
446 stepFrameArmed_ =
false;
448 virtualSources_.clear();
452 std::lock_guard<std::mutex> lock(mu_);
456 stepFrameArmed_ =
false;
457 callGraph_ =
nullptr;
459 virtualSources_.clear();
463 std::lock_guard<std::mutex> lock(mu_);
468 std::lock_guard<std::mutex> lock(mu_);
473 return callGraph_ ? *callGraph_ : ownGraph_;
476const CallGraph& Debugger::activeGraph()
const {
477 return callGraph_ ? *callGraph_ : ownGraph_;
480std::string Debugger::lineAt(
const std::string&
source,
int line)
const {
481 if (
line <= 0)
return {};
483 std::lock_guard<std::mutex> lock(mu_);
484 for (
const auto& [
name,
text] : virtualSources_) {
491std::string Debugger::resolveCallName(
const SourceLoc& loc)
const {
492 if (!isGenericFuncName(loc.function))
return loc.function;
495 const std::string
name = inferNameFromDefinitionLine(lineAt(loc.source, loc.line));
497 return loc.function.empty() ?
"unknown" : loc.function;
501 const std::string
name = resolveCallName(loc);
511std::string Debugger::nameFromPrototype(
int level,
const char* rawName,
const SourceLoc& loc)
const {
512 if (!isGenericFuncName(rawName))
return rawName;
515 std::memset(&fi, 0,
sizeof(fi));
516 if (SQ_SUCCEEDED(sq_getfunctioninfo(vm_,
level, &fi))) {
517 if (!isGenericFuncName(fi.name))
return fi.name;
518 const char* src = (fi.source && fi.source[0]) ? fi.source : loc.
source.c_str();
519 const int defLine = fi.
line > 0 ?
static_cast<int>(fi.line) : loc.
line;
520 const std::string inferred = inferNameFromDefinitionLine(lineAt(src, defLine));
521 if (!inferred.empty())
return inferred;
524 const std::string fromPc = inferNameFromDefinitionLine(lineAt(loc.
source, loc.
line));
525 if (!fromPc.empty())
return fromPc;
526 for (
int ln = loc.
line - 1; ln > 0 && ln >= loc.
line - 32; --ln) {
527 const std::string inferred = inferNameFromDefinitionLine(lineAt(loc.
source, ln));
528 if (!inferred.empty())
return inferred;
533void Debugger::applyCallGraphNames(std::vector<StackFrameInfo>& frames)
const {
534 std::vector<std::string> cgInner;
536 for (
auto it = stack.rbegin(); it != stack.rend(); ++it) {
537 if (it->loc.source ==
"NATIVE")
continue;
538 cgInner.push_back(it->loc.function);
542 const int delta =
static_cast<int>(frames.size()) -
static_cast<int>(cgInner.size());
543 for (
size_t i = 0; i < frames.size(); ++i) {
544 if (!isGenericFuncName(frames[i].
name))
continue;
545 const int cgi =
static_cast<int>(i) - delta;
546 if (cgi < 0 || cgi >=
static_cast<int>(cgInner.size()))
continue;
547 if (isGenericFuncName(cgInner[
static_cast<size_t>(cgi)]))
continue;
548 frames[i].name = cgInner[
static_cast<size_t>(cgi)];
549 frames[i].loc.function = frames[i].name;
565 reason_.store(reason);
567 remainingHostFrames_ = 0;
568 pauseLoc_ = std::move(loc);
574 stepFrameArmed_ =
false;
575 remainingHostFrames_ = 0;
584 stepFrameArmed_ =
true;
598 if (SQ_FAILED(sq_stackinfos(
vm,
level, &si)))
break;
604void Debugger::beginScriptStep(
RunMode stepMode) {
609 stepSkipLoc_ = pauseLoc_;
612 if (stepStartDepth_ <= 0) {
617 mode_.store(stepMode);
636 if (!pauseLoc_.
empty() &&
657 stepFrameArmed_ =
false;
658 if (remainingHostFrames_ > 1) {
659 --remainingHostFrames_;
663 remainingHostFrames_ = 0;
671bool Debugger::matchBreakpoint(
const std::string&
source,
int line)
const {
672 if (!bpsEnabled_.load())
return false;
673 for (
const auto& bp : bps_) {
674 if (!bp.enabled || bp.line !=
line)
continue;
680bool Debugger::matchBreakpointAny(
const std::string&
source,
int line)
const {
681 for (
const auto& bp : bps_) {
682 if (bp.line !=
line)
continue;
688bool Debugger::conditionHolds(
const Breakpoint& bp)
const {
689 if (bp.condition.empty())
return true;
691 const VariableInfo
r =
evaluate(bp.condition, 0);
692 if (
r.type ==
"error")
return true;
693 return valueTruthy(
r);
704 std::vector<int> verifiedNow;
706 std::lock_guard<std::mutex> lock(mu_);
707 if (!bps_.empty() && matchBreakpointAny(loc.
source, loc.
line)) {
708 for (
auto& bp : bps_) {
709 if (bp.verified || bp.line != loc.
line ||
713 verifiedNow.push_back(bp.id);
717 for (
const int id : verifiedNow) {
718 if (bpEventFn_) bpEventFn_(
id, loc.
source, loc.
line,
true);
721 const bool stepping =
726 if (stepping && !stepSkipLoc_.
empty() && stepSkipLoc_.
line == loc.
line &&
735 std::lock_guard<std::mutex> lock(mu_);
736 if (bpsEnabled_.load()) {
737 for (
const auto& bp : bps_) {
738 if (!bp.enabled || bp.line != loc.
line ||
748 if (!conditionHolds(matched))
return false;
764 stop =
depth <= stepStartDepth_;
766 stop =
depth < stepStartDepth_;
786 static thread_local int pausePumpDepth = 0;
787 if (pausePumpDepth > 0)
return;
790 ~Guard() { --pausePumpDepth; }
795 else if (pump_) pump_();
796 std::this_thread::sleep_for(std::chrono::milliseconds(5));
809 std::lock_guard<std::mutex> lock(mu_);
810 for (
auto& bp : bps_) {
828 std::lock_guard<std::mutex> lock(mu_);
829 const auto before = bps_.size();
830 bps_.erase(std::remove_if(bps_.begin(), bps_.end(),
832 return bp.line == line && sourcesMatch(source, bp.source);
835 return bps_.size() != before;
839 std::lock_guard<std::mutex> lock(mu_);
844 bps_.erase(std::remove_if(bps_.begin(), bps_.end(),
845 [&](
const Breakpoint& bp) { return sourcesMatch(source, bp.source); }),
850 std::lock_guard<std::mutex> lock(mu_);
851 for (
auto& bp : bps_) {
852 if (bp.id !=
id)
continue;
860 std::lock_guard<std::mutex> lock(mu_);
865 std::lock_guard<std::mutex> lock(mu_);
870 if (expression.empty())
return;
871 std::lock_guard<std::mutex> lock(mu_);
872 for (
const auto& e : watchExprs_) {
873 if (e == expression)
return;
875 watchExprs_.push_back(std::move(expression));
879 std::lock_guard<std::mutex> lock(mu_);
880 const auto before = watchExprs_.size();
881 watchExprs_.erase(std::remove(watchExprs_.begin(), watchExprs_.end(), expression),
883 return watchExprs_.size() != before;
887 std::lock_guard<std::mutex> lock(mu_);
893 std::lock_guard<std::mutex> lock(mu_);
898 std::vector<std::string> exprs;
900 std::lock_guard<std::mutex> lock(mu_);
903 std::vector<WatchEntry> cache;
904 cache.reserve(exprs.size());
905 for (
const auto& e : exprs) {
909 w.ok = !
info.type.empty() &&
info.type !=
"error";
910 w.value =
info.value;
911 cache.push_back(std::move(
w));
913 std::lock_guard<std::mutex> lock(mu_);
914 watchCache_ = std::move(cache);
922 info.value =
"no vm";
925 for (SQUnsignedInteger
n = 0;; ++
n) {
926 const SQInteger
top = sq_gettop(
vm);
927 const SQChar* lname = sq_getlocal(
vm,
level,
n);
933 info.value = describeSqValue(
vm, -1);
934 info.type = typeName(
vm, -1);
941 info.value =
"not found";
950 info.value =
"no vm";
953 const SQInteger
top = sq_gettop(
vm);
954 sq_pushroottable(
vm);
955 sq_pushstring(
vm,
name.c_str(), -1);
956 if (SQ_SUCCEEDED(sq_get(
vm, -2))) {
957 info.value = describeSqValue(
vm, -1);
958 info.type = typeName(
vm, -1);
961 info.value =
"not found";
967VariableInfo Debugger::evaluateLegacy(
const std::string& expression)
const {
969 if (!
vm || expression.empty()) {
971 info.name = expression;
973 info.value =
"unavailable";
978 auto local = readLocal(
vm, 0, expression);
982 if (expression.find(
'.') != std::string::npos) {
984 info.name = expression;
985 const SQInteger
top = sq_gettop(
vm);
986 sq_pushroottable(
vm);
989 while (
start < expression.size()) {
990 size_t dot = expression.find(
'.',
start);
991 if (dot == std::string::npos)
dot = expression.size();
992 const std::string part = expression.substr(
start, dot -
start);
993 sq_pushstring(
vm, part.c_str(), -1);
994 if (SQ_FAILED(sq_get(
vm, -2))) {
1003 info.value = describeSqValue(
vm, -1);
1004 info.type = typeName(
vm, -1);
1006 info.type =
"error";
1007 info.value =
"not found";
1012 return readRoot(
vm, expression);
1016 std::vector<VariableInfo> out;
1018 if (!
vm)
return out;
1019 const SQUnsignedInteger
level =
static_cast<SQUnsignedInteger
>(stackLevel < 0 ? 0 : stackLevel);
1020 for (SQUnsignedInteger
n = 0;; ++
n) {
1021 const SQInteger
top = sq_gettop(
vm);
1030 out.push_back(std::move(
info));
1036 std::vector<VariableInfo> out;
1038 if (!
vm)
return out;
1039 const SQInteger
top = sq_gettop(
vm);
1040 sq_pushroottable(
vm);
1041 out = enumerateAt(
vm, -1);
1047 for (SQUnsignedInteger
n = 0;; ++
n) {
1048 const SQInteger
top = sq_gettop(
vm);
1049 const SQChar* lname = sq_getlocal(
vm,
level,
n);
1054 if (
name == lname)
return true;
1060 const std::vector<std::string>&
path)
const {
1061 if (!
vm)
return false;
1062 const SQInteger
top = sq_gettop(
vm);
1064 sq_pushroottable(
vm);
1066 if (
path.empty())
return false;
1067 if (!pushLocalValue(
vm,
static_cast<unsigned>(frame < 0 ? 0 : frame),
path[0]))
1074 for (
size_t i =
start; i <
path.size(); ++i) {
1075 const SQInteger before = sq_gettop(
vm);
1076 const int curAbs =
static_cast<int>(before);
1077 sq_pushstring(
vm,
path[i].c_str(), -1);
1078 if (SQ_FAILED(sq_get(
vm, curAbs))) {
1088 const std::vector<std::string>&
path)
const {
1096 const SQInteger
top = sq_gettop(
vm);
1101 std::vector<VariableInfo> out = enumerateAt(
vm, -1);
1108 if (!
vm || expression.empty()) {
1111 info.type =
"error";
1112 info.value =
"unavailable";
1115 const SQInteger
top = sq_gettop(
vm);
1116 const int level = frameLevel < 0 ? 0 : frameLevel;
1121 for (SQUnsignedInteger
n = 0;; ++
n) {
1122 const SQInteger before = sq_gettop(
vm);
1123 const SQChar* lname = sq_getlocal(
vm,
static_cast<SQUnsignedInteger
>(
level),
n);
1125 sq_settop(
vm, before);
1129 sq_pushstring(
vm, lname, -1);
1132 sq_newslot(
vm, -3, SQFalse);
1134 sq_pushroottable(
vm);
1135 if (SQ_FAILED(sq_setdelegate(
vm, -2))) {
1137 return evaluateLegacy(expression);
1140 const std::string src =
"return (" + expression +
");";
1143 if (SQ_FAILED(sq_compilebuffer(
vm, src.c_str(),
static_cast<SQInteger
>(src.size()),
1144 _SC(
"memory://devtools/evaluate"), SQTrue))) {
1149 info.type =
"error";
1155 sq_setclosureroot(
vm, -2);
1156 sq_pushroottable(
vm);
1159 if (SQ_SUCCEEDED(sq_call(
vm, 1, SQTrue, SQFalse))) {
1160 info = describeAt(
vm, -1);
1163 info.type =
"error";
1171 std::vector<StackFrameInfo> out;
1173 if (!
vm)
return out;
1174 if (maxFrames <= 0) maxFrames = 32;
1177 if (SQ_FAILED(sq_stackinfos(
vm,
level, &si)))
break;
1180 if (si.source)
f.loc.source = si.source;
1181 f.loc.line =
static_cast<int>(si.line);
1187 if (
f.loc.source ==
"NATIVE" || (
f.loc.source.empty() &&
f.loc.line <= 0))
continue;
1188 f.name = nameFromPrototype(
level, si.funcname,
f.loc);
1189 f.loc.function =
f.name;
1190 out.push_back(std::move(
f));
1192 applyCallGraphNames(out);
struct SQVM * HSQUIRRELVM
std::map< std::string, std::vector< std::string > > graph
const UnitySourceAsset & source
Runtime call graph + dynamic data-flow tracer with backward slicing.
std::vector< CallFrame > currentStack() const
uint32_t onCall(const SourceLoc &loc, const std::string &funcName={})
uint32_t onReturn(const SourceLoc &loc, const std::string &funcName={})
Script + frame debugger: pause/step, breakpoints, watches.
bool hasBreakpoint(const std::string &source, int line) const
True if a breakpoint is registered at source+line. Ignores the skip-all master switch and the per-bre...
void setVirtualSource(std::string source, std::string text)
In-memory script text used to name anonymous name = function() bindings when the hook source is a com...
void waitWhilePaused(const std::function< void()> &pump={})
Block until resume/step/detach (processes external poll callbacks).
static std::string sourceBasename(const std::string &source)
Basename of a normalized path (empty-safe).
std::vector< VariableInfo > containerChildren(VarKind kind, int frame, const std::vector< std::string > &path) const
Children of a container variable. path is the key chain that locates the container from its root (loc...
void stepInto()
Enter calls: stop on the next script line at any depth.
void refreshWatches()
Re-evaluate all watches against current VM (paused preferred).
bool setBreakpointEnabled(int id, bool enabled)
Enable or disable by id.
void notifyFrameDone()
After a frame when StepFrame was active → return to Paused.
void stepOut()
Finish current function: stop when stack depth drops.
bool onScriptLine(const SourceLoc &loc)
Called from Squirrel line debug hook. Returns true if execution should block (breakpoint / step).
std::vector< WatchEntry > watches() const
void stepFrames(int count)
Run count game frames then pause. Each frame is consumed by shouldRunUpdate / notifyFrameDone on the ...
static std::string normalizeSource(std::string source)
Normalize source paths for breakpoint matching (basename fallback).
std::string onCall(SourceLoc loc)
Squirrel call debug hook ('c'). Records a frame on the active CallGraph.
std::vector< VariableInfo > globals() const
Root-table slots (globals).
void pauseAt(PauseReason reason, SourceLoc loc)
Pause and record the script site (copied; Observed until the next pause/resume). Thread: VM / game lo...
bool removeWatch(const std::string &expression)
void setCallGraph(CallGraph *graph)
Use DevTool's CallGraph for stack names (same object the slicer sees). Pass nullptr to record calls o...
std::vector< VariableInfo > locals(int stackLevel=0) const
Locals of the given call-stack level (0 = current script frame).
void stepOver()
Skip calls: stop on the next line at ≤ current stack depth.
void addWatch(std::string expression)
int scriptStackDepth() const
Current Squirrel call depth (1 = topmost script frame). 0 if none.
bool shouldRunUpdate()
Frame loop: true ⇒ call eve_update this frame. Consumes StepFrame.
void pause(PauseReason reason=PauseReason::PauseKey)
Frame-level pause without a script site (PauseKey clears pauseLocation). For Exception / Snapshot,...
bool clearBreakpoint(std::string source, int line)
int setBreakpoint(std::string source, int line, bool enabled=true, std::string condition={})
Add or update a line breakpoint. A later call with the same source+line replaces enabled and conditio...
void onReturn(const SourceLoc &loc)
Squirrel return debug hook ('r'). Pops the active CallGraph frame.
static Debugger & instance()
std::vector< Breakpoint > breakpoints() const
void step()
Convenience: script stepOver when mid-hook; otherwise one game frame. Prefer stepInto/stepOver/stepOu...
static bool sourcesMatch(const std::string &a, const std::string &b)
True when two source paths refer to the same script file. Matches exact path, or one path as a direct...
void attach(HSQUIRRELVM vm)
std::vector< StackFrameInfo > stackTrace(int maxFrames=32) const
VariableInfo evaluate(const std::string &expression, int frameLevel=0) const
Evaluate an expression in the given frame's scope. Understands plain names, a.b paths,...
void clearBreakpoints(const std::string &source={})
void notifyPending(const std::string &reason, const std::string &source, int line)
Record that a breakpoint / critical site wants a vision dump.
static RenderVision & instance()
VarKind
Where a variable tree node is rooted (used by containerChildren).
@ Snapshot
Script state was restored while the debugger was already paused.
double dot(const Vec2 &a, const Vec2 &b)
Dot.
std::string formatScriptError(const ScriptErrorContext &ctx)
Formats a context into a human-readable multi-line report.
ScriptErrorContext takeLastScriptError(HSQUIRRELVM vm)
Consumes and clears the last recorded error for a VM.
ScriptErrorContext captureCompileError(HSQUIRRELVM vm)
Captures the last compilation error recorded by the VM.
Source location in a Squirrel (or synthetic) script.
Structured snapshot of a script error: message, throw site and stack.
bool empty() const noexcept
True when no error payload was captured.