* 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>
149 lines
5.1 KiB
C++
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
|