21#ifndef TRINITY_API_USE_DYNAMIC_LINKING
24std::shared_ptr<ModuleReference>
53#include <boost/algorithm/string/replace.hpp>
54#include <boost/dll/runtime_symbol_info.hpp>
55#include <boost/filesystem.hpp>
56#include <boost/system/system_error.hpp>
57#include <efsw/efsw.hpp>
65#include <unordered_map>
66#include <unordered_set>
71#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
79#undef sScriptReloadMgr
80#define sScriptReloadMgr static_cast<HotSwapScriptReloadMgr*>(ScriptReloadMgr::instance())
83static char const* GetSharedLibraryPrefix()
85#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
93static char const* GetSharedLibraryExtension()
95#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
97#elif TRINITY_PLATFORM == TRINITY_PLATFORM_APPLE
104#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
105typedef HMODULE HandleType;
107typedef void* HandleType;
110static fs::path GetDirectoryOfExecutable()
112 return boost::dll::program_location().parent_path();
115class SharedLibraryUnloader
118 explicit SharedLibraryUnloader(fs::path path)
119 : path_(
std::move(path)) { }
121 : path_(
std::move(path)), cache_path_(
std::move(cache_path)) { }
123 void operator() (HandleType handle)
const
126#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
127 bool success = (FreeLibrary(handle) != 0);
129 bool success = (dlclose(handle) == 0);
134 TC_LOG_ERROR(
"scripts.hotswap",
"Failed to unload (syscall) the shared library \"{}\".",
135 path_.generic_string());
143 boost::system::error_code error;
144 if (!fs::remove(*cache_path_, error))
146 TC_LOG_ERROR(
"scripts.hotswap",
"Failed to delete the cached shared library \"{}\" ({}).",
147 cache_path_->generic_string(), error.message());
152 TC_LOG_TRACE(
"scripts.hotswap",
"Lazy unloaded the shared library \"{}\" "
153 "and deleted it's cached version at \"{}\"",
154 path_.generic_string(), cache_path_->generic_string());
158 TC_LOG_TRACE(
"scripts.hotswap",
"Lazy unloaded the shared library \"{}\".",
159 path_.generic_string());
164 fs::path
const path_;
168typedef std::unique_ptr<typename std::remove_pointer<HandleType>::type, SharedLibraryUnloader> HandleHolder;
170typedef char const* (*GetScriptModuleRevisionHashType)();
171typedef void (*AddScriptsType)();
172typedef char const* (*GetScriptModuleType)();
173typedef char const* (*GetBuildDirectiveType)();
179 explicit ScriptModule(HandleHolder handle, GetScriptModuleRevisionHashType getScriptModuleRevisionHash,
180 AddScriptsType addScripts, GetScriptModuleType getScriptModule,
181 GetBuildDirectiveType getBuildDirective, fs::path
const& path)
182 : _handle(
std::forward<HandleHolder>(handle)), _getScriptModuleRevisionHash(getScriptModuleRevisionHash),
183 _addScripts(addScripts), _getScriptModule(getScriptModule),
184 _getBuildDirective(getBuildDirective), _path(path) { }
186 ScriptModule(ScriptModule
const&) =
delete;
187 ScriptModule(ScriptModule&& right) =
delete;
189 ScriptModule& operator= (ScriptModule
const&) =
delete;
190 ScriptModule& operator= (ScriptModule&& right) =
delete;
195 static void ScheduleDelayedDelete(ScriptModule* module);
197 char const* GetScriptModuleRevisionHash()
const override
199 return _getScriptModuleRevisionHash();
204 return _addScripts();
207 char const* GetScriptModule()
const override
209 return _getScriptModule();
212 char const* GetBuildDirective()
const
214 return _getBuildDirective();
217 fs::path
const& GetModulePath()
const override
223 HandleHolder _handle;
225 GetScriptModuleRevisionHashType _getScriptModuleRevisionHash;
226 AddScriptsType _addScripts;
227 GetScriptModuleType _getScriptModule;
228 GetBuildDirectiveType _getBuildDirective;
234static bool GetFunctionFromSharedLibrary(HandleType handle, std::string
const& name, Fn& fn)
236#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
237 fn =
reinterpret_cast<Fn
>(GetProcAddress(handle, name.c_str()));
239 fn =
reinterpret_cast<Fn
>(dlsym(handle, name.c_str()));
241 return fn !=
nullptr;
248 auto const load_path = [&] () -> fs::path {
255#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
256 HandleType handle = LoadLibrary(load_path.generic_string().c_str());
258 HandleType handle = dlopen(load_path.generic_string().c_str(), RTLD_LAZY);
265 TC_LOG_ERROR(
"scripts.hotswap",
"Could not dynamic load the shared library \"{}\" "
266 "(the library is cached at {})",
267 path.generic_string(), cache_path->generic_string());
271 TC_LOG_ERROR(
"scripts.hotswap",
"Could not dynamic load the shared library \"{}\".",
272 path.generic_string());
279 HandleHolder holder(handle, SharedLibraryUnloader(path, std::move(cache_path)));
281 GetScriptModuleRevisionHashType getScriptModuleRevisionHash;
282 AddScriptsType addScripts;
283 GetScriptModuleType getScriptModule;
284 GetBuildDirectiveType getBuildDirective;
286 if (GetFunctionFromSharedLibrary(handle,
"GetScriptModuleRevisionHash", getScriptModuleRevisionHash) &&
287 GetFunctionFromSharedLibrary(handle,
"AddScripts", addScripts) &&
288 GetFunctionFromSharedLibrary(handle,
"GetScriptModule", getScriptModule) &&
289 GetFunctionFromSharedLibrary(handle,
"GetBuildDirective", getBuildDirective))
291 auto module =
new ScriptModule(std::move(holder), getScriptModuleRevisionHash,
292 addScripts, getScriptModule, getBuildDirective, path);
295 return std::shared_ptr<ScriptModule>(module, ScheduleDelayedDelete);
299 TC_LOG_ERROR(
"scripts.hotswap",
"Could not extract all required functions from the shared library \"{}\"!",
300 path.generic_string());
306static bool HasValidScriptModuleName(std::string
const& name)
311 GetSharedLibraryPrefix(),
312 GetSharedLibraryExtension()));
314 return Trinity::regex_match(name,
regex);
318class LibraryUpdateListener :
public efsw::FileWatchListener
321 LibraryUpdateListener() { }
322 virtual ~LibraryUpdateListener() { }
324 void handleFileAction(efsw::WatchID , std::string
const& dir,
325 std::string
const& filename,
efsw::Action action, std::string oldFilename =
"") final override;
328static LibraryUpdateListener libraryUpdateListener;
331class SourceUpdateListener : public efsw::FileWatchListener
333 fs::path
const path_;
335 std::string
const script_module_name_;
337 efsw::WatchID
const watcher_id_;
340 explicit SourceUpdateListener(fs::path path, std::string script_module_name);
342 virtual ~SourceUpdateListener();
344 void handleFileAction(efsw::WatchID , std::string
const& dir,
345 std::string
const& filename,
efsw::Action action, std::string oldFilename =
"") final override;
351 struct hash<
fs::path>
355 std::size_t operator()(fs::path
const& key)
const noexcept
357 return hasher(key.generic_string());
363template<
typename... T>
364static int InvokeCMakeCommand(T&&... args)
368 std::forward<T>(args)...
369 },
"scripts.hotswap");
373template<
typename... T>
374static std::shared_ptr<Trinity::AsyncProcessResult> InvokeAsyncCMakeCommand(T&&... args)
378 std::forward<T>(args)...
379 },
"scripts.hotswap");
384static std::string CalculateScriptModuleProjectName(std::string
const& module)
386 std::string module_project =
"scripts_" + module;
389 return module_project;
394static bool IsDebuggerBlockingRebuild()
396#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
397 if (IsDebuggerPresent())
415class HotSwapScriptReloadMgr final
419 friend class SourceUpdateListener;
423 enum class ChangeStateRequest :
uint8
425 CHANGE_REQUEST_ADDED,
426 CHANGE_REQUEST_MODIFIED,
427 CHANGE_REQUEST_REMOVED
431 enum class BuildJobType :
uint8
434 BUILD_JOB_RERUN_CMAKE,
443 std::string script_module_name_;
445 std::string script_module_project_name_;
449 std::string script_module_build_directive_;
456 std::shared_ptr<Trinity::AsyncProcessResult> async_result_;
459 explicit BuildJob(std::string script_module_name, std::string script_module_project_name,
460 std::string script_module_build_directive)
461 : script_module_name_(
std::move(script_module_name)),
462 script_module_project_name_(
std::move(script_module_project_name)),
463 script_module_build_directive_(
std::move(script_module_build_directive)),
464 start_time_(
getMSTime()), type_(BuildJobType::BUILD_JOB_NONE) { }
468 return type_ != BuildJobType::BUILD_JOB_NONE;
471 std::string
const& GetModuleName()
const {
return script_module_name_; }
473 std::string
const& GetProjectName()
const {
return script_module_project_name_; }
475 std::string
const& GetBuildDirective()
const {
return script_module_build_directive_; }
479 BuildJobType GetType()
const {
return type_; }
481 std::shared_ptr<Trinity::AsyncProcessResult>
const& GetProcess()
const
483 ASSERT(async_result_,
"Tried to access an empty process handle!");
484 return async_result_;
488 void UpdateCurrentJob(BuildJobType type,
489 std::shared_ptr<Trinity::AsyncProcessResult> async_result)
491 ASSERT(type != BuildJobType::BUILD_JOB_NONE,
"None isn't allowed here!");
492 ASSERT(async_result,
"The async result must not be empty!");
495 async_result_ = std::move(async_result);
500 class ScriptReloaderMessage
503 virtual ~ScriptReloaderMessage() { }
506 virtual void operator() (HotSwapScriptReloadMgr* reloader) = 0;
512 class ScriptReloaderMessageImplementation
513 :
public ScriptReloaderMessage
518 explicit ScriptReloaderMessageImplementation(T dispatcher)
519 : dispatcher_(
std::move(dispatcher)) { }
521 void operator() (HotSwapScriptReloadMgr* reloader)
final override
523 dispatcher_(reloader);
530 auto MakeMessage(T&& dispatcher)
531 -> ScriptReloaderMessageImplementation<typename std::decay<T>::type>*
533 return new ScriptReloaderMessageImplementation<typename std::decay<T>::type>
534 (std::forward<T>(dispatcher));
538 HotSwapScriptReloadMgr()
539 : _libraryWatcher(-1), _unique_library_name_counter(0),
540 _last_time_library_changed(0), _last_time_sources_changed(0),
541 _last_time_user_informed(0), terminate_early(false) { }
543 virtual ~HotSwapScriptReloadMgr()
546 ScriptReloaderMessage* message;
547 while (_messages.Dequeue(message))
552 static fs::path GetLibraryDirectory()
556 fs::path path(
sConfigMgr->GetStringDefault(
"HotSwap.ScriptDir",
"scripts"));
557 if (path.is_absolute())
560 return fs::absolute(path, GetDirectoryOfExecutable());
582 TC_LOG_ERROR(
"scripts.hotswap",
"Your build directory path \"{}\" "
583 "contains spaces, which isn't allowed for compatibility reasons! "
584 "You need to create a build directory which doesn't contain any space character "
592 auto const library_directory = GetLibraryDirectory();
593 if (!fs::exists(library_directory) || !fs::is_directory(library_directory))
595 TC_LOG_ERROR(
"scripts.hotswap",
"Library directory \"{}\" doesn't exist!.",
596 library_directory.generic_string());
601 temporary_cache_path_ = CalculateTemporaryCachePath();
605 boost::system::error_code code;
606 if ((!fs::exists(temporary_cache_path_, code)
607 || (fs::remove_all(temporary_cache_path_, code) > 0)) &&
608 !fs::create_directories(temporary_cache_path_, code))
610 TC_LOG_ERROR(
"scripts.hotswap",
"Couldn't create the cache directory at \"{}\".",
611 temporary_cache_path_.generic_string());
621 DoCMakePrefixCorrectionIfNeeded();
623 InitializeDefaultLibraries();
624 InitializeFileWatchers();
632 void Update() final
override
635 ScriptReloaderMessage* message;
636 while (_messages.Dequeue(message))
642 DispatchRunningBuildJobs();
643 DispatchModuleChanges();
647 void Unload() final
override
649 if (_libraryWatcher >= 0)
651 _fileWatcher.removeWatch(_libraryWatcher);
652 _libraryWatcher = -1;
658 _build_job->GetProcess()->Terminate();
665 _running_script_modules.clear();
671 void QueueMessage(T&& message)
673 _messages.Enqueue(MakeMessage(std::forward<T>(message)));
679 void QueueSharedLibraryChanged(fs::path
const& path)
681 _last_time_library_changed =
getMSTime();
682 _libraries_changed.insert(path);
687 void QueueAddSourceFile(std::string
const& module_name, fs::path
const& path)
689 UpdateSourceChangeRequest(module_name, path, ChangeStateRequest::CHANGE_REQUEST_ADDED);
694 void QueueModifySourceFile(std::string
const& module_name, fs::path
const& path)
696 UpdateSourceChangeRequest(module_name, path, ChangeStateRequest::CHANGE_REQUEST_MODIFIED);
701 void QueueRemoveSourceFile(std::string
const& module_name, fs::path
const& path)
703 UpdateSourceChangeRequest(module_name, path, ChangeStateRequest::CHANGE_REQUEST_REMOVED);
709 void InitializeDefaultLibraries()
711 fs::path
const libraryDirectory(GetLibraryDirectory());
712 fs::directory_iterator
const dir_end;
717 for (fs::directory_iterator dir_itr(libraryDirectory); dir_itr != dir_end ; ++dir_itr)
718 if (fs::is_regular_file(dir_itr->path()) && HasValidScriptModuleName(dir_itr->path().filename().generic_string()))
720 TC_LOG_INFO(
"scripts.hotswap",
"Loading script module \"{}\"...",
721 dir_itr->path().filename().generic_string());
724 ProcessLoadScriptModule(dir_itr->path(),
false);
728 TC_LOG_INFO(
"scripts.hotswap",
">> Loaded {} script modules.", count);
733 void InitializeFileWatchers()
735 _libraryWatcher = _fileWatcher.addWatch(GetLibraryDirectory().generic_string(), &libraryUpdateListener,
false);
736 if (_libraryWatcher >= 0)
738 TC_LOG_INFO(
"scripts.hotswap",
">> Library reloader is listening on \"{}\".",
739 GetLibraryDirectory().generic_string());
743 TC_LOG_ERROR(
"scripts.hotswap",
"Failed to initialize the library reloader on \"{}\".",
744 GetLibraryDirectory().generic_string());
747 _fileWatcher.watch();
750 static fs::path CalculateTemporaryCachePath()
752 auto path = fs::temp_directory_path();
760 fs::path GenerateUniquePathForLibraryInCache(fs::path path)
762 ASSERT(!temporary_cache_path_.empty(),
763 "The temporary cache path wasn't set!");
766 auto cache_path = temporary_cache_path_;
768 path.stem().generic_string(),
769 _unique_library_name_counter++,
770 path.extension().generic_string());
776 void UpdateSourceChangeRequest(std::string
const& module_name,
777 fs::path
const& path,
778 ChangeStateRequest state)
780 _last_time_sources_changed =
getMSTime();
783 auto module_itr = _sources_changed.find(module_name);
787 if (module_itr == _sources_changed.end())
788 module_itr = _sources_changed.insert(std::make_pair(
789 module_name,
decltype(_sources_changed)::mapped_type{})).first;
792 if (state == ChangeStateRequest::CHANGE_REQUEST_MODIFIED)
796 auto const itr = module_itr->second.find(path);
797 if (itr == module_itr->second.end())
799 module_itr->second.insert(std::make_pair(path, state));
803 ASSERT((itr->second == ChangeStateRequest::CHANGE_REQUEST_ADDED)
804 || (itr->second == ChangeStateRequest::CHANGE_REQUEST_REMOVED),
805 "Stored value is invalid!");
807 ASSERT((state == ChangeStateRequest::CHANGE_REQUEST_ADDED)
808 || (state == ChangeStateRequest::CHANGE_REQUEST_REMOVED),
809 "The given state is invalid!");
811 ASSERT(state != itr->second,
812 "Tried to apply a state which is stored already!");
814 module_itr->second.erase(itr);
819 void DispatchModuleChanges()
822 if (_libraries_changed.empty())
829 for (
auto const& path : _libraries_changed)
831 bool const is_running =
832 _running_script_module_names.find(path) != _running_script_module_names.end();
834 bool const exists = fs::exists(path);
839 ProcessReloadScriptModule(path);
841 ProcessUnloadScriptModule(path);
844 ProcessLoadScriptModule(path);
847 _libraries_changed.clear();
850 void ProcessLoadScriptModule(fs::path
const& path,
bool swap_context =
true)
852 ASSERT(_running_script_module_names.find(path) == _running_script_module_names.end(),
853 "Can't load a module which is running already!");
856 auto cache_path = GenerateUniquePathForLibraryInCache(path);
859 boost::system::error_code code;
860 fs::copy_file(path, cache_path, fs::copy_option::fail_if_exists, code);
863 TC_LOG_FATAL(
"scripts.hotswap",
">> Failed to create cache entry for module "
864 "\"{}\" at \"{}\" with reason (\"{}\")!",
865 path.filename().generic_string(), cache_path.generic_string(),
870 std::this_thread::sleep_for(std::chrono::seconds(5));
875 TC_LOG_TRACE(
"scripts.hotswap",
">> Copied the shared library \"{}\" to \"{}\" for caching.",
876 path.filename().generic_string(), cache_path.generic_string());
879 auto module = ScriptModule::CreateFromPath(path, cache_path);
882 TC_LOG_FATAL(
"scripts.hotswap",
">> Failed to load script module \"{}\"!",
883 path.filename().generic_string());
887 std::this_thread::sleep_for(std::chrono::seconds(5));
893 std::string module_revision((*module)->GetScriptModuleRevisionHash());
894 if (module_revision.size() >= 7)
895 module_revision = module_revision.substr(0, 7);
897 std::string
const module_name = (*module)->GetScriptModule();
898 TC_LOG_INFO(
"scripts.hotswap",
">> Loaded script module \"{}\" (\"{}\" - {}).",
899 path.filename().generic_string(), module_name, module_revision);
901 if (module_revision.empty())
903 TC_LOG_WARN(
"scripts.hotswap",
">> Script module \"{}\" has an empty revision hash!",
904 path.filename().generic_string());
910 std::size_t
const trim = std::min(module_revision.size(), my_revision_hash.size());
911 my_revision_hash = my_revision_hash.substr(0, trim);
912 module_revision = module_revision.substr(0, trim);
914 if (my_revision_hash != module_revision)
916 TC_LOG_WARN(
"scripts.hotswap",
">> Script module \"{}\" has a different revision hash! "
917 "Binary incompatibility could lead to unknown behaviour!", path.filename().generic_string());
922 auto const itr = _running_script_modules.find(module_name);
923 if (itr != _running_script_modules.end())
925 TC_LOG_ERROR(
"scripts.hotswap",
">> Attempt to load a module twice \"{}\" (loaded module is at {})!",
926 path.generic_string(), itr->second.first->GetModulePath().generic_string());
933 auto listener = std::make_unique<SourceUpdateListener>(
938 _known_modules_build_directives.insert(std::make_pair(module_name, (*module)->GetBuildDirective()));
939 _running_script_modules.insert(std::make_pair(module_name,
940 std::make_pair(*module, std::move(listener))));
941 _running_script_module_names.insert(std::make_pair(path, module_name));
945 (*module)->AddScripts();
946 TC_LOG_TRACE(
"scripts.hotswap",
">> Registered all scripts of module {}.", module_name);
952 void ProcessReloadScriptModule(fs::path
const& path)
954 ProcessUnloadScriptModule(path,
false);
955 ProcessLoadScriptModule(path);
958 void ProcessUnloadScriptModule(fs::path
const& path,
bool finish =
true)
960 auto const itr = _running_script_module_names.find(path);
962 ASSERT(itr != _running_script_module_names.end(),
963 "Can't unload a module which isn't running!");
966 sScriptMgr->ReleaseScriptContext(itr->second);
971 TC_LOG_INFO(
"scripts.hotswap",
"Released script module \"{}\" (\"{}\")...",
972 path.filename().generic_string(), itr->second);
975 auto ref = _running_script_modules.find(itr->second);
976 ASSERT(ref != _running_script_modules.end() &&
977 "Expected the script reference to be present!");
983 if (ref->second.first.use_count() != 1)
986 "Script module {} is still used by {} spell, aura or instance scripts. "
987 "Will lazy unload the module once all scripts stopped using it, "
988 "to use the latest version of an edited script unbind yourself from "
989 "the instance or re-cast the spell.",
990 ref->second.first->GetScriptModule(), ref->second.first.use_count() - 1);
994 _running_script_modules.erase(ref);
995 _running_script_module_names.erase(itr);
1000 void DispatchRunningBuildJobs()
1008 if (!terminate_early && _sources_changed.find(_build_job->GetModuleName()) != _sources_changed.end())
1023 terminate_early =
true;
1030 if (_build_job->GetProcess()->GetFutureResult().
1031 wait_for(0s) == std::future_status::ready)
1032 ProcessReadyBuildJob();
1042 if ((_last_time_sources_changed != 0) &&
1047 if (_sources_changed.empty())
1051 if (IsDebuggerBlockingRebuild())
1053 if ((_last_time_user_informed == 0) ||
1059 TC_LOG_INFO(
"scripts.hotswap",
"Your attached debugger is blocking the TrinityCore "
1060 "automatic script rebuild, please detach it!");
1068 bool rebuild_buildfiles;
1069 auto module_name = [&]
1071 auto itr = _sources_changed.begin();
1072 auto name = itr->first;
1073 rebuild_buildfiles = !itr->second.empty();
1076 for (
auto const& entry : itr->second)
1078 TC_LOG_TRACE(
"scripts.hotswap",
"Source file {} was {}.",
1079 entry.first.generic_string(),
1080 ((entry.second == ChangeStateRequest::CHANGE_REQUEST_ADDED) ?
1081 "added" :
"removed"));
1084 _sources_changed.erase(itr);
1091 if (rebuild_buildfiles)
1093 for (
auto& entry : _sources_changed)
1094 entry.second.clear();
1097 ASSERT(!module_name.empty(),
1098 "The current module name is invalid!");
1100 TC_LOG_INFO(
"scripts.hotswap",
"Recompiling Module \"{}\"...",
1104 auto project_name = CalculateScriptModuleProjectName(module_name);
1107 auto build_directive = [&] () -> std::string
1109 auto directive =
sConfigMgr->GetStringDefault(
"HotSwap.ReCompilerBuildType",
"");
1110 if (!directive.empty())
1113 auto const itr = _known_modules_build_directives.find(module_name);
1114 if (itr != _known_modules_build_directives.end())
1117 return _BUILD_DIRECTIVE;
1121 _build_job = BuildJob(std::move(module_name),
1122 std::move(project_name), std::move(build_directive));
1125 if (rebuild_buildfiles
1129 DoCompileCurrentProcessedModule();
1132 void ProcessReadyBuildJob()
1134 ASSERT(_build_job->IsValid(),
"Invalid build job!");
1137 auto const error = _build_job->GetProcess()->GetFutureResult().get();
1139 if (terminate_early)
1142 terminate_early =
false;
1146 switch (_build_job->GetType())
1148 case BuildJobType::BUILD_JOB_RERUN_CMAKE:
1152 TC_LOG_INFO(
"scripts.hotswap",
">> Successfully updated the build files!");
1156 TC_LOG_INFO(
"scripts.hotswap",
">> Failed to update the build files at \"{}\", "
1157 "it's possible that recently added sources are not included "
1158 "in your next builds, rerun CMake manually.",
1162 DoCompileCurrentProcessedModule();
1165 case BuildJobType::BUILD_JOB_COMPILE:
1173 ">> Successfully build module {}, continue with installing...",
1174 _build_job->GetModuleName());
1176 DoInstallCurrentProcessedModule();
1182 ">> Successfully build module {}, skipped the installation.",
1183 _build_job->GetModuleName());
1188 ">> The build of module {} failed! See the log for details.",
1189 _build_job->GetModuleName());
1193 case BuildJobType::BUILD_JOB_INSTALL:
1198 TC_LOG_INFO(
"scripts.hotswap",
">> Successfully installed module {} in {}s",
1199 _build_job->GetModuleName(),
1206 ">> The installation of module {} failed! See the log for details.",
1207 _build_job->GetModuleName());
1222 ASSERT(_build_job,
"There isn't any active build job!");
1224 TC_LOG_INFO(
"scripts.hotswap",
"Rerunning CMake because there were sources added or removed...");
1226 _build_job->UpdateCurrentJob(BuildJobType::BUILD_JOB_RERUN_CMAKE,
1231 void DoCompileCurrentProcessedModule()
1233 ASSERT(_build_job,
"There isn't any active build job!");
1235 TC_LOG_INFO(
"scripts.hotswap",
"Starting asynchronous build job for module {}...",
1236 _build_job->GetModuleName());
1238 _build_job->UpdateCurrentJob(BuildJobType::BUILD_JOB_COMPILE,
1239 InvokeAsyncCMakeCommand(
1241 "--target", _build_job->GetProjectName(),
1242 "--config", _build_job->GetBuildDirective()));
1246 void DoInstallCurrentProcessedModule()
1248 ASSERT(_build_job,
"There isn't any active build job!");
1250 TC_LOG_INFO(
"scripts.hotswap",
"Starting asynchronous install job for module {}...",
1251 _build_job->GetModuleName());
1253 _build_job->UpdateCurrentJob(BuildJobType::BUILD_JOB_INSTALL,
1254 InvokeAsyncCMakeCommand(
1255 "-DCOMPONENT=" + _build_job->GetProjectName(),
1256 "-DBUILD_TYPE=" + _build_job->GetBuildDirective(),
1257 "-P", fs::absolute(
"cmake_install.cmake",
1264 void DoCMakePrefixCorrectionIfNeeded()
1266 TC_LOG_INFO(
"scripts.hotswap",
"Correcting your CMAKE_INSTALL_PREFIX in \"{}\"...",
1269 auto const cmake_cache_path = fs::absolute(
"CMakeCache.txt",
1275 boost::system::error_code error;
1276 if (!fs::exists(cmake_cache_path, error))
1278 TC_LOG_ERROR(
"scripts.hotswap",
">> CMake cache \"{}\" doesn't exist, "
1279 "set the \"BuildDirectory\" option in your worldserver.conf to point"
1280 "to your build directory!",
1281 cmake_cache_path.generic_string());
1290 TC_LOG_TRACE(
"scripts.hotswap",
"Checking CMake cache (\"{}\") "
1291 "for the correct CMAKE_INSTALL_PREFIX location...",
1292 cmake_cache_path.generic_string());
1294 std::string cmake_cache_content;
1296 std::ifstream in(cmake_cache_path.generic_string());
1299 TC_LOG_ERROR(
"scripts.hotswap",
">> Failed to read the CMake cache at \"{}\"!",
1300 cmake_cache_path.generic_string());
1305 std::ostringstream ss;
1307 cmake_cache_content = ss.str();
1312 static std::string
const prefix_key =
"CMAKE_INSTALL_PREFIX:PATH=";
1315 auto begin = cmake_cache_content.find(prefix_key);
1316 if (begin != std::string::npos)
1318 begin += prefix_key.length();
1319 auto const end = cmake_cache_content.find(
"\n", begin);
1320 if (end != std::string::npos)
1322 fs::path value = cmake_cache_content.substr(begin, end - begin);
1324 auto current_path = fs::current_path();
1326 #if TRINITY_PLATFORM != TRINITY_PLATFORM_WINDOWS
1329 current_path = current_path.parent_path();
1332 if (value != current_path)
1336 bool const is_in_path = [&]
1339 fs::path branch = value;
1340 while (!branch.empty())
1345 branch = branch.parent_path();
1354 TC_LOG_INFO(
"scripts.hotswap",
">> Found outdated CMAKE_INSTALL_PREFIX (\"{}\"), "
1355 "worldserver is currently installed at {}",
1356 value.generic_string(), current_path.generic_string());
1360 TC_LOG_INFO(
"scripts.hotswap",
">> CMAKE_INSTALL_PREFIX is equal to the current path of execution.");
1366 TC_LOG_INFO(
"scripts.hotswap",
"Invoking CMake cache correction...");
1368 auto const error = InvokeCMakeCommand(
1369 "-DCMAKE_INSTALL_PREFIX:PATH=" + fs::current_path().generic_string(),
1374 TC_LOG_ERROR(
"scripts.hotswap",
">> Failed to update the CMAKE_INSTALL_PREFIX! "
1375 "This could lead to unexpected behaviour!");
1379 TC_LOG_ERROR(
"scripts.hotswap",
">> Successfully corrected your CMAKE_INSTALL_PREFIX variable"
1380 "to point at your current path of execution.");
1385 efsw::FileWatcher _fileWatcher;
1386 efsw::WatchID _libraryWatcher;
1390 uint32 _unique_library_name_counter;
1396 std::unordered_set<fs::path > _libraries_changed;
1398 uint32 _last_time_library_changed;
1403 std::unordered_map<std::string ,
1404 std::pair<std::shared_ptr<ScriptModule>, std::unique_ptr<SourceUpdateListener>>
1405 > _running_script_modules;
1407 std::unordered_map<fs::path, std::string > _running_script_module_names;
1409 std::unordered_map<std::string , std::string > _known_modules_build_directives;
1412 std::unordered_map<std::string ,
1413 std::unordered_map<fs::path , ChangeStateRequest >> _sources_changed;
1415 uint32 _last_time_sources_changed;
1418 uint32 _last_time_user_informed;
1425 bool terminate_early;
1428 fs::path temporary_cache_path_;
1431class ScriptModuleDeleteMessage
1434 explicit ScriptModuleDeleteMessage(ScriptModule* module)
1435 : module_(module) { }
1437 void operator() (HotSwapScriptReloadMgr*)
1443 std::unique_ptr<ScriptModule> module_;
1446void ScriptModule::ScheduleDelayedDelete(ScriptModule* module)
1456 case efsw::Action::Add:
1458 case efsw::Action::Delete:
1460 case efsw::Action::Moved:
1467void LibraryUpdateListener::handleFileAction(efsw::WatchID watchid, std::string
const& dir,
1468 std::string
const& filename,
efsw::Action action, std::string oldFilename)
1473 if (action == efsw::Action::Moved)
1475 ASSERT(!oldFilename.empty(),
"Old filename doesn't exist!");
1476 handleFileAction(watchid, dir, oldFilename, efsw::Action::Delete);
1477 handleFileAction(watchid, dir, filename, efsw::Action::Add);
1481 sScriptReloadMgr->QueueMessage([=](HotSwapScriptReloadMgr* reloader)
mutable
1483 auto const path = fs::absolute(
1487 if (!HasValidScriptModuleName(filename))
1492 case efsw::Actions::Add:
1493 TC_LOG_TRACE(
"scripts.hotswap",
">> Loading \"{}\" ({})...",
1494 path.generic_string(), ActionToString(action));
1495 reloader->QueueSharedLibraryChanged(path);
1497 case efsw::Actions::Delete:
1498 TC_LOG_TRACE(
"scripts.hotswap",
">> Unloading \"{}\" ({})...",
1499 path.generic_string(), ActionToString(action));
1500 reloader->QueueSharedLibraryChanged(path);
1502 case efsw::Actions::Modified:
1503 TC_LOG_TRACE(
"scripts.hotswap",
">> Reloading \"{}\" ({})...",
1504 path.generic_string(), ActionToString(action));
1505 reloader->QueueSharedLibraryChanged(path);
1515static bool HasCXXSourceFileExtension(fs::path
const& path)
1518 return Trinity::regex_match(path.extension().generic_string(),
regex);
1521SourceUpdateListener::SourceUpdateListener(fs::path path, std::string script_module_name)
1522 : path_(
std::move(path)), script_module_name_(
std::move(script_module_name)),
1523 watcher_id_(
sScriptReloadMgr->_fileWatcher.addWatch(path_.generic_string(), this, true))
1525 if (watcher_id_ >= 0)
1527 TC_LOG_TRACE(
"scripts.hotswap",
">> Attached the source recompiler to \"{}\".",
1528 path_.generic_string());
1532 TC_LOG_ERROR(
"scripts.hotswap",
"Failed to initialize thesource recompiler on \"{}\".",
1533 path_.generic_string());
1537SourceUpdateListener::~SourceUpdateListener()
1539 if (watcher_id_ >= 0)
1543 TC_LOG_TRACE(
"scripts.hotswap",
">> Detached the source recompiler from \"{}\".",
1544 path_.generic_string());
1548void SourceUpdateListener::handleFileAction(efsw::WatchID watchid, std::string
const& dir,
1549 std::string
const& filename,
efsw::Action action, std::string oldFilename)
1558 if (action == efsw::Action::Moved)
1560 ASSERT(!oldFilename.empty(),
"Old filename doesn't exist!");
1561 handleFileAction(watchid, dir, oldFilename, efsw::Action::Delete);
1562 handleFileAction(watchid, dir, filename, efsw::Action::Add);
1566 fs::path path = fs::absolute(
1571 if (!path.has_extension() || !HasCXXSourceFileExtension(path))
1575 sScriptReloadMgr->QueueMessage([=,
this, path = std::move(path)](HotSwapScriptReloadMgr* reloader)
1577 TC_LOG_TRACE(
"scripts.hotswap",
"Detected source change on module \"{}\", "
1578 "queued for recompilation...", script_module_name_);
1582 case efsw::Actions::Add:
1583 TC_LOG_TRACE(
"scripts.hotswap",
"Source file {} of module {} was added.",
1584 path.generic_string(), script_module_name_);
1585 reloader->QueueAddSourceFile(script_module_name_, path);
1587 case efsw::Actions::Delete:
1588 TC_LOG_TRACE(
"scripts.hotswap",
"Source file {} of module {} was deleted.",
1589 path.generic_string(), script_module_name_);
1590 reloader->QueueRemoveSourceFile(script_module_name_, path);
1592 case efsw::Actions::Modified:
1593 TC_LOG_TRACE(
"scripts.hotswap",
"Source file {} of module {} was modified.",
1594 path.generic_string(), script_module_name_);
1595 reloader->QueueModifySourceFile(script_module_name_, path);
1605std::shared_ptr<ModuleReference>
1614 &&
"Requested a reference to a non existent script context!");
1616 return itr->second.first;
1622 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)
int StartProcess(std::string const &executable, std::vector< std::string > const &args, std::string const &logger, std::string input_file, bool secure)
std::string StringFormat(FormatString< Args... > fmt, Args &&... args)
Default TC string format function.
void Update(VignetteData &vignette, WorldObject const *owner)