From a49082fa8c94899a26c3818971731f73452e6362 Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 5 Mar 2024 16:44:45 -0800 Subject: [PATCH] Lua: Add LuaLoader, Lua API improvements --- CMakeLists.txt | 56 ++- cmake.toml | 23 +- lua-api/Main.cpp | 16 +- lua-api/{ => lib/include}/ScriptContext.hpp | 47 ++- lua-api/lib/include/ScriptState.hpp | 69 ++++ lua-api/{ => lib/src}/ScriptContext.cpp | 380 +++++++++++++------- lua-api/lib/src/ScriptState.cpp | 187 ++++++++++ src/Mods.cpp | 2 + src/mods/LuaLoader.cpp | 269 ++++++++++++++ src/mods/LuaLoader.hpp | 121 +++++++ src/mods/PluginLoader.cpp | 1 - src/mods/PluginLoader.hpp | 2 + 12 files changed, 1025 insertions(+), 148 deletions(-) rename lua-api/{ => lib/include}/ScriptContext.hpp (73%) create mode 100644 lua-api/lib/include/ScriptState.hpp rename lua-api/{ => lib/src}/ScriptContext.cpp (59%) create mode 100644 lua-api/lib/src/ScriptState.cpp create mode 100644 src/mods/LuaLoader.cpp create mode 100644 src/mods/LuaLoader.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e0df61f..de9a1f22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -590,14 +590,60 @@ target_include_directories(sol2 INTERFACE unset(CMKR_TARGET) unset(CMKR_SOURCES) +# Target luavrlib +set(CMKR_TARGET luavrlib) +set(luavrlib_SOURCES "") + +list(APPEND luavrlib_SOURCES + "lua-api/lib/src/ScriptContext.cpp" + "lua-api/lib/src/ScriptState.cpp" + "lua-api/lib/include/ScriptContext.hpp" + "lua-api/lib/include/ScriptState.hpp" +) + +list(APPEND luavrlib_SOURCES + cmake.toml +) + +set(CMKR_SOURCES ${luavrlib_SOURCES}) +add_library(luavrlib STATIC) + +if(luavrlib_SOURCES) + target_sources(luavrlib PRIVATE ${luavrlib_SOURCES}) +endif() + +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${luavrlib_SOURCES}) + +target_compile_features(luavrlib PUBLIC + cxx_std_23 +) + +target_compile_options(luavrlib PUBLIC + "/bigobj" + "/EHa" + "/MP" +) + +target_include_directories(luavrlib PUBLIC + "include/" + "lua-api/lib/include" +) + +target_link_libraries(luavrlib PUBLIC + lua + sol2 + kananlib +) + +unset(CMKR_TARGET) +unset(CMKR_SOURCES) + # Target LuaVR set(CMKR_TARGET LuaVR) set(LuaVR_SOURCES "") list(APPEND LuaVR_SOURCES "lua-api/Main.cpp" - "lua-api/ScriptContext.cpp" - "lua-api/ScriptContext.hpp" ) list(APPEND LuaVR_SOURCES @@ -628,9 +674,7 @@ target_include_directories(LuaVR PUBLIC ) target_link_libraries(LuaVR PUBLIC - lua - sol2 - kananlib + luavrlib ) set_target_properties(LuaVR PROPERTIES @@ -669,6 +713,7 @@ list(APPEND uevr_SOURCES "src/hooks/XInputHook.cpp" "src/mods/FrameworkConfig.cpp" "src/mods/ImGuiThemeHelpers.cpp" + "src/mods/LuaLoader.cpp" "src/mods/PluginLoader.cpp" "src/mods/UObjectHook.cpp" "src/mods/VR.cpp" @@ -787,6 +832,7 @@ target_link_libraries(uevr PUBLIC DirectXTK12 sdkgenny asmjit + luavrlib ) target_link_libraries(uevr PUBLIC diff --git a/cmake.toml b/cmake.toml index 1651452e..ddb64c11 100644 --- a/cmake.toml +++ b/cmake.toml @@ -192,19 +192,29 @@ include-directories = ["dependencies/lua/src"] type = "interface" include-directories = ["dependencies/sol2/single/single/include"] -[target.LuaVR] -type = "shared" +[target.luavrlib] +type = "static" compile-features = ["cxx_std_23"] compile-options = ["/bigobj", "/EHa", "/MP"] -include-directories = ["include/"] -sources = ["lua-api/**.cpp", "lua-api/**.c"] -headers = ["lua-api/**.hpp", "lua-api/**.h"] +include-directories = ["include/", "lua-api/lib/include"] +sources = ["lua-api/lib/**.cpp", "lua-api/lib/**.c"] +headers = ["lua-api/lib/**.hpp", "lua-api/lib/**.h"] link-libraries = [ "lua", "sol2", "kananlib" ] +[target.LuaVR] +type = "shared" +compile-features = ["cxx_std_23"] +compile-options = ["/bigobj", "/EHa", "/MP"] +include-directories = ["include/"] +sources = ["lua-api/Main.cpp"] +link-libraries = [ + "luavrlib" +] + [target.LuaVR.properties] RUNTIME_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO = "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" @@ -241,7 +251,8 @@ link-libraries = [ "DirectXTK", "DirectXTK12", "sdkgenny", - "asmjit" + "asmjit", + "luavrlib" ] [template.ue4template.properties] diff --git a/lua-api/Main.cpp b/lua-api/Main.cpp index 666c961f..47bfb941 100644 --- a/lua-api/Main.cpp +++ b/lua-api/Main.cpp @@ -1,24 +1,26 @@ // Lua API to expose UEVR functionality to Lua scripts via UE4SS #include -#include "ScriptContext.hpp" +#include "lib/include/ScriptContext.hpp" + +std::shared_ptr g_script_context{}; // Main exported function that takes in the lua_State* extern "C" __declspec(dllexport) int luaopen_LuaVR(lua_State* L) { luaL_checkversion(L); - ScriptContext::log("Initializing LuaVR..."); + uevr::ScriptContext::log("Initializing LuaVR..."); - auto script_context = ScriptContext::reinitialize(L); + g_script_context = uevr::ScriptContext::create(L); - if (!script_context->valid()) { - ScriptContext::log("LuaVR failed to initialize! Make sure to inject VR first!"); + if (!g_script_context->valid()) { + uevr::ScriptContext::log("LuaVR failed to initialize! Make sure to inject VR first!"); return 0; } - ScriptContext::log("LuaVR initialized!"); + uevr::ScriptContext::log("LuaVR initialized!"); - return script_context->setup_bindings(); + return g_script_context->setup_bindings(); } BOOL APIENTRY DllMain(HMODULE module, DWORD ul_reason_for_call, LPVOID reserved) { diff --git a/lua-api/ScriptContext.hpp b/lua-api/lib/include/ScriptContext.hpp similarity index 73% rename from lua-api/ScriptContext.hpp rename to lua-api/lib/include/ScriptContext.hpp index 2a48a100..16dbbe24 100644 --- a/lua-api/ScriptContext.hpp +++ b/lua-api/lib/include/ScriptContext.hpp @@ -8,10 +8,12 @@ #include -class ScriptContext { +namespace uevr { +class ScriptContext : public std::enable_shared_from_this { public: - static std::shared_ptr get(); - static std::shared_ptr reinitialize(lua_State* l, UEVR_PluginInitializeParam* param = nullptr); + static std::shared_ptr create(lua_State* l, UEVR_PluginInitializeParam* param = nullptr) { + return std::make_shared(l, param); + } ScriptContext(lua_State* l, UEVR_PluginInitializeParam* param = nullptr); @@ -54,6 +56,34 @@ class ScriptContext { } } + auto& get_mutex() { + return m_mtx; + } + + void script_reset() { + std::scoped_lock _{m_mtx}; + + for (auto& cb : m_on_script_reset_callbacks) { + handle_protected_result(cb()); + } + } + + void frame() { + std::scoped_lock _{m_mtx}; + + for (auto& cb : m_on_frame_callbacks) { + handle_protected_result(cb()); + } + } + + void draw_ui() { + std::scoped_lock _{m_mtx}; + + for (auto& cb : m_on_draw_ui_callbacks) { + handle_protected_result(cb()); + } + } + private: std::vector m_callbacks_to_remove{}; @@ -69,6 +99,11 @@ class ScriptContext { std::vector m_on_pre_viewport_client_draw_callbacks{}; std::vector m_on_post_viewport_client_draw_callbacks{}; + // Custom UEVR callbacks + std::vector m_on_frame_callbacks{}; + std::vector m_on_draw_ui_callbacks{}; + std::vector m_on_script_reset_callbacks{}; + static void on_pre_engine_tick(UEVR_UGameEngineHandle engine, float delta_seconds); static void on_post_engine_tick(UEVR_UGameEngineHandle engine, float delta_seconds); static void on_pre_slate_draw_window_render_thread(UEVR_FSlateRHIRendererHandle renderer, UEVR_FViewportInfoHandle viewport_info); @@ -77,4 +112,8 @@ class ScriptContext { static void on_post_calculate_stereo_view_offset(UEVR_StereoRenderingDeviceHandle device, int view_index, float world_to_meters, UEVR_Vector3f* position, UEVR_Rotatorf* rotation, bool is_double); static void on_pre_viewport_client_draw(UEVR_UGameViewportClientHandle viewport_client, UEVR_FViewportHandle viewport, UEVR_FCanvasHandle canvas); static void on_post_viewport_client_draw(UEVR_UGameViewportClientHandle viewport_client, UEVR_FViewportHandle viewport, UEVR_FCanvasHandle canvas); -}; \ No newline at end of file + static void on_frame(); + static void on_draw_ui(); + static void on_script_reset(); +}; +} \ No newline at end of file diff --git a/lua-api/lib/include/ScriptState.hpp b/lua-api/lib/include/ScriptState.hpp new file mode 100644 index 00000000..aa70e118 --- /dev/null +++ b/lua-api/lib/include/ScriptState.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include + +#include "ScriptContext.hpp" + +namespace uevr { +class ScriptState { +public: + enum class GarbageCollectionHandler : uint32_t { + UEVR_MANAGED = 0, + LUA_MANAGED = 1, + LAST + }; + + enum class GarbageCollectionType : uint32_t { + STEP = 0, + FULL = 1, + LAST + }; + + enum class GarbageCollectionMode : uint32_t { + GENERATIONAL = 0, + INCREMENTAL = 1, + LAST + }; + + struct GarbageCollectionData { + GarbageCollectionHandler gc_handler{GarbageCollectionHandler::UEVR_MANAGED}; + GarbageCollectionType gc_type{GarbageCollectionType::FULL}; + GarbageCollectionMode gc_mode{GarbageCollectionMode::GENERATIONAL}; + std::chrono::microseconds gc_budget{1000}; + + uint32_t gc_minor_multiplier{1}; + uint32_t gc_major_multiplier{100}; + }; + + ScriptState(const GarbageCollectionData& gc_data, UEVR_PluginInitializeParam* param, bool is_main_state); + ~ScriptState(); + + void run_script(const std::string& p); + sol::protected_function_result handle_protected_result(sol::protected_function_result result); // because protected_functions don't throw + + void gc_data_changed(GarbageCollectionData data); + void on_frame(); + void on_draw_ui(); + void on_script_reset(); + + auto& context() { + return m_context; + } + + auto& lua() { return m_lua; } + +private: + sol::state m_lua{}; + std::shared_ptr m_context{nullptr}; + + GarbageCollectionData m_gc_data{}; + bool m_is_main_state; +}; +} \ No newline at end of file diff --git a/lua-api/ScriptContext.cpp b/lua-api/lib/src/ScriptContext.cpp similarity index 59% rename from lua-api/ScriptContext.cpp rename to lua-api/lib/src/ScriptContext.cpp index e0cdd74c..78e6604b 100644 --- a/lua-api/ScriptContext.cpp +++ b/lua-api/lib/src/ScriptContext.cpp @@ -8,17 +8,39 @@ #include "ScriptContext.hpp" -std::shared_ptr g_script_context{}; +namespace uevr { +class ScriptContexts { +public: + void add(std::shared_ptr ctx) { + std::scoped_lock _{mtx}; + + // Check if the context is already in the list + for (auto& c : list) { + if (c == ctx) { + return; + } + } -std::shared_ptr ScriptContext::get() { - return g_script_context; -} + list.push_back(ctx); + } -std::shared_ptr ScriptContext::reinitialize(lua_State* l, UEVR_PluginInitializeParam* param) { - g_script_context.reset(); - g_script_context = std::make_shared(l, param); - return g_script_context; -} + void remove(std::shared_ptr ctx) { + std::scoped_lock _{mtx}; + list.erase(std::remove(list.begin(), list.end(), ctx), list.end()); + } + + template + void for_each(T&& fn) { + std::scoped_lock _{mtx}; + for (auto& ctx : list) { + fn(ctx); + } + } + +private: + std::vector> list{}; + std::mutex mtx{}; +} g_contexts{}; ScriptContext::ScriptContext(lua_State* l, UEVR_PluginInitializeParam* param) : m_lua{l} @@ -44,6 +66,7 @@ ScriptContext::ScriptContext(lua_State* l, UEVR_PluginInitializeParam* param) ScriptContext::~ScriptContext() { std::scoped_lock _{m_mtx}; + // TODO: this probably does not support multiple states if (m_plugin_initialize_param != nullptr) { for (auto& cb : m_callbacks_to_remove) { m_plugin_initialize_param->functions->remove_callback(cb); @@ -51,6 +74,8 @@ ScriptContext::~ScriptContext() { m_callbacks_to_remove.clear(); } + + g_contexts.remove(shared_from_this()); } void ScriptContext::log(const std::string& message) { @@ -66,52 +91,68 @@ void ScriptContext::setup_callback_bindings() { auto cbs = m_plugin_initialize_param->sdk->callbacks; - g_script_context->add_callback(cbs->on_pre_engine_tick, on_pre_engine_tick); - g_script_context->add_callback(cbs->on_post_engine_tick, on_post_engine_tick); - g_script_context->add_callback(cbs->on_pre_slate_draw_window_render_thread, on_pre_slate_draw_window_render_thread); - g_script_context->add_callback(cbs->on_post_slate_draw_window_render_thread, on_post_slate_draw_window_render_thread); - g_script_context->add_callback(cbs->on_pre_calculate_stereo_view_offset, on_pre_calculate_stereo_view_offset); - g_script_context->add_callback(cbs->on_post_calculate_stereo_view_offset, on_post_calculate_stereo_view_offset); - g_script_context->add_callback(cbs->on_pre_viewport_client_draw, on_pre_viewport_client_draw); - g_script_context->add_callback(cbs->on_post_viewport_client_draw, on_post_viewport_client_draw); + add_callback(cbs->on_pre_engine_tick, on_pre_engine_tick); + add_callback(cbs->on_post_engine_tick, on_post_engine_tick); + add_callback(cbs->on_pre_slate_draw_window_render_thread, on_pre_slate_draw_window_render_thread); + add_callback(cbs->on_post_slate_draw_window_render_thread, on_post_slate_draw_window_render_thread); + add_callback(cbs->on_pre_calculate_stereo_view_offset, on_pre_calculate_stereo_view_offset); + add_callback(cbs->on_post_calculate_stereo_view_offset, on_post_calculate_stereo_view_offset); + add_callback(cbs->on_pre_viewport_client_draw, on_pre_viewport_client_draw); + add_callback(cbs->on_post_viewport_client_draw, on_post_viewport_client_draw); m_lua.new_usertype("UEVR_SDKCallbacks", - "on_pre_engine_tick", [](sol::function fn) { - std::scoped_lock _{ g_script_context->m_mtx }; - g_script_context->m_on_pre_engine_tick_callbacks.push_back(fn); + "on_pre_engine_tick", [this](sol::function fn) { + std::scoped_lock _{ m_mtx }; + m_on_pre_engine_tick_callbacks.push_back(fn); + }, + "on_post_engine_tick", [this](sol::function fn) { + std::scoped_lock _{ m_mtx }; + m_on_post_engine_tick_callbacks.push_back(fn); + }, + "on_pre_slate_draw_window_render_thread", [this](sol::function fn) { + std::scoped_lock _{ m_mtx }; + m_on_pre_slate_draw_window_render_thread_callbacks.push_back(fn); }, - "on_post_engine_tick", [](sol::function fn) { - std::scoped_lock _{ g_script_context->m_mtx }; - g_script_context->m_on_post_engine_tick_callbacks.push_back(fn); + "on_post_slate_draw_window_render_thread", [this](sol::function fn) { + std::scoped_lock _{ m_mtx }; + m_on_post_slate_draw_window_render_thread_callbacks.push_back(fn); }, - "on_pre_slate_draw_window_render_thread", [](sol::function fn) { - std::scoped_lock _{ g_script_context->m_mtx }; - g_script_context->m_on_pre_slate_draw_window_render_thread_callbacks.push_back(fn); + "on_pre_calculate_stereo_view_offset", [this](sol::function fn) { + std::scoped_lock _{ m_mtx }; + m_on_pre_calculate_stereo_view_offset_callbacks.push_back(fn); }, - "on_post_slate_draw_window_render_thread", [](sol::function fn) { - std::scoped_lock _{ g_script_context->m_mtx }; - g_script_context->m_on_post_slate_draw_window_render_thread_callbacks.push_back(fn); + "on_post_calculate_stereo_view_offset", [this](sol::function fn) { + std::scoped_lock _{ m_mtx }; + m_on_post_calculate_stereo_view_offset_callbacks.push_back(fn); }, - "on_pre_calculate_stereo_view_offset", [](sol::function fn) { - std::scoped_lock _{ g_script_context->m_mtx }; - g_script_context->m_on_pre_calculate_stereo_view_offset_callbacks.push_back(fn); + "on_pre_viewport_client_draw", [this](sol::function fn) { + std::scoped_lock _{ m_mtx }; + m_on_pre_viewport_client_draw_callbacks.push_back(fn); }, - "on_post_calculate_stereo_view_offset", [](sol::function fn) { - std::scoped_lock _{ g_script_context->m_mtx }; - g_script_context->m_on_post_calculate_stereo_view_offset_callbacks.push_back(fn); + "on_post_viewport_client_draw", [this](sol::function fn) { + std::scoped_lock _{ m_mtx }; + m_on_post_viewport_client_draw_callbacks.push_back(fn); }, - "on_pre_viewport_client_draw", [](sol::function fn) { - std::scoped_lock _{ g_script_context->m_mtx }; - g_script_context->m_on_pre_viewport_client_draw_callbacks.push_back(fn); + "on_frame", [this](sol::function fn) { + std::scoped_lock _{ m_mtx }; + m_on_frame_callbacks.push_back(fn); }, - "on_post_viewport_client_draw", [](sol::function fn) { - std::scoped_lock _{ g_script_context->m_mtx }; - g_script_context->m_on_post_viewport_client_draw_callbacks.push_back(fn); + "on_draw_ui", [this](sol::function fn) { + std::scoped_lock _{ m_mtx }; + m_on_draw_ui_callbacks.push_back(fn); + }, + "on_script_reset", [this](sol::function fn) { + std::scoped_lock _{ m_mtx }; + m_on_script_reset_callbacks.push_back(fn); } ); } int ScriptContext::setup_bindings() { + g_contexts.add(shared_from_this()); + + m_lua.registry()["uevr_context"] = this; + m_lua.set_function("test_function", ScriptContext::test_function); m_lua.new_usertype("UEVR_PluginInitializeParam", @@ -347,19 +388,6 @@ int ScriptContext::setup_bindings() { "find_command", &uevr::API::FConsoleManager::find_command ); - m_lua.new_usertype("UEVR_API", - "sdk", &uevr::API::sdk, - "find_uobject", &uevr::API::find_uobject, - "get_engine", &uevr::API::get_engine, - "get_player_controller", &uevr::API::get_player_controller, - "get_local_pawn", &uevr::API::get_local_pawn, - "spawn_object", &uevr::API::spawn_object, - "execute_command", &uevr::API::execute_command, - "execute_command_ex", &uevr::API::execute_command_ex, - "get_uobject_array", &uevr::API::get_uobject_array, - "get_console_manager", &uevr::API::get_console_manager - ); - m_lua.new_usertype("UEVR_IConsoleObject", "as_command", &uevr::API::IConsoleObject::as_command ); @@ -396,106 +424,208 @@ int ScriptContext::setup_bindings() { "execute", &uevr::API::IConsoleCommand::execute ); + m_lua.new_usertype("UEVR_FUObjectArray", + "get", &uevr::API::FUObjectArray::get, + "is_chunked", &uevr::API::FUObjectArray::is_chunked, + "is_inlined", &uevr::API::FUObjectArray::is_inlined, + "get_objects_offset", &uevr::API::FUObjectArray::get_objects_offset, + "get_item_distance", &uevr::API::FUObjectArray::get_item_distance, + "get_object_count", &uevr::API::FUObjectArray::get_object_count, + "get_objects_ptr", &uevr::API::FUObjectArray::get_objects_ptr, + "get_object", &uevr::API::FUObjectArray::get_object, + "get_item", &uevr::API::FUObjectArray::get_item + ); + + m_lua.new_usertype("UEVR_API", + "sdk", &uevr::API::sdk, + "find_uobject", &uevr::API::find_uobject, + "get_engine", &uevr::API::get_engine, + "get_player_controller", &uevr::API::get_player_controller, + "get_local_pawn", &uevr::API::get_local_pawn, + "spawn_object", &uevr::API::spawn_object, + "execute_command", &uevr::API::execute_command, + "execute_command_ex", &uevr::API::execute_command_ex, + "get_uobject_array", &uevr::API::get_uobject_array, + "get_console_manager", &uevr::API::get_console_manager + ); + setup_callback_bindings(); auto out = m_lua.create_table(); out["params"] = m_plugin_initialize_param; out["api"] = uevr::API::get().get(); + out["types"] = m_lua.create_table_with( + "UObject", m_lua["UEVR_UObject"], + "UStruct", m_lua["UEVR_UStruct"], + "UClass", m_lua["UEVR_UClass"], + "UFunction", m_lua["UEVR_UFunction"], + "FField", m_lua["UEVR_FField"], + "FFieldClass", m_lua["UEVR_FFieldClass"], + "FConsoleManager", m_lua["UEVR_FConsoleManager"], + "IConsoleObject", m_lua["UEVR_IConsoleObject"], + "IConsoleVariable", m_lua["UEVR_IConsoleVariable"], + "IConsoleCommand", m_lua["UEVR_IConsoleCommand"], + "FName", m_lua["UEVR_FName"], + "FUObjectArray", m_lua["UEVR_FUObjectArray"] + ); + + out["plugin_callbacks"] = m_plugin_initialize_param->callbacks; + out["sdk"] = m_plugin_initialize_param->sdk; return out.push(m_lua.lua_state()); } void ScriptContext::on_pre_engine_tick(UEVR_UGameEngineHandle engine, float delta_seconds) { - std::scoped_lock _{ g_script_context->m_mtx }; - for (auto& fn : g_script_context->m_on_pre_engine_tick_callbacks) try { - g_script_context->handle_protected_result(fn(engine, delta_seconds)); - } catch (const std::exception& e) { - ScriptContext::log("Exception in on_pre_engine_tick: " + std::string(e.what())); - } catch (...) { - ScriptContext::log("Unknown exception in on_pre_engine_tick"); - } + g_contexts.for_each([=](auto ctx) { + std::scoped_lock _{ ctx->m_mtx }; + + for (auto& fn : ctx->m_on_pre_engine_tick_callbacks) try { + ctx->handle_protected_result(fn(engine, delta_seconds)); + } catch (const std::exception& e) { + ScriptContext::log("Exception in on_pre_engine_tick: " + std::string(e.what())); + } catch (...) { + ScriptContext::log("Unknown exception in on_pre_engine_tick"); + } + }); } void ScriptContext::on_post_engine_tick(UEVR_UGameEngineHandle engine, float delta_seconds) { - std::scoped_lock _{ g_script_context->m_mtx }; - - for (auto& fn : g_script_context->m_on_post_engine_tick_callbacks) try { - g_script_context->handle_protected_result(fn(engine, delta_seconds)); - } catch (const std::exception& e) { - ScriptContext::log("Exception in on_post_engine_tick: " + std::string(e.what())); - } catch (...) { - ScriptContext::log("Unknown exception in on_post_engine_tick"); - } + g_contexts.for_each([=](auto ctx) { + std::scoped_lock _{ ctx->m_mtx }; + + for (auto& fn : ctx->m_on_post_engine_tick_callbacks) try { + ctx->handle_protected_result(fn(engine, delta_seconds)); + } catch (const std::exception& e) { + ScriptContext::log("Exception in on_post_engine_tick: " + std::string(e.what())); + } catch (...) { + ScriptContext::log("Unknown exception in on_post_engine_tick"); + } + }); } void ScriptContext::on_pre_slate_draw_window_render_thread(UEVR_FSlateRHIRendererHandle renderer, UEVR_FViewportInfoHandle viewport_info) { - std::scoped_lock _{ g_script_context->m_mtx }; - - for (auto& fn : g_script_context->m_on_pre_slate_draw_window_render_thread_callbacks) try { - g_script_context->handle_protected_result(fn(renderer, viewport_info)); - } catch (const std::exception& e) { - ScriptContext::log("Exception in on_pre_slate_draw_window_render_thread: " + std::string(e.what())); - } catch (...) { - ScriptContext::log("Unknown exception in on_pre_slate_draw_window_render_thread"); - } + g_contexts.for_each([=](auto ctx) { + std::scoped_lock _{ ctx->m_mtx }; + + for (auto& fn : ctx->m_on_pre_slate_draw_window_render_thread_callbacks) try { + ctx->handle_protected_result(fn(renderer, viewport_info)); + } catch (const std::exception& e) { + ScriptContext::log("Exception in on_pre_slate_draw_window_render_thread: " + std::string(e.what())); + } catch (...) { + ScriptContext::log("Unknown exception in on_pre_slate_draw_window_render_thread"); + } + }); } void ScriptContext::on_post_slate_draw_window_render_thread(UEVR_FSlateRHIRendererHandle renderer, UEVR_FViewportInfoHandle viewport_info) { - std::scoped_lock _{ g_script_context->m_mtx }; - - for (auto& fn : g_script_context->m_on_post_slate_draw_window_render_thread_callbacks) try { - g_script_context->handle_protected_result(fn(renderer, viewport_info)); - } catch (const std::exception& e) { - ScriptContext::log("Exception in on_post_slate_draw_window_render_thread: " + std::string(e.what())); - } catch (...) { - ScriptContext::log("Unknown exception in on_post_slate_draw_window_render_thread"); - } + g_contexts.for_each([=](auto ctx) { + std::scoped_lock _{ ctx->m_mtx }; + + for (auto& fn : ctx->m_on_post_slate_draw_window_render_thread_callbacks) try { + ctx->handle_protected_result(fn(renderer, viewport_info)); + } catch (const std::exception& e) { + ScriptContext::log("Exception in on_post_slate_draw_window_render_thread: " + std::string(e.what())); + } catch (...) { + ScriptContext::log("Unknown exception in on_post_slate_draw_window_render_thread"); + } + }); } void ScriptContext::on_pre_calculate_stereo_view_offset(UEVR_StereoRenderingDeviceHandle device, int view_index, float world_to_meters, UEVR_Vector3f* position, UEVR_Rotatorf* rotation, bool is_double) { - std::scoped_lock _{ g_script_context->m_mtx }; - - for (auto& fn : g_script_context->m_on_pre_calculate_stereo_view_offset_callbacks) try { - g_script_context->handle_protected_result(fn(device, view_index, world_to_meters, position, rotation, is_double)); - } catch (const std::exception& e) { - ScriptContext::log("Exception in on_pre_calculate_stereo_view_offset: " + std::string(e.what())); - } catch (...) { - ScriptContext::log("Unknown exception in on_pre_calculate_stereo_view_offset"); - } + g_contexts.for_each([=](auto ctx) { + std::scoped_lock _{ ctx->m_mtx }; + + for (auto& fn : ctx->m_on_pre_calculate_stereo_view_offset_callbacks) try { + ctx->handle_protected_result(fn(device, view_index, world_to_meters, position, rotation, is_double)); + } catch (const std::exception& e) { + ScriptContext::log("Exception in on_pre_calculate_stereo_view_offset: " + std::string(e.what())); + } catch (...) { + ScriptContext::log("Unknown exception in on_pre_calculate_stereo_view_offset"); + } + }); } void ScriptContext::on_post_calculate_stereo_view_offset(UEVR_StereoRenderingDeviceHandle device, int view_index, float world_to_meters, UEVR_Vector3f* position, UEVR_Rotatorf* rotation, bool is_double) { - std::scoped_lock _{ g_script_context->m_mtx }; - - for (auto& fn : g_script_context->m_on_post_calculate_stereo_view_offset_callbacks) try { - g_script_context->handle_protected_result(fn(device, view_index, world_to_meters, position, rotation, is_double)); - } catch (const std::exception& e) { - ScriptContext::log("Exception in on_post_calculate_stereo_view_offset: " + std::string(e.what())); - } catch (...) { - ScriptContext::log("Unknown exception in on_post_calculate_stereo_view_offset"); - } + g_contexts.for_each([=](auto ctx) { + std::scoped_lock _{ ctx->m_mtx }; + + for (auto& fn : ctx->m_on_post_calculate_stereo_view_offset_callbacks) try { + ctx->handle_protected_result(fn(device, view_index, world_to_meters, position, rotation, is_double)); + } catch (const std::exception& e) { + ScriptContext::log("Exception in on_post_calculate_stereo_view_offset: " + std::string(e.what())); + } catch (...) { + ScriptContext::log("Unknown exception in on_post_calculate_stereo_view_offset"); + } + }); } void ScriptContext::on_pre_viewport_client_draw(UEVR_UGameViewportClientHandle viewport_client, UEVR_FViewportHandle viewport, UEVR_FCanvasHandle canvas) { - std::scoped_lock _{ g_script_context->m_mtx }; - - for (auto& fn : g_script_context->m_on_pre_viewport_client_draw_callbacks) try { - g_script_context->handle_protected_result(fn(viewport_client, viewport, canvas)); - } catch (const std::exception& e) { - ScriptContext::log("Exception in on_pre_viewport_client_draw: " + std::string(e.what())); - } catch (...) { - ScriptContext::log("Unknown exception in on_pre_viewport_client_draw"); - } + g_contexts.for_each([=](auto ctx) { + std::scoped_lock _{ ctx->m_mtx }; + + for (auto& fn : ctx->m_on_pre_viewport_client_draw_callbacks) try { + ctx->handle_protected_result(fn(viewport_client, viewport, canvas)); + } catch (const std::exception& e) { + ScriptContext::log("Exception in on_pre_viewport_client_draw: " + std::string(e.what())); + } catch (...) { + ScriptContext::log("Unknown exception in on_pre_viewport_client_draw"); + } + }); } void ScriptContext::on_post_viewport_client_draw(UEVR_UGameViewportClientHandle viewport_client, UEVR_FViewportHandle viewport, UEVR_FCanvasHandle canvas) { - std::scoped_lock _{ g_script_context->m_mtx }; - - for (auto& fn : g_script_context->m_on_post_viewport_client_draw_callbacks) try { - g_script_context->handle_protected_result(fn(viewport_client, viewport, canvas)); - } catch (const std::exception& e) { - ScriptContext::log("Exception in on_post_viewport_client_draw: " + std::string(e.what())); - } catch (...) { - ScriptContext::log("Unknown exception in on_post_viewport_client_draw"); - } + g_contexts.for_each([=](auto ctx) { + std::scoped_lock _{ ctx->m_mtx }; + + for (auto& fn : ctx->m_on_post_viewport_client_draw_callbacks) try { + ctx->handle_protected_result(fn(viewport_client, viewport, canvas)); + } catch (const std::exception& e) { + ScriptContext::log("Exception in on_post_viewport_client_draw: " + std::string(e.what())); + } catch (...) { + ScriptContext::log("Unknown exception in on_post_viewport_client_draw"); + } + }); +} + +void ScriptContext::on_frame() { + g_contexts.for_each([=](auto ctx) { + std::scoped_lock _{ ctx->m_mtx }; + + for (auto& fn : ctx->m_on_frame_callbacks) try { + ctx->handle_protected_result(fn()); + } catch (const std::exception& e) { + ScriptContext::log("Exception in on_frame: " + std::string(e.what())); + } catch (...) { + ScriptContext::log("Unknown exception in on_frame"); + } + }); +} + +void ScriptContext::on_draw_ui() { + g_contexts.for_each([=](auto ctx) { + std::scoped_lock _{ ctx->m_mtx }; + + for (auto& fn : ctx->m_on_draw_ui_callbacks) try { + ctx->handle_protected_result(fn()); + } catch (const std::exception& e) { + ScriptContext::log("Exception in on_draw_ui: " + std::string(e.what())); + } catch (...) { + ScriptContext::log("Unknown exception in on_draw_ui"); + } + }); +} + +void ScriptContext::on_script_reset() { + g_contexts.for_each([=](auto ctx) { + std::scoped_lock _{ ctx->m_mtx }; + + for (auto& fn : ctx->m_on_script_reset_callbacks) try { + ctx->handle_protected_result(fn()); + } catch (const std::exception& e) { + ScriptContext::log("Exception in on_script_reset: " + std::string(e.what())); + } catch (...) { + ScriptContext::log("Unknown exception in on_script_reset"); + } + }); +} } \ No newline at end of file diff --git a/lua-api/lib/src/ScriptState.cpp b/lua-api/lib/src/ScriptState.cpp new file mode 100644 index 00000000..af898fef --- /dev/null +++ b/lua-api/lib/src/ScriptState.cpp @@ -0,0 +1,187 @@ +#include + +#include + +#include "uevr/API.hpp" +#include "ScriptState.hpp" + +namespace api::ue { +void msg(const char* text) { + MessageBoxA(GetForegroundWindow(), text, "LuaLoader Message", MB_ICONINFORMATION | MB_OK); +} +} + +namespace uevr { +ScriptState::ScriptState(const ScriptState::GarbageCollectionData& gc_data, UEVR_PluginInitializeParam* param, bool is_main_state) { + if (param != nullptr) { + uevr::API::initialize(param); + } + + if (param != nullptr && param->functions != nullptr) { + param->functions->log_info("Creating new ScriptState..."); + } + + m_is_main_state = is_main_state; + m_lua.registry()["uevr_state"] = this; + m_lua.open_libraries(sol::lib::base, sol::lib::package, sol::lib::string, sol::lib::math, sol::lib::table, sol::lib::bit32, + sol::lib::utf8, sol::lib::os, sol::lib::coroutine); + + // Disable garbage collection. We will manually do it at the end of each frame. + gc_data_changed(gc_data); + + // Restrict os library + auto os = m_lua["os"]; + os["remove"] = sol::nil; + os["rename"] = sol::nil; + os["execute"] = sol::nil; + os["exit"] = sol::nil; + os["setlocale"] = sol::nil; + os["getenv"] = sol::nil; + + // TODO: Make this actually support multiple states + // This stores a global reference to itself, meaning it doesn't support multiple states + m_context = ScriptContext::create(m_lua.lua_state(), param); + + if (!m_context->valid()) { + if (param != nullptr && param->functions != nullptr) { + param->functions->log_error("Failed to create new ScriptState!"); + } + + return; + } + + const auto result = m_context->setup_bindings(); + + if (result != 0) { + const auto table = sol::stack::pop(m_lua); + m_lua["uevr"] = table; + } +} + +ScriptState::~ScriptState() { + +} + +void ScriptState::run_script(const std::string& p) { + uevr::API::get()->log_info(std::format("Running script {}...", p).c_str()); + + std::string old_package_path = m_lua["package"]["path"]; + std::string old_cpath = m_lua["package"]["cpath"]; + + try { + auto path = std::filesystem::path(p); + auto dir = path.parent_path(); + + std::string package_path = m_lua["package"]["path"]; + std::string cpath = m_lua["package"]["cpath"]; + + package_path = old_package_path + ";" + dir.string() + "/?.lua"; + package_path = package_path + ";" + dir.string() + "/?/init.lua"; + //package_path = package_path + ";" + dir.string() + "/?.dll"; + + cpath = old_cpath + ";" + dir.string() + "/?.dll"; + + m_lua["package"]["path"] = package_path; + m_lua.safe_script_file(p); + } catch (const std::exception& e) { + //LuaLoader::get()->spew_error(e.what()); + api::ue::msg(e.what()); + } catch (...) { + //LuaLoader::get()->spew_error((std::stringstream{} << "Unknown error when running script " << p).str()); + api::ue::msg((std::stringstream{} << "Unknown error when running script " << p).str().c_str()); + } + + m_lua["package"]["path"] = old_package_path; + m_lua["package"]["cpath"] = old_cpath; +} + +void ScriptState::gc_data_changed(GarbageCollectionData data) { + // Handler + switch (data.gc_handler) { + case ScriptState::GarbageCollectionHandler::UEVR_MANAGED: + lua_gc(m_lua, LUA_GCSTOP); + break; + case ScriptState::GarbageCollectionHandler::LUA_MANAGED: + lua_gc(m_lua, LUA_GCRESTART); + break; + default: + lua_gc(m_lua, LUA_GCRESTART); + data.gc_handler = ScriptState::GarbageCollectionHandler::LUA_MANAGED; + break; + } + + // Type + if (data.gc_type >= ScriptState::GarbageCollectionType::LAST) { + data.gc_type = ScriptState::GarbageCollectionType::STEP; + } + + // Mode + if (data.gc_mode >= ScriptState::GarbageCollectionMode::LAST) { + data.gc_mode = ScriptState::GarbageCollectionMode::GENERATIONAL; + } + + switch (data.gc_mode) { + case ScriptState::GarbageCollectionMode::GENERATIONAL: + lua_gc(m_lua, LUA_GCGEN, data.gc_minor_multiplier, data.gc_major_multiplier); + break; + case ScriptState::GarbageCollectionMode::INCREMENTAL: + lua_gc(m_lua, LUA_GCINC); + break; + default: + lua_gc(m_lua, LUA_GCGEN, data.gc_minor_multiplier, data.gc_major_multiplier); + data.gc_mode = ScriptState::GarbageCollectionMode::GENERATIONAL; + break; + } + + m_gc_data = data; +} + +void ScriptState::on_script_reset() { + if (m_context == nullptr) { + return; + } + + m_context->script_reset(); +} + +void ScriptState::on_frame() { + if (m_context != nullptr) { + m_context->frame(); + } + + if (m_gc_data.gc_handler != ScriptState::GarbageCollectionHandler::UEVR_MANAGED) { + return; + } + + // This is thread safe, so we don't need to lock the mutex + switch (m_gc_data.gc_type) { + case ScriptState::GarbageCollectionType::FULL: + lua_gc(m_lua, LUA_GCCOLLECT); + break; + case ScriptState::GarbageCollectionType::STEP: + { + const auto now = std::chrono::high_resolution_clock::now(); + + if (m_gc_data.gc_mode == ScriptState::GarbageCollectionMode::GENERATIONAL) { + lua_gc(m_lua, LUA_GCSTEP, 1); + } else { + while (lua_gc(m_lua, LUA_GCSTEP, 1) == 0) { + if (std::chrono::high_resolution_clock::now() - now >= m_gc_data.gc_budget) { + break; + } + } + } + } + break; + default: + lua_gc(m_lua, LUA_GCCOLLECT); + break; + }; +} + +void ScriptState::on_draw_ui() { + if (m_context != nullptr) { + m_context->draw_ui(); + } +} +} \ No newline at end of file diff --git a/src/Mods.cpp b/src/Mods.cpp index dc637b83..d5d546a4 100644 --- a/src/Mods.cpp +++ b/src/Mods.cpp @@ -5,6 +5,7 @@ #include "mods/FrameworkConfig.hpp" #include "mods/VR.hpp" #include "mods/PluginLoader.hpp" +#include "mods/LuaLoader.hpp" #include "mods/UObjectHook.hpp" #include "Mods.hpp" @@ -14,6 +15,7 @@ Mods::Mods() { m_mods.emplace_back(UObjectHook::get()); m_mods.emplace_back(PluginLoader::get()); + m_mods.emplace_back(LuaLoader::get()); } std::optional Mods::on_initialize() const { diff --git a/src/mods/LuaLoader.cpp b/src/mods/LuaLoader.cpp new file mode 100644 index 00000000..f526fd2d --- /dev/null +++ b/src/mods/LuaLoader.cpp @@ -0,0 +1,269 @@ +#include +#include + +#include "Framework.hpp" +#include "PluginLoader.hpp" +#include "LuaLoader.hpp" + +#include // weird include order because of sol +#include + +std::shared_ptr& LuaLoader::get() { + static auto instance = std::make_shared(); + return instance; +} + +std::optional LuaLoader::on_initialize_d3d_thread() { + // TODO? + return Mod::on_initialize_d3d_thread(); +} + +void LuaLoader::on_config_load(const utility::Config& cfg, bool set_defaults) { + std::scoped_lock _{m_access_mutex}; + + for (IModValue& option : m_options) { + option.config_load(cfg, set_defaults); + } + + if (m_main_state != nullptr) { + m_main_state->gc_data_changed(make_gc_data()); + } +} + +void LuaLoader::on_config_save(utility::Config& cfg) { + std::scoped_lock _{m_access_mutex}; + + for (IModValue& option : m_options) { + option.config_save(cfg); + } + + + // TODO: Add config save callback to ScriptState + if (m_main_state != nullptr) { + //m_main_state->on_config_save(); + } +} + +void LuaLoader::on_frame() { + std::scoped_lock _{m_access_mutex}; + + if (m_needs_first_reset) { + spdlog::info("[LuaLoader] Initializing Lua state for the first time..."); + + // Calling reset_scripts even though the scripts have never been set yet still works. + reset_scripts(); + m_needs_first_reset = false; + + spdlog::info("[LuaLoader] Lua state initialized."); + } + + for (auto state_to_delete : m_states_to_delete) { + std::erase_if(m_states, [&](std::shared_ptr state) { return state->lua().lua_state() == state_to_delete; }); + } + + m_states_to_delete.clear(); + + if (m_main_state == nullptr) { + return; + } + + for (auto &state : m_states) { + state->on_frame(); + } +} + +void LuaLoader::on_draw_ui() { + ImGui::SetNextItemOpen(false, ImGuiCond_::ImGuiCond_Once); + + if (ImGui::CollapsingHeader(get_name().data())) { + if (ImGui::Button("Run script")) { + OPENFILENAME ofn{}; + char file[260]{}; + + ofn.lStructSize = sizeof(ofn); + ofn.hwndOwner = g_framework->get_window(); + ofn.lpstrFile = file; + ofn.nMaxFile = sizeof(file); + ofn.lpstrFilter = "Lua script files (*.lua)\0*.lua\0"; + ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR; + + if (GetOpenFileName(&ofn) != FALSE) { + std::scoped_lock _{ m_access_mutex }; + m_main_state->run_script(file); + m_loaded_scripts.emplace_back(std::filesystem::path{file}.filename().string()); + } + } + + ImGui::SameLine(); + + if (ImGui::Button("Reset scripts")) { + reset_scripts(); + } + + ImGui::SameLine(); + + if (ImGui::Button("Spawn Debug Console")) { + if (!m_console_spawned) { + AllocConsole(); + freopen("CONIN$", "r", stdin); + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); + + m_console_spawned = true; + } + } + + //Garbage collection currently only showing from main lua state, might rework to show total later? + if (ImGui::TreeNode("Garbage Collection Stats")) { + std::scoped_lock _{ m_access_mutex }; + + auto g = G(m_main_state->lua().lua_state()); + const auto bytes_in_use = g->totalbytes + g->GCdebt; + + ImGui::Text("Megabytes in use: %.2f", (float)bytes_in_use / 1024.0f / 1024.0f); + + ImGui::TreePop(); + } + + if (m_gc_handler->draw("Garbage Collection Handler")) { + std::scoped_lock _{ m_access_mutex }; + m_main_state->gc_data_changed(make_gc_data()); + } + + if (m_gc_mode->draw("Garbage Collection Mode")) { + std::scoped_lock _{ m_access_mutex }; + m_main_state->gc_data_changed(make_gc_data()); + } + + if ((uint32_t)m_gc_mode->value() == (uint32_t)ScriptState::GarbageCollectionMode::GENERATIONAL) { + if (m_gc_minor_multiplier->draw("Minor GC Multiplier")) { + std::scoped_lock _{ m_access_mutex }; + m_main_state->gc_data_changed(make_gc_data()); + } + + if (m_gc_major_multiplier->draw("Major GC Multiplier")) { + std::scoped_lock _{ m_access_mutex }; + m_main_state->gc_data_changed(make_gc_data()); + } + } + + if (m_gc_handler->value() == (int32_t)ScriptState::GarbageCollectionHandler::UEVR_MANAGED) { + if (m_gc_type->draw("Garbage Collection Type")) { + std::scoped_lock _{ m_access_mutex }; + m_main_state->gc_data_changed(make_gc_data()); + } + + if ((uint32_t)m_gc_mode->value() != (uint32_t)ScriptState::GarbageCollectionMode::GENERATIONAL) { + if (m_gc_budget->draw("Garbage Collection Budget")) { + std::scoped_lock _{ m_access_mutex }; + m_main_state->gc_data_changed(make_gc_data()); + } + } + } + + m_log_to_disk->draw("Log Lua Errors to Disk"); + + if (!m_last_script_error.empty()) { + std::shared_lock _{m_script_error_mutex}; + + const auto now = std::chrono::system_clock::now(); + const auto diff = now - m_last_script_error_time; + const auto sec = std::chrono::duration(diff).count(); + + ImGui::TextWrapped("Last Error Time: %.2f seconds ago", sec); + + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); + ImGui::TextWrapped("Last Script Error: %s", m_last_script_error.data()); + ImGui::PopStyleColor(); + } else { + ImGui::TextWrapped("No Script Errors... yet!"); + } + + if (!m_known_scripts.empty()) { + ImGui::Text("Known scripts:"); + + for (auto&& name : m_known_scripts) { + if (ImGui::Checkbox(name.data(), &m_loaded_scripts_map[name])) { + reset_scripts(); + break; + } + } + } else { + ImGui::Text("No scripts loaded."); + } + } + + if (ImGui::CollapsingHeader("Script Generated UI")) { + std::scoped_lock _{m_access_mutex}; + + if (m_states.empty()) { + return; + } + + for (auto& state : m_states) { + state->on_draw_ui(); + } + } +} + +void LuaLoader::reset_scripts() { + spdlog::info("[LuaLoader] Resetting scripts..."); + + std::scoped_lock _{ m_access_mutex }; + + { + std::unique_lock _{ m_script_error_mutex }; + m_last_script_error.clear(); + } + + if (!m_states.empty()) { + /*auto& mods = g_framework->get_mods()->get_mods(); + + for (auto& mod : mods) { + mod->on_lua_state_destroyed(m_main_state->lua()); + }*/ + + m_main_state->on_script_reset(); + } + + m_main_state.reset(); + m_states.clear(); + + m_main_state = std::make_shared(make_gc_data(), &g_plugin_initialize_param, true); + m_states.insert(m_states.begin(), m_main_state); + + //callback functions for main lua state creation + /*auto& mods = g_framework->get_mods()->get_mods(); + for (auto& mod : mods) { + mod->on_lua_state_created(m_main_state->lua()); + }*/ + + m_loaded_scripts.clear(); + m_known_scripts.clear(); + + const auto autorun_path = Framework::get_persistent_dir() / "scripts"; + + spdlog::info("[LuaLoader] Creating directories {}", autorun_path.string()); + std::filesystem::create_directories(autorun_path); + spdlog::info("[LuaLoader] Loading scripts..."); + + for (auto&& entry : std::filesystem::directory_iterator{autorun_path}) { + auto&& path = entry.path(); + + if (path.has_extension() && path.extension() == ".lua") { + if (!m_loaded_scripts_map.contains(path.filename().string())) { + m_loaded_scripts_map.emplace(path.filename().string(), true); + } + + if (m_loaded_scripts_map[path.filename().string()] == true) { + m_main_state->run_script(path.string()); + m_loaded_scripts.emplace_back(path.filename().string()); + } + + m_known_scripts.emplace_back(path.filename().string()); + } + } + + std::sort(m_known_scripts.begin(), m_known_scripts.end()); + std::sort(m_loaded_scripts.begin(), m_loaded_scripts.end()); +} \ No newline at end of file diff --git a/src/mods/LuaLoader.hpp b/src/mods/LuaLoader.hpp new file mode 100644 index 00000000..c938964a --- /dev/null +++ b/src/mods/LuaLoader.hpp @@ -0,0 +1,121 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "ScriptContext.hpp" +#include "ScriptState.hpp" + +#include "Mod.hpp" + +using namespace uevr; + +class LuaLoader : public Mod { +public: + static std::shared_ptr& get(); + + std::string_view get_name() const override { return "LuaLoader"; } + bool is_advanced_mod() const override { return true; } + std::optional on_initialize_d3d_thread() override; + void on_draw_ui() override; + void on_frame() override; + + void on_config_load(const utility::Config& cfg, bool set_defaults) override; + void on_config_save(utility::Config& cfg) override; + + + const auto& get_state() { + return m_main_state; + } + + const auto& get_state(int index) { + return m_states[index]; + } + +private: + ScriptState::GarbageCollectionData make_gc_data() const { + ScriptState::GarbageCollectionData data{}; + + data.gc_handler = (decltype(ScriptState::GarbageCollectionData::gc_handler))m_gc_handler->value(); + data.gc_type = (decltype(ScriptState::GarbageCollectionData::gc_type))m_gc_type->value(); + data.gc_mode = (decltype(ScriptState::GarbageCollectionData::gc_mode))m_gc_mode->value(); + data.gc_budget = std::chrono::microseconds{(uint32_t)m_gc_budget->value()}; + data.gc_minor_multiplier = (uint32_t)m_gc_minor_multiplier->value(); + data.gc_major_multiplier = (uint32_t)m_gc_major_multiplier->value(); + + return data; + } + + std::shared_ptr m_main_state{}; + std::vector> m_states{}; + std::recursive_mutex m_access_mutex{}; + + // A list of Lua files that have been explicitly loaded either through the user manually loading the script, or + // because the script was in the autorun directory. + std::vector m_loaded_scripts{}; + std::vector m_known_scripts{}; + std::unordered_map m_loaded_scripts_map{}; + std::vector m_states_to_delete{}; + std::string m_last_script_error{}; + std::shared_mutex m_script_error_mutex{}; + std::chrono::system_clock::time_point m_last_script_error_time{}; + + bool m_console_spawned{false}; + bool m_needs_first_reset{true}; + + const ModToggle::Ptr m_log_to_disk{ ModToggle::create(generate_name("LogToDisk"), false) }; + + const ModCombo::Ptr m_gc_handler { + ModCombo::create(generate_name("GarbageCollectionHandler"), + { + "Managed by UEVR", + "Managed by Lua" + }, (int)ScriptState::GarbageCollectionHandler::UEVR_MANAGED) + }; + + const ModCombo::Ptr m_gc_type { + ModCombo::create(generate_name("GarbageCollectionType"), + { + "Step", + "Full", + }, (int)ScriptState::GarbageCollectionType::STEP) + }; + + const ModCombo::Ptr m_gc_mode { + ModCombo::create(generate_name("GarbageCollectionMode"), + { + "Generational", + "Incremental (Mark & Sweep)", + }, (int)ScriptState::GarbageCollectionMode::GENERATIONAL) + }; + + // Garbage collection budget in microseconds. + const ModSlider::Ptr m_gc_budget { + ModSlider::create(generate_name("GarbageCollectionBudget"), 0.0f, 2000.0f, 1000.0f) + }; + + const ModSlider::Ptr m_gc_minor_multiplier { + ModSlider::create(generate_name("GarbageCollectionMinorMultiplier"), 1.0f, 200.0f, 1.0f) + }; + + const ModSlider::Ptr m_gc_major_multiplier { + ModSlider::create(generate_name("GarbageCollectionMajorMultiplier"), 1.0f, 1000.0f, 100.0f) + }; + + ValueList m_options{ + *m_log_to_disk, + *m_gc_handler, + *m_gc_type, + *m_gc_mode, + *m_gc_budget, + *m_gc_minor_multiplier, + *m_gc_major_multiplier + }; + + // Resets the ScriptState and runs autorun scripts again. + void reset_scripts(); +}; \ No newline at end of file diff --git a/src/mods/PluginLoader.cpp b/src/mods/PluginLoader.cpp index 202af65d..2e0eb37e 100644 --- a/src/mods/PluginLoader.cpp +++ b/src/mods/PluginLoader.cpp @@ -1561,7 +1561,6 @@ void PluginLoader::on_xinput_set_state(uint32_t* retval, uint32_t user_index, XI } } - void PluginLoader::on_pre_engine_tick(sdk::UGameEngine* engine, float delta) { std::shared_lock _{m_api_cb_mtx}; diff --git a/src/mods/PluginLoader.hpp b/src/mods/PluginLoader.hpp index 18d7fa32..d97c2094 100644 --- a/src/mods/PluginLoader.hpp +++ b/src/mods/PluginLoader.hpp @@ -13,6 +13,8 @@ namespace uevr { extern UEVR_RendererData g_renderer_data; } +extern "C" __declspec(dllexport) UEVR_PluginInitializeParam g_plugin_initialize_param; + class PluginLoader : public Mod { public: static std::shared_ptr& get();