载入中...
搜索中...
未找到
DevTool.cpp
浏览该文件的文档.
2
9
10#include "common/Module.h"
11#include "common/RenderTrace.h"
12#include "common/ScriptError.h"
13#include "event/Event.h"
14
15#include <simplesquirrel/simplesquirrel.hpp>
16#include <squirrel.h>
17
18#include <algorithm>
19#include <cstring>
20#include <sstream>
21#include <string>
22#include <vector>
23
24namespace eve::dev {
25namespace {
26
27thread_local DevTool* g_active = nullptr;
28
29std::string describeSqValue(HSQUIRRELVM vm, SQInteger idx) {
30 const SQObjectType t = sq_gettype(vm, idx);
31 switch (t) {
32 case OT_NULL:
33 return "null";
34 case OT_INTEGER: {
35 SQInteger v = 0;
36 sq_getinteger(vm, idx, &v);
37 return "i:" + std::to_string(static_cast<long long>(v));
38 }
39 case OT_FLOAT: {
40 SQFloat v = 0;
41 sq_getfloat(vm, idx, &v);
42 return "f:" + std::to_string(static_cast<double>(v));
43 }
44 case OT_BOOL: {
45 SQBool v = SQFalse;
46 sq_getbool(vm, idx, &v);
47 return v ? "b:1" : "b:0";
48 }
49 case OT_STRING: {
50 const SQChar* s = nullptr;
51 sq_getstring(vm, idx, &s);
52 return std::string("s:") + (s ? s : "");
53 }
54 case OT_TABLE:
55 return "table";
56 case OT_ARRAY:
57 return "array";
58 case OT_USERDATA:
59 return "userdata";
60 case OT_CLOSURE:
61 return "closure";
62 case OT_NATIVECLOSURE:
63 return "native";
64 case OT_GENERATOR:
65 return "generator";
66 case OT_USERPOINTER:
67 return "userpointer";
68 case OT_THREAD:
69 return "thread";
70 case OT_CLASS:
71 return "class";
72 case OT_INSTANCE:
73 return "instance";
74 case OT_WEAKREF:
75 return "weakref";
76 default:
77 return "other";
78 }
79}
80
81void nativeDebugHook(HSQUIRRELVM v, SQInteger type, const SQChar* sourcename, SQInteger line,
82 const SQChar* funcname) {
83 if (!g_active) return;
84 g_active->handleDebugEvent(v, static_cast<int>(type), sourcename ? sourcename : "",
85 static_cast<int>(line), funcname ? funcname : "");
86}
87
89SQInteger runtimeErrorHook(HSQUIRRELVM v) {
90 if (!g_active) return 0;
91 // Capture the full context (message + live call stack) so Runtime::execute
92 // can enrich the ScriptException it throws once the call unwinds.
94 const std::string msg = ctx.empty() ? std::string("script error")
95 : eve::script::formatScriptError(ctx);
96 try {
97 g_active->notifyError(msg);
98 } catch (...) {
99 }
100 ctx.reported = true;
101 eve::script::setLastScriptError(v, std::move(ctx));
102 return 0;
103}
104
105SourceLoc stackTopLoc(HSQUIRRELVM vm) {
106 SourceLoc loc;
107 SQStackInfos si;
108 if (SQ_SUCCEEDED(sq_stackinfos(vm, 1, &si))) {
109 if (si.source) loc.source = si.source;
110 loc.line = static_cast<int>(si.line);
111 if (si.funcname) loc.function = si.funcname;
112 }
113 return loc;
114}
115
116} // namespace
117
119 static DevTool inst;
120 return inst;
121}
122
123void DevTool::attach(ssq::VM& vm, bool sampleLocals) { attach(vm.getHandle(), sampleLocals); }
124
125void DevTool::installRenderTracer() {
126 renderFlow_.clear();
127 eve::debug::setRenderTracer(&renderFlow_);
128 renderTraceEnabled_ = true;
129}
130
131void DevTool::uninstallRenderTracer() {
132 if (eve::debug::renderTracer() == &renderFlow_) eve::debug::setRenderTracer(nullptr);
133 renderTraceEnabled_ = false;
134}
135
137 if (on)
138 installRenderTracer();
139 else
140 uninstallRenderTracer();
141}
142
143void DevTool::attach(HSQUIRRELVM vm, bool sampleLocals) {
144 if (!vm) return;
145 detach();
146 vm_ = vm;
147 sampleLocals_ = sampleLocals;
148 graph_.clear();
149 localSnap_.clear();
150 lastReport_.clear();
151
153 Debugger::instance().setPump([this]() { pumpWhilePaused(); });
154
155 sq_enabledebuginfo(vm_, SQTrue);
156 sq_setnativedebughook(vm_, nativeDebugHook);
157 // Route uncaught script errors into the debugger (break-on-error aware).
158 sq_newclosure(vm_, runtimeErrorHook, 0);
159 sq_seterrorhandler(vm_);
160 g_active = this;
161 installRenderTracer();
162
164 ConsolePanel::instance().addLog("info", "DevTools attached");
165}
166
168 stopDap();
169 stopMcp();
171 if (vm_) {
172 sq_setnativedebughook(vm_, nullptr);
173 // Leave debuginfo enabled; harmless for subsequent runs on same VM.
174 }
175 if (g_active == this) g_active = nullptr;
177 vm_ = nullptr;
178 localSnap_.clear();
179 uninstallRenderTracer();
180}
181
182int DevTool::startDap(uint16_t port) { return DebugAdapter::instance().listen(port); }
183
185
186int DevTool::startMcp(uint16_t port) { return McpServer::instance().listen(port); }
187
189
191
193
195
200
202
204
206 try {
207 ssq::Table eveTbl = vm.find("eve").toTable();
208 ssq::Table dev = eveTbl.addTable("dev");
209
210 dev.addFunc("pause", [this]() {
212 dap().notifyStopped(PauseReason::PauseKey, debugger().pauseLocation());
213 });
214 // Note: Squirrel reserves `resume` (generators) and `continue` (loops),
215 // so the script binding cannot be named either of those.
216 dev.addFunc("continueRun", [this]() {
217 debugger().resume();
219 });
220 dev.addFunc("togglePause", [this]() {
221 if (debugger().isPaused()) {
222 debugger().resume();
224 } else {
226 dap().notifyStopped(PauseReason::PauseKey, debugger().pauseLocation());
227 }
228 });
229 dev.addFunc("isPaused", [this]() { return debugger().isPaused(); });
230 dev.addFunc("stepFrame", [this]() {
233 });
234 // stepInto / stepOver / stepOut — primary script stepping (DAP F11 / F10 / Shift+F11).
235 dev.addFunc("stepInto", [this]() {
236 debugger().stepInto();
238 });
239 dev.addFunc("stepOver", [this]() {
240 debugger().stepOver();
242 });
243 dev.addFunc("stepOut", [this]() {
244 debugger().stepOut();
246 });
247 // Historical alias for stepInto.
248 dev.addFunc("stepLine", [this]() {
249 debugger().stepLine();
251 });
252 // Convenience: stepOver when mid-script, else one frame.
253 dev.addFunc("step", [this]() {
254 debugger().step();
256 });
257 dev.addFunc("shouldRunUpdate", [this]() { return debugger().shouldRunUpdate(); });
258 dev.addFunc("notifyFrameDone", [this]() {
259 const bool wasStep = debugger().mode() == RunMode::StepFrame;
261 if (wasStep || debugger().isPaused())
262 dap().notifyStopped(debugger().lastPauseReason(), debugger().pauseLocation());
263 });
264 dev.addFunc("poll", [this]() { poll(); });
265
266 dev.addFunc("setBreakpoint", [this](std::string source, int line) {
267 return debugger().setBreakpoint(std::move(source), line, true);
268 });
269 dev.addFunc("clearBreakpoint", [this](std::string source, int line) {
270 return debugger().clearBreakpoint(std::move(source), line);
271 });
272 dev.addFunc("clearBreakpoints", [this]() { debugger().clearBreakpoints(); });
273
274 dev.addFunc("addWatch", [this](std::string expr) { debugger().addWatch(std::move(expr)); });
275 dev.addFunc("removeWatch", [this](std::string expr) { return debugger().removeWatch(expr); });
276 dev.addFunc("clearWatches", [this]() { debugger().clearWatches(); });
277 dev.addFunc("eval", [this](std::string expr) {
278 auto info = debugger().evaluate(expr);
279 return info.value;
280 });
281 dev.addFunc("reportError", [this](std::string msg) {
282 return notifyError(msg.empty() ? std::string("script error") : std::move(msg));
283 });
284 dev.addFunc("setBreakOnError", [this](bool on) { debugger().setBreakOnError(on); });
285 dev.addFunc("breakOnError", [this]() { return debugger().breakOnError(); });
286 dev.addFunc("setBreakpointsEnabled", [this](bool on) {
288 });
289 dev.addFunc("breakpointsEnabled", [this]() { return debugger().breakpointsEnabled(); });
290 dev.addFunc("lastError", [this]() { return lastError(); });
291 dev.addFunc("profileReport", [this]() { return formatProfileReport(); });
292 dev.addFunc("profileClear", [this]() { profileClear(); });
293 dev.addFunc("registerSource", [this](std::string name, std::string content) {
294 dap().registerSource(std::move(name), std::move(content));
295 });
296
297 dev.addFunc("markStateRoot",
298 [](std::string name) { Snapshot::instance().markRoot(std::move(name)); });
299 dev.addFunc("unmarkStateRoot",
300 [](std::string name) { Snapshot::instance().unmarkRoot(name); });
301 dev.addFunc("clearStateRoots", []() { Snapshot::instance().clearRoots(); });
302 dev.addFunc("stateRoots", [this]() { return snapshot().rootsFor(vm_); });
303 dev.addFunc("saveSnapshot", [this](std::string path) {
304 std::string err;
305 const bool ok = snapshot().saveFile(vm_, path, &err);
306 if (!ok) return std::string("error:") + err;
307 return std::string("ok");
308 });
309 dev.addFunc("loadSnapshot", [this](std::string path) {
310 std::string err;
311 const bool ok = snapshot().loadFile(vm_, path, &err);
312 if (!ok) return std::string("error:") + err;
313 return std::string("ok");
314 });
315 dev.addFunc("captureSnapshot", [this]() {
316 std::string err;
317 return snapshot().capture(vm_, &err);
318 });
319 dev.addFunc("restoreSnapshot", [this](std::string json) {
320 std::string err;
321 const bool ok = snapshot().restore(vm_, json, &err);
322 if (!ok) return std::string("error:") + err;
323 return std::string("ok");
324 });
325 dev.addFunc("beginStateReload", [this]() {
326 std::string err;
327 if (!ReloadSession::instance().begin(vm_, &err)) return std::string("error:") + err;
328 return std::string("");
329 });
330 dev.addFunc("commitStateReload", [this]() {
331 std::string err;
332 if (!ReloadSession::instance().commit(vm_, &err)) return std::string("error:") + err;
333 return std::string("");
334 });
335 dev.addFunc("abortStateReload", [this]() {
336 std::string err;
337 if (!ReloadSession::instance().abort(vm_, &err)) return std::string("error:") + err;
338 return std::string("");
339 });
340
341 // AI / MCP surface (DevTools panel + agent session log).
342 ssq::Table ai = dev.addTable("ai");
343 ai.addFunc("status", []() { return AiPanel::instance().statusLine(); });
344 ai.addFunc("isVisible", []() { return AiPanel::instance().isVisible(); });
345 ai.addFunc("setVisible", [](bool on) { AiPanel::instance().setVisible(on); });
346 ai.addFunc("toggleVisible", []() { AiPanel::instance().toggleVisible(); });
347 ai.addFunc("note", [](std::string text) {
348 AiPanel::instance().addNote(std::move(text));
349 return std::string("ok");
350 });
351 ai.addFunc("log", []() { return AiPanel::instance().formatLog(64); });
352 ai.addFunc("clearLog", []() { AiPanel::instance().clearLog(); });
353 ai.addFunc("mcpPort", []() { return AiPanel::instance().mcpPort(); });
354 ai.addFunc("mcpConnected", []() { return AiPanel::instance().mcpConnected(); });
355 ai.addFunc("draw", [this]() { drawAiPanel(); });
356
357 // Runtime console / log / REPL surface.
358 ssq::Table consoleTbl = dev.addTable("console");
359 consoleTbl.addFunc("log", [](std::string text) {
360 ConsolePanel::instance().addLog("info", std::move(text));
361 return std::string("ok");
362 });
363 consoleTbl.addFunc("info", [](std::string text) {
364 ConsolePanel::instance().addInfo(std::move(text));
365 return std::string("ok");
366 });
367 consoleTbl.addFunc("warn", [](std::string text) {
368 ConsolePanel::instance().addWarn(std::move(text));
369 return std::string("ok");
370 });
371 consoleTbl.addFunc("error", [](std::string text) {
372 ConsolePanel::instance().addError(std::move(text));
373 return std::string("ok");
374 });
375 consoleTbl.addFunc("debug", [](std::string text) {
376 ConsolePanel::instance().addLog("debug", std::move(text));
377 return std::string("ok");
378 });
379 consoleTbl.addFunc("eval", [this](std::string expr) { return console().eval(std::move(expr)); });
380 consoleTbl.addFunc("clear", []() {
382 return std::string("ok");
383 });
384 consoleTbl.addFunc("recent", [](int n) {
385 const auto lines = ConsolePanel::instance().recent(
386 static_cast<size_t>(n > 0 ? n : 64));
387 std::vector<std::string> out;
388 out.reserve(lines.size());
389 for (const auto& l : lines) out.push_back("[" + l.timestamp + "] " + l.level + " | " + l.text);
390 return out;
391 });
392 consoleTbl.addFunc("format", [](int n) {
393 return ConsolePanel::instance().format(static_cast<size_t>(n > 0 ? n : 64));
394 });
395 consoleTbl.addFunc("isVisible", []() { return ConsolePanel::instance().isVisible(); });
396 consoleTbl.addFunc("setVisible", [](bool on) { ConsolePanel::instance().setVisible(on); });
397 consoleTbl.addFunc("toggleVisible", []() { ConsolePanel::instance().toggleVisible(); });
398 consoleTbl.addFunc("draw", [this]() { drawConsolePanel(); });
399 } catch (...) {
400 // If eve table missing, skip — attach still useful for C++/DAP/MCP.
401 }
402}
403
404void DevTool::handleDebugEvent(HSQUIRRELVM vm, int type, const char* source, int line,
405 const char* funcname) {
406 SourceLoc loc;
407 loc.source = source ? source : "";
408 loc.line = line;
409 loc.function = funcname ? funcname : "";
410
411 // Squirrel passes 'l' / 'c' / 'r' as event type characters.
412 switch (type) {
413 case 'c':
414 graph_.onCall(loc, loc.function);
415 localSnap_.erase(static_cast<int>(graph_.currentStack().size()));
416 profileCall(loc.function);
417 break;
418 case 'r':
419 graph_.onReturn(loc, loc.function);
420 // Drop snapshot for the frame that just returned.
421 localSnap_.erase(static_cast<int>(graph_.currentStack().size()) + 1);
422 profileReturn();
423 break;
424 case 'l':
425 graph_.onLine(loc);
426 profileLine(loc.function);
427 if (sampleLocals_) sampleFrameLocals(vm, loc);
428 if (Debugger::instance().onScriptLine(loc)) {
429 dap().notifyStopped(Debugger::instance().lastPauseReason(), loc);
431 Debugger::instance().waitWhilePaused([this]() { pumpWhilePaused(); });
432 }
433 break;
434 default:
435 break;
436 }
437}
438
439void DevTool::handleDebugHotkey(const std::string& key) {
440 if (key.empty()) return;
441 Debugger& dbg = debugger();
442 if (key == "F5") {
443 if (!dbg.isPaused()) return;
444 dbg.resume();
446 return;
447 }
448 if (key == "F10") {
449 if (!dbg.isPaused()) return;
450 dbg.stepOver();
452 return;
453 }
454 if (key == "F11") {
455 if (!dbg.isPaused()) return;
456 dbg.stepInto();
458 return;
459 }
460 if (key == "F8") {
461 if (!dbg.isPaused()) return;
462 dbg.stepFrame();
464 return;
465 }
466 if (key == "Pause") {
467 // Already inside a script pause — Pause resumes (toggle).
468 if (!dbg.isPaused()) return;
469 dbg.resume();
471 }
472}
473
474void DevTool::pumpWhilePaused() {
475 poll(); // DAP continue / next / pause
476
477 auto* ev = eve::ModuleManager::getInstance<eve::event::Event>("Event");
478 if (!ev) return;
479 ev->pump();
480
481 // Consume debug hotkeys so F5/F8/F10/F11 work while blocked in the line hook;
482 // re-queue everything else for the main loop after resume.
483 std::vector<eve::event::Message*> keep;
484 while (eve::event::Message* msg = ev->poll()) {
485 bool handled = false;
486 if (msg->name == "keypressed" && !msg->args.empty() &&
487 msg->args[0].type == eve::event::Variant::Type::String) {
488 const std::string& key = msg->args[0].s;
489 if (key == "F5" || key == "F8" || key == "F10" || key == "F11" || key == "Pause") {
490 handleDebugHotkey(key);
491 handled = true;
492 }
493 }
494 if (handled)
495 delete msg;
496 else
497 keep.push_back(msg);
498 }
499 for (eve::event::Message* msg : keep) ev->push(msg);
500}
501
502void DevTool::sampleFrameLocals(HSQUIRRELVM vm, const SourceLoc& loc) {
503 if (!vm) return;
504 const int depth = static_cast<int>(graph_.currentStack().size());
505 auto& prev = localSnap_[depth];
506 std::unordered_map<std::string, std::string> cur;
507
508 // Level 0 = current script frame. The native debug hook is invoked as a
509 // direct C call (no CallInfo pushed), so the top of the Squirrel call
510 // stack is the script frame that triggered the line event.
511 const SQUnsignedInteger level = 0;
512 for (SQUnsignedInteger n = 0;; ++n) {
513 const SQInteger top = sq_gettop(vm);
514 const SQChar* name = sq_getlocal(vm, level, n);
515 if (!name) {
516 sq_settop(vm, top);
517 break;
518 }
519 const std::string key(name);
520 // Skip temporaries / this binding noise if desired; keep "this" for OO flow.
521 const std::string val = describeSqValue(vm, -1);
522 sq_settop(vm, top);
523
524 cur[key] = val;
525 auto it = prev.find(key);
526 if (it == prev.end() || it->second != val) {
527 // First sighting or value change ⇒ definition for the data-flow graph.
528 graph_.onDef(loc, key);
529 }
530 }
531 prev.swap(cur);
532}
533
534SliceResult DevTool::analyzeError(const std::string& errorMessage,
535 const std::vector<std::string>& hintVars) const {
537 c.variables = hintVars;
538
539 if (vm_) {
540 SQStackInfos si;
541 if (SQ_SUCCEEDED(sq_stackinfos(vm_, 1, &si))) {
542 if (si.source) c.loc.source = si.source;
543 c.loc.line = static_cast<int>(si.line);
544 if (si.funcname) c.loc.function = si.funcname;
545 } else {
546 c.loc = stackTopLoc(vm_);
547 }
548 }
549
550 // Prefer last recorded line if stackinfos unavailable.
551 if (c.loc.empty() && !graph_.events().empty()) {
552 const auto ev = graph_.events();
553 for (size_t i = ev.size(); i-- > 0;) {
554 if (ev[i].kind == TraceKind::Line || ev[i].kind == TraceKind::Use ||
555 ev[i].kind == TraceKind::Def) {
556 c.loc = ev[i].loc;
557 break;
558 }
559 }
560 }
561
562 (void)errorMessage;
563 return graph_.sliceBackward(c);
564}
565
566std::string DevTool::formatError(const std::string& errorMessage,
567 const std::vector<std::string>& hintVars) const {
569 c.variables = hintVars;
570 if (vm_) {
571 SQStackInfos si;
572 if (SQ_SUCCEEDED(sq_stackinfos(vm_, 1, &si))) {
573 if (si.source) c.loc.source = si.source;
574 c.loc.line = static_cast<int>(si.line);
575 if (si.funcname) c.loc.function = si.funcname;
576 }
577 }
578 if (c.loc.empty() && !graph_.events().empty()) {
579 const auto ev = graph_.events();
580 for (size_t i = ev.size(); i-- > 0;) {
581 if (!ev[i].loc.empty()) {
582 c.loc = ev[i].loc;
583 break;
584 }
585 }
586 }
587 return graph_.formatErrorReport(errorMessage, c);
588}
589
590void DevTool::markErrorUses(const SourceLoc& loc,
591 const std::vector<std::string>& hintVars) {
592 if (!vm_) return;
593 SourceLoc site = loc;
594 if (site.empty()) {
595 SQStackInfos si;
596 if (SQ_SUCCEEDED(sq_stackinfos(vm_, 1, &si))) {
597 if (si.source) site.source = si.source;
598 site.line = static_cast<int>(si.line);
599 if (si.funcname) site.function = si.funcname;
600 }
601 }
602 graph_.onLine(site);
603
604 const SQUnsignedInteger level = 0;
605 for (SQUnsignedInteger n = 0;; ++n) {
606 const SQInteger top = sq_gettop(vm_);
607 const SQChar* name = sq_getlocal(vm_, level, n);
608 if (!name) {
609 sq_settop(vm_, top);
610 break;
611 }
612 const std::string key(name);
613 sq_settop(vm_, top);
614 if (!hintVars.empty()) {
615 bool wanted = false;
616 for (const auto& h : hintVars) {
617 if (h == key) {
618 wanted = true;
619 break;
620 }
621 }
622 if (!wanted) continue;
623 }
624 graph_.onUse(site, key);
625 }
626}
627
628std::string DevTool::notifyError(const std::string& errorMessage,
629 const std::vector<std::string>& hintVars) {
630 SourceLoc site;
631 if (vm_) {
632 // When called from the uncaught-error hook, level 0 is the native hook
633 // itself and level 1 is the throwing script frame, so this already
634 // lands on the exact throw site (before the stack unwinds). When called
635 // from a script catch (eve.dev.reportError), level 1 is the catch
636 // statement that reported the error (the game script when load.nut
637 // calls the native reporter directly).
638 SQStackInfos si;
639 if (SQ_SUCCEEDED(sq_stackinfos(vm_, 1, &si))) {
640 if (si.source) site.source = si.source;
641 site.line = static_cast<int>(si.line);
642 if (si.funcname) site.function = si.funcname;
643 }
644 }
645 markErrorUses(site, hintVars);
646 // Ensure the error is marked on the render flow even if Exception ctor
647 // already did (idempotent append of another Error node is fine).
648 if (renderTraceEnabled_) renderFlow_.error(errorMessage.c_str());
649
650 std::string report;
651 if (vm_ || !graph_.events().empty()) report += formatError(errorMessage, hintVars);
652 if (renderTraceEnabled_ && !renderFlow_.events().empty()) {
653 if (!report.empty()) report += "\n";
654 report += renderFlow_.formatErrorReport(errorMessage);
655 }
656 if (report.empty()) report = std::string("Error: ") + errorMessage + "\n";
657 lastReport_ = report;
658 lastError_ = errorMessage;
659
660 RenderVision::instance().notifyPending("error", site.source, site.line);
661 if (debugger().breakOnError()) {
662 // Godot "Break on Error": stop at the reported site. Block inside the
663 // hook so the IDE sees a stable frame instead of the next executed line.
665 dap().notifyStopped(PauseReason::Exception, site, errorMessage);
666 debugger().waitWhilePaused([this]() { pumpWhilePaused(); });
667 }
668
669 return lastReport_;
670}
671
673
674std::size_t mcpCallgraphEvents() { return DevTool::instance().graph().events().size(); }
675
677 return DevTool::instance().graph().currentStack().size();
678}
679
680const std::string& mcpLastReport() { return DevTool::instance().lastReport(); }
681
682std::string mcpFormatError(const std::string& message) {
683 return DevTool::instance().formatError(message);
684}
685
686void DevTool::profileCall(const std::string& func) {
687 profStack_.emplace_back(func, std::chrono::steady_clock::now());
688}
689
690void DevTool::profileLine(const std::string& func) {
691 const auto now = std::chrono::steady_clock::now();
692 if (!profStack_.empty()) {
693 auto& top = profStack_.back();
694 profile_[top.first].ns +=
695 std::chrono::duration_cast<std::chrono::nanoseconds>(now - top.second).count();
696 top.second = now;
697 }
698 if (!func.empty()) ++profile_[func].lines;
699}
700
701void DevTool::profileReturn() {
702 const auto now = std::chrono::steady_clock::now();
703 if (profStack_.empty()) return;
704 auto& top = profStack_.back();
705 profile_[top.first].ns +=
706 std::chrono::duration_cast<std::chrono::nanoseconds>(now - top.second).count();
707 ++profile_[top.first].calls;
708 profStack_.pop_back();
709}
710
711std::string DevTool::formatProfileReport() const {
712 std::ostringstream oss;
713 oss << "function, calls, lines, time_ms\n";
714 std::vector<std::pair<std::string, ProfileEntry>> rows(profile_.begin(), profile_.end());
715 std::sort(rows.begin(), rows.end(), [](const auto& a, const auto& b) {
716 return a.second.ns > b.second.ns;
717 });
718 for (const auto& [name, e] : rows) {
719 oss << name << ", " << e.calls << ", " << e.lines << ", "
720 << (static_cast<double>(e.ns) / 1e6) << "\n";
721 }
722 return oss.str();
723}
724
725} // namespace eve::dev
struct SQVM * HSQUIRRELVM
int line
Tok kind
HSQUIRRELVM vm
Definition ECS.cpp:20
std::string type
glm::vec3 n
Definition Grass.cpp:64
int h
float depth
uint32_t a
uint32_t b
uint32_t c
int idx
const char * name
Definition RockMesh.cpp:21
int v
uint32_t s
Definition Weather.cpp:28
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
uint32_t onCall(const SourceLoc &loc, const std::string &funcName={})
EventsView events() const
uint32_t onReturn(const SourceLoc &loc, const std::string &funcName={})
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:83
void waitWhilePaused(const std::function< void()> &pump={})
Block until resume/step/detach (processes external poll callbacks).
Definition Debugger.cpp:572
void setBreakOnError(bool on)
Break on script errors (Godot "Break on Error"). Default off.
Definition Debugger.hpp:168
void stepInto()
Enter calls: stop on the next script line at any depth.
Definition Debugger.cpp:419
void refreshWatches()
Re-evaluate all watches against current VM (paused preferred).
Definition Debugger.cpp:663
void notifyFrameDone()
After a frame when StepFrame was active → return to Paused.
Definition Debugger.cpp:453
void stepOut()
Finish current function: stop when stack depth drops.
Definition Debugger.cpp:423
bool isPaused() const
Definition Debugger.hpp:112
RunMode mode() const
Definition Debugger.hpp:113
bool removeWatch(const std::string &expression)
Definition Debugger.cpp:644
void stepOver()
Skip calls: stop on the next line at ≤ current stack depth.
Definition Debugger.cpp:421
void addWatch(std::string expression)
Definition Debugger.cpp:635
bool shouldRunUpdate()
Frame loop: true ⇒ call eve_update this frame. Consumes StepFrame.
Definition Debugger.cpp:443
void pause(PauseReason reason=PauseReason::PauseKey)
Definition Debugger.cpp:368
bool clearBreakpoint(std::string source, int line)
Definition Debugger.cpp:603
void setBreakpointsEnabled(bool on)
Master switch for all breakpoints ("skip all breakpoints").
Definition Debugger.hpp:171
int setBreakpoint(std::string source, int line, bool enabled=true, std::string condition={})
Definition Debugger.cpp:581
void setPump(PumpFn pump)
Definition Debugger.hpp:179
static Debugger & instance()
Definition Debugger.cpp:304
void step()
Convenience: script stepOver when mid-hook; otherwise one game frame. Prefer stepInto/stepOver/stepOu...
Definition Debugger.cpp:432
bool breakOnError() const
Definition Debugger.hpp:169
void attach(HSQUIRRELVM vm)
Definition Debugger.cpp:351
void stepLine()
Alias for stepInto (historical name).
Definition Debugger.hpp:106
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,...
Definition Debugger.cpp:872
void clearBreakpoints(const std::string &source={})
Definition Debugger.cpp:614
bool breakpointsEnabled() const
Definition Debugger.hpp:172
Platform-level script + render debugger / dynamic slicer front-end.
Definition DevTool.hpp:43
const std::string & lastReport() const
Definition DevTool.hpp:95
void enableRenderTrace(bool on=true)
Enable render-flow tracing without a Squirrel VM (C++ / unit tests).
Definition DevTool.cpp:136
int startDap(uint16_t port)
Start DAP server; returns bound port (0 on failure).
Definition DevTool.cpp:182
void handleDebugEvent(HSQUIRRELVM vm, int type, const char *source, int line, const char *funcname)
Definition DevTool.cpp:404
Debugger & debugger()
Definition DevTool.hpp:82
McpServer & mcp()
Definition DevTool.cpp:190
void attach(ssq::VM &vm, bool sampleLocals=true)
Attach script tracer to VM and enable render tracing.
Definition DevTool.cpp:123
void exposeScriptApi(ssq::VM &vm)
Expose eve.dev script API (pause/breakpoint/watch/snapshot/AI).
Definition DevTool.cpp:205
const std::string & lastError() const
Definition DevTool.hpp:96
Snapshot & snapshot()
Definition DevTool.hpp:83
CallGraph & graph()
Definition DevTool.hpp:78
bool sampleLocals() const
Definition DevTool.hpp:75
int startMcp(uint16_t port)
Start MCP server for AI tooling; returns bound port (0 on failure).
Definition DevTool.cpp:186
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:628
ConsolePanel & console()
Definition DevTool.cpp:194
AiPanel & ai()
Definition DevTool.cpp:192
void drawConsolePanel()
Draw DevTools console ImGui panel when visible (call from UI/frame loop).
Definition DevTool.cpp:203
std::string formatProfileReport() const
Definition DevTool.cpp:711
bool isAttached() const
Definition DevTool.hpp:73
std::string formatError(const std::string &errorMessage, const std::vector< std::string > &hintVars={}) const
Definition DevTool.cpp:566
void drawAiPanel()
Draw DevTools AI ImGui panel when visible (call from UI/frame loop).
Definition DevTool.cpp:201
static DevTool & instance()
Definition DevTool.cpp:118
SliceResult analyzeError(const std::string &errorMessage, const std::vector< std::string > &hintVars={}) const
Definition DevTool.cpp:534
DebugAdapter & dap()
Definition DevTool.hpp:84
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()
std::string formatErrorReport(const std::string &errorMessage, const RenderSliceCriterion &c={}) const
void error(const char *message) override
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()
std::vector< std::string > rootsFor(HSQUIRRELVM vm) const
Marked roots, or heuristic roots when none marked (script-facing).
Definition Snapshot.hpp:48
bool restore(HSQUIRRELVM vm, const std::string &json, std::string *error=nullptr) const
Definition Snapshot.cpp:468
std::string capture(HSQUIRRELVM vm, std::string *error=nullptr) const
Capture marked roots, or heuristic roots when none marked.
Definition Snapshot.cpp:401
bool loadFile(HSQUIRRELVM vm, const std::string &path, std::string *error=nullptr) const
Definition Snapshot.cpp:494
void markRoot(std::string name)
Definition Snapshot.cpp:292
bool saveFile(HSQUIRRELVM vm, const std::string &path, std::string *error=nullptr) const
Definition Snapshot.cpp:482
static Snapshot & instance()
Definition Snapshot.cpp:285
void unmarkRoot(const std::string &name)
Definition Snapshot.cpp:300
A named event carrying an ordered list of Variant payloads. Pushed messages are heap-allocated; the q...
Definition Event.h:56
void setRenderTracer(IRenderTracer *tracer)
IRenderTracer * renderTracer()
std::size_t mcpCallgraphStackDepth()
Definition DevTool.cpp:676
std::size_t mcpCallgraphEvents()
Definition DevTool.cpp:674
std::string mcpFormatError(const std::string &message)
Definition DevTool.cpp:682
const std::string & mcpLastReport()
Definition DevTool.cpp:680
bool mcpDevAttached()
Thin hooks so McpServer.cpp need not include DevTool.hpp (avoids a cycle).
Definition DevTool.cpp:672
std::string formatScriptError(const ScriptErrorContext &ctx)
Formats a context into a human-readable multi-line report.
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.
Definition Build.cpp:11
Criterion for a Weiser-style dynamic backward slice.
Definition CallGraph.hpp:57
std::vector< std::string > variables
Definition CallGraph.hpp:59
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
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