Files
EVEngine/src/engine/cmdline/Run.cpp
T
西风逍遥游andCursor Agent 2d7b9f9287 feat(devtools): script error tracing with dynamic slicer (#4)
* feat(devtools): add dynamic slicer for script error tracing

Record call/return/line and Def/Use data-flow in CallGraph, attach a
Squirrel native debug hook via DevTool, and emit a Weiser-style backward
slice report on failures when running with `eve run --debug`.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* fix(devtools): use one-in-one-out ring buffer for trace events

Replace half-window drops with a deque ring buffer: when full, each new
event evicts exactly one oldest event, with monotonic ids and index cleanup.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* feat(devtools): add render-pipeline error tracing

Introduce common RenderTrace hooks and a RenderFlow slicer so Graphics
passes/binds/draws are attributed on failure. Wire RenderSystem 2D/3D,
mark Exception sites, and merge render reports into DevTool --debug output.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* test(devtools): expand CallGraph and RenderFlow coverage

Add extensive unit tests for source locations, data-flow slicing,
ring-buffer eviction, nested passes/binds, Exception hooks, and RAII
scopes (54 cases / 202 assertions under ^devtools\.).

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-08-09 01:00:43 +08:00

149 lines
5.1 KiB
C++

#include "cmdline.h"
#include "scripts.h"
#include "common/Module.h"
#include "common/config.h"
#include "filesystem/Filesystem.h"
#if !defined(EVENGINE_ANDROID) && !defined(EVENGINE_IOS)
#include "devtools/DevTool.hpp"
#endif
#include <simplesquirrel/simplesquirrel.hpp>
#include <CLI11.hpp>
#include <string>
#include <filesystem>
#if defined(EVENGINE_ANDROID)
#include <android/log.h>
#define EVE_ANDROID_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "EVEngine", __VA_ARGS__)
#elif defined(EVENGINE_IOS)
#include <cstdio>
#define EVE_ANDROID_LOGE(...) do { fprintf(stderr, "EVEngine: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } while (0)
#else
#define EVE_ANDROID_LOGE(...) ((void)0)
#endif
using namespace std;
namespace eve::cmd
{
struct RunArgs : Handler {
std::string log_path, root_path;
bool no_window = false, debug = false;
void setup(CLI::App& app, std::shared_ptr<CLI::Formatter> formatter) override {
auto run = app.add_subcommand("run", "Run game under current path");
run->allow_extras()->formatter(formatter);
run->add_flag("--no-window", no_window, "Run script only, no window mode");
run->add_flag("--debug", debug, "debug mode");
run->add_option("-l,--log", log_path, "log messages into a file");
run->add_option("-r,--root", root_path, "give a entry script instead of using the system default one");
}
int parse(CLI::App& app, Cmdline& cmd) override {
auto run = app.get_subcommand("run");
if (run->parsed() || cmd.getArgc() == 1) {
std::string current_path = cmd.get_remaining(run);
if (root_path != "") {
ifstream ifs(root_path);
if (!ifs) {
cerr << "Cannot open root script: " << root_path << endl;
return -1;
}
std::string load_root((istreambuf_iterator<char>(ifs)), (istreambuf_iterator<char>()));
return cmd.Run(current_path, load_root, debug);
} else
return cmd.Run(current_path, load_content, debug);
}
return -1; // not handle
}
};
CMD_REG(RunArgs);
// create a new project
int Cmdline::Run(std::string path, std::string root, bool debug) {
try {
// Switch to the game directory so load.nut can dofile("config.nut") / "main.nut".
if (!path.empty() && path != ".") {
std::error_code ec;
std::filesystem::current_path(path, ec);
if (ec) {
cerr << "Cannot chdir to game path '" << path << "': " << ec.message() << endl;
EVE_ANDROID_LOGE("Cannot chdir to game path '%s': %s", path.c_str(), ec.message().c_str());
return 2;
}
}
// Mount game dir for PhysFS so relative watch/read resolve (hot reload).
{
auto *fs = eve::filesystem::Filesystem::create();
if (fs) {
std::error_code ec;
auto cwd = std::filesystem::current_path(ec);
if (!ec) {
// setSource only succeeds once; ignore failure if already mounted.
fs->setSource(cwd.string());
}
}
}
ssq::VM vm(2048, ssq::Libs::ALL);
ModuleManager::expose(vm);
#if !defined(EVENGINE_ANDROID) && !defined(EVENGINE_IOS)
if (debug) {
eve::dev::DevTool::instance().attach(vm, /*sampleLocals=*/true);
}
#else
(void)debug;
#endif
// Embedded default demo (src/scripts/demo.nut); load.nut runs it when no main.nut.
{
ssq::Table eve(vm.find("eve"));
eve.set("demoScript", std::string(demo_content ? demo_content : ""));
eve.set("asyncScript", std::string(async_content ? async_content : ""));
}
ssq::Script script = vm.compileSource(root.c_str());
vm.run(script);
#if !defined(EVENGINE_ANDROID) && !defined(EVENGINE_IOS)
if (debug) eve::dev::DevTool::instance().detach();
#endif
return 0;
} catch (const std::exception& e) {
#if !defined(EVENGINE_ANDROID) && !defined(EVENGINE_IOS)
if (debug) {
const std::string report =
eve::dev::DevTool::instance().notifyError(e.what());
cerr << report << endl;
eve::dev::DevTool::instance().detach();
} else {
cerr << "Run failed: " << e.what() << endl;
}
#else
cerr << "Run failed: " << e.what() << endl;
#endif
EVE_ANDROID_LOGE("Run failed: %s", e.what());
return 3;
} catch (...) {
#if !defined(EVENGINE_ANDROID) && !defined(EVENGINE_IOS)
if (debug) {
const std::string report =
eve::dev::DevTool::instance().notifyError("unknown exception");
cerr << report << endl;
eve::dev::DevTool::instance().detach();
} else {
cerr << "Run failed: unknown exception" << endl;
}
#else
cerr << "Run failed: unknown exception" << endl;
#endif
EVE_ANDROID_LOGE("Run failed: unknown exception");
return 3;
}
}
} // namespace eve