diff --git a/doc/PACKS.md b/doc/PACKS.md index fb8a9e5c..94ef0016 100644 --- a/doc/PACKS.md +++ b/doc/PACKS.md @@ -157,7 +157,12 @@ a table representing an enum with the following constants: \ ### other globals -* `DEBUG` set to true to get more error or debug output +* `DEBUG` set to true or an array of strings to get more error or debug output + * `false` or `nil`: disable everything + * `true`: enable everything + * `{'fps'}`: enable FPS output in console + * `{'errors'}`: enable more detailed error reporting + * `{'fps', 'errors', ...}`: enable multiple * `require` function, see [ScriptHost:LoadScript](#global-scripthost) diff --git a/src/core/tracker.cpp b/src/core/tracker.cpp index 822b5c98..285eb006 100644 --- a/src/core/tracker.cpp +++ b/src/core/tracker.cpp @@ -37,16 +37,36 @@ static const char* timeout_error_message = "Execution aborted. Limit reached."; int Tracker::luaErrorHandler(lua_State *L) { - // skip trace for certain errors unless running in debug mode (DEBUG == true) + // skip trace for certain errors unless running in debug mode (DEBUG == true or "errors" in DEBUG) if (lua_isstring(L, -1) && strstr(lua_tostring(L, -1), timeout_error_message)) { - lua_getglobal(L, "DEBUG"); - lua_pushboolean(L, true); - if (!lua_compare(L, -1, -2, LUA_OPEQ)) { - // return original error - lua_pop(L, 2); - return 1; + bool skip_trace; + int t = lua_getglobal(L, "DEBUG"); + if (t == LUA_TNIL) { + skip_trace = true; + } else if (t == LUA_TTABLE) { + lua_pushnil(L); + skip_trace = true; + while (lua_next(L, -2) != 0) { + if (strcmp(lua_tostring(L, -1), "errors") == 0) { + skip_trace = false; + lua_pop(L, 2); // key, value + break; + } + lua_pop(L, 1); // value + } + } else if (t == LUA_TBOOLEAN) { + lua_pushboolean(L, false); + if (lua_compare(L, -1, -2, LUA_OPEQ)) + skip_trace = true; + else + skip_trace = false; + lua_pop(L, 1); // true + } else { + skip_trace = false; } - lua_pop(L, 2); + lua_pop(L, 1); // DEBUG + if (skip_trace) + return 1; // original error } // generate and print trace, then return original error luaL_traceback(L, L, NULL, 1); diff --git a/src/poptracker.cpp b/src/poptracker.cpp index 3aec253b..ef1ec5ef 100644 --- a/src/poptracker.cpp +++ b/src/poptracker.cpp @@ -36,6 +36,81 @@ enum HotkeyID { HOTKEY_SHOW_HELP, }; +static char _globalStoreKey = 'k'; +static const uintptr_t globalStoreIndex = (uintptr_t)&_globalStoreKey; +static char _globalPopKey = 'k'; +static const uintptr_t globalPopIndex = (uintptr_t)&_globalPopKey; + +int PopTracker::global_index(lua_State *L) +{ + lua_rawgeti(L, LUA_REGISTRYINDEX, (lua_Integer)globalStoreIndex); + lua_pushvalue(L, -2); + lua_rawget(L, -2); + return 1; +} + +int PopTracker::global_newindex(lua_State *L) +{ + bool toStore = false; + if (lua_isstring(L, -2)) { + // filter out special keys + const char* key = lua_tostring(L, -2); + if (strcmp(key, "DEBUG") == 0) { + lua_rawgeti(L, LUA_REGISTRYINDEX, (lua_Integer)globalPopIndex); + PopTracker* pop = static_cast(lua_touserdata(L, -1)); + lua_pop(L, 1); // pop + if (!pop) { + luaL_error(L, "Lua state not initialized correctly!"); + } else if (lua_isboolean(L, -1) || lua_isnil(L, -1)) { + if (lua_toboolean(L, -1)) { + for (const char* flag: PopTracker::ALL_DEBUG_FLAGS) + pop->_debugFlags.insert(flag); + } else { + pop->_debugFlags.clear(); + } + } else if (lua_istable(L, -1)) { + pop->_debugFlags.clear(); + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + pop->_debugFlags.insert(lua_tostring(L, -1)); + lua_pop(L, 1); // value + } + } else if (lua_isstring(L, -1)) { + pop->_debugFlags.clear(); + pop->_debugFlags.insert(lua_tostring(L, -1)); + } else { + luaL_error(L, "Invalid assignment to global DEBUG"); + } + toStore = true; + } + } + if (toStore) { + // store into separate store so we receive updates + lua_rawgeti(L, LUA_REGISTRYINDEX, (lua_Integer)globalStoreIndex); + lua_insert(L, -3); + lua_rawset(L, -3); + } else { + // store into global object for speed optimization + lua_rawset(L, -3); + } + return 0; +} + +void PopTracker::global_wrap(lua_State *L, PopTracker* self) +{ + lua_pushglobaltable(L); + lua_createtable(L, 0, 2); + lua_pushcfunction(L, global_index); + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, global_newindex); + lua_setfield(L,-2, "__newindex"); + lua_setmetatable(L, -2); + lua_pop(L,1); + lua_newtable(L); + lua_rawseti(L, LUA_REGISTRYINDEX, (lua_Integer)globalStoreIndex); + lua_pushlightuserdata(L, self); + lua_rawseti(L, LUA_REGISTRYINDEX, (lua_Integer)globalPopIndex); +} PopTracker::PopTracker(int argc, char** argv, bool cli, const json& args) { @@ -172,6 +247,21 @@ PopTracker::PopTracker(int argc, char** argv, bool cli, const json& args) } else if (!_config["override_rule_exec_limit"].is_null()) _config["override_rule_exec_limit"] = nullptr; // clear invalid value + auto debugIt = _config.find("debug"); + if (debugIt == _config.end()) { + _config["debug"] = nullptr; + } else if (debugIt.value().is_boolean()) { + if (debugIt.value().get()) { + for (const char* flag: ALL_DEBUG_FLAGS) + _defaultDebugFlags.insert(flag); + } + } else if (debugIt.value().is_array()) { + try { + _defaultDebugFlags = debugIt.value().get>(); + } catch (...) {} + } + _debugFlags = _defaultDebugFlags; + saveConfig(); _ui = nullptr; // UI init moved to start() @@ -740,7 +830,8 @@ bool PopTracker::frame() td = std::chrono::duration_cast(now - _fpsTimer).count(); if (td >= 5000) { unsigned f = _frames*1000; f/=td; - printf("FPS:%4u (max %2dms)\n", f, _maxFrameTime); + if (_debugFlags.count("fps") || _maxFrameTime > 1000) + printf("FPS:%4u (max %2dms)\n", f, _maxFrameTime); _frames = 0; _fpsTimer = now; _maxFrameTime = 0; @@ -940,6 +1031,8 @@ void PopTracker::unloadTracker() _scriptHost = nullptr; _pack = nullptr; _archipelago = nullptr; + + _debugFlags = _defaultDebugFlags; } bool PopTracker::loadTracker(const std::string& pack, const std::string& variant, bool loadAutosave) @@ -1029,7 +1122,12 @@ bool PopTracker::loadTracker(const std::string& pack, const std::string& variant lua_pushstring(_L, "Pack"); lua_pushlightuserdata(_L, _pack); lua_settable(_L, LUA_REGISTRYINDEX); - + + // store native debug flags set in registry + lua_pushstring(_L, "DebugFlags"); + lua_pushlightuserdata(_L, &_debugFlags); + lua_settable(_L, LUA_REGISTRYINDEX); + printf("Creating Script Host...\n"); _scriptHost = new ScriptHost(_pack, _L, _tracker); printf("Registering in Lua...\n"); @@ -1095,6 +1193,14 @@ bool PopTracker::loadTracker(const std::string& pack, const std::string& variant {"Cleared", AccessibilityLevel::CLEARED}, }).Lua_SetGlobal(_L, "AccessibilityLevel"); + printf("Hooking Lua globals...\n"); + global_wrap(_L, this); + if (_debugFlags.empty()) + lua_pushnil(_L); + else + json_to_lua(_L, _debugFlags); + lua_setglobal(_L, "DEBUG"); + printf("Updating UI\n"); _win->setTracker(_tracker); if (auto at = _scriptHost->getAutoTracker()) { diff --git a/src/poptracker.h b/src/poptracker.h index 8b5676fe..cb136870 100644 --- a/src/poptracker.h +++ b/src/poptracker.h @@ -48,6 +48,8 @@ class PopTracker final : public App { std::string _exportDir; std::string _homePackDir; std::string _appPackDir; + std::set _debugFlags; + std::set _defaultDebugFlags; unsigned _frames = 0; unsigned _maxFrameTime = 0; @@ -75,6 +77,10 @@ class PopTracker final : public App { bool saveConfig(); + static int global_index(lua_State* L); + static int global_newindex(lua_State* L); + static void global_wrap(lua_State* L, PopTracker* self); + public: PopTracker(int argc, char** argv, bool cli=false, const json& args=nullptr); virtual ~PopTracker(); @@ -82,6 +88,8 @@ class PopTracker final : public App { bool ListPacks(PackManager::confirmation_callback confirm = nullptr, bool installable = true); bool InstallPack(const std::string& uid, PackManager::confirmation_callback confirm = nullptr); + static constexpr std::initializer_list ALL_DEBUG_FLAGS = {"errors", "fps"}; + static constexpr const char APPNAME[] = "PopTracker"; static constexpr const char VERSION_STRING[] = APP_VERSION_STRING; static const Version VERSION;