载入中...
搜索中...
未找到
DevTool.cpp
浏览该文件的文档.
2
13
14#include "common/Module.h"
16#include "common/RenderTrace.h"
17#include "common/Runtime.h"
18#include "common/ScriptError.h"
20
21#include <Poco/JSON/Parser.h>
22#include <simplesquirrel/simplesquirrel.hpp>
23#include <squirrel.h>
24
25#include <algorithm>
26#include <cstring>
27#include <sstream>
28#include <string>
29#include <vector>
30
31namespace eve::dev {
32namespace {
33
34thread_local DevTool* g_active = nullptr;
35
36std::string describeSqValue(HSQUIRRELVM vm, SQInteger idx) {
37 const SQObjectType t = sq_gettype(vm, idx);
38 switch (t) {
39 case OT_NULL:
40 return "null";
41 case OT_INTEGER: {
42 SQInteger v = 0;
43 sq_getinteger(vm, idx, &v);
44 return "i:" + std::to_string(static_cast<long long>(v));
45 }
46 case OT_FLOAT: {
47 SQFloat v = 0;
48 sq_getfloat(vm, idx, &v);
49 return "f:" + std::to_string(static_cast<double>(v));
50 }
51 case OT_BOOL: {
52 SQBool v = SQFalse;
53 sq_getbool(vm, idx, &v);
54 return v ? "b:1" : "b:0";
55 }
56 case OT_STRING: {
57 const SQChar* s = nullptr;
58 sq_getstring(vm, idx, &s);
59 return std::string("s:") + (s ? s : "");
60 }
61 case OT_TABLE:
62 return "table";
63 case OT_ARRAY:
64 return "array";
65 case OT_USERDATA:
66 return "userdata";
67 case OT_CLOSURE:
68 return "closure";
69 case OT_NATIVECLOSURE:
70 return "native";
71 case OT_GENERATOR:
72 return "generator";
73 case OT_USERPOINTER:
74 return "userpointer";
75 case OT_THREAD:
76 return "thread";
77 case OT_CLASS:
78 return "class";
79 case OT_INSTANCE:
80 return "instance";
81 case OT_WEAKREF:
82 return "weakref";
83 default:
84 return "other";
85 }
86}
87
88void nativeDebugHook(HSQUIRRELVM v, SQInteger type, const SQChar* sourcename, SQInteger line,
89 const SQChar* funcname) {
90 if (!g_active) return;
91 g_active->handleDebugEvent(v, static_cast<int>(type), sourcename ? sourcename : "",
92 static_cast<int>(line), funcname ? funcname : "");
93}
94
96SQInteger runtimeErrorHook(HSQUIRRELVM v) {
97 if (!g_active) return 0;
98 // Capture while the throw-site stack is still intact (including caught
99 // throws when sq_notifyallexceptions is on).
101 // When attached through Runtime, uncaught errors are reported by the
102 // Runtime error sink after unwind. Notifying here as well would treat
103 // expected catches (file_exists, migrate_instance) as debugger errors.
104 if (g_active->isRuntimeBound()) {
105 eve::script::setLastScriptError(v, std::move(ctx));
106 return 0;
107 }
108 const std::string msg = ctx.empty() ? std::string("script error")
110 try {
111 g_active->notifyError(msg);
112 } catch (...) {
113 }
114 ctx.reported = true;
115 eve::script::setLastScriptError(v, std::move(ctx));
116 return 0;
117}
118
119SourceLoc stackTopLoc(HSQUIRRELVM vm) {
120 SourceLoc loc;
121 SQStackInfos si;
122 if (SQ_SUCCEEDED(sq_stackinfos(vm, 1, &si))) {
123 if (si.source) loc.source = si.source;
124 loc.line = static_cast<int>(si.line);
125 if (si.funcname) loc.function = si.funcname;
126 }
127 return loc;
128}
129
130} // namespace
131
133 static DevTool inst;
134 return inst;
135}
136
137void DevTool::attach(ssq::VM& vm, bool sampleLocals) { attach(vm.getHandle(), sampleLocals); }
138
139void DevTool::attach(eve::Runtime& runtime, bool sampleLocals) {
140 attach(runtime.vm(), sampleLocals);
141 runtime_ = &runtime;
142 // Capture throw-site stacks for try/catch so reportError / lastScriptError
143 // can surface the original site (load.nut pattern). Safe here because the
144 // Runtime-bound error hook only stashes context and does not notify/pause.
145 sq_notifyallexceptions(runtime.handle(), SQTrue);
146 // Route Runtime-boundary errors into the slicer/report. The VM error hook
147 // only captures the stack when this Runtime sink is bound; this handler
148 // reports compile / reflect / unload failures and uncaught execute errors
149 // exactly once.
150 runtime.setErrorHandler([this](const eve::ScriptException& error) {
151 if (error.reported()) return;
152 try {
153 notifyError(error.what());
154 } catch (...) {
155 }
156 });
157}
158
159void DevTool::installRenderTracer() {
160 renderFlow_.clear();
161 eve::debug::setRenderTracer(&renderFlow_);
162 renderTraceEnabled_ = true;
163}
164
165void DevTool::uninstallRenderTracer() {
166 if (eve::debug::renderTracer() == &renderFlow_) eve::debug::setRenderTracer(nullptr);
167 renderTraceEnabled_ = false;
168}
169
171 if (on)
172 installRenderTracer();
173 else
174 uninstallRenderTracer();
175}
176
177void DevTool::attach(HSQUIRRELVM vm, bool sampleLocals) {
178 if (!vm) return;
179 // Drop script tools registered against another VM *before* touching it: if
180 // that VM went away without detach(), its memory is freed and sq_release
181 // into it would corrupt the heap. Passing the incoming VM as the owner means
182 // nothing is released here, only discarded.
184 detach();
185 vm_ = vm;
186 sampleLocals_ = sampleLocals;
187 graph_.clear();
188 localSnap_.clear();
189 lastReport_.clear();
190 lastError_.clear();
191 lastSlice_ = {};
192
195 Debugger::instance().setPump([this]() { pumpWhilePaused(); });
196
197 sq_enabledebuginfo(vm_, SQTrue);
198 // Do not enable sq_notifyallexceptions here. With notify-all + break-on-error,
199 // this hook would pause on every caught throw before script catch/reportError
200 // runs (double pause → hung script thread → flaky teardown SEGFAULT in
201 // devtools.dap.caughtErrorPausesAtReportSite). Runtime::installErrorHandler
202 // already turns notify-all on for production VMs; attach(Runtime&) keeps it.
203 sq_setnativedebughook(vm_, nativeDebugHook);
204 // Route uncaught script errors into the debugger (break-on-error aware).
205 sq_newclosure(vm_, runtimeErrorHook, 0);
206 sq_seterrorhandler(vm_);
207 g_active = this;
208 installRenderTracer();
209
211 ConsolePanel::instance().addLog("info", "DevTools attached");
212}
213
216 // Handlers registered by project scripts live in this VM; drop them while it
217 // is still valid so the references are released, not leaked.
218 clearScriptTools(vm_);
219 if (runtime_) {
220 runtime_->setErrorHandler({});
221 runtime_ = nullptr;
222 }
223 stopDap();
224 stopMcp();
227 if (vm_) {
228 sq_setnativedebughook(vm_, nullptr);
229 // Leave debuginfo enabled; harmless for subsequent runs on same VM.
230 }
231 if (g_active == this) g_active = nullptr;
233 vm_ = nullptr;
234 localSnap_.clear();
235 lastReport_.clear();
236 lastError_.clear();
237 lastSlice_ = {};
238 uninstallRenderTracer();
239}
240
241int DevTool::startDap(uint16_t port) { return DebugAdapter::instance().listen(port); }
242
244
245int DevTool::startMcp(uint16_t port) { return McpServer::instance().listen(port); }
246
248
250
252
254
256
261
263
265
267
269 try {
270 ssq::Table eveTbl = vm.find("eve").toTable();
271 ssq::Table dev = eveTbl.addTable("dev");
272 // Project-script MCP export surface (eve.mcp.tool/remove/tools/clear).
274
275 dev.addFunc("pause", [this]() {
277 dap().notifyStopped(PauseReason::PauseKey, debugger().pauseLocation());
278 });
279 // Note: Squirrel reserves `resume` (generators) and `continue` (loops),
280 // so the script binding cannot be named either of those.
281 dev.addFunc("continueRun", [this]() {
282 debugger().resume();
284 });
285 dev.addFunc("togglePause", [this]() {
286 if (debugger().isPaused()) {
287 debugger().resume();
289 } else {
291 dap().notifyStopped(PauseReason::PauseKey, debugger().pauseLocation());
292 }
293 });
294 dev.addFunc("isPaused", [this]() { return debugger().isPaused(); });
295 dev.addFunc("stepFrame", [this]() {
298 });
299 // stepInto / stepOver / stepOut — primary script stepping (DAP F11 / F10 / Shift+F11).
300 dev.addFunc("stepInto", [this]() {
301 debugger().stepInto();
303 });
304 dev.addFunc("stepOver", [this]() {
305 debugger().stepOver();
307 });
308 dev.addFunc("stepOut", [this]() {
309 debugger().stepOut();
311 });
312 // Historical alias for stepInto.
313 dev.addFunc("stepLine", [this]() {
314 debugger().stepLine();
316 });
317 // Convenience: stepOver when mid-script, else one frame.
318 dev.addFunc("step", [this]() {
319 debugger().step();
321 });
322 dev.addFunc("shouldRunUpdate", [this]() { return debugger().shouldRunUpdate(); });
323 dev.addFunc("notifyFrameDone", [this]() {
324 const bool wasStep = debugger().mode() == RunMode::StepFrame;
326 if (wasStep || debugger().isPaused())
327 dap().notifyStopped(debugger().lastPauseReason(), debugger().pauseLocation());
328 });
329 dev.addFunc("poll", [this]() { poll(); });
330
331 dev.addFunc("setBreakpoint", [this](std::string source, int line, std::string condition) {
332 return debugger().setBreakpoint(std::move(source), line, true, std::move(condition));
333 });
334 dev.addFunc("clearBreakpoint", [this](std::string source, int line) {
335 return debugger().clearBreakpoint(std::move(source), line);
336 });
337 dev.addFunc("clearBreakpoints", [this]() { debugger().clearBreakpoints(); });
338
339 dev.addFunc("addWatch", [this](std::string expr) { debugger().addWatch(std::move(expr)); });
340 dev.addFunc("removeWatch", [this](std::string expr) { return debugger().removeWatch(expr); });
341 dev.addFunc("clearWatches", [this]() { debugger().clearWatches(); });
342 dev.addFunc("eval", [this](std::string expr) {
343 auto info = debugger().evaluate(expr);
344 return info.value;
345 });
346 dev.addFunc("reportError", [this](std::string msg) {
347 return notifyError(msg.empty() ? std::string("script error") : std::move(msg));
348 });
349 dev.addFunc("setBreakOnError", [this](bool on) { debugger().setBreakOnError(on); });
350 dev.addFunc("breakOnError", [this]() { return debugger().breakOnError(); });
351 dev.addFunc("setBreakpointsEnabled", [this](bool on) {
353 });
354 dev.addFunc("breakpointsEnabled", [this]() { return debugger().breakpointsEnabled(); });
355 dev.addFunc("lastError", [this]() { return lastError(); });
356 dev.addFunc("profileReport", [this]() { return formatProfileReport(); });
357 dev.addFunc("profileClear", [this]() { profileClear(); });
358 dev.addFunc("registerSource", [this](std::string name, std::string content) {
359 dap().registerSource(std::move(name), std::move(content));
360 });
361
362 dev.addFunc("markStateRoot",
363 [](std::string name) { Snapshot::instance().markRoot(std::move(name)); });
364 dev.addFunc("unmarkStateRoot",
365 [](std::string name) { Snapshot::instance().unmarkRoot(name); });
366 dev.addFunc("markTransientStateRoot",
367 [](std::string name) { Snapshot::instance().markTransientRoot(std::move(name)); });
368 dev.addFunc("unmarkTransientStateRoot",
369 [](std::string name) { Snapshot::instance().unmarkTransientRoot(name); });
370 dev.addFunc("clearStateRoots", []() { Snapshot::instance().clearRoots(); });
371 dev.addFunc("stateRoots", [this]() { return snapshot().rootsFor(vm_); });
372 dev.addFunc("transientStateRoots", []() { return Snapshot::instance().transientRoots(); });
373 auto markSnapshotPause = [this]() {
374 if (!debugger().isPaused()) return;
375 debugger().pauseAt(PauseReason::Snapshot, debugger().pauseLocation());
377 dap().notifyStopped(PauseReason::Snapshot, debugger().pauseLocation(),
378 "snapshot restored");
379 };
380 dev.addFunc("saveSnapshot", [this](std::string path) {
381 std::string err;
382 const bool ok = snapshot().saveFile(vm_, path, &err);
383 if (!ok) return std::string("error:") + err;
384 return std::string("ok");
385 });
386 dev.addFunc("loadSnapshot", [this, markSnapshotPause](std::string path) {
387 std::string err;
388 const bool ok = snapshot().loadFile(vm_, path, &err);
389 if (!ok) return std::string("error:") + err;
390 markSnapshotPause();
391 return std::string("ok");
392 });
393 dev.addFunc("captureSnapshot", [this]() {
394 std::string err;
395 return snapshot().capture(vm_, &err);
396 });
397 dev.addFunc("restoreSnapshot", [this, markSnapshotPause](std::string json) {
398 std::string err;
399 const bool ok = snapshot().restore(vm_, json, &err);
400 if (!ok) return std::string("error:") + err;
401 markSnapshotPause();
402 return std::string("ok");
403 });
404 dev.addFunc("beginStateReload", [this]() {
405 std::string err;
406 if (!ReloadSession::instance().begin(vm_, &err)) return std::string("error:") + err;
407 return std::string("");
408 });
409 dev.addFunc("commitStateReload", [this]() {
410 std::string err;
411 if (!ReloadSession::instance().commit(vm_, &err)) return std::string("error:") + err;
412 return std::string("");
413 });
414 dev.addFunc("abortStateReload", [this]() {
415 std::string err;
416 if (!ReloadSession::instance().abort(vm_, &err)) return std::string("error:") + err;
417 return std::string("");
418 });
419 // Soft restart: reset native providers / re-run init without dropping
420 // ResourceManager. MCP eve_restart is the agent-facing entry; scripts
421 // can also call these helpers from eve_before_restart hooks.
422 dev.addFunc("resetNativeState", []() {
423 return static_cast<int>(resetNativeStateProviders());
424 });
425 dev.addFunc("resourceCacheCount", []() {
426 return static_cast<int>(resourceCacheCount());
427 });
428 // Parameter must not be named args*/argN — binding-contract codegen
429 // treats those as unresolved placeholders and fails the build.
430 dev.addFunc("softRestart", [this](std::string requestJson, bool reloadScripts) {
432 request.reloadScripts = reloadScripts;
433 if (!requestJson.empty()) {
434 try {
435 Poco::JSON::Parser parser;
436 auto parsed = parser.parse(requestJson);
437 request.args = parsed.extract<Poco::JSON::Object::Ptr>();
438 } catch (const std::exception& e) {
439 return std::string("error: invalid request JSON: ") + e.what();
440 } catch (...) {
441 return std::string("error: invalid request JSON");
442 }
443 }
444 auto result = executeSoftRestart(vm_, std::move(request));
445 if (!result) {
446 const auto& status = result.status();
447 const auto* diag = status.primaryDiagnostic();
448 return std::string("error:") +
449 (diag && !diag->message().empty() ? diag->message()
450 : std::string(statusCodeName(status.code())));
451 }
452 const SoftRestartReport& report = result.value();
453 return std::string("ok:resources=") + std::to_string(report.resourceCountAfter) +
454 ":scripts=" + (report.scriptsReloaded ? "1" : "0");
455 });
456
457 // ---- state-driven bug reproduction (baseline snapshot + step replay) ----
458 dev.addFunc("beginScenario", [this]() {
459 std::string err;
460 if (!ScenarioRecorder::instance().begin(vm_, &err))
461 return std::string("error:") + err;
462 return std::string("ok");
463 });
464 dev.addFunc("scenarioFrame", []() { ScenarioRecorder::instance().markFrame(); });
465 dev.addFunc("scenarioRecording",
466 []() { return ScenarioRecorder::instance().recording(); });
467 dev.addFunc("endScenario", [](std::string path) {
468 std::string err;
469 if (!ScenarioRecorder::instance().end(path, &err))
470 return std::string("error:") + err;
471 return std::string("ok");
472 });
473 dev.addFunc("cancelScenario", []() { ScenarioRecorder::instance().cancel(); });
474 dev.addFunc("beginReplay", [this](std::string path) {
475 std::string err;
476 if (!ScenarioRecorder::instance().beginReplay(vm_, path, &err))
477 return std::string("error:") + err;
478 return std::string("ok");
479 });
480 dev.addFunc("replayFrame", []() { return ScenarioRecorder::instance().stageFrame(); });
481 dev.addFunc("replayRemaining",
482 []() { return ScenarioRecorder::instance().framesRemaining(); });
483 dev.addFunc("replayErrorReport",
484 []() { return ScenarioRecorder::instance().errorReport(); });
485 dev.addFunc("gameplay", [](const std::string& requestJson) {
486 auto result = eve::executeGameplayControlJson(requestJson);
487 return result ? std::move(result).takeValue()
488 : std::string("error:") + result.status().describe();
489 });
490
491 // AI / MCP surface (DevTools panel + agent session log).
492 ssq::Table ai = dev.addTable("ai");
493 ai.addFunc("status", []() { return AiPanel::instance().statusLine(); });
494 ai.addFunc("isVisible", []() { return AiPanel::instance().isVisible(); });
495 ai.addFunc("setVisible", [](bool on) { AiPanel::instance().setVisible(on); });
496 ai.addFunc("toggleVisible", []() { AiPanel::instance().toggleVisible(); });
497 ai.addFunc("note", [](std::string text) {
498 AiPanel::instance().addNote(std::move(text));
499 return std::string("ok");
500 });
501 ai.addFunc("log", []() { return AiPanel::instance().formatLog(64); });
502 ai.addFunc("clearLog", []() { AiPanel::instance().clearLog(); });
503 ai.addFunc("mcpPort", []() { return AiPanel::instance().mcpPort(); });
504 ai.addFunc("mcpConnected", []() { return AiPanel::instance().mcpConnected(); });
505 ai.addFunc("draw", [this]() { drawAiPanel(); });
506
507 // Runtime console / log / REPL surface.
508 ssq::Table consoleTbl = dev.addTable("console");
509 consoleTbl.addFunc("log", [](std::string text) {
510 ConsolePanel::instance().addLog("info", std::move(text));
511 return std::string("ok");
512 });
513 consoleTbl.addFunc("info", [](std::string text) {
514 ConsolePanel::instance().addInfo(std::move(text));
515 return std::string("ok");
516 });
517 consoleTbl.addFunc("warn", [](std::string text) {
518 ConsolePanel::instance().addWarn(std::move(text));
519 return std::string("ok");
520 });
521 consoleTbl.addFunc("error", [](std::string text) {
523 return std::string("ok");
524 });
525 consoleTbl.addFunc("debug", [](std::string text) {
526 ConsolePanel::instance().addLog("debug", std::move(text));
527 return std::string("ok");
528 });
529 consoleTbl.addFunc("eval", [this](std::string expr) { return console().eval(std::move(expr)); });
530 consoleTbl.addFunc("clear", []() {
532 return std::string("ok");
533 });
534 consoleTbl.addFunc("recent", [](int n) {
535 const auto lines = ConsolePanel::instance().recent(
536 static_cast<size_t>(n > 0 ? n : 64));
537 std::vector<std::string> out;
538 out.reserve(lines.size());
539 for (const auto& l : lines) out.push_back("[" + l.timestamp + "] " + l.level + " | " + l.text);
540 return out;
541 });
542 consoleTbl.addFunc("format", [](int n) {
543 return ConsolePanel::instance().format(static_cast<size_t>(n > 0 ? n : 64));
544 });
545 consoleTbl.addFunc("isVisible", []() { return ConsolePanel::instance().isVisible(); });
546 consoleTbl.addFunc("setVisible", [](bool on) { ConsolePanel::instance().setVisible(on); });
547 consoleTbl.addFunc("toggleVisible", []() { ConsolePanel::instance().toggleVisible(); });
548 consoleTbl.addFunc("draw", [this]() { drawConsolePanel(); });
549
550 // FPS / frame-time overlay (sampled from load.nut each frame).
551 ssq::Table statsTbl = dev.addTable("stats");
552 statsTbl.addFunc("sample", [](float dt) {
554 return std::string("ok");
555 });
556 statsTbl.addFunc("setEntityCount", [](int n) {
558 return std::string("ok");
559 });
560 statsTbl.addFunc("clearEntityCount", []() {
562 return std::string("ok");
563 });
564 statsTbl.addFunc("fps", []() { return FrameStatsPanel::instance().fps(); });
565 statsTbl.addFunc("frameMs", []() { return FrameStatsPanel::instance().frameMs(); });
566 statsTbl.addFunc("entityCount", []() { return FrameStatsPanel::instance().entityCount(); });
567 statsTbl.addFunc("hasEntityCount", []() { return FrameStatsPanel::instance().hasEntityCount(); });
568 statsTbl.addFunc("format", []() { return FrameStatsPanel::instance().format(); });
569 statsTbl.addFunc("isVisible", []() { return FrameStatsPanel::instance().isVisible(); });
570 statsTbl.addFunc("setVisible", [](bool on) { FrameStatsPanel::instance().setVisible(on); });
571 statsTbl.addFunc("toggleVisible", []() { FrameStatsPanel::instance().toggleVisible(); });
572 statsTbl.addFunc("draw", [this]() { drawFrameStatsPanel(); });
573
574 // Native binding is arity-3; keep `setBreakpoint(file, line)` working.
575 ssq::Script bpWrap = vm.compileSource(
576 "local _setBreakpoint = eve.dev.setBreakpoint\n"
577 "eve.dev.setBreakpoint = function(source, line, condition = \"\") {\n"
578 " return _setBreakpoint(source, line, condition)\n"
579 "}\n");
580 vm.run(bpWrap);
581 } catch (...) {
582 // If eve table missing, skip — attach still useful for C++/DAP/MCP.
583 }
584}
585
587 const char* funcname) {
588 SourceLoc loc;
589 loc.source = source ? source : "";
590 loc.line = line;
591 loc.function = funcname ? funcname : "";
592
593 // Squirrel passes 'l' / 'c' / 'r' as event type characters.
594 switch (type) {
595 case 'c':
597 localSnap_.erase(static_cast<int>(graph_.currentStack().size()));
598 profileCall(loc.function);
599 break;
600 case 'r':
602 // Drop snapshot for the frame that just returned.
603 localSnap_.erase(static_cast<int>(graph_.currentStack().size()) + 1);
604 profileReturn();
605 break;
606 case 'l':
607 graph_.onLine(loc);
608 profileLine(loc.function);
609 if (sampleLocals_) sampleFrameLocals(vm, loc);
610 if (Debugger::instance().onScriptLine(loc)) {
611 dap().notifyStopped(Debugger::instance().lastPauseReason(), loc);
613 Debugger::instance().waitWhilePaused([this]() { pumpWhilePaused(); });
614 }
615 break;
616 default:
617 break;
618 }
619}
620
621void DevTool::handleDebugHotkey(const std::string& key) {
622 if (key.empty()) return;
623 Debugger& dbg = debugger();
624 if (key == "F5") {
625 if (!dbg.isPaused()) return;
626 dbg.resume();
628 return;
629 }
630 if (key == "F10") {
631 if (!dbg.isPaused()) return;
632 dbg.stepOver();
634 return;
635 }
636 if (key == "F11") {
637 if (!dbg.isPaused()) return;
638 dbg.stepInto();
640 return;
641 }
642 if (key == "F8") {
643 if (!dbg.isPaused()) return;
644 dbg.stepFrame();
646 return;
647 }
648 if (key == "Pause") {
649 // Already inside a script pause — Pause resumes (toggle).
650 if (!dbg.isPaused()) return;
651 dbg.resume();
653 }
654}
655
656void DevTool::pumpWhilePaused() {
657 poll(); // DAP continue / next / pause
658
659 auto* ev = eve::ModuleManager::getInstance<eve::platform_event::PlatformEvent>("PlatformEvent");
660 if (!ev) return;
661 ev->pump();
662
663 // Consume debug hotkeys so F5/F8/F10/F11 work while blocked in the line hook;
664 // re-queue everything else for the main loop after resume.
665 std::vector<std::unique_ptr<eve::platform_event::Message>> keep;
666 while (auto msg = ev->pollOwned()) {
667 bool handled = false;
668 if (msg->name == "keypressed" && !msg->args.empty() &&
669 msg->args[0].type == eve::platform_event::Variant::Type::String) {
670 const std::string& key = msg->args[0].s;
671 if (key == "F5" || key == "F8" || key == "F10" || key == "F11" || key == "Pause") {
672 handleDebugHotkey(key);
673 handled = true;
674 }
675 }
676 if (!handled) keep.push_back(std::move(msg));
677 }
678 for (auto& msg : keep) ev->push(std::move(msg));
679}
680
681void DevTool::sampleFrameLocals(HSQUIRRELVM vm, const SourceLoc& loc) {
682 if (!vm) return;
683 const int depth = static_cast<int>(graph_.currentStack().size());
684 auto& prev = localSnap_[depth];
685 std::unordered_map<std::string, std::string> cur;
686
687 // Level 0 = current script frame. The native debug hook is invoked as a
688 // direct C call (no CallInfo pushed), so the top of the Squirrel call
689 // stack is the script frame that triggered the line event.
690 const SQUnsignedInteger level = 0;
691 for (SQUnsignedInteger n = 0;; ++n) {
692 const SQInteger top = sq_gettop(vm);
693 const SQChar* name = sq_getlocal(vm, level, n);
694 if (!name) {
695 sq_settop(vm, top);
696 break;
697 }
698 const std::string key(name);
699 // Skip temporaries / this binding noise if desired; keep "this" for OO flow.
700 const std::string val = describeSqValue(vm, -1);
701 sq_settop(vm, top);
702
703 cur[key] = val;
704 auto it = prev.find(key);
705 if (it == prev.end() || it->second != val) {
706 // First sighting or value change ⇒ definition for the data-flow graph.
707 graph_.onDef(loc, key);
708 }
709 }
710 prev.swap(cur);
711}
712
713SliceResult DevTool::analyzeError(const std::string& errorMessage,
714 const std::vector<std::string>& hintVars) const {
716 c.variables = hintVars;
717
718 if (vm_) {
719 SQStackInfos si;
720 if (SQ_SUCCEEDED(sq_stackinfos(vm_, 1, &si))) {
721 if (si.source) c.loc.source = si.source;
722 c.loc.line = static_cast<int>(si.line);
723 if (si.funcname) c.loc.function = si.funcname;
724 } else {
725 c.loc = stackTopLoc(vm_);
726 }
727 }
728
729 // Prefer last recorded line if stackinfos unavailable.
730 if (c.loc.empty() && !graph_.events().empty()) {
731 const auto ev = graph_.events();
732 for (size_t i = ev.size(); i-- > 0;) {
733 if (ev[i].kind == TraceKind::Line || ev[i].kind == TraceKind::Use ||
734 ev[i].kind == TraceKind::Def) {
735 c.loc = ev[i].loc;
736 break;
737 }
738 }
739 }
740
741 (void)errorMessage;
742 return graph_.sliceBackward(c);
743}
744
745std::string DevTool::formatError(const std::string& errorMessage,
746 const std::vector<std::string>& hintVars) const {
748 c.variables = hintVars;
749 if (vm_) {
750 SQStackInfos si;
751 if (SQ_SUCCEEDED(sq_stackinfos(vm_, 1, &si))) {
752 if (si.source) c.loc.source = si.source;
753 c.loc.line = static_cast<int>(si.line);
754 if (si.funcname) c.loc.function = si.funcname;
755 }
756 }
757 if (c.loc.empty() && !graph_.events().empty()) {
758 const auto ev = graph_.events();
759 for (size_t i = ev.size(); i-- > 0;) {
760 if (!ev[i].loc.empty()) {
761 c.loc = ev[i].loc;
762 break;
763 }
764 }
765 }
766 return graph_.formatErrorReport(errorMessage, c);
767}
768
769void DevTool::markErrorUses(const SourceLoc& loc,
770 const std::vector<std::string>& hintVars) {
771 if (!vm_) return;
772 SourceLoc site = loc;
773 if (site.empty()) {
774 SQStackInfos si;
775 if (SQ_SUCCEEDED(sq_stackinfos(vm_, 1, &si))) {
776 if (si.source) site.source = si.source;
777 site.line = static_cast<int>(si.line);
778 if (si.funcname) site.function = si.funcname;
779 }
780 }
781 graph_.onLine(site);
782
783 const SQUnsignedInteger level = 0;
784 for (SQUnsignedInteger n = 0;; ++n) {
785 const SQInteger top = sq_gettop(vm_);
786 const SQChar* name = sq_getlocal(vm_, level, n);
787 if (!name) {
788 sq_settop(vm_, top);
789 break;
790 }
791 const std::string key(name);
792 sq_settop(vm_, top);
793 if (!hintVars.empty()) {
794 bool wanted = false;
795 for (const auto& h : hintVars) {
796 if (h == key) {
797 wanted = true;
798 break;
799 }
800 }
801 if (!wanted) continue;
802 }
803 graph_.onUse(site, key);
804 }
805}
806
807std::string DevTool::notifyError(const std::string& errorMessage,
808 const std::vector<std::string>& hintVars) {
809 SourceLoc site;
810 if (vm_) {
811 // Prefer the stack captured at throw time. reportError runs from a
812 // script catch, so sq_stackinfos(level 1) is the catch site (load.nut)
813 // rather than the original throw in main.nut.
814 if (const auto* ctx = eve::script::peekLastScriptError(vm_)) {
815 if (!ctx->stack.empty() &&
816 (errorMessage.find(ctx->message) != std::string::npos ||
817 ctx->message.find(errorMessage) != std::string::npos)) {
818 site.source = ctx->source;
819 site.line = ctx->line;
820 site.function = ctx->function;
821 }
822 }
823 if (site.empty()) {
824 SQStackInfos si;
825 if (SQ_SUCCEEDED(sq_stackinfos(vm_, 1, &si))) {
826 if (si.source) site.source = si.source;
827 site.line = static_cast<int>(si.line);
828 if (si.funcname) site.function = si.funcname;
829 }
830 }
831 }
832 markErrorUses(site, hintVars);
833 // Ensure the error is marked on the render flow even if Exception ctor
834 // already did (idempotent append of another Error node is fine).
835 if (renderTraceEnabled_) renderFlow_.error(errorMessage.c_str());
836
837 std::string report;
838 if (vm_ || !graph_.events().empty()) report += formatError(errorMessage, hintVars);
839 if (renderTraceEnabled_ && !renderFlow_.events().empty()) {
840 if (!report.empty()) report += "\n";
841 report += renderFlow_.formatErrorReport(errorMessage);
842 }
843 if (report.empty()) report = std::string("Error: ") + errorMessage + "\n";
844 lastReport_ = report;
845 lastError_ = errorMessage;
846 lastSlice_ = analyzeError(errorMessage, hintVars);
847
848 // If a scenario is being recorded, pair the failure report + site with it so
849 // the dumped scenario becomes a reproducible baseline + input sequence.
850 ScenarioRecorder::instance().setErrorInfo(report, site.source + ":" + std::to_string(site.line));
851
852 RenderVision::instance().notifyPending("error", site.source, site.line);
853 if (debugger().breakOnError()) {
854 // Godot "Break on Error": stop at the reported site. Block inside the
855 // hook so the IDE sees a stable frame instead of the next executed line.
857 dap().notifyStopped(PauseReason::Exception, site, errorMessage);
858 debugger().waitWhilePaused([this]() { pumpWhilePaused(); });
859 }
860
861 return lastReport_;
862}
863
865
866std::size_t mcpCallgraphEvents() { return DevTool::instance().graph().events().size(); }
867
869 return DevTool::instance().graph().currentStack().size();
870}
871
872const std::string& mcpLastReport() { return DevTool::instance().lastReport(); }
873
874std::string mcpFormatError(const std::string& message) {
876}
877
878void DevTool::profileCall(const std::string& func) {
879 profStack_.emplace_back(func, std::chrono::steady_clock::now());
880}
881
882void DevTool::profileLine(const std::string& func) {
883 const auto now = std::chrono::steady_clock::now();
884 if (!profStack_.empty()) {
885 auto& top = profStack_.back();
886 profile_[top.first].ns +=
887 std::chrono::duration_cast<std::chrono::nanoseconds>(now - top.second).count();
888 top.second = now;
889 }
890 if (!func.empty()) ++profile_[func].lines;
891}
892
893void DevTool::profileReturn() {
894 const auto now = std::chrono::steady_clock::now();
895 if (profStack_.empty()) return;
896 auto& top = profStack_.back();
897 profile_[top.first].ns +=
898 std::chrono::duration_cast<std::chrono::nanoseconds>(now - top.second).count();
899 ++profile_[top.first].calls;
900 profStack_.pop_back();
901}
902
903std::string DevTool::formatProfileReport() const {
904 std::ostringstream oss;
905 oss << "function, calls, lines, time_ms\n";
906 std::vector<std::pair<std::string, ProfileEntry>> rows(profile_.begin(), profile_.end());
907 std::sort(rows.begin(), rows.end(), [](const auto& a, const auto& b) {
908 return a.second.ns > b.second.ns;
909 });
910 for (const auto& [name, e] : rows) {
911 oss << name << ", " << e.calls << ", " << e.lines << ", "
912 << (static_cast<double>(e.ns) / 1e6) << "\n";
913 }
914 return oss.str();
915}
916
917} // namespace eve::dev
SQInteger top
const std::string & s
struct SQVM * HSQUIRRELVM
eve::Value condition
HSQUIRRELVM vm
Definition ECS.cpp:20
int rows
Versioned JSON facade shared by scripting, MCP and scenario tooling.
std::string message
const GltfImportRequest & request
std::uint32_t key
wgpu::PopErrorScopeStatus status
glm::vec3 n
Definition Grass.cpp:63
float v
std::int32_t c
int h
std::string text
TokenKind kind
std::string name
Project-script → MCP export registry.
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
int level
std::string error
Definition Package.cpp:60
int idx
std::string script
Definition PlayHost.cpp:116
std::string path
Definition PlayHost.cpp:110
float begin
float t
Soft-restart a live game without unloading the resource cache.
std::vector< std::string_view > lines
const UnitySourceAsset & source
std::uint32_t depth
EVENGINE_API_FOUNDATION public API.
Definition Runtime.h:189
void setErrorHandler(ErrorHandler handler)
Replaces the handler invoked for script errors at the Runtime boundary.
Definition Runtime.h:412
Exception raised at the public Runtime boundary.
Definition Runtime.h:61
In-engine AI / MCP session surface for DevTools.
Definition AiPanel.hpp:27
void addNote(std::string text)
Definition AiPanel.cpp:95
void setVisible(bool on)
Definition AiPanel.cpp:37
std::string statusLine() const
Compact status line for overlays / MCP resources.
Definition AiPanel.cpp:117
std::string formatLog(size_t max=64) const
Definition AiPanel.cpp:106
bool mcpConnected() const
Definition AiPanel.cpp:64
void toggleVisible()
Definition AiPanel.cpp:47
static AiPanel & instance()
Definition AiPanel.cpp:15
int mcpPort() const
Definition AiPanel.cpp:54
void drawImGui()
Optional ImGui draw hook. Default no-op: in-engine AI status is exposed via MCP / eve....
Definition AiPanel.cpp:140
bool isVisible() const
Definition AiPanel.cpp:42
SliceResult sliceBackward(const SliceCriterion &criterion) const
Dynamic backward slice from an error criterion. Follows data dependencies (Use←Def) and control prede...
uint32_t onLine(const SourceLoc &loc)
uint32_t onUse(const SourceLoc &loc, const std::string &var)
std::string formatErrorReport(const std::string &errorMessage, const SliceCriterion &criterion) const
Human-readable report: message + call stack + data-flow + slice locs.
uint32_t onDef(const SourceLoc &loc, const std::string &var)
std::vector< CallFrame > currentStack() const
EventsView events() const
In-engine runtime console / log ring buffer for DevTools.
std::vector< ConsoleLine > recent(size_t max=128) const
void addWarn(std::string text)
void addLog(std::string level, std::string text)
Append a leveled log line (thread-safe).
static ConsolePanel & instance()
std::string eval(const std::string &expression)
Evaluate a Squirrel expression against the root table and return a formatted result (or error message...
void addError(std::string text)
void addInfo(std::string text)
void attach(HSQUIRRELVM vm)
Attach to a Squirrel VM: capture print/script errors into the log.
std::string format(size_t max=128) const
void drawImGui()
Optional ImGui draw hook. Default no-op: console UI is registered by the host (see setImGuiDrawer) so...
void poll()
Accept clients + process one request batch (non-blocking).
int listen(uint16_t port)
Bind TCP listen port (0 = ephemeral). Returns bound port or 0 on failure.
void registerSource(std::string name, std::string content)
Register virtual source content for compiled buffers (compilestring).
void notifyStopped(PauseReason reason, const SourceLoc &loc, const std::string &description={})
Emit stopped event to the connected client (if any).
static DebugAdapter & instance()
Script + frame debugger: pause/step, breakpoints, watches.
Definition Debugger.hpp:86
void waitWhilePaused(const std::function< void()> &pump={})
Block until resume/step/detach (processes external poll callbacks).
Definition Debugger.cpp:780
void setBreakOnError(bool on)
Break on script errors (Godot "Break on Error"). Default off.
Definition Debugger.hpp:226
void stepInto()
Enter calls: stop on the next script line at any depth.
Definition Debugger.cpp:620
void refreshWatches()
Re-evaluate all watches against current VM (paused preferred).
Definition Debugger.cpp:897
void notifyFrameDone()
After a frame when StepFrame was active → return to Paused.
Definition Debugger.cpp:655
void stepOut()
Finish current function: stop when stack depth drops.
Definition Debugger.cpp:624
bool isPaused() const
Definition Debugger.hpp:133
RunMode mode() const
Definition Debugger.hpp:134
std::string onCall(SourceLoc loc)
Squirrel call debug hook ('c'). Records a frame on the active CallGraph.
Definition Debugger.cpp:500
void pauseAt(PauseReason reason, SourceLoc loc)
Pause and record the script site (copied; Observed until the next pause/resume). Thread: VM / game lo...
Definition Debugger.cpp:564
bool removeWatch(const std::string &expression)
Definition Debugger.cpp:878
void setCallGraph(CallGraph *graph)
Use DevTool's CallGraph for stack names (same object the slicer sees). Pass nullptr to record calls o...
Definition Debugger.cpp:462
void stepOver()
Skip calls: stop on the next line at ≤ current stack depth.
Definition Debugger.cpp:622
void addWatch(std::string expression)
Definition Debugger.cpp:869
bool shouldRunUpdate()
Frame loop: true ⇒ call eve_update this frame. Consumes StepFrame.
Definition Debugger.cpp:645
void pause(PauseReason reason=PauseReason::PauseKey)
Frame-level pause without a script site (PauseKey clears pauseLocation). For Exception / Snapshot,...
Definition Debugger.cpp:553
bool clearBreakpoint(std::string source, int line)
Definition Debugger.cpp:827
void setBreakpointsEnabled(bool on)
Master switch for all breakpoints ("skip all breakpoints").
Definition Debugger.hpp:229
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...
Definition Debugger.cpp:805
void onReturn(const SourceLoc &loc)
Squirrel return debug hook ('r'). Pops the active CallGraph frame.
Definition Debugger.cpp:507
void setPump(PumpFn pump)
Definition Debugger.hpp:237
static Debugger & instance()
Definition Debugger.cpp:387
void step()
Convenience: script stepOver when mid-hook; otherwise one game frame. Prefer stepInto/stepOver/stepOu...
Definition Debugger.cpp:633
bool breakOnError() const
Definition Debugger.hpp:227
void attach(HSQUIRRELVM vm)
Definition Debugger.cpp:440
void stepLine()
Alias for stepInto (historical name).
Definition Debugger.hpp:127
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={})
Definition Debugger.cpp:838
bool breakpointsEnabled() const
Definition Debugger.hpp:230
Platform-level script + render debugger / dynamic slicer front-end.
Definition DevTool.hpp:50
const std::string & lastReport() const
Definition DevTool.hpp:116
void enableRenderTrace(bool on=true)
Enable render-flow tracing without a Squirrel VM (C++ / unit tests).
Definition DevTool.cpp:170
int startDap(uint16_t port)
Start DAP server; returns bound port (0 on failure).
Definition DevTool.cpp:241
void handleDebugEvent(HSQUIRRELVM vm, int type, const char *source, int line, const char *funcname)
Definition DevTool.cpp:586
Debugger & debugger()
Definition DevTool.hpp:101
McpServer & mcp()
Definition DevTool.cpp:249
void attach(ssq::VM &vm, bool sampleLocals=true)
Attach script tracer to VM and enable render tracing.
Definition DevTool.cpp:137
void exposeScriptApi(ssq::VM &vm)
Expose eve.dev script API (pause/breakpoint/watch/snapshot/AI).
Definition DevTool.cpp:268
const std::string & lastError() const
Definition DevTool.hpp:117
Snapshot & snapshot()
Definition DevTool.hpp:102
CallGraph & graph()
Definition DevTool.hpp:97
void drawFrameStatsPanel()
Draw FPS / frame-time overlay when visible (call from UI/frame loop).
Definition DevTool.cpp:266
bool sampleLocals() const
Definition DevTool.hpp:94
int startMcp(uint16_t port)
Start MCP server for AI tooling; returns bound port (0 on failure).
Definition DevTool.cpp:245
std::string notifyError(const std::string &errorMessage, const std::vector< std::string > &hintVars={})
Record an error; includes script slice and render-pipeline slice when enabled.
Definition DevTool.cpp:807
ConsolePanel & console()
Definition DevTool.cpp:253
AiPanel & ai()
Definition DevTool.cpp:251
void drawConsolePanel()
Draw DevTools console ImGui panel when visible (call from UI/frame loop).
Definition DevTool.cpp:264
std::string formatProfileReport() const
Definition DevTool.cpp:903
bool isAttached() const
Definition DevTool.hpp:90
std::string formatError(const std::string &errorMessage, const std::vector< std::string > &hintVars={}) const
Definition DevTool.cpp:745
void drawAiPanel()
Draw DevTools AI ImGui panel when visible (call from UI/frame loop).
Definition DevTool.cpp:262
static DevTool & instance()
Definition DevTool.cpp:132
SliceResult analyzeError(const std::string &errorMessage, const std::vector< std::string > &hintVars={}) const
Definition DevTool.cpp:713
FrameStatsPanel & frameStats()
Definition DevTool.cpp:255
DebugAdapter & dap()
Definition DevTool.hpp:104
Lightweight FPS / frame-time overlay for DevTools (0.6 closeout).
void setEntityCount(int count)
Optional entity / actor count shown in the overlay; negative clears.
int entityCount() const
-1 when unset
static FrameStatsPanel & instance()
void sample(float dtSeconds)
Feed one frame duration in seconds (must be finite and > 0). Updates EMA FPS and last frame milliseco...
std::string format() const
One-line summary for logs / scripts, e.g. 60.1 fps | 16.6 ms | 128 entities.
Embedded Model Context Protocol (MCP) server for AI-assisted game development.
Definition McpServer.hpp:35
void poll()
Accept clients + process one request batch (non-blocking).
int listen(uint16_t port)
Bind TCP listen port (0 = ephemeral). Returns bound port or 0 on failure.
static McpServer & instance()
static ReloadSession & instance()
Instance.
std::string formatErrorReport(const std::string &errorMessage, const RenderSliceCriterion &c={}) const
void error(const char *message) override
Error.
EventsView events() const
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()
void setErrorInfo(const std::string &report, const std::string &site)
Called by DevTool when a script error is dumped during recording.
bool stageFrame()
Re-push the next recorded frame's events into the event queue.
int framesRemaining() const
Number of frames remaining to stage.
void markFrame()
Begin a new frame bucket; no-op unless recording.
void cancel()
Abort recording and restore the previous event observer.
const std::string & errorReport() const
Error report.
bool begin(HSQUIRRELVM vm, std::string *err=nullptr)
Start recording: capture the baseline snapshot and observe the event queue.
bool recording() const
True while a scenario is being recorded.
static ScenarioRecorder & instance()
Instance.
std::vector< std::string > rootsFor(HSQUIRRELVM vm) const
Marked roots, or heuristic roots when none marked (script-facing).
Definition Snapshot.hpp:62
bool restore(HSQUIRRELVM vm, const std::string &json, std::string *error=nullptr) const
Definition Snapshot.cpp:512
void unmarkTransientRoot(const std::string &name)
Remove an explicit transient-root declaration.
Definition Snapshot.cpp:319
std::string capture(HSQUIRRELVM vm, std::string *error=nullptr) const
Capture marked roots, or heuristic roots when none marked.
Definition Snapshot.cpp:438
bool loadFile(HSQUIRRELVM vm, const std::string &path, std::string *error=nullptr) const
Definition Snapshot.cpp:538
std::vector< std::string > transientRoots() const
Return the explicit transient roots.
Definition Snapshot.cpp:330
void markTransientRoot(std::string name)
Mark a root for reconstruction from new script definitions.
Definition Snapshot.cpp:310
void markRoot(std::string name)
Mark a root as persistent, replacing a transient declaration.
Definition Snapshot.cpp:297
bool saveFile(HSQUIRRELVM vm, const std::string &path, std::string *error=nullptr) const
Definition Snapshot.cpp:526
static Snapshot & instance()
Definition Snapshot.cpp:290
void unmarkRoot(const std::string &name)
Remove an explicit persistent-root declaration.
Definition Snapshot.cpp:306
void clearRoots()
Clear every explicit persistent and transient declaration.
Definition Snapshot.cpp:323
void setRenderTracer(IRenderTracer *tracer)
Sets the render tracer.
IRenderTracer * renderTracer()
Renders tracer.
std::size_t resourceCacheCount()
Current ResourceManager cache entry count (for restart telemetry).
std::size_t mcpCallgraphStackDepth()
Definition DevTool.cpp:868
void clearScriptTools(HSQUIRRELVM owner)
Drop registry entries owned by owner.
std::size_t mcpCallgraphEvents()
Definition DevTool.cpp:866
std::uint32_t resetNativeStateProviders()
Reset every registered IStateProvider to its defaults.
std::string mcpFormatError(const std::string &message)
Definition DevTool.cpp:874
const std::string & mcpLastReport()
Definition DevTool.cpp:872
Result< SoftRestartReport > executeSoftRestart(HSQUIRRELVM vm, SoftRestartRequest request)
Soft-restart the game attached to vm.
bool mcpDevAttached()
Thin hooks so McpServer.cpp need not include DevTool.hpp (avoids a cycle).
Definition DevTool.cpp:864
void exposeMcpScriptApi(ssq::VM &vm)
Expose eve.mcp.tool/remove/tools/clear on a VM.
@ Snapshot
Script state was restored while the debugger was already paused.
std::string formatScriptError(const ScriptErrorContext &ctx)
Formats a context into a human-readable multi-line report.
const ScriptErrorContext * peekLastScriptError(HSQUIRRELVM vm)
Peeks at the last recorded error for a VM without clearing it.
void setLastScriptError(HSQUIRRELVM vm, ScriptErrorContext ctx)
Records the last error for a VM (thread-local, synchronous consumers).
ScriptErrorContext captureScriptError(HSQUIRRELVM vm)
Captures the pending runtime error from inside the Squirrel error handler.
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16
Result< std::string > executeGameplayControlJson(std::string_view requestJson)
Parse, execute and serialize one gameplay-control JSON request.
constexpr std::string_view statusCodeName(StatusCode code) noexcept
Return the stable protocol spelling of a status code.
Definition Status.h:45
Criterion for a Weiser-style dynamic backward slice.
Definition CallGraph.hpp:57
std::vector< std::string > variables
Definition CallGraph.hpp:59
Outcome of a successful soft restart. @ownership Owning value; safe to keep across frames.
bool scriptsReloaded
Whether tracked scripts were re-dofile'd.
std::size_t resourceCountAfter
Cached resource entries after the restart.
Inputs for one soft restart. @thread Game / MCP poll thread only.
bool reloadScripts
When true (default), re-dofile tracked scripts after clearing persist/state roots so initializers run...
Source location in a Squirrel (or synthetic) script.
Definition CallGraph.hpp:16
std::string function
Definition CallGraph.hpp:19
bool empty() const
Definition CallGraph.hpp:21
std::string source
Definition CallGraph.hpp:17
Structured snapshot of a script error: message, throw site and stack.
Definition ScriptError.h:25
int line
1-based throw-site line; -1 when unknown.
Definition ScriptError.h:29
std::string function
Function of the throwing frame.
Definition ScriptError.h:28
bool reported
True when a reporter already handled this error.
Definition ScriptError.h:33
bool empty() const noexcept
True when no error payload was captured.
Definition ScriptError.h:36
std::string message
Raw error value ("kaboom", "42", ...).
Definition ScriptError.h:26
std::vector< ScriptFrame > stack
Call stack, innermost frame first.
Definition ScriptError.h:32
std::string source
Source of the throwing frame.
Definition ScriptError.h:27
glm::uvec4 info