diff --git a/.travis.yml b/.travis.yml index b2fcdcacb543a..f2bf4cd20fbdc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,7 @@ install: - mysql -uroot -e 'create database test_mysql;' - mkdir bin - cd bin - - cmake ../ -DWITH_WARNINGS=1 -DWITH_COREDEBUG=0 -DUSE_COREPCH=1 -DUSE_SCRIPTPCH=1 -DTOOLS=1 -DSCRIPTS=1 -DSERVERS=1 -DNOJEM=1 -DWITH_DYNAMIC_LINKING=1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-Werror" -DCMAKE_CXX_FLAGS="-Werror" -DCMAKE_INSTALL_PREFIX=check_install + - cmake ../ -DWITH_WARNINGS=1 -DWITH_COREDEBUG=0 -DUSE_COREPCH=1 -DUSE_SCRIPTPCH=1 -DTOOLS=1 -DSCRIPTS="dynamic" -DSERVERS=1 -DNOJEM=1 -DWITH_DYNAMIC_LINKING=1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-Werror" -DCMAKE_CXX_FLAGS="-Werror" -DCMAKE_INSTALL_PREFIX=check_install - cd .. - sudo chmod +x contrib/check_updates.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 01d785ca855ab..de08fe3181fda 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,7 @@ endif() include(CheckCXXSourceRuns) include(CheckIncludeFiles) +include(ConfigureScripts) # set default buildoptions and print them include(cmake/options.cmake) diff --git a/cmake/compiler/clang/settings.cmake b/cmake/compiler/clang/settings.cmake index 9a8cb85275e21..1cd95bbf7039e 100644 --- a/cmake/compiler/clang/settings.cmake +++ b/cmake/compiler/clang/settings.cmake @@ -19,7 +19,7 @@ endif() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wno-narrowing -Wno-deprecated-register") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG=1") -if (WITH_DYNAMIC_LINKING) +if (BUILD_SHARED_LIBS) # -fPIC is needed to allow static linking in shared libs. # -fvisibility=hidden sets the default visibility to hidden to prevent exporting of all symbols. set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC -fvisibility=hidden") diff --git a/cmake/compiler/gcc/settings.cmake b/cmake/compiler/gcc/settings.cmake index d9eda767b8e30..c4f97f4ffd4f5 100644 --- a/cmake/compiler/gcc/settings.cmake +++ b/cmake/compiler/gcc/settings.cmake @@ -35,7 +35,7 @@ if( WITH_COREDEBUG ) message(STATUS "GCC: Debug-flags set (-g3)") endif() -if (WITH_DYNAMIC_LINKING) +if (BUILD_SHARED_LIBS) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC -fvisibility=hidden -Wno-attributes") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -fvisibility=hidden -Wno-attributes") diff --git a/cmake/compiler/msvc/settings.cmake b/cmake/compiler/msvc/settings.cmake index ee8a1826ec500..bd5ea65da478d 100644 --- a/cmake/compiler/msvc/settings.cmake +++ b/cmake/compiler/msvc/settings.cmake @@ -42,7 +42,7 @@ endif() # multithreaded compiling on VS set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") -if((PLATFORM EQUAL 64) OR (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19.0.23026.0) OR WITH_DYNAMIC_LINKING) +if((PLATFORM EQUAL 64) OR (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19.0.23026.0) OR BUILD_SHARED_LIBS) # Enable extended object support set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj") message(STATUS "MSVC: Enabled increased number of sections in object files") @@ -81,7 +81,7 @@ if(NOT WITH_WARNINGS) message(STATUS "MSVC: Disabled generic compiletime warnings") endif() -if (WITH_DYNAMIC_LINKING) +if (BUILD_SHARED_LIBS) # C4251: needs to have dll-interface to be used by clients of class '...' # C4275: non dll-interface class ...' used as base for dll-interface class '...' set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4251 /wd4275") diff --git a/cmake/macros/ConfigureScripts.cmake b/cmake/macros/ConfigureScripts.cmake new file mode 100644 index 0000000000000..c783cc98ab772 --- /dev/null +++ b/cmake/macros/ConfigureScripts.cmake @@ -0,0 +1,83 @@ +# Copyright (C) 2008-2016 TrinityCore +# +# This file is free software; as a special exception the author gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# Stores the project name of the given module in the variable +function(GetProjectNameOfScriptModule module variable) + string(TOLOWER "scripts_${SCRIPT_MODULE}" GENERATED_NAME) + set(${variable} "${GENERATED_NAME}" PARENT_SCOPE) +endfunction() + +# Creates a list of all script modules +# and stores it in the given variable. +function(GetScriptModuleList variable) + GetPathToScriptModule("" BASE_PATH) + file(GLOB LOCALE_SCRIPT_MODULE_LIST RELATIVE + ${BASE_PATH} + ${BASE_PATH}/*) + + set(${variable}) + foreach(SCRIPT_MODULE ${LOCALE_SCRIPT_MODULE_LIST}) + GetPathToScriptModule(${SCRIPT_MODULE} SCRIPT_MODULE_PATH) + if (IS_DIRECTORY ${SCRIPT_MODULE_PATH}) + list(APPEND ${variable} ${SCRIPT_MODULE}) + endif() + endforeach() + set(${variable} ${${variable}} PARENT_SCOPE) +endfunction() + +# Converts the given script module name into it's +# variable name which holds the linkage type. +function(ScriptModuleNameToVariable module variable) + string(TOUPPER ${module} ${variable}) + set(${variable} "SCRIPTS_${${variable}}") + set(${variable} ${${variable}} PARENT_SCOPE) +endfunction() + +# Stores in the given variable whether dynamic linking is required +function(IsDynamicLinkingRequired variable) + if(SCRIPTS MATCHES "dynamic") + set(IS_DEFAULT_VALUE_DYNAMIC ON) + endif() + + GetScriptModuleList(SCRIPT_MODULE_LIST) + set(IS_REQUIRED OFF) + foreach(SCRIPT_MODULE ${SCRIPT_MODULE_LIST}) + ScriptModuleNameToVariable(${SCRIPT_MODULE} SCRIPT_MODULE_VARIABLE) + if ((${SCRIPT_MODULE_VARIABLE} STREQUAL "dynamic") OR + (${SCRIPT_MODULE_VARIABLE} STREQUAL "default" AND IS_DEFAULT_VALUE_DYNAMIC)) + set(IS_REQUIRED ON) + break() + endif() + endforeach() + set(${variable} ${IS_REQUIRED} PARENT_SCOPE) +endfunction() + +# Stores the absolut path of the given module in the variable +function(GetPathToScriptModule module variable) + set(${variable} "${CMAKE_SOURCE_DIR}/src/server/scripts/${module}" PARENT_SCOPE) +endfunction() + +# Stores the native variable name +function(GetNativeSharedLibraryName module variable) + if(WIN32) + set(${variable} "${module}.dll" PARENT_SCOPE) + else() + set(${variable} "lib${module}.so" PARENT_SCOPE) + endif() +endfunction() + +# Stores the native install path in the variable +function(GetInstallOffset variable) + if(WIN32) + set(${variable} "${CMAKE_INSTALL_PREFIX}/scripts" PARENT_SCOPE) + else() + set(${variable} "${CMAKE_INSTALL_PREFIX}/bin/scripts" PARENT_SCOPE) + endif() +endfunction() diff --git a/cmake/options.cmake b/cmake/options.cmake index 1a0289590474f..93e51141207c8 100644 --- a/cmake/options.cmake +++ b/cmake/options.cmake @@ -9,11 +9,30 @@ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. option(SERVERS "Build worldserver and bnetserver" 1) -option(SCRIPTS "Build core with scripts included" 1) +set(SCRIPTS "static" CACHE STRING "Build core with scripts") +set_property(CACHE SCRIPTS PROPERTY STRINGS none static dynamic minimal-static minimal-dynamic) + +# Build a list of all script modules when -DSCRIPT="custom" is selected +GetScriptModuleList(SCRIPT_MODULE_LIST) +foreach(SCRIPT_MODULE ${SCRIPT_MODULE_LIST}) + ScriptModuleNameToVariable(${SCRIPT_MODULE} SCRIPT_MODULE_VARIABLE) + set(${SCRIPT_MODULE_VARIABLE} "default" CACHE STRING "Build type of the ${SCRIPT_MODULE} module.") + set_property(CACHE ${SCRIPT_MODULE_VARIABLE} PROPERTY STRINGS default disabled static dynamic) +endforeach() + option(TOOLS "Build map/vmap/mmap extraction/assembler tools" 0) option(USE_SCRIPTPCH "Use precompiled headers when compiling scripts" 1) option(USE_COREPCH "Use precompiled headers when compiling servers" 1) option(WITH_DYNAMIC_LINKING "Enable dynamic library linking." 0) +IsDynamicLinkingRequired(WITH_DYNAMIC_LINKING_FORCED) +if (WITH_DYNAMIC_LINKING AND WITH_DYNAMIC_LINKING_FORCED) + set(WITH_DYNAMIC_LINKING_FORCED OFF) +endif() +if (WITH_DYNAMIC_LINKING OR WITH_DYNAMIC_LINKING_FORCED) + set(BUILD_SHARED_LIBS ON) +else() + set(BUILD_SHARED_LIBS OFF) +endif() option(WITH_WARNINGS "Show all warnings during compile" 0) option(WITH_COREDEBUG "Include additional debug-code in core" 0) set(WITH_SOURCE_TREE "hierarchical" CACHE STRING "Build the source tree for IDE's.") diff --git a/cmake/showoptions.cmake b/cmake/showoptions.cmake index b485cfe5ce66a..27ffe0578ef78 100644 --- a/cmake/showoptions.cmake +++ b/cmake/showoptions.cmake @@ -23,9 +23,8 @@ else() message("* Build world/bnetserver : No") endif() -if( SCRIPTS ) - message("* Build with scripts : Yes (default)") - add_definitions(-DSCRIPTS) +if(SCRIPTS AND (NOT SCRIPTS STREQUAL "none")) + message("* Build with scripts : Yes (${SCRIPTS})") else() message("* Build with scripts : No") endif() @@ -70,7 +69,7 @@ else() endif() if( NOT WITH_SOURCE_TREE STREQUAL "no" ) - message("* Show source tree : Yes - \"${WITH_SOURCE_TREE}\"") + message("* Show source tree : Yes (${WITH_SOURCE_TREE})") else() message("* Show source tree : No") endif() @@ -87,7 +86,7 @@ if ( WITHOUT_GIT ) message(" *** version of git for the revision-hash to work, and be allowede to ask for") message(" *** support if needed.") else() - message("* Use GIT revision hash : Yes") + message("* Use GIT revision hash : Yes (default)") endif() if ( NOJEM ) @@ -113,15 +112,16 @@ if ( HELGRIND ) add_definitions(-DHELGRIND) endif() -if (WITH_DYNAMIC_LINKING) +if (BUILD_SHARED_LIBS) message("") message(" *** WITH_DYNAMIC_LINKING - INFO!") message(" *** Will link against shared libraries!") message(" *** Please note that this is an experimental feature!") + if (WITH_DYNAMIC_LINKING_FORCED) + message("") + message(" *** Dynamic linking was enforced through a dynamic script module!") + endif() add_definitions(-DTRINITY_API_USE_DYNAMIC_LINKING) - set(BUILD_SHARED_LIBS ON) -else() - set(BUILD_SHARED_LIBS OFF) endif() message("") diff --git a/dep/CMakeLists.txt b/dep/CMakeLists.txt index 2ba94b593037f..86fdd8f38ad90 100644 --- a/dep/CMakeLists.txt +++ b/dep/CMakeLists.txt @@ -20,6 +20,7 @@ add_subdirectory(threads) if(SERVERS OR TOOLS) add_subdirectory(boost) + add_subdirectory(process) add_subdirectory(zlib) add_subdirectory(g3dlite) add_subdirectory(recastnavigation) @@ -33,7 +34,6 @@ endif() if(SERVERS) add_subdirectory(mysql) - add_subdirectory(process) add_subdirectory(readline) add_subdirectory(gsoap) add_subdirectory(rapidjson) diff --git a/dep/boost/CMakeLists.txt b/dep/boost/CMakeLists.txt index 118635c85bdca..6cda5fbec4ead 100644 --- a/dep/boost/CMakeLists.txt +++ b/dep/boost/CMakeLists.txt @@ -35,19 +35,14 @@ include (CheckCXXSourceCompiles) set(CMAKE_REQUIRED_INCLUDES ${Boost_INCLUDE_DIR}) set(CMAKE_REQUIRED_LIBRARIES ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_IOSTREAMS_LIBRARY}) set(CMAKE_REQUIRED_FLAGS "-std=c++11") -unset(boost_filesystem_copy_links_without_NO_SCOPED_ENUM CACHE) check_cxx_source_compiles(" #include #include int main() { boost::filesystem::copy_file(boost::filesystem::path(), boost::filesystem::path()); }" boost_filesystem_copy_links_without_NO_SCOPED_ENUM) -unset(CMAKE_REQUIRED_INCLUDES CACHE) -unset(CMAKE_REQUIRED_LIBRARIES CACHE) -unset(CMAKE_REQUIRED_FLAGS CACHE) - -if (NOT boost_filesystem_copy_links_without_NO_SCOPED_ENUM) - set(OPTIONAL_BOOST_NO_SCOPED_ENUMS -DBOOST_NO_CXX11_SCOPED_ENUMS) -endif() +unset(CMAKE_REQUIRED_INCLUDES) +unset(CMAKE_REQUIRED_LIBRARIES) +unset(CMAKE_REQUIRED_FLAGS) add_library(boost INTERFACE) @@ -59,9 +54,17 @@ target_include_directories(boost INTERFACE ${Boost_INCLUDE_DIRS}) -target_compile_definitions(boost - INTERFACE - -DBOOST_DATE_TIME_NO_LIB - -DBOOST_REGEX_NO_LIB - -DBOOST_CHRONO_NO_LIB - ${OPTIONAL_BOOST_NO_SCOPED_ENUMS}) +if (boost_filesystem_copy_links_without_NO_SCOPED_ENUM) + target_compile_definitions(boost + INTERFACE + -DBOOST_DATE_TIME_NO_LIB + -DBOOST_REGEX_NO_LIB + -DBOOST_CHRONO_NO_LIB) +else() + target_compile_definitions(boost + INTERFACE + -DBOOST_DATE_TIME_NO_LIB + -DBOOST_REGEX_NO_LIB + -DBOOST_CHRONO_NO_LIB + -DBOOST_NO_CXX11_SCOPED_ENUMS) +endif() diff --git a/dep/efsw/CMakeLists.txt b/dep/efsw/CMakeLists.txt index 81a1d20b204e1..eecc553194470 100644 --- a/dep/efsw/CMakeLists.txt +++ b/dep/efsw/CMakeLists.txt @@ -1,4 +1,4 @@ -if (WITH_DYNAMIC_LINKING) +if (BUILD_SHARED_LIBS) set(SRCS src/efsw/DirectorySnapshot.cpp src/efsw/DirectorySnapshotDiff.cpp @@ -81,5 +81,5 @@ if (WITH_DYNAMIC_LINKING) FOLDER "dep") else() - add_library(efsw INTERFACE) + add_library(efsw INTERFACE IMPORTED GLOBAL) endif() diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 436620e29005a..0428738f2dd4b 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -71,7 +71,9 @@ target_link_libraries(common openssl valgrind threads - jemalloc) + jemalloc + PRIVATE + process) add_dependencies(common revision_data.h) diff --git a/src/common/Utilities/StartProcess.cpp b/src/common/Utilities/StartProcess.cpp new file mode 100644 index 0000000000000..c47c02bbe873e --- /dev/null +++ b/src/common/Utilities/StartProcess.cpp @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2008-2016 TrinityCore + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "StartProcess.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "Common.h" +#include "Log.h" + +using namespace boost::process; +using namespace boost::process::initializers; +using namespace boost::iostreams; + +namespace Trinity { + +template +class TCLogSink +{ + T callback_; + +public: + typedef char char_type; + typedef sink_tag category; + + // Requires a callback type which has a void(std::string) signature + TCLogSink(T callback) + : callback_(std::move(callback)) { } + + std::streamsize write(const char* str, std::streamsize size) + { + callback_(std::string(str, size)); + return size; + } +}; + +template +auto MakeTCLogSink(T&& callback) + -> TCLogSink::type> +{ + return { std::forward(callback) }; +} + +template +static int CreateChildProcess(T waiter, std::string const& executable, + std::vector const& args, + std::string const& logger, std::string const& input, + bool secure) +{ + auto outPipe = create_pipe(); + auto errPipe = create_pipe(); + + Optional inputSource; + + if (!secure) + { + TC_LOG_TRACE(logger.c_str(), "Starting process \"%s\" with arguments: \"%s\".", + executable.c_str(), boost::algorithm::join(args, " ").c_str()); + } + + // Start the child process + child c = [&] + { + if (!input.empty()) + { + inputSource = file_descriptor_source(input); + + // With binding stdin + return execute(run_exe(boost::filesystem::absolute(executable)), + set_args(args), + bind_stdin(*inputSource), + bind_stdout(file_descriptor_sink(outPipe.sink, close_handle)), + bind_stderr(file_descriptor_sink(errPipe.sink, close_handle))); + } + else + { + // Without binding stdin + return execute(run_exe(boost::filesystem::absolute(executable)), + set_args(args), + bind_stdout(file_descriptor_sink(outPipe.sink, close_handle)), + bind_stderr(file_descriptor_sink(errPipe.sink, close_handle))); + } + }(); + + file_descriptor_source outFd(outPipe.source, close_handle); + file_descriptor_source errFd(errPipe.source, close_handle); + + auto outInfo = MakeTCLogSink([&](std::string msg) + { + TC_LOG_INFO(logger.c_str(), "%s", msg.c_str()); + }); + + auto outError = MakeTCLogSink([&](std::string msg) + { + TC_LOG_ERROR(logger.c_str(), "%s", msg.c_str()); + }); + + copy(outFd, outInfo); + copy(errFd, outError); + + // Call the waiter in the current scope to prevent + // the streams from closing too early on leaving the scope. + int const result = waiter(c); + + if (!secure) + { + TC_LOG_TRACE(logger.c_str(), ">> Process \"%s\" finished with return value %i.", + executable.c_str(), result); + } + + if (inputSource) + inputSource->close(); + + return result; +} + +int StartProcess(std::string const& executable, std::vector const& args, + std::string const& logger, std::string input_file, bool secure) +{ + return CreateChildProcess([](child& c) -> int + { + try + { + return wait_for_exit(c); + } + catch (...) + { + return EXIT_FAILURE; + } + }, executable, args, logger, input_file, secure); +} + +class AsyncProcessResultImplementation + : public AsyncProcessResult +{ + std::string const executable; + std::vector const args; + std::string const logger; + std::string const input_file; + bool const is_secure; + + std::atomic was_terminated; + + // Workaround for missing move support in boost < 1.57 + Optional>> result; + Optional> my_child; + +public: + explicit AsyncProcessResultImplementation(std::string executable_, std::vector args_, + std::string logger_, std::string input_file_, + bool secure) + : executable(std::move(executable_)), args(std::move(args_)), + logger(std::move(logger_)), input_file(input_file_), + is_secure(secure), was_terminated(false) { } + + AsyncProcessResultImplementation(AsyncProcessResultImplementation const&) = delete; + AsyncProcessResultImplementation& operator= (AsyncProcessResultImplementation const&) = delete; + AsyncProcessResultImplementation(AsyncProcessResultImplementation&&) = delete; + AsyncProcessResultImplementation& operator= (AsyncProcessResultImplementation&&) = delete; + + int StartProcess() + { + ASSERT(!my_child, "Process started already!"); + + return CreateChildProcess([&](child& c) -> int + { + int result; + my_child = std::reference_wrapper(c); + + try + { + result = wait_for_exit(c); + } + catch (...) + { + result = EXIT_FAILURE; + } + + my_child.reset(); + return was_terminated ? EXIT_FAILURE : result; + + }, executable, args, logger, input_file, is_secure); + } + + void SetFuture(std::future result_) + { + result = std::make_shared>(std::move(result_)); + } + + /// Returns the future which contains the result of the process + /// as soon it is finished. + std::future& GetFutureResult() override + { + ASSERT(*result, "The process wasn't started!"); + return **result; + } + + /// Tries to terminate the process + void Terminate() override + { + if (!my_child) + { + was_terminated = true; + try + { + terminate(my_child->get()); + } + catch(...) + { + // Do nothing + } + } + } +}; + +TC_COMMON_API std::shared_ptr + StartAsyncProcess(std::string executable, std::vector args, + std::string logger, std::string input_file, bool secure) +{ + auto handle = std::make_shared( + std::move(executable), std::move(args), std::move(logger), std::move(input_file), secure); + + handle->SetFuture(std::async(std::launch::async, [handle] { return handle->StartProcess(); })); + return handle; +} + +Optional SearchExecutableInPath(std::string const& filename) +{ + try + { + auto result = search_path(filename); + if (result.empty()) + return boost::none; + else + return result; + } + catch (...) + { + return boost::none; + } +} + +} // namespace Trinity diff --git a/src/common/Utilities/StartProcess.h b/src/common/Utilities/StartProcess.h new file mode 100644 index 0000000000000..3b380bd4f4e1b --- /dev/null +++ b/src/common/Utilities/StartProcess.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2008-2016 TrinityCore + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef Process_h__ +#define Process_h__ + +#include +#include +#include "Common.h" + +namespace Trinity { + +/// Starts a process with the given arguments and parameters and will block +/// until the process is finished. +/// When an input path is given, the file will be routed to the processes stdin. +/// When the process is marked as secure no arguments are leaked to logs. +/// Note that most executables expect it's name as the first argument. +TC_COMMON_API int StartProcess(std::string const& executable, std::vector const& args, + std::string const& logger, std::string input_file = "", + bool secure = false); + +/// Platform and library independent representation +/// of asynchronous process results +class AsyncProcessResult +{ +public: + virtual ~AsyncProcessResult() { } + + /// Returns the future which contains the result of the process + /// as soon it is finished. + virtual std::future& GetFutureResult() = 0; + + /// Tries to terminate the process + virtual void Terminate() = 0; +}; + +/// Starts a process asynchronously with the given arguments and parameters and +/// returns an AsyncProcessResult immediately which is set, when the process exits. +/// When an input path is given, the file will be routed to the processes stdin. +/// When the process is marked as secure no arguments are leaked to logs. +/// Note that most executables expect it's name as the first argument. +TC_COMMON_API std::shared_ptr + StartAsyncProcess(std::string executable, std::vector args, + std::string logger, std::string input_file = "", + bool secure = false); + +/// Searches for the given executable in the PATH variable +/// and returns a present optional when it was found. +TC_COMMON_API Optional SearchExecutableInPath(std::string const& filename); + +} // namespace Trinity + +#endif // Process_h__ diff --git a/src/server/database/CMakeLists.txt b/src/server/database/CMakeLists.txt index 236586075ee94..bd2fa280ad620 100644 --- a/src/server/database/CMakeLists.txt +++ b/src/server/database/CMakeLists.txt @@ -52,7 +52,6 @@ add_definitions(-DTRINITY_API_EXPORT_DATABASE) target_link_libraries(database PUBLIC common - process mysql) set_target_properties(database diff --git a/src/server/database/Updater/DBUpdater.cpp b/src/server/database/Updater/DBUpdater.cpp index ef4a0d407cb81..b13920cbeebfe 100644 --- a/src/server/database/Updater/DBUpdater.cpp +++ b/src/server/database/Updater/DBUpdater.cpp @@ -22,19 +22,11 @@ #include "DatabaseLoader.h" #include "Config.h" #include "BuiltInConfig.h" +#include "StartProcess.h" #include #include #include -#include -#include -#include -#include -#include - -using namespace boost::process; -using namespace boost::process::initializers; -using namespace boost::iostreams; std::string DBUpdaterUtil::GetCorrectedMySQLExecutable() { @@ -51,19 +43,16 @@ bool DBUpdaterUtil::CheckExecutable() { exe.clear(); - try - { - exe = search_path("mysql"); - } - catch (std::runtime_error&) + if (auto path = Trinity::SearchExecutableInPath("mysql")) { - } + exe = std::move(*path); - if (!exe.empty() && exists(exe)) - { - // Correct the path to the cli - corrected_path() = absolute(exe).generic_string(); - return true; + if (!exe.empty() && exists(exe)) + { + // Correct the path to the cli + corrected_path() = absolute(exe).generic_string(); + return true; + } } TC_LOG_FATAL("sql.updates", "Didn't find executeable mysql binary at \'%s\' or in path, correct the path in the *.conf (\"Updates.MySqlCLIPath\").", @@ -419,44 +408,9 @@ void DBUpdater::ApplyFile(DatabaseWorkerPool& pool, std::string const& hos if (!database.empty()) args.push_back(database); - // ToDo: use the existing query in memory as virtual file if possible - file_descriptor_source source(path); - - uint32 ret; - try - { - boost::process::pipe outPipe = create_pipe(); - boost::process::pipe errPipe = create_pipe(); - - child c = execute(run_exe( - boost::filesystem::absolute(DBUpdaterUtil::GetCorrectedMySQLExecutable()).generic_string()), - set_args(args), bind_stdin(source), throw_on_error(), - bind_stdout(file_descriptor_sink(outPipe.sink, close_handle)), - bind_stderr(file_descriptor_sink(errPipe.sink, close_handle))); - - file_descriptor_source mysqlOutfd(outPipe.source, close_handle); - file_descriptor_source mysqlErrfd(errPipe.source, close_handle); - - stream mysqlOutStream(mysqlOutfd); - stream mysqlErrStream(mysqlErrfd); - - std::stringstream out; - std::stringstream err; - - copy(mysqlOutStream, out); - copy(mysqlErrStream, err); - - TC_LOG_INFO("sql.updates", "%s", out.str().c_str()); - TC_LOG_ERROR("sql.updates", "%s", err.str().c_str()); - - ret = wait_for_exit(c); - } - catch (boost::system::system_error&) - { - ret = EXIT_FAILURE; - } - - source.close(); + // Invokes a mysql process which doesn't leak credentials to logs + int const ret = Trinity::StartProcess(DBUpdaterUtil::GetCorrectedMySQLExecutable(), args, + "sql.updates", path.generic_string(), true); if (ret != EXIT_SUCCESS) { diff --git a/src/server/game/Chat/Chat.cpp b/src/server/game/Chat/Chat.cpp index 2e4b7a388b2d4..15743a0e686e8 100644 --- a/src/server/game/Chat/Chat.cpp +++ b/src/server/game/Chat/Chat.cpp @@ -33,18 +33,19 @@ #include "ChatLink.h" #include "Group.h" -bool ChatHandler::load_command_table = true; +// Lazy loading of the command table cache from commands and the +// ScriptMgr should be thread safe since the player commands, +// cli commands and ScriptMgr updates are all dispatched one after +// one inside the world update loop. +static Optional> commandTableCache; std::vector const& ChatHandler::getCommandTable() { - static std::vector commandTableCache; - - if (LoadCommandTable()) + if (!commandTableCache) { - SetLoadCommandTable(false); - - std::vector cmds = sScriptMgr->GetChatCommands(); - commandTableCache.swap(cmds); + // We need to initialize this at top since SetDataForCommandInTable + // calls getCommandTable() recursively. + commandTableCache = sScriptMgr->GetChatCommands(); PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_COMMANDS); PreparedQueryResult result = WorldDatabase.Query(stmt); @@ -55,13 +56,18 @@ std::vector const& ChatHandler::getCommandTable() Field* fields = result->Fetch(); std::string name = fields[0].GetString(); - SetDataForCommandInTable(commandTableCache, name.c_str(), fields[1].GetUInt16(), fields[2].GetString(), name); + SetDataForCommandInTable(*commandTableCache, name.c_str(), fields[1].GetUInt16(), fields[2].GetString(), name); } while (result->NextRow()); } } - return commandTableCache; + return *commandTableCache; +} + +void ChatHandler::invalidateCommandTable() +{ + commandTableCache.reset(); } char const* ChatHandler::GetTrinityString(uint32 entry) const diff --git a/src/server/game/Chat/Chat.h b/src/server/game/Chat/Chat.h index 226ec1fe6ad1d..04d040d7508e3 100644 --- a/src/server/game/Chat/Chat.h +++ b/src/server/game/Chat/Chat.h @@ -89,6 +89,7 @@ class TC_GAME_API ChatHandler bool ParseCommands(const char* text); static std::vector const& getCommandTable(); + static void invalidateCommandTable(); bool isValidChatMessage(const char* msg); void SendGlobalSysMessage(const char *str); @@ -136,8 +137,6 @@ class TC_GAME_API ChatHandler GameObject* GetObjectGlobalyWithGuidOrNearWithDbGuid(ObjectGuid::LowType lowguid, uint32 entry); bool HasSentErrorMessage() const { return sentErrorMessage; } void SetSentErrorMessage(bool val){ sentErrorMessage = val; } - static bool LoadCommandTable() { return load_command_table; } - static void SetLoadCommandTable(bool val) { load_command_table = val; } bool ShowHelpForCommand(std::vector const& table, const char* cmd); protected: @@ -150,7 +149,6 @@ class TC_GAME_API ChatHandler WorldSession* m_session; // != NULL for chat command call and NULL for CLI command // common global flag - static bool load_command_table; bool sentErrorMessage; }; diff --git a/src/server/game/DungeonFinding/LFGMgr.cpp b/src/server/game/DungeonFinding/LFGMgr.cpp index e328732c0c5a8..33b252008c93d 100644 --- a/src/server/game/DungeonFinding/LFGMgr.cpp +++ b/src/server/game/DungeonFinding/LFGMgr.cpp @@ -40,8 +40,6 @@ namespace lfg LFGMgr::LFGMgr(): m_QueueTimer(0), m_lfgProposalId(1), m_options(sWorld->getIntConfig(CONFIG_LFG_OPTIONSMASK)) { - new LFGPlayerScript(); - new LFGGroupScript(); } LFGMgr::~LFGMgr() diff --git a/src/server/game/DungeonFinding/LFGScripts.cpp b/src/server/game/DungeonFinding/LFGScripts.cpp index 6e34af3f19b67..da1a34f2da842 100644 --- a/src/server/game/DungeonFinding/LFGScripts.cpp +++ b/src/server/game/DungeonFinding/LFGScripts.cpp @@ -241,4 +241,10 @@ void LFGGroupScript::OnInviteMember(Group* group, ObjectGuid guid) sLFGMgr->LeaveLfg(leader); } +void AddSC_LFGScripts() +{ + new LFGPlayerScript(); + new LFGGroupScript(); +} + } // namespace lfg diff --git a/src/server/game/DungeonFinding/LFGScripts.h b/src/server/game/DungeonFinding/LFGScripts.h index ec64604a28250..9f52668ea616f 100644 --- a/src/server/game/DungeonFinding/LFGScripts.h +++ b/src/server/game/DungeonFinding/LFGScripts.h @@ -53,4 +53,6 @@ class TC_GAME_API LFGGroupScript : public GroupScript void OnInviteMember(Group* group, ObjectGuid guid) override; }; +/*keep private*/ void AddSC_LFGScripts(); + } // namespace lfg diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 45dd6aeb4cdf6..b8c5d5b3340fe 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -792,6 +792,24 @@ void Creature::DoFleeToGetAssistance() } } +bool Creature::AIM_Destroy() +{ + if (m_AI_locked) + { + TC_LOG_DEBUG("scripts", "AIM_Destroy: failed to destroy, locked."); + return false; + } + + ASSERT(!i_disabledAI, + "The disabled AI wasn't cleared!"); + + delete i_AI; + i_AI = nullptr; + + IsAIEnabled = false; + return true; +} + bool Creature::AIM_Initialize(CreatureAI* ai) { // make sure nothing can change the AI during AI update @@ -801,12 +819,12 @@ bool Creature::AIM_Initialize(CreatureAI* ai) return false; } - UnitAI* oldAI = i_AI; + AIM_Destroy(); Motion_Initialize(); i_AI = ai ? ai : FactorySelector::selectAI(this); - delete oldAI; + IsAIEnabled = true; i_AI->InitializeAI(); // Initialize vehicle diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 63a6c3b9564ab..6e723a29191a8 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -519,6 +519,7 @@ class TC_GAME_API Creature : public Unit, public GridObject, public Ma bool IsInEvadeMode() const { return HasUnitState(UNIT_STATE_EVADE); } + bool AIM_Destroy(); bool AIM_Initialize(CreatureAI* ai = NULL); void Motion_Initialize(); diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp index 671aa0af0450f..06851b938ffbf 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -74,9 +74,15 @@ GameObject::~GameObject() // CleanupsBeforeDelete(); } -bool GameObject::AIM_Initialize() +void GameObject::AIM_Destroy() { delete m_AI; + m_AI = nullptr; +} + +bool GameObject::AIM_Initialize() +{ + AIM_Destroy(); m_AI = FactorySelector::SelectGameObjectAI(this); diff --git a/src/server/game/Entities/GameObject/GameObject.h b/src/server/game/Entities/GameObject/GameObject.h index 47d06e1492bb6..ada2680050534 100644 --- a/src/server/game/Entities/GameObject/GameObject.h +++ b/src/server/game/Entities/GameObject/GameObject.h @@ -1086,8 +1086,10 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject uint16 GetAIAnimKitId() const override { return _animKitId; } void SetAnimKitId(uint16 animKitId, bool oneshot); - protected: + void AIM_Destroy(); bool AIM_Initialize(); + + protected: GameObjectModel* CreateModel(); void UpdateModel(); // updates model in case displayId were changed uint32 m_spellId; diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index a4ebb1d565c7b..2c3f054531868 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -4587,7 +4587,7 @@ void ObjectMgr::LoadScripts(ScriptsType type) if (tableName.empty()) return; - if (sScriptMgr->IsScriptScheduled()) // function cannot be called when scripts are in use. + if (sMapMgr->IsScriptScheduled()) // function cannot be called when scripts are in use. return; TC_LOG_INFO("server.loading", "Loading %s...", tableName.c_str()); @@ -5044,7 +5044,7 @@ void ObjectMgr::LoadSpellScriptNames() } while (spellInfo) { - _spellScriptsStore.insert(SpellScriptsContainer::value_type(spellInfo->Id, GetScriptId(scriptName))); + _spellScriptsStore.insert(SpellScriptsContainer::value_type(spellInfo->Id, std::make_pair(GetScriptId(scriptName), true))); spellInfo = spellInfo->GetNextRankSpell(); } } @@ -5053,7 +5053,7 @@ void ObjectMgr::LoadSpellScriptNames() if (spellInfo->IsRanked()) TC_LOG_ERROR("sql.sql", "Scriptname: `%s` spell (Id: %d) is ranked spell. Perhaps not all ranks are assigned to this script.", scriptName.c_str(), spellId); - _spellScriptsStore.insert(SpellScriptsContainer::value_type(spellInfo->Id, GetScriptId(scriptName))); + _spellScriptsStore.insert(SpellScriptsContainer::value_type(spellInfo->Id, std::make_pair(GetScriptId(scriptName), true))); } ++count; @@ -5075,45 +5075,48 @@ void ObjectMgr::ValidateSpellScripts() uint32 count = 0; - for (SpellScriptsContainer::iterator itr = _spellScriptsStore.begin(); itr != _spellScriptsStore.end();) + for (auto spell : _spellScriptsStore) { - SpellInfo const* spellEntry = sSpellMgr->GetSpellInfo(itr->first); - std::vector > SpellScriptLoaders; - sScriptMgr->CreateSpellScriptLoaders(itr->first, SpellScriptLoaders); - itr = _spellScriptsStore.upper_bound(itr->first); + SpellInfo const* spellEntry = sSpellMgr->GetSpellInfo(spell.first); - for (std::vector >::iterator sitr = SpellScriptLoaders.begin(); sitr != SpellScriptLoaders.end(); ++sitr) + auto const bounds = sObjectMgr->GetSpellScriptsBounds(spell.first); + + for (auto itr = bounds.first; itr != bounds.second; ++itr) { - SpellScript* spellScript = sitr->first->GetSpellScript(); - AuraScript* auraScript = sitr->first->GetAuraScript(); - bool valid = true; - if (!spellScript && !auraScript) - { - TC_LOG_ERROR("scripts", "Functions GetSpellScript() and GetAuraScript() of script `%s` do not return objects - script skipped", GetScriptName(sitr->second->second).c_str()); - valid = false; - } - if (spellScript) - { - spellScript->_Init(&sitr->first->GetName(), spellEntry->Id); - spellScript->_Register(); - if (!spellScript->_Validate(spellEntry)) - valid = false; - delete spellScript; - } - if (auraScript) - { - auraScript->_Init(&sitr->first->GetName(), spellEntry->Id); - auraScript->_Register(); - if (!auraScript->_Validate(spellEntry)) - valid = false; - delete auraScript; - } - if (!valid) + if (SpellScriptLoader* spellScriptLoader = sScriptMgr->GetSpellScriptLoader(itr->second.first)) { - _spellScriptsStore.erase(sitr->second); + ++count; + + std::unique_ptr spellScript(spellScriptLoader->GetSpellScript()); + std::unique_ptr auraScript(spellScriptLoader->GetAuraScript()); + + if (!spellScript && !auraScript) + { + TC_LOG_ERROR("scripts", "Functions GetSpellScript() and GetAuraScript() of script `%s` do not return objects - script skipped", GetScriptName(itr->second.first).c_str()); + + itr->second.second = false; + continue; + } + + if (spellScript) + { + spellScript->_Init(&spellScriptLoader->GetName(), spellEntry->Id); + spellScript->_Register(); + + if (!spellScript->_Validate(spellEntry)) + itr->second.second = false; + } + + if (auraScript) + { + auraScript->_Init(&spellScriptLoader->GetName(), spellEntry->Id); + auraScript->_Register(); + + if (!auraScript->_Validate(spellEntry)) + itr->second.second = false; + } } } - ++count; } TC_LOG_INFO("server.loading", ">> Validated %u scripts in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); @@ -8569,6 +8572,8 @@ void ObjectMgr::LoadScriptNames() { uint32 oldMSTime = getMSTime(); + // We insert an empty placeholder here so we can use the + // script id 0 as dummy for "no script found". _scriptNamesStore.emplace_back(""); QueryResult result = WorldDatabase.Query( @@ -8610,18 +8615,18 @@ void ObjectMgr::LoadScriptNames() std::sort(_scriptNamesStore.begin(), _scriptNamesStore.end()); -#ifdef SCRIPTS - for (size_t i = 1; i < _scriptNamesStore.size(); ++i) - UnusedScriptNames.push_back(_scriptNamesStore[i]); -#endif - TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " ScriptNames in %u ms", _scriptNamesStore.size(), GetMSTimeDiffToNow(oldMSTime)); } +ObjectMgr::ScriptNameContainer const& ObjectMgr::GetAllScriptNames() const +{ + return _scriptNamesStore; +} + std::string const& ObjectMgr::GetScriptName(uint32 id) const { static std::string const empty = ""; - return id < _scriptNamesStore.size() ? _scriptNamesStore[id] : empty; + return (id < _scriptNamesStore.size()) ? _scriptNamesStore[id] : empty; } uint32 ObjectMgr::GetScriptId(std::string const& name) diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index 40e8429087de4..03ad619cad877 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -374,7 +374,7 @@ struct ScriptInfo typedef std::multimap ScriptMap; typedef std::map ScriptMapMap; -typedef std::multimap SpellScriptsContainer; +typedef std::multimap> SpellScriptsContainer; typedef std::pair SpellScriptsBounds; TC_GAME_API extern ScriptMapMap sSpellScripts; TC_GAME_API extern ScriptMapMap sEventScripts; @@ -1281,6 +1281,7 @@ class TC_GAME_API ObjectMgr bool IsVendorItemValid(uint32 vendor_entry, uint32 id, int32 maxcount, uint32 ptime, uint32 ExtendedCost, uint8 type, Player* player = NULL, std::set* skip_vendors = NULL, uint32 ORnpcflag = 0) const; void LoadScriptNames(); + ScriptNameContainer const& GetAllScriptNames() const; std::string const& GetScriptName(uint32 id) const; uint32 GetScriptId(std::string const& name); diff --git a/src/server/game/Instances/InstanceScript.cpp b/src/server/game/Instances/InstanceScript.cpp index 8892be38d37b1..b4c56c3239493 100644 --- a/src/server/game/Instances/InstanceScript.cpp +++ b/src/server/game/Instances/InstanceScript.cpp @@ -29,6 +29,8 @@ #include "Pet.h" #include "WorldSession.h" #include "Opcodes.h" +#include "ScriptReloadMgr.h" +#include "ScriptMgr.h" BossBoundaryData::~BossBoundaryData() { @@ -36,6 +38,18 @@ BossBoundaryData::~BossBoundaryData() delete it->Boundary; } +InstanceScript::InstanceScript(Map* map) : instance(map), completedEncounters(0) +{ +#ifdef TRINITY_API_USE_DYNAMIC_LINKING + uint32 scriptId = sObjectMgr->GetInstanceTemplate(map->GetId())->ScriptId; + auto const scriptname = sObjectMgr->GetScriptName(scriptId); + ASSERT(!scriptname.empty()); + // Acquire a strong reference from the script module + // to keep it loaded until this object is destroyed. + module_reference = sScriptMgr->AcquireModuleReferenceOfScriptName(scriptname); +#endif // #ifndef TRINITY_API_USE_DYNAMIC_LINKING +} + void InstanceScript::SaveToDB() { std::string data = GetSaveData(); diff --git a/src/server/game/Instances/InstanceScript.h b/src/server/game/Instances/InstanceScript.h index 2bb2f14ca1292..1611f3c290f6d 100644 --- a/src/server/game/Instances/InstanceScript.h +++ b/src/server/game/Instances/InstanceScript.h @@ -37,6 +37,7 @@ class Unit; class Player; class GameObject; class Creature; +class ModuleReference; enum EncounterFrameType { @@ -141,7 +142,7 @@ typedef std::map ObjectInfoMap; class TC_GAME_API InstanceScript : public ZoneScript { public: - explicit InstanceScript(Map* map) : instance(map), completedEncounters(0) { } + explicit InstanceScript(Map* map); virtual ~InstanceScript() { } @@ -296,6 +297,11 @@ class TC_GAME_API InstanceScript : public ZoneScript ObjectInfoMap _gameObjectInfo; ObjectGuidMap _objectGuids; uint32 completedEncounters; // completed encounter mask, bit indexes are DungeonEncounter.dbc boss numbers, used for packets + + #ifdef TRINITY_API_USE_DYNAMIC_LINKING + // Strong reference to the associated script module + std::shared_ptr module_reference; + #endif // #ifndef TRINITY_API_USE_DYNAMIC_LINKING }; template diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index 51554d67c4c2e..de774df8ce28c 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -71,7 +71,7 @@ Map::~Map() } if (!m_scriptSchedule.empty()) - sScriptMgr->DecreaseScheduledScriptCount(m_scriptSchedule.size()); + sMapMgr->DecreaseScheduledScriptCount(m_scriptSchedule.size()); MMAP::MMapFactory::createOrGetMMapManager()->unloadMapInstance(GetId(), i_InstanceId); } diff --git a/src/server/game/Maps/MapManager.cpp b/src/server/game/Maps/MapManager.cpp index 184af8fc36061..985ce1af681e5 100644 --- a/src/server/game/Maps/MapManager.cpp +++ b/src/server/game/Maps/MapManager.cpp @@ -38,10 +38,10 @@ #include "MiscPackets.h" MapManager::MapManager() + : _nextInstanceId(0), _scheduledScripts(0) { i_gridCleanUpDelay = sWorld->getIntConfig(CONFIG_INTERVAL_GRIDCLEAN); i_timer.SetInterval(sWorld->getIntConfig(CONFIG_INTERVAL_MAPUPDATE)); - _nextInstanceId = 0; } MapManager::~MapManager() { } diff --git a/src/server/game/Maps/MapManager.h b/src/server/game/Maps/MapManager.h index a4075f735635f..a7fdc37d324a0 100644 --- a/src/server/game/Maps/MapManager.h +++ b/src/server/game/Maps/MapManager.h @@ -126,7 +126,12 @@ class TC_GAME_API MapManager template void DoForAllMapsWithMapId(uint32 mapId, Worker&& worker); -private: + uint32 IncreaseScheduledScriptsCount() { return ++_scheduledScripts; } + uint32 DecreaseScheduledScriptCount() { return --_scheduledScripts; } + uint32 DecreaseScheduledScriptCount(size_t count) { return _scheduledScripts -= count; } + bool IsScriptScheduled() const { return _scheduledScripts > 0; } + + private: typedef std::unordered_map MapMapType; typedef std::vector InstanceIds; @@ -150,6 +155,9 @@ class TC_GAME_API MapManager InstanceIds _instanceIds; uint32 _nextInstanceId; MapUpdater m_updater; + + // atomic op counter for active scripts amount + std::atomic _scheduledScripts; }; template diff --git a/src/server/game/Scripting/MapScripts.cpp b/src/server/game/Maps/MapScripts.cpp similarity index 99% rename from src/server/game/Scripting/MapScripts.cpp rename to src/server/game/Maps/MapScripts.cpp index 8caabbbe1ec95..fd1f798d2d036 100644 --- a/src/server/game/Scripting/MapScripts.cpp +++ b/src/server/game/Maps/MapScripts.cpp @@ -20,6 +20,7 @@ #include "GridNotifiers.h" #include "GossipDef.h" #include "Map.h" +#include "MapManager.h" #include "ObjectMgr.h" #include "Pet.h" #include "Item.h" @@ -57,7 +58,7 @@ void Map::ScriptsStart(ScriptMapMap const& scripts, uint32 id, Object* source, O if (iter->first == 0) immedScript = true; - sScriptMgr->IncreaseScheduledScriptsCount(); + sMapMgr->IncreaseScheduledScriptsCount(); } ///- If one of the effects should be immediate, launch the script execution if (/*start &&*/ immedScript && !i_scriptLock) @@ -85,7 +86,7 @@ void Map::ScriptCommandStart(ScriptInfo const& script, uint32 delay, Object* sou sa.script = &script; m_scriptSchedule.insert(ScriptScheduleMap::value_type(time_t(sWorld->GetGameTime() + delay), sa)); - sScriptMgr->IncreaseScheduledScriptsCount(); + sMapMgr->IncreaseScheduledScriptsCount(); ///- If effects should be immediate, launch the script execution if (delay == 0 && !i_scriptLock) @@ -902,6 +903,6 @@ void Map::ScriptsProcess() m_scriptSchedule.erase(iter); iter = m_scriptSchedule.begin(); - sScriptMgr->DecreaseScheduledScriptCount(); + sMapMgr->DecreaseScheduledScriptCount(); } } diff --git a/src/server/game/OutdoorPvP/OutdoorPvPMgr.cpp b/src/server/game/OutdoorPvP/OutdoorPvPMgr.cpp index 4081652cac736..fe9ba1eab2cf6 100644 --- a/src/server/game/OutdoorPvP/OutdoorPvPMgr.cpp +++ b/src/server/game/OutdoorPvP/OutdoorPvPMgr.cpp @@ -33,8 +33,14 @@ void OutdoorPvPMgr::Die() for (OutdoorPvPSet::iterator itr = m_OutdoorPvPSet.begin(); itr != m_OutdoorPvPSet.end(); ++itr) delete *itr; + m_OutdoorPvPSet.clear(); + for (OutdoorPvPDataMap::iterator itr = m_OutdoorPvPDatas.begin(); itr != m_OutdoorPvPDatas.end(); ++itr) delete itr->second; + + m_OutdoorPvPDatas.clear(); + + m_OutdoorPvPMap.clear(); } OutdoorPvPMgr* OutdoorPvPMgr::instance() diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index 77d747898f9ed..0ff4e03ba7281 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -17,6 +17,7 @@ */ #include "ScriptMgr.h" +#include "ScriptReloadMgr.h" #include "Config.h" #include "DatabaseEnv.h" #include "DBCStores.h" @@ -34,11 +35,9 @@ #include "WorldPacket.h" #include "WorldSession.h" #include "Chat.h" - -// namespace -// { - UnusedScriptNamesContainer UnusedScriptNames; -// } +#include "MapManager.h" +#include "LFGScripts.h" +#include "InstanceScript.h" // Trait which indicates whether this script type // must be assigned in the database. @@ -66,6 +65,10 @@ template<> struct is_script_database_bound : std::true_type { }; +template<> +struct is_script_database_bound + : std::true_type { }; + template<> struct is_script_database_bound : std::true_type { }; @@ -94,143 +97,838 @@ template<> struct is_script_database_bound : std::true_type { }; +enum Spells +{ + SPELL_HOTSWAP_VISUAL_SPELL_EFFECT = 40162 // 59084 +}; + +class ScriptRegistryInterface +{ +public: + ScriptRegistryInterface() { } + virtual ~ScriptRegistryInterface() { } + + ScriptRegistryInterface(ScriptRegistryInterface const&) = delete; + ScriptRegistryInterface(ScriptRegistryInterface&&) = delete; + + ScriptRegistryInterface& operator= (ScriptRegistryInterface const&) = delete; + ScriptRegistryInterface& operator= (ScriptRegistryInterface&&) = delete; + + /// Removes all scripts associated with the given script context. + /// Requires ScriptRegistryBase::SwapContext to be called after all transfers have finished. + virtual void ReleaseContext(std::string const& context) = 0; + + /// Injects and updates the changed script objects. + virtual void SwapContext(bool initialize) = 0; + + /// Removes the scripts used by this registry from the given container. + /// Used to find unused script names. + virtual void RemoveUsedScriptsFromContainer(std::unordered_set& scripts) = 0; + + /// Unloads the script registry. + virtual void Unload() = 0; +}; + +template +class ScriptRegistry; + +class ScriptRegistryCompositum + : public ScriptRegistryInterface +{ + ScriptRegistryCompositum() { } + + template + friend class ScriptRegistry; + + /// Type erasure wrapper for objects + class DeleteableObjectBase + { + public: + DeleteableObjectBase() { } + virtual ~DeleteableObjectBase() { } + + DeleteableObjectBase(DeleteableObjectBase const&) = delete; + DeleteableObjectBase& operator= (DeleteableObjectBase const&) = delete; + }; + + template + class DeleteableObject + : public DeleteableObjectBase + { + public: + DeleteableObject(T&& object) + : _object(std::forward(object)) { } + + private: + T _object; + }; + +public: + void SetScriptNameInContext(std::string const& scriptname, std::string const& context) + { + ASSERT(_scriptnames_to_context.find(scriptname) == _scriptnames_to_context.end()); + _scriptnames_to_context.insert(std::make_pair(scriptname, context)); + } + + std::string const& GetScriptContextOfScriptName(std::string const& scriptname) const + { + auto itr = _scriptnames_to_context.find(scriptname); + ASSERT(itr != _scriptnames_to_context.end() && + "Given scriptname doesn't exist!"); + return itr->second; + } + + void ReleaseContext(std::string const& context) final override + { + for (auto itr = _scriptnames_to_context.begin(); + itr != _scriptnames_to_context.end();) + if (itr->second == context) + itr = _scriptnames_to_context.erase(itr); + else + ++itr; + + for (auto const registry : _registries) + registry->ReleaseContext(context); + } + + void SwapContext(bool initialize) final override + { + for (auto const registry : _registries) + registry->SwapContext(initialize); + + DoDelayedDelete(); + } + + void RemoveUsedScriptsFromContainer(std::unordered_set& scripts) final override + { + for (auto const registry : _registries) + registry->RemoveUsedScriptsFromContainer(scripts); + } + + void Unload() final override + { + for (auto const registry : _registries) + registry->Unload(); + } + + template + void QueueForDelayedDelete(T&& any) + { + _delayed_delete_queue.push_back( + Trinity::make_unique< + DeleteableObject::type> + >(std::forward(any)) + ); + } + + static ScriptRegistryCompositum* Instance() + { + static ScriptRegistryCompositum instance; + return &instance; + } + +private: + void Register(ScriptRegistryInterface* registry) + { + _registries.insert(registry); + } + + void DoDelayedDelete() + { + _delayed_delete_queue.clear(); + } + + std::unordered_set _registries; + + std::vector> _delayed_delete_queue; + + std::unordered_map< + std::string /*script name*/, + std::string /*context*/ + > _scriptnames_to_context; +}; + +#define sScriptRegistryCompositum ScriptRegistryCompositum::Instance() + +template +class SpecializedScriptRegistry; + // This is the global static registry of scripts. -template -class ScriptRegistry +template +class ScriptRegistry final + : public SpecializedScriptRegistry< + ScriptType, is_script_database_bound::value> { + ScriptRegistry() + { + sScriptRegistryCompositum->Register(this); + } + +public: + static ScriptRegistry* Instance() + { + static ScriptRegistry instance; + return &instance; + } + + void LogDuplicatedScriptPointerError(ScriptType const* first, ScriptType const* second) + { + // See if the script is using the same memory as another script. If this happens, it means that + // someone forgot to allocate new memory for a script. + TC_LOG_ERROR("scripts", "Script '%s' has same memory pointer as '%s'.", + first->GetName().c_str(), second->GetName().c_str()); + } +}; + +class ScriptRegistrySwapHookBase +{ +public: + ScriptRegistrySwapHookBase() { } + virtual ~ScriptRegistrySwapHookBase() { } + + ScriptRegistrySwapHookBase(ScriptRegistrySwapHookBase const&) = delete; + ScriptRegistrySwapHookBase(ScriptRegistrySwapHookBase&&) = delete; + + ScriptRegistrySwapHookBase& operator= (ScriptRegistrySwapHookBase const&) = delete; + ScriptRegistrySwapHookBase& operator= (ScriptRegistrySwapHookBase&&) = delete; + + /// Called before the actual context release happens + virtual void BeforeReleaseContext(std::string const& /*context*/) { } + + /// Called before SwapContext + virtual void BeforeSwapContext() { } + + /// Called before Unload + virtual void BeforeUnload() { } +}; + +template +class ScriptRegistrySwapHooks + : public ScriptRegistrySwapHookBase +{ +}; + +/// This hook is responsible for swapping OutdoorPvP's +template +class UnsupportedScriptRegistrySwapHooks + : public ScriptRegistrySwapHookBase +{ +public: + void BeforeReleaseContext(std::string const& context) final override + { + auto const bounds = static_cast(this)->_ids_of_contexts.equal_range(context); + ASSERT(bounds.first == bounds.second); + } +}; + +/// This hook is responsible for swapping Creature and GameObject AI's +template +class CreatureGameObjectScriptRegistrySwapHooks + : public ScriptRegistrySwapHookBase +{ + template + class AIFunctionMapWorker + { public: + template + AIFunctionMapWorker(T&& worker) + : _worker(std::forward(worker)) { } + + void Visit(std::unordered_map& objects) + { + _worker(objects); + } + + template + void Visit(std::unordered_map&) { } - typedef std::map ScriptMap; - typedef typename ScriptMap::iterator ScriptMapIterator; + private: + W _worker; + }; - // The actual list of scripts. This will be accessed concurrently, so it must not be modified - // after server startup. - static ScriptMap ScriptPointerList; - static std::vector Scripts; + class AsyncCastHotswapEffectEvent : public BasicEvent + { + public: + explicit AsyncCastHotswapEffectEvent(Unit* owner) : owner_(owner) { } - static void AddScript(TScript* const script, bool addToDeleteContainer = true) + bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) override { - ASSERT(script); + owner_->CastSpell(owner_, SPELL_HOTSWAP_VISUAL_SPELL_EFFECT, true); + return true; + } + + private: + Unit* owner_; + }; + + // Hook which is called before a creature is swapped + static void UnloadStage1(Creature* creature) + { + if (creature->IsCharmed()) + creature->RemoveCharmedBy(nullptr); + + ASSERT(!creature->IsCharmed(), + "There is a disabled AI which is still loaded."); + + creature->AI()->EnterEvadeMode(); + } + + static void UnloadStage2(Creature* creature) + { + bool const destroyed = creature->AIM_Destroy(); + ASSERT(destroyed, + "Destroying the AI should never fail here!"); + (void)destroyed; + + ASSERT(!creature->AI(), + "The AI should be null here!"); + } + + // Hook which is called before a gameobject is swapped + static void UnloadStage1(GameObject* gameobject) + { + gameobject->AI()->Reset(); + } - // See if the script is using the same memory as another script. If this happens, it means that - // someone forgot to allocate new memory for a script. - for (ScriptMapIterator it = ScriptPointerList.begin(); it != ScriptPointerList.end(); ++it) + static void UnloadStage2(GameObject* gameobject) + { + gameobject->AIM_Destroy(); + + ASSERT(!gameobject->AI(), + "The AI should be null here!"); + } + + // Hook which is called after a creature was swapped + static void LoadStage1(Creature* creature) + { + ASSERT(!creature->AI(), + "The AI should be null here!"); + + if (creature->IsAlive()) + creature->ClearUnitState(UNIT_STATE_EVADE); + + bool const created = creature->AIM_Initialize(); + ASSERT(created, + "Creating the AI should never fail here!"); + (void)created; + } + + static void LoadStage2(Creature* creature) + { + if (!creature->IsAlive()) + return; + + creature->AI()->EnterEvadeMode(); + + // Cast a dummy visual spell asynchronously here to signal + // that the AI was hot swapped + creature->m_Events.AddEvent(new AsyncCastHotswapEffectEvent(creature), + creature->m_Events.CalculateTime(0)); + } + + // Hook which is called after a gameobject was swapped + static void LoadStage1(GameObject* gameobject) + { + ASSERT(!gameobject->AI(), + "The AI should be null here!"); + + gameobject->AIM_Initialize(); + } + + static void LoadStage2(GameObject* gameobject) + { + gameobject->AI()->Reset(); + } + + template + void RunOverAllEntities(T fn) + { + auto evaluator = [&](std::unordered_map& objects) + { + for (auto object : objects) + fn(object.second); + }; + + AIFunctionMapWorker::type> worker(std::move(evaluator)); + TypeContainerVisitor visitor(worker); + + sMapMgr->DoForAllMaps([&](Map* map) + { + // Run the worker over all maps + visitor.Visit(map->GetObjectsStore()); + }); + } + +public: + void BeforeReleaseContext(std::string const& context) final override + { + auto ids_to_remove = static_cast(this)->GetScriptIDsToRemove(context); + + std::vector stage2; + + RunOverAllEntities([&](ObjectType* object) + { + if (ids_to_remove.find(object->GetScriptId()) != ids_to_remove.end()) { - if (it->second == script) - { - TC_LOG_ERROR("scripts", "Script '%s' has same memory pointer as '%s'.", - script->GetName().c_str(), it->second->GetName().c_str()); + UnloadStage1(object); + stage2.push_back(object); + } + }); - return; + for (auto object : stage2) + UnloadStage2(object); + + // Add the new ids which are removed to the global ids to remove set + ids_removed_.insert(ids_to_remove.begin(), ids_to_remove.end()); + } + + void BeforeSwapContext() final override + { + // Add the recently added scripts to the deleted scripts to replace + // default AI's with recently added core scripts. + ids_removed_.insert(static_cast(this)->GetRecentlyAddedScriptIDs().begin(), + static_cast(this)->GetRecentlyAddedScriptIDs().end()); + + std::vector remove; + std::vector stage2; + + RunOverAllEntities([&](ObjectType* object) + { + if (ids_removed_.find(object->GetScriptId()) != ids_removed_.end()) + { + if (object->AI()) + { + // Overwrite existing (default) AI's which are replaced by a new script + UnloadStage1(object); + remove.push_back(object); } + + stage2.push_back(object); } + }); + + for (auto object : remove) + UnloadStage2(object); - AddScript(is_script_database_bound{}, script); - if (addToDeleteContainer) - Scripts.push_back(script); + for (auto object : stage2) + LoadStage1(object); + + for (auto object : stage2) + LoadStage2(object); + + ids_removed_.clear(); + } + + void BeforeUnload() final override + { + ASSERT(ids_removed_.empty()); + } + +private: + std::unordered_set ids_removed_; +}; + +// This hook is responsible for swapping CreatureAI's +template +class ScriptRegistrySwapHooks + : public CreatureGameObjectScriptRegistrySwapHooks< + Creature, CreatureScript, Base + > { }; + +// This hook is responsible for swapping GameObjectAI's +template +class ScriptRegistrySwapHooks + : public CreatureGameObjectScriptRegistrySwapHooks< + GameObject, GameObjectScript, Base + > { }; + +/// This hook is responsible for swapping BattlegroundScript's +template +class ScriptRegistrySwapHooks + : public UnsupportedScriptRegistrySwapHooks { }; + +/// This hook is responsible for swapping OutdoorPvP's +template +class ScriptRegistrySwapHooks + : public ScriptRegistrySwapHookBase +{ +public: + ScriptRegistrySwapHooks() : swapped(false) { } + + void BeforeReleaseContext(std::string const& context) final override + { + auto const bounds = static_cast(this)->_ids_of_contexts.equal_range(context); + + if ((!swapped) && (bounds.first != bounds.second)) + { + swapped = true; + sOutdoorPvPMgr->Die(); } + } - // Gets a script by its ID (assigned by ObjectMgr). - static TScript* GetScriptById(uint32 id) + void BeforeSwapContext() final override + { + if (swapped) { - ScriptMapIterator it = ScriptPointerList.find(id); - if (it != ScriptPointerList.end()) - return it->second; + sOutdoorPvPMgr->InitOutdoorPvP(); + swapped = false; + } + } + + void BeforeUnload() final override + { + ASSERT(!swapped); + } + +private: + bool swapped; +}; + +/// This hook is responsible for swapping InstanceMapScript's +template +class ScriptRegistrySwapHooks + : public ScriptRegistrySwapHookBase +{ +public: + ScriptRegistrySwapHooks() : swapped(false) { } - return NULL; + void BeforeReleaseContext(std::string const& context) final override + { + auto const bounds = static_cast(this)->_ids_of_contexts.equal_range(context); + if (bounds.first != bounds.second) + swapped = true; + } + + void BeforeSwapContext() final override + { + swapped = false; + } + + void BeforeUnload() final override + { + ASSERT(!swapped); + } + +private: + bool swapped; +}; + +/// This hook is responsible for swapping SpellScriptLoader's +template +class ScriptRegistrySwapHooks + : public ScriptRegistrySwapHookBase +{ +public: + ScriptRegistrySwapHooks() : swapped(false) { } + + void BeforeReleaseContext(std::string const& context) final override + { + auto const bounds = static_cast(this)->_ids_of_contexts.equal_range(context); + + if (bounds.first != bounds.second) + swapped = true; + } + + void BeforeSwapContext() final override + { + if (swapped) + { + sObjectMgr->ValidateSpellScripts(); + swapped = false; } + } - private: + void BeforeUnload() final override + { + ASSERT(!swapped); + } + +private: + bool swapped; +}; + +// Database bound script registry +template +class SpecializedScriptRegistry + : public ScriptRegistryInterface, + public ScriptRegistrySwapHooks> +{ + template + friend class UnsupportedScriptRegistrySwapHooks; + + template + friend class ScriptRegistrySwapHooks; + + template + friend class CreatureGameObjectScriptRegistrySwapHooks; + +public: + SpecializedScriptRegistry() { } + + typedef std::unordered_map< + uint32 /*script id*/, + std::unique_ptr + > ScriptStoreType; + + typedef typename ScriptStoreType::iterator ScriptStoreIteratorType; + + void ReleaseContext(std::string const& context) final override + { + this->BeforeReleaseContext(context); + + auto const bounds = _ids_of_contexts.equal_range(context); + for (auto itr = bounds.first; itr != bounds.second; ++itr) + _scripts.erase(itr->second); + } + + void SwapContext(bool initialize) final override + { + if (!initialize) + this->BeforeSwapContext(); + + _recently_added_ids.clear(); + } + + void RemoveUsedScriptsFromContainer(std::unordered_set& scripts) final override + { + for (auto const& script : _scripts) + scripts.erase(script.second->GetName()); + } + + void Unload() final override + { + this->BeforeUnload(); + + ASSERT(_recently_added_ids.empty(), + "Recently added script ids should be empty here!"); + + _scripts.clear(); + _ids_of_contexts.clear(); + } + + // Adds a database bound script + void AddScript(ScriptType* script) + { + ASSERT(script, + "Tried to call AddScript with a nullpointer!"); + ASSERT(!sScriptMgr->GetCurrentScriptContext().empty(), + "Tried to register a script without being in a valid script context!"); - // Adds a database bound script - static void AddScript(std::true_type, TScript* const script) + std::unique_ptr script_ptr(script); + + // Get an ID for the script. An ID only exists if it's a script that is assigned in the database + // through a script name (or similar). + if (uint32 const id = sObjectMgr->GetScriptId(script->GetName())) { - // Get an ID for the script. An ID only exists if it's a script that is assigned in the database - // through a script name (or similar). - uint32 id = sObjectMgr->GetScriptId(script->GetName()); - if (id) + // Try to find an existing script. + for (auto const& stored_script : _scripts) { - // Try to find an existing script. - bool existing = false; - for (ScriptMapIterator it = ScriptPointerList.begin(); it != ScriptPointerList.end(); ++it) - { - // If the script names match... - if (it->second->GetName() == script->GetName()) - { - // ... It exists. - existing = true; - break; - } - } - - // If the script isn't assigned -> assign it! - if (!existing) - { - ScriptPointerList[id] = script; - sScriptMgr->IncrementScriptCount(); - - #ifdef SCRIPTS - UnusedScriptNamesContainer::iterator itr = std::lower_bound(UnusedScriptNames.begin(), UnusedScriptNames.end(), script->GetName()); - if (itr != UnusedScriptNames.end() && *itr == script->GetName()) - UnusedScriptNames.erase(itr); - #endif - } - else + // If the script names match... + if (stored_script.second->GetName() == script->GetName()) { // If the script is already assigned -> delete it! - TC_LOG_ERROR("scripts", "Script '%s' already assigned with the same script name, so the script can't work.", - script->GetName().c_str()); + TC_LOG_ERROR("scripts", "Script '%s' already assigned with the same script name, " + "so the script can't work.", script->GetName().c_str()); - ABORT(); // Error that should be fixed ASAP. + // Error that should be fixed ASAP. + sScriptRegistryCompositum->QueueForDelayedDelete(std::move(script_ptr)); + ABORT(); + return; } } - else - { - // The script uses a script name from database, but isn't assigned to anything. - TC_LOG_ERROR("sql.sql", "Script named '%s' does not have a script name assigned in database.", script->GetName().c_str()); - } - } - // Adds a non database bound script - static void AddScript(std::false_type, TScript* const script) + // If the script isn't assigned -> assign it! + _scripts.insert(std::make_pair(id, std::move(script_ptr))); + _ids_of_contexts.insert(std::make_pair(sScriptMgr->GetCurrentScriptContext(), id)); + _recently_added_ids.insert(id); + + sScriptRegistryCompositum->SetScriptNameInContext(script->GetName(), + sScriptMgr->GetCurrentScriptContext()); + } + else { - // We're dealing with a code-only script; just add it. - ScriptPointerList[_scriptIdCounter++] = script; - sScriptMgr->IncrementScriptCount(); + // The script uses a script name from database, but isn't assigned to anything. + TC_LOG_ERROR("sql.sql", "Script named '%s' does not have a script name assigned in database.", + script->GetName().c_str()); + + // Avoid calling "delete script;" because we are currently in the script constructor + // In a valid scenario this will not happen because every script has a name assigned in the database + sScriptRegistryCompositum->QueueForDelayedDelete(std::move(script_ptr)); + return; } + } + + // Gets a script by its ID (assigned by ObjectMgr). + ScriptType* GetScriptById(uint32 id) + { + auto const itr = _scripts.find(id); + if (itr != _scripts.end()) + return itr->second.get(); + + return nullptr; + } + + ScriptStoreType& GetScripts() + { + return _scripts; + } - // Counter used for code-only scripts. - static uint32 _scriptIdCounter; +protected: + // Returns the script id's which are registered to a certain context + std::unordered_set GetScriptIDsToRemove(std::string const& context) const + { + // Create a set of all ids which are removed + std::unordered_set scripts_to_remove; + + auto const bounds = _ids_of_contexts.equal_range(context); + for (auto itr = bounds.first; itr != bounds.second; ++itr) + scripts_to_remove.insert(itr->second); + + return scripts_to_remove; + } + + std::unordered_set const& GetRecentlyAddedScriptIDs() const + { + return _recently_added_ids; + } + +private: + ScriptStoreType _scripts; + + // Scripts of a specific context + std::unordered_multimap _ids_of_contexts; + + // Script id's which were registered recently + std::unordered_set _recently_added_ids; +}; + +/// This hook is responsible for swapping CommandScript's +template +class ScriptRegistrySwapHooks + : public ScriptRegistrySwapHookBase +{ +public: + void BeforeReleaseContext(std::string const& /*context*/) final override + { + ChatHandler::invalidateCommandTable(); + } + + void BeforeSwapContext() final override + { + ChatHandler::invalidateCommandTable(); + } + + void BeforeUnload() final override + { + ChatHandler::invalidateCommandTable(); + } +}; + +// Database unbound script registry +template +class SpecializedScriptRegistry + : public ScriptRegistryInterface, + public ScriptRegistrySwapHooks> +{ + template + friend class ScriptRegistrySwapHooks; + +public: + typedef std::unordered_multimap> ScriptStoreType; + typedef typename ScriptStoreType::iterator ScriptStoreIteratorType; + + SpecializedScriptRegistry() { } + + void ReleaseContext(std::string const& context) final override + { + this->BeforeReleaseContext(context); + + _scripts.erase(context); + } + + void SwapContext(bool) final override + { + this->BeforeSwapContext(); + } + + void RemoveUsedScriptsFromContainer(std::unordered_set& scripts) final override + { + for (auto const& script : _scripts) + scripts.erase(script.second->GetName()); + } + + void Unload() final override + { + this->BeforeUnload(); + + _scripts.clear(); + } + + // Adds a non database bound script + void AddScript(ScriptType* script) + { + ASSERT(script, + "Tried to call AddScript with a nullpointer!"); + ASSERT(!sScriptMgr->GetCurrentScriptContext().empty(), + "Tried to register a script without being in a valid script context!"); + + std::unique_ptr script_ptr(script); + + for (auto const& entry : _scripts) + if (entry.second.get() == script) + { + static_cast*>(this)-> + LogDuplicatedScriptPointerError(script, entry.second.get()); + + sScriptRegistryCompositum->QueueForDelayedDelete(std::move(script_ptr)); + return; + } + + // We're dealing with a code-only script, just add it. + _scripts.insert(std::make_pair(sScriptMgr->GetCurrentScriptContext(), std::move(script_ptr))); + } + + ScriptStoreType& GetScripts() + { + return _scripts; + } + +private: + ScriptStoreType _scripts; }; // Utility macros to refer to the script registry. -#define SCR_REG_MAP(T) ScriptRegistry::ScriptMap -#define SCR_REG_ITR(T) ScriptRegistry::ScriptMapIterator -#define SCR_REG_LST(T) ScriptRegistry::ScriptPointerList -#define SCR_REG_VEC(T) ScriptRegistry::Scripts +#define SCR_REG_MAP(T) ScriptRegistry::ScriptStoreType +#define SCR_REG_ITR(T) ScriptRegistry::ScriptStoreIteratorType +#define SCR_REG_LST(T) ScriptRegistry::Instance()->GetScripts() // Utility macros for looping over scripts. #define FOR_SCRIPTS(T, C, E) \ if (SCR_REG_LST(T).empty()) \ return; \ + \ for (SCR_REG_ITR(T) C = SCR_REG_LST(T).begin(); \ C != SCR_REG_LST(T).end(); ++C) + #define FOR_SCRIPTS_RET(T, C, E, R) \ if (SCR_REG_LST(T).empty()) \ return R; \ + \ for (SCR_REG_ITR(T) C = SCR_REG_LST(T).begin(); \ C != SCR_REG_LST(T).end(); ++C) + #define FOREACH_SCRIPT(T) \ FOR_SCRIPTS(T, itr, end) \ - itr->second + itr->second // Utility macros for finding specific scripts. #define GET_SCRIPT(T, I, V) \ - T* V = ScriptRegistry::GetScriptById(I); \ + T* V = ScriptRegistry::Instance()->GetScriptById(I); \ if (!V) \ return; + #define GET_SCRIPT_RET(T, I, V, R) \ - T* V = ScriptRegistry::GetScriptById(I); \ + T* V = ScriptRegistry::Instance()->GetScriptById(I); \ if (!V) \ return R; @@ -240,8 +938,18 @@ struct TSpellSummary uint8 Effects; // set of enum SelectEffect } *SpellSummary; +ScriptObject::ScriptObject(const char* name) : _name(name) +{ + sScriptMgr->IncreaseScriptCount(); +} + +ScriptObject::~ScriptObject() +{ + sScriptMgr->DecreaseScriptCount(); +} + ScriptMgr::ScriptMgr() - : _scriptCount(0), _scheduledScripts(0), _script_loader_callback(nullptr) + : _scriptCount(0), _script_loader_callback(nullptr) { } @@ -255,6 +963,9 @@ ScriptMgr* ScriptMgr::instance() void ScriptMgr::Initialize() { + ASSERT(sSpellMgr->GetSpellInfo(SPELL_HOTSWAP_VISUAL_SPELL_EFFECT) + && "Reload hotswap spell effect for creatures isn't valid!"); + uint32 oldMSTime = getMSTime(); LoadDatabase(); @@ -263,59 +974,84 @@ void ScriptMgr::Initialize() FillSpellSummary(); + // Load core scripts + SetScriptContext("___static___"); + + // SmartAI AddSC_SmartScripts(); + // LFGScripts + lfg::AddSC_LFGScripts(); + + // Load all static linked scripts through the script loader function. ASSERT(_script_loader_callback, "Script loader callback wasn't registered!"); - _script_loader_callback(); -#ifdef SCRIPTS - for (std::string const& scriptName : UnusedScriptNames) + // Initialize all dynamic scripts + // and finishes the context switch to do + // bulk loading + sScriptReloadMgr->Initialize(); + + // Loads all scripts from the current context + sScriptMgr->SwapScriptContext(true); + + // Print unused script names. + std::unordered_set unusedScriptNames( + sObjectMgr->GetAllScriptNames().begin(), + sObjectMgr->GetAllScriptNames().end()); + + // Remove the used scripts from the given container. + sScriptRegistryCompositum->RemoveUsedScriptsFromContainer(unusedScriptNames); + + for (std::string const& scriptName : unusedScriptNames) { - TC_LOG_ERROR("sql.sql", "ScriptName '%s' exists in database, but no core script found!", scriptName.c_str()); + // Avoid complaining about empty script names since the + // script name container contains a placeholder as the 0 element. + if (scriptName.empty()) + continue; + + TC_LOG_ERROR("sql.sql", "ScriptName '%s' exists in database, " + "but no core script found!", scriptName.c_str()); } -#endif - TC_LOG_INFO("server.loading", ">> Loaded %u C++ scripts in %u ms", GetScriptCount(), GetMSTimeDiffToNow(oldMSTime)); + TC_LOG_INFO("server.loading", ">> Loaded %u C++ scripts in %u ms", + GetScriptCount(), GetMSTimeDiffToNow(oldMSTime)); +} + +void ScriptMgr::SetScriptContext(std::string const& context) +{ + _currentContext = context; +} + +void ScriptMgr::SwapScriptContext(bool initialize) +{ + sScriptRegistryCompositum->SwapContext(initialize); + _currentContext.clear(); +} + +void ScriptMgr::ReleaseScriptContext(std::string const& context) +{ + sScriptRegistryCompositum->ReleaseContext(context); +} + +std::shared_ptr + ScriptMgr::AcquireModuleReferenceOfScriptName(std::string const& scriptname) const +{ +#ifdef TRINITY_API_USE_DYNAMIC_LINKING + // Returns the reference to the module of the given scriptname + return ScriptReloadMgr::AcquireModuleReferenceOfContext( + sScriptRegistryCompositum->GetScriptContextOfScriptName(scriptname)); +#else + // Something went wrong when this function is used in + // a static linked context. + WPAbort(); +#endif // #ifndef TRINITY_API_USE_DYNAMIC_LINKING } void ScriptMgr::Unload() { - #define SCR_CLEAR(T) \ - for (T* scr : SCR_REG_VEC(T)) \ - delete scr; \ - SCR_REG_VEC(T).clear(); - - // Clear scripts for every script type. - SCR_CLEAR(SpellScriptLoader); - SCR_CLEAR(ServerScript); - SCR_CLEAR(WorldScript); - SCR_CLEAR(FormulaScript); - SCR_CLEAR(WorldMapScript); - SCR_CLEAR(InstanceMapScript); - SCR_CLEAR(BattlegroundMapScript); - SCR_CLEAR(ItemScript); - SCR_CLEAR(CreatureScript); - SCR_CLEAR(GameObjectScript); - SCR_CLEAR(AreaTriggerScript); - SCR_CLEAR(BattlegroundScript); - SCR_CLEAR(OutdoorPvPScript); - SCR_CLEAR(CommandScript); - SCR_CLEAR(WeatherScript); - SCR_CLEAR(AuctionHouseScript); - SCR_CLEAR(ConditionScript); - SCR_CLEAR(VehicleScript); - SCR_CLEAR(DynamicObjectScript); - SCR_CLEAR(TransportScript); - SCR_CLEAR(AchievementCriteriaScript); - SCR_CLEAR(PlayerScript); - SCR_CLEAR(AccountScript); - SCR_CLEAR(GuildScript); - SCR_CLEAR(GroupScript); - SCR_CLEAR(UnitScript); - - #undef SCR_CLEAR + sScriptRegistryCompositum->Unload(); delete[] SpellSummary; delete[] UnitAI::AISpellInfo; @@ -416,38 +1152,22 @@ void ScriptMgr::FillSpellSummary() } } -void ScriptMgr::CreateSpellScripts(uint32 spellId, std::list& scriptVector) +template +void CreateSpellOrAuraScripts(uint32 spellId, std::list& scriptVector, F&& extractor) { SpellScriptsBounds bounds = sObjectMgr->GetSpellScriptsBounds(spellId); for (SpellScriptsContainer::iterator itr = bounds.first; itr != bounds.second; ++itr) { - SpellScriptLoader* tmpscript = ScriptRegistry::GetScriptById(itr->second); - if (!tmpscript) + // When the script is disabled continue with the next one + if (itr->second.second) continue; - SpellScript* script = tmpscript->GetSpellScript(); - - if (!script) - continue; - - script->_Init(&tmpscript->GetName(), spellId); - - scriptVector.push_back(script); - } -} - -void ScriptMgr::CreateAuraScripts(uint32 spellId, std::list& scriptVector) -{ - SpellScriptsBounds bounds = sObjectMgr->GetSpellScriptsBounds(spellId); - - for (SpellScriptsContainer::iterator itr = bounds.first; itr != bounds.second; ++itr) - { - SpellScriptLoader* tmpscript = ScriptRegistry::GetScriptById(itr->second); + SpellScriptLoader* tmpscript = ScriptRegistry::Instance()->GetScriptById(itr->second.first); if (!tmpscript) continue; - AuraScript* script = tmpscript->GetAuraScript(); + T* script = (*tmpscript.*extractor)(); if (!script) continue; @@ -458,19 +1178,19 @@ void ScriptMgr::CreateAuraScripts(uint32 spellId, std::list& script } } -void ScriptMgr::CreateSpellScriptLoaders(uint32 spellId, std::vector >& scriptVector) +void ScriptMgr::CreateSpellScripts(uint32 spellId, std::list& scriptVector) { - SpellScriptsBounds bounds = sObjectMgr->GetSpellScriptsBounds(spellId); - scriptVector.reserve(std::distance(bounds.first, bounds.second)); + CreateSpellOrAuraScripts(spellId, scriptVector, &SpellScriptLoader::GetSpellScript); +} - for (SpellScriptsContainer::iterator itr = bounds.first; itr != bounds.second; ++itr) - { - SpellScriptLoader* tmpscript = ScriptRegistry::GetScriptById(itr->second); - if (!tmpscript) - continue; +void ScriptMgr::CreateAuraScripts(uint32 spellId, std::list& scriptVector) +{ + CreateSpellOrAuraScripts(spellId, scriptVector, &SpellScriptLoader::GetAuraScript); +} - scriptVector.push_back(std::make_pair(tmpscript, itr)); - } +SpellScriptLoader* ScriptMgr::GetSpellScriptLoader(uint32 scriptId) +{ + return ScriptRegistry::Instance()->GetScriptById(scriptId); } void ScriptMgr::OnNetworkStart() @@ -1498,56 +2218,62 @@ void ScriptMgr::OnGroupDisband(Group* group) void ScriptMgr::OnHeal(Unit* healer, Unit* reciever, uint32& gain) { FOREACH_SCRIPT(UnitScript)->OnHeal(healer, reciever, gain); + FOREACH_SCRIPT(PlayerScript)->OnHeal(healer, reciever, gain); } void ScriptMgr::OnDamage(Unit* attacker, Unit* victim, uint32& damage) { FOREACH_SCRIPT(UnitScript)->OnDamage(attacker, victim, damage); + FOREACH_SCRIPT(PlayerScript)->OnDamage(attacker, victim, damage); } void ScriptMgr::ModifyPeriodicDamageAurasTick(Unit* target, Unit* attacker, uint32& damage) { FOREACH_SCRIPT(UnitScript)->ModifyPeriodicDamageAurasTick(target, attacker, damage); + FOREACH_SCRIPT(PlayerScript)->ModifyPeriodicDamageAurasTick(target, attacker, damage); } void ScriptMgr::ModifyMeleeDamage(Unit* target, Unit* attacker, uint32& damage) { FOREACH_SCRIPT(UnitScript)->ModifyMeleeDamage(target, attacker, damage); + FOREACH_SCRIPT(PlayerScript)->ModifyMeleeDamage(target, attacker, damage); } void ScriptMgr::ModifySpellDamageTaken(Unit* target, Unit* attacker, int32& damage) { FOREACH_SCRIPT(UnitScript)->ModifySpellDamageTaken(target, attacker, damage); + FOREACH_SCRIPT(PlayerScript)->ModifySpellDamageTaken(target, attacker, damage); } SpellScriptLoader::SpellScriptLoader(const char* name) : ScriptObject(name) { - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } ServerScript::ServerScript(const char* name) : ScriptObject(name) { - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } WorldScript::WorldScript(const char* name) : ScriptObject(name) { - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } FormulaScript::FormulaScript(const char* name) : ScriptObject(name) { - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } UnitScript::UnitScript(const char* name, bool addToScripts) : ScriptObject(name) { - ScriptRegistry::AddScript(this, addToScripts); + if (addToScripts) + ScriptRegistry::Instance()->AddScript(this); } WorldMapScript::WorldMapScript(const char* name, uint32 mapId) @@ -1556,7 +2282,7 @@ WorldMapScript::WorldMapScript(const char* name, uint32 mapId) if (GetEntry() && !GetEntry()->IsWorldMap()) TC_LOG_ERROR("scripts", "WorldMapScript for map %u is invalid.", mapId); - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } InstanceMapScript::InstanceMapScript(const char* name, uint32 mapId) @@ -1565,7 +2291,7 @@ InstanceMapScript::InstanceMapScript(const char* name, uint32 mapId) if (GetEntry() && !GetEntry()->IsDungeon()) TC_LOG_ERROR("scripts", "InstanceMapScript for map %u is invalid.", mapId); - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } BattlegroundMapScript::BattlegroundMapScript(const char* name, uint32 mapId) @@ -1574,122 +2300,117 @@ BattlegroundMapScript::BattlegroundMapScript(const char* name, uint32 mapId) if (GetEntry() && !GetEntry()->IsBattleground()) TC_LOG_ERROR("scripts", "BattlegroundMapScript for map %u is invalid.", mapId); - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } ItemScript::ItemScript(const char* name) : ScriptObject(name) { - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } CreatureScript::CreatureScript(const char* name) : UnitScript(name, false) { - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } GameObjectScript::GameObjectScript(const char* name) : ScriptObject(name) { - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } AreaTriggerScript::AreaTriggerScript(const char* name) : ScriptObject(name) { - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } BattlegroundScript::BattlegroundScript(const char* name) : ScriptObject(name) { - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } OutdoorPvPScript::OutdoorPvPScript(const char* name) : ScriptObject(name) { - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } CommandScript::CommandScript(const char* name) : ScriptObject(name) { - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } WeatherScript::WeatherScript(const char* name) : ScriptObject(name) { - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } AuctionHouseScript::AuctionHouseScript(const char* name) : ScriptObject(name) { - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } ConditionScript::ConditionScript(const char* name) : ScriptObject(name) { - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } VehicleScript::VehicleScript(const char* name) : ScriptObject(name) { - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } DynamicObjectScript::DynamicObjectScript(const char* name) : ScriptObject(name) { - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } TransportScript::TransportScript(const char* name) : ScriptObject(name) { - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } AchievementCriteriaScript::AchievementCriteriaScript(const char* name) : ScriptObject(name) { - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } PlayerScript::PlayerScript(const char* name) : UnitScript(name, false) { - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } AccountScript::AccountScript(const char* name) : ScriptObject(name) { - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } GuildScript::GuildScript(const char* name) : ScriptObject(name) { - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } GroupScript::GroupScript(const char* name) : ScriptObject(name) { - ScriptRegistry::AddScript(this); + ScriptRegistry::Instance()->AddScript(this); } -// Instantiate static members of ScriptRegistry. -template std::map ScriptRegistry::ScriptPointerList; -template std::vector ScriptRegistry::Scripts; -template uint32 ScriptRegistry::_scriptIdCounter = 0; - // Specialize for each script type class like so: template class TC_GAME_API ScriptRegistry; template class TC_GAME_API ScriptRegistry; @@ -1717,13 +2438,3 @@ template class TC_GAME_API ScriptRegistry; template class TC_GAME_API ScriptRegistry; template class TC_GAME_API ScriptRegistry; template class TC_GAME_API ScriptRegistry; - -// Undefine utility macros. -#undef GET_SCRIPT_RET -#undef GET_SCRIPT -#undef FOREACH_SCRIPT -#undef FOR_SCRIPTS_RET -#undef FOR_SCRIPTS -#undef SCR_REG_LST -#undef SCR_REG_ITR -#undef SCR_REG_MAP diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index a94efc24e0d82..c3deab43ee6c8 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -46,6 +46,7 @@ class InstanceMap; class InstanceScript; class Item; class Map; +class ModuleReference; class OutdoorPvP; class Player; class Quest; @@ -156,14 +157,8 @@ class TC_GAME_API ScriptObject protected: - ScriptObject(const char* name) - : _name(name) - { - } - - virtual ~ScriptObject() - { - } + ScriptObject(const char* name); + virtual ~ScriptObject(); private: @@ -337,7 +332,8 @@ class TC_GAME_API WorldMapScript : public ScriptObject, public MapScript WorldMapScript(const char* name, uint32 mapId); }; -class TC_GAME_API InstanceMapScript : public ScriptObject, public MapScript +class TC_GAME_API InstanceMapScript + : public ScriptObject, public MapScript { protected: @@ -833,15 +829,6 @@ class TC_GAME_API GroupScript : public ScriptObject virtual void OnDisband(Group* /*group*/) { } }; -// Placed here due to ScriptRegistry::AddScript dependency. -#define sScriptMgr ScriptMgr::instance() - -// namespace -// { - typedef std::list UnusedScriptNamesContainer; - TC_GAME_API extern UnusedScriptNamesContainer UnusedScriptNames; -// } - // Manages registration, loading, and execution of scripts. class TC_GAME_API ScriptMgr { @@ -851,16 +838,17 @@ class TC_GAME_API ScriptMgr ScriptMgr(); virtual ~ScriptMgr(); + void FillSpellSummary(); + void LoadDatabase(); + + void IncreaseScriptCount() { ++_scriptCount; } + void DecreaseScriptCount() { --_scriptCount; } + public: /* Initialization */ static ScriptMgr* instance(); void Initialize(); - void LoadDatabase(); - void FillSpellSummary(); - - const char* ScriptsVersion() const { return "Integrated Trinity Scripts"; } - void IncrementScriptCount() { ++_scriptCount; } uint32 GetScriptCount() const { return _scriptCount; } typedef void(*ScriptLoaderCallbackType)(); @@ -872,6 +860,27 @@ class TC_GAME_API ScriptMgr _script_loader_callback = script_loader_callback; } + public: /* Script contexts */ + /// Set the current script context, which allows the ScriptMgr + /// to accept new scripts in this context. + /// Requires a SwapScriptContext() call afterwards to load the new scripts. + void SetScriptContext(std::string const& context); + /// Returns the current script context. + std::string const& GetCurrentScriptContext() const { return _currentContext; } + /// Releases all scripts associated with the given script context immediately. + /// Requires a SwapScriptContext() call afterwards to finish the unloading. + void ReleaseScriptContext(std::string const& context); + /// Executes all changed introduced by SetScriptContext and ReleaseScriptContext. + /// It is possible to combine multiple SetScriptContext and ReleaseScriptContext + /// calls for better performance (bulk changes). + void SwapScriptContext(bool initialize = false); + + /// Acquires a strong module reference to the module containing the given script name, + /// which prevents the shared library which contains the script from unloading. + /// The shared library is lazy unloaded as soon as all references to it are released. + std::shared_ptr AcquireModuleReferenceOfScriptName( + std::string const& scriptname) const; + public: /* Unloading */ void Unload(); @@ -880,7 +889,7 @@ class TC_GAME_API ScriptMgr void CreateSpellScripts(uint32 spellId, std::list& scriptVector); void CreateAuraScripts(uint32 spellId, std::list& scriptVector); - void CreateSpellScriptLoaders(uint32 spellId, std::vector::iterator> >& scriptVector); + SpellScriptLoader* GetSpellScriptLoader(uint32 scriptId); public: /* ServerScript */ @@ -1093,21 +1102,14 @@ class TC_GAME_API ScriptMgr void ModifyMeleeDamage(Unit* target, Unit* attacker, uint32& damage); void ModifySpellDamageTaken(Unit* target, Unit* attacker, int32& damage); - public: /* Scheduled scripts */ - - uint64 IncreaseScheduledScriptsCount() { return ++_scheduledScripts; } - uint64 DecreaseScheduledScriptCount() { return --_scheduledScripts; } - uint64 DecreaseScheduledScriptCount(uint64 count) { return _scheduledScripts -= count; } - bool IsScriptScheduled() const { return _scheduledScripts > 0; } - private: - uint32 _scriptCount; - //atomic op counter for active scripts amount - std::atomic _scheduledScripts; - ScriptLoaderCallbackType _script_loader_callback; + + std::string _currentContext; }; +#define sScriptMgr ScriptMgr::instance() + #endif diff --git a/src/server/game/Scripting/ScriptReloadMgr.cpp b/src/server/game/Scripting/ScriptReloadMgr.cpp new file mode 100644 index 0000000000000..66a1081cf4671 --- /dev/null +++ b/src/server/game/Scripting/ScriptReloadMgr.cpp @@ -0,0 +1,1450 @@ +/* + * Copyright (C) 2008-2016 TrinityCore + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "ScriptReloadMgr.h" +#include "Errors.h" + +#ifndef TRINITY_API_USE_DYNAMIC_LINKING + +// This method should never be called +std::shared_ptr + ScriptReloadMgr::AcquireModuleReferenceOfContext(std::string const& context) +{ + WPAbort(); +} + +// Returns the empty implemented ScriptReloadMgr +ScriptReloadMgr* ScriptReloadMgr::instance() +{ + static ScriptReloadMgr instance; + return &instance; +} + +#else + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "efsw/efsw.hpp" + +#include "Log.h" +#include "Config.h" +#include "BuiltInConfig.h" +#include "ScriptMgr.h" +#include "StartProcess.h" +#include "MPSCQueue.h" +#include "GitRevision.h" + +namespace fs = boost::filesystem; + +#ifdef _WIN32 + #include +#else // Posix + #include +#endif + +// Promote the sScriptReloadMgr to a HotSwapScriptReloadMgr +// in this compilation unit. +#undef sScriptReloadMgr +#define sScriptReloadMgr static_cast(ScriptReloadMgr::instance()) + +// Returns "" on Windows and "lib" on posix. +static char const* GetSharedLibraryPrefix() +{ +#ifdef _WIN32 + return ""; +#else // Posix + return "lib"; +#endif +} + +// Returns "dll" on Windows and "so" on posix. +static char const* GetSharedLibraryExtension() +{ +#ifdef _WIN32 + return "dll"; +#else // Posix + return "so"; +#endif +} + +#ifdef _WIN32 +typedef HMODULE HandleType; +#else // Posix +typedef void* HandleType; +#endif + +class SharedLibraryUnloader +{ +public: + SharedLibraryUnloader() + : _path() { } + explicit SharedLibraryUnloader(fs::path const& path) + : _path(path) { } + + void operator() (HandleType handle) const + { + // Unload the associated shared library. +#ifdef _WIN32 + bool success = (FreeLibrary(handle) != 0); +#else // Posix + bool success = (dlclose(handle) == 0); +#endif + + if (!success) + { + TC_LOG_ERROR("scripts.hotswap", "Failed to close the shared library \"%s\".", + _path.generic_string().c_str()); + + return; + } + + boost::system::error_code error; + if (fs::remove(_path, error)) + { + TC_LOG_TRACE("scripts.hotswap", "Lazy unloaded and deleted the shared library \"%s\".", + _path.generic_string().c_str()); + } + else + { + TC_LOG_ERROR("scripts.hotswap", "Failed to delete the shared library \"%s\".", + _path.generic_string().c_str()); + } + + (void)error; + } + +private: + fs::path _path; +}; + +typedef std::unique_ptr::type, SharedLibraryUnloader> HandleHolder; + +typedef char const* (*GetScriptModuleRevisionHashType)(); +typedef void (*AddScriptsType)(); +typedef char const* (*GetScriptModuleType)(); +typedef char const* (*GetBuildDirectiveType)(); + +class ScriptModule + : public ModuleReference +{ +public: + explicit ScriptModule(HandleHolder handle, GetScriptModuleRevisionHashType getScriptModuleRevisionHash, + AddScriptsType addScripts, GetScriptModuleType getScriptModule, + GetBuildDirectiveType getBuildDirective, fs::path const& path) + : _handle(std::forward(handle)), _getScriptModuleRevisionHash(getScriptModuleRevisionHash), + _addScripts(addScripts), _getScriptModule(getScriptModule), + _getBuildDirective(getBuildDirective), _path(path) { } + + ScriptModule(ScriptModule const&) = delete; + ScriptModule(ScriptModule&& right) = delete; + + ScriptModule& operator= (ScriptModule const&) = delete; + ScriptModule& operator= (ScriptModule&& right) = delete; + + static Optional> CreateFromPath(fs::path const& path); + + char const* GetScriptModuleRevisionHash() const override + { + return _getScriptModuleRevisionHash(); + } + + void AddScripts() const + { + return _addScripts(); + } + + char const* GetScriptModule() const override + { + return _getScriptModule(); + } + + char const* GetBuildDirective() const + { + return _getBuildDirective(); + } + + fs::path const& GetModulePath() const override + { + return _path; + } + +private: + HandleHolder _handle; + + GetScriptModuleRevisionHashType _getScriptModuleRevisionHash; + AddScriptsType _addScripts; + GetScriptModuleType _getScriptModule; + GetBuildDirectiveType _getBuildDirective; + + fs::path _path; +}; + +template +static bool GetFunctionFromSharedLibrary(HandleType handle, std::string const& name, Fn& fn) +{ +#ifdef _WIN32 + fn = reinterpret_cast(GetProcAddress(handle, name.c_str())); +#else // Posix + fn = reinterpret_cast(dlsym(handle, name.c_str())); +#endif + return fn != nullptr; +} + +// Load a shared library from the given path. +Optional> ScriptModule::CreateFromPath(fs::path const& path) +{ +#ifdef _WIN32 + HandleType handle = LoadLibrary(path.generic_string().c_str()); +#else // Posix + HandleType handle = dlopen(path.c_str(), RTLD_LAZY); +#endif + + if (!handle) + { + TC_LOG_ERROR("scripts.hotswap", "Could not load the shared library \"%s\" for reading.", + path.generic_string().c_str()); + return boost::none; + } + + // Use RAII to release the library on failure. + HandleHolder holder(handle, SharedLibraryUnloader(path)); + + GetScriptModuleRevisionHashType getScriptModuleRevisionHash; + AddScriptsType addScripts; + GetScriptModuleType getScriptModule; + GetBuildDirectiveType getBuildDirective; + + if (GetFunctionFromSharedLibrary(handle, "GetScriptModuleRevisionHash", getScriptModuleRevisionHash) && + GetFunctionFromSharedLibrary(handle, "AddScripts", addScripts) && + GetFunctionFromSharedLibrary(handle, "GetScriptModule", getScriptModule) && + GetFunctionFromSharedLibrary(handle, "GetBuildDirective", getBuildDirective)) + return std::make_shared(std::move(holder), getScriptModuleRevisionHash, + addScripts, getScriptModule, getBuildDirective, path); + else + { + TC_LOG_ERROR("scripts.hotswap", "Could not extract all required functions from the shared library \"%s\"!", + path.generic_string().c_str()); + + return boost::none; + } +} + +static bool HasValidScriptModuleName(std::string const& name) +{ + // Detects scripts_NAME.dll's / .so's + static std::regex const regex( + Trinity::StringFormat("^%s[sS]cripts_[a-zA-Z0-9_]+\\.%s$", + GetSharedLibraryPrefix(), + GetSharedLibraryExtension())); + + return std::regex_match(name, regex); +} + +class LibraryUpdateListener : public efsw::FileWatchListener +{ +public: + LibraryUpdateListener() { } + virtual ~LibraryUpdateListener() { } + + void handleFileAction(efsw::WatchID /*watchid*/, std::string const& dir, + std::string const& filename, efsw::Action action, std::string oldFilename = "") final override; +}; + +static LibraryUpdateListener libraryUpdateListener; + +class SourceUpdateListener : public efsw::FileWatchListener +{ +public: + SourceUpdateListener() { } + virtual ~SourceUpdateListener() { } + + void handleFileAction(efsw::WatchID /*watchid*/, std::string const& dir, + std::string const& filename, efsw::Action action, std::string oldFilename = "") final override; +}; + +static SourceUpdateListener sourceUpdateListener; + +namespace std +{ + template <> + struct hash + { + hash hasher; + + std::size_t operator()(fs::path const& key) const + { + return hasher(key.generic_string()); + } + }; +} + +/// Quotes the given string +static std::string CMakeStringify(std::string const& str) +{ + return Trinity::StringFormat("\"%s\"", str); +} + +/// Invokes a synchronous CMake command action +static int InvokeCMakeCommand(std::vector const& args) +{ + return Trinity::StartProcess(BuiltInConfig::GetCMakeCommand(), args, "scripts.hotswap"); +} + +/// Invokes an asynchronous CMake command action +static std::shared_ptr InvokeAsyncCMakeCommand(std::vector args) +{ + return Trinity::StartAsyncProcess(BuiltInConfig::GetCMakeCommand(), + std::move(args), "scripts.hotswap"); +} + +/// Calculates the C++ project name of the given module which is +/// the lowercase string of scripts_${module}. +static std::string CalculateScriptModuleProjectName(std::string const& module) +{ + std::string module_project = "scripts_" + module; + std::transform(module_project.begin(), module_project.end(), + module_project.begin(), ::tolower); + + return module_project; +} + +/// ScriptReloadMgr which is used when dynamic linking is enabled +/// +/// This class manages shared library loading/unloading through watching +/// the script module directory. Loaded shared libraries are mirrored +/// into a .cache subdirectory to allow lazy unloading as long as +/// the shared library is still used which is useful for scripts +/// which can't be instantly replaced like spells or instances. +/// Several modules which reference different versions can be kept loaded +/// to serve scripts of different versions to entities and spells. +/// +/// Also this class invokes rebuilds as soon as the source of loaded +/// scripts change and installs the modules correctly through CMake. +class HotSwapScriptReloadMgr final + : public ScriptReloadMgr +{ + friend class ScriptReloadMgr; + + /// Reflects a queued change on a shared library or shared library + /// which is waiting for processing + enum class ChangeStateRequest : uint8 + { + CHANGE_REQUEST_ADDED, + CHANGE_REQUEST_MODIFIED, + CHANGE_REQUEST_REMOVED + }; + + /// Reflects a running job of an invoked asynchronous external process + enum class BuildJobType : uint8 + { + BUILD_JOB_NONE, + BUILD_JOB_RERUN_CMAKE, + BUILD_JOB_COMPILE, + BUILD_JOB_INSTALL, + }; + + // Represents a job which was invoked through a source or shared library change + class BuildJob + { + // Script module which is processed in the current running job + std::string script_module_name_; + // The C++ project name of the module which is processed + std::string script_module_project_name_; + // The build directive of the current module which is processed + // like "Release" or "Debug". The build directive from the + // previous same module is used if there was any. + std::string script_module_build_directive_; + + // Type of the current running job + BuildJobType type_; + // The async process result of the current job + std::shared_ptr async_result_; + + public: + explicit BuildJob(std::string script_module_name, std::string script_module_project_name, + std::string script_module_build_directive) + : script_module_name_(std::move(script_module_name)), + script_module_project_name_(std::move(script_module_project_name)), + script_module_build_directive_(std::move(script_module_build_directive)), + type_(BuildJobType::BUILD_JOB_NONE) { } + + bool IsValid() const + { + return type_ != BuildJobType::BUILD_JOB_NONE; + } + + std::string const& GetModuleName() const { return script_module_name_; } + + std::string const& GetProjectName() const { return script_module_project_name_; } + + std::string const& GetBuildDirective() const { return script_module_build_directive_; } + + BuildJobType GetType() const { return type_; } + + std::shared_ptr const& GetProcess() const + { + ASSERT(async_result_, "Tried to access an empty process handle!"); + return async_result_; + } + + /// Updates the current running job with the given type and async result + void UpdateCurrentJob(BuildJobType type, + std::shared_ptr async_result) + { + ASSERT(type != BuildJobType::BUILD_JOB_NONE, "None isn't allowed here!"); + ASSERT(async_result, "The async result must not be empty!"); + + type_ = type; + async_result_ = std::move(async_result); + } + }; + + /// Base class for lockfree asynchronous messages to the script reloader + class ScriptReloaderMessage + { + public: + virtual ~ScriptReloaderMessage() { } + + /// Invoke this function to run a message thread safe on the reloader + virtual void operator() (HotSwapScriptReloadMgr* reloader) = 0; + }; + + /// Implementation class which wraps functional types and dispatches + /// it in the overwritten implementation of the reloader messages. + template + class ScriptReloaderMessageImplementation + : public ScriptReloaderMessage + { + T dispatcher_; + + public: + explicit ScriptReloaderMessageImplementation(T dispatcher) + : dispatcher_(std::move(dispatcher)) { } + + void operator() (HotSwapScriptReloadMgr* reloader) final override + { + dispatcher_(reloader); + } + }; + + /// Uses the given functional type and creates a asynchronous reloader + /// message on the heap, which requires deletion. + template + auto MakeMessage(T&& dispatcher) + -> ScriptReloaderMessageImplementation::type>* + { + return new ScriptReloaderMessageImplementation::type> + (std::forward(dispatcher)); + } + +public: + HotSwapScriptReloadMgr() + : _libraryWatcher(0), _sourceWatcher(0), + _unique_library_name_counter(0), _last_time_library_changed(0), + _last_time_sources_changed(0), terminate_early(false) { } + + virtual ~HotSwapScriptReloadMgr() + { + // Delete all messages + ScriptReloaderMessage* message; + while (_messages.Dequeue(message)) + delete message; + } + + /// Returns the absolute path to the script module directory + static fs::path GetLibraryDirectory() + { + return fs::absolute(sConfigMgr->GetStringDefault("HotSwap.ScriptDir", "scripts")); + } + + /// Returns the absolute path to the scripts directory in the source tree. + static fs::path GetSourceDirectory() + { + return fs::absolute("src/server/scripts", BuiltInConfig::GetSourceDirectory()); + } + + /// Initializes the file watchers and loads all existing shared libraries + /// into the running server. + void Initialize() final override + { + if (!sWorld->getBoolConfig(CONFIG_HOTSWAP_ENABLED)) + return; + + { + auto const library_directory = GetLibraryDirectory(); + if (!fs::exists(library_directory) || !fs::is_directory(library_directory)) + { + TC_LOG_ERROR("scripts.hotswap", "Library directory \"%s\" doesn't exist!.", + library_directory.generic_string().c_str()); + return; + } + } + + // Get the cache directory path + fs::path const cache_path = [] + { + auto path = fs::absolute(sScriptReloadMgr->GetLibraryDirectory()); + path /= ".cache"; + return path; + }(); + + // We use the boost filesystem function versions which accept + // an error code to prevent it from throwing exceptions. + boost::system::error_code code; + if ((!fs::exists(cache_path, code) || (fs::remove_all(cache_path, code) > 0)) && + !fs::create_directory(cache_path, code)) + { + TC_LOG_ERROR("scripts.hotswap", "Couldn't create the cache directory \"%s\".", + cache_path.generic_string().c_str()); + return; + } + + // Used to silent compiler warnings + (void)code; + + // Correct the CMake prefix when needed + if (sWorld->getBoolConfig(CONFIG_HOTSWAP_PREFIX_CORRECTION_ENABLED)) + DoCMakePrefixCorrectionIfNeeded(); + + InitializeDefaultLibraries(); + InitializeFileWatchers(); + } + + /// Needs to be called periodically from the worldserver loop + /// to invoke queued actions like module loading/unloading and script + /// compilation. + /// This method should be invoked from a thread safe point to + /// prevent misbehavior. + void Update() final override + { + // Consume all messages + ScriptReloaderMessage* message; + while (_messages.Dequeue(message)) + { + (*message)(this); + delete message; + } + + DispatchRunningBuildJobs(); + DispatchModuleChanges(); + } + + /// Unloads the manager and cancels all runnings jobs immediately + void Unload() final override + { + if (_libraryWatcher) + { + _fileWatcher.removeWatch(_libraryWatcher); + _libraryWatcher = 0; + } + + if (_sourceWatcher) + { + _fileWatcher.removeWatch(_sourceWatcher); + _sourceWatcher = 0; + } + + // If a build is in progress cancel it + if (_build_job) + { + _build_job->GetProcess()->Terminate(); + _build_job.reset(); + } + + // Release all strong references to script modules + // to trigger unload actions as early as possible, + // otherwise the worldserver will crash on exit. + _running_script_modules.clear(); + } + + /// Queue's a thread safe message to the reloader which is executed on + /// the next world server update tick. + template + void QueueMessage(T&& message) + { + _messages.Enqueue(MakeMessage(std::forward(message))); + } + + /// Queues an action which marks the given shared library as changed + /// which will add, unload or reload it at the next world update tick. + /// This method is thread safe. + void QueueSharedLibraryChanged(fs::path const& path) + { + _last_time_library_changed = getMSTime(); + _libraries_changed.insert(path); + } + + /// Queues a notification that a source file was added + /// This method is thread unsafe. + void QueueAddSourceFile(std::string const& module_name, fs::path const& path) + { + UpdateSourceChangeRequest(module_name, path, ChangeStateRequest::CHANGE_REQUEST_ADDED); + } + + /// Queues a notification that a source file was modified + /// This method is thread unsafe. + void QueueModifySourceFile(std::string const& module_name, fs::path const& path) + { + UpdateSourceChangeRequest(module_name, path, ChangeStateRequest::CHANGE_REQUEST_MODIFIED); + } + + /// Queues a notification that a source file was removed + /// This method is thread unsafe. + void QueueRemoveSourceFile(std::string const& module_name, fs::path const& path) + { + UpdateSourceChangeRequest(module_name, path, ChangeStateRequest::CHANGE_REQUEST_REMOVED); + } + + /// Returns true when the given module name is tracked + bool HasModuleToTrack(std::string const& module) const + { + return _modules_to_track.find(module) == _modules_to_track.end(); + } + +private: + // Loads all shared libraries which are contained in the + // scripts directory on startup. + void InitializeDefaultLibraries() + { + fs::path const libraryDirectory(GetLibraryDirectory()); + fs::directory_iterator const dir_end; + + uint32 count = 0; + + // Iterate through all shared libraries in the script directory and load it + for (fs::directory_iterator dir_itr(libraryDirectory); dir_itr != dir_end ; ++dir_itr) + if (fs::is_regular_file(dir_itr->path()) && HasValidScriptModuleName(dir_itr->path().filename().generic_string())) + { + TC_LOG_INFO("scripts.hotswap", "Loading script module \"%s\"...", + dir_itr->path().filename().generic_string().c_str()); + + // Don't swap the script context to do bulk loading + ProcessLoadScriptModule(dir_itr->path(), false); + ++count; + } + + TC_LOG_INFO("scripts.hotswap", ">> Loaded %u script modules.", count); + } + + // Initialize all enabled file watchers. + // Needs to be called after InitializeDefaultLibraries()! + void InitializeFileWatchers() + { + _libraryWatcher = _fileWatcher.addWatch(GetLibraryDirectory().generic_string(), &libraryUpdateListener, false); + if (_libraryWatcher) + { + TC_LOG_INFO("scripts.hotswap", ">> Library reloader is listening on \"%s\".", + GetLibraryDirectory().generic_string().c_str()); + } + else + { + TC_LOG_ERROR("scripts.hotswap", "Failed to initialize the library reloader on \"%s\".", + GetLibraryDirectory().generic_string().c_str()); + } + + std::string const scriptsPath = GetSourceDirectory().generic_string(); + + _sourceWatcher = _fileWatcher.addWatch(scriptsPath, &sourceUpdateListener, true); + if (_sourceWatcher) + { + TC_LOG_INFO("scripts.hotswap", ">> Source recompiler is recursively listening on \"%s\".", + scriptsPath.c_str()); + } + else + { + TC_LOG_ERROR("scripts.hotswap", "Failed to initialize the script reloader on \"%s\".", + scriptsPath.c_str()); + } + + _fileWatcher.watch(); + } + + /// Updates the current state of the given source path + void UpdateSourceChangeRequest(std::string const& module_name, + fs::path const& path, + ChangeStateRequest state) + { + _last_time_sources_changed = getMSTime(); + + // Write when there is no module with the given name known + auto module_itr = _sources_changed.find(module_name); + + // When the file was modified it's enough to mark the module as + // dirty by initializing the associated map. + if (module_itr == _sources_changed.end()) + module_itr = _sources_changed.insert(std::make_pair( + module_name, decltype(_sources_changed)::mapped_type{})).first; + + // Leave when the file was just modified as explained above + if (state == ChangeStateRequest::CHANGE_REQUEST_MODIFIED) + return; + + // Insert when the given path isn't existent + auto const itr = module_itr->second.find(path); + if (itr == module_itr->second.end()) + { + module_itr->second.insert(std::make_pair(path, state)); + return; + } + + ASSERT((itr->second == ChangeStateRequest::CHANGE_REQUEST_ADDED) + || (itr->second == ChangeStateRequest::CHANGE_REQUEST_REMOVED), + "Stored value is invalid!"); + + ASSERT((state == ChangeStateRequest::CHANGE_REQUEST_ADDED) + || (state == ChangeStateRequest::CHANGE_REQUEST_REMOVED), + "The given state is invalid!"); + + ASSERT(state != itr->second, + "Tried to apply a state which is stored already!"); + + module_itr->second.erase(itr); + } + + /// Called periodically on the worldserver tick to process all + /// load/unload/reload requests of shared libraries. + void DispatchModuleChanges() + { + // When there are no libraries to change return + if (_libraries_changed.empty()) + return; + + // Wait some time after changes to catch bulk changes + if (GetMSTimeDiffToNow(_last_time_library_changed) < 500) + return; + + for (auto const& path : _libraries_changed) + { + bool const is_running = + _running_script_module_names.find(path) != _running_script_module_names.end(); + + bool const exists = fs::exists(path); + + if (is_running) + { + if (exists) + ProcessReloadScriptModule(path); + else + ProcessUnloadScriptModule(path); + } + else if (exists) + ProcessLoadScriptModule(path); + } + + _libraries_changed.clear(); + } + + void ProcessLoadScriptModule(fs::path const& path, bool swap_context = true) + { + ASSERT(_running_script_module_names.find(path) == _running_script_module_names.end(), + "Can't load a module which is running already!"); + + // Create the cache path and increment the library counter to use an unique name for each library + fs::path cache_path = fs::absolute(sScriptReloadMgr->GetLibraryDirectory()); + cache_path /= ".cache"; + cache_path /= Trinity::StringFormat("%s.%u%s", + path.stem().generic_string().c_str(), + _unique_library_name_counter++, + path.extension().generic_string().c_str()); + + if ([&] + { + boost::system::error_code code; + fs::copy_file(path, cache_path, fs::copy_option::fail_if_exists, code); + return code; + }()) + { + TC_LOG_FATAL("scripts.hotswap", ">> Failed to create cache entry for module \"%s\"!", + path.filename().generic_string().c_str()); + + // Find a better solution for this but it's much better + // to start the core without scripts + std::this_thread::sleep_for(std::chrono::seconds(5)); + ABORT(); + return; + } + + auto module = ScriptModule::CreateFromPath(cache_path); + if (!module) + { + TC_LOG_FATAL("scripts.hotswap", ">> Failed to load script module \"%s\"!", + path.filename().generic_string().c_str()); + + // Find a better solution for this but it's much better + // to start the core without scripts + std::this_thread::sleep_for(std::chrono::seconds(5)); + ABORT(); + return; + } + + // Limit the git revision hash to 7 characters. + std::string module_revision((*module)->GetScriptModuleRevisionHash()); + if (module_revision.size() >= 7) + module_revision = module_revision.substr(0, 7); + + std::string const module_name = (*module)->GetScriptModule(); + TC_LOG_INFO("scripts.hotswap", ">> Loaded script module \"%s\" (\"%s\" - %s).", + path.filename().generic_string().c_str(), module_name.c_str(), module_revision.c_str()); + + if (module_revision.empty()) + { + TC_LOG_WARN("scripts.hotswap", ">> Script module \"%s\" has an empty revision hash!", + path.filename().generic_string().c_str()); + } + else + { + // Trim the revision hash + std::string my_revision_hash = GitRevision::GetHash(); + std::size_t const trim = std::min(module_revision.size(), my_revision_hash.size()); + my_revision_hash = my_revision_hash.substr(0, trim); + module_revision = module_revision.substr(0, trim); + + if (my_revision_hash != module_revision) + { + TC_LOG_WARN("scripts.hotswap", ">> Script module \"%s\" has a different revision hash! " + "Binary incompatibility could lead to unknown behaviour!", path.filename().generic_string().c_str()); + } + } + + { + auto const itr = _running_script_modules.find(module_name); + if (itr != _running_script_modules.end()) + { + TC_LOG_ERROR("scripts.hotswap", ">> Attempt to load a module twice \"%s\" (loaded module is at %s)!", + path.generic_string().c_str(), itr->second->GetModulePath().generic_string().c_str()); + + return; + } + } + + sScriptMgr->SetScriptContext(module_name); + (*module)->AddScripts(); + TC_LOG_TRACE("scripts.hotswap", ">> Registered all scripts of module %s.", module_name.c_str()); + + if (swap_context) + sScriptMgr->SwapScriptContext(); + + _modules_to_track.insert(module_name); + + // Store the module + _known_modules_build_directives.insert(std::make_pair(module_name, (*module)->GetBuildDirective())); + _running_script_modules.insert(std::make_pair(module_name, std::move(*module))); + _running_script_module_names.insert(std::make_pair(path, module_name)); + } + + void ProcessReloadScriptModule(fs::path const& path) + { + ProcessUnloadScriptModule(path, false); + ProcessLoadScriptModule(path); + } + + void ProcessUnloadScriptModule(fs::path const& path, bool finish = true) + { + auto const itr = _running_script_module_names.find(path); + + ASSERT(itr != _running_script_module_names.end(), + "Can't unload a module which isn't running!"); + + // Unload the script context + sScriptMgr->ReleaseScriptContext(itr->second); + + if (finish) + sScriptMgr->SwapScriptContext(); + + _modules_to_track.erase(itr->second); + + TC_LOG_INFO("scripts.hotswap", "Released script module \"%s\" (\"%s\")...", + path.filename().generic_string().c_str(), itr->second.c_str()); + + // Unload the script module + auto ref = _running_script_modules.find(itr->second); + ASSERT(ref != _running_script_modules.end() && + "Expected the script reference to be present!"); + + // Yield a message when there are other owning references to + // the module which prevents it from unloading. + // The module will be unloaded once all scripts provided from the module + // are destroyed. + if (!ref->second.unique()) + { + TC_LOG_INFO("scripts.hotswap", + "Script module %s is still used by %lu scripts. " + "Will lazy unload the module once all scripts stopped using it.", + ref->second->GetScriptModule(), ref->second.use_count() - 1); + } + + // Remove the owning reference from the reloader + _running_script_modules.erase(ref); + _running_script_module_names.erase(itr); + } + + /// Called periodically on the worldserver tick to process all recompile + /// requests. This method invokes one build or install job at the time + void DispatchRunningBuildJobs() + { + if (_build_job) + { + // Terminate the current build job when an associated source was changed + // while compiling and the terminate early option is enabled. + if (sWorld->getBoolConfig(CONFIG_HOTSWAP_EARLY_TERMINATION_ENABLED)) + { + if (!terminate_early && _sources_changed.find(_build_job->GetModuleName()) != _sources_changed.end()) + { + /* + FIXME: Currently crashes the server + TC_LOG_INFO("scripts.hotswap", "Terminating the running build of module \"%s\"...", + _build_job->GetModuleName().c_str()); + + _build_job->GetProcess()->Terminate(); + _build_job.reset(); + + // Continue with the default execution path + DispatchRunningBuildJobs(); + return; + */ + + terminate_early = true; + return; + } + } + + // Wait for the current build job to finish, if the job finishes in time + // evaluate it and continue with the next one. + if (_build_job->GetProcess()->GetFutureResult(). + wait_for(std::chrono::seconds(0)) == std::future_status::ready) + ProcessReadyBuildJob(); + else + return; // Return when the job didn't finish in time + + // Skip this cycle when the previous job scheduled a new one + if (_build_job) + return; + } + + // Avoid burst updates through waiting for a short time after changes + if ((_last_time_sources_changed != 0) && + (GetMSTimeDiffToNow(_last_time_sources_changed) < 500)) + return; + + // If the changed sources are empty do nothing + if (_sources_changed.empty()) + return; + + // Find all source files of a changed script module and removes + // it from the changed source list, invoke the build afterwards. + bool rebuild_buildfiles; + auto module_name = [&] + { + auto itr = _sources_changed.begin(); + auto name = itr->first; + rebuild_buildfiles = !itr->second.empty(); + _sources_changed.erase(itr); + return name; + }(); + + // Erase the added delete history all modules when we + // invoke a cmake rebuild since we add all + // added files of other modules to the build as well + if (rebuild_buildfiles) + { + for (auto& entry : _sources_changed) + entry.second.clear(); + } + + ASSERT(!module_name.empty(), + "The current module name is invalid!"); + + TC_LOG_INFO("scripts.hotswap", "Recompiling Module \"%s\"...", + module_name.c_str()); + + // Calculate the project name of the script module + auto project_name = CalculateScriptModuleProjectName(module_name); + + // Find the best build directive for the module + auto build_directive = [&] () -> std::string + { + auto directive = sConfigMgr->GetStringDefault("HotSwap.ReCompilerBuildType", ""); + if (!directive.empty()) + return std::move(directive); + + auto const itr = _known_modules_build_directives.find(module_name); + if (itr != _known_modules_build_directives.end()) + return itr->second; + else // If no build directive of the module was found use the one from the game library + return _BUILD_DIRECTIVE; + }(); + + // Initiate the new build job + _build_job = BuildJob(std::move(module_name), + std::move(project_name), std::move(build_directive)); + + // Rerun CMake when we need to recreate the build files + if (rebuild_buildfiles + && sWorld->getBoolConfig(CONFIG_HOTSWAP_BUILD_FILE_RECREATION_ENABLED)) + DoRerunCMake(); + else + DoCompileCurrentProcessedModule(); + } + + void ProcessReadyBuildJob() + { + ASSERT(_build_job->IsValid(), "Invalid build job!"); + + // Retrieve the result + auto const error = _build_job->GetProcess()->GetFutureResult().get(); + + if (terminate_early) + { + _build_job.reset(); + terminate_early = false; + return; + } + + switch (_build_job->GetType()) + { + case BuildJobType::BUILD_JOB_RERUN_CMAKE: + { + if (!error) + { + TC_LOG_INFO("scripts.hotswap", ">> Successfully updated the build files!"); + } + else + { + TC_LOG_INFO("scripts.hotswap", ">> Failed to update the build files at \"%s\", " + "it's possible that recently added sources are not included " + "in your next builds, rerun CMake manually.", + BuiltInConfig::GetBuildDirectory().c_str()); + } + // Continue with building the changes sources + DoCompileCurrentProcessedModule(); + return; + } + case BuildJobType::BUILD_JOB_COMPILE: + { + if (!error) // Build was successful + { + if (sWorld->getBoolConfig(CONFIG_HOTSWAP_INSTALL_ENABLED)) + { + // Continue with the installation when it's enabled + TC_LOG_INFO("scripts.hotswap", + ">> Successfully build module %s, continue with installing...", + _build_job->GetModuleName().c_str()); + + DoInstallCurrentProcessedModule(); + return; + } + + // Skip the installation because it's disabled in config + TC_LOG_INFO("scripts.hotswap", + ">> Successfully build module %s, skipped the installation.", + _build_job->GetModuleName().c_str()); + } + else // Build wasn't successful + { + TC_LOG_ERROR("scripts.hotswap", + ">> The build of module %s failed! See the log for details.", + _build_job->GetModuleName().c_str()); + } + break; + } + case BuildJobType::BUILD_JOB_INSTALL: + { + if (!error) + { + // Installation was successful + TC_LOG_INFO("scripts.hotswap", ">> Successfully installed module %s.", + _build_job->GetModuleName().c_str()); + } + else + { + // Installation wasn't successful + TC_LOG_INFO("scripts.hotswap", + ">> The installation of module %s failed! See the log for details.", + _build_job->GetModuleName().c_str()); + } + break; + } + default: + break; + } + + // Clear the current job + _build_job.reset(); + } + + /// Reruns CMake asynchronously over the build directory + void DoRerunCMake() + { + ASSERT(_build_job, "There isn't any active build job!"); + + TC_LOG_INFO("scripts.hotswap", "Rerunning CMake because there were sources added or removed..."); + + _build_job->UpdateCurrentJob(BuildJobType::BUILD_JOB_RERUN_CMAKE, + InvokeAsyncCMakeCommand({ + "cmake", + CMakeStringify(BuiltInConfig::GetBuildDirectory()) + })); + } + + /// Invokes a new build of the current active module job + void DoCompileCurrentProcessedModule() + { + ASSERT(_build_job, "There isn't any active build job!"); + + TC_LOG_INFO("scripts.hotswap", "Starting asynchronous build job for module %s...", + _build_job->GetModuleName().c_str()); + + _build_job->UpdateCurrentJob(BuildJobType::BUILD_JOB_COMPILE, + InvokeAsyncCMakeCommand({ + "cmake", + "--build", CMakeStringify(BuiltInConfig::GetBuildDirectory()), + "--target", CMakeStringify(_build_job->GetProjectName()), + "--config", CMakeStringify(_build_job->GetBuildDirective()) + })); + } + + /// Invokes a new asynchronous install of the current active module job + void DoInstallCurrentProcessedModule() + { + ASSERT(_build_job, "There isn't any active build job!"); + + TC_LOG_INFO("scripts.hotswap", "Starting asynchronous install job for module %s...", + _build_job->GetModuleName().c_str()); + + _build_job->UpdateCurrentJob(BuildJobType::BUILD_JOB_INSTALL, + InvokeAsyncCMakeCommand({ + "cmake", + "-DCOMPONENT=" + CMakeStringify(_build_job->GetProjectName()), + "-DBUILD_TYPE=" + CMakeStringify(_build_job->GetBuildDirective()), + "-P", CMakeStringify(fs::absolute("cmake_install.cmake", + BuiltInConfig::GetBuildDirectory()).generic_string()) + })); + } + + /// Sets the CMAKE_INSTALL_PREFIX variable in the CMake cache + /// to point to the current worldserver position, + /// since most users will forget this. + void DoCMakePrefixCorrectionIfNeeded() + { + TC_LOG_INFO("scripts.hotswap", "Correcting your CMAKE_INSTALL_PREFIX in \"%s\"...", + BuiltInConfig::GetBuildDirectory().c_str()); + + auto const cmake_cache_path = fs::absolute("CMakeCache.txt", + BuiltInConfig::GetBuildDirectory()); + + // Stop when the CMakeCache wasn't found + if (![&] + { + boost::system::error_code error; + if (!fs::exists(cmake_cache_path, error)) + { + TC_LOG_ERROR("scripts.hotswap", ">> CMake cache \"%s\" doesn't exist, " + "set the \"BuildDirectory\" option in your worldserver.conf to point" + "to your build directory!", + cmake_cache_path.generic_string().c_str()); + + return false; + } + else + return true; + }()) + return; + + TC_LOG_TRACE("scripts.hotswap", "Checking CMake cache (\"%s\") " + "for the correct CMAKE_INSTALL_PREFIX location...", + cmake_cache_path.generic_string().c_str()); + + std::string cmake_cache_content; + { + std::ifstream in(cmake_cache_path.generic_string()); + if (!in.is_open()) + { + TC_LOG_ERROR("scripts.hotswap", ">> Failed to read the CMake cache at \"%s\"!", + cmake_cache_path.generic_string().c_str()); + + return; + } + + std::ostringstream ss; + ss << in.rdbuf(); + cmake_cache_content = ss.str(); + + in.close(); + } + + static std::string const prefix_key = "CMAKE_INSTALL_PREFIX:PATH="; + + // Extract the value of CMAKE_INSTALL_PREFIX + auto begin = cmake_cache_content.find(prefix_key); + if (begin != std::string::npos) + { + begin += prefix_key.length(); + auto const end = cmake_cache_content.find("\n", begin); + if (end != std::string::npos) + { + fs::path const value = cmake_cache_content.substr(begin, end - begin); + if (value != fs::current_path()) + { + // Prevent correction of the install prefix + // when we are starting the core from inside the build tree + bool const is_in_path = [&] + { + fs::path base = BuiltInConfig::GetBuildDirectory(); + fs::path branch = value; + while (!branch.empty()) + { + if (base == branch) + return true; + + branch = branch.remove_leaf(); + } + + return false; + }(); + + if (is_in_path) + return; + + TC_LOG_INFO("scripts.hotswap", ">> Found outdated CMAKE_INSTALL_PREFIX (\"%s\")...", + value.generic_string().c_str()); + } + else + { + TC_LOG_INFO("scripts.hotswap", ">> CMAKE_INSTALL_PREFIX is equal to the current path of execution."); + return; + } + } + } + + TC_LOG_INFO("scripts.hotswap", "Invoking CMake cache correction..."); + + auto const error = InvokeCMakeCommand({ + "cmake", + "-DCMAKE_INSTALL_PREFIX:PATH=" + CMakeStringify(fs::current_path().generic_string()), + CMakeStringify(BuiltInConfig::GetBuildDirectory()) + }); + + if (error) + { + TC_LOG_ERROR("scripts.hotswap", ">> Failed to update the CMAKE_INSTALL_PREFIX! " + "This could lead to unexpected behaviour!"); + } + else + { + TC_LOG_ERROR("scripts.hotswap", ">> Successfully corrected your CMAKE_INSTALL_PREFIX variable" + "to point at your current path of execution."); + } + } + + // File watcher instance and watcher ID's + efsw::FileWatcher _fileWatcher; + efsw::WatchID _libraryWatcher; + efsw::WatchID _sourceWatcher; + + // Unique library name counter which is used to + // generate unique names for every shared library version. + uint32 _unique_library_name_counter; + + // Queue which is used for thread safe message processing + MPSCQueue _messages; + + // Change requests to load or unload shared libraries + std::unordered_set _libraries_changed; + // The timestamp which indicates the last time a library was changed + uint32 _last_time_library_changed; + + // Contains all running script modules + // The associated shared libraries are unloaded immediately + // on loosing ownership through RAII. + std::unordered_map> _running_script_modules; + // Container which maps the path of a shared library to it's module name + std::unordered_map _running_script_module_names; + // Container which maps the module name to it's last known build directive + std::unordered_map _known_modules_build_directives; + + // Modules which were changed and are queued for recompilation + std::unordered_map> _sources_changed; + // Tracks the time since the last module has changed to avoid burst updates + uint32 _last_time_sources_changed; + + // Script modules which are tracked by the reloader + // Any changes to modules not listed in this set are discarded. + std::unordered_set _modules_to_track; + + // Represents the current build job which is in progress + Optional _build_job; + + // Is true when the build job dispatcher should stop after + // the current job has finished + bool terminate_early; +}; + +void LibraryUpdateListener::handleFileAction(efsw::WatchID watchid, std::string const& dir, + std::string const& filename, efsw::Action action, std::string oldFilename) +{ + // TC_LOG_TRACE("scripts.hotswap", "Library listener detected change on possible module \"%s\".", filename.c_str()); + + // Split moved actions into a delete and an add action + if (action == efsw::Action::Moved) + { + ASSERT(!oldFilename.empty(), "Old filename doesn't exist!"); + handleFileAction(watchid, dir, oldFilename, efsw::Action::Delete); + handleFileAction(watchid, dir, filename, efsw::Action::Add); + return; + } + + sScriptReloadMgr->QueueMessage([=](HotSwapScriptReloadMgr* reloader) mutable + { + auto const path = fs::absolute( + filename, + sScriptReloadMgr->GetLibraryDirectory()); + + if (!HasValidScriptModuleName(filename)) + return; + + switch (action) + { + case efsw::Actions::Add: + TC_LOG_TRACE("scripts.hotswap", ">> Loading \"%s\"...", path.generic_string().c_str()); + reloader->QueueSharedLibraryChanged(path); + break; + case efsw::Actions::Delete: + TC_LOG_TRACE("scripts.hotswap", ">> Unloading \"%s\"...", path.generic_string().c_str()); + reloader->QueueSharedLibraryChanged(path); + break; + case efsw::Actions::Modified: + TC_LOG_TRACE("scripts.hotswap", ">> Reloading \"%s\"...", path.generic_string().c_str()); + reloader->QueueSharedLibraryChanged(path); + break; + default: + WPAbort(); + break; + } + }); +} + +/// Returns true when the given path has a known C++ file extension +static bool HasCXXSourceFileExtension(fs::path const& path) +{ + static std::regex const regex("^\\.(h|hpp|c|cc|cpp)$"); + return std::regex_match(path.extension().generic_string(), regex); +} + +void SourceUpdateListener::handleFileAction(efsw::WatchID watchid, std::string const& dir, + std::string const& filename, efsw::Action action, std::string oldFilename) +{ + // TC_LOG_TRACE("scripts.hotswap", "Source listener detected change on possible file \"%s\".", filename.c_str()); + + // Skip the file change notification if the recompiler is disabled + if (!sWorld->getBoolConfig(CONFIG_HOTSWAP_RECOMPILER_ENABLED)) + return; + + // Split moved actions into a delete and an add action + if (action == efsw::Action::Moved) + { + ASSERT(!oldFilename.empty(), "Old filename doesn't exist!"); + handleFileAction(watchid, dir, oldFilename, efsw::Action::Delete); + handleFileAction(watchid, dir, filename, efsw::Action::Add); + return; + } + + auto const path = fs::absolute( + filename, + sScriptReloadMgr->GetSourceDirectory()); + + // Check if the file is a C/C++ source file. + if (!path.has_extension() || !HasCXXSourceFileExtension(path)) + return; + + // The file watcher returns the path relative to the watched directory + // Check has a root directory + fs::path const relative(filename); + if (relative.begin() == relative.end()) + return; + + fs::path const root_directory = fs::absolute( + *relative.begin(), + sScriptReloadMgr->GetSourceDirectory()); + + if (!fs::is_directory(root_directory)) + return; + + std::string const module = root_directory.filename().generic_string(); + + /// Thread safe part + sScriptReloadMgr->QueueMessage([=](HotSwapScriptReloadMgr* reloader) + { + if (reloader->HasModuleToTrack(module)) + { + TC_LOG_TRACE("scripts.hotswap", "File %s (Module \"%s\") doesn't belong " + "to an observed module, skipped!", filename.c_str(), module.c_str()); + + return; + } + + TC_LOG_TRACE("scripts.hotswap", "Detected source change on module \"%s\", " + "queued for recompilation...", module.c_str()); + + switch (action) + { + case efsw::Actions::Add: + reloader->QueueAddSourceFile(module, path); + break; + case efsw::Actions::Delete: + reloader->QueueRemoveSourceFile(module, path); + break; + case efsw::Actions::Modified: + reloader->QueueModifySourceFile(module, path); + break; + default: + WPAbort(); + break; + } + }); +} + +// Returns the module reference of the given context +std::shared_ptr + ScriptReloadMgr::AcquireModuleReferenceOfContext(std::string const& context) +{ + auto const itr = sScriptReloadMgr->_running_script_modules.find(context); + if (itr != sScriptReloadMgr->_running_script_modules.end()) + return itr->second; + else + return { }; +} + +// Returns the full hot swap implemented ScriptReloadMgr +ScriptReloadMgr* ScriptReloadMgr::instance() +{ + static HotSwapScriptReloadMgr instance; + return &instance; +} + +#endif // #ifndef TRINITY_API_USE_DYNAMIC_LINKING diff --git a/src/server/game/Scripting/ScriptReloadMgr.h b/src/server/game/Scripting/ScriptReloadMgr.h new file mode 100644 index 0000000000000..f9b388f8eb05f --- /dev/null +++ b/src/server/game/Scripting/ScriptReloadMgr.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2008-2016 TrinityCore + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef SCRIPT_RELOADER_H +#define SCRIPT_RELOADER_H + +#include +#include +#include "Define.h" +#include + +/// Represents a strong reference to a dynamic library which +/// provides C++ scripts. As long as one reference to the library exists +/// the library is kept loaded in the server, which makes it possible to lazy +/// unload several script types on demand (like SpellScripts), and to +/// provide multiple versions of the same script to the script factories. +/// +/// Acquire a new reference through using: +/// `ScriptReloadMgr::AcquireModuleReferenceOfContext` +class ModuleReference +{ +public: + virtual ~ModuleReference() { } + + /// Returns the git revision hash of the referenced script module + virtual char const* GetScriptModuleRevisionHash() const = 0; + /// Returns the name of the referenced script module + virtual char const* GetScriptModule() const = 0; + /// Returns the path to the script module + virtual boost::filesystem::path const& GetModulePath() const = 0; +}; + +/// Provides the whole physical dynamic library unloading capability. +/// Loads, Reloads and Unloads dynamic libraries on changes and +/// informs the ScriptMgr about changes which were made. +/// The ScriptReloadMgr is also responsible for watching the source directory +/// and to invoke a build on changes. +class TC_GAME_API ScriptReloadMgr +{ +protected: + ScriptReloadMgr() { } + +public: + virtual ~ScriptReloadMgr() { } + + /// Initializes the ScriptReloadMgr + virtual void Initialize() { } + + /// Needs to be called periodically to check for updates on script modules. + /// Expects to be invoked in a thread safe way which means it's required that + /// the current thread is the only one which accesses the world data. + virtual void Update() { } + + /// Unloads the ScriptReloadMgr + virtual void Unload() { } + + /// Returns an owning reference to the current module of the given context + static std::shared_ptr AcquireModuleReferenceOfContext( + std::string const& context); + + /// Returns the unique ScriptReloadMgr singleton instance + static ScriptReloadMgr* instance(); +}; + +#define sScriptReloadMgr ScriptReloadMgr::instance() + +#endif // SCRIPT_RELOADER_H diff --git a/src/server/game/Spells/SpellScript.cpp b/src/server/game/Spells/SpellScript.cpp index cdc1b558d1819..c1eb798760be4 100644 --- a/src/server/game/Spells/SpellScript.cpp +++ b/src/server/game/Spells/SpellScript.cpp @@ -16,6 +16,7 @@ */ #include "Spell.h" +#include "ScriptMgr.h" #include "SpellAuras.h" #include "SpellScript.h" @@ -50,6 +51,12 @@ void _SpellScript::_Init(std::string const* scriptname, uint32 spellId) m_currentScriptState = SPELL_SCRIPT_STATE_NONE; m_scriptName = scriptname; m_scriptSpellId = spellId; + +#ifdef TRINITY_API_USE_DYNAMIC_LINKING + // Acquire a strong reference to the binary code + // to keep it loaded until all spells are destroyed. + m_moduleReference = sScriptMgr->AcquireModuleReferenceOfScriptName(*scriptname); +#endif // #ifndef TRINITY_API_USE_DYNAMIC_LINKING } std::string const* _SpellScript::_GetScriptName() const diff --git a/src/server/game/Spells/SpellScript.h b/src/server/game/Spells/SpellScript.h index aca3e074b9a64..97bbdf14231a8 100644 --- a/src/server/game/Spells/SpellScript.h +++ b/src/server/game/Spells/SpellScript.h @@ -22,6 +22,7 @@ #include "SharedDefines.h" #include "SpellAuraDefines.h" #include "Spell.h" +#include "ScriptReloadMgr.h" #include class Unit; @@ -105,6 +106,16 @@ class TC_GAME_API _SpellScript uint8 m_currentScriptState; std::string const* m_scriptName; uint32 m_scriptSpellId; + + private: + +#ifdef TRINITY_API_USE_DYNAMIC_LINKING + + // Strong reference to keep the binary code loaded + std::shared_ptr m_moduleReference; + +#endif // #ifndef TRINITY_API_USE_DYNAMIC_LINKING + public: // // SpellScript/AuraScript interface base diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 0c1be4097caba..c40ebb0835fc2 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -59,6 +59,7 @@ #include "PoolMgr.h" #include "GitRevision.h" #include "ScriptMgr.h" +#include "ScriptReloadMgr.h" #include "SkillDiscovery.h" #include "SkillExtraItems.h" #include "SmartAI.h" @@ -1438,6 +1439,14 @@ void World::LoadConfigSettings(bool reload) m_int_configs[CONFIG_BLACKMARKET_MAXAUCTIONS] = sConfigMgr->GetIntDefault("BlackMarket.MaxAuctions", 12); m_int_configs[CONFIG_BLACKMARKET_UPDATE_PERIOD] = sConfigMgr->GetIntDefault("BlackMarket.UpdatePeriod", 24); + // HotSwap + m_bool_configs[CONFIG_HOTSWAP_ENABLED] = sConfigMgr->GetBoolDefault("HotSwap.Enabled", true); + m_bool_configs[CONFIG_HOTSWAP_RECOMPILER_ENABLED] = sConfigMgr->GetBoolDefault("HotSwap.EnableReCompiler", true); + m_bool_configs[CONFIG_HOTSWAP_EARLY_TERMINATION_ENABLED] = sConfigMgr->GetBoolDefault("HotSwap.EnableEarlyTermination", true); + m_bool_configs[CONFIG_HOTSWAP_BUILD_FILE_RECREATION_ENABLED] = sConfigMgr->GetBoolDefault("HotSwap.EnableBuildFileRecreation", true); + m_bool_configs[CONFIG_HOTSWAP_INSTALL_ENABLED] = sConfigMgr->GetBoolDefault("HotSwap.EnableInstall", true); + m_bool_configs[CONFIG_HOTSWAP_PREFIX_CORRECTION_ENABLED] = sConfigMgr->GetBoolDefault("HotSwap.EnablePrefixCorrection", true); + // call ScriptMgr if we're reloading the configuration if (reload) sScriptMgr->OnConfigLoad(reload); @@ -2003,6 +2012,8 @@ void World::SetInitialWorldSettings() blackmarket_timer = 0; + m_timers[WUPDATE_CHECK_FILECHANGES].SetInterval(500); + //to set mailtimer to return mails every day between 4 and 5 am //mailtimer is increased when updating auctions //one second is 1000 -(tested on win system) @@ -2282,6 +2293,13 @@ void World::Update(uint32 diff) m_timers[WUPDATE_AHBOT].Reset(); } + ///
  • Handle file changes + if (m_timers[WUPDATE_CHECK_FILECHANGES].Passed()) + { + sScriptReloadMgr->Update(); + m_timers[WUPDATE_CHECK_FILECHANGES].Reset(); + } + ///
  • Handle session updates when the timer has passed ResetTimeDiffRecord(); UpdateSessions(diff); diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index 2b0af679ed27e..73f22336de8e4 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -92,6 +92,7 @@ enum WorldTimers WUPDATE_PINGDB, WUPDATE_GUILDSAVE, WUPDATE_BLACKMARKET, + WUPDATE_CHECK_FILECHANGES, WUPDATE_COUNT }; @@ -181,6 +182,12 @@ enum WorldBoolConfigs CONFIG_BASEMAP_LOAD_GRIDS, CONFIG_INSTANCEMAP_LOAD_GRIDS, CONFIG_BLACKMARKET_ENABLED, + CONFIG_HOTSWAP_ENABLED, + CONFIG_HOTSWAP_RECOMPILER_ENABLED, + CONFIG_HOTSWAP_EARLY_TERMINATION_ENABLED, + CONFIG_HOTSWAP_BUILD_FILE_RECREATION_ENABLED, + CONFIG_HOTSWAP_INSTALL_ENABLED, + CONFIG_HOTSWAP_PREFIX_CORRECTION_ENABLED, BOOL_CONFIG_VALUE_COUNT }; diff --git a/src/server/scripts/CMakeLists.txt b/src/server/scripts/CMakeLists.txt index 71e991b0c918c..83870fe527514 100644 --- a/src/server/scripts/CMakeLists.txt +++ b/src/server/scripts/CMakeLists.txt @@ -8,70 +8,232 @@ # WITHOUT ANY WARRANTY, to the extent permitted by law; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# Enable precompiled headers when using the GCC compiler. - -message(STATUS "SCRIPT PREPARATIONS") - -macro(PrepareScripts name out) - file(GLOB_RECURSE found - ${name}/*.h - ${name}/*.cpp - ) - list(APPEND ${out} ${found}) - message(STATUS " -> Prepared: ${name}") -endmacro(PrepareScripts) - -PrepareScripts(Spells PRIVATE_SOURCES) -PrepareScripts(Commands PRIVATE_SOURCES) - -if(SCRIPTS) - PrepareScripts(Custom PRIVATE_SOURCES) - PrepareScripts(World PRIVATE_SOURCES) - PrepareScripts(OutdoorPvP PRIVATE_SOURCES) - PrepareScripts(EasternKingdoms PRIVATE_SOURCES) - PrepareScripts(Kalimdor PRIVATE_SOURCES) - PrepareScripts(Outland PRIVATE_SOURCES) - PrepareScripts(Northrend PRIVATE_SOURCES) - PrepareScripts(Maelstrom PRIVATE_SOURCES) - PrepareScripts(Events PRIVATE_SOURCES) - PrepareScripts(Pet PRIVATE_SOURCES) +message("") + +# Make the script module list available in the current scope +GetScriptModuleList(SCRIPT_MODULE_LIST) + +# Make the native install offset available in this scope +GetInstallOffset(INSTALL_OFFSET) + +# Sets the SCRIPTS_${SCRIPT_MODULE} variables +# when using predefined templates for script building +# like dynamic, static, minimal-static... +# Sets SCRIPTS_DEFAULT_LINKAGE +if (SCRIPTS MATCHES "dynamic") + set(SCRIPTS_DEFAULT_LINKAGE "dynamic") +elseif(SCRIPTS MATCHES "static") + set(SCRIPTS_DEFAULT_LINKAGE "static") +else() + set(SCRIPTS_DEFAULT_LINKAGE "disabled") +endif() +# Sets SCRIPTS_USE_WHITELIST +# Sets SCRIPTS_WHITELIST +if (SCRIPTS MATCHES "minimal") + set(SCRIPTS_USE_WHITELIST ON) + # Whitelist which is used when minimal is selected + list(APPEND SCRIPTS_WHITELIST Commands Spells) endif() -message(STATUS "SCRIPT PREPARATION COMPLETE") -message("") +# Set the SCRIPTS_${SCRIPT_MODULE} variables from the +# variables set above +foreach(SCRIPT_MODULE ${SCRIPT_MODULE_LIST}) + ScriptModuleNameToVariable(${SCRIPT_MODULE} SCRIPT_MODULE_VARIABLE) + if (${SCRIPT_MODULE_VARIABLE} STREQUAL "default") + if(SCRIPTS_USE_WHITELIST) + list(FIND SCRIPTS_WHITELIST "${SCRIPT_MODULE}" INDEX) + if (${INDEX} GREATER -1) + set(${SCRIPT_MODULE_VARIABLE} ${SCRIPTS_DEFAULT_LINKAGE}) + else() + set(${SCRIPT_MODULE_VARIABLE} "disabled") + endif() + else() + set(${SCRIPT_MODULE_VARIABLE} ${SCRIPTS_DEFAULT_LINKAGE}) + endif() + endif() + # Build the Graph values + if (${SCRIPT_MODULE_VARIABLE} MATCHES "dynamic") + GetProjectNameOfScriptModule(${SCRIPT_MODULE} SCRIPT_MODULE_PROJECT_NAME) + GetNativeSharedLibraryName(${SCRIPT_MODULE_PROJECT_NAME} SCRIPT_PROJECT_LIBRARY) + list(APPEND GRAPH_KEYS ${SCRIPT_MODULE_PROJECT_NAME}) + set(GRAPH_VALUE_DISPLAY_${SCRIPT_MODULE_PROJECT_NAME} ${SCRIPT_PROJECT_LIBRARY}) + list(APPEND GRAPH_VALUE_CONTAINS_MODULES_${SCRIPT_MODULE_PROJECT_NAME} ${SCRIPT_MODULE}) + elseif(${SCRIPT_MODULE_VARIABLE} MATCHES "static") + list(APPEND GRAPH_KEYS worldserver) + set(GRAPH_VALUE_DISPLAY_worldserver worldserver) + list(APPEND GRAPH_VALUE_CONTAINS_MODULES_worldserver ${SCRIPT_MODULE}) + else() + list(APPEND GRAPH_KEYS disabled) + set(GRAPH_VALUE_DISPLAY_disabled disabled) + list(APPEND GRAPH_VALUE_CONTAINS_MODULES_disabled ${SCRIPT_MODULE}) + endif() +endforeach() + +list(SORT GRAPH_KEYS) +list(REMOVE_DUPLICATES GRAPH_KEYS) -list(APPEND PRIVATE_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/ScriptLoader.h - ${CMAKE_CURRENT_SOURCE_DIR}/ScriptLoader.cpp) +# Display the script graph +message("* Script configuration (${SCRIPTS}): + |") +foreach(GRAPH_KEY ${GRAPH_KEYS}) + if (NOT GRAPH_KEY STREQUAL "disabled") + message(" +- ${GRAPH_VALUE_DISPLAY_${GRAPH_KEY}}") + else() + message(" | ${GRAPH_VALUE_DISPLAY_${GRAPH_KEY}}") + endif() + foreach(GRAPH_PROJECT_ENTRY ${GRAPH_VALUE_CONTAINS_MODULES_${GRAPH_KEY}}) + message(" | +- ${GRAPH_PROJECT_ENTRY}") + endforeach() + message(" |") +endforeach() + +# Base sources which are used by every script project if (USE_SCRIPTPCH) - set(PRIVATE_PCH_HEADER PrecompiledHeaders/ScriptPCH.h) - set(PRIVATE_PCH_SOURCE PrecompiledHeaders/ScriptPCH.cpp) + set(PRIVATE_PCH_HEADER ScriptPCH.h) + set(PRIVATE_PCH_SOURCE ScriptPCH.cpp) endif () GroupSources(${CMAKE_CURRENT_SOURCE_DIR}) +# Configures the scriptloader with the given name and stores the output in the LOADER_OUT variable. +# It is possible to expose multiple subdirectories from the same scriptloader through passing +# it to the variable arguments +function(ConfigureScriptLoader SCRIPTLOADER_NAME LOADER_OUT IS_DYNAMIC_SCRIPTLOADER) + # Deduces following variables which are referenced by thge template: + # TRINITY_IS_DYNAMIC_SCRIPTLOADER + # TRINITY_SCRIPTS_FORWARD_DECL + # TRINITY_SCRIPTS_INVOKE + # TRINITY_CURRENT_SCRIPT_PROJECT + + # To generate export macros + set(TRINITY_IS_DYNAMIC_SCRIPTLOADER ${IS_DYNAMIC_SCRIPTLOADER}) + # To generate forward declarations of the loading functions + unset(TRINITY_SCRIPTS_FORWARD_DECL) + unset(TRINITY_SCRIPTS_INVOKE) + # The current script project which is built in + set(TRINITY_CURRENT_SCRIPT_PROJECT ${SCRIPTLOADER_NAME}) + foreach(LOCALE_SCRIPT_MODULE ${ARGN}) + # Determine the loader function ("Add##${NameOfDirectory}##Scripts()") + set(LOADER_FUNCTION + "Add${LOCALE_SCRIPT_MODULE}Scripts()") + # Generate the funciton call and the forward declarations + set(TRINITY_SCRIPTS_FORWARD_DECL + "${TRINITY_SCRIPTS_FORWARD_DECL}void ${LOADER_FUNCTION};\n") + set(TRINITY_SCRIPTS_INVOKE + "${TRINITY_SCRIPTS_INVOKE} ${LOADER_FUNCTION};\n") + endforeach() + set(GENERATED_LOADER ${CMAKE_CURRENT_BINARY_DIR}/gen_scriptloader/${SCRIPTLOADER_NAME}/ScriptLoader.cpp) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/ScriptLoader.cpp.in.cmake ${GENERATED_LOADER}) + set(${LOADER_OUT} ${GENERATED_LOADER} PARENT_SCOPE) +endfunction() + +# Generates the actual script projects +# Fills the STATIC_SCRIPT_MODULES and DYNAMIC_SCRIPT_MODULE_PROJECTS variables +# which contain the names which scripts are linked statically/dynamically and +# adds the sources of the static modules to the PRIVATE_SOURCES variable. +foreach(SCRIPT_MODULE ${SCRIPT_MODULE_LIST}) + GetPathToScriptModule(${SCRIPT_MODULE} SCRIPT_MODULE_PATH) + ScriptModuleNameToVariable(${SCRIPT_MODULE} SCRIPT_MODULE_VARIABLE) + + if ((${SCRIPT_MODULE_VARIABLE} STREQUAL "disabled") OR + (${SCRIPT_MODULE_VARIABLE} STREQUAL "static")) + # Uninstall disabled modules + GetProjectNameOfScriptModule(${SCRIPT_MODULE} SCRIPT_MODULE_PROJECT_NAME) + GetNativeSharedLibraryName(${SCRIPT_MODULE_PROJECT_NAME} SCRIPT_MODULE_OUTPUT_NAME) + list(APPEND DISABLED_SCRIPT_MODULE_PROJECTS ${INSTALL_OFFSET}/${SCRIPT_MODULE_OUTPUT_NAME}) + if (${SCRIPT_MODULE_VARIABLE} STREQUAL "static") + # Add the module name to STATIC_SCRIPT_MODULES + list(APPEND STATIC_SCRIPT_MODULES ${SCRIPT_MODULE}) + # Add the module content to the whole static module + CollectSourceFiles(${SCRIPT_MODULE_PATH} PRIVATE_SOURCES) + endif() + elseif(${SCRIPT_MODULE_VARIABLE} STREQUAL "dynamic") + # Generate an own dynamic module which is loadable on runtime + # Add the module content to the whole static module + unset(SCRIPT_MODULE_PRIVATE_SOURCES) + CollectSourceFiles(${SCRIPT_MODULE_PATH} SCRIPT_MODULE_PRIVATE_SOURCES) + # Configure the scriptloader + ConfigureScriptLoader(${SCRIPT_MODULE} SCRIPT_MODULE_PRIVATE_SCRIPTLOADER ON ${SCRIPT_MODULE}) + GetProjectNameOfScriptModule(${SCRIPT_MODULE} SCRIPT_MODULE_PROJECT_NAME) + # Add the module name to DYNAMIC_SCRIPT_MODULES + list(APPEND DYNAMIC_SCRIPT_MODULE_PROJECTS ${SCRIPT_MODULE_PROJECT_NAME}) + # Create the script module project + add_library(${SCRIPT_MODULE_PROJECT_NAME} SHARED + ${PRIVATE_PCH_SOURCE} + ${SCRIPT_MODULE_PRIVATE_SOURCES} + ${SCRIPT_MODULE_PRIVATE_SCRIPTLOADER}) + target_link_libraries(${SCRIPT_MODULE_PROJECT_NAME} + PUBLIC + game) + set_target_properties(${SCRIPT_MODULE_PROJECT_NAME} + PROPERTIES + FOLDER + "scripts") + + if(UNIX) + install(TARGETS ${SCRIPT_MODULE_PROJECT_NAME} + DESTINATION ${INSTALL_OFFSET} + COMPONENT ${SCRIPT_MODULE_PROJECT_NAME}) + elseif(WIN32) + install(TARGETS ${SCRIPT_MODULE_PROJECT_NAME} + RUNTIME DESTINATION ${INSTALL_OFFSET} + COMPONENT ${SCRIPT_MODULE_PROJECT_NAME}) + if(MSVC) + # Place the script modules in the script subdirectory + set_target_properties(${SCRIPT_MODULE_PROJECT_NAME} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin/Debug/scripts + RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin/Release/scripts + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}/bin/RelWithDebInfo/scripts + RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${CMAKE_BINARY_DIR}/bin/MinSizeRel/scripts) + endif() + endif() + else() + message(FATAL_ERROR "Unknown value \"${${SCRIPT_MODULE_VARIABLE}}\"!") + endif() +endforeach() + +# Add the dynamic script modules to the worldserver as dependency +set(WORLDSERVER_DYNAMIC_SCRIPT_MODULES_DEPENDENCIES ${DYNAMIC_SCRIPT_MODULE_PROJECTS} PARENT_SCOPE) + +ConfigureScriptLoader("static" SCRIPT_MODULE_PRIVATE_SCRIPTLOADER OFF ${STATIC_SCRIPT_MODULES}) + add_library(scripts STATIC + ScriptLoader.h ${PRIVATE_PCH_SOURCE} - ${PRIVATE_SOURCES} -) - -target_include_directories(scripts - PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR} - PRIVATE - ${CMAKE_CURRENT_BINARY_DIR}) + ${SCRIPT_MODULE_PRIVATE_SCRIPTLOADER} + ${PRIVATE_SOURCES}) target_link_libraries(scripts PUBLIC game) +target_include_directories(scripts + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}) + set_target_properties(scripts - PROPERTIES - FOLDER - "server") + PROPERTIES + FOLDER + "scripts") # Generate precompiled header if (USE_SCRIPTPCH) - add_cxx_pch(scripts ${PRIVATE_PCH_HEADER} ${PRIVATE_PCH_SOURCE}) + list(APPEND ALL_SCRIPT_PROJECTS scripts ${DYNAMIC_SCRIPT_MODULE_PROJECTS}) + add_cxx_pch("${ALL_SCRIPT_PROJECTS}" ${PRIVATE_PCH_HEADER} ${PRIVATE_PCH_SOURCE}) +endif() + +# Remove all shared libraries in the installl directory which +# are contained in the static library already. +if (DISABLED_SCRIPT_MODULE_PROJECTS) + install(CODE " + foreach(SCRIPT_TO_UNINSTALL ${DISABLED_SCRIPT_MODULE_PROJECTS}) + if (EXISTS \"\${SCRIPT_TO_UNINSTALL}\") + message(STATUS \"Uninstalling: \${SCRIPT_TO_UNINSTALL}\") + file(REMOVE \"\${SCRIPT_TO_UNINSTALL}\") + endif() + endforeach() + ") endif() + +message("") diff --git a/src/server/scripts/Commands/cs_reload.cpp b/src/server/scripts/Commands/cs_reload.cpp index bf89359a422ce..4ef4f5807b231 100644 --- a/src/server/scripts/Commands/cs_reload.cpp +++ b/src/server/scripts/Commands/cs_reload.cpp @@ -253,7 +253,7 @@ class reload_commandscript : public CommandScript static bool HandleReloadAllScriptsCommand(ChatHandler* handler, const char* /*args*/) { - if (sScriptMgr->IsScriptScheduled()) + if (sMapMgr->IsScriptScheduled()) { handler->PSendSysMessage("DB scripts used currently, please attempt reload later."); handler->SetSentErrorMessage(true); @@ -391,7 +391,7 @@ class reload_commandscript : public CommandScript static bool HandleReloadCommandCommand(ChatHandler* handler, const char* /*args*/) { - handler->SetLoadCommandTable(true); + ChatHandler::invalidateCommandTable(); handler->SendGlobalGMSysMessage("DB table `command` will be reloaded at next chat command use."); return true; } @@ -883,7 +883,7 @@ class reload_commandscript : public CommandScript static bool HandleReloadEventScriptsCommand(ChatHandler* handler, const char* args) { - if (sScriptMgr->IsScriptScheduled()) + if (sMapMgr->IsScriptScheduled()) { handler->SendSysMessage("DB scripts used currently, please attempt reload later."); handler->SetSentErrorMessage(true); @@ -903,7 +903,7 @@ class reload_commandscript : public CommandScript static bool HandleReloadWpScriptsCommand(ChatHandler* handler, const char* args) { - if (sScriptMgr->IsScriptScheduled()) + if (sMapMgr->IsScriptScheduled()) { handler->SendSysMessage("DB scripts used currently, please attempt reload later."); handler->SetSentErrorMessage(true); @@ -936,7 +936,7 @@ class reload_commandscript : public CommandScript static bool HandleReloadSpellScriptsCommand(ChatHandler* handler, const char* args) { - if (sScriptMgr->IsScriptScheduled()) + if (sMapMgr->IsScriptScheduled()) { handler->SendSysMessage("DB scripts used currently, please attempt reload later."); handler->SetSentErrorMessage(true); diff --git a/src/server/scripts/Kalimdor/kalimdor_script_loader.cpp b/src/server/scripts/Kalimdor/kalimdor_script_loader.cpp index 0a19ecb54e844..60a9d7dbb0fd8 100644 --- a/src/server/scripts/Kalimdor/kalimdor_script_loader.cpp +++ b/src/server/scripts/Kalimdor/kalimdor_script_loader.cpp @@ -47,6 +47,7 @@ void AddSC_boss_meathook(); void AddSC_culling_of_stratholme(); void AddSC_instance_culling_of_stratholme(); void AddSC_instance_dire_maul(); //Dire Maul +void AddSC_instance_ragefire_chasm(); //Ragefire Chasm void AddSC_boss_celebras_the_cursed(); //Maraudon void AddSC_boss_landslide(); void AddSC_boss_noxxion(); @@ -54,7 +55,6 @@ void AddSC_boss_ptheradras(); void AddSC_instance_maraudon(); void AddSC_boss_onyxia(); //Onyxia's Lair void AddSC_instance_onyxias_lair(); -void AddSC_instance_ragefire_chasm(); //Ragefire Chasm void AddSC_boss_tuten_kash(); //Razorfen Downs void AddSC_boss_mordresh_fire_eye(); void AddSC_boss_glutton(); @@ -150,6 +150,7 @@ void AddKalimdorScripts() AddSC_culling_of_stratholme(); AddSC_instance_culling_of_stratholme(); AddSC_instance_dire_maul(); //Dire Maul + AddSC_instance_ragefire_chasm(); //Ragefire Chasm AddSC_boss_celebras_the_cursed(); //Maraudon AddSC_boss_landslide(); AddSC_boss_noxxion(); @@ -157,7 +158,6 @@ void AddKalimdorScripts() AddSC_instance_maraudon(); AddSC_boss_onyxia(); //Onyxia's Lair AddSC_instance_onyxias_lair(); - AddSC_instance_ragefire_chasm(); //Ragefire Chasm AddSC_boss_tuten_kash(); //Razorfen Downs AddSC_boss_mordresh_fire_eye(); AddSC_boss_glutton(); diff --git a/src/server/scripts/ScriptLoader.cpp b/src/server/scripts/ScriptLoader.cpp deleted file mode 100644 index 145977a5f794b..0000000000000 --- a/src/server/scripts/ScriptLoader.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2008-2016 TrinityCore - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -#include "ScriptLoader.h" -#include "World.h" - -void AddSpellsScripts(); -void AddCommandsScripts(); - -#ifdef SCRIPTS -void AddWorldScripts(); -void AddEasternKingdomsScripts(); -void AddKalimdorScripts(); -void AddOutlandScripts(); -void AddNorthrendScripts(); -void AddMaelstromScripts(); -void AddEventsScripts(); -void AddPetScripts(); -void AddOutdoorPvPScripts(); -void AddCustomScripts(); -#endif - -void AddScripts() -{ - AddSpellsScripts(); - AddCommandsScripts(); -#ifdef SCRIPTS - AddWorldScripts(); - AddEasternKingdomsScripts(); - AddKalimdorScripts(); - AddOutlandScripts(); - AddNorthrendScripts(); - AddMaelstromScripts(); - AddEventsScripts(); - AddPetScripts(); - AddOutdoorPvPScripts(); - AddCustomScripts(); -#endif -} diff --git a/src/server/scripts/ScriptLoader.cpp.in.cmake b/src/server/scripts/ScriptLoader.cpp.in.cmake new file mode 100644 index 0000000000000..33c336a9a9390 --- /dev/null +++ b/src/server/scripts/ScriptLoader.cpp.in.cmake @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2008-2016 TrinityCore + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +// This file was created automatically from your script configuration! +// Use CMake to reconfigure this file, never change it on your own! + +#cmakedefine TRINITY_IS_DYNAMIC_SCRIPTLOADER + +#include "Define.h" +#include +#include + +@TRINITY_SCRIPTS_FORWARD_DECL@ +#ifdef TRINITY_IS_DYNAMIC_SCRIPTLOADER +# include "revision_data.h" +# define TC_SCRIPT_API TC_API_EXPORT +extern "C" { + +/// Exposed in script modules to return the script module revision hash. +TC_SCRIPT_API char const* GetScriptModuleRevisionHash() +{ + return _HASH; +} + +/// Exposed in script module to return the name of the script module +/// contained in this shared library. +TC_SCRIPT_API char const* GetScriptModule() +{ + return "@TRINITY_CURRENT_SCRIPT_PROJECT@"; +} + +#else +# include "ScriptLoader.h" +# define TC_SCRIPT_API +#endif + +/// Exposed in script modules to register all scripts to the ScriptMgr. +TC_SCRIPT_API void AddScripts() +{ +@TRINITY_SCRIPTS_INVOKE@} + +/// Exposed in script modules to get the build directive of the module. +TC_SCRIPT_API char const* GetBuildDirective() +{ + return _BUILD_DIRECTIVE; +} + +#ifdef TRINITY_IS_DYNAMIC_SCRIPTLOADER +} // extern "C" +#endif diff --git a/src/server/scripts/PrecompiledHeaders/ScriptPCH.cpp b/src/server/scripts/ScriptPCH.cpp similarity index 100% rename from src/server/scripts/PrecompiledHeaders/ScriptPCH.cpp rename to src/server/scripts/ScriptPCH.cpp diff --git a/src/server/scripts/PrecompiledHeaders/ScriptPCH.h b/src/server/scripts/ScriptPCH.h similarity index 100% rename from src/server/scripts/PrecompiledHeaders/ScriptPCH.h rename to src/server/scripts/ScriptPCH.h diff --git a/src/server/worldserver/CMakeLists.txt b/src/server/worldserver/CMakeLists.txt index 561271f7d3823..ff452dfaba867 100644 --- a/src/server/worldserver/CMakeLists.txt +++ b/src/server/worldserver/CMakeLists.txt @@ -67,6 +67,11 @@ set_target_properties(worldserver FOLDER "server") +# Add all dynamic projects as dependency to the worldserver +if (WORLDSERVER_DYNAMIC_SCRIPT_MODULES_DEPENDENCIES) + add_dependencies(worldserver ${WORLDSERVER_DYNAMIC_SCRIPT_MODULES_DEPENDENCIES}) +endif() + if( WIN32 ) if ( MSVC ) add_custom_command(TARGET worldserver diff --git a/src/server/worldserver/Main.cpp b/src/server/worldserver/Main.cpp index 770ba475352bc..d8859b1abc6c5 100644 --- a/src/server/worldserver/Main.cpp +++ b/src/server/worldserver/Main.cpp @@ -33,6 +33,7 @@ #include "InstanceSaveMgr.h" #include "ObjectAccessor.h" #include "ScriptMgr.h" +#include "ScriptReloadMgr.h" #include "ScriptLoader.h" #include "OutdoorPvP/OutdoorPvPMgr.h" #include "BattlegroundMgr.h" @@ -275,6 +276,7 @@ extern int main(int argc, char** argv) sOutdoorPvPMgr->Die(); // unload it before MapManager sMapMgr->UnloadAll(); // unload all grids (including locked in memory) sScriptMgr->Unload(); + sScriptReloadMgr->Unload(); // set server offline LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = flag | %u WHERE id = '%d'", REALM_FLAG_OFFLINE, realm.Id.Realm); diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index 0086cbcdf0414..1e72d3413be96 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -12,6 +12,7 @@ # SERVER LOGGING # SERVER SETTINGS # UPDATE SETTINGS +# HOTSWAP SETTINGS # WARDEN SETTINGS # PLAYER INTERACTION # CREATURE SETTINGS @@ -1292,6 +1293,78 @@ Updates.CleanDeadRefMaxCount = 3 # ################################################################################################### +################################################################################################### +# HOTSWAP SETTINGS +# +# HotSwap.Enabled (Requires compilation with DYNAMIC_LINKING=1) +# Description: Enables dynamic script hotswapping. +# Reloads scripts on changes. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +HotSwap.Enabled = 1 + +# +# HotSwap.ScriptDir +# Description: Directory containing the script shared libraries (.dll/.so). +# Example: "/usr/local/scripts" +# Default: "scripts" + +HotSwap.ScriptDir = "scripts" + +# HotSwap.EnableReCompiler +# Description: Enables the dynamic script recompiler. +# Watches your script source directories and recompiles the +# script modules on changes. +# Default: 0 - (Disabled) +# 1 - (Enabled) + +HotSwap.EnableReCompiler = 1 + +# HotSwap.EnableEarlyTermination +# Description: Terminate the build of a module when an associated +# source file was changed meanwhile. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +HotSwap.EnableEarlyTermination = 1 + +# HotSwap.EnableBuildFileRecreation +# Description: Recreate build files when sources to a module +# were added or removed. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +HotSwap.EnableBuildFileRecreation = 1 + +# +# HotSwap.EnableInstall +# Description: Enables cmake install after automatic builds have finished +# Default: 1 - (Enabled) +# 0 - (Disabled) + +HotSwap.EnableInstall = 1 + +# +# HotSwap.EnablePrefixCorrection +# Description: Allows the core to automatic set the CMAKE_INSTALL_PREFIX +# to it's current location in the filesystem. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +HotSwap.EnablePrefixCorrection = 1 + +# HotSwap.ReCompilerBuildType +# Description: Defines the build type of the builds invoked by the recompiler. +# Default: "" - Built-in build type of the module is used. +# "Release" - Release builds only +# "Debug" - Debug builds only + +HotSwap.ReCompilerBuildType = "" + +# +################################################################################################### + ################################################################################################### # WARDEN SETTINGS # @@ -3609,6 +3682,7 @@ Logger.mmaps=3,Server #Logger.scenes=3,Console Server #Logger.scripts=3,Console Server #Logger.scripts.ai=3,Console Server +#Logger.scripts.hotswap=3,Console Server #Logger.server.bnetserver=3,Console Server #Logger.spells=3,Console Server #Logger.spells.periodic=3,Console Server