From c6e7a2f14471e151bccd8ffd1fcacc15524fbea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=92=80=E5=A2=83=E7=9F=B3?= Date: Sun, 17 Nov 2024 00:20:18 +0800 Subject: [PATCH 1/8] import sqlite3 --- cmake/import/all.cmake | 1 + cmake/import/sqlite3.cmake | 39 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 cmake/import/sqlite3.cmake diff --git a/cmake/import/all.cmake b/cmake/import/all.cmake index 66a721aa..6035eab8 100644 --- a/cmake/import/all.cmake +++ b/cmake/import/all.cmake @@ -17,6 +17,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/tinyobjloader.cmake) include(${CMAKE_CURRENT_LIST_DIR}/pcg.cmake) include(${CMAKE_CURRENT_LIST_DIR}/xxhash.cmake) include(${CMAKE_CURRENT_LIST_DIR}/simdutf.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/sqlite3.cmake) include(${CMAKE_CURRENT_LIST_DIR}/wil.cmake) include(${CMAKE_CURRENT_LIST_DIR}/directx_tk.cmake) diff --git a/cmake/import/sqlite3.cmake b/cmake/import/sqlite3.cmake new file mode 100644 index 00000000..931ee5bc --- /dev/null +++ b/cmake/import/sqlite3.cmake @@ -0,0 +1,39 @@ +# sqlite3 + +CPMAddPackage( + NAME sqlite3 + VERSION 3.47.0 + URL https://sqlite.org/2024/sqlite-amalgamation-3470000.zip + URL_HASH SHA256=2842FDDBB1CC33F66C7DA998A57535F14A6BFEE159676A07BB4BF3E59375D93E + DOWNLOAD_ONLY YES +) + +if (sqlite3_ADDED) + set(sqlite3_root ${sqlite3_SOURCE_DIR}) + + add_library(sqlite3 STATIC) + if (CMAKE_C_COMPILER_ID STREQUAL "MSVC") + target_compile_options(sqlite3 PRIVATE "/utf-8") + endif () + target_compile_features(sqlite3 PRIVATE c_std_17) + target_include_directories(sqlite3 PUBLIC ${sqlite3_root}) + target_sources(sqlite3 PRIVATE ${sqlite3_root}/sqlite3.h ${sqlite3_root}/sqlite3.c) + set_target_properties(sqlite3 PROPERTIES OUTPUT_NAME "libsqlite3") + + add_executable(sqlite3_cli) + if (CMAKE_C_COMPILER_ID STREQUAL "MSVC") + target_compile_options(sqlite3_cli PRIVATE "/utf-8") + endif () + target_compile_features(sqlite3_cli PRIVATE c_std_17) + target_sources(sqlite3_cli PRIVATE ${sqlite3_root}/shell.c) + target_link_libraries(sqlite3_cli PRIVATE sqlite3) + set_target_properties(sqlite3_cli PROPERTIES OUTPUT_NAME "sqlite3") + + add_custom_command(TARGET sqlite3_cli POST_BUILD + COMMAND ${CMAKE_COMMAND} ARGS -E make_directory ${CMAKE_BINARY_DIR}/bin + COMMAND ${CMAKE_COMMAND} ARGS -E copy $ ${CMAKE_BINARY_DIR}/bin + ) + + set_target_properties(sqlite3 PROPERTIES FOLDER external) + set_target_properties(sqlite3_cli PROPERTIES FOLDER external) +endif () From ba95327152461d6019a31304901012bc6b4fdd3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=92=80=E5=A2=83=E7=9F=B3?= Date: Sun, 17 Nov 2024 00:42:14 +0800 Subject: [PATCH 2/8] better copy commands --- cmake/import/sqlite3.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmake/import/sqlite3.cmake b/cmake/import/sqlite3.cmake index 931ee5bc..34f7657a 100644 --- a/cmake/import/sqlite3.cmake +++ b/cmake/import/sqlite3.cmake @@ -31,7 +31,8 @@ if (sqlite3_ADDED) add_custom_command(TARGET sqlite3_cli POST_BUILD COMMAND ${CMAKE_COMMAND} ARGS -E make_directory ${CMAKE_BINARY_DIR}/bin - COMMAND ${CMAKE_COMMAND} ARGS -E copy $ ${CMAKE_BINARY_DIR}/bin + COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different $ ${CMAKE_BINARY_DIR}/bin/$ + VERBATIM ) set_target_properties(sqlite3 PROPERTIES FOLDER external) From c58a8e97dcd1ea2e42354c6c81fdaac7bc3181b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=92=80=E5=A2=83=E7=9F=B3?= Date: Sun, 17 Nov 2024 14:40:39 +0800 Subject: [PATCH 3/8] script engine --- engine/CMakeLists.txt | 1 + engine/scriptable/CMakeLists.txt | 21 ++ engine/scriptable/core/ScriptEngine.cpp | 18 ++ engine/scriptable/core/ScriptEngine.hpp | 33 +++ engine/scriptable/core/ScriptEngineLua.cpp | 308 +++++++++++++++++++++ engine/scriptable/test/test.cpp | 37 +++ engine/scriptable/test/test.lua.txt | 188 +++++++++++++ 7 files changed, 606 insertions(+) create mode 100644 engine/scriptable/CMakeLists.txt create mode 100644 engine/scriptable/core/ScriptEngine.cpp create mode 100644 engine/scriptable/core/ScriptEngine.hpp create mode 100644 engine/scriptable/core/ScriptEngineLua.cpp create mode 100644 engine/scriptable/test/test.cpp create mode 100644 engine/scriptable/test/test.lua.txt diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index c88254db..3f2ac9a6 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(configuration) +add_subdirectory(scriptable) diff --git a/engine/scriptable/CMakeLists.txt b/engine/scriptable/CMakeLists.txt new file mode 100644 index 00000000..9002aa2f --- /dev/null +++ b/engine/scriptable/CMakeLists.txt @@ -0,0 +1,21 @@ +set(lib_name LuaSTG.Sub.Scriptable) + +file(GLOB_RECURSE lib_src RELATIVE ${CMAKE_CURRENT_LIST_DIR} "core/*.hpp" "core/*.cpp") +source_group(TREE ${CMAKE_CURRENT_LIST_DIR} FILES ${lib_src}) + +add_library(${lib_name} STATIC) +luastg_target_common_options(${lib_name}) +luastg_target_more_warning(${lib_name}) +target_compile_features(${lib_name} PRIVATE cxx_std_20) +target_include_directories(${lib_name} PUBLIC .) +target_sources(${lib_name} PRIVATE ${lib_src}) +target_link_libraries(${lib_name} PUBLIC lua51_static) + +set(lib_test_name LuaSTG.Sub.Scriptable.Test) + +add_executable(${lib_test_name}) +luastg_target_common_options(${lib_test_name}) +luastg_target_more_warning(${lib_test_name}) +target_compile_features(${lib_test_name} PRIVATE cxx_std_20) +target_sources(${lib_test_name} PRIVATE test/test.cpp) +target_link_libraries(${lib_test_name} PRIVATE ${lib_name}) diff --git a/engine/scriptable/core/ScriptEngine.cpp b/engine/scriptable/core/ScriptEngine.cpp new file mode 100644 index 00000000..3fd3dabb --- /dev/null +++ b/engine/scriptable/core/ScriptEngine.cpp @@ -0,0 +1,18 @@ +#include "core/ScriptEngine.hpp" + +namespace core { + void* ScriptEngine::getNativeHandle() { + return handle; + } + + ScriptEngine::ScriptEngine() : handle(nullptr) { + } + ScriptEngine::~ScriptEngine() { + close(); + } + + ScriptEngine& ScriptEngine::getInstance() { + static ScriptEngine instance; + return instance; + } +} diff --git a/engine/scriptable/core/ScriptEngine.hpp b/engine/scriptable/core/ScriptEngine.hpp new file mode 100644 index 00000000..1a37b34b --- /dev/null +++ b/engine/scriptable/core/ScriptEngine.hpp @@ -0,0 +1,33 @@ +#pragma once +#include +#include + +namespace core { + class ScriptEngine { + public: + class CallResult { + friend class ScriptEngine; + public: + inline explicit CallResult(bool status) : message(status ? "" : "unknown error") {} + inline explicit CallResult(std::string_view const& s) : message(s) {} + inline explicit CallResult(std::string&& s) : message(s) {} + inline operator bool() const noexcept { return message.empty(); } + inline std::string const& getMessage() const noexcept { return message; } + private: + std::string message; + }; + public: + CallResult loadFromFile(std::string_view const& path); + CallResult call(std::string_view const& function_name); + bool open(); + void close(); + void* getNativeHandle(); + public: + ScriptEngine(); + ~ScriptEngine(); + public: + static ScriptEngine& getInstance(); + private: + void* handle; + }; +} diff --git a/engine/scriptable/core/ScriptEngineLua.cpp b/engine/scriptable/core/ScriptEngineLua.cpp new file mode 100644 index 00000000..356a9675 --- /dev/null +++ b/engine/scriptable/core/ScriptEngineLua.cpp @@ -0,0 +1,308 @@ +#include "core/ScriptEngine.hpp" +#include +#include +#include +#include +#include "lua.hpp" + +using namespace std::string_view_literals; + +namespace core { +#define LVM static_cast(handle) + + static void* debug_trackback_key{}; + static void* stack_trackback_function_name{}; + + static int stackTraceBack(lua_State* L) { + std::array buf{}; + std::pmr::monotonic_buffer_resource res(buf.data(), buf.size()); + std::pmr::string msg(&res); + + size_t l{}; + if (char const* m = lua_tolstring(L, 1, &l); m) { + msg.append(m, l); + } + else if (luaL_callmeta(L, 1, "__tostring")) { + char const* m = lua_tolstring(L, -1, &l); + msg.append("(error object is a '"sv); + msg.append(m, l); + msg.append("' value)"sv); + lua_pop(L, 1); + } + else { + msg.append("(error object is a "sv); + if (lua_isnoneornil(L, 1)) { + msg.append("(error object is a nil value)"sv); + } + else if (lua_isboolean(L, 1)) { + msg.append(lua_toboolean(L, 1) ? "true"sv : "false"sv); + } + else if (lua_istable(L, 1)) { + msg.append("table"sv); + } + else if (lua_islightuserdata(L, 1)) { + msg.append("light userdata"sv); + } + else if (lua_isuserdata(L, 1)) { + msg.append("userdata"sv); + } + else if (lua_iscfunction(L, 1)) { + msg.append("C function"sv); + } + else if (lua_isfunction(L, 1)) { + msg.append("function"sv); + } + else if (lua_isthread(L, 1)) { + msg.append("thread"sv); + } + else { + msg.append("?"sv); + } + msg.append(" value)"sv); + } + + msg.append("\nstack traceback:"sv); + + lua_Debug debug{}; + for (int level = 1; lua_getstack(L, level, &debug); level += 1) { + lua_getinfo(L, "Sfln", &debug); + msg.append("\n\t"sv); + msg.append(debug.short_src); + msg.append(":"sv); + if (debug.currentline > 0) { + char line_number[22]{}; + int n = std::snprintf(line_number, 22, "%d", debug.currentline); + msg.append(line_number, static_cast(n)); + msg.append(":"sv); + } + if (debug.name && debug.name[0]) { + msg.append(" in"sv); + if (debug.namewhat && debug.namewhat[0]) { + msg.append(" ("sv); + msg.append(debug.namewhat); + msg.append(")"sv); + } + msg.append(" function '"sv); + msg.append(debug.name); + msg.append("'"sv); + } + else if (debug.what) { + if (debug.what[0] == 'm' /* "main" */) { + msg.append(" in main chunk"sv); + } + else if (debug.what[0] == 'C' /* "C" */) { + msg.append(" at C"sv); + } + else { + lua_pushlightuserdata(L, &stack_trackback_function_name); + lua_gettable(L, LUA_REGISTRYINDEX); + if (debug.what[0] == 'L' /* "Lua" */ && lua_isstring(L, -1)) { + size_t l{}; + auto const* s = lua_tolstring(L, -1, &l); + msg.append(" in (global) function '"sv); + msg.append(s, l); + msg.append("'"sv); + } + else { + msg.append(" in function <"sv); + msg.append(debug.short_src); + msg.append(":"sv); + char line_number[22]{}; + int n = std::snprintf(line_number, 22, "%d", debug.linedefined); + msg.append(line_number, static_cast(n)); + msg.append(">"sv); + } + lua_pop(L, 1); + } + } + else { + msg.append(" unknown"sv); + } + } + + lua_pushlstring(L, msg.c_str(), msg.length()); + + return 1; + } + + ScriptEngine::CallResult ScriptEngine::loadFromFile(std::string_view const& path) { + assert(handle); + constexpr auto function_name = "dofile"sv; + + // -------------------- get stack traceback function + + lua_pushlightuserdata(LVM, &stackTraceBack); + // ^stack: ... *key + lua_gettable(LVM, LUA_REGISTRYINDEX); + // ^stack: ... trackback + auto const debug_trackback_index = lua_gettop(LVM); + + // -------------------- get calling function from global + + lua_pushlstring(LVM, function_name.data(), function_name.size()); + // ^stack: ... trackback "FUNCTION" + lua_gettable(LVM, LUA_GLOBALSINDEX); + // ^stack: ... trackback FUNCTION? + + // -------------------- call + + lua_pushlstring(LVM, path.data(), path.size()); + // ^stack: ... trackback FUNCTION? "path" + auto const r = lua_pcall(LVM, 1, 0, debug_trackback_index); + // ^stack: ... trackback error? + + // -------------------- check call result + + if (r != 0) { + if (r == LUA_ERRMEM) { + return CallResult("memory allocation error"sv); + } + else if (r == LUA_ERRERR) { + return CallResult("error while running the error handler function"sv); + } + else { + size_t l{}; + if (char const* m = lua_tolstring(LVM, -1, &l); m) { + return CallResult(l > 0 ? std::string_view{ m, l } : "unknown error"sv); + } + else { + return CallResult("(error object is a nil value)"sv); + } + } + } + + // -------------------- clean + + lua_settop(LVM, debug_trackback_index - 1); + // ^stack: ... + + return CallResult(true); + } + ScriptEngine::CallResult ScriptEngine::call(std::string_view const& function_name) { + assert(handle); + + // -------------------- get stack traceback function + + lua_pushlightuserdata(LVM, &stackTraceBack); + // ^stack: ... *key + lua_gettable(LVM, LUA_REGISTRYINDEX); + // ^stack: ... trackback + auto const debug_trackback_index = lua_gettop(LVM); + + // -------------------- set calling function name + + lua_pushlightuserdata(LVM, &stack_trackback_function_name); + // ^stack: ... trackback *key + lua_pushlstring(LVM, function_name.data(), function_name.size()); + // ^stack: ... trackback *key "FUNCTION" + lua_settable(LVM, LUA_REGISTRYINDEX); + // ^stack: ... trackback + + // -------------------- get calling function from global + + lua_pushlstring(LVM, function_name.data(), function_name.size()); + // ^stack: ... trackback "FUNCTION" + lua_gettable(LVM, LUA_GLOBALSINDEX); + // ^stack: ... trackback FUNCTION? + + // -------------------- call + + auto const r = lua_pcall(LVM, 0, 0, debug_trackback_index); + // ^stack: ... trackback error? + + // -------------------- clean calling function name + + lua_pushlightuserdata(LVM, &stack_trackback_function_name); + // ^stack: ... trackback error? *key + lua_pushnil(LVM); + // ^stack: ... trackback error? *key nil + lua_settable(LVM, LUA_REGISTRYINDEX); + // ^stack: ... trackback error? + + // -------------------- check call result + + if (r != 0) { + if (r == LUA_ERRMEM) { + return CallResult("memory allocation error"sv); + } + else if (r == LUA_ERRERR) { + return CallResult("error while running the error handler function"sv); + } + else { + size_t l{}; + if (char const* m = lua_tolstring(LVM, -1, &l); m) { + return CallResult(l > 0 ? std::string_view{ m, l } : "unknown error"sv); + } + else { + return CallResult("(error object is a nil value)"sv); + } + } + } + + // -------------------- clean + + lua_settop(LVM, debug_trackback_index - 1); + // ^stack: ... + + return CallResult(true); + } + + bool ScriptEngine::open() { + // -------------------- create VM + + handle = static_cast(luaL_newstate()); + if (!handle) { + return false; + } + + // -------------------- register standard libraries + + luaL_openlibs(LVM); + + // -------------------- store debug.traceback + + // ^stack: ... + lua_getglobal(LVM, "require"); + // ^stack: ... require + if (!lua_isfunction(LVM, lua_gettop(LVM))) { + return false; + } + lua_pushstring(LVM, "debug"); + // ^stack: ... require "debug" + lua_call(LVM, 1, 1); + // ^stack: ... debug? + int const debug_index = lua_gettop(LVM); + if (!lua_istable(LVM, debug_index)) { + return false; + } + // ^stack: ... debug + lua_pushlightuserdata(LVM, &debug_trackback_key); + // ^stack: ... debug *key + lua_getfield(LVM, debug_index, "traceback"); + // ^stack: ... debug *key traceback + if (!lua_isfunction(LVM, debug_index + 2)) { + return false; + } + lua_settable(LVM, LUA_REGISTRYINDEX); + // ^stack: ... debug + lua_pop(LVM, 1); + // ^stack: ... + + // -------------------- register global stack traceback function + + lua_pushlightuserdata(LVM, &stackTraceBack); + // ^stack: ... *key + lua_pushcfunction(LVM, &stackTraceBack); + // ^stack: ... *key stackTraceBack + lua_settable(LVM, LUA_REGISTRYINDEX); + // ^stack: ... + + return true; + } + void ScriptEngine::close() { + if (LVM) { + lua_close(LVM); + handle = nullptr; + } + } +} diff --git a/engine/scriptable/test/test.cpp b/engine/scriptable/test/test.cpp new file mode 100644 index 00000000..f9c70137 --- /dev/null +++ b/engine/scriptable/test/test.cpp @@ -0,0 +1,37 @@ +#include "core/ScriptEngine.hpp" +#include +#include + +using namespace std::string_view_literals; + +int main() { + auto& vm = core::ScriptEngine::getInstance(); + if (!vm.open()) { + return 1; + } + if (auto const load_result = vm.loadFromFile("main.lua"sv); !load_result) { + std::cout << load_result.getMessage() << std::endl; + } + std::vector tests{ + "test_call_error"sv, + "test_call_error_with_nil"sv, + "test_call_error_with_false"sv, + "test_call_error_with_true"sv, + "test_call_error_with_pi"sv, + "test_call_error_with_hello"sv, + "test_call_error_with_table"sv, + "test_call_error_with_table_x"sv, + "test_call_error_with_userdata"sv, + "call_self"sv, + "call_f0"sv, + }; + for (auto const& test : tests) { + std::cout << "========== test: "sv << test << std::endl; + auto const call_result = vm.call(test); + if (!call_result) { + std::cout << call_result.getMessage() << std::endl; + } + } + vm.close(); + return 0; +} diff --git a/engine/scriptable/test/test.lua.txt b/engine/scriptable/test/test.lua.txt new file mode 100644 index 00000000..c2cac598 --- /dev/null +++ b/engine/scriptable/test/test.lua.txt @@ -0,0 +1,188 @@ +local function call_error_with_userdata() + local f = io.open("__", "r") + error(f) +end + +function test_call_error_with_userdata() + call_error_with_userdata() +end + +local function call_error_with_table_x() + error(setmetatable({}, { __tostring = function (_) + return "super fuck" + end})) +end + +function test_call_error_with_table_x() + call_error_with_table_x() +end + +local function call_error_with_table() + error({}) +end + +function test_call_error_with_table() + call_error_with_table() +end + +local function call_error_with_hello() + error("hello") +end + +function test_call_error_with_hello() + call_error_with_hello() +end + +local function call_error_with_pi() + error(math.pi) +end + +function test_call_error_with_pi() + call_error_with_pi() +end + +local function call_error_with_true() + error(true) +end + +function test_call_error_with_true() + call_error_with_true() +end + +local function call_error_with_false() + error(false) +end + +function test_call_error_with_false() + call_error_with_false() +end + +local function call_error_with_nil() + error(nil) +end + +function test_call_error_with_nil() + call_error_with_nil() +end + +local function call_error() + error() +end + +function test_call_error() + call_error() +end + +function print_hello_world() + print("Hello world!") +end + +local strsB = {} +local strs = {} + +function call_self_B() + table.insert(strsB, math.random()) + return call_self_A() +end + +function call_self_A() + table.insert(strs, math.random()) + if #strs > 1000 and #strsB > 1000 then + error("fuck you") + end + return call_self_B() +end + +function call_self() + return call_self_A() +end + +function print_strs() + print(strs[1], strsB[2]) +end + + +local function f10() + if math.random() > 0.5 then + print("f10") + end + f11() + print("--") +end +local function f9() + if math.random() > 0.5 then + print("f9") + end + f10() + print("--") +end +local function f8() + if math.random() > 0.5 then + print("f8") + end + f9() + print("--") +end +local function f7() + if math.random() > 0.5 then + print("f7") + end + f8() + print("--") +end +local function f6() + if math.random() > 0.5 then + print("f6") + end + f7() + print("--") +end +local function f5() + if math.random() > 0.5 then + print("f5") + end + f6() + print("--") +end +local function f4() + if math.random() > 0.5 then + print("f4") + end + f5() + print("--") +end +local function f3() + if math.random() > 0.5 then + print("f3") + end + f4() + print("--") +end +local function f2() + if math.random() > 0.5 then + print("f2") + end + f3() + print("--") +end +local function f1() + if math.random() > 0.5 then + print("f1") + end + f2() + print("--") +end +local function f0() + if math.random() > 0.5 then + print("f0") + end + f1() + print("--") +end +function call_f0() + print("----") + f0() + print("----") +end + +call_f0() From 94f2a9c080625670d0d7a170225e1dacffb68d8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=92=80=E5=A2=83=E7=9F=B3?= Date: Sun, 17 Nov 2024 15:12:48 +0800 Subject: [PATCH 4/8] test env --- engine/scriptable/CMakeLists.txt | 10 ++++++ engine/scriptable/core/ScriptEngine.hpp | 2 ++ engine/scriptable/core/ScriptEngineLua.cpp | 38 +++++++++++++--------- engine/scriptable/test/test.cpp | 7 ++-- 4 files changed, 40 insertions(+), 17 deletions(-) diff --git a/engine/scriptable/CMakeLists.txt b/engine/scriptable/CMakeLists.txt index 9002aa2f..18924255 100644 --- a/engine/scriptable/CMakeLists.txt +++ b/engine/scriptable/CMakeLists.txt @@ -1,5 +1,7 @@ set(lib_name LuaSTG.Sub.Scriptable) +option(LUASTG_LINK_LUAJIT "LuaSTG: link LuaJIT" ON) + file(GLOB_RECURSE lib_src RELATIVE ${CMAKE_CURRENT_LIST_DIR} "core/*.hpp" "core/*.cpp") source_group(TREE ${CMAKE_CURRENT_LIST_DIR} FILES ${lib_src}) @@ -7,6 +9,9 @@ add_library(${lib_name} STATIC) luastg_target_common_options(${lib_name}) luastg_target_more_warning(${lib_name}) target_compile_features(${lib_name} PRIVATE cxx_std_20) +if (LUASTG_LINK_LUAJIT) + target_compile_definitions(${lib_name} PRIVATE LUASTG_LINK_LUAJIT) +endif () target_include_directories(${lib_name} PUBLIC .) target_sources(${lib_name} PRIVATE ${lib_src}) target_link_libraries(${lib_name} PUBLIC lua51_static) @@ -19,3 +24,8 @@ luastg_target_more_warning(${lib_test_name}) target_compile_features(${lib_test_name} PRIVATE cxx_std_20) target_sources(${lib_test_name} PRIVATE test/test.cpp) target_link_libraries(${lib_test_name} PRIVATE ${lib_name}) + +add_custom_command(TARGET ${lib_test_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different ${CMAKE_CURRENT_LIST_DIR}/test/test.lua.txt $/test.lua + VERBATIM +) diff --git a/engine/scriptable/core/ScriptEngine.hpp b/engine/scriptable/core/ScriptEngine.hpp index 1a37b34b..2d788329 100644 --- a/engine/scriptable/core/ScriptEngine.hpp +++ b/engine/scriptable/core/ScriptEngine.hpp @@ -27,6 +27,8 @@ namespace core { ~ScriptEngine(); public: static ScriptEngine& getInstance(); + private: + bool registerStackTraceBackHandler(); private: void* handle; }; diff --git a/engine/scriptable/core/ScriptEngineLua.cpp b/engine/scriptable/core/ScriptEngineLua.cpp index 356a9675..e1d07175 100644 --- a/engine/scriptable/core/ScriptEngineLua.cpp +++ b/engine/scriptable/core/ScriptEngineLua.cpp @@ -4,6 +4,9 @@ #include #include #include "lua.hpp" +#ifdef LUASTG_LINK_LUAJIT +#include "luajit.h" +#endif using namespace std::string_view_literals; @@ -23,7 +26,7 @@ namespace core { msg.append(m, l); } else if (luaL_callmeta(L, 1, "__tostring")) { - char const* m = lua_tolstring(L, -1, &l); + m = lua_tolstring(L, -1, &l); msg.append("(error object is a '"sv); msg.append(m, l); msg.append("' value)"sv); @@ -97,10 +100,10 @@ namespace core { lua_pushlightuserdata(L, &stack_trackback_function_name); lua_gettable(L, LUA_REGISTRYINDEX); if (debug.what[0] == 'L' /* "Lua" */ && lua_isstring(L, -1)) { - size_t l{}; - auto const* s = lua_tolstring(L, -1, &l); + size_t len{}; + auto const* str = lua_tolstring(L, -1, &len); msg.append(" in (global) function '"sv); - msg.append(s, l); + msg.append(str, len); msg.append("'"sv); } else { @@ -248,16 +251,27 @@ namespace core { } bool ScriptEngine::open() { - // -------------------- create VM - handle = static_cast(luaL_newstate()); if (!handle) { return false; } - - // -------------------- register standard libraries - + #ifdef LUASTG_LINK_LUAJIT + if (0 == luaJIT_setmode(LVM, 0, LUAJIT_MODE_ENGINE | LUAJIT_MODE_ON)) { + // TODO; write log + } + #endif // LUASTG_LINK_LUAJIT luaL_openlibs(LVM); + return registerStackTraceBackHandler(); + } + void ScriptEngine::close() { + if (LVM) { + lua_close(LVM); + handle = nullptr; + } + } + + bool ScriptEngine::registerStackTraceBackHandler() { + assert(handle); // -------------------- store debug.traceback @@ -299,10 +313,4 @@ namespace core { return true; } - void ScriptEngine::close() { - if (LVM) { - lua_close(LVM); - handle = nullptr; - } - } } diff --git a/engine/scriptable/test/test.cpp b/engine/scriptable/test/test.cpp index f9c70137..4294a921 100644 --- a/engine/scriptable/test/test.cpp +++ b/engine/scriptable/test/test.cpp @@ -1,15 +1,18 @@ #include "core/ScriptEngine.hpp" #include #include +#include using namespace std::string_view_literals; -int main() { +int main(int argc, char** argv) { auto& vm = core::ScriptEngine::getInstance(); if (!vm.open()) { return 1; } - if (auto const load_result = vm.loadFromFile("main.lua"sv); !load_result) { + std::filesystem::path const path(argv[0]); + auto const test_path = path.parent_path() / "test.lua"sv; + if (auto const load_result = vm.loadFromFile(test_path.lexically_normal().generic_string()); !load_result) { std::cout << load_result.getMessage() << std::endl; } std::vector tests{ From b352d846756253af80561c8359c5b4e6c5d73f09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=92=80=E5=A2=83=E7=9F=B3?= Date: Sun, 17 Nov 2024 15:23:39 +0800 Subject: [PATCH 5/8] wut --- engine/scriptable/CMakeLists.txt | 7 +- engine/scriptable/lua/Stack.cpp | 32 +++++++++ engine/scriptable/lua/Stack.hpp | 113 +++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 engine/scriptable/lua/Stack.cpp create mode 100644 engine/scriptable/lua/Stack.hpp diff --git a/engine/scriptable/CMakeLists.txt b/engine/scriptable/CMakeLists.txt index 18924255..c168a4cb 100644 --- a/engine/scriptable/CMakeLists.txt +++ b/engine/scriptable/CMakeLists.txt @@ -2,7 +2,12 @@ set(lib_name LuaSTG.Sub.Scriptable) option(LUASTG_LINK_LUAJIT "LuaSTG: link LuaJIT" ON) -file(GLOB_RECURSE lib_src RELATIVE ${CMAKE_CURRENT_LIST_DIR} "core/*.hpp" "core/*.cpp") +file(GLOB_RECURSE lib_src RELATIVE ${CMAKE_CURRENT_LIST_DIR} + "core/*.hpp" + "core/*.cpp" + "lua/*.hpp" + "lua/*.cpp" +) source_group(TREE ${CMAKE_CURRENT_LIST_DIR} FILES ${lib_src}) add_library(${lib_name} STATIC) diff --git a/engine/scriptable/lua/Stack.cpp b/engine/scriptable/lua/Stack.cpp new file mode 100644 index 00000000..a460a9cb --- /dev/null +++ b/engine/scriptable/lua/Stack.cpp @@ -0,0 +1,32 @@ +#include "lua/Stack.hpp" + +namespace lua { + StackBalancer::StackBalancer(lua_State* L) : L(L), N(lua_gettop(L)) { + } + StackBalancer::~StackBalancer() { lua_settop(L, N); } +} + +namespace lua { + template<> + [[nodiscard]] int32_t Stack::getValue(StackIndex const index) const { + return static_cast(luaL_checkinteger(L, index.value)); + } + + template<> + [[nodiscard]] std::string_view Stack::getValue(StackIndex const index) const { + size_t len{}; + auto str = luaL_checklstring(L, index.value, &len); + // managed by lua VM + // ReSharper disable once CppDFALocalValueEscapesFunction + return { str, len }; + } + + template<> + [[nodiscard]] std::string Stack::getValue(StackIndex const index) const { + size_t len{}; + auto str = luaL_checklstring(L, index.value, &len); + // managed by lua VM + // ReSharper disable once CppDFALocalValueEscapesFunction + return { str, len }; + } +} diff --git a/engine/scriptable/lua/Stack.hpp b/engine/scriptable/lua/Stack.hpp new file mode 100644 index 00000000..21a9682b --- /dev/null +++ b/engine/scriptable/lua/Stack.hpp @@ -0,0 +1,113 @@ +#pragma once +#include +#include +#include +#include +#include "lua.hpp" + +namespace lua { + struct StackIndex { + int32_t value{}; + + inline explicit StackIndex(int32_t const value) noexcept : value(value) { + } + }; + + class StackBalancer { + public: + explicit StackBalancer(lua_State* L); + ~StackBalancer(); + private: + lua_State* L; + int32_t N; + }; + + class Stack { + public: + explicit Stack(lua_State* L) noexcept : L(L) { + } + + [[nodiscard]] StackIndex indexOfTop() const { + return StackIndex(lua_gettop(L)); + } + + void pushValue(std::nullopt_t const) const { + lua_pushnil(L); + } + + void pushValue(bool const value) const { + lua_pushboolean(L, value ? 1 : 0); + } + + void pushValue(std::string_view const& value) const { + lua_pushlstring(L, value.data(), value.length()); + } + + void pushValue(StackIndex const value) const { + lua_pushvalue(L, value.value); + } + + template + [[nodiscard]] T getValue(StackIndex index) const; + + template + [[nodiscard]] T getValue(int32_t const index) const { + return getValue(StackIndex(index)); + } + + // module and class system + + [[nodiscard]] StackIndex pushModule(std::string_view const& name) const { + constexpr luaL_Reg empty[] = { {} }; + luaL_register(L, name.data(), empty); + auto const index = lua_gettop(L); + lua_pushnil(L); + lua_setglobal(L, name.data()); + return StackIndex(index); + } + + [[nodiscard]] StackIndex createMetaTable(std::string_view const& name) const { + luaL_newmetatable(L, name.data()); + return StackIndex(lua_gettop(L)); + } + + void setMetaTable(StackIndex const index, std::string_view const& name) const { + luaL_getmetatable(L, name.data()); + lua_setmetatable(L, index.value); + } + + template + [[nodiscard]] T* createUserData() const { + return static_cast(lua_newuserdata(L, sizeof(T))); + } + + template + [[nodiscard]] T* createUserDataWithNew() const { + auto p = static_cast(lua_newuserdata(L, sizeof(T))); + return new(p) T(); + } + + // map + + [[nodiscard]] StackIndex createMap(size_t const reserve = 0) const { + lua_createtable(L, 0, static_cast(reserve)); + return indexOfTop(); + } + + void setMapValue(StackIndex const index, std::string_view const& key, lua_CFunction const value) const { + lua_pushcfunction(L, value); + lua_setfield(L, index.value, key.data()); + } + + void setMapValue(StackIndex const index, std::string_view const& key, StackIndex const value_index) const { + lua_pushvalue(L, value_index.value); + lua_setfield(L, index.value, key.data()); + } + + private: + lua_State* L{}; + }; + + static_assert(sizeof(Stack) == sizeof(lua_State*)); + static_assert(alignof(Stack) == alignof(lua_State*)); +} From ffbb5b4d5d6dbc53922a693988ed311e069152c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=92=80=E5=A2=83=E7=9F=B3?= Date: Sun, 17 Nov 2024 16:55:31 +0800 Subject: [PATCH 6/8] lua sqlite3 init --- LuaSTG/CMakeLists.txt | 1 + LuaSTG/LuaSTG/AppFrameLua.cpp | 2 + external/CMakeLists.txt | 5 + external/lua-sqlite3/CMakeLists.txt | 5 + external/lua-sqlite3/lua_sqlite3.cpp | 248 +++++++++++++++++++++++++++ external/lua-sqlite3/lua_sqlite3.h | 13 ++ 6 files changed, 274 insertions(+) create mode 100644 external/lua-sqlite3/CMakeLists.txt create mode 100644 external/lua-sqlite3/lua_sqlite3.cpp create mode 100644 external/lua-sqlite3/lua_sqlite3.h diff --git a/LuaSTG/CMakeLists.txt b/LuaSTG/CMakeLists.txt index a2fb006c..6b0c41b0 100644 --- a/LuaSTG/CMakeLists.txt +++ b/LuaSTG/CMakeLists.txt @@ -185,6 +185,7 @@ target_link_libraries(LuaSTG PRIVATE lua_filesystem #lua_xlsx_csv lua_imgui + lua_sqlite3 imgui_impl_win32ex imgui_impl_dx11 lua_steam_api diff --git a/LuaSTG/LuaSTG/AppFrameLua.cpp b/LuaSTG/LuaSTG/AppFrameLua.cpp index 6ab6408b..39a17c70 100644 --- a/LuaSTG/LuaSTG/AppFrameLua.cpp +++ b/LuaSTG/LuaSTG/AppFrameLua.cpp @@ -17,6 +17,7 @@ extern "C" { #endif //#include "lua_xlsx_csv.h" #include "lua_steam.h" +#include "lua_sqlite3.h" #include "LuaBinding/lua_xinput.hpp" #include "LuaBinding/lua_random.hpp" #include "LuaBinding/lua_dwrite.hpp" @@ -309,6 +310,7 @@ namespace LuaSTGPlus //lua_xlsx_open(L); //lua_csv_open(L); lua_steam_open(L); + luaopen_sqlite3(L); lua_xinput_open(L); luaopen_dwrite(L); luaopen_random(L); diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index bcbdb632..53485ea3 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -82,6 +82,11 @@ set_target_properties(lua_cjson PROPERTIES FOLDER lualib) # #set_target_properties(lua_xlsx_csv PROPERTIES FOLDER external) +# ==================== lua sqlite3 ==================== + +add_subdirectory(lua-sqlite3) +set_target_properties(lua_sqlite3 PROPERTIES FOLDER lualib) + # ==================== xmath ==================== add_library(xmath STATIC) diff --git a/external/lua-sqlite3/CMakeLists.txt b/external/lua-sqlite3/CMakeLists.txt new file mode 100644 index 00000000..5df4c30d --- /dev/null +++ b/external/lua-sqlite3/CMakeLists.txt @@ -0,0 +1,5 @@ +add_library(lua_sqlite3 STATIC) +target_compile_features(lua_sqlite3 PRIVATE c_std_17 cxx_std_20) +target_include_directories(lua_sqlite3 PUBLIC .) +target_sources(lua_sqlite3 PRIVATE lua_sqlite3.h lua_sqlite3.cpp) +target_link_libraries(lua_sqlite3 PUBLIC lua51_static sqlite3) diff --git a/external/lua-sqlite3/lua_sqlite3.cpp b/external/lua-sqlite3/lua_sqlite3.cpp new file mode 100644 index 00000000..667a5ecc --- /dev/null +++ b/external/lua-sqlite3/lua_sqlite3.cpp @@ -0,0 +1,248 @@ +#include "lua_sqlite3.h" +#include +#include +#include +#include +#include + +using namespace std::string_view_literals; + +namespace lua { + class StackIndex { + public: + //inline explicit StackIndex(int32_t index) noexcept : value_(index) {} + inline constexpr explicit StackIndex(int32_t index) noexcept : value_(index) {} + inline int32_t value() const noexcept { return value_; } + private: + int32_t const value_; + }; + + constexpr auto arg1 = StackIndex(1); + constexpr auto arg2 = StackIndex(2); + constexpr auto arg3 = StackIndex(3); + constexpr auto arg4 = StackIndex(4); + constexpr auto arg5 = StackIndex(5); + constexpr auto arg6 = StackIndex(6); + constexpr auto arg7 = StackIndex(7); + constexpr auto arg8 = StackIndex(8); + constexpr auto arg9 = StackIndex(9); + + class StackBalancer { + public: + inline explicit StackBalancer(lua_State* L_) : L(L_), N(lua_gettop(L_)) {} + inline ~StackBalancer() { lua_settop(L, N); } + private: + lua_State* const L; + int32_t const N; + }; + + class Stack { + public: + template + [[nodiscard]] T getValue(StackIndex const index) const { + if constexpr (std::is_same_v) { + return static_cast(luaL_checkinteger(L, index.value())); + } + else if constexpr (std::is_same_v) { + size_t l{}; + char const* s = luaL_checklstring(L, index.value(), &l); + return { s, l }; + } + else { + static_assert(false, "not implemented"); + } + } + template + void pushValue(T const& value) const { + if constexpr (std::is_same_v) { + lua_pushnil(L); + } + else if constexpr (std::is_same_v) { + lua_pushvalue(L, value.value()); + } + else if constexpr (std::is_same_v) { + lua_pushboolean(L, value ? 1 : 0); + } + else if constexpr (std::is_same_v) { + lua_pushinteger(L, value); + } + else if constexpr (std::is_same_v) { + lua_pushstring(L, value); + } + else if constexpr (std::is_same_v) { + lua_pushlstring(L, value.data(), value.size()); + } + else if constexpr (std::is_same_v) { + lua_pushcfunction(L, value); + } + else { + static_assert(false, "not implemented"); + } + } + + [[nodiscard]] StackIndex createMap(size_t cap = 0) const { + lua_createtable(L, 0, static_cast(cap)); + return StackIndex(lua_gettop(L)); + } + + template + void setMapValue(StackIndex const index, std::string_view const& key, T const& value) const { + pushValue(key); + pushValue(value); + lua_settable(L, index.value()); + } + + [[nodiscard]] StackIndex createMetaTable(std::string_view const& name) const { + luaL_newmetatable(L, name.data()); + return StackIndex(lua_gettop(L)); + } + + [[nodiscard]] StackIndex pushModule(std::string_view const& name) const { + constexpr luaL_Reg empty[] = { {} }; + luaL_register(L, name.data(), empty); + auto const index = lua_gettop(L); + lua_pushnil(L); + lua_setglobal(L, name.data()); + return StackIndex(index); + } + public: + inline explicit Stack(lua_State* L_) : L(L_) {} + private: + lua_State* const L; + }; +} + +namespace { + struct Database { + static const std::string_view class_name; + + static void registerClass(lua_State* L); + static Database* create(lua_State* L); + static Database* as(lua_State* L, int index); + static bool is(lua_State* L, int index); + + sqlite3* database; + }; + + const std::string_view Database::class_name{ "sqlite3.Database"sv }; + + struct DatabaseBinding : public Database { + // meta methods + + static int __gc(lua_State* L) { + auto* self = as(L, 1); + if (self->database) { + if (auto const result = sqlite3_close_v2(self->database); result == SQLITE_OK) { + self->database = nullptr; + } + } + return 0; + } + static int __tostring(lua_State* L) { + lua::Stack S(L); + [[maybe_unused]] auto* self = as(L, 1); + S.pushValue(class_name); + return 1; + } + + // instance methods + + static int close(lua_State* L) { + lua::Stack S(L); + auto* self = as(L, 1); + if (self->database) { + if (auto const result = sqlite3_close_v2(self->database); result == SQLITE_OK) { + self->database = nullptr; + } + else { + S.pushValue(false); + S.pushValue(sqlite3_errmsg(self->database)); + S.pushValue(result); + return 3; + } + } + S.pushValue(true); + return 1; + } + + // static methods + + static int open(lua_State* L) { + lua::Stack S(L); + auto const file_name = S.getValue(lua::arg1); + auto const flags = S.getValue(lua::arg2); + auto* self = create(L); + if (auto const result = sqlite3_open_v2(file_name.data(), &self->database, flags, nullptr); result != SQLITE_OK) { + S.pushValue(std::nullopt); + S.pushValue(sqlite3_errmsg(self->database)); + S.pushValue(result); + return 3; + } + return 1; + } + }; + + void Database::registerClass(lua_State* L) { + [[maybe_unused]] lua::StackBalancer SB(L); + lua::Stack S(L); + + auto const class_table = S.pushModule(class_name); + S.setMapValue(class_table, "close"sv, &DatabaseBinding::close); + S.setMapValue(class_table, "open"sv, &DatabaseBinding::open); + + auto const meta_table = S.createMetaTable(class_name); + S.setMapValue(meta_table, "__gc"sv, &DatabaseBinding::__gc); + S.setMapValue(meta_table, "__tostring"sv, &DatabaseBinding::__tostring); + S.setMapValue(meta_table, "__index"sv, class_table); + } + Database* Database::create(lua_State* L) { + auto* self = static_cast(lua_newuserdata(L, sizeof(Database))); + self->database = nullptr; + luaL_setmetatable(L, class_name.data()); + return self; + } + Database* Database::as(lua_State* L, int index) { + return static_cast(luaL_checkudata(L, index, class_name.data())); + } + bool Database::is(lua_State* L, int index) { + return luaL_testudata(L, index, class_name.data()) != nullptr; + } +} + +extern "C" int luaopen_sqlite3(lua_State* L) { + [[maybe_unused]] lua::StackBalancer SB(L); + lua::Stack S(L); + + Database::registerClass(L); + + auto const module_table = S.pushModule("sqlite"sv); + +#define CODE(X) S.setMapValue(module_table, "" #X ""sv, SQLITE_##X) + + CODE(OK); + + CODE(OPEN_READONLY ); + CODE(OPEN_READWRITE ); + CODE(OPEN_CREATE ); + CODE(OPEN_DELETEONCLOSE); + CODE(OPEN_EXCLUSIVE ); + CODE(OPEN_AUTOPROXY ); + CODE(OPEN_URI ); + CODE(OPEN_MEMORY ); + CODE(OPEN_MAIN_DB ); + CODE(OPEN_TEMP_DB ); + CODE(OPEN_TRANSIENT_DB ); + CODE(OPEN_MAIN_JOURNAL ); + CODE(OPEN_TEMP_JOURNAL ); + CODE(OPEN_SUBJOURNAL ); + CODE(OPEN_SUPER_JOURNAL); + CODE(OPEN_NOMUTEX ); + CODE(OPEN_FULLMUTEX ); + CODE(OPEN_SHAREDCACHE ); + CODE(OPEN_PRIVATECACHE ); + CODE(OPEN_WAL ); + CODE(OPEN_NOFOLLOW ); + CODE(OPEN_EXRESCODE ); + + return 1; +} diff --git a/external/lua-sqlite3/lua_sqlite3.h b/external/lua-sqlite3/lua_sqlite3.h new file mode 100644 index 00000000..0ddcc8ec --- /dev/null +++ b/external/lua-sqlite3/lua_sqlite3.h @@ -0,0 +1,13 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +int luaopen_sqlite3(lua_State* L); + +#ifdef __cplusplus +} +#endif From cacc67daa8a7b26f9a625a2543c45bb823022eb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=92=80=E5=A2=83=E7=9F=B3?= Date: Sun, 17 Nov 2024 18:04:40 +0800 Subject: [PATCH 7/8] exec --- external/lua-sqlite3/lua_sqlite3.cpp | 93 +++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/external/lua-sqlite3/lua_sqlite3.cpp b/external/lua-sqlite3/lua_sqlite3.cpp index 667a5ecc..e9137aa0 100644 --- a/external/lua-sqlite3/lua_sqlite3.cpp +++ b/external/lua-sqlite3/lua_sqlite3.cpp @@ -27,6 +27,16 @@ namespace lua { constexpr auto arg8 = StackIndex(8); constexpr auto arg9 = StackIndex(9); + constexpr auto arg_r1 = StackIndex(-1); + constexpr auto arg_r2 = StackIndex(-2); + constexpr auto arg_r3 = StackIndex(-3); + constexpr auto arg_r4 = StackIndex(-4); + constexpr auto arg_r5 = StackIndex(-5); + constexpr auto arg_r6 = StackIndex(-6); + constexpr auto arg_r7 = StackIndex(-7); + constexpr auto arg_r8 = StackIndex(-8); + constexpr auto arg_r9 = StackIndex(-9); + class StackBalancer { public: inline explicit StackBalancer(lua_State* L_) : L(L_), N(lua_gettop(L_)) {} @@ -66,7 +76,7 @@ namespace lua { else if constexpr (std::is_same_v) { lua_pushinteger(L, value); } - else if constexpr (std::is_same_v) { + else if constexpr (std::is_same_v || std::is_same_v) { lua_pushstring(L, value); } else if constexpr (std::is_same_v) { @@ -80,6 +90,22 @@ namespace lua { } } + [[nodiscard]] StackIndex createList(int32_t cap = 0) const { + lua_createtable(L, cap, 0); + return StackIndex(lua_gettop(L)); + } + [[nodiscard]] StackIndex createList(size_t cap = 0) const { + lua_createtable(L, static_cast(cap), 0); + return StackIndex(lua_gettop(L)); + } + + template + void setListValue(StackIndex const index, size_t key, T const& value) const { + pushValue(static_cast(key + 1)); + pushValue(value); + lua_settable(L, index.value()); + } + [[nodiscard]] StackIndex createMap(size_t cap = 0) const { lua_createtable(L, 0, static_cast(cap)); return StackIndex(lua_gettop(L)); @@ -127,6 +153,8 @@ namespace { const std::string_view Database::class_name{ "sqlite3.Database"sv }; struct DatabaseBinding : public Database { + #define verify if (!self->database) return luaL_error(L, "database is closed"); + // meta methods static int __gc(lua_State* L) { @@ -147,6 +175,66 @@ namespace { // instance methods + static int exec(lua_State* L) { + lua::Stack S(L); + auto* self = as(L, 1); + verify; + auto const sql = S.getValue(lua::arg2); + auto const has_callback = lua_isfunction(L, 3); + + struct CallbackContext { + sqlite3* database{}; + lua_State* L{}; + static int callback(void* userdata, int column_count, char** column_values, char** column_names) { + auto& ctx = *static_cast(userdata); + [[maybe_unused]] lua::StackBalancer SB(ctx.L); + lua::Stack S(ctx.L); + S.pushValue(lua::arg3); + // ^stack: ... callback + S.pushValue(column_count); + // ^stack: ... callback column_count + auto const value_list = S.createList(column_count); + // ^stack: ... callback column_count column_values + for (int i = 0; i < column_count; i += 1) { + S.setListValue(value_list, i, column_values[i]); + } + auto const name_list = S.createList(column_count); + // ^stack: ... callback column_count column_values column_names + for (int i = 0; i < column_count; i += 1) { + S.setListValue(name_list, i, column_names[i]); + } + lua_call(ctx.L, 3, 1); + // ^stack: ... code? + return S.getValue(lua::arg_r1); + } + }; + + char* error_message{}; + int result{}; + if (has_callback) { + CallbackContext callback_context{ self->database, L }; + result = sqlite3_exec(self->database, sql.data(), &CallbackContext::callback, &callback_context, &error_message); + } + else { + result = sqlite3_exec(self->database, sql.data(), nullptr, nullptr, &error_message); + } + + if (result != SQLITE_OK) { + S.pushValue(false); + if (error_message) { + S.pushValue(error_message); + sqlite3_free(error_message); + } + else { + S.pushValue("exec error"sv); + } + S.pushValue(result); + return 3; + } + + S.pushValue(true); + return 1; + } static int close(lua_State* L) { lua::Stack S(L); auto* self = as(L, 1); @@ -180,6 +268,8 @@ namespace { } return 1; } + + #undef verify }; void Database::registerClass(lua_State* L) { @@ -187,6 +277,7 @@ namespace { lua::Stack S(L); auto const class_table = S.pushModule(class_name); + S.setMapValue(class_table, "exec"sv, &DatabaseBinding::exec); S.setMapValue(class_table, "close"sv, &DatabaseBinding::close); S.setMapValue(class_table, "open"sv, &DatabaseBinding::open); From 31c8ff81b15b453dac60abc4c0b619fe056d4c1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=92=80=E5=A2=83=E7=9F=B3?= Date: Wed, 4 Dec 2024 14:31:36 +0800 Subject: [PATCH 8/8] oops --- engine/CMakeLists.txt | 1 - engine/scriptable/CMakeLists.txt | 36 --- engine/scriptable/core/ScriptEngine.cpp | 18 -- engine/scriptable/core/ScriptEngine.hpp | 35 --- engine/scriptable/core/ScriptEngineLua.cpp | 316 --------------------- engine/scriptable/lua/Stack.cpp | 32 --- engine/scriptable/lua/Stack.hpp | 113 -------- engine/scriptable/test/test.cpp | 40 --- engine/scriptable/test/test.lua.txt | 188 ------------ 9 files changed, 779 deletions(-) delete mode 100644 engine/scriptable/CMakeLists.txt delete mode 100644 engine/scriptable/core/ScriptEngine.cpp delete mode 100644 engine/scriptable/core/ScriptEngine.hpp delete mode 100644 engine/scriptable/core/ScriptEngineLua.cpp delete mode 100644 engine/scriptable/lua/Stack.cpp delete mode 100644 engine/scriptable/lua/Stack.hpp delete mode 100644 engine/scriptable/test/test.cpp delete mode 100644 engine/scriptable/test/test.lua.txt diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 3f2ac9a6..c88254db 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -1,2 +1 @@ add_subdirectory(configuration) -add_subdirectory(scriptable) diff --git a/engine/scriptable/CMakeLists.txt b/engine/scriptable/CMakeLists.txt deleted file mode 100644 index c168a4cb..00000000 --- a/engine/scriptable/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -set(lib_name LuaSTG.Sub.Scriptable) - -option(LUASTG_LINK_LUAJIT "LuaSTG: link LuaJIT" ON) - -file(GLOB_RECURSE lib_src RELATIVE ${CMAKE_CURRENT_LIST_DIR} - "core/*.hpp" - "core/*.cpp" - "lua/*.hpp" - "lua/*.cpp" -) -source_group(TREE ${CMAKE_CURRENT_LIST_DIR} FILES ${lib_src}) - -add_library(${lib_name} STATIC) -luastg_target_common_options(${lib_name}) -luastg_target_more_warning(${lib_name}) -target_compile_features(${lib_name} PRIVATE cxx_std_20) -if (LUASTG_LINK_LUAJIT) - target_compile_definitions(${lib_name} PRIVATE LUASTG_LINK_LUAJIT) -endif () -target_include_directories(${lib_name} PUBLIC .) -target_sources(${lib_name} PRIVATE ${lib_src}) -target_link_libraries(${lib_name} PUBLIC lua51_static) - -set(lib_test_name LuaSTG.Sub.Scriptable.Test) - -add_executable(${lib_test_name}) -luastg_target_common_options(${lib_test_name}) -luastg_target_more_warning(${lib_test_name}) -target_compile_features(${lib_test_name} PRIVATE cxx_std_20) -target_sources(${lib_test_name} PRIVATE test/test.cpp) -target_link_libraries(${lib_test_name} PRIVATE ${lib_name}) - -add_custom_command(TARGET ${lib_test_name} POST_BUILD - COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different ${CMAKE_CURRENT_LIST_DIR}/test/test.lua.txt $/test.lua - VERBATIM -) diff --git a/engine/scriptable/core/ScriptEngine.cpp b/engine/scriptable/core/ScriptEngine.cpp deleted file mode 100644 index 3fd3dabb..00000000 --- a/engine/scriptable/core/ScriptEngine.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "core/ScriptEngine.hpp" - -namespace core { - void* ScriptEngine::getNativeHandle() { - return handle; - } - - ScriptEngine::ScriptEngine() : handle(nullptr) { - } - ScriptEngine::~ScriptEngine() { - close(); - } - - ScriptEngine& ScriptEngine::getInstance() { - static ScriptEngine instance; - return instance; - } -} diff --git a/engine/scriptable/core/ScriptEngine.hpp b/engine/scriptable/core/ScriptEngine.hpp deleted file mode 100644 index 2d788329..00000000 --- a/engine/scriptable/core/ScriptEngine.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once -#include -#include - -namespace core { - class ScriptEngine { - public: - class CallResult { - friend class ScriptEngine; - public: - inline explicit CallResult(bool status) : message(status ? "" : "unknown error") {} - inline explicit CallResult(std::string_view const& s) : message(s) {} - inline explicit CallResult(std::string&& s) : message(s) {} - inline operator bool() const noexcept { return message.empty(); } - inline std::string const& getMessage() const noexcept { return message; } - private: - std::string message; - }; - public: - CallResult loadFromFile(std::string_view const& path); - CallResult call(std::string_view const& function_name); - bool open(); - void close(); - void* getNativeHandle(); - public: - ScriptEngine(); - ~ScriptEngine(); - public: - static ScriptEngine& getInstance(); - private: - bool registerStackTraceBackHandler(); - private: - void* handle; - }; -} diff --git a/engine/scriptable/core/ScriptEngineLua.cpp b/engine/scriptable/core/ScriptEngineLua.cpp deleted file mode 100644 index e1d07175..00000000 --- a/engine/scriptable/core/ScriptEngineLua.cpp +++ /dev/null @@ -1,316 +0,0 @@ -#include "core/ScriptEngine.hpp" -#include -#include -#include -#include -#include "lua.hpp" -#ifdef LUASTG_LINK_LUAJIT -#include "luajit.h" -#endif - -using namespace std::string_view_literals; - -namespace core { -#define LVM static_cast(handle) - - static void* debug_trackback_key{}; - static void* stack_trackback_function_name{}; - - static int stackTraceBack(lua_State* L) { - std::array buf{}; - std::pmr::monotonic_buffer_resource res(buf.data(), buf.size()); - std::pmr::string msg(&res); - - size_t l{}; - if (char const* m = lua_tolstring(L, 1, &l); m) { - msg.append(m, l); - } - else if (luaL_callmeta(L, 1, "__tostring")) { - m = lua_tolstring(L, -1, &l); - msg.append("(error object is a '"sv); - msg.append(m, l); - msg.append("' value)"sv); - lua_pop(L, 1); - } - else { - msg.append("(error object is a "sv); - if (lua_isnoneornil(L, 1)) { - msg.append("(error object is a nil value)"sv); - } - else if (lua_isboolean(L, 1)) { - msg.append(lua_toboolean(L, 1) ? "true"sv : "false"sv); - } - else if (lua_istable(L, 1)) { - msg.append("table"sv); - } - else if (lua_islightuserdata(L, 1)) { - msg.append("light userdata"sv); - } - else if (lua_isuserdata(L, 1)) { - msg.append("userdata"sv); - } - else if (lua_iscfunction(L, 1)) { - msg.append("C function"sv); - } - else if (lua_isfunction(L, 1)) { - msg.append("function"sv); - } - else if (lua_isthread(L, 1)) { - msg.append("thread"sv); - } - else { - msg.append("?"sv); - } - msg.append(" value)"sv); - } - - msg.append("\nstack traceback:"sv); - - lua_Debug debug{}; - for (int level = 1; lua_getstack(L, level, &debug); level += 1) { - lua_getinfo(L, "Sfln", &debug); - msg.append("\n\t"sv); - msg.append(debug.short_src); - msg.append(":"sv); - if (debug.currentline > 0) { - char line_number[22]{}; - int n = std::snprintf(line_number, 22, "%d", debug.currentline); - msg.append(line_number, static_cast(n)); - msg.append(":"sv); - } - if (debug.name && debug.name[0]) { - msg.append(" in"sv); - if (debug.namewhat && debug.namewhat[0]) { - msg.append(" ("sv); - msg.append(debug.namewhat); - msg.append(")"sv); - } - msg.append(" function '"sv); - msg.append(debug.name); - msg.append("'"sv); - } - else if (debug.what) { - if (debug.what[0] == 'm' /* "main" */) { - msg.append(" in main chunk"sv); - } - else if (debug.what[0] == 'C' /* "C" */) { - msg.append(" at C"sv); - } - else { - lua_pushlightuserdata(L, &stack_trackback_function_name); - lua_gettable(L, LUA_REGISTRYINDEX); - if (debug.what[0] == 'L' /* "Lua" */ && lua_isstring(L, -1)) { - size_t len{}; - auto const* str = lua_tolstring(L, -1, &len); - msg.append(" in (global) function '"sv); - msg.append(str, len); - msg.append("'"sv); - } - else { - msg.append(" in function <"sv); - msg.append(debug.short_src); - msg.append(":"sv); - char line_number[22]{}; - int n = std::snprintf(line_number, 22, "%d", debug.linedefined); - msg.append(line_number, static_cast(n)); - msg.append(">"sv); - } - lua_pop(L, 1); - } - } - else { - msg.append(" unknown"sv); - } - } - - lua_pushlstring(L, msg.c_str(), msg.length()); - - return 1; - } - - ScriptEngine::CallResult ScriptEngine::loadFromFile(std::string_view const& path) { - assert(handle); - constexpr auto function_name = "dofile"sv; - - // -------------------- get stack traceback function - - lua_pushlightuserdata(LVM, &stackTraceBack); - // ^stack: ... *key - lua_gettable(LVM, LUA_REGISTRYINDEX); - // ^stack: ... trackback - auto const debug_trackback_index = lua_gettop(LVM); - - // -------------------- get calling function from global - - lua_pushlstring(LVM, function_name.data(), function_name.size()); - // ^stack: ... trackback "FUNCTION" - lua_gettable(LVM, LUA_GLOBALSINDEX); - // ^stack: ... trackback FUNCTION? - - // -------------------- call - - lua_pushlstring(LVM, path.data(), path.size()); - // ^stack: ... trackback FUNCTION? "path" - auto const r = lua_pcall(LVM, 1, 0, debug_trackback_index); - // ^stack: ... trackback error? - - // -------------------- check call result - - if (r != 0) { - if (r == LUA_ERRMEM) { - return CallResult("memory allocation error"sv); - } - else if (r == LUA_ERRERR) { - return CallResult("error while running the error handler function"sv); - } - else { - size_t l{}; - if (char const* m = lua_tolstring(LVM, -1, &l); m) { - return CallResult(l > 0 ? std::string_view{ m, l } : "unknown error"sv); - } - else { - return CallResult("(error object is a nil value)"sv); - } - } - } - - // -------------------- clean - - lua_settop(LVM, debug_trackback_index - 1); - // ^stack: ... - - return CallResult(true); - } - ScriptEngine::CallResult ScriptEngine::call(std::string_view const& function_name) { - assert(handle); - - // -------------------- get stack traceback function - - lua_pushlightuserdata(LVM, &stackTraceBack); - // ^stack: ... *key - lua_gettable(LVM, LUA_REGISTRYINDEX); - // ^stack: ... trackback - auto const debug_trackback_index = lua_gettop(LVM); - - // -------------------- set calling function name - - lua_pushlightuserdata(LVM, &stack_trackback_function_name); - // ^stack: ... trackback *key - lua_pushlstring(LVM, function_name.data(), function_name.size()); - // ^stack: ... trackback *key "FUNCTION" - lua_settable(LVM, LUA_REGISTRYINDEX); - // ^stack: ... trackback - - // -------------------- get calling function from global - - lua_pushlstring(LVM, function_name.data(), function_name.size()); - // ^stack: ... trackback "FUNCTION" - lua_gettable(LVM, LUA_GLOBALSINDEX); - // ^stack: ... trackback FUNCTION? - - // -------------------- call - - auto const r = lua_pcall(LVM, 0, 0, debug_trackback_index); - // ^stack: ... trackback error? - - // -------------------- clean calling function name - - lua_pushlightuserdata(LVM, &stack_trackback_function_name); - // ^stack: ... trackback error? *key - lua_pushnil(LVM); - // ^stack: ... trackback error? *key nil - lua_settable(LVM, LUA_REGISTRYINDEX); - // ^stack: ... trackback error? - - // -------------------- check call result - - if (r != 0) { - if (r == LUA_ERRMEM) { - return CallResult("memory allocation error"sv); - } - else if (r == LUA_ERRERR) { - return CallResult("error while running the error handler function"sv); - } - else { - size_t l{}; - if (char const* m = lua_tolstring(LVM, -1, &l); m) { - return CallResult(l > 0 ? std::string_view{ m, l } : "unknown error"sv); - } - else { - return CallResult("(error object is a nil value)"sv); - } - } - } - - // -------------------- clean - - lua_settop(LVM, debug_trackback_index - 1); - // ^stack: ... - - return CallResult(true); - } - - bool ScriptEngine::open() { - handle = static_cast(luaL_newstate()); - if (!handle) { - return false; - } - #ifdef LUASTG_LINK_LUAJIT - if (0 == luaJIT_setmode(LVM, 0, LUAJIT_MODE_ENGINE | LUAJIT_MODE_ON)) { - // TODO; write log - } - #endif // LUASTG_LINK_LUAJIT - luaL_openlibs(LVM); - return registerStackTraceBackHandler(); - } - void ScriptEngine::close() { - if (LVM) { - lua_close(LVM); - handle = nullptr; - } - } - - bool ScriptEngine::registerStackTraceBackHandler() { - assert(handle); - - // -------------------- store debug.traceback - - // ^stack: ... - lua_getglobal(LVM, "require"); - // ^stack: ... require - if (!lua_isfunction(LVM, lua_gettop(LVM))) { - return false; - } - lua_pushstring(LVM, "debug"); - // ^stack: ... require "debug" - lua_call(LVM, 1, 1); - // ^stack: ... debug? - int const debug_index = lua_gettop(LVM); - if (!lua_istable(LVM, debug_index)) { - return false; - } - // ^stack: ... debug - lua_pushlightuserdata(LVM, &debug_trackback_key); - // ^stack: ... debug *key - lua_getfield(LVM, debug_index, "traceback"); - // ^stack: ... debug *key traceback - if (!lua_isfunction(LVM, debug_index + 2)) { - return false; - } - lua_settable(LVM, LUA_REGISTRYINDEX); - // ^stack: ... debug - lua_pop(LVM, 1); - // ^stack: ... - - // -------------------- register global stack traceback function - - lua_pushlightuserdata(LVM, &stackTraceBack); - // ^stack: ... *key - lua_pushcfunction(LVM, &stackTraceBack); - // ^stack: ... *key stackTraceBack - lua_settable(LVM, LUA_REGISTRYINDEX); - // ^stack: ... - - return true; - } -} diff --git a/engine/scriptable/lua/Stack.cpp b/engine/scriptable/lua/Stack.cpp deleted file mode 100644 index a460a9cb..00000000 --- a/engine/scriptable/lua/Stack.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "lua/Stack.hpp" - -namespace lua { - StackBalancer::StackBalancer(lua_State* L) : L(L), N(lua_gettop(L)) { - } - StackBalancer::~StackBalancer() { lua_settop(L, N); } -} - -namespace lua { - template<> - [[nodiscard]] int32_t Stack::getValue(StackIndex const index) const { - return static_cast(luaL_checkinteger(L, index.value)); - } - - template<> - [[nodiscard]] std::string_view Stack::getValue(StackIndex const index) const { - size_t len{}; - auto str = luaL_checklstring(L, index.value, &len); - // managed by lua VM - // ReSharper disable once CppDFALocalValueEscapesFunction - return { str, len }; - } - - template<> - [[nodiscard]] std::string Stack::getValue(StackIndex const index) const { - size_t len{}; - auto str = luaL_checklstring(L, index.value, &len); - // managed by lua VM - // ReSharper disable once CppDFALocalValueEscapesFunction - return { str, len }; - } -} diff --git a/engine/scriptable/lua/Stack.hpp b/engine/scriptable/lua/Stack.hpp deleted file mode 100644 index 21a9682b..00000000 --- a/engine/scriptable/lua/Stack.hpp +++ /dev/null @@ -1,113 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include "lua.hpp" - -namespace lua { - struct StackIndex { - int32_t value{}; - - inline explicit StackIndex(int32_t const value) noexcept : value(value) { - } - }; - - class StackBalancer { - public: - explicit StackBalancer(lua_State* L); - ~StackBalancer(); - private: - lua_State* L; - int32_t N; - }; - - class Stack { - public: - explicit Stack(lua_State* L) noexcept : L(L) { - } - - [[nodiscard]] StackIndex indexOfTop() const { - return StackIndex(lua_gettop(L)); - } - - void pushValue(std::nullopt_t const) const { - lua_pushnil(L); - } - - void pushValue(bool const value) const { - lua_pushboolean(L, value ? 1 : 0); - } - - void pushValue(std::string_view const& value) const { - lua_pushlstring(L, value.data(), value.length()); - } - - void pushValue(StackIndex const value) const { - lua_pushvalue(L, value.value); - } - - template - [[nodiscard]] T getValue(StackIndex index) const; - - template - [[nodiscard]] T getValue(int32_t const index) const { - return getValue(StackIndex(index)); - } - - // module and class system - - [[nodiscard]] StackIndex pushModule(std::string_view const& name) const { - constexpr luaL_Reg empty[] = { {} }; - luaL_register(L, name.data(), empty); - auto const index = lua_gettop(L); - lua_pushnil(L); - lua_setglobal(L, name.data()); - return StackIndex(index); - } - - [[nodiscard]] StackIndex createMetaTable(std::string_view const& name) const { - luaL_newmetatable(L, name.data()); - return StackIndex(lua_gettop(L)); - } - - void setMetaTable(StackIndex const index, std::string_view const& name) const { - luaL_getmetatable(L, name.data()); - lua_setmetatable(L, index.value); - } - - template - [[nodiscard]] T* createUserData() const { - return static_cast(lua_newuserdata(L, sizeof(T))); - } - - template - [[nodiscard]] T* createUserDataWithNew() const { - auto p = static_cast(lua_newuserdata(L, sizeof(T))); - return new(p) T(); - } - - // map - - [[nodiscard]] StackIndex createMap(size_t const reserve = 0) const { - lua_createtable(L, 0, static_cast(reserve)); - return indexOfTop(); - } - - void setMapValue(StackIndex const index, std::string_view const& key, lua_CFunction const value) const { - lua_pushcfunction(L, value); - lua_setfield(L, index.value, key.data()); - } - - void setMapValue(StackIndex const index, std::string_view const& key, StackIndex const value_index) const { - lua_pushvalue(L, value_index.value); - lua_setfield(L, index.value, key.data()); - } - - private: - lua_State* L{}; - }; - - static_assert(sizeof(Stack) == sizeof(lua_State*)); - static_assert(alignof(Stack) == alignof(lua_State*)); -} diff --git a/engine/scriptable/test/test.cpp b/engine/scriptable/test/test.cpp deleted file mode 100644 index 4294a921..00000000 --- a/engine/scriptable/test/test.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "core/ScriptEngine.hpp" -#include -#include -#include - -using namespace std::string_view_literals; - -int main(int argc, char** argv) { - auto& vm = core::ScriptEngine::getInstance(); - if (!vm.open()) { - return 1; - } - std::filesystem::path const path(argv[0]); - auto const test_path = path.parent_path() / "test.lua"sv; - if (auto const load_result = vm.loadFromFile(test_path.lexically_normal().generic_string()); !load_result) { - std::cout << load_result.getMessage() << std::endl; - } - std::vector tests{ - "test_call_error"sv, - "test_call_error_with_nil"sv, - "test_call_error_with_false"sv, - "test_call_error_with_true"sv, - "test_call_error_with_pi"sv, - "test_call_error_with_hello"sv, - "test_call_error_with_table"sv, - "test_call_error_with_table_x"sv, - "test_call_error_with_userdata"sv, - "call_self"sv, - "call_f0"sv, - }; - for (auto const& test : tests) { - std::cout << "========== test: "sv << test << std::endl; - auto const call_result = vm.call(test); - if (!call_result) { - std::cout << call_result.getMessage() << std::endl; - } - } - vm.close(); - return 0; -} diff --git a/engine/scriptable/test/test.lua.txt b/engine/scriptable/test/test.lua.txt deleted file mode 100644 index c2cac598..00000000 --- a/engine/scriptable/test/test.lua.txt +++ /dev/null @@ -1,188 +0,0 @@ -local function call_error_with_userdata() - local f = io.open("__", "r") - error(f) -end - -function test_call_error_with_userdata() - call_error_with_userdata() -end - -local function call_error_with_table_x() - error(setmetatable({}, { __tostring = function (_) - return "super fuck" - end})) -end - -function test_call_error_with_table_x() - call_error_with_table_x() -end - -local function call_error_with_table() - error({}) -end - -function test_call_error_with_table() - call_error_with_table() -end - -local function call_error_with_hello() - error("hello") -end - -function test_call_error_with_hello() - call_error_with_hello() -end - -local function call_error_with_pi() - error(math.pi) -end - -function test_call_error_with_pi() - call_error_with_pi() -end - -local function call_error_with_true() - error(true) -end - -function test_call_error_with_true() - call_error_with_true() -end - -local function call_error_with_false() - error(false) -end - -function test_call_error_with_false() - call_error_with_false() -end - -local function call_error_with_nil() - error(nil) -end - -function test_call_error_with_nil() - call_error_with_nil() -end - -local function call_error() - error() -end - -function test_call_error() - call_error() -end - -function print_hello_world() - print("Hello world!") -end - -local strsB = {} -local strs = {} - -function call_self_B() - table.insert(strsB, math.random()) - return call_self_A() -end - -function call_self_A() - table.insert(strs, math.random()) - if #strs > 1000 and #strsB > 1000 then - error("fuck you") - end - return call_self_B() -end - -function call_self() - return call_self_A() -end - -function print_strs() - print(strs[1], strsB[2]) -end - - -local function f10() - if math.random() > 0.5 then - print("f10") - end - f11() - print("--") -end -local function f9() - if math.random() > 0.5 then - print("f9") - end - f10() - print("--") -end -local function f8() - if math.random() > 0.5 then - print("f8") - end - f9() - print("--") -end -local function f7() - if math.random() > 0.5 then - print("f7") - end - f8() - print("--") -end -local function f6() - if math.random() > 0.5 then - print("f6") - end - f7() - print("--") -end -local function f5() - if math.random() > 0.5 then - print("f5") - end - f6() - print("--") -end -local function f4() - if math.random() > 0.5 then - print("f4") - end - f5() - print("--") -end -local function f3() - if math.random() > 0.5 then - print("f3") - end - f4() - print("--") -end -local function f2() - if math.random() > 0.5 then - print("f2") - end - f3() - print("--") -end -local function f1() - if math.random() > 0.5 then - print("f1") - end - f2() - print("--") -end -local function f0() - if math.random() > 0.5 then - print("f0") - end - f1() - print("--") -end -function call_f0() - print("----") - f0() - print("----") -end - -call_f0()