mirror of
https://github.com/YimMenu/YimMenuV2.git
synced 2026-10-11 01:38:13 +08:00
feat(lua): improve prototype
This commit is contained in:
1 parent
52411d2ebe
commit
b3d741ca51
10 files changed
+520
-29
No files matched your search
@@ -1,4 +1,5 @@
|
||||
#include "ScriptMgr.hpp"
|
||||
#include "core/scripting/LuaManager.hpp"
|
||||
|
||||
namespace YimMenu
|
||||
{
|
||||
@@ -67,7 +68,10 @@ namespace YimMenu
|
||||
|
||||
void ScriptMgr::YieldImpl(std::optional<std::chrono::high_resolution_clock::duration> time)
|
||||
{
|
||||
if (auto script = static_cast<Script*>(GetFiberData()))
|
||||
// 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()))
|
||||
script->Yield(time);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include "LuaScript.hpp"
|
||||
|
||||
namespace YimMenu::Lua
|
||||
{
|
||||
int LatentFunction(lua_State* lua)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,11 @@
|
||||
|
||||
namespace YimMenu
|
||||
{
|
||||
void LuaManager::AddUnloadedScript(std::string_view name, std::string_view path)
|
||||
{
|
||||
m_UnloadedScripts.push_back({std::string(name), std::string(path)});
|
||||
}
|
||||
|
||||
void LuaManager::RegisterLibraryImpl(LuaLibrary* library)
|
||||
{
|
||||
m_Libraries.push_back(library);
|
||||
@@ -42,28 +47,68 @@ namespace YimMenu
|
||||
std::lock_guard lock(m_LoadMutex);
|
||||
while (!m_ScriptsToLoad.empty())
|
||||
{
|
||||
std::erase_if(m_UnloadedScripts, [this](auto& script) {
|
||||
return std::filesystem::equivalent(m_ScriptsToLoad.front(), script.m_Path);
|
||||
});
|
||||
m_LoadedScripts.push_back(std::make_shared<LuaScript>(m_ScriptsToLoad.front()));
|
||||
m_ScriptsToLoad.pop();
|
||||
}
|
||||
|
||||
// 2) remove malfunctioning scripts
|
||||
std::erase_if(m_LoadedScripts, [](auto& script) {
|
||||
auto is_malfunctioning = script->IsMalfunctioning();
|
||||
if (is_malfunctioning)
|
||||
// 2) remove scripts if needed
|
||||
std::erase_if(m_LoadedScripts, [this](auto& script) {
|
||||
if (!script->SafeToUnload())
|
||||
return false;
|
||||
|
||||
bool unload = false;
|
||||
|
||||
if (script->IsMalfunctioning())
|
||||
{
|
||||
script->Invalidate();
|
||||
Notifications::Show("Lua Scripting", std::format("Script {} has been unloaded due to a malfunction", script->GetName()), NotificationType::Warning);
|
||||
return true;
|
||||
unload = true;
|
||||
}
|
||||
else if (script->GetLoadState() == LuaScript::LoadState::WANT_UNLOAD)
|
||||
{
|
||||
unload = true;
|
||||
}
|
||||
else if (script->GetLoadState() == LuaScript::LoadState::WANT_RELOAD)
|
||||
{
|
||||
m_ScriptsToLoad.push(std::string(script->GetPath())); // will be reloaded next tick
|
||||
unload = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
if (unload)
|
||||
{
|
||||
script->MarkUnloaded();
|
||||
AddUnloadedScript(script->GetName(), script->GetPath());
|
||||
}
|
||||
|
||||
return unload;
|
||||
});
|
||||
|
||||
// 3) refresh unloaded scripts (every 10 seconds)
|
||||
if (m_LastRefreshedUnloadedScripts + 10s < std::chrono::system_clock::now())
|
||||
{
|
||||
m_UnloadedScripts.clear();
|
||||
for (const auto& entry : std::filesystem::directory_iterator(scripts_dir.Path()))
|
||||
{
|
||||
if (entry.path().extension() != ".lua")
|
||||
continue;
|
||||
|
||||
for (auto& script : m_LoadedScripts)
|
||||
if (std::filesystem::equivalent(script->GetPath(), entry.path().string()))
|
||||
continue;
|
||||
|
||||
AddUnloadedScript(entry.path().filename().string(), entry.path().string());
|
||||
}
|
||||
m_LastRefreshedUnloadedScripts = std::chrono::system_clock::now();
|
||||
}
|
||||
}
|
||||
|
||||
// 3) run tick coroutines
|
||||
{
|
||||
// TODO!
|
||||
}
|
||||
// 4) run tick coroutines
|
||||
for (auto& script : m_LoadedScripts)
|
||||
if (script->IsValid())
|
||||
script->Tick();
|
||||
|
||||
ScriptMgr::Yield();
|
||||
}
|
||||
}
|
||||
@@ -72,4 +117,31 @@ namespace YimMenu
|
||||
{
|
||||
return GetCurrentThreadId() == m_MainThreadId;
|
||||
}
|
||||
|
||||
void LuaManager::SetRunningCoroutineImpl(lua_State* script)
|
||||
{
|
||||
if (!m_RunningCoroutine || !script)
|
||||
{
|
||||
m_RunningCoroutine = script;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGF(FATAL, "LuaManager::SetRunningCoroutineImpl: {} attempted to enter a coroutine when a coroutine from {} is already running", LuaScript::GetScript(script).GetName(), LuaScript::GetScript(m_RunningCoroutine).GetName());
|
||||
LuaScript::GetScript(script).SetMalfunctioning();
|
||||
}
|
||||
}
|
||||
|
||||
void LuaManager::ForAllLoadedScriptsImpl(ForAllLoadedScriptsCallback callback)
|
||||
{
|
||||
std::lock_guard lock(m_LoadMutex);
|
||||
for (auto& script : m_LoadedScripts)
|
||||
callback(script);
|
||||
}
|
||||
|
||||
void LuaManager::ForAllUnloadedScriptsImpl(ForAllUnloadedScriptsCallback callback)
|
||||
{
|
||||
std::lock_guard lock(m_LoadMutex);
|
||||
for (auto& script : m_UnloadedScripts)
|
||||
callback(script);
|
||||
}
|
||||
}
|
||||
@@ -12,18 +12,36 @@ namespace YimMenu
|
||||
{
|
||||
};
|
||||
|
||||
struct UnloadedScript
|
||||
{
|
||||
std::string m_Name;
|
||||
std::string m_Path;
|
||||
};
|
||||
|
||||
using ForAllLoadedScriptsCallback = void(*)(std::shared_ptr<LuaScript>& script);
|
||||
using ForAllUnloadedScriptsCallback = void(*)(UnloadedScript& script);
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<LuaScript>> m_LoadedScripts;
|
||||
std::vector<UnloadedScript> m_UnloadedScripts;
|
||||
std::chrono::system_clock::time_point m_LastRefreshedUnloadedScripts;
|
||||
std::vector<LuaLibrary*> m_Libraries;
|
||||
std::queue<std::string> m_ScriptsToLoad;
|
||||
std::mutex m_LoadMutex;
|
||||
std::uint32_t m_MainThreadId;
|
||||
lua_State* m_RunningCoroutine = nullptr;
|
||||
|
||||
// m_LoadMutex MUST be locked when calling this function
|
||||
void AddUnloadedScript(std::string_view name, std::string_view path);
|
||||
|
||||
void RegisterLibraryImpl(LuaLibrary* library);
|
||||
void LoadLibrariesImpl(lua_State* state);
|
||||
void LoadScriptImpl(std::string path);
|
||||
void RunScriptImpl();
|
||||
bool IsRunningInMainThreadImpl();
|
||||
void SetRunningCoroutineImpl(lua_State* script);
|
||||
void ForAllLoadedScriptsImpl(ForAllLoadedScriptsCallback callback);
|
||||
void ForAllUnloadedScriptsImpl(ForAllUnloadedScriptsCallback callback);
|
||||
|
||||
static LuaManager& GetInstance()
|
||||
{
|
||||
@@ -56,5 +74,26 @@ namespace YimMenu
|
||||
{
|
||||
return GetInstance().IsRunningInMainThreadImpl();
|
||||
}
|
||||
|
||||
static void SetRunningCoroutine(lua_State* script)
|
||||
{
|
||||
GetInstance().SetRunningCoroutineImpl(script);
|
||||
}
|
||||
|
||||
static lua_State* GetRunningCoroutine()
|
||||
{
|
||||
return GetInstance().m_RunningCoroutine;
|
||||
}
|
||||
|
||||
// these can be safely called from the DX thread
|
||||
static void ForAllLoadedScripts(ForAllLoadedScriptsCallback callback)
|
||||
{
|
||||
GetInstance().ForAllLoadedScriptsImpl(callback);
|
||||
}
|
||||
|
||||
static void ForAllUnloadedScripts(ForAllUnloadedScriptsCallback callback)
|
||||
{
|
||||
GetInstance().ForAllUnloadedScriptsImpl(callback);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -24,38 +24,91 @@ namespace YimMenu
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool LuaScript::CallFunction(int n_args, int n_results)
|
||||
bool LuaScript::CallFunction(int n_args, int n_results, lua_State* override_state)
|
||||
{
|
||||
auto state = override_state;
|
||||
if (!state)
|
||||
state = m_State;
|
||||
|
||||
if (!LuaManager::IsRunningInMainThread())
|
||||
{
|
||||
LOGF(FATAL, "LuaScript::CallFunction: {} attempted to call a Lua function outside the main thread. This is not allowed", m_ModuleName);
|
||||
lua_pop(m_State, 1); // pop the function from the stack since we aren't going to call it
|
||||
lua_pop(state, 1); // pop the function from the stack since we aren't going to call it
|
||||
SetMalfunctioning();
|
||||
return false;
|
||||
}
|
||||
|
||||
lua_pushcfunction(m_State, &ErrorHandler);
|
||||
int handler_index = lua_gettop(m_State) - n_args - 1;
|
||||
lua_insert(m_State, handler_index); // move it before all the args
|
||||
lua_pushcfunction(state, &ErrorHandler);
|
||||
int handler_index = lua_gettop(state) - n_args - 1;
|
||||
lua_insert(state, handler_index); // move it before all the args
|
||||
|
||||
auto result = lua_pcall(m_State, n_args, n_results, handler_index);
|
||||
auto result = lua_pcall(state, n_args, n_results, handler_index);
|
||||
|
||||
if (result == LUA_OK)
|
||||
{
|
||||
// the lua runtime doesn't pop the handler if there's no error
|
||||
lua_remove(m_State, handler_index); // TODO: does this actually work? What happens if your function returns stuff?
|
||||
lua_remove(state, handler_index); // TODO: does this actually work? What happens if your function returns stuff?
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto trace = lua_tostring(m_State, -1);
|
||||
auto trace = lua_tostring(state, -1);
|
||||
LOGF(FATAL, "{}: {}", m_ModuleName, trace);
|
||||
lua_pop(m_State, 1); // pop the stack trace
|
||||
lua_pop(state, 1); // pop the stack trace
|
||||
SetMalfunctioning();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int LuaScript::ResumeCoroutine(int n_args, int n_results, lua_State* coro_state)
|
||||
{
|
||||
if (!LuaManager::IsRunningInMainThread())
|
||||
{
|
||||
LOGF(FATAL, "LuaScript::ResumeCoroutine: {} attempted to resume a Lua coroutine outside the main thread. This is not allowed", m_ModuleName);
|
||||
SetMalfunctioning();
|
||||
return LUA_ERRRUN;
|
||||
}
|
||||
|
||||
auto result = lua_resume(coro_state, n_args);
|
||||
if (result == LUA_OK || result == LUA_YIELD)
|
||||
{
|
||||
// move returns to the main stack
|
||||
|
||||
auto num_rets = lua_gettop(coro_state);
|
||||
if (num_rets != n_results && result == LUA_YIELD)
|
||||
{
|
||||
LOGF(FATAL, "LuaScript::ResumeCoroutine: {} yielded {} values when code expected {} values to be yielded", m_ModuleName, num_rets, n_results);
|
||||
SetMalfunctioning();
|
||||
return LUA_ERRRUN;
|
||||
}
|
||||
|
||||
if (num_rets)
|
||||
lua_xmove(coro_state, m_State, num_rets);
|
||||
}
|
||||
else
|
||||
{
|
||||
// there would be an error object on the top of stack
|
||||
std::string error_msg = lua_tostring(coro_state, -1);
|
||||
lua_pop(coro_state, 1);
|
||||
|
||||
// traceback
|
||||
luaL_traceback(coro_state, coro_state, error_msg.c_str(), 1);
|
||||
LOGF(FATAL, "{}: {}", m_ModuleName, lua_tostring(coro_state, -1));
|
||||
lua_pop(coro_state, 1);
|
||||
|
||||
SetMalfunctioning();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void LuaScript::RemoveScriptCallback(ScriptCallback& callback)
|
||||
{
|
||||
luaL_unref(m_State, LUA_REGISTRYINDEX, callback.m_Coroutine);
|
||||
if (callback.m_Fiber)
|
||||
DeleteFiber(callback.m_Fiber);
|
||||
}
|
||||
|
||||
LuaScript::LuaScript(std::string_view file_name) :
|
||||
m_FileName(file_name),
|
||||
m_ModuleName(std::filesystem::path(file_name).filename().string())
|
||||
@@ -77,7 +130,7 @@ namespace YimMenu
|
||||
|
||||
// we should have a function in the top of stack
|
||||
CallFunction(0, 0);
|
||||
m_IsValid = true;
|
||||
m_LoadState = LoadState::RUNNING;
|
||||
}
|
||||
|
||||
LuaScript::~LuaScript()
|
||||
@@ -89,6 +142,15 @@ namespace YimMenu
|
||||
}
|
||||
}
|
||||
|
||||
bool LuaScript::SafeToUnload()
|
||||
{
|
||||
for (auto& callback : m_ScriptCallbacks)
|
||||
if (callback.m_LastYieldFromCode)
|
||||
return false; // don't unload if we're calling a latent function
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
LuaScript& LuaScript::GetScript(lua_State* state)
|
||||
{
|
||||
lua_getfield(state, LUA_REGISTRYINDEX, "context");
|
||||
@@ -96,4 +158,69 @@ namespace YimMenu
|
||||
lua_pop(state, 1);
|
||||
return *script;
|
||||
}
|
||||
|
||||
void LuaScript::AddScriptCallback(int coro_handle)
|
||||
{
|
||||
ScriptCallback callback;
|
||||
callback.m_Coroutine = coro_handle;
|
||||
callback.m_LastYieldFromCode = false;
|
||||
callback.m_TimeToResume = std::nullopt;
|
||||
callback.m_Fiber = nullptr;
|
||||
callback.m_ParentFiber = nullptr;
|
||||
callback.m_LatentTarget = nullptr;
|
||||
|
||||
// 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)
|
||||
m_QueuedScriptCallbacks.push_back(callback);
|
||||
else
|
||||
m_ScriptCallbacks.push_back(callback);
|
||||
}
|
||||
|
||||
void LuaScript::Yield(lua_State* state, int millis, bool from_code)
|
||||
{
|
||||
lua_pushinteger(state, millis);
|
||||
lua_pushboolean(state, from_code);
|
||||
lua_yield(state, 2);
|
||||
}
|
||||
|
||||
void LuaScript::Tick()
|
||||
{
|
||||
m_RunningScriptCallbacks = true;
|
||||
std::erase_if(m_ScriptCallbacks, [this](ScriptCallback& callback) {
|
||||
if (callback.m_TimeToResume && *callback.m_TimeToResume > std::chrono::high_resolution_clock::now())
|
||||
return false;
|
||||
|
||||
lua_rawgeti(m_State, LUA_REGISTRYINDEX, callback.m_Coroutine);
|
||||
lua_State* coro_state = lua_tothread(m_State, -1);
|
||||
lua_pop(m_State, 1);
|
||||
|
||||
auto state = ResumeCoroutine(0, 2, coro_state);
|
||||
|
||||
if (state != LUA_YIELD)
|
||||
{
|
||||
RemoveScriptCallback(callback);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto time = lua_tointeger(m_State, -2);
|
||||
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.m_LastYieldFromCode = from_code;
|
||||
|
||||
return false;
|
||||
});
|
||||
m_RunningScriptCallbacks = false;
|
||||
|
||||
std::ranges::move(m_QueuedScriptCallbacks, std::back_inserter(m_ScriptCallbacks));
|
||||
m_QueuedScriptCallbacks.clear();
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,42 @@ namespace YimMenu
|
||||
{
|
||||
class LuaScript
|
||||
{
|
||||
lua_State* m_State;
|
||||
public:
|
||||
enum class LoadState
|
||||
{
|
||||
EMPTY,
|
||||
RUNNING,
|
||||
PAUSED,
|
||||
WANT_UNLOAD,
|
||||
WANT_RELOAD,
|
||||
UNLOADED
|
||||
};
|
||||
|
||||
private:
|
||||
struct ScriptCallback
|
||||
{
|
||||
int m_Coroutine;
|
||||
std::optional<std::chrono::high_resolution_clock::time_point> m_TimeToResume;
|
||||
bool m_LastYieldFromCode;
|
||||
PVOID m_Fiber;
|
||||
PVOID m_ParentFiber;
|
||||
lua_CFunction m_LatentTarget;
|
||||
int m_LastReturnValue;
|
||||
};
|
||||
|
||||
LoadState m_LoadState = LoadState::EMPTY;
|
||||
lua_State* m_State = nullptr;
|
||||
std::string m_FileName;
|
||||
std::string m_ModuleName;
|
||||
bool m_IsMalfunctioning; // when this is set to true, the script will be unloaded next tick
|
||||
bool m_IsValid;
|
||||
bool m_IsMalfunctioning = false; // when this is set to true, the script will be unloaded next tick
|
||||
std::vector<ScriptCallback> m_ScriptCallbacks;
|
||||
std::vector<ScriptCallback> m_QueuedScriptCallbacks;
|
||||
bool m_RunningScriptCallbacks = false;
|
||||
|
||||
// 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);
|
||||
bool CallFunction(int n_args, int n_results, lua_State* override_state = nullptr);
|
||||
int ResumeCoroutine(int n_args, int n_results, lua_State* coro_state);
|
||||
void RemoveScriptCallback(ScriptCallback& callback);
|
||||
|
||||
public:
|
||||
LuaScript(std::string_view file_name);
|
||||
@@ -25,12 +53,12 @@ namespace YimMenu
|
||||
|
||||
bool IsValid() const
|
||||
{
|
||||
return m_IsValid;
|
||||
return m_LoadState == LoadState::RUNNING;
|
||||
}
|
||||
|
||||
void Invalidate()
|
||||
LoadState GetLoadState() const
|
||||
{
|
||||
m_IsValid = false;
|
||||
return m_LoadState;
|
||||
}
|
||||
|
||||
void SetMalfunctioning()
|
||||
@@ -38,12 +66,57 @@ namespace YimMenu
|
||||
m_IsMalfunctioning = true;
|
||||
}
|
||||
|
||||
void Unload()
|
||||
{
|
||||
if (m_LoadState == LoadState::RUNNING)
|
||||
m_LoadState = LoadState::WANT_UNLOAD;
|
||||
}
|
||||
|
||||
void Reload()
|
||||
{
|
||||
if (m_LoadState == LoadState::RUNNING)
|
||||
m_LoadState = LoadState::WANT_RELOAD;
|
||||
}
|
||||
|
||||
void Pause()
|
||||
{
|
||||
if (m_LoadState == LoadState::RUNNING)
|
||||
m_LoadState = LoadState::PAUSED;
|
||||
}
|
||||
|
||||
void Resume()
|
||||
{
|
||||
if (m_LoadState == LoadState::PAUSED)
|
||||
m_LoadState = LoadState::RUNNING;
|
||||
}
|
||||
|
||||
// Should only be called by LuaManager::RunScriptImpl
|
||||
void MarkUnloaded()
|
||||
{
|
||||
m_LoadState = LoadState::UNLOADED;
|
||||
}
|
||||
|
||||
bool SafeToUnload();
|
||||
|
||||
std::string_view GetName()
|
||||
{
|
||||
return m_ModuleName;
|
||||
}
|
||||
|
||||
std::string_view GetPath()
|
||||
{
|
||||
return m_FileName;
|
||||
}
|
||||
|
||||
// we're guaranteed to have a LuaScript for each lua_State, so we can return it as a reference
|
||||
static LuaScript& GetScript(lua_State* state);
|
||||
|
||||
|
||||
void AddScriptCallback(int coro_handle);
|
||||
|
||||
// must be called from a coroutine
|
||||
void Yield(lua_State* state, int millis = 0, bool from_code = true);
|
||||
|
||||
void Tick();
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
#include "core/scripting/LuaLibrary.hpp"
|
||||
#include "core/scripting/LuaScript.hpp"
|
||||
|
||||
namespace YimMenu::Lua
|
||||
{
|
||||
static bool IsCoroutine(lua_State* state)
|
||||
{
|
||||
bool result = lua_pushthread(state);
|
||||
lua_pop(state, 1);
|
||||
return !result; // this is dumb af. Why do you need to push a value to the stack to figure out if you're in the main thread?
|
||||
}
|
||||
|
||||
class Script : LuaLibrary
|
||||
{
|
||||
using LuaLibrary::LuaLibrary;
|
||||
|
||||
static int RunInCallback(lua_State* state)
|
||||
{
|
||||
auto& script = LuaScript::GetScript(state);
|
||||
|
||||
luaL_checktype(state, 1, LUA_TFUNCTION); // will throw error if a1 isn't a function. not sure what happens if you don't pass any parameters
|
||||
|
||||
lua_State* coro_state = lua_newthread(state);
|
||||
lua_pushvalue(state, 1); // xmove can only move from top of stack, so we have to push the function again even if it's already in the stack
|
||||
lua_xmove(state, coro_state, 1);
|
||||
|
||||
auto coro_handle = luaL_ref(state, LUA_REGISTRYINDEX);
|
||||
script.AddScriptCallback(coro_handle);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int Yield(lua_State* state)
|
||||
{
|
||||
auto& script = LuaScript::GetScript(state);
|
||||
|
||||
if (!IsCoroutine(state))
|
||||
{
|
||||
luaL_error(state, "Attempting to yield outside a script callback");
|
||||
}
|
||||
|
||||
auto time = lua_gettop(state) >= 1 ? (int)luaL_checkinteger(state, 1) : 0;
|
||||
script.Yield(state, time, false);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int IsInsideCallback(lua_State* state)
|
||||
{
|
||||
lua_pushboolean(state, IsCoroutine(state));
|
||||
return 1;
|
||||
}
|
||||
|
||||
virtual void Register(lua_State* state) override
|
||||
{
|
||||
lua_newtable(state);
|
||||
|
||||
lua_pushcfunction(state, RunInCallback);
|
||||
lua_setfield(state, -2, "run_in_callback");
|
||||
|
||||
lua_pushcfunction(state, Yield);
|
||||
lua_setfield(state, -2, "yield");
|
||||
|
||||
lua_pushcfunction(state, IsInsideCallback);
|
||||
lua_setfield(state, -2, "is_inside_callback");
|
||||
|
||||
lua_setglobal(state, "script");
|
||||
}
|
||||
};
|
||||
|
||||
Script _Script;
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "game/backend/Self.hpp"
|
||||
#include "game/frontend/items/Items.hpp"
|
||||
#include "game/frontend/items/DrawHotkey.hpp"
|
||||
#include "game/frontend/submenus/Settings/LuaScripts.hpp"
|
||||
|
||||
namespace YimMenu::Submenus
|
||||
{
|
||||
@@ -108,5 +109,6 @@ namespace YimMenu::Submenus
|
||||
AddCategory(std::move(hotkeys));
|
||||
AddCategory(std::move(gui));
|
||||
AddCategory(std::move(game));
|
||||
AddCategory(BuildLuaScriptsMenu());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
#include "LuaScripts.hpp"
|
||||
#include "core/backend/ScriptMgr.hpp"
|
||||
#include "core/backend/FiberPool.hpp"
|
||||
#include "core/scripting/LuaManager.hpp"
|
||||
#include "core/frontend/widgets/imgui_colors.h"
|
||||
|
||||
namespace YimMenu::Submenus
|
||||
{
|
||||
std::shared_ptr<Category> BuildLuaScriptsMenu()
|
||||
{
|
||||
auto menu = std::make_shared<Category>("Lua Scripts");
|
||||
|
||||
static std::shared_ptr<LuaScript> selectedScript;
|
||||
|
||||
menu->AddItem(std::make_unique<ImGuiItem>([] {
|
||||
const float height = 15 * ImGui::GetTextLineHeightWithSpacing();
|
||||
if (ImGui::BeginListBox("##vehicles", {300.f, height}))
|
||||
{
|
||||
static std::optional<std::string> loadingScript;
|
||||
|
||||
LuaManager::ForAllLoadedScripts([](std::shared_ptr<LuaScript>& script) {
|
||||
if (ImGui::Selectable(script->GetName().data(), script == selectedScript))
|
||||
{
|
||||
selectedScript = script;
|
||||
}
|
||||
});
|
||||
|
||||
LuaManager::ForAllUnloadedScripts([](LuaManager::UnloadedScript& script) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::Colors::Gray.Value);
|
||||
if (ImGui::Selectable(script.m_Name.data(), false))
|
||||
{
|
||||
loadingScript = script.m_Path; // can't load the script directly from this callback or it'll deadlock
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
ImGui::SetTooltip("Click to load this script");
|
||||
}
|
||||
});
|
||||
|
||||
if (loadingScript)
|
||||
{
|
||||
LuaManager::LoadScript(*loadingScript);
|
||||
loadingScript = std::nullopt;
|
||||
}
|
||||
|
||||
ImGui::EndListBox();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginGroup();
|
||||
if (selectedScript && selectedScript->GetLoadState() == LuaScript::LoadState::UNLOADED)
|
||||
selectedScript.reset();
|
||||
|
||||
if (selectedScript)
|
||||
{
|
||||
ImGui::Text("%s", selectedScript->GetName().data());
|
||||
|
||||
bool paused = selectedScript->GetLoadState() == LuaScript::LoadState::PAUSED;
|
||||
if (ImGui::Button(paused ? "Pause" : "Resume"))
|
||||
{
|
||||
if (paused)
|
||||
selectedScript->Resume();
|
||||
else
|
||||
selectedScript->Pause();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Reload"))
|
||||
{
|
||||
selectedScript->Reload();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Unload"))
|
||||
{
|
||||
selectedScript->Unload();
|
||||
}
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
}));
|
||||
|
||||
return menu;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#include "core/frontend/manager/Category.hpp"
|
||||
#include "game/frontend/items/Items.hpp"
|
||||
|
||||
namespace YimMenu::Submenus
|
||||
{
|
||||
std::shared_ptr<Category> BuildLuaScriptsMenu();
|
||||
}
|
||||
Reference in new issue
Block a user