mirror of
https://github.com/azerothcore/mod-ale
synced 2025-11-29 15:38:17 +08:00
feat(ElunaFileWatcher): Add file watcher and autoreload (#286)
This commit is contained in:
@@ -38,6 +38,20 @@
|
|||||||
# "/usr/local/lib/lua/%s/?.so;/usr/lib/x86_64-linux-gnu/lua/%s/?.so;/usr/local/lib/lua/%s/loadall.so;"
|
# "/usr/local/lib/lua/%s/?.so;/usr/lib/x86_64-linux-gnu/lua/%s/?.so;/usr/local/lib/lua/%s/loadall.so;"
|
||||||
# Default: ""
|
# Default: ""
|
||||||
#
|
#
|
||||||
|
# Eluna.AutoReload
|
||||||
|
# Description: Enable or disable automatic reloading of Lua scripts when files are modified.
|
||||||
|
# This feature watches the script directory for changes and automatically
|
||||||
|
# triggers a reload when .lua files are added, modified, or deleted.
|
||||||
|
# Useful for development but should be disabled in production environments.
|
||||||
|
# Default: false - (disabled)
|
||||||
|
# true - (enabled)
|
||||||
|
#
|
||||||
|
# Eluna.AutoReloadInterval
|
||||||
|
# Description: Sets the interval in seconds between file system checks for auto-reload.
|
||||||
|
# Lower values provide faster detection but use more CPU resources.
|
||||||
|
# Higher values reduce CPU usage but increase detection delay.
|
||||||
|
# Default: 1 - (check every 1 second)
|
||||||
|
#
|
||||||
# Eluna.BytecodeCache
|
# Eluna.BytecodeCache
|
||||||
# Description: Enable or disable bytecode caching for improved performance.
|
# Description: Enable or disable bytecode caching for improved performance.
|
||||||
# When enabled, Lua/MoonScript files are compiled to bytecode and cached in memory.
|
# When enabled, Lua/MoonScript files are compiled to bytecode and cached in memory.
|
||||||
@@ -52,6 +66,8 @@ Eluna.ScriptPath = "lua_scripts"
|
|||||||
Eluna.PlayerAnnounceReload = false
|
Eluna.PlayerAnnounceReload = false
|
||||||
Eluna.RequirePaths = ""
|
Eluna.RequirePaths = ""
|
||||||
Eluna.RequireCPaths = ""
|
Eluna.RequireCPaths = ""
|
||||||
|
Eluna.AutoReload = false
|
||||||
|
Eluna.AutoReloadInterval = 1
|
||||||
Eluna.BytecodeCache = true
|
Eluna.BytecodeCache = true
|
||||||
|
|
||||||
###################################################################################################
|
###################################################################################################
|
||||||
|
|||||||
216
src/LuaEngine/ElunaFileWatcher.cpp
Normal file
216
src/LuaEngine/ElunaFileWatcher.cpp
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 - 2016 Eluna Lua Engine <http://emudevs.com/>
|
||||||
|
* This program is free software licensed under GPL version 3
|
||||||
|
* Please see the included DOCS/LICENSE.md for more information
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ElunaFileWatcher.h"
|
||||||
|
#include "LuaEngine.h"
|
||||||
|
#include "ElunaUtility.h"
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
|
||||||
|
ElunaFileWatcher::ElunaFileWatcher() : running(false), checkInterval(1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ElunaFileWatcher::~ElunaFileWatcher()
|
||||||
|
{
|
||||||
|
StopWatching();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElunaFileWatcher::StartWatching(const std::string& scriptPath, uint32 intervalSeconds)
|
||||||
|
{
|
||||||
|
if (running.load())
|
||||||
|
{
|
||||||
|
ELUNA_LOG_DEBUG("[ElunaFileWatcher]: Already watching files");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scriptPath.empty())
|
||||||
|
{
|
||||||
|
ELUNA_LOG_ERROR("[ElunaFileWatcher]: Cannot start watching - script path is empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
watchPath = scriptPath;
|
||||||
|
checkInterval = intervalSeconds;
|
||||||
|
running.store(true);
|
||||||
|
|
||||||
|
ScanDirectory(watchPath);
|
||||||
|
|
||||||
|
watcherThread = std::thread(&ElunaFileWatcher::WatchLoop, this);
|
||||||
|
|
||||||
|
ELUNA_LOG_INFO("[ElunaFileWatcher]: Started watching '{}' (interval: {}s)", watchPath, checkInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElunaFileWatcher::StopWatching()
|
||||||
|
{
|
||||||
|
if (!running.load())
|
||||||
|
return;
|
||||||
|
|
||||||
|
running.store(false);
|
||||||
|
|
||||||
|
if (watcherThread.joinable())
|
||||||
|
watcherThread.join();
|
||||||
|
|
||||||
|
fileTimestamps.clear();
|
||||||
|
|
||||||
|
ELUNA_LOG_INFO("[ElunaFileWatcher]: Stopped watching files");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElunaFileWatcher::WatchLoop()
|
||||||
|
{
|
||||||
|
while (running.load())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CheckForChanges();
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
ELUNA_LOG_ERROR("[ElunaFileWatcher]: Error during file watching: {}", e.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(checkInterval));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ElunaFileWatcher::IsWatchedFileType(const std::string& filename) {
|
||||||
|
return (filename.length() >= 4 && filename.substr(filename.length() - 4) == ".lua") ||
|
||||||
|
(filename.length() >= 4 && filename.substr(filename.length() - 4) == ".ext") ||
|
||||||
|
(filename.length() >= 5 && filename.substr(filename.length() - 5) == ".moon");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElunaFileWatcher::ScanDirectory(const std::string& path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
boost::filesystem::path dir(path);
|
||||||
|
|
||||||
|
if (!boost::filesystem::exists(dir) || !boost::filesystem::is_directory(dir))
|
||||||
|
return;
|
||||||
|
|
||||||
|
boost::filesystem::directory_iterator end_iter;
|
||||||
|
|
||||||
|
for (boost::filesystem::directory_iterator dir_iter(dir); dir_iter != end_iter; ++dir_iter)
|
||||||
|
{
|
||||||
|
std::string fullpath = dir_iter->path().generic_string();
|
||||||
|
|
||||||
|
if (boost::filesystem::is_directory(dir_iter->status()))
|
||||||
|
{
|
||||||
|
ScanDirectory(fullpath);
|
||||||
|
}
|
||||||
|
else if (boost::filesystem::is_regular_file(dir_iter->status()))
|
||||||
|
{
|
||||||
|
std::string filename = dir_iter->path().filename().generic_string();
|
||||||
|
|
||||||
|
if (IsWatchedFileType(filename))
|
||||||
|
{
|
||||||
|
fileTimestamps[fullpath] = boost::filesystem::last_write_time(dir_iter->path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
ELUNA_LOG_ERROR("[ElunaFileWatcher]: Error scanning directory '{}': {}", path, e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElunaFileWatcher::CheckForChanges()
|
||||||
|
{
|
||||||
|
bool hasChanges = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
boost::filesystem::path dir(watchPath);
|
||||||
|
|
||||||
|
if (!boost::filesystem::exists(dir) || !boost::filesystem::is_directory(dir))
|
||||||
|
return;
|
||||||
|
|
||||||
|
boost::filesystem::directory_iterator end_iter;
|
||||||
|
|
||||||
|
for (boost::filesystem::directory_iterator dir_iter(dir); dir_iter != end_iter; ++dir_iter)
|
||||||
|
{
|
||||||
|
if (ShouldReloadFile(dir_iter->path().generic_string()))
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it = fileTimestamps.begin(); it != fileTimestamps.end();)
|
||||||
|
{
|
||||||
|
if (!boost::filesystem::exists(it->first))
|
||||||
|
{
|
||||||
|
ELUNA_LOG_DEBUG("[ElunaFileWatcher]: File deleted: {}", it->first);
|
||||||
|
it = fileTimestamps.erase(it);
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
ELUNA_LOG_ERROR("[ElunaFileWatcher]: Error checking for changes: {}", e.what());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasChanges)
|
||||||
|
{
|
||||||
|
ELUNA_LOG_INFO("[ElunaFileWatcher]: Lua script changes detected - triggering reload");
|
||||||
|
Eluna::ReloadEluna();
|
||||||
|
|
||||||
|
ScanDirectory(watchPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ElunaFileWatcher::ShouldReloadFile(const std::string& filepath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
boost::filesystem::path file(filepath);
|
||||||
|
|
||||||
|
if (boost::filesystem::is_directory(file))
|
||||||
|
{
|
||||||
|
boost::filesystem::directory_iterator end_iter;
|
||||||
|
|
||||||
|
for (boost::filesystem::directory_iterator dir_iter(file); dir_iter != end_iter; ++dir_iter)
|
||||||
|
{
|
||||||
|
if (ShouldReloadFile(dir_iter->path().generic_string()))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!boost::filesystem::is_regular_file(file))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::string filename = file.filename().generic_string();
|
||||||
|
|
||||||
|
if (!IsWatchedFileType(filename)) return false;
|
||||||
|
|
||||||
|
auto currentTime = boost::filesystem::last_write_time(file);
|
||||||
|
auto it = fileTimestamps.find(filepath);
|
||||||
|
|
||||||
|
if (it == fileTimestamps.end())
|
||||||
|
{
|
||||||
|
ELUNA_LOG_DEBUG("[ElunaFileWatcher]: New file detected: {}", filepath);
|
||||||
|
fileTimestamps[filepath] = currentTime;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it->second != currentTime)
|
||||||
|
{
|
||||||
|
ELUNA_LOG_DEBUG("[ElunaFileWatcher]: File modified: {}", filepath);
|
||||||
|
it->second = currentTime;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
ELUNA_LOG_ERROR("[ElunaFileWatcher]: Error checking file '{}': {}", filepath, e.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
43
src/LuaEngine/ElunaFileWatcher.h
Normal file
43
src/LuaEngine/ElunaFileWatcher.h
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 - 2016 Eluna Lua Engine <http://emudevs.com/>
|
||||||
|
* This program is free software licensed under GPL version 3
|
||||||
|
* Please see the included DOCS/LICENSE.md for more information
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ELUNA_FILE_WATCHER_H
|
||||||
|
#define ELUNA_FILE_WATCHER_H
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
#include <atomic>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <chrono>
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
#include "Common.h"
|
||||||
|
|
||||||
|
class ElunaFileWatcher
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ElunaFileWatcher();
|
||||||
|
~ElunaFileWatcher();
|
||||||
|
|
||||||
|
void StartWatching(const std::string& scriptPath, uint32 intervalSeconds = 1);
|
||||||
|
void StopWatching();
|
||||||
|
bool IsWatching() const { return running.load(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void WatchLoop();
|
||||||
|
void ScanDirectory(const std::string& path);
|
||||||
|
void CheckForChanges();
|
||||||
|
bool ShouldReloadFile(const std::string& filepath);
|
||||||
|
bool IsWatchedFileType(const std::string& filename);
|
||||||
|
|
||||||
|
std::thread watcherThread;
|
||||||
|
std::atomic<bool> running;
|
||||||
|
std::string watchPath;
|
||||||
|
uint32 checkInterval;
|
||||||
|
|
||||||
|
std::map<std::string, std::time_t> fileTimestamps;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -50,6 +50,7 @@ Eluna* Eluna::GEluna = NULL;
|
|||||||
bool Eluna::reload = false;
|
bool Eluna::reload = false;
|
||||||
bool Eluna::initialized = false;
|
bool Eluna::initialized = false;
|
||||||
Eluna::LockType Eluna::lock;
|
Eluna::LockType Eluna::lock;
|
||||||
|
std::unique_ptr<ElunaFileWatcher> Eluna::fileWatcher;
|
||||||
|
|
||||||
// Global bytecode cache that survives Eluna reloads
|
// Global bytecode cache that survives Eluna reloads
|
||||||
static std::unordered_map<std::string, GlobalCacheEntry> globalBytecodeCache;
|
static std::unordered_map<std::string, GlobalCacheEntry> globalBytecodeCache;
|
||||||
@@ -75,6 +76,14 @@ void Eluna::Initialize()
|
|||||||
|
|
||||||
// Create global eluna
|
// Create global eluna
|
||||||
GEluna = new Eluna();
|
GEluna = new Eluna();
|
||||||
|
|
||||||
|
// Start file watcher if enabled
|
||||||
|
if (eConfigMgr->GetOption<bool>("Eluna.AutoReload", false))
|
||||||
|
{
|
||||||
|
uint32 watchInterval = eConfigMgr->GetOption<uint32>("Eluna.AutoReloadInterval", 1);
|
||||||
|
fileWatcher = std::make_unique<ElunaFileWatcher>();
|
||||||
|
fileWatcher->StartWatching(lua_folderpath, watchInterval);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Eluna::Uninitialize()
|
void Eluna::Uninitialize()
|
||||||
@@ -82,6 +91,13 @@ void Eluna::Uninitialize()
|
|||||||
LOCK_ELUNA;
|
LOCK_ELUNA;
|
||||||
ASSERT(IsInitialized());
|
ASSERT(IsInitialized());
|
||||||
|
|
||||||
|
// Stop file watcher
|
||||||
|
if (fileWatcher)
|
||||||
|
{
|
||||||
|
fileWatcher->StopWatching();
|
||||||
|
fileWatcher.reset();
|
||||||
|
}
|
||||||
|
|
||||||
delete GEluna;
|
delete GEluna;
|
||||||
GEluna = NULL;
|
GEluna = NULL;
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include "HttpManager.h"
|
#include "HttpManager.h"
|
||||||
#include "EventEmitter.h"
|
#include "EventEmitter.h"
|
||||||
#include "TicketMgr.h"
|
#include "TicketMgr.h"
|
||||||
|
#include "ElunaFileWatcher.h"
|
||||||
#include "LootMgr.h"
|
#include "LootMgr.h"
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -120,6 +121,7 @@ private:
|
|||||||
static bool reload;
|
static bool reload;
|
||||||
static bool initialized;
|
static bool initialized;
|
||||||
static LockType lock;
|
static LockType lock;
|
||||||
|
static std::unique_ptr<ElunaFileWatcher> fileWatcher;
|
||||||
|
|
||||||
// Lua script locations
|
// Lua script locations
|
||||||
static ScriptList lua_scripts;
|
static ScriptList lua_scripts;
|
||||||
|
|||||||
Reference in New Issue
Block a user