From 30b3c4845b611b257d8f5260ee06ad39adda3fc2 Mon Sep 17 00:00:00 2001 From: harrand Date: Mon, 28 Aug 2023 13:04:42 +0100 Subject: [PATCH] [lua] formalised lua api c++-side --- CMakeLists.txt | 1 + src/tz/lua/api.cpp | 72 ++++++++++++++++++++ src/tz/lua/api.hpp | 15 ++++- src/tz/lua/state.cpp | 154 +++++++++++++++++++++++-------------------- src/tz/lua/state.hpp | 9 +++ 5 files changed, 178 insertions(+), 73 deletions(-) create mode 100644 src/tz/lua/api.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 849f6cb4e4..fbf9ff3748 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -242,6 +242,7 @@ add_library(topaz STATIC src/tz/io/image.hpp # tz::lua + src/tz/lua/api.cpp src/tz/lua/api.hpp src/tz/lua/lua.hpp src/tz/lua/state.cpp diff --git a/src/tz/lua/api.cpp b/src/tz/lua/api.cpp new file mode 100644 index 0000000000..17293b5c01 --- /dev/null +++ b/src/tz/lua/api.cpp @@ -0,0 +1,72 @@ +#include "tz/lua/api.hpp" +#include "tz/dbgui/dbgui.hpp" +#include +#include + +extern "C" +{ +#include "lauxlib.h" +#include "lua.h" +#include "lualib.h" +} + +namespace tz::lua +{ + LUA_BEGIN(assert) + auto st = reinterpret_cast(s); + + bool b = state.stack_get_bool(1); + std::string stack = state.collect_stack(); + lua_Debug ar; + lua_getstack(st, 1, &ar); + lua_getinfo(st, "nSl", &ar); + if(!b && TZ_DEBUG) + { + tz::dbgui::add_to_lua_log("<>"); + } + tz::assert(b, "Lua Assertion Failure: ```lua\n\n%s\n\n```\nOn line %d\nStack:\n%s", ar.source, ar.currentline, stack.c_str()); + return 0; + LUA_END + + LUA_BEGIN(error) + auto st = reinterpret_cast(s); + lua_pushboolean(st, false); + LUA_FN_NAME(assert)(st); + state.stack_pop(); + return 0; + LUA_END + + LUA_BEGIN(print) + int nargs = state.stack_size(); + for(int i = 1; i <= nargs; i++) + { + std::string msg = state.stack_get_string(i); + tz::dbgui::add_to_lua_log(msg); + } + tz::dbgui::add_to_lua_log("\n"); + state.stack_pop(nargs); + return 0; + LUA_END + + void api_initialise(state& s) + { + s.assign_emptytable("tz"); + s.assign_func("tz.assert", LUA_FN_NAME(assert)); + s.assign_func("tz.error", LUA_FN_NAME(error)); + LUA_REGISTER_ONE(print, s); + s.assign_emptytable("tz.version"); + s.assign_emptytable("thread"); + + std::ostringstream sstr; + sstr << std::this_thread::get_id(); + s.assign_string("thread.id", sstr.str()); + s.assign_string("LUA_PATH", std::filesystem::current_path().generic_string()); + + tz::version ver = tz::get_version(); + + s.assign_uint("tz.version.major", ver.major); + s.assign_uint("tz.version.minor", ver.minor); + s.assign_uint("tz.version.patch", ver.patch); + s.assign_string("tz.version.string", ver.to_string()); + } +} \ No newline at end of file diff --git a/src/tz/lua/api.hpp b/src/tz/lua/api.hpp index 0e253f4e63..e0b6d5c323 100644 --- a/src/tz/lua/api.hpp +++ b/src/tz/lua/api.hpp @@ -3,10 +3,19 @@ #include "tz/lua/state.hpp" #include "tz/core/debug.hpp" -#define LUA_BEGIN(name) int luafn_##name(void* state){ + +// `s` == underlying lua_State*. `state` == tz::lua::state +#define LUA_BEGIN(name) int luafn_##name(void* s){tz::lua::state state{s}; #define LUA_END } -#define LUA_REGISTER(name) tz::lua::for_all_states([](tz::lua::state& s){s.assign_func(#name, luafn_##name);}); +#define LUA_REGISTER_ALL(name) tz::lua::for_all_states([](tz::lua::state& s){s.assign_func(#name, luafn_##name);}); +#define LUA_REGISTER_ONE(name, state) s.assign_func(#name, luafn_##name) +#define LUA_FN_NAME(name) luafn_##name + +namespace tz::lua +{ + void api_initialise(state& s); +} // example: define in a TU //LUA_BEGIN(test_me_please) @@ -15,7 +24,7 @@ //LUA_END // usage: somewhere during runtime, invoke: -// LUA_REGISTER(test_me_please) +// LUA_REGISTER_ALL(test_me_please) // which makes the function resident to all lua states on both the main thread and the job system worker threads. #endif // TZ_LUA_API_HPP \ No newline at end of file diff --git a/src/tz/lua/state.cpp b/src/tz/lua/state.cpp index b2d98be6a2..c677823347 100644 --- a/src/tz/lua/state.cpp +++ b/src/tz/lua/state.cpp @@ -1,14 +1,13 @@ #include "tz/lua/state.hpp" +#include "tz/lua/api.hpp" #include "tz/core/debug.hpp" #include "tz/core/data/version.hpp" #include "tz/dbgui/dbgui.hpp" #include "tz/core/job/job.hpp" -#include #include #include #include #include -#include #include extern "C" @@ -204,6 +203,83 @@ namespace tz::lua return std::nullopt; } + std::size_t state::stack_size() const + { + auto* s = static_cast(this->lstate); + return lua_gettop(s); + } + + void state::stack_pop(std::size_t count) + { + auto* s = static_cast(this->lstate); + lua_pop(s, count); + } + + bool state::stack_get_bool(std::size_t idx, bool type_check) const + { + auto* s = static_cast(this->lstate); + if(lua_isboolean(s, idx) || !type_check) + { + return lua_toboolean(s, idx); + } + else + { + std::string stackdata = this->collect_stack(); + tz::error("Lua stack entry %zu requested as `bool`, type error. Stack:\n%s", idx, stackdata.c_str()); + return false; + } + } + + double state::stack_get_double(std::size_t idx, bool type_check) const + { + auto* s = static_cast(this->lstate); + if(lua_isnumber(s, idx) || !type_check) + { + return lua_tonumber(s, idx); + } + else + { + std::string stackdata = this->collect_stack(); + tz::error("Lua stack entry %zu requested as `double/float`, type error. Stack:\n%s", idx, stackdata.c_str()); + return false; + } + } + + float state::stack_get_float(std::size_t idx, bool type_check) const + { + return static_cast(this->stack_get_double(idx, type_check)); + } + + std::int64_t state::stack_get_int(std::size_t idx, bool type_check) const + { + return static_cast(this->stack_get_double(idx, type_check)); + } + + std::uint64_t state::stack_get_uint(std::size_t idx, bool type_check) const + { + return static_cast(this->stack_get_double(idx, type_check)); + } + + std::string state::stack_get_string(std::size_t idx, bool type_check) const + { + auto* s = static_cast(this->lstate); + // important note: lua under-the-hood does implicit conversions to-and-from + // string unless i explicitly disable them (e.g -DLUA_NOCVTN2S). this means + // that type check will always succeed for strings, even when they are not. + // however, i'm keeping the logic here still just for consistency. + if(lua_isstring(s, idx) || !type_check) + { + std::size_t len; + return {lua_tolstring(s, idx, &len), len}; + } + else + { + std::string stackdata = this->collect_stack(); + tz::error("Lua stack entry %zu requested as `string`, type error. Stack:\n%s", idx, stackdata.c_str()); + return ""; + } + } + std::string state::collect_stack() const { if(!this->impl_check_stack(3)) @@ -248,23 +324,20 @@ namespace tz::lua return this->owner; } + void* state::operator()() const + { + return this->lstate; + } + bool state::impl_check_stack(std::size_t sz) const { auto* s = static_cast(this->lstate); return lua_checkstack(s, sz); } - void tz_inject_state(state& s); - thread_local state defstate = {}; std::mutex state_creation_mtx; - int morb(lua_State* state) - { - tz::report("its morbin' time!"); - return 0; - } - state& get_state() { if(!defstate.valid()) @@ -275,7 +348,7 @@ namespace tz::lua luaL_openlibs(l); defstate = state{static_cast(l)}; - tz_inject_state(defstate); + api_initialise(defstate); } return defstate; } @@ -300,63 +373,4 @@ namespace tz::lua // remember also add it for main thread (which is us). fn(get_state()); } - - int tz_lua_assert(lua_State* state) - { - bool b = lua_toboolean(state, 1); - std::string stack = lua::state{state}.collect_stack(); - lua_Debug ar; - lua_getstack(state, 1, &ar); - lua_getinfo(state, "nSl", &ar); - if(!b && TZ_DEBUG) - { - tz::dbgui::add_to_lua_log("<>"); - } - tz::assert(b, "Lua Assertion Failure: ```lua\n\n%s\n\n```\nOn line %d\nStack:\n%s", ar.source, ar.currentline, stack.c_str()); - return 0; - } - - int tz_lua_error(lua_State* state) - { - lua_pushboolean(state, false); - return 1 + tz_lua_assert(state); - } - - int tz_print(lua_State* state) - { - int nargs = lua_gettop(state); - for(int i = 1; i <= nargs; i++) - { - const char* msg = luaL_tolstring(state, i, nullptr); - if(msg != nullptr) - { - tz::dbgui::add_to_lua_log(msg); - } - } - tz::dbgui::add_to_lua_log("\n"); - lua_pop(state, nargs); - return 0; - } - - void tz_inject_state(state& s) - { - s.assign_emptytable("tz"); - s.assign_emptytable("tz.version"); - s.assign_func("tz.assert", tz_lua_assert); - s.assign_func("tz.error", tz_lua_error); - s.assign_func("print", tz_print); - - std::ostringstream sstr; - sstr << std::this_thread::get_id(); - s.assign_string("tz.thread", sstr.str()); - - s.assign_string("LUA_PATH", std::filesystem::current_path().generic_string()); - - tz::version ver = tz::get_version(); - - s.assign_uint("tz.version.major", ver.major); - s.assign_uint("tz.version.minor", ver.minor); - s.assign_uint("tz.version.patch", ver.patch); - s.assign_string("tz.version.string", ver.to_string()); - } } diff --git a/src/tz/lua/state.hpp b/src/tz/lua/state.hpp index d95e61e52f..1e3143cfba 100644 --- a/src/tz/lua/state.hpp +++ b/src/tz/lua/state.hpp @@ -63,9 +63,18 @@ namespace tz::lua std::optional get_double(const char* varname) const; std::optional get_int(const char* varname) const; std::optional get_uint(const char* varname) const; + std::size_t stack_size() const; + void stack_pop(std::size_t count = 1); + bool stack_get_bool(std::size_t idx, bool type_check = true) const; + double stack_get_double(std::size_t idx, bool type_check = true) const; + float stack_get_float(std::size_t idx, bool type_check = true) const; + std::int64_t stack_get_int(std::size_t idx, bool type_check = true) const; + std::uint64_t stack_get_uint(std::size_t idx, bool type_check = true) const; + std::string stack_get_string(std::size_t idx, bool type_check = true) const; std::string collect_stack() const; const std::string& get_last_error() const; std::thread::id get_owner_thread_id() const; + void* operator()() const; private: bool impl_check_stack(std::size_t sz) const; mutable std::string last_error = "";