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;"
|
||||
# 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
|
||||
# Description: Enable or disable bytecode caching for improved performance.
|
||||
# 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.RequirePaths = ""
|
||||
Eluna.RequireCPaths = ""
|
||||
Eluna.AutoReload = false
|
||||
Eluna.AutoReloadInterval = 1
|
||||
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::initialized = false;
|
||||
Eluna::LockType Eluna::lock;
|
||||
std::unique_ptr<ElunaFileWatcher> Eluna::fileWatcher;
|
||||
|
||||
// Global bytecode cache that survives Eluna reloads
|
||||
static std::unordered_map<std::string, GlobalCacheEntry> globalBytecodeCache;
|
||||
@@ -75,6 +76,14 @@ void Eluna::Initialize()
|
||||
|
||||
// Create global 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()
|
||||
@@ -82,6 +91,13 @@ void Eluna::Uninitialize()
|
||||
LOCK_ELUNA;
|
||||
ASSERT(IsInitialized());
|
||||
|
||||
// Stop file watcher
|
||||
if (fileWatcher)
|
||||
{
|
||||
fileWatcher->StopWatching();
|
||||
fileWatcher.reset();
|
||||
}
|
||||
|
||||
delete GEluna;
|
||||
GEluna = NULL;
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "HttpManager.h"
|
||||
#include "EventEmitter.h"
|
||||
#include "TicketMgr.h"
|
||||
#include "ElunaFileWatcher.h"
|
||||
#include "LootMgr.h"
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
@@ -120,6 +121,7 @@ private:
|
||||
static bool reload;
|
||||
static bool initialized;
|
||||
static LockType lock;
|
||||
static std::unique_ptr<ElunaFileWatcher> fileWatcher;
|
||||
|
||||
// Lua script locations
|
||||
static ScriptList lua_scripts;
|
||||
|
||||
Reference in New Issue
Block a user