feat(lua): implement latent functions

This commit is contained in:
maybegreat48
2025-06-26 11:15:56 -04:00
parent b3d741ca51
commit c9e4e74acf
6 changed files with 149 additions and 14 deletions
-1
View File
@@ -68,7 +68,6 @@ namespace YimMenu
void ScriptMgr::YieldImpl(std::optional<std::chrono::high_resolution_clock::duration> time)
{
// TODO: this wouldn't work!!! fix ASAP!
if (auto script = LuaManager::GetRunningCoroutine())
LuaScript::GetScript(script).Yield(script, time ? std::chrono::duration_cast<std::chrono::milliseconds>(*time).count() : 0);
else if (auto script = static_cast<Script*>(GetFiberData()))
+38
View File
@@ -0,0 +1,38 @@
#include "LatentFunction.hpp"
namespace YimMenu::Lua
{
static void LatentFiber(LPVOID parameter)
{
auto callback = reinterpret_cast<LuaScript::ScriptCallback*>(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;
}
}
+5 -2
View File
@@ -3,8 +3,11 @@
namespace YimMenu::Lua
{
int LatentFunction(lua_State* lua)
int LatentFunctionInternal(lua_State* state, lua_CFunction func);
template<lua_CFunction function>
int LatentFunction(lua_State* state)
{
return LatentFunctionInternal(state, function);
}
}
+62 -10
View File
@@ -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::high_resolution_clock::duration>(std::chrono::milliseconds(static_cast<std::uint64_t>(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::high_resolution_clock::duration>(std::chrono::milliseconds(static_cast<std::uint64_t>(millis)));
}
}
}
+10 -1
View File
@@ -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<ScriptCallback> m_ScriptCallbacks;
std::vector<ScriptCallback> 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;
}
};
}
+34
View File
@@ -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<SpawnVehicle>);
lua_setfield(state, -2, "spawn_vehicle");
lua_setglobal(state, "internal");
}
};
Internal _Internal;
}