From 8f569699979808aeb12cf03f09801e596d604bd7 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 8 Oct 2025 21:35:37 +0300 Subject: [PATCH] add 'get-value' command --- res/scripts/stdmin.lua | 19 ++- src/devtools/DebuggingServer.cpp | 45 ++++++ src/devtools/DebuggingServer.hpp | 14 +- src/logic/scripting/lua/lua_extensions.cpp | 180 ++++++++++++++------- src/logic/scripting/lua/lua_util.hpp | 2 +- src/logic/scripting/lua/lua_wrapper.hpp | 4 +- 6 files changed, 195 insertions(+), 69 deletions(-) diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index adf78fcf..5854f428 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -1,6 +1,3 @@ --- Lua has no parallelizm, also _set_data does not call any lua functions so --- may be reused one global ffi buffer per lua_State - local breakpoints = {} local dbg_steps_mode = false local dbg_step_into_func = false @@ -9,6 +6,7 @@ local current_func local current_func_stack_size local _debug_getinfo = debug.getinfo +local _debug_getlocal = debug.getlocal -- 'return' hook not called for some functions -- todo: speedup @@ -64,8 +62,11 @@ local DBG_EVENT_RM_BREAKPOINT = 2 local DBG_EVENT_STEP = 3 local DBG_EVENT_STEP_INTO_FUNCTION = 4 local DBG_EVENT_RESUME = 5 +local DBG_EVENT_GET_VALUE = 6 local __pull_events = debug.__pull_events +local __sendvalue = debug.__sendvalue debug.__pull_events = nil +debug.__sendvalue = nil function debug.pull_events() local events = __pull_events() @@ -86,6 +87,16 @@ function debug.pull_events() elseif event[1] == DBG_EVENT_RESUME then dbg_steps_mode = false dbg_step_into_func = false + elseif event[1] == DBG_EVENT_GET_VALUE then + local _, value = _debug_getlocal(event[2] + 3, event[3]) + for _, key in ipairs(event[4]) do + if value == nil then + value = "error: index nil value" + break + end + value = value[key] + end + __sendvalue(value, event[2], event[3], event[4]) end end end @@ -107,6 +118,8 @@ function debug.remove_breakpoint(source, line) bps[source] = nil end +-- Lua has no parallelizm, also _set_data does not call any lua functions so +-- may be reused one global ffi buffer per lua_State local canvas_ffi_buffer local canvas_ffi_buffer_size = 0 diff --git a/src/devtools/DebuggingServer.cpp b/src/devtools/DebuggingServer.cpp index 9ef677e3..d6777db7 100644 --- a/src/devtools/DebuggingServer.cpp +++ b/src/devtools/DebuggingServer.cpp @@ -186,6 +186,27 @@ bool DebuggingServer::performCommand( breakpointEvents.push_back(DebuggingEvent { DebuggingEventType::RESUME, SignalEventDto {}}); return true; + } else if (type == "get-value") { + if (!map.has("frame") || !map.has("local") || !map.has("path")) + return false; + + int frame = map["frame"].asInteger(); + int localIndex = map["local"].asInteger(); + + ValuePath path; + for (const auto& segment : map["path"]) { + if (segment.isString()) { + path.emplace_back(segment.asString()); + } else { + path.emplace_back(static_cast(segment.asInteger())); + } + } + breakpointEvents.push_back(DebuggingEvent { + DebuggingEventType::GET_VALUE, GetValueEventDto { + frame, localIndex, std::move(path) + } + }); + return true; } else { logger.error() << "unsupported command '" << type << "'"; } @@ -204,6 +225,30 @@ void DebuggingServer::onHitBreakpoint(dv::value&& stackTrace) { engine.startPauseLoop(); } +void DebuggingServer::sendValue( + dv::value&& value, int frame, int local, ValuePath&& path +) { + auto pathValue = dv::list(); + for (const auto& segment : path) { + if (auto string = std::get_if(&segment)) { + pathValue.add(*string); + } else { + pathValue.add(std::get(segment)); + } + } + connection->send(dv::object({ + {"type", std::string("value")}, + {"frame", frame}, + {"local", local}, + {"path", std::move(pathValue)}, + {"value", std::move(value)}, + })); +} + +void DebuggingServer::pause() { + engine.startPauseLoop(); +} + void DebuggingServer::setClient(u64id_t client) { this->connection = std::make_unique(engine.getNetwork(), client); diff --git a/src/devtools/DebuggingServer.hpp b/src/devtools/DebuggingServer.hpp index d7be32cf..a4557a7a 100644 --- a/src/devtools/DebuggingServer.hpp +++ b/src/devtools/DebuggingServer.hpp @@ -43,6 +43,7 @@ namespace devtools { STEP, STEP_INTO_FUNCTION, RESUME, + GET_VALUE, }; struct BreakpointEventDto { @@ -53,9 +54,17 @@ namespace devtools { struct SignalEventDto { }; + using ValuePath = std::vector>; + + struct GetValueEventDto { + int frame; + int localIndex; + ValuePath path; + }; + struct DebuggingEvent { DebuggingEventType type; - std::variant data; + std::variant data; }; class DebuggingServer { @@ -65,6 +74,9 @@ namespace devtools { bool update(); void onHitBreakpoint(dv::value&& stackTrace); + void pause(); + + void sendValue(dv::value&& value, int frame, int local, ValuePath&& path); void setClient(u64id_t client); std::vector pullEvents(); diff --git a/src/logic/scripting/lua/lua_extensions.cpp b/src/logic/scripting/lua/lua_extensions.cpp index fc312820..e791a87d 100644 --- a/src/logic/scripting/lua/lua_extensions.cpp +++ b/src/logic/scripting/lua/lua_extensions.cpp @@ -9,6 +9,7 @@ #include "devtools/DebuggingServer.hpp" #include "logic/scripting/scripting.hpp" +using namespace devtools; using namespace scripting; static debug::Logger logger("lua-debug"); @@ -164,8 +165,52 @@ static int l_math_normal_random(lua::State* L) { constexpr inline int MAX_SHORT_STRING_LEN = 50; +static std::string get_short_value(lua::State* L, int idx, int type) { + switch (type) { + case LUA_TNIL: + return "nil"; + case LUA_TBOOLEAN: + return lua::toboolean(L, idx) ? "true" : "false"; + case LUA_TNUMBER: { + std::stringstream ss; + ss << lua::tonumber(L, idx); + return ss.str(); + } + case LUA_TSTRING: { + const char* str = lua::tostring(L, idx); + if (strlen(str) > MAX_SHORT_STRING_LEN) { + return std::string(str, MAX_SHORT_STRING_LEN); + } else { + return str; + } + } + case LUA_TTABLE: + return "{...}"; + case LUA_TFUNCTION: { + std::stringstream ss; + ss << "function: 0x" << std::hex << lua::topointer(L, idx); + return ss.str(); + } + case LUA_TUSERDATA: { + std::stringstream ss; + ss << "userdata: 0x" << std::hex << lua::topointer(L, idx); + return ss.str(); + } + case LUA_TTHREAD: { + std::stringstream ss; + ss << "thread: 0x" << std::hex << lua::topointer(L, idx); + return ss.str(); + } + default: { + std::stringstream ss; + ss << "cdata: 0x" << std::hex << lua::topointer(L, idx); + return ss.str(); + } + } +} + static dv::value collect_locals(lua::State* L, lua_Debug& frame) { - auto locals = dv::object(); + auto locals = dv::list(); int localIndex = 1; const char* name; @@ -175,68 +220,13 @@ static dv::value collect_locals(lua::State* L, lua_Debug& frame) { continue; } auto local = dv::object(); + local["name"] = name; + local["index"] = localIndex - 1; int type = lua::type(L, -1); - switch (type) { - case LUA_TNIL: - local["short"] = "nil"; - local["type"] = "nil"; - break; - case LUA_TBOOLEAN: - local["short"] = lua::toboolean(L, -1) ? "true" : "false"; - local["type"] = "boolean"; - break; - case LUA_TNUMBER: { - std::stringstream ss; - ss << lua::tonumber(L, -1); - local["short"] = ss.str(); - local["type"] = "number"; - break; - } - case LUA_TSTRING: { - const char* str = lua::tostring(L, -1); - if (strlen(str) > MAX_SHORT_STRING_LEN) { - local["short"] = std::string(str, MAX_SHORT_STRING_LEN); - } else { - local["short"] = str; - } - local["type"] = "string"; - break; - } - case LUA_TTABLE: - local["short"] = "{...}"; - local["type"] = "table"; - break; - case LUA_TFUNCTION: { - std::stringstream ss; - ss << "function: 0x" << std::hex << lua::topointer(L, -1); - local["short"] = ss.str(); - local["type"] = "function"; - break; - } - case LUA_TUSERDATA: { - std::stringstream ss; - ss << "userdata: 0x" << std::hex << lua::topointer(L, -1); - local["short"] = ss.str(); - local["type"] = "userdata"; - break; - } - case LUA_TTHREAD: { - std::stringstream ss; - ss << "thread: 0x" << std::hex << lua::topointer(L, -1); - local["short"] = ss.str(); - local["type"] = "thread"; - break; - } - default: { - std::stringstream ss; - ss << "cdata: 0x" << std::hex << lua::topointer(L, -1); - local["short"] = ss.str(); - local["type"] = "cdata"; - break; - } - } - locals[name] = std::move(local); + local["type"] = lua::type_name(L, type); + local["short"] = get_short_value(L, -1, type); + locals.add(std::move(local)); lua::pop(L); } return locals; @@ -279,6 +269,51 @@ static int l_debug_breakpoint(lua::State* L) { return 0; } +static int l_debug_sendvalue(lua::State* L) { + auto server = engine->getDebuggingServer(); + if (!server) { + return 0; + } + int frame = lua::tointeger(L, 2); + int local = lua::tointeger(L, 3); + + ValuePath path; + int pathSectors = lua::objlen(L, 4); + for (int i = 0; i < pathSectors; i++) { + lua::rawgeti(L, i + 1, 4); + if (lua::isstring(L, -1)) { + path.emplace_back(lua::tostring(L, -1)); + } else { + path.emplace_back(static_cast(lua::tointeger(L, -1))); + } + lua::pop(L); + } + + dv::value value = nullptr; + if (lua::istable(L, 1)) { + auto table = dv::object(); + + lua::pushnil(L); + while (lua::next(L, 1)) { + auto key = lua::tolstring(L, -2); + + int type = lua::type(L, -1); + table[std::string(key)] = dv::object({ + {"type", std::string(lua::type_name(L, type))}, + {"short", get_short_value(L, -1, type)}, + }); + lua::pop(L); + } + lua::pop(L); + value = std::move(table); + } else { + value = lua::tovalue(L, 1); + } + + server->sendValue(std::move(value), frame, local, std::move(path)); + return 0; +} + static int l_debug_pull_events(lua::State* L) { auto server = engine->getDebuggingServer(); if (!server) { @@ -296,12 +331,30 @@ static int l_debug_pull_events(lua::State* L) { lua::pushinteger(L, static_cast(event.type)); lua::rawseti(L, 1); - if (auto dto = std::get_if(&event.data)) { + if (auto dto = std::get_if(&event.data)) { lua::pushstring(L, dto->source); lua::rawseti(L, 2); lua::pushinteger(L, dto->line); lua::rawseti(L, 3); + } else if (auto dto = std::get_if(&event.data)) { + lua::pushinteger(L, dto->frame); + lua::rawseti(L, 2); + + lua::pushinteger(L, dto->localIndex); + lua::rawseti(L, 3); + + lua::createtable(L, dto->path.size(), 0); + for (int i = 0; i < dto->path.size(); i++) { + const auto& segment = dto->path[i]; + if (auto string = std::get_if(&segment)) { + lua::pushstring(L, *string); + } else { + lua::pushinteger(L, std::get(segment)); + } + lua::rawseti(L, i + 1); + } + lua::rawseti(L, 4); } lua::rawseti(L, i + 1); @@ -329,6 +382,9 @@ void initialize_libs_extends(lua::State* L) { lua::pushcfunction(L, lua::wrap); lua::setfield(L, "__pull_events"); + lua::pushcfunction(L, lua::wrap); + lua::setfield(L, "__sendvalue"); + lua::pop(L); } if (lua::getglobal(L, "math")) { diff --git a/src/logic/scripting/lua/lua_util.hpp b/src/logic/scripting/lua/lua_util.hpp index e72b56eb..a8fb246e 100644 --- a/src/logic/scripting/lua/lua_util.hpp +++ b/src/logic/scripting/lua/lua_util.hpp @@ -249,7 +249,7 @@ namespace lua { inline lua::Number tonumber(lua::State* L, int idx) { #ifndef NDEBUG if (lua_type(L, idx) != LUA_TNUMBER && !lua_isnoneornil(L, idx)) { - throw std::runtime_error("integer expected"); + throw std::runtime_error("number expected"); } #endif return lua_tonumber(L, idx); diff --git a/src/logic/scripting/lua/lua_wrapper.hpp b/src/logic/scripting/lua/lua_wrapper.hpp index f82f5bd4..df130af1 100644 --- a/src/logic/scripting/lua/lua_wrapper.hpp +++ b/src/logic/scripting/lua/lua_wrapper.hpp @@ -50,8 +50,8 @@ namespace lua { inline int type(lua::State* L, int idx) { return lua_type(L, idx); } - inline const char* type_name(lua::State* L, int idx) { - return lua_typename(L, idx); + inline const char* type_name(lua::State* L, int tp) { + return lua_typename(L, tp); } inline int rawget(lua::State* L, int idx = -2) { lua_rawget(L, idx);