Using the script hotswapping system

Since April 2016 TrinityCore supports C++ script reloading while the server is running (without restarting the server).

This tutorial will guide you step by step through the features which the script hotswap system offers.


Enabling the hotswap system

Static linking (default)

Maybe you came in touch already as you read the core setup guide with the SCRIPTS CMake variable which controls the build behaviour of the C++ scripts (the .cpp files you'll find in the src/server/scripts directory).

When you build the scripts statically (SCRIPTS="static") all scripts are compiled into the worldserver, that's why the size of it's executable is huge when building statically. The static build is easy to build and maintain and isn't error prone, but it requires the worldserver to be rebuilt as soon as you edit a script which heavily slows down the fun/ and time when working with scripts.


Script projects which were built statically into the worldserver aren't reloadable at runtime!


Dynamic linking

Dynamic linking splits the compiled code into multiple shared libraries (.dll on Windows, .so on Linux), which decreases the worldserver executable size but also makes it possible to attach/detach shared libraries while the application is running.

The hotswap system heavily abuses shared library attaching and detaching to hotswap C++ code into the running server without restarting, which heavily speeds up the development time of scripts.

You can always enable the dynamic linking by enabling the CMake WITH_DYNAMIC_LINKING option ("-DWITH_DYNAMIC_LINKING=ON") without making any change to the script configuration.


Script projects which were built dynamically are reloadable at runtime, (since it is built into separated shared libraries, which you will find in the the "script" directory).



Scripts as independent modules

You can choose for every script subproject how you want to build it, there are 4 options available, which you can configure through the SCRIPTS_${MODULENAME} CMake variables:


  • "default": The linking mode from the SCRIPTS variable is used
  • "disabled": Exclude the script subdirectory from the build (excluding unused scripts will speed up the build time).
  • "static": Build the subdirectory always statically (no hotswapping is supported for this module then).
  • "dynamic": Builds the subdirectory dynamically (to a shared library), which is reloadable.

Setting the variable for a script subdirectory automatically overwrites the value of the SCRIPT variable which offers you some predefined build templates:

  • "none": disables all scripts
  • "static": builds all scripts statically (this is the old -DSCRIPTS=1 option).
  • "dynamic": builds all scripts dynamically
  • "minimal-static": builds commands and spells statically, disables other scripts (this is the old -DSCRIPTS=0 option).
  • "minimal-dynamic": builds commands and spells dynamically, disables other scripts.


Building at least one script project dynamically will automatically enforce the CMake WITH_DYNAMIC_LINKING option which compiles the hotswap logic into the worldserver.

A worldserver build without the WITH_DYNAMIC_LINKING option isn't capable of loading any shared library.

Configuring the hotswap system

The hotswap system is enabled by default, but it requires the server to be built with the CMake WITH_DYNAMIC_LINKING option as explained above.

If you want to disable the whole system you may disable it through the HotSwap.Enabled worldserver.conf setting.


To run the system without errors you need to run the worldserver outside of your build directory.

Use the CMAKE_INSTALL_PREFIX to install TrinityCore to your preferred install location when using the hotswap system.

  1. Set the CMAKE_INSTALL_PREFIX to a location in your filesystem (not C:/ on windows since it's write protected in most cases).
  2. Use the cmake INSTALL project target in Visual Studio (or `make install on unix`), to install TrinityCore to the location specified by CMAKE_INSTALL_PREFIX .
  3. Start the worldserver from the location specified by the CMAKE_INSTALL_PREFIX.

Starting the worldserver inside your build directory may work but this method is unsupported!



Additionnaly, you cannot use the built-in feature of VS around cmake (using the project as a cmake ready directory).

You have to make cmake create a .sln project file and open that file in VS. (back-story behind this is that the internal feature of VS will add path of VS installation of std includes that the recompiler wont have at the time of recompile : https://github.com/TrinityCore/TrinityCore/issues/25445 ).


The hotswap system checks the CMAKE_INSTALL_PREFIX at startup and will correct it in your build directory automatically if needed (HotSwap.EnablePrefixCorrection=1 -> default).

The shared libraries which contain the scripts are installed into the scripts directory relative to the worldserver. when you start the worldserver you will recognize the log messages which are telling you that the shared libraries were loaded (disabled scripts could lead to error messages - but that's not harmful, it just tells you that the scripts aren't useable → but don't expect the disabled scripts or functionality to work):

(The orange messages are verbose outputs → you need to configurate your logger in order to display it).

Using the hotswap system

As explained in the section "Configuring the hotswap system" the shared libraries are located in the scripts directory, and are automatically observed through the worldserver.

It's possible to remove shared libraries from the script directory or add some to it, the worldserver will instantly unload, load or reload the C++ scripts from those modules → thanks to the efsw filewatcher library.

Loading, unloading or reloading scripts will affect the in-game entities like creatures immediately:

  • Database unbound scripts (the scripts which doesn't have a scriptname assigned in the database) are reloaded instantly without any issues.
  • Database bound scripts are complicated to swap which means some reset mechanic is needed to do so.

Reloading database bound scripts will trigger following actions while swapping the script immediately:

  • Creatures which AI's are swapped enter into evade mode immediately which cause the creature to reset, the script is swapped immediately.
  • GameObjects are swapped immediately with triggering a Reset() before swapping.
  • Commands: The command cache is cleared, the commands are reloaded immediately.
  • OutdoorPvPScript's are killed and re-initialized after reloading → any progress is lost.

InstanceScripts and Spell/AuraScripts are most difficult to swap that's why the worldserver is using lazy unloading for it:

  • Spell/AuraScripts are never swapped, but new instances of the spells or auras are using the latest C++ script version which is available → re-cast or re-apply the aura you want to try out to get the latest script version (.unaura all, .cast ...).
  • InstanceScripts are also never swapped, the worldserver will use the latest C++ script version available of the instance script when creating new ones. (Use .instance unbind all and reenter the instance to get the latest script version).

Lazy unloading keeps copies of still referenced shared libraries in the cache as long as it is used by some entities (spells or instances) and removes the cached libraries as soon as it is no longer referenced → this means that the worldserver can server multiple versions of a script at once.

Getting the feeling of a real scripting engine (using the recompiler)

Most scripting engines support save to reload which inspired me to implement this feature also for our "C++ scripting engine".

The hotswap system tracks the source files for changes and invokes an automatic rebuild on that project ("HotSwap.EnableReCompiler = 1" worldserver.conf option),

which will cause the worldserver to reload the associated shared library:


Just change a script source file in your dedicated IDE or text editor → the worldserver will recompile and reload it in a short time.

In detail the hotswap system invokes a specific build followed by an install (that's why setting the CMAKE_INSTALL_PREFIX variable is very important).

The recompiler should work out of the box, and will cause the script to be reloaded in a few seconds (depends on your machine specs).

It also observes the directories and re-invokes CMake to take care of added or removed source files.


Troubleshooting


 Failed to create Cache entry assertion

Make sure you are running the server outside your build directory see "Configuring the Hotswap System"