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