From bac4124efe12e89833c3148376a811b0866d110e Mon Sep 17 00:00:00 2001 From: Foereaper Date: Sun, 2 Jun 2024 02:11:01 +0200 Subject: [PATCH] Add file watcher and auto reload Move reload functionality to its own function, ideally we should move the reload command away from the reload hook.. Co-authored-by: robinsch --- ElunaConfig.cpp | 1 + ElunaConfig.h | 1 + ElunaLoader.cpp | 82 +++++++++++++++++++++++++++++++++++++++++++ ElunaLoader.h | 35 ++++++++++++++++++ hooks/PlayerHooks.cpp | 34 ++---------------- 5 files changed, 121 insertions(+), 32 deletions(-) diff --git a/ElunaConfig.cpp b/ElunaConfig.cpp index ce69e41403..b3bcf0adaa 100644 --- a/ElunaConfig.cpp +++ b/ElunaConfig.cpp @@ -31,6 +31,7 @@ void ElunaConfig::Initialize() SetConfig(CONFIG_ELUNA_ENABLED, "Eluna.Enabled", true); SetConfig(CONFIG_ELUNA_COMPATIBILITY_MODE, "Eluna.CompatibilityMode", true); SetConfig(CONFIG_ELUNA_TRACEBACK, "Eluna.TraceBack", false); + SetConfig(CONFIG_ELUNA_SCRIPT_RELOADER, "Eluna.ScriptReloader", false); // Load strings SetConfig(CONFIG_ELUNA_SCRIPT_PATH, "Eluna.ScriptPath", "lua_scripts"); diff --git a/ElunaConfig.h b/ElunaConfig.h index 8d4ee08471..398f039a7e 100644 --- a/ElunaConfig.h +++ b/ElunaConfig.h @@ -14,6 +14,7 @@ enum ElunaConfigBoolValues CONFIG_ELUNA_ENABLED, CONFIG_ELUNA_COMPATIBILITY_MODE, CONFIG_ELUNA_TRACEBACK, + CONFIG_ELUNA_SCRIPT_RELOADER, CONFIG_ELUNA_BOOL_COUNT }; diff --git a/ElunaLoader.cpp b/ElunaLoader.cpp index f1a088ffe0..2a7a6fdfc3 100644 --- a/ElunaLoader.cpp +++ b/ElunaLoader.cpp @@ -36,8 +36,28 @@ extern "C" { #include } +#ifdef TRINITY +void ElunaUpdateListener::handleFileAction(efsw::WatchID, std::string const& dir, std::string const& filename, efsw::Action, std::string oldFilename) +{ + auto const path = fs::absolute(filename, dir); + if (!path.has_extension()) + return; + + std::string ext = path.extension().string(); + std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c) { return std::tolower(c); }); + + if (ext != ".lua" && ext != ".ext") + return; + + sElunaLoader->ReloadElunaForMap(RELOAD_ALL_STATES); +} +#endif + ElunaLoader::ElunaLoader() { +#ifdef TRINITY + lua_scriptWatcher = -1; +#endif } ElunaLoader* ElunaLoader::instance() @@ -48,6 +68,13 @@ ElunaLoader* ElunaLoader::instance() ElunaLoader::~ElunaLoader() { +#ifdef TRINITY + if (lua_scriptWatcher >= 0) + { + lua_fileWatcher.removeWatch(lua_scriptWatcher); + lua_scriptWatcher = -1; + } +#endif } void ElunaLoader::LoadScripts() @@ -263,6 +290,25 @@ void ElunaLoader::ProcessScript(lua_State* L, std::string filename, const std::s ELUNA_LOG_DEBUG("[Eluna]: ProcessScript processed `%s` successfully", fullpath.c_str()); } +#ifdef TRINITY +void ElunaLoader::InitializeFileWatcher() +{ + lua_scriptWatcher = lua_fileWatcher.addWatch(lua_folderpath, &elunaUpdateListener, false); + if (lua_scriptWatcher >= 0) + { + ELUNA_LOG_INFO("[Eluna]: Script reloader is listening on `%s`.", + lua_folderpath.c_str()); + } + else + { + ELUNA_LOG_INFO("[Eluna]: Failed to initialize the script reloader on `%s`.", + lua_folderpath.c_str()); + } + + lua_fileWatcher.watch(); +} +#endif + static bool ScriptPathComparator(const LuaScript& first, const LuaScript& second) { return first.filepath < second.filepath; @@ -283,3 +329,39 @@ bool ElunaLoader::ShouldMapLoadEluna(uint32 id) return (std::find(requiredMaps.begin(), requiredMaps.end(), id) != requiredMaps.end()); } + +void ElunaLoader::ReloadElunaForMap(int mapId) +{ + const int mapid_reload_cache_only = -3; + const int mapid_reload_all = -2; // reserved for reloading all states (default if no args) + const int mapid_reload_global = -1; // reserved for reloading global state + // otherwise reload the state of the specific mapid + // If a mapid is provided but does not match any map or reserved id then only script storage is loaded + + sElunaLoader->LoadScripts(); + if (mapId != mapid_reload_cache_only) + { + if (mapId == mapid_reload_global || mapId == mapid_reload_all) +#ifdef TRINITY + if (sWorld->GetEluna()) + sWorld->GetEluna()->ReloadEluna(); +#else + if (sWorld.GetEluna()) + sWorld.GetEluna()->ReloadEluna(); +#endif + +#ifdef TRINITY + sMapMgr->DoForAllMaps([&](Map* map) +#else + sMapMgr.DoForAllMaps([&](Map* map) +#endif + { + if (mapId == mapid_reload_all || mapId == static_cast(map->GetId())) + { + if (map->GetEluna()) + map->GetEluna()->ReloadEluna(); + } + } + ); + } +} diff --git a/ElunaLoader.h b/ElunaLoader.h index 02428e5552..8d087350ad 100644 --- a/ElunaLoader.h +++ b/ElunaLoader.h @@ -10,11 +10,23 @@ #include "ElunaUtility.h" +#ifdef TRINITY +#include +#include +#endif + extern "C" { #include "lua.h" }; +enum ElunaReloadActions +{ + RELOAD_CACHE_ONLY = -3, + RELOAD_ALL_STATES = -2, + RELOAD_GLOBAL_STATE = -1 +}; + struct LuaScript; class ElunaLoader @@ -37,6 +49,7 @@ class ElunaLoader bool ShouldMapLoadEluna(uint32 mapId); bool CompileScript(lua_State* L, LuaScript& script); static int LoadBytecodeChunk(lua_State* L, uint8* bytes, size_t len, BytecodeBuffer* buffer); + void ReloadElunaForMap(int mapId); // Lua script folder path std::string lua_folderpath; @@ -49,8 +62,30 @@ class ElunaLoader ScriptList lua_extensions; std::vector combined_scripts; std::list requiredMaps; + +#ifdef TRINITY + // efsw file watcher + void InitializeFileWatcher(); + efsw::FileWatcher lua_fileWatcher; + efsw::WatchID lua_scriptWatcher; +#endif }; +#ifdef TRINITY +/// File watcher responsible for watching lua scripts +class ElunaUpdateListener : public efsw::FileWatchListener +{ +public: + ElunaUpdateListener() { } + virtual ~ElunaUpdateListener() { } + + void handleFileAction(efsw::WatchID /*watchid*/, std::string const& dir, + std::string const& filename, efsw::Action /*action*/, std::string oldFilename = "") final override; +}; + +static ElunaUpdateListener elunaUpdateListener; +#endif + #define sElunaLoader ElunaLoader::instance() #endif diff --git a/hooks/PlayerHooks.cpp b/hooks/PlayerHooks.cpp index e70b3f7fc7..26841c8e5e 100644 --- a/hooks/PlayerHooks.cpp +++ b/hooks/PlayerHooks.cpp @@ -80,43 +80,13 @@ bool Eluna::OnCommand(Player* player, const char* text) const std::string reload_command = "reload eluna"; if (reload.find(reload_command) == 0) { - const int mapid_reload_cache_only = -3; - const int mapid_reload_all = -2; // reserved for reloading all states (default if no args) - const int mapid_reload_global = -1; // reserved for reloading global state - // otherwise reload the state of the specific mapid - // If a mapid is provided but does not match any map or reserved id then only script storage is loaded - - int mapId = mapid_reload_all; + int mapId = RELOAD_ALL_STATES; std::string args = reload.substr(reload_command.length()); if (!args.empty()) mapId = strtol(args.c_str(), nullptr, 10); - sElunaLoader->LoadScripts(); - if (mapid_reload_cache_only != mapId) - { - if (mapId == mapid_reload_global || mapId == mapid_reload_all) -#ifdef TRINITY - if (sWorld->GetEluna()) - sWorld->GetEluna()->ReloadEluna(); -#else - if (sWorld.GetEluna()) - sWorld.GetEluna()->ReloadEluna(); -#endif + sElunaLoader->ReloadElunaForMap(mapId); -#ifdef TRINITY - sMapMgr->DoForAllMaps([&](Map* map) -#else - sMapMgr.DoForAllMaps([&](Map* map) -#endif - { - if (mapId == mapid_reload_all || mapId == static_cast(map->GetId())) - { - if (map->GetEluna()) - map->GetEluna()->ReloadEluna(); - } - } - ); - } return false; } }