22#ifndef TRINITY_API_USE_DYNAMIC_LINKING
25std::shared_ptr<ModuleReference>
52#include <boost/algorithm/string/replace.hpp>
53#include <boost/filesystem.hpp>
54#include <boost/system/system_error.hpp>
55#include <efsw/efsw.hpp>
63#include <unordered_map>
64#include <unordered_set>
69#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
77#undef sScriptReloadMgr
78#define sScriptReloadMgr static_cast<HotSwapScriptReloadMgr*>(ScriptReloadMgr::instance())
81static char const* GetSharedLibraryPrefix()
83#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
91static char const* GetSharedLibraryExtension()
93#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
95#elif TRINITY_PLATFORM == TRINITY_PLATFORM_APPLE
102#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
103typedef HMODULE HandleType;
105typedef void* HandleType;
108static fs::path GetDirectoryOfExecutable()
111 "Expected the arguments to contain at least 1 element!");
114 if (path.is_absolute())
115 return path.parent_path();
117 return fs::canonical(fs::absolute(path)).parent_path();
120class SharedLibraryUnloader
123 explicit SharedLibraryUnloader(fs::path path)
124 : path_(
std::move(path)) { }
126 : path_(
std::move(path)), cache_path_(
std::move(cache_path)) { }
128 void operator() (HandleType handle)
const
131#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
132 bool success = (FreeLibrary(handle) != 0);
134 bool success = (dlclose(handle) == 0);
139 TC_LOG_ERROR(
"scripts.hotswap",
"Failed to unload (syscall) the shared library \"{}\".",
140 path_.generic_string());
148 boost::system::error_code error;
149 if (!fs::remove(*cache_path_, error))
151 TC_LOG_ERROR(
"scripts.hotswap",
"Failed to delete the cached shared library \"{}\" ({}).",
152 cache_path_->generic_string(), error.message());
157 TC_LOG_TRACE(
"scripts.hotswap",
"Lazy unloaded the shared library \"{}\" "
158 "and deleted it's cached version at \"{}\"",
159 path_.generic_string(), cache_path_->generic_string());
163 TC_LOG_TRACE(
"scripts.hotswap",
"Lazy unloaded the shared library \"{}\".",
164 path_.generic_string());
169 fs::path
const path_;
173typedef std::unique_ptr<typename std::remove_pointer<HandleType>::type, SharedLibraryUnloader> HandleHolder;
175typedef char const* (*GetScriptModuleRevisionHashType)();
176typedef void (*AddScriptsType)();
177typedef char const* (*GetScriptModuleType)();
178typedef char const* (*GetBuildDirectiveType)();
184 explicit ScriptModule(HandleHolder handle, GetScriptModuleRevisionHashType getScriptModuleRevisionHash,
185 AddScriptsType addScripts, GetScriptModuleType getScriptModule,
186 GetBuildDirectiveType getBuildDirective, fs::path
const& path)
187 : _handle(
std::forward<HandleHolder>(handle)), _getScriptModuleRevisionHash(getScriptModuleRevisionHash),
188 _addScripts(addScripts), _getScriptModule(getScriptModule),
189 _getBuildDirective(getBuildDirective), _path(path) { }
191 ScriptModule(ScriptModule
const&) =
delete;
192 ScriptModule(ScriptModule&& right) =
delete;
194 ScriptModule& operator= (ScriptModule
const&) =
delete;
195 ScriptModule& operator= (ScriptModule&& right) =
delete;
200 static void ScheduleDelayedDelete(ScriptModule* module);
202 char const* GetScriptModuleRevisionHash()
const override
204 return _getScriptModuleRevisionHash();
209 return _addScripts();
212 char const* GetScriptModule()
const override
214 return _getScriptModule();
217 char const* GetBuildDirective()
const
219 return _getBuildDirective();
222 fs::path
const& GetModulePath()
const override
228 HandleHolder _handle;
230 GetScriptModuleRevisionHashType _getScriptModuleRevisionHash;
231 AddScriptsType _addScripts;
232 GetScriptModuleType _getScriptModule;
233 GetBuildDirectiveType _getBuildDirective;
239static bool GetFunctionFromSharedLibrary(HandleType handle, std::string
const& name, Fn& fn)
241#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
242 fn =
reinterpret_cast<Fn
>(GetProcAddress(handle, name.c_str()));
244 fn =
reinterpret_cast<Fn
>(dlsym(handle, name.c_str()));
246 return fn !=
nullptr;
253 auto const load_path = [&] () -> fs::path {
260#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
261 HandleType handle = LoadLibrary(load_path.generic_string().c_str());
263 HandleType handle = dlopen(load_path.generic_string().c_str(), RTLD_LAZY);
270 TC_LOG_ERROR(
"scripts.hotswap",
"Could not dynamic load the shared library \"{}\" "
271 "(the library is cached at {})",
272 path.generic_string(), cache_path->generic_string());
276 TC_LOG_ERROR(
"scripts.hotswap",
"Could not dynamic load the shared library \"{}\".",
277 path.generic_string());
284 HandleHolder holder(handle, SharedLibraryUnloader(path, std::move(cache_path)));
286 GetScriptModuleRevisionHashType getScriptModuleRevisionHash;
287 AddScriptsType addScripts;
288 GetScriptModuleType getScriptModule;
289 GetBuildDirectiveType getBuildDirective;
291 if (GetFunctionFromSharedLibrary(handle,
"GetScriptModuleRevisionHash", getScriptModuleRevisionHash) &&
292 GetFunctionFromSharedLibrary(handle,
"AddScripts", addScripts) &&
293 GetFunctionFromSharedLibrary(handle,
"GetScriptModule", getScriptModule) &&
294 GetFunctionFromSharedLibrary(handle,
"GetBuildDirective", getBuildDirective))
296 auto module = new ScriptModule(std::move(holder), getScriptModuleRevisionHash,
297 addScripts, getScriptModule, getBuildDirective, path);
300 return std::shared_ptr<ScriptModule>(module, ScheduleDelayedDelete);
304 TC_LOG_ERROR(
"scripts.hotswap",
"Could not extract all required functions from the shared library \"{}\"!",
305 path.generic_string());
311static bool HasValidScriptModuleName(std::string
const& name)
316 GetSharedLibraryPrefix(),
317 GetSharedLibraryExtension()));
319 return Trinity::regex_match(name, regex);
323class LibraryUpdateListener :
public efsw::FileWatchListener
326 LibraryUpdateListener() { }
327 virtual ~LibraryUpdateListener() { }
329 void handleFileAction(efsw::WatchID , std::string
const& dir,
330 std::string
const& filename, efsw::Action action, std::string oldFilename =
"") final override;
333static LibraryUpdateListener libraryUpdateListener;
336class SourceUpdateListener : public efsw::FileWatchListener
338 fs::path
const path_;
340 std::string
const script_module_name_;
342 efsw::WatchID
const watcher_id_;
345 explicit SourceUpdateListener(fs::path path, std::string script_module_name);
347 virtual ~SourceUpdateListener();
349 void handleFileAction(efsw::WatchID , std::string
const& dir,
350 std::string
const& filename, efsw::Action action, std::string oldFilename =
"") final override;
356 struct hash<
fs::path>
360 std::size_t operator()(fs::path
const& key)
const
362 return hasher(key.generic_string());
368template<
typename... T>
369static int InvokeCMakeCommand(T&&... args)
373 std::forward<T>(args)...
374 },
"scripts.hotswap");
378template<
typename... T>
379static std::shared_ptr<Trinity::AsyncProcessResult> InvokeAsyncCMakeCommand(T&&... args)
383 std::forward<T>(args)...
384 },
"scripts.hotswap");
389static std::string CalculateScriptModuleProjectName(std::string
const& module)
391 std::string module_project =
"scripts_" +
module;
394 return module_project;
399static bool IsDebuggerBlockingRebuild()
401#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
402 if (IsDebuggerPresent())
420class HotSwapScriptReloadMgr final
424 friend class SourceUpdateListener;
428 enum class ChangeStateRequest :
uint8
430 CHANGE_REQUEST_ADDED,
431 CHANGE_REQUEST_MODIFIED,
432 CHANGE_REQUEST_REMOVED
436 enum class BuildJobType :
uint8
439 BUILD_JOB_RERUN_CMAKE,
448 std::string script_module_name_;
450 std::string script_module_project_name_;
454 std::string script_module_build_directive_;
461 std::shared_ptr<Trinity::AsyncProcessResult> async_result_;
464 explicit BuildJob(std::string script_module_name, std::string script_module_project_name,
465 std::string script_module_build_directive)
466 : script_module_name_(
std::move(script_module_name)),
467 script_module_project_name_(
std::move(script_module_project_name)),
468 script_module_build_directive_(
std::move(script_module_build_directive)),
469 start_time_(
getMSTime()), type_(BuildJobType::BUILD_JOB_NONE) { }
473 return type_ != BuildJobType::BUILD_JOB_NONE;
476 std::string
const& GetModuleName()
const {
return script_module_name_; }
478 std::string
const& GetProjectName()
const {
return script_module_project_name_; }
480 std::string
const& GetBuildDirective()
const {
return script_module_build_directive_; }
484 BuildJobType GetType()
const {
return type_; }
486 std::shared_ptr<Trinity::AsyncProcessResult>
const& GetProcess()
const
488 ASSERT(async_result_,
"Tried to access an empty process handle!");
489 return async_result_;
493 void UpdateCurrentJob(BuildJobType type,
494 std::shared_ptr<Trinity::AsyncProcessResult> async_result)
496 ASSERT(type != BuildJobType::BUILD_JOB_NONE,
"None isn't allowed here!");
497 ASSERT(async_result,
"The async result must not be empty!");
500 async_result_ = std::move(async_result);
505 class ScriptReloaderMessage
508 virtual ~ScriptReloaderMessage() { }
511 virtual void operator() (HotSwapScriptReloadMgr* reloader) = 0;
517 class ScriptReloaderMessageImplementation
518 :
public ScriptReloaderMessage
523 explicit ScriptReloaderMessageImplementation(T dispatcher)
524 : dispatcher_(
std::move(dispatcher)) { }
526 void operator() (HotSwapScriptReloadMgr* reloader)
final override
528 dispatcher_(reloader);
535 auto MakeMessage(T&& dispatcher)
536 -> ScriptReloaderMessageImplementation<typename std::decay<T>::type>*
538 return new ScriptReloaderMessageImplementation<typename std::decay<T>::type>
539 (std::forward<T>(dispatcher));
543 HotSwapScriptReloadMgr()
544 : _libraryWatcher(-1), _unique_library_name_counter(0),
545 _last_time_library_changed(0), _last_time_sources_changed(0),
546 _last_time_user_informed(0), terminate_early(false) { }
548 virtual ~HotSwapScriptReloadMgr()
551 ScriptReloaderMessage* message;
552 while (_messages.Dequeue(message))
557 static fs::path GetLibraryDirectory()
561 fs::path path(
sConfigMgr->GetStringDefault(
"HotSwap.ScriptDir",
"scripts"));
562 if (path.is_absolute())
565 return fs::absolute(path, GetDirectoryOfExecutable());
587 TC_LOG_ERROR(
"scripts.hotswap",
"Your build directory path \"{}\" "
588 "contains spaces, which isn't allowed for compatibility reasons! "
589 "You need to create a build directory which doesn't contain any space character "
597 auto const library_directory = GetLibraryDirectory();
598 if (!fs::exists(library_directory) || !fs::is_directory(library_directory))
600 TC_LOG_ERROR(
"scripts.hotswap",
"Library directory \"{}\" doesn't exist!.",
601 library_directory.generic_string());
606 temporary_cache_path_ = CalculateTemporaryCachePath();
610 boost::system::error_code code;
611 if ((!fs::exists(temporary_cache_path_, code)
612 || (fs::remove_all(temporary_cache_path_, code) > 0)) &&
613 !fs::create_directories(temporary_cache_path_, code))
615 TC_LOG_ERROR(
"scripts.hotswap",
"Couldn't create the cache directory at \"{}\".",
616 temporary_cache_path_.generic_string());
626 DoCMakePrefixCorrectionIfNeeded();
628 InitializeDefaultLibraries();
629 InitializeFileWatchers();
637 void Update() final
override
640 ScriptReloaderMessage* message;
641 while (_messages.Dequeue(message))
647 DispatchRunningBuildJobs();
648 DispatchModuleChanges();
652 void Unload() final
override
654 if (_libraryWatcher >= 0)
656 _fileWatcher.removeWatch(_libraryWatcher);
657 _libraryWatcher = -1;
663 _build_job->GetProcess()->Terminate();
670 _running_script_modules.clear();
676 void QueueMessage(T&& message)
678 _messages.Enqueue(MakeMessage(std::forward<T>(message)));
684 void QueueSharedLibraryChanged(fs::path
const& path)
686 _last_time_library_changed =
getMSTime();
687 _libraries_changed.insert(path);
692 void QueueAddSourceFile(std::string
const& module_name, fs::path
const& path)
694 UpdateSourceChangeRequest(module_name, path, ChangeStateRequest::CHANGE_REQUEST_ADDED);
699 void QueueModifySourceFile(std::string
const& module_name, fs::path
const& path)
701 UpdateSourceChangeRequest(module_name, path, ChangeStateRequest::CHANGE_REQUEST_MODIFIED);
706 void QueueRemoveSourceFile(std::string
const& module_name, fs::path
const& path)
708 UpdateSourceChangeRequest(module_name, path, ChangeStateRequest::CHANGE_REQUEST_REMOVED);
714 void InitializeDefaultLibraries()
716 fs::path
const libraryDirectory(GetLibraryDirectory());
717 fs::directory_iterator
const dir_end;
722 for (fs::directory_iterator dir_itr(libraryDirectory); dir_itr != dir_end ; ++dir_itr)
723 if (fs::is_regular_file(dir_itr->path()) && HasValidScriptModuleName(dir_itr->path().filename().generic_string()))
725 TC_LOG_INFO(
"scripts.hotswap",
"Loading script module \"{}\"...",
726 dir_itr->path().filename().generic_string());
729 ProcessLoadScriptModule(dir_itr->path(),
false);
733 TC_LOG_INFO(
"scripts.hotswap",
">> Loaded {} script modules.", count);
738 void InitializeFileWatchers()
740 _libraryWatcher = _fileWatcher.addWatch(GetLibraryDirectory().generic_string(), &libraryUpdateListener,
false);
741 if (_libraryWatcher >= 0)
743 TC_LOG_INFO(
"scripts.hotswap",
">> Library reloader is listening on \"{}\".",
744 GetLibraryDirectory().generic_string());
748 TC_LOG_ERROR(
"scripts.hotswap",
"Failed to initialize the library reloader on \"{}\".",
749 GetLibraryDirectory().generic_string());
752 _fileWatcher.watch();
755 static fs::path CalculateTemporaryCachePath()
757 auto path = fs::temp_directory_path();
765 fs::path GenerateUniquePathForLibraryInCache(fs::path path)
767 ASSERT(!temporary_cache_path_.empty(),
768 "The temporary cache path wasn't set!");
771 auto cache_path = temporary_cache_path_;
773 path.stem().generic_string(),
774 _unique_library_name_counter++,
775 path.extension().generic_string());
781 void UpdateSourceChangeRequest(std::string
const& module_name,
782 fs::path
const& path,
783 ChangeStateRequest state)
785 _last_time_sources_changed =
getMSTime();
788 auto module_itr = _sources_changed.find(module_name);
792 if (module_itr == _sources_changed.end())
793 module_itr = _sources_changed.insert(std::make_pair(
794 module_name,
decltype(_sources_changed)::mapped_type{})).first;
797 if (state == ChangeStateRequest::CHANGE_REQUEST_MODIFIED)
801 auto const itr = module_itr->second.find(path);
802 if (itr == module_itr->second.end())
804 module_itr->second.insert(std::make_pair(path, state));
808 ASSERT((itr->second == ChangeStateRequest::CHANGE_REQUEST_ADDED)
809 || (itr->second == ChangeStateRequest::CHANGE_REQUEST_REMOVED),
810 "Stored value is invalid!");
812 ASSERT((state == ChangeStateRequest::CHANGE_REQUEST_ADDED)
813 || (state == ChangeStateRequest::CHANGE_REQUEST_REMOVED),
814 "The given state is invalid!");
816 ASSERT(state != itr->second,
817 "Tried to apply a state which is stored already!");
819 module_itr->second.erase(itr);
824 void DispatchModuleChanges()
827 if (_libraries_changed.empty())
834 for (
auto const& path : _libraries_changed)
836 bool const is_running =
837 _running_script_module_names.find(path) != _running_script_module_names.end();
839 bool const exists = fs::exists(path);
844 ProcessReloadScriptModule(path);
846 ProcessUnloadScriptModule(path);
849 ProcessLoadScriptModule(path);
852 _libraries_changed.clear();
855 void ProcessLoadScriptModule(fs::path
const& path,
bool swap_context =
true)
857 ASSERT(_running_script_module_names.find(path) == _running_script_module_names.end(),
858 "Can't load a module which is running already!");
861 auto cache_path = GenerateUniquePathForLibraryInCache(path);
864 boost::system::error_code code;
865 fs::copy_file(path, cache_path, code);
868 TC_LOG_FATAL(
"scripts.hotswap",
">> Failed to create cache entry for module "
869 "\"{}\" at \"{}\" with reason (\"{}\")!",
870 path.filename().generic_string(), cache_path.generic_string(),
875 std::this_thread::sleep_for(std::chrono::seconds(5));
880 TC_LOG_TRACE(
"scripts.hotswap",
">> Copied the shared library \"{}\" to \"{}\" for caching.",
881 path.filename().generic_string(), cache_path.generic_string());
884 auto module = ScriptModule::CreateFromPath(path, cache_path);
887 TC_LOG_FATAL(
"scripts.hotswap",
">> Failed to load script module \"{}\"!",
888 path.filename().generic_string());
892 std::this_thread::sleep_for(std::chrono::seconds(5));
898 std::string module_revision((*module)->GetScriptModuleRevisionHash());
899 if (module_revision.size() >= 7)
900 module_revision = module_revision.substr(0, 7);
902 std::string
const module_name = (*module)->GetScriptModule();
903 TC_LOG_INFO(
"scripts.hotswap",
">> Loaded script module \"{}\" (\"{}\" - {}).",
904 path.filename().generic_string(), module_name, module_revision);
906 if (module_revision.empty())
908 TC_LOG_WARN(
"scripts.hotswap",
">> Script module \"{}\" has an empty revision hash!",
909 path.filename().generic_string());
915 std::size_t
const trim = std::min(module_revision.size(), my_revision_hash.size());
916 my_revision_hash = my_revision_hash.substr(0, trim);
917 module_revision = module_revision.substr(0, trim);
919 if (my_revision_hash != module_revision)
921 TC_LOG_WARN(
"scripts.hotswap",
">> Script module \"{}\" has a different revision hash! "
922 "Binary incompatibility could lead to unknown behaviour!", path.filename().generic_string());
927 auto const itr = _running_script_modules.find(module_name);
928 if (itr != _running_script_modules.end())
930 TC_LOG_ERROR(
"scripts.hotswap",
">> Attempt to load a module twice \"{}\" (loaded module is at {})!",
931 path.generic_string(), itr->second.first->GetModulePath().generic_string());
938 auto listener = std::make_unique<SourceUpdateListener>(
943 _known_modules_build_directives.insert(std::make_pair(module_name, (*module)->GetBuildDirective()));
944 _running_script_modules.insert(std::make_pair(module_name,
945 std::make_pair(*module, std::move(listener))));
946 _running_script_module_names.insert(std::make_pair(path, module_name));
950 (*module)->AddScripts();
951 TC_LOG_TRACE(
"scripts.hotswap",
">> Registered all scripts of module {}.", module_name);
957 void ProcessReloadScriptModule(fs::path
const& path)
959 ProcessUnloadScriptModule(path,
false);
960 ProcessLoadScriptModule(path);
963 void ProcessUnloadScriptModule(fs::path
const& path,
bool finish =
true)
965 auto const itr = _running_script_module_names.find(path);
967 ASSERT(itr != _running_script_module_names.end(),
968 "Can't unload a module which isn't running!");
971 sScriptMgr->ReleaseScriptContext(itr->second);
976 TC_LOG_INFO(
"scripts.hotswap",
"Released script module \"{}\" (\"{}\")...",
977 path.filename().generic_string(), itr->second);
980 auto ref = _running_script_modules.find(itr->second);
981 ASSERT(ref != _running_script_modules.end() &&
982 "Expected the script reference to be present!");
988 if (ref->second.first.use_count() != 1)
991 "Script module {} is still used by {} spell, aura or instance scripts. "
992 "Will lazy unload the module once all scripts stopped using it, "
993 "to use the latest version of an edited script unbind yourself from "
994 "the instance or re-cast the spell.",
995 ref->second.first->GetScriptModule(), ref->second.first.use_count() - 1);
999 _running_script_modules.erase(ref);
1000 _running_script_module_names.erase(itr);
1005 void DispatchRunningBuildJobs()
1013 if (!terminate_early && _sources_changed.find(_build_job->GetModuleName()) != _sources_changed.end())
1028 terminate_early =
true;
1035 if (_build_job->GetProcess()->GetFutureResult().
1036 wait_for(std::chrono::seconds(0)) == std::future_status::ready)
1037 ProcessReadyBuildJob();
1047 if ((_last_time_sources_changed != 0) &&
1052 if (_sources_changed.empty())
1056 if (IsDebuggerBlockingRebuild())
1058 if ((_last_time_user_informed == 0) ||
1064 TC_LOG_INFO(
"scripts.hotswap",
"Your attached debugger is blocking the TrinityCore "
1065 "automatic script rebuild, please detach it!");
1073 bool rebuild_buildfiles;
1074 auto module_name = [&]
1076 auto itr = _sources_changed.begin();
1077 auto name = itr->first;
1078 rebuild_buildfiles = !itr->second.empty();
1081 for (
auto const& entry : itr->second)
1083 TC_LOG_TRACE(
"scripts.hotswap",
"Source file {} was {}.",
1084 entry.first.generic_string(),
1085 ((entry.second == ChangeStateRequest::CHANGE_REQUEST_ADDED) ?
1086 "added" :
"removed"));
1089 _sources_changed.erase(itr);
1096 if (rebuild_buildfiles)
1098 for (
auto& entry : _sources_changed)
1099 entry.second.clear();
1102 ASSERT(!module_name.empty(),
1103 "The current module name is invalid!");
1105 TC_LOG_INFO(
"scripts.hotswap",
"Recompiling Module \"{}\"...",
1109 auto project_name = CalculateScriptModuleProjectName(module_name);
1112 auto build_directive = [&] () -> std::string
1114 auto directive =
sConfigMgr->GetStringDefault(
"HotSwap.ReCompilerBuildType",
"");
1115 if (!directive.empty())
1118 auto const itr = _known_modules_build_directives.find(module_name);
1119 if (itr != _known_modules_build_directives.end())
1122 return TRINITY_BUILD_TYPE;
1126 _build_job = BuildJob(std::move(module_name),
1127 std::move(project_name), std::move(build_directive));
1130 if (rebuild_buildfiles
1134 DoCompileCurrentProcessedModule();
1137 void ProcessReadyBuildJob()
1139 ASSERT(_build_job->IsValid(),
"Invalid build job!");
1142 auto const error = _build_job->GetProcess()->GetFutureResult().get();
1144 if (terminate_early)
1147 terminate_early =
false;
1151 switch (_build_job->GetType())
1153 case BuildJobType::BUILD_JOB_RERUN_CMAKE:
1157 TC_LOG_INFO(
"scripts.hotswap",
">> Successfully updated the build files!");
1161 TC_LOG_INFO(
"scripts.hotswap",
">> Failed to update the build files at \"{}\", "
1162 "it's possible that recently added sources are not included "
1163 "in your next builds, rerun CMake manually.",
1167 DoCompileCurrentProcessedModule();
1170 case BuildJobType::BUILD_JOB_COMPILE:
1178 ">> Successfully build module {}, continue with installing...",
1179 _build_job->GetModuleName());
1181 DoInstallCurrentProcessedModule();
1187 ">> Successfully build module {}, skipped the installation.",
1188 _build_job->GetModuleName());
1193 ">> The build of module {} failed! See the log for details.",
1194 _build_job->GetModuleName());
1198 case BuildJobType::BUILD_JOB_INSTALL:
1203 TC_LOG_INFO(
"scripts.hotswap",
">> Successfully installed module {} in {}s",
1204 _build_job->GetModuleName(),
1211 ">> The installation of module {} failed! See the log for details.",
1212 _build_job->GetModuleName());
1227 ASSERT(_build_job,
"There isn't any active build job!");
1229 TC_LOG_INFO(
"scripts.hotswap",
"Rerunning CMake because there were sources added or removed...");
1231 _build_job->UpdateCurrentJob(BuildJobType::BUILD_JOB_RERUN_CMAKE,
1236 void DoCompileCurrentProcessedModule()
1238 ASSERT(_build_job,
"There isn't any active build job!");
1240 TC_LOG_INFO(
"scripts.hotswap",
"Starting asynchronous build job for module {}...",
1241 _build_job->GetModuleName());
1243 _build_job->UpdateCurrentJob(BuildJobType::BUILD_JOB_COMPILE,
1244 InvokeAsyncCMakeCommand(
1246 "--target", _build_job->GetProjectName(),
1247 "--config", _build_job->GetBuildDirective()));
1251 void DoInstallCurrentProcessedModule()
1253 ASSERT(_build_job,
"There isn't any active build job!");
1255 TC_LOG_INFO(
"scripts.hotswap",
"Starting asynchronous install job for module {}...",
1256 _build_job->GetModuleName());
1258 _build_job->UpdateCurrentJob(BuildJobType::BUILD_JOB_INSTALL,
1259 InvokeAsyncCMakeCommand(
1260 "-DCOMPONENT=" + _build_job->GetProjectName(),
1261 "-DBUILD_TYPE=" + _build_job->GetBuildDirective(),
1262 "-P", fs::absolute(
"cmake_install.cmake",
1269 void DoCMakePrefixCorrectionIfNeeded()
1271 TC_LOG_INFO(
"scripts.hotswap",
"Correcting your CMAKE_INSTALL_PREFIX in \"{}\"...",
1274 auto const cmake_cache_path = fs::absolute(
"CMakeCache.txt",
1280 boost::system::error_code error;
1281 if (!fs::exists(cmake_cache_path, error))
1283 TC_LOG_ERROR(
"scripts.hotswap",
">> CMake cache \"{}\" doesn't exist, "
1284 "set the \"BuildDirectory\" option in your worldserver.conf to point"
1285 "to your build directory!",
1286 cmake_cache_path.generic_string());
1295 TC_LOG_TRACE(
"scripts.hotswap",
"Checking CMake cache (\"{}\") "
1296 "for the correct CMAKE_INSTALL_PREFIX location...",
1297 cmake_cache_path.generic_string());
1299 std::string cmake_cache_content;
1301 std::ifstream in(cmake_cache_path.generic_string());
1304 TC_LOG_ERROR(
"scripts.hotswap",
">> Failed to read the CMake cache at \"{}\"!",
1305 cmake_cache_path.generic_string());
1310 std::ostringstream ss;
1312 cmake_cache_content = ss.str();
1317 static std::string
const prefix_key =
"CMAKE_INSTALL_PREFIX:PATH=";
1320 auto begin = cmake_cache_content.find(prefix_key);
1321 if (begin != std::string::npos)
1323 begin += prefix_key.length();
1324 auto const end = cmake_cache_content.find(
"\n", begin);
1325 if (end != std::string::npos)
1327 fs::path value = cmake_cache_content.substr(begin, end - begin);
1329 auto current_path = fs::current_path();
1331 #if TRINITY_PLATFORM != TRINITY_PLATFORM_WINDOWS
1334 current_path = current_path.parent_path();
1337 if (value != current_path)
1341 bool const is_in_path = [&]
1344 fs::path branch = value;
1345 while (!branch.empty())
1350 branch = branch.parent_path();
1359 TC_LOG_INFO(
"scripts.hotswap",
">> Found outdated CMAKE_INSTALL_PREFIX (\"{}\"), "
1360 "worldserver is currently installed at {}",
1361 value.generic_string(), current_path.generic_string());
1365 TC_LOG_INFO(
"scripts.hotswap",
">> CMAKE_INSTALL_PREFIX is equal to the current path of execution.");
1371 TC_LOG_INFO(
"scripts.hotswap",
"Invoking CMake cache correction...");
1373 auto const error = InvokeCMakeCommand(
1374 "-DCMAKE_INSTALL_PREFIX:PATH=" + fs::current_path().generic_string(),
1379 TC_LOG_ERROR(
"scripts.hotswap",
">> Failed to update the CMAKE_INSTALL_PREFIX! "
1380 "This could lead to unexpected behaviour!");
1384 TC_LOG_ERROR(
"scripts.hotswap",
">> Successfully corrected your CMAKE_INSTALL_PREFIX variable"
1385 "to point at your current path of execution.");
1390 efsw::FileWatcher _fileWatcher;
1391 efsw::WatchID _libraryWatcher;
1395 uint32 _unique_library_name_counter;
1401 std::unordered_set<fs::path > _libraries_changed;
1403 uint32 _last_time_library_changed;
1408 std::unordered_map<std::string ,
1409 std::pair<std::shared_ptr<ScriptModule>, std::unique_ptr<SourceUpdateListener>>
1410 > _running_script_modules;
1412 std::unordered_map<fs::path, std::string > _running_script_module_names;
1414 std::unordered_map<std::string , std::string > _known_modules_build_directives;
1417 std::unordered_map<std::string ,
1418 std::unordered_map<fs::path , ChangeStateRequest >> _sources_changed;
1420 uint32 _last_time_sources_changed;
1423 uint32 _last_time_user_informed;
1430 bool terminate_early;
1433 fs::path temporary_cache_path_;
1436class ScriptModuleDeleteMessage
1439 explicit ScriptModuleDeleteMessage(ScriptModule* module)
1440 : module_(module) { }
1442 void operator() (HotSwapScriptReloadMgr*)
1448 std::unique_ptr<ScriptModule> module_;
1451void ScriptModule::ScheduleDelayedDelete(ScriptModule* module)
1457static char const* ActionToString(efsw::Action action)
1461 case efsw::Action::Add:
1463 case efsw::Action::Delete:
1465 case efsw::Action::Moved:
1472void LibraryUpdateListener::handleFileAction(efsw::WatchID watchid, std::string
const& dir,
1473 std::string
const& filename, efsw::Action action, std::string oldFilename)
1478 if (action == efsw::Action::Moved)
1480 ASSERT(!oldFilename.empty(),
"Old filename doesn't exist!");
1481 handleFileAction(watchid, dir, oldFilename, efsw::Action::Delete);
1482 handleFileAction(watchid, dir, filename, efsw::Action::Add);
1486 sScriptReloadMgr->QueueMessage([=](HotSwapScriptReloadMgr* reloader)
mutable
1488 auto const path = fs::absolute(
1492 if (!HasValidScriptModuleName(filename))
1497 case efsw::Actions::Add:
1498 TC_LOG_TRACE(
"scripts.hotswap",
">> Loading \"{}\" ({})...",
1499 path.generic_string(), ActionToString(action));
1500 reloader->QueueSharedLibraryChanged(path);
1502 case efsw::Actions::Delete:
1503 TC_LOG_TRACE(
"scripts.hotswap",
">> Unloading \"{}\" ({})...",
1504 path.generic_string(), ActionToString(action));
1505 reloader->QueueSharedLibraryChanged(path);
1507 case efsw::Actions::Modified:
1508 TC_LOG_TRACE(
"scripts.hotswap",
">> Reloading \"{}\" ({})...",
1509 path.generic_string(), ActionToString(action));
1510 reloader->QueueSharedLibraryChanged(path);
1520static bool HasCXXSourceFileExtension(fs::path
const& path)
1523 return Trinity::regex_match(path.extension().generic_string(), regex);
1526SourceUpdateListener::SourceUpdateListener(fs::path path, std::string script_module_name)
1527 : path_(
std::move(path)), script_module_name_(
std::move(script_module_name)),
1528 watcher_id_(
sScriptReloadMgr->_fileWatcher.addWatch(path_.generic_string(), this, true))
1530 if (watcher_id_ >= 0)
1532 TC_LOG_TRACE(
"scripts.hotswap",
">> Attached the source recompiler to \"{}\".",
1533 path_.generic_string());
1537 TC_LOG_ERROR(
"scripts.hotswap",
"Failed to initialize thesource recompiler on \"{}\".",
1538 path_.generic_string());
1542SourceUpdateListener::~SourceUpdateListener()
1544 if (watcher_id_ >= 0)
1548 TC_LOG_TRACE(
"scripts.hotswap",
">> Detached the source recompiler from \"{}\".",
1549 path_.generic_string());
1553void SourceUpdateListener::handleFileAction(efsw::WatchID watchid, std::string
const& dir,
1554 std::string
const& filename, efsw::Action action, std::string oldFilename)
1563 if (action == efsw::Action::Moved)
1565 ASSERT(!oldFilename.empty(),
"Old filename doesn't exist!");
1566 handleFileAction(watchid, dir, oldFilename, efsw::Action::Delete);
1567 handleFileAction(watchid, dir, filename, efsw::Action::Add);
1571 fs::path path = fs::absolute(
1576 if (!path.has_extension() || !HasCXXSourceFileExtension(path))
1580 sScriptReloadMgr->QueueMessage([=,
this, path = std::move(path)](HotSwapScriptReloadMgr* reloader)
1582 TC_LOG_TRACE(
"scripts.hotswap",
"Detected source change on module \"{}\", "
1583 "queued for recompilation...", script_module_name_);
1587 case efsw::Actions::Add:
1588 TC_LOG_TRACE(
"scripts.hotswap",
"Source file {} of module {} was added.",
1589 path.generic_string(), script_module_name_);
1590 reloader->QueueAddSourceFile(script_module_name_, path);
1592 case efsw::Actions::Delete:
1593 TC_LOG_TRACE(
"scripts.hotswap",
"Source file {} of module {} was deleted.",
1594 path.generic_string(), script_module_name_);
1595 reloader->QueueRemoveSourceFile(script_module_name_, path);
1597 case efsw::Actions::Modified:
1598 TC_LOG_TRACE(
"scripts.hotswap",
"Source file {} of module {} was modified.",
1599 path.generic_string(), script_module_name_);
1600 reloader->QueueModifySourceFile(script_module_name_, path);
1610std::shared_ptr<ModuleReference>
1619 &&
"Requested a reference to a non existent script context!");
1621 return itr->second.first;
1627 static HotSwapScriptReloadMgr
instance;
#define TC_LOG_WARN(filterType__,...)
#define TC_LOG_TRACE(filterType__,...)
#define TC_LOG_ERROR(filterType__,...)
#define TC_LOG_INFO(filterType__,...)
#define TC_LOG_FATAL(filterType__,...)
std::conditional_t< IntrusiveLink !=nullptr, Trinity::Impl::MPSCQueueIntrusive< T, IntrusiveLink >, Trinity::Impl::MPSCQueueNonIntrusive< T > > MPSCQueue
std::optional< T > Optional
Optional helper class to wrap optional values within.
uint32 GetMSTimeDiffToNow(uint32 oldMSTime)
void strToLower(std::string &str)
std::string ByteArrayToHexStr(Container const &c, bool reverse=false)
static std::string const & GetNameOfStaticContext()
Returns the context name of the static context provided by the worldserver.
static ScriptReloadMgr * instance()
Returns the unique ScriptReloadMgr singleton instance.
static std::shared_ptr< ModuleReference > AcquireModuleReferenceOfContext(std::string const &context)
Returns an owning reference to the current module of the given context.
static Digest GetDigestOf(uint8 const *data, size_t len)
@ CONFIG_HOTSWAP_INSTALL_ENABLED
@ CONFIG_HOTSWAP_EARLY_TERMINATION_ENABLED
@ CONFIG_HOTSWAP_PREFIX_CORRECTION_ENABLED
@ CONFIG_HOTSWAP_BUILD_FILE_RECREATION_ENABLED
@ CONFIG_HOTSWAP_RECOMPILER_ENABLED
TC_COMMON_API std::string GetSourceDirectory()
TC_COMMON_API std::string GetBuildDirectory()
TC_COMMON_API std::string GetCMakeCommand()
TC_COMMON_API char const * GetBranch()
TC_COMMON_API char const * GetHash()
TC_REGEX_NAMESPACE ::regex regex
std::shared_ptr< AsyncProcessResult > StartAsyncProcess(std::string executable, std::vector< std::string > args, std::string logger, std::string input_file, bool secure)
std::string StringFormat(FormatString< Args... > fmt, Args &&... args)
Default TC string format function.
int32 StartProcess(std::string executable, std::vector< std::string > args, std::string logger, std::string input_file, bool secure)