TrinityCore
ScriptReloadMgr.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "ScriptReloadMgr.h"
19#include "Errors.h"
20
21#ifndef TRINITY_API_USE_DYNAMIC_LINKING
22
23// This method should never be called
24std::shared_ptr<ModuleReference>
26{
27 WPAbort();
28}
29
30// Returns the empty implemented ScriptReloadMgr
32{
34 return &instance;
35}
36
37#else
38
39#include "BuiltInConfig.h"
40#include "Config.h"
41#include "GitRevision.h"
42#include "CryptoHash.h"
43#include "Duration.h"
44#include "Log.h"
45#include "MPSCQueue.h"
46#include "Optional.h"
47#include "Regex.h"
48#include "ScriptMgr.h"
49#include "StartProcess.h"
50#include "Timer.h"
51#include "Util.h"
52#include "World.h"
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>
58#include <algorithm>
59#include <fstream>
60#include <future>
61#include <memory>
62#include <sstream>
63#include <thread>
64#include <type_traits>
65#include <unordered_map>
66#include <unordered_set>
67#include <vector>
68
69namespace fs = boost::filesystem;
70
71#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
72 #include <windows.h>
73#else // Posix and Apple
74 #include <dlfcn.h>
75#endif
76
77// Promote the sScriptReloadMgr to a HotSwapScriptReloadMgr
78// in this compilation unit.
79#undef sScriptReloadMgr
80#define sScriptReloadMgr static_cast<HotSwapScriptReloadMgr*>(ScriptReloadMgr::instance())
81
82// Returns "" on Windows and "lib" on posix.
83static char const* GetSharedLibraryPrefix()
84{
85#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
86 return "";
87#else // Posix
88 return "lib";
89#endif
90}
91
92// Returns "dll" on Windows, "dylib" on OS X, and "so" on posix.
93static char const* GetSharedLibraryExtension()
94{
95#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
96 return "dll";
97#elif TRINITY_PLATFORM == TRINITY_PLATFORM_APPLE
98 return "dylib";
99#else // Posix
100 return "so";
101#endif
102}
103
104#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
105typedef HMODULE HandleType;
106#else // Posix
107typedef void* HandleType;
108#endif
109
110static fs::path GetDirectoryOfExecutable()
111{
112 return boost::dll::program_location().parent_path();
113}
114
115class SharedLibraryUnloader
116{
117public:
118 explicit SharedLibraryUnloader(fs::path path)
119 : path_(std::move(path)) { }
120 SharedLibraryUnloader(fs::path path, Optional<fs::path> cache_path)
121 : path_(std::move(path)), cache_path_(std::move(cache_path)) { }
122
123 void operator() (HandleType handle) const
124 {
125 // Unload the associated shared library.
126#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
127 bool success = (FreeLibrary(handle) != 0);
128#else // Posix
129 bool success = (dlclose(handle) == 0);
130#endif
131
132 if (!success)
133 {
134 TC_LOG_ERROR("scripts.hotswap", "Failed to unload (syscall) the shared library \"{}\".",
135 path_.generic_string());
136
137 return;
138 }
139
141 if (cache_path_)
142 {
143 boost::system::error_code error;
144 if (!fs::remove(*cache_path_, error))
145 {
146 TC_LOG_ERROR("scripts.hotswap", "Failed to delete the cached shared library \"{}\" ({}).",
147 cache_path_->generic_string(), error.message());
148
149 return;
150 }
151
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());
155 }
156 else
157 {
158 TC_LOG_TRACE("scripts.hotswap", "Lazy unloaded the shared library \"{}\".",
159 path_.generic_string());
160 }
161 }
162
163private:
164 fs::path const path_;
165 Optional<fs::path> const cache_path_;
166};
167
168typedef std::unique_ptr<typename std::remove_pointer<HandleType>::type, SharedLibraryUnloader> HandleHolder;
169
170typedef char const* (*GetScriptModuleRevisionHashType)();
171typedef void (*AddScriptsType)();
172typedef char const* (*GetScriptModuleType)();
173typedef char const* (*GetBuildDirectiveType)();
174
175class ScriptModule
176 : public ModuleReference
177{
178public:
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) { }
185
186 ScriptModule(ScriptModule const&) = delete;
187 ScriptModule(ScriptModule&& right) = delete;
188
189 ScriptModule& operator= (ScriptModule const&) = delete;
190 ScriptModule& operator= (ScriptModule&& right) = delete;
191
193 CreateFromPath(fs::path const& path, Optional<fs::path> cache_path);
194
195 static void ScheduleDelayedDelete(ScriptModule* module);
196
197 char const* GetScriptModuleRevisionHash() const override
198 {
199 return _getScriptModuleRevisionHash();
200 }
201
202 void AddScripts() const
203 {
204 return _addScripts();
205 }
206
207 char const* GetScriptModule() const override
208 {
209 return _getScriptModule();
210 }
211
212 char const* GetBuildDirective() const
213 {
214 return _getBuildDirective();
215 }
216
217 fs::path const& GetModulePath() const override
218 {
219 return _path;
220 }
221
222private:
223 HandleHolder _handle;
224
225 GetScriptModuleRevisionHashType _getScriptModuleRevisionHash;
226 AddScriptsType _addScripts;
227 GetScriptModuleType _getScriptModule;
228 GetBuildDirectiveType _getBuildDirective;
229
230 fs::path _path;
231};
232
233template<typename Fn>
234static bool GetFunctionFromSharedLibrary(HandleType handle, std::string const& name, Fn& fn)
235{
236#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
237 fn = reinterpret_cast<Fn>(GetProcAddress(handle, name.c_str()));
238#else // Posix
239 fn = reinterpret_cast<Fn>(dlsym(handle, name.c_str()));
240#endif
241 return fn != nullptr;
242}
243
244// Load a shared library from the given path.
246 ScriptModule::CreateFromPath(fs::path const& path, Optional<fs::path> cache_path)
247{
248 auto const load_path = [&] () -> fs::path {
249 if (cache_path)
250 return *cache_path;
251 else
252 return path;
253 }();
254
255#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
256 HandleType handle = LoadLibrary(load_path.generic_string().c_str());
257#else // Posix
258 HandleType handle = dlopen(load_path.generic_string().c_str(), RTLD_LAZY);
259#endif
260
261 if (!handle)
262 {
263 if (cache_path)
264 {
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());
268 }
269 else
270 {
271 TC_LOG_ERROR("scripts.hotswap", "Could not dynamic load the shared library \"{}\".",
272 path.generic_string());
273 }
274
275 return {};
276 }
277
278 // Use RAII to release the library on failure.
279 HandleHolder holder(handle, SharedLibraryUnloader(path, std::move(cache_path)));
280
281 GetScriptModuleRevisionHashType getScriptModuleRevisionHash;
282 AddScriptsType addScripts;
283 GetScriptModuleType getScriptModule;
284 GetBuildDirectiveType getBuildDirective;
285
286 if (GetFunctionFromSharedLibrary(handle, "GetScriptModuleRevisionHash", getScriptModuleRevisionHash) &&
287 GetFunctionFromSharedLibrary(handle, "AddScripts", addScripts) &&
288 GetFunctionFromSharedLibrary(handle, "GetScriptModule", getScriptModule) &&
289 GetFunctionFromSharedLibrary(handle, "GetBuildDirective", getBuildDirective))
290 {
291 auto module = new ScriptModule(std::move(holder), getScriptModuleRevisionHash,
292 addScripts, getScriptModule, getBuildDirective, path);
293
294 // Unload the module at the next update tick as soon as all references are removed
295 return std::shared_ptr<ScriptModule>(module, ScheduleDelayedDelete);
296 }
297 else
298 {
299 TC_LOG_ERROR("scripts.hotswap", "Could not extract all required functions from the shared library \"{}\"!",
300 path.generic_string());
301
302 return {};
303 }
304}
305
306static bool HasValidScriptModuleName(std::string const& name)
307{
308 // Detects scripts_NAME.dll's / .so's
309 static Trinity::regex const regex(
310 Trinity::StringFormat("^{}[sS]cripts_[a-zA-Z0-9_]+\\.{}$",
311 GetSharedLibraryPrefix(),
312 GetSharedLibraryExtension()));
313
314 return Trinity::regex_match(name, regex);
315}
316
318class LibraryUpdateListener : public efsw::FileWatchListener
319{
320public:
321 LibraryUpdateListener() { }
322 virtual ~LibraryUpdateListener() { }
323
324 void handleFileAction(efsw::WatchID /*watchid*/, std::string const& dir,
325 std::string const& filename, efsw::Action action, std::string oldFilename = "") final override;
326};
327
328static LibraryUpdateListener libraryUpdateListener;
329
331class SourceUpdateListener : public efsw::FileWatchListener
332{
333 fs::path const path_;
334
335 std::string const script_module_name_;
336
337 efsw::WatchID const watcher_id_;
338
339public:
340 explicit SourceUpdateListener(fs::path path, std::string script_module_name);
341
342 virtual ~SourceUpdateListener();
343
344 void handleFileAction(efsw::WatchID /*watchid*/, std::string const& dir,
345 std::string const& filename, efsw::Action action, std::string oldFilename = "") final override;
346};
347
348namespace std
349{
350 template <>
351 struct hash<fs::path>
352 {
353 hash<string> hasher;
354
355 std::size_t operator()(fs::path const& key) const noexcept
356 {
357 return hasher(key.generic_string());
358 }
359 };
360}
361
363template<typename... T>
364static int InvokeCMakeCommand(T&&... args)
365{
366 auto const executable = BuiltInConfig::GetCMakeCommand();
367 return Trinity::StartProcess(executable, {
368 std::forward<T>(args)...
369 }, "scripts.hotswap");
370}
371
373template<typename... T>
374static std::shared_ptr<Trinity::AsyncProcessResult> InvokeAsyncCMakeCommand(T&&... args)
375{
376 auto const executable = BuiltInConfig::GetCMakeCommand();
377 return Trinity::StartAsyncProcess(executable, {
378 std::forward<T>(args)...
379 }, "scripts.hotswap");
380}
381
384static std::string CalculateScriptModuleProjectName(std::string const& module)
385{
386 std::string module_project = "scripts_" + module;
387 strToLower(module_project);
388
389 return module_project;
390}
391
394static bool IsDebuggerBlockingRebuild()
395{
396#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
397 if (IsDebuggerPresent())
398 return true;
399#endif
400 return false;
401}
402
415class HotSwapScriptReloadMgr final
416 : public ScriptReloadMgr
417{
418 friend class ScriptReloadMgr;
419 friend class SourceUpdateListener;
420
423 enum class ChangeStateRequest : uint8
424 {
425 CHANGE_REQUEST_ADDED,
426 CHANGE_REQUEST_MODIFIED,
427 CHANGE_REQUEST_REMOVED
428 };
429
431 enum class BuildJobType : uint8
432 {
433 BUILD_JOB_NONE,
434 BUILD_JOB_RERUN_CMAKE,
435 BUILD_JOB_COMPILE,
436 BUILD_JOB_INSTALL,
437 };
438
439 // Represents a job which was invoked through a source or shared library change
440 class BuildJob
441 {
442 // Script module which is processed in the current running job
443 std::string script_module_name_;
444 // The C++ project name of the module which is processed
445 std::string script_module_project_name_;
446 // The build directive of the current module which is processed
447 // like "Release" or "Debug". The build directive from the
448 // previous same module is used if there was any.
449 std::string script_module_build_directive_;
450 // The time where the build job started
451 uint32 start_time_;
452
453 // Type of the current running job
454 BuildJobType type_;
455 // The async process result of the current job
456 std::shared_ptr<Trinity::AsyncProcessResult> async_result_;
457
458 public:
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) { }
465
466 bool IsValid() const
467 {
468 return type_ != BuildJobType::BUILD_JOB_NONE;
469 }
470
471 std::string const& GetModuleName() const { return script_module_name_; }
472
473 std::string const& GetProjectName() const { return script_module_project_name_; }
474
475 std::string const& GetBuildDirective() const { return script_module_build_directive_; }
476
477 uint32 GetTimeFromStart() const { return GetMSTimeDiffToNow(start_time_); }
478
479 BuildJobType GetType() const { return type_; }
480
481 std::shared_ptr<Trinity::AsyncProcessResult> const& GetProcess() const
482 {
483 ASSERT(async_result_, "Tried to access an empty process handle!");
484 return async_result_;
485 }
486
488 void UpdateCurrentJob(BuildJobType type,
489 std::shared_ptr<Trinity::AsyncProcessResult> async_result)
490 {
491 ASSERT(type != BuildJobType::BUILD_JOB_NONE, "None isn't allowed here!");
492 ASSERT(async_result, "The async result must not be empty!");
493
494 type_ = type;
495 async_result_ = std::move(async_result);
496 }
497 };
498
500 class ScriptReloaderMessage
501 {
502 public:
503 virtual ~ScriptReloaderMessage() { }
504
506 virtual void operator() (HotSwapScriptReloadMgr* reloader) = 0;
507 };
508
511 template<typename T>
512 class ScriptReloaderMessageImplementation
513 : public ScriptReloaderMessage
514 {
515 T dispatcher_;
516
517 public:
518 explicit ScriptReloaderMessageImplementation(T dispatcher)
519 : dispatcher_(std::move(dispatcher)) { }
520
521 void operator() (HotSwapScriptReloadMgr* reloader) final override
522 {
523 dispatcher_(reloader);
524 }
525 };
526
529 template<typename T>
530 auto MakeMessage(T&& dispatcher)
531 -> ScriptReloaderMessageImplementation<typename std::decay<T>::type>*
532 {
533 return new ScriptReloaderMessageImplementation<typename std::decay<T>::type>
534 (std::forward<T>(dispatcher));
535 }
536
537public:
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) { }
542
543 virtual ~HotSwapScriptReloadMgr()
544 {
545 // Delete all messages
546 ScriptReloaderMessage* message;
547 while (_messages.Dequeue(message))
548 delete message;
549 }
550
552 static fs::path GetLibraryDirectory()
553 {
554 // When an absolute path is given in the config use it,
555 // otherwise interpret paths relative to the executable.
556 fs::path path(sConfigMgr->GetStringDefault("HotSwap.ScriptDir", "scripts"));
557 if (path.is_absolute())
558 return path;
559 else
560 return fs::absolute(path, GetDirectoryOfExecutable());
561 }
562
564 static fs::path GetSourceDirectory()
565 {
566 fs::path dir = BuiltInConfig::GetSourceDirectory();
567 dir /= "src";
568 dir /= "server";
569 dir /= "scripts";
570 return dir;
571 }
572
575 void Initialize() final override
576 {
577 if (!sWorld->getBoolConfig(CONFIG_HOTSWAP_ENABLED))
578 return;
579
580 if (BuiltInConfig::GetBuildDirectory().find(" ") != std::string::npos)
581 {
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 "
585 "in it's path!",
587
588 return;
589 }
590
591 {
592 auto const library_directory = GetLibraryDirectory();
593 if (!fs::exists(library_directory) || !fs::is_directory(library_directory))
594 {
595 TC_LOG_ERROR("scripts.hotswap", "Library directory \"{}\" doesn't exist!.",
596 library_directory.generic_string());
597 return;
598 }
599 }
600
601 temporary_cache_path_ = CalculateTemporaryCachePath();
602
603 // We use the boost filesystem function versions which accept
604 // an error code to prevent it from throwing exceptions.
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))
609 {
610 TC_LOG_ERROR("scripts.hotswap", "Couldn't create the cache directory at \"{}\".",
611 temporary_cache_path_.generic_string());
612
613 return;
614 }
615
616 // Used to silent compiler warnings
617 (void)code;
618
619 // Correct the CMake prefix when needed
621 DoCMakePrefixCorrectionIfNeeded();
622
623 InitializeDefaultLibraries();
624 InitializeFileWatchers();
625 }
626
632 void Update() final override
633 {
634 // Consume all messages
635 ScriptReloaderMessage* message;
636 while (_messages.Dequeue(message))
637 {
638 (*message)(this);
639 delete message;
640 }
641
642 DispatchRunningBuildJobs();
643 DispatchModuleChanges();
644 }
645
647 void Unload() final override
648 {
649 if (_libraryWatcher >= 0)
650 {
651 _fileWatcher.removeWatch(_libraryWatcher);
652 _libraryWatcher = -1;
653 }
654
655 // If a build is in progress cancel it
656 if (_build_job)
657 {
658 _build_job->GetProcess()->Terminate();
659 _build_job.reset();
660 }
661
662 // Release all strong references to script modules
663 // to trigger unload actions as early as possible,
664 // otherwise the worldserver will crash on exit.
665 _running_script_modules.clear();
666 }
667
670 template<typename T>
671 void QueueMessage(T&& message)
672 {
673 _messages.Enqueue(MakeMessage(std::forward<T>(message)));
674 }
675
679 void QueueSharedLibraryChanged(fs::path const& path)
680 {
681 _last_time_library_changed = getMSTime();
682 _libraries_changed.insert(path);
683 }
684
687 void QueueAddSourceFile(std::string const& module_name, fs::path const& path)
688 {
689 UpdateSourceChangeRequest(module_name, path, ChangeStateRequest::CHANGE_REQUEST_ADDED);
690 }
691
694 void QueueModifySourceFile(std::string const& module_name, fs::path const& path)
695 {
696 UpdateSourceChangeRequest(module_name, path, ChangeStateRequest::CHANGE_REQUEST_MODIFIED);
697 }
698
701 void QueueRemoveSourceFile(std::string const& module_name, fs::path const& path)
702 {
703 UpdateSourceChangeRequest(module_name, path, ChangeStateRequest::CHANGE_REQUEST_REMOVED);
704 }
705
706private:
707 // Loads all shared libraries which are contained in the
708 // scripts directory on startup.
709 void InitializeDefaultLibraries()
710 {
711 fs::path const libraryDirectory(GetLibraryDirectory());
712 fs::directory_iterator const dir_end;
713
714 uint32 count = 0;
715
716 // Iterate through all shared libraries in the script directory and load it
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()))
719 {
720 TC_LOG_INFO("scripts.hotswap", "Loading script module \"{}\"...",
721 dir_itr->path().filename().generic_string());
722
723 // Don't swap the script context to do bulk loading
724 ProcessLoadScriptModule(dir_itr->path(), false);
725 ++count;
726 }
727
728 TC_LOG_INFO("scripts.hotswap", ">> Loaded {} script modules.", count);
729 }
730
731 // Initialize all enabled file watchers.
732 // Needs to be called after InitializeDefaultLibraries()!
733 void InitializeFileWatchers()
734 {
735 _libraryWatcher = _fileWatcher.addWatch(GetLibraryDirectory().generic_string(), &libraryUpdateListener, false);
736 if (_libraryWatcher >= 0)
737 {
738 TC_LOG_INFO("scripts.hotswap", ">> Library reloader is listening on \"{}\".",
739 GetLibraryDirectory().generic_string());
740 }
741 else
742 {
743 TC_LOG_ERROR("scripts.hotswap", "Failed to initialize the library reloader on \"{}\".",
744 GetLibraryDirectory().generic_string());
745 }
746
747 _fileWatcher.watch();
748 }
749
750 static fs::path CalculateTemporaryCachePath()
751 {
752 auto path = fs::temp_directory_path();
753 path /= Trinity::StringFormat("tc_script_cache_{}_{}",
756
757 return path;
758 }
759
760 fs::path GenerateUniquePathForLibraryInCache(fs::path path)
761 {
762 ASSERT(!temporary_cache_path_.empty(),
763 "The temporary cache path wasn't set!");
764
765 // Create the cache path and increment the library counter to use an unique name for each library
766 auto cache_path = temporary_cache_path_;
767 cache_path /= Trinity::StringFormat("{}.{}{}",
768 path.stem().generic_string(),
769 _unique_library_name_counter++,
770 path.extension().generic_string());
771
772 return cache_path;
773 }
774
776 void UpdateSourceChangeRequest(std::string const& module_name,
777 fs::path const& path,
778 ChangeStateRequest state)
779 {
780 _last_time_sources_changed = getMSTime();
781
782 // Write when there is no module with the given name known
783 auto module_itr = _sources_changed.find(module_name);
784
785 // When the file was modified it's enough to mark the module as
786 // dirty by initializing the associated map.
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;
790
791 // Leave when the file was just modified as explained above
792 if (state == ChangeStateRequest::CHANGE_REQUEST_MODIFIED)
793 return;
794
795 // Insert when the given path isn't existent
796 auto const itr = module_itr->second.find(path);
797 if (itr == module_itr->second.end())
798 {
799 module_itr->second.insert(std::make_pair(path, state));
800 return;
801 }
802
803 ASSERT((itr->second == ChangeStateRequest::CHANGE_REQUEST_ADDED)
804 || (itr->second == ChangeStateRequest::CHANGE_REQUEST_REMOVED),
805 "Stored value is invalid!");
806
807 ASSERT((state == ChangeStateRequest::CHANGE_REQUEST_ADDED)
808 || (state == ChangeStateRequest::CHANGE_REQUEST_REMOVED),
809 "The given state is invalid!");
810
811 ASSERT(state != itr->second,
812 "Tried to apply a state which is stored already!");
813
814 module_itr->second.erase(itr);
815 }
816
819 void DispatchModuleChanges()
820 {
821 // When there are no libraries to change return
822 if (_libraries_changed.empty())
823 return;
824
825 // Wait some time after changes to catch bulk changes
826 if (GetMSTimeDiffToNow(_last_time_library_changed) < 500)
827 return;
828
829 for (auto const& path : _libraries_changed)
830 {
831 bool const is_running =
832 _running_script_module_names.find(path) != _running_script_module_names.end();
833
834 bool const exists = fs::exists(path);
835
836 if (is_running)
837 {
838 if (exists)
839 ProcessReloadScriptModule(path);
840 else
841 ProcessUnloadScriptModule(path);
842 }
843 else if (exists)
844 ProcessLoadScriptModule(path);
845 }
846
847 _libraries_changed.clear();
848 }
849
850 void ProcessLoadScriptModule(fs::path const& path, bool swap_context = true)
851 {
852 ASSERT(_running_script_module_names.find(path) == _running_script_module_names.end(),
853 "Can't load a module which is running already!");
854
855 // Copy the shared library into a cache
856 auto cache_path = GenerateUniquePathForLibraryInCache(path);
857
858 {
859 boost::system::error_code code;
860 fs::copy_file(path, cache_path, fs::copy_option::fail_if_exists, code);
861 if (code)
862 {
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(),
866 code.message());
867
868 // Find a better solution for this but it's much better
869 // to start the core without scripts
870 std::this_thread::sleep_for(std::chrono::seconds(5));
871 ABORT();
872 return;
873 }
874
875 TC_LOG_TRACE("scripts.hotswap", ">> Copied the shared library \"{}\" to \"{}\" for caching.",
876 path.filename().generic_string(), cache_path.generic_string());
877 }
878
879 auto module = ScriptModule::CreateFromPath(path, cache_path);
880 if (!module)
881 {
882 TC_LOG_FATAL("scripts.hotswap", ">> Failed to load script module \"{}\"!",
883 path.filename().generic_string());
884
885 // Find a better solution for this but it's much better
886 // to start the core without scripts
887 std::this_thread::sleep_for(std::chrono::seconds(5));
888 ABORT();
889 return;
890 }
891
892 // Limit the git revision hash to 7 characters.
893 std::string module_revision((*module)->GetScriptModuleRevisionHash());
894 if (module_revision.size() >= 7)
895 module_revision = module_revision.substr(0, 7);
896
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);
900
901 if (module_revision.empty())
902 {
903 TC_LOG_WARN("scripts.hotswap", ">> Script module \"{}\" has an empty revision hash!",
904 path.filename().generic_string());
905 }
906 else
907 {
908 // Trim the revision hash
909 std::string my_revision_hash = GitRevision::GetHash();
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);
913
914 if (my_revision_hash != module_revision)
915 {
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());
918 }
919 }
920
921 {
922 auto const itr = _running_script_modules.find(module_name);
923 if (itr != _running_script_modules.end())
924 {
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());
927
928 return;
929 }
930 }
931
932 // Create the source listener
933 auto listener = std::make_unique<SourceUpdateListener>(
934 sScriptReloadMgr->GetSourceDirectory() / module_name,
935 module_name);
936
937 // Store the module
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));
942
943 // Process the script loading after the module was registered correctly (#17557).
944 sScriptMgr->SetScriptContext(module_name);
945 (*module)->AddScripts();
946 TC_LOG_TRACE("scripts.hotswap", ">> Registered all scripts of module {}.", module_name);
947
948 if (swap_context)
949 sScriptMgr->SwapScriptContext();
950 }
951
952 void ProcessReloadScriptModule(fs::path const& path)
953 {
954 ProcessUnloadScriptModule(path, false);
955 ProcessLoadScriptModule(path);
956 }
957
958 void ProcessUnloadScriptModule(fs::path const& path, bool finish = true)
959 {
960 auto const itr = _running_script_module_names.find(path);
961
962 ASSERT(itr != _running_script_module_names.end(),
963 "Can't unload a module which isn't running!");
964
965 // Unload the script context
966 sScriptMgr->ReleaseScriptContext(itr->second);
967
968 if (finish)
969 sScriptMgr->SwapScriptContext();
970
971 TC_LOG_INFO("scripts.hotswap", "Released script module \"{}\" (\"{}\")...",
972 path.filename().generic_string(), itr->second);
973
974 // Unload the script module
975 auto ref = _running_script_modules.find(itr->second);
976 ASSERT(ref != _running_script_modules.end() &&
977 "Expected the script reference to be present!");
978
979 // Yield a message when there are other owning references to
980 // the module which prevents it from unloading.
981 // The module will be unloaded once all scripts provided from the module
982 // are destroyed.
983 if (ref->second.first.use_count() != 1)
984 {
985 TC_LOG_INFO("scripts.hotswap",
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);
991 }
992
993 // Remove the owning reference from the reloader
994 _running_script_modules.erase(ref);
995 _running_script_module_names.erase(itr);
996 }
997
1000 void DispatchRunningBuildJobs()
1001 {
1002 if (_build_job)
1003 {
1004 // Terminate the current build job when an associated source was changed
1005 // while compiling and the terminate early option is enabled.
1007 {
1008 if (!terminate_early && _sources_changed.find(_build_job->GetModuleName()) != _sources_changed.end())
1009 {
1010 /*
1011 FIXME: Currently crashes the server
1012 TC_LOG_INFO("scripts.hotswap", "Terminating the running build of module \"{}\"...",
1013 _build_job->GetModuleName());
1014
1015 _build_job->GetProcess()->Terminate();
1016 _build_job.reset();
1017
1018 // Continue with the default execution path
1019 DispatchRunningBuildJobs();
1020 return;
1021 */
1022
1023 terminate_early = true;
1024 return;
1025 }
1026 }
1027
1028 // Wait for the current build job to finish, if the job finishes in time
1029 // evaluate it and continue with the next one.
1030 if (_build_job->GetProcess()->GetFutureResult().
1031 wait_for(0s) == std::future_status::ready)
1032 ProcessReadyBuildJob();
1033 else
1034 return; // Return when the job didn't finish in time
1035
1036 // Skip this cycle when the previous job scheduled a new one
1037 if (_build_job)
1038 return;
1039 }
1040
1041 // Avoid burst updates through waiting for a short time after changes
1042 if ((_last_time_sources_changed != 0) &&
1043 (GetMSTimeDiffToNow(_last_time_sources_changed) < 500))
1044 return;
1045
1046 // If the changed sources are empty do nothing
1047 if (_sources_changed.empty())
1048 return;
1049
1050 // Wait until are attached debugger were detached.
1051 if (IsDebuggerBlockingRebuild())
1052 {
1053 if ((_last_time_user_informed == 0) ||
1054 (GetMSTimeDiffToNow(_last_time_user_informed) > 7500))
1055 {
1056 _last_time_user_informed = getMSTime();
1057
1058 // Informs the user that the attached debugger is blocking the automatic script rebuild.
1059 TC_LOG_INFO("scripts.hotswap", "Your attached debugger is blocking the TrinityCore "
1060 "automatic script rebuild, please detach it!");
1061 }
1062
1063 return;
1064 }
1065
1066 // Find all source files of a changed script module and removes
1067 // it from the changed source list, invoke the build afterwards.
1068 bool rebuild_buildfiles;
1069 auto module_name = [&]
1070 {
1071 auto itr = _sources_changed.begin();
1072 auto name = itr->first;
1073 rebuild_buildfiles = !itr->second.empty();
1074
1075 if (sLog->ShouldLog("scripts.hotswap", LogLevel::LOG_LEVEL_TRACE))
1076 for (auto const& entry : itr->second)
1077 {
1078 TC_LOG_TRACE("scripts.hotswap", "Source file {} was {}.",
1079 entry.first.generic_string(),
1080 ((entry.second == ChangeStateRequest::CHANGE_REQUEST_ADDED) ?
1081 "added" : "removed"));
1082 }
1083
1084 _sources_changed.erase(itr);
1085 return name;
1086 }();
1087
1088 // Erase the added delete history all modules when we
1089 // invoke a cmake rebuild since we add all
1090 // added files of other modules to the build as well
1091 if (rebuild_buildfiles)
1092 {
1093 for (auto& entry : _sources_changed)
1094 entry.second.clear();
1095 }
1096
1097 ASSERT(!module_name.empty(),
1098 "The current module name is invalid!");
1099
1100 TC_LOG_INFO("scripts.hotswap", "Recompiling Module \"{}\"...",
1101 module_name);
1102
1103 // Calculate the project name of the script module
1104 auto project_name = CalculateScriptModuleProjectName(module_name);
1105
1106 // Find the best build directive for the module
1107 auto build_directive = [&] () -> std::string
1108 {
1109 auto directive = sConfigMgr->GetStringDefault("HotSwap.ReCompilerBuildType", "");
1110 if (!directive.empty())
1111 return directive;
1112
1113 auto const itr = _known_modules_build_directives.find(module_name);
1114 if (itr != _known_modules_build_directives.end())
1115 return itr->second;
1116 else // If no build directive of the module was found use the one from the game library
1117 return _BUILD_DIRECTIVE;
1118 }();
1119
1120 // Initiate the new build job
1121 _build_job = BuildJob(std::move(module_name),
1122 std::move(project_name), std::move(build_directive));
1123
1124 // Rerun CMake when we need to recreate the build files
1125 if (rebuild_buildfiles
1127 DoRerunCMake();
1128 else
1129 DoCompileCurrentProcessedModule();
1130 }
1131
1132 void ProcessReadyBuildJob()
1133 {
1134 ASSERT(_build_job->IsValid(), "Invalid build job!");
1135
1136 // Retrieve the result
1137 auto const error = _build_job->GetProcess()->GetFutureResult().get();
1138
1139 if (terminate_early)
1140 {
1141 _build_job.reset();
1142 terminate_early = false;
1143 return;
1144 }
1145
1146 switch (_build_job->GetType())
1147 {
1148 case BuildJobType::BUILD_JOB_RERUN_CMAKE:
1149 {
1150 if (!error)
1151 {
1152 TC_LOG_INFO("scripts.hotswap", ">> Successfully updated the build files!");
1153 }
1154 else
1155 {
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.",
1160 }
1161 // Continue with building the changes sources
1162 DoCompileCurrentProcessedModule();
1163 return;
1164 }
1165 case BuildJobType::BUILD_JOB_COMPILE:
1166 {
1167 if (!error) // Build was successful
1168 {
1169 if (sWorld->getBoolConfig(CONFIG_HOTSWAP_INSTALL_ENABLED))
1170 {
1171 // Continue with the installation when it's enabled
1172 TC_LOG_INFO("scripts.hotswap",
1173 ">> Successfully build module {}, continue with installing...",
1174 _build_job->GetModuleName());
1175
1176 DoInstallCurrentProcessedModule();
1177 return;
1178 }
1179
1180 // Skip the installation because it's disabled in config
1181 TC_LOG_INFO("scripts.hotswap",
1182 ">> Successfully build module {}, skipped the installation.",
1183 _build_job->GetModuleName());
1184 }
1185 else // Build wasn't successful
1186 {
1187 TC_LOG_ERROR("scripts.hotswap",
1188 ">> The build of module {} failed! See the log for details.",
1189 _build_job->GetModuleName());
1190 }
1191 break;
1192 }
1193 case BuildJobType::BUILD_JOB_INSTALL:
1194 {
1195 if (!error)
1196 {
1197 // Installation was successful
1198 TC_LOG_INFO("scripts.hotswap", ">> Successfully installed module {} in {}s",
1199 _build_job->GetModuleName(),
1200 _build_job->GetTimeFromStart() / IN_MILLISECONDS);
1201 }
1202 else
1203 {
1204 // Installation wasn't successful
1205 TC_LOG_INFO("scripts.hotswap",
1206 ">> The installation of module {} failed! See the log for details.",
1207 _build_job->GetModuleName());
1208 }
1209 break;
1210 }
1211 default:
1212 break;
1213 }
1214
1215 // Clear the current job
1216 _build_job.reset();
1217 }
1218
1220 void DoRerunCMake()
1221 {
1222 ASSERT(_build_job, "There isn't any active build job!");
1223
1224 TC_LOG_INFO("scripts.hotswap", "Rerunning CMake because there were sources added or removed...");
1225
1226 _build_job->UpdateCurrentJob(BuildJobType::BUILD_JOB_RERUN_CMAKE,
1227 InvokeAsyncCMakeCommand(BuiltInConfig::GetBuildDirectory()));
1228 }
1229
1231 void DoCompileCurrentProcessedModule()
1232 {
1233 ASSERT(_build_job, "There isn't any active build job!");
1234
1235 TC_LOG_INFO("scripts.hotswap", "Starting asynchronous build job for module {}...",
1236 _build_job->GetModuleName());
1237
1238 _build_job->UpdateCurrentJob(BuildJobType::BUILD_JOB_COMPILE,
1239 InvokeAsyncCMakeCommand(
1241 "--target", _build_job->GetProjectName(),
1242 "--config", _build_job->GetBuildDirective()));
1243 }
1244
1246 void DoInstallCurrentProcessedModule()
1247 {
1248 ASSERT(_build_job, "There isn't any active build job!");
1249
1250 TC_LOG_INFO("scripts.hotswap", "Starting asynchronous install job for module {}...",
1251 _build_job->GetModuleName());
1252
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",
1258 BuiltInConfig::GetBuildDirectory()).generic_string()));
1259 }
1260
1264 void DoCMakePrefixCorrectionIfNeeded()
1265 {
1266 TC_LOG_INFO("scripts.hotswap", "Correcting your CMAKE_INSTALL_PREFIX in \"{}\"...",
1268
1269 auto const cmake_cache_path = fs::absolute("CMakeCache.txt",
1271
1272 // Stop when the CMakeCache wasn't found
1273 if (![&]
1274 {
1275 boost::system::error_code error;
1276 if (!fs::exists(cmake_cache_path, error))
1277 {
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());
1282
1283 return false;
1284 }
1285 else
1286 return true;
1287 }())
1288 return;
1289
1290 TC_LOG_TRACE("scripts.hotswap", "Checking CMake cache (\"{}\") "
1291 "for the correct CMAKE_INSTALL_PREFIX location...",
1292 cmake_cache_path.generic_string());
1293
1294 std::string cmake_cache_content;
1295 {
1296 std::ifstream in(cmake_cache_path.generic_string());
1297 if (!in.is_open())
1298 {
1299 TC_LOG_ERROR("scripts.hotswap", ">> Failed to read the CMake cache at \"{}\"!",
1300 cmake_cache_path.generic_string());
1301
1302 return;
1303 }
1304
1305 std::ostringstream ss;
1306 ss << in.rdbuf();
1307 cmake_cache_content = ss.str();
1308
1309 in.close();
1310 }
1311
1312 static std::string const prefix_key = "CMAKE_INSTALL_PREFIX:PATH=";
1313
1314 // Extract the value of CMAKE_INSTALL_PREFIX
1315 auto begin = cmake_cache_content.find(prefix_key);
1316 if (begin != std::string::npos)
1317 {
1318 begin += prefix_key.length();
1319 auto const end = cmake_cache_content.find("\n", begin);
1320 if (end != std::string::npos)
1321 {
1322 fs::path value = cmake_cache_content.substr(begin, end - begin);
1323
1324 auto current_path = fs::current_path();
1325
1326 #if TRINITY_PLATFORM != TRINITY_PLATFORM_WINDOWS
1327 // The worldserver location is ${CMAKE_INSTALL_PREFIX}/bin
1328 // on all other platforms then windows
1329 current_path = current_path.parent_path();
1330 #endif
1331
1332 if (value != current_path)
1333 {
1334 // Prevent correction of the install prefix
1335 // when we are starting the core from inside the build tree
1336 bool const is_in_path = [&]
1337 {
1338 fs::path base = BuiltInConfig::GetBuildDirectory();
1339 fs::path branch = value;
1340 while (!branch.empty())
1341 {
1342 if (base == branch)
1343 return true;
1344
1345 branch = branch.parent_path();
1346 }
1347
1348 return false;
1349 }();
1350
1351 if (is_in_path)
1352 return;
1353
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());
1357 }
1358 else
1359 {
1360 TC_LOG_INFO("scripts.hotswap", ">> CMAKE_INSTALL_PREFIX is equal to the current path of execution.");
1361 return;
1362 }
1363 }
1364 }
1365
1366 TC_LOG_INFO("scripts.hotswap", "Invoking CMake cache correction...");
1367
1368 auto const error = InvokeCMakeCommand(
1369 "-DCMAKE_INSTALL_PREFIX:PATH=" + fs::current_path().generic_string(),
1371
1372 if (error)
1373 {
1374 TC_LOG_ERROR("scripts.hotswap", ">> Failed to update the CMAKE_INSTALL_PREFIX! "
1375 "This could lead to unexpected behaviour!");
1376 }
1377 else
1378 {
1379 TC_LOG_ERROR("scripts.hotswap", ">> Successfully corrected your CMAKE_INSTALL_PREFIX variable"
1380 "to point at your current path of execution.");
1381 }
1382 }
1383
1384 // File watcher instance and watcher ID's
1385 efsw::FileWatcher _fileWatcher;
1386 efsw::WatchID _libraryWatcher;
1387
1388 // Unique library name counter which is used to
1389 // generate unique names for every shared library version.
1390 uint32 _unique_library_name_counter;
1391
1392 // Queue which is used for thread safe message processing
1394
1395 // Change requests to load or unload shared libraries
1396 std::unordered_set<fs::path /*path*/> _libraries_changed;
1397 // The timestamp which indicates the last time a library was changed
1398 uint32 _last_time_library_changed;
1399
1400 // Contains all running script modules
1401 // The associated shared libraries are unloaded immediately
1402 // on loosing ownership through RAII.
1403 std::unordered_map<std::string /*module name*/,
1404 std::pair<std::shared_ptr<ScriptModule>, std::unique_ptr<SourceUpdateListener>>
1405 > _running_script_modules;
1406 // Container which maps the path of a shared library to it's module name
1407 std::unordered_map<fs::path, std::string /*module name*/> _running_script_module_names;
1408 // Container which maps the module name to it's last known build directive
1409 std::unordered_map<std::string /*module name*/, std::string /*build directive*/> _known_modules_build_directives;
1410
1411 // Modules which were changed and are queued for recompilation
1412 std::unordered_map<std::string /*module*/,
1413 std::unordered_map<fs::path /*path*/, ChangeStateRequest /*state*/>> _sources_changed;
1414 // Tracks the time since the last module has changed to avoid burst updates
1415 uint32 _last_time_sources_changed;
1416
1417 // Tracks the last timestamp the user was informed about a certain repeating event.
1418 uint32 _last_time_user_informed;
1419
1420 // Represents the current build job which is in progress
1421 Optional<BuildJob> _build_job;
1422
1423 // Is true when the build job dispatcher should stop after
1424 // the current job has finished
1425 bool terminate_early;
1426
1427 // The path to the tc_scripts temporary cache
1428 fs::path temporary_cache_path_;
1429};
1430
1431class ScriptModuleDeleteMessage
1432{
1433public:
1434 explicit ScriptModuleDeleteMessage(ScriptModule* module)
1435 : module_(module) { }
1436
1437 void operator() (HotSwapScriptReloadMgr*)
1438 {
1439 module_.reset();
1440 }
1441
1442private:
1443 std::unique_ptr<ScriptModule> module_;
1444};
1445
1446void ScriptModule::ScheduleDelayedDelete(ScriptModule* module)
1447{
1448 sScriptReloadMgr->QueueMessage(ScriptModuleDeleteMessage(module));
1449}
1450
1452static char const* ActionToString(efsw::Action action)
1453{
1454 switch (action)
1455 {
1456 case efsw::Action::Add:
1457 return "added";
1458 case efsw::Action::Delete:
1459 return "deleted";
1460 case efsw::Action::Moved:
1461 return "moved";
1462 default:
1463 return "modified";
1464 }
1465}
1466
1467void LibraryUpdateListener::handleFileAction(efsw::WatchID watchid, std::string const& dir,
1468 std::string const& filename, efsw::Action action, std::string oldFilename)
1469{
1470 // TC_LOG_TRACE("scripts.hotswap", "Library listener detected change on possible module \"{}\ ({})".", filename, ActionToString(action));
1471
1472 // Split moved actions into a delete and an add action
1473 if (action == efsw::Action::Moved)
1474 {
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);
1478 return;
1479 }
1480
1481 sScriptReloadMgr->QueueMessage([=](HotSwapScriptReloadMgr* reloader) mutable
1482 {
1483 auto const path = fs::absolute(
1484 filename,
1485 sScriptReloadMgr->GetLibraryDirectory());
1486
1487 if (!HasValidScriptModuleName(filename))
1488 return;
1489
1490 switch (action)
1491 {
1492 case efsw::Actions::Add:
1493 TC_LOG_TRACE("scripts.hotswap", ">> Loading \"{}\" ({})...",
1494 path.generic_string(), ActionToString(action));
1495 reloader->QueueSharedLibraryChanged(path);
1496 break;
1497 case efsw::Actions::Delete:
1498 TC_LOG_TRACE("scripts.hotswap", ">> Unloading \"{}\" ({})...",
1499 path.generic_string(), ActionToString(action));
1500 reloader->QueueSharedLibraryChanged(path);
1501 break;
1502 case efsw::Actions::Modified:
1503 TC_LOG_TRACE("scripts.hotswap", ">> Reloading \"{}\" ({})...",
1504 path.generic_string(), ActionToString(action));
1505 reloader->QueueSharedLibraryChanged(path);
1506 break;
1507 default:
1508 ABORT();
1509 break;
1510 }
1511 });
1512}
1513
1515static bool HasCXXSourceFileExtension(fs::path const& path)
1516{
1517 static Trinity::regex const regex("^\\.(h|hpp|c|cc|cpp)$");
1518 return Trinity::regex_match(path.extension().generic_string(), regex);
1519}
1520
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))
1524{
1525 if (watcher_id_ >= 0)
1526 {
1527 TC_LOG_TRACE("scripts.hotswap", ">> Attached the source recompiler to \"{}\".",
1528 path_.generic_string());
1529 }
1530 else
1531 {
1532 TC_LOG_ERROR("scripts.hotswap", "Failed to initialize thesource recompiler on \"{}\".",
1533 path_.generic_string());
1534 }
1535}
1536
1537SourceUpdateListener::~SourceUpdateListener()
1538{
1539 if (watcher_id_ >= 0)
1540 {
1541 sScriptReloadMgr->_fileWatcher.removeWatch(watcher_id_);
1542
1543 TC_LOG_TRACE("scripts.hotswap", ">> Detached the source recompiler from \"{}\".",
1544 path_.generic_string());
1545 }
1546}
1547
1548void SourceUpdateListener::handleFileAction(efsw::WatchID watchid, std::string const& dir,
1549 std::string const& filename, efsw::Action action, std::string oldFilename)
1550{
1551 // TC_LOG_TRACE("scripts.hotswap", "Source listener detected change on possible file \"{}/{}\" ({}).", dir, filename, ActionToString(action));
1552
1553 // Skip the file change notification if the recompiler is disabled
1554 if (!sWorld->getBoolConfig(CONFIG_HOTSWAP_RECOMPILER_ENABLED))
1555 return;
1556
1557 // Split moved actions into a delete and an add action
1558 if (action == efsw::Action::Moved)
1559 {
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);
1563 return;
1564 }
1565
1566 fs::path path = fs::absolute(
1567 filename,
1568 dir);
1569
1570 // Check if the file is a C/C++ source file.
1571 if (!path.has_extension() || !HasCXXSourceFileExtension(path))
1572 return;
1573
1575 sScriptReloadMgr->QueueMessage([=, this, path = std::move(path)](HotSwapScriptReloadMgr* reloader)
1576 {
1577 TC_LOG_TRACE("scripts.hotswap", "Detected source change on module \"{}\", "
1578 "queued for recompilation...", script_module_name_);
1579
1580 switch (action)
1581 {
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);
1586 break;
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);
1591 break;
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);
1596 break;
1597 default:
1598 ABORT();
1599 break;
1600 }
1601 });
1602}
1603
1604// Returns the module reference of the given context
1605std::shared_ptr<ModuleReference>
1606 ScriptReloadMgr::AcquireModuleReferenceOfContext(std::string const& context)
1607{
1608 // Return empty references for the static context exported by the worldserver
1609 if (context == ScriptMgr::GetNameOfStaticContext())
1610 return { };
1611
1612 auto const itr = sScriptReloadMgr->_running_script_modules.find(context);
1613 ASSERT(itr != sScriptReloadMgr->_running_script_modules.end()
1614 && "Requested a reference to a non existent script context!");
1615
1616 return itr->second.first;
1617}
1618
1619// Returns the full hot swap implemented ScriptReloadMgr
1621{
1622 static HotSwapScriptReloadMgr instance;
1623 return &instance;
1624}
1625
1626#endif // #ifndef TRINITY_API_USE_DYNAMIC_LINKING
@ IN_MILLISECONDS
Definition: Common.h:35
#define sConfigMgr
Definition: Config.h:61
uint8_t uint8
Definition: Define.h:144
uint32_t uint32
Definition: Define.h:142
#define ABORT
Definition: Errors.h:74
#define WPAbort()
Definition: Errors.h:61
#define ASSERT
Definition: Errors.h:68
@ LOG_LEVEL_TRACE
Definition: LogCommon.h:27
#define TC_LOG_WARN(filterType__,...)
Definition: Log.h:162
#define TC_LOG_TRACE(filterType__,...)
Definition: Log.h:153
#define TC_LOG_ERROR(filterType__,...)
Definition: Log.h:165
#define sLog
Definition: Log.h:130
#define TC_LOG_INFO(filterType__,...)
Definition: Log.h:159
#define TC_LOG_FATAL(filterType__,...)
Definition: Log.h:168
std::conditional_t< IntrusiveLink !=nullptr, Trinity::Impl::MPSCQueueIntrusive< T, IntrusiveLink >, Trinity::Impl::MPSCQueueNonIntrusive< T > > MPSCQueue
Definition: MPSCQueue.h:173
std::optional< T > Optional
Optional helper class to wrap optional values within.
Definition: Optional.h:25
void AddScripts()
#define sScriptMgr
Definition: ScriptMgr.h:1418
#define sScriptReloadMgr
uint32 GetMSTimeDiffToNow(uint32 oldMSTime)
Definition: Timer.h:57
uint32 getMSTime()
Definition: Timer.h:33
void strToLower(std::string &str)
Definition: Util.cpp:482
std::string ByteArrayToHexStr(Container const &c, bool reverse=false)
Definition: Util.h:368
Action
static std::string const & GetNameOfStaticContext()
Returns the context name of the static context provided by the worldserver.
Definition: ScriptMgr.cpp:1310
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)
Definition: CryptoHash.h:49
#define sWorld
Definition: World.h:931
@ CONFIG_HOTSWAP_INSTALL_ENABLED
Definition: World.h:188
@ CONFIG_HOTSWAP_EARLY_TERMINATION_ENABLED
Definition: World.h:186
@ CONFIG_HOTSWAP_ENABLED
Definition: World.h:184
@ CONFIG_HOTSWAP_PREFIX_CORRECTION_ENABLED
Definition: World.h:189
@ CONFIG_HOTSWAP_BUILD_FILE_RECREATION_ENABLED
Definition: World.h:187
@ CONFIG_HOTSWAP_RECOMPILER_ENABLED
Definition: World.h:185
TC_COMMON_API std::string GetSourceDirectory()
TC_COMMON_API std::string GetBuildDirectory()
TC_COMMON_API std::string GetCMakeCommand()
TC_COMMON_API char const * GetBranch()
Definition: GitRevision.cpp:31
TC_COMMON_API char const * GetHash()
Definition: GitRevision.cpp:21
TC_REGEX_NAMESPACE ::regex regex
Definition: Regex.h:28
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.
Definition: StringFormat.h:38
void Update(VignetteData &vignette, WorldObject const *owner)
Definition: Vignette.cpp:90
STL namespace.
int finish(char const *message, int returnValue)