diff --git a/src/core/backend/ScriptMgr.cpp b/src/core/backend/ScriptMgr.cpp index 97987b2..e663bf3 100644 --- a/src/core/backend/ScriptMgr.cpp +++ b/src/core/backend/ScriptMgr.cpp @@ -68,7 +68,6 @@ namespace YimMenu void ScriptMgr::YieldImpl(std::optional time) { - // TODO: this wouldn't work!!! fix ASAP! if (auto script = LuaManager::GetRunningCoroutine()) LuaScript::GetScript(script).Yield(script, time ? std::chrono::duration_cast(*time).count() : 0); else if (auto script = static_cast(GetFiberData())) diff --git a/src/core/scripting/LatentFunction.cpp b/src/core/scripting/LatentFunction.cpp new file mode 100644 index 0000000..7be5414 --- /dev/null +++ b/src/core/scripting/LatentFunction.cpp @@ -0,0 +1,38 @@ +#include "LatentFunction.hpp" + +namespace YimMenu::Lua +{ + static void LatentFiber(LPVOID parameter) + { + auto callback = reinterpret_cast(parameter); + callback->m_LastReturnValue = callback->m_LatentTarget(callback->m_CoroState); + SwitchToFiber(callback->m_ParentFiber); + } + + int LatentFunctionInternal(lua_State* state, lua_CFunction func) + { + auto& script = LuaScript::GetScript(state); + auto callback = script.GetRunningCallback(); + + if (!callback) + luaL_error(state, "Attempted to call a latent function outside a script callback"); + + // destroy remnants from a previous latent function call + if (callback->m_Fiber) + { + DeleteFiber(callback->m_Fiber); + callback->m_Fiber = nullptr; + } + + callback->m_Fiber = CreateFiber(0, &LatentFiber, callback); // TODO: maybe create a latent function fiber pool? + callback->m_ParentFiber = GetCurrentFiber(); // the scripting system has already turned the main thread into a fiber + callback->m_LatentTarget = func; + callback->m_CoroState = state; + callback->m_LastReturnValue = -1; + + SwitchToFiber(callback->m_Fiber); // switch to the new fiber + // we'll be back eventually + + return script.GetRunningCallback()->m_LastReturnValue; + } +} \ No newline at end of file diff --git a/src/core/scripting/LatentFunction.hpp b/src/core/scripting/LatentFunction.hpp index d1d989c..769e634 100644 --- a/src/core/scripting/LatentFunction.hpp +++ b/src/core/scripting/LatentFunction.hpp @@ -3,8 +3,11 @@ namespace YimMenu::Lua { - int LatentFunction(lua_State* lua) + int LatentFunctionInternal(lua_State* state, lua_CFunction func); + + template + int LatentFunction(lua_State* state) { - + return LatentFunctionInternal(state, function); } } \ No newline at end of file diff --git a/src/core/scripting/LuaScript.cpp b/src/core/scripting/LuaScript.cpp index 7ded134..b442540 100644 --- a/src/core/scripting/LuaScript.cpp +++ b/src/core/scripting/LuaScript.cpp @@ -168,6 +168,8 @@ namespace YimMenu callback.m_Fiber = nullptr; callback.m_ParentFiber = nullptr; callback.m_LatentTarget = nullptr; + callback.m_CoroState = nullptr; + callback.m_LastReturnValue = -1; // we don't want to push any additional callbacks to the main array when we're in the middle of running, and potentially deleting, them if (m_RunningScriptCallbacks) @@ -180,7 +182,18 @@ namespace YimMenu { lua_pushinteger(state, millis); lua_pushboolean(state, from_code); - lua_yield(state, 2); + if (from_code) + { + if (lua_isyieldable(state) && lua_status(state) != LUA_YIELD) // only yield for the first time, since after that we're not yielding for real + lua_yield(state, 2); + GetScript(state).m_CurrentlyExecutingCallback->m_LastReturnValue = -1; + SwitchToFiber(GetScript(state).m_CurrentlyExecutingCallback->m_ParentFiber); + } + else + { + lua_yield(state, 2); + // function must return -1 immediately after this + } } void LuaScript::Tick() @@ -194,7 +207,41 @@ namespace YimMenu lua_State* coro_state = lua_tothread(m_State, -1); lua_pop(m_State, 1); - auto state = ResumeCoroutine(0, 2, coro_state); + int num_args = 0; + + if (callback.m_LastYieldFromCode) + { + // we need to pretend we're running in a coroutine + LuaManager::SetRunningCoroutine(coro_state); + m_CurrentlyExecutingCallback = &callback; + SwitchToFiber(callback.m_Fiber); + m_CurrentlyExecutingCallback = nullptr; + LuaManager::SetRunningCoroutine(nullptr); + + if (callback.m_LastReturnValue < 0) + { + // yielded again into a latent function + auto time = lua_tointeger(m_State, -2); + lua_pop(m_State, 2); + + callback.SetTimeToResume(time); + return false; + } + else + { + // done with this one + callback.m_LastYieldFromCode = false; + num_args = callback.m_LastReturnValue; + } + } + + LuaManager::SetRunningCoroutine(coro_state); + m_CurrentlyExecutingCallback = &callback; + + auto state = ResumeCoroutine(num_args, 2, coro_state); + + m_CurrentlyExecutingCallback = nullptr; + LuaManager::SetRunningCoroutine(nullptr); if (state != LUA_YIELD) { @@ -206,14 +253,7 @@ namespace YimMenu auto from_code = lua_toboolean(m_State, -1); lua_pop(m_State, 2); - if (time == 0) - { - callback.m_TimeToResume = std::nullopt; - } - else - { - callback.m_TimeToResume = std::chrono::high_resolution_clock::now() + std::chrono::duration_cast(std::chrono::milliseconds(static_cast(time))); - } + callback.SetTimeToResume(time); callback.m_LastYieldFromCode = from_code; return false; @@ -223,4 +263,16 @@ namespace YimMenu std::ranges::move(m_QueuedScriptCallbacks, std::back_inserter(m_ScriptCallbacks)); m_QueuedScriptCallbacks.clear(); } + + void LuaScript::ScriptCallback::SetTimeToResume(int millis) + { + if (millis == 0) + { + m_TimeToResume = std::nullopt; + } + else + { + m_TimeToResume = std::chrono::high_resolution_clock::now() + std::chrono::duration_cast(std::chrono::milliseconds(static_cast(millis))); + } + } } \ No newline at end of file diff --git a/src/core/scripting/LuaScript.hpp b/src/core/scripting/LuaScript.hpp index 6264b83..b5b1741 100644 --- a/src/core/scripting/LuaScript.hpp +++ b/src/core/scripting/LuaScript.hpp @@ -16,7 +16,6 @@ namespace YimMenu UNLOADED }; - private: struct ScriptCallback { int m_Coroutine; @@ -25,9 +24,13 @@ namespace YimMenu PVOID m_Fiber; PVOID m_ParentFiber; lua_CFunction m_LatentTarget; + lua_State* m_CoroState; int m_LastReturnValue; + + void SetTimeToResume(int millis); }; + private: LoadState m_LoadState = LoadState::EMPTY; lua_State* m_State = nullptr; std::string m_FileName; @@ -36,6 +39,7 @@ namespace YimMenu std::vector m_ScriptCallbacks; std::vector m_QueuedScriptCallbacks; bool m_RunningScriptCallbacks = false; + ScriptCallback* m_CurrentlyExecutingCallback = nullptr; // Calls the function at the top of stack. If this returns false the stack would have nothing on it bool CallFunction(int n_args, int n_results, lua_State* override_state = nullptr); @@ -118,5 +122,10 @@ namespace YimMenu void Yield(lua_State* state, int millis = 0, bool from_code = true); void Tick(); + + ScriptCallback* GetRunningCallback() + { + return m_CurrentlyExecutingCallback; + } }; } \ No newline at end of file diff --git a/src/core/scripting/libraries/Internal.cpp b/src/core/scripting/libraries/Internal.cpp new file mode 100644 index 0000000..cf8e902 --- /dev/null +++ b/src/core/scripting/libraries/Internal.cpp @@ -0,0 +1,34 @@ +#include "core/scripting/LuaLibrary.hpp" +#include "core/scripting/LuaScript.hpp" +#include "core/scripting/LatentFunction.hpp" +#include "game/gta/Vehicle.hpp" +#include "game/backend/Self.hpp" +#include "core/util/Joaat.hpp" + +namespace YimMenu::Lua +{ + // only use for internal testing + class Internal : LuaLibrary + { + using LuaLibrary::LuaLibrary; + + static int SpawnVehicle(lua_State* state) + { + const char* model = luaL_checkstring(state, 1); + Vehicle::Create(Joaat(model), Self::GetPed().GetPosition()); + return 0; + } + + virtual void Register(lua_State* state) override + { + lua_newtable(state); + + lua_pushcfunction(state, LatentFunction); + lua_setfield(state, -2, "spawn_vehicle"); + + lua_setglobal(state, "internal"); + } + }; + + Internal _Internal; +} \ No newline at end of file