diff options
author | Naios <naios-dev@live.de> | 2016-03-11 17:09:26 +0100 |
---|---|---|
committer | Naios <naios-dev@live.de> | 2016-04-11 21:42:28 +0200 |
commit | eaf102b3c59232af9371839c10b6b3e8368b9cfd (patch) | |
tree | a6b290844d65f5cda4b46de4117e784470a815c5 | |
parent | 3123c278b9f7dca36d4edcafc95f40f37ec3e4eb (diff) |
Core/Game: Rewrote the ScriptMgr to support script reloading.
* Finally this commit enables dynamic script hotswapping
and finished the PR #15671.
* Split the storage layout to use optimized storages
for database bound and unbound scripts.
* Add several unload workers to reload scripts correctly
-> Requires further investigation.
* Fixes memory leaks in ScriptMgr when dropping invalid scripts.
* Fixes VehicleScripts
* Makes OutdoorPvP scripts reloadable
* Makes InstanceMapScripts reloadable
* Makes CommandScripts reloadable
(cherry picked from commit 9cc97f226d79e8e0bbe1fdc386ec9f065c0a2226)
20 files changed, 1076 insertions, 296 deletions
diff --git a/src/server/game/Chat/Chat.cpp b/src/server/game/Chat/Chat.cpp index f0e98ee8c1f..ec90a5f7efb 100644 --- a/src/server/game/Chat/Chat.cpp +++ b/src/server/game/Chat/Chat.cpp @@ -32,18 +32,19 @@ #include "ScriptMgr.h" #include "ChatLink.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<std::vector<ChatCommand>> commandTableCache; std::vector<ChatCommand> const& ChatHandler::getCommandTable() { - static std::vector<ChatCommand> commandTableCache; - - if (LoadCommandTable()) + if (!commandTableCache) { - SetLoadCommandTable(false); - - std::vector<ChatCommand> 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); @@ -54,13 +55,18 @@ std::vector<ChatCommand> 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 a061ff63b00..1c9368275ad 100644 --- a/src/server/game/Chat/Chat.h +++ b/src/server/game/Chat/Chat.h @@ -96,6 +96,7 @@ class TC_GAME_API ChatHandler bool ParseCommands(const char* text); static std::vector<ChatCommand> const& getCommandTable(); + static void invalidateCommandTable(); bool isValidChatMessage(const char* msg); void SendGlobalSysMessage(const char *str); @@ -143,8 +144,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<ChatCommand> const& table, const char* cmd); protected: @@ -157,7 +156,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 02fc69b8365..e72859c23e7 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 79d36055870..1d2b963b254 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 ec64604a282..9f52668ea61 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 61d5d7d9459..2b595b71e01 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -791,6 +791,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 @@ -800,12 +818,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 c6b19dbb939..8466dad9e95 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -476,6 +476,7 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, 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 3fcd249c9be..7f922f89572 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -71,9 +71,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 49f94013ac2..2b092427178 100644 --- a/src/server/game/Entities/GameObject/GameObject.h +++ b/src/server/game/Entities/GameObject/GameObject.h @@ -849,8 +849,10 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject> void UpdateModelPosition(); - 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 596411f3d67..3513b5d97b8 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -5162,7 +5162,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(); } } @@ -5171,7 +5171,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; @@ -5193,45 +5193,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<std::pair<SpellScriptLoader *, SpellScriptsContainer::iterator> > SpellScriptLoaders; - sScriptMgr->CreateSpellScriptLoaders(itr->first, SpellScriptLoaders); - itr = _spellScriptsStore.upper_bound(itr->first); + SpellInfo const* spellEntry = sSpellMgr->GetSpellInfo(spell.first); - for (std::vector<std::pair<SpellScriptLoader *, SpellScriptsContainer::iterator> >::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> spellScript(spellScriptLoader->GetSpellScript()); + std::unique_ptr<AuraScript> 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)); @@ -8571,6 +8574,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( @@ -8612,18 +8617,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; } diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index 73c03849699..b59aa5a8d98 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -370,7 +370,7 @@ struct ScriptInfo typedef std::multimap<uint32, ScriptInfo> ScriptMap; typedef std::map<uint32, ScriptMap > ScriptMapMap; -typedef std::multimap<uint32, uint32> SpellScriptsContainer; +typedef std::multimap<uint32 /*spell id*/, std::pair<uint32 /*script id*/, bool /*disabled*/>> SpellScriptsContainer; typedef std::pair<SpellScriptsContainer::iterator, SpellScriptsContainer::iterator> SpellScriptsBounds; TC_GAME_API extern ScriptMapMap sSpellScripts; TC_GAME_API extern ScriptMapMap sEventScripts; @@ -1272,6 +1272,7 @@ class TC_GAME_API ObjectMgr bool IsVendorItemValid(uint32 vendor_entry, uint32 item, int32 maxcount, uint32 ptime, uint32 ExtendedCost, Player* player = NULL, std::set<uint32>* 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 1575b50098f..0887d183a8b 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 949cdf5abcb..3814afe2f4b 100644 --- a/src/server/game/Instances/InstanceScript.h +++ b/src/server/game/Instances/InstanceScript.h @@ -36,6 +36,7 @@ class Unit; class Player; class GameObject; class Creature; +class ModuleReference; enum EncounterFrameType { @@ -137,7 +138,7 @@ typedef std::map<uint32 /*entry*/, uint32 /*type*/> ObjectInfoMap; class TC_GAME_API InstanceScript : public ZoneScript { public: - explicit InstanceScript(Map* map) : instance(map), completedEncounters(0) { } + explicit InstanceScript(Map* map); virtual ~InstanceScript() { } @@ -289,6 +290,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<ModuleReference> module_reference; + #endif // #ifndef TRINITY_API_USE_DYNAMIC_LINKING }; template<class AI, class T> diff --git a/src/server/game/OutdoorPvP/OutdoorPvPMgr.cpp b/src/server/game/OutdoorPvP/OutdoorPvPMgr.cpp index 7f0695e16b3..aa00d211d6e 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 26a7809eaa9..4c9487874de 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. @@ -67,6 +66,10 @@ struct is_script_database_bound<GameObjectScript> : std::true_type { }; template<> +struct is_script_database_bound<VehicleScript> + : std::true_type { }; + +template<> struct is_script_database_bound<AreaTriggerScript> : std::true_type { }; @@ -94,143 +97,838 @@ template<> struct is_script_database_bound<AchievementCriteriaScript> : 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<std::string>& scripts) = 0; + + /// Unloads the script registry. + virtual void Unload() = 0; +}; + +template<class> +class ScriptRegistry; + +class ScriptRegistryCompositum + : public ScriptRegistryInterface +{ + ScriptRegistryCompositum() { } + + template<class> + friend class ScriptRegistry; + + /// Type erasure wrapper for objects + class DeleteableObjectBase + { + public: + DeleteableObjectBase() { } + virtual ~DeleteableObjectBase() { } + + DeleteableObjectBase(DeleteableObjectBase const&) = delete; + DeleteableObjectBase& operator= (DeleteableObjectBase const&) = delete; + }; + + template<typename T> + class DeleteableObject + : public DeleteableObjectBase + { + public: + DeleteableObject(T&& object) + : _object(std::forward<T>(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<std::string>& scripts) final override + { + for (auto const registry : _registries) + registry->RemoveUsedScriptsFromContainer(scripts); + } + + void Unload() final override + { + for (auto const registry : _registries) + registry->Unload(); + } + + template<typename T> + void QueueForDelayedDelete(T&& any) + { + _delayed_delete_queue.push_back( + Trinity::make_unique< + DeleteableObject<typename std::decay<T>::type> + >(std::forward<T>(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<ScriptRegistryInterface*> _registries; + + std::vector<std::unique_ptr<DeleteableObjectBase>> _delayed_delete_queue; + + std::unordered_map< + std::string /*script name*/, + std::string /*context*/ + > _scriptnames_to_context; +}; + +#define sScriptRegistryCompositum ScriptRegistryCompositum::Instance() + +template<typename /*ScriptType*/, bool /*IsDatabaseBound*/> +class SpecializedScriptRegistry; + // This is the global static registry of scripts. -template<class TScript> -class ScriptRegistry +template<class ScriptType> +class ScriptRegistry final + : public SpecializedScriptRegistry< + ScriptType, is_script_database_bound<ScriptType>::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<typename ScriptType, typename Base> +class ScriptRegistrySwapHooks + : public ScriptRegistrySwapHookBase +{ +}; + +/// This hook is responsible for swapping OutdoorPvP's +template<typename Base> +class UnsupportedScriptRegistrySwapHooks + : public ScriptRegistrySwapHookBase +{ +public: + void BeforeReleaseContext(std::string const& context) final override + { + auto const bounds = static_cast<Base*>(this)->_ids_of_contexts.equal_range(context); + ASSERT(bounds.first == bounds.second); + } +}; + +/// This hook is responsible for swapping Creature and GameObject AI's +template<typename ObjectType, typename ScriptType, typename Base> +class CreatureGameObjectScriptRegistrySwapHooks + : public ScriptRegistrySwapHookBase { + template<typename W> + class AIFunctionMapWorker + { public: + template<typename T> + AIFunctionMapWorker(T&& worker) + : _worker(std::forward<T>(worker)) { } + + void Visit(std::unordered_map<ObjectGuid, ObjectType*>& objects) + { + _worker(objects); + } + + template<typename O> + void Visit(std::unordered_map<ObjectGuid, O*>&) { } - typedef std::map<uint32, TScript*> 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<TScript*> 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(); + } + + static void UnloadStage2(GameObject* gameobject) + { + gameobject->AIM_Destroy(); - // 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) + 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<typename T> + void RunOverAllEntities(T fn) + { + auto evaluator = [&](std::unordered_map<ObjectGuid, ObjectType*>& objects) + { + for (auto object : objects) + fn(object.second); + }; + + AIFunctionMapWorker<typename std::decay<decltype(evaluator)>::type> worker(std::move(evaluator)); + TypeContainerVisitor<decltype(worker), MapStoredObjectTypesContainer> 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<Base*>(this)->GetScriptIDsToRemove(context); + + std::vector<ObjectType*> 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<Base*>(this)->GetRecentlyAddedScriptIDs().begin(), + static_cast<Base*>(this)->GetRecentlyAddedScriptIDs().end()); + + std::vector<ObjectType*> remove; + std::vector<ObjectType*> 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); + + for (auto object : stage2) + LoadStage1(object); + + for (auto object : stage2) + LoadStage2(object); + + ids_removed_.clear(); + } + + void BeforeUnload() final override + { + ASSERT(ids_removed_.empty()); + } - AddScript(is_script_database_bound<TScript>{}, script); - if (addToDeleteContainer) - Scripts.push_back(script); +private: + std::unordered_set<uint32> ids_removed_; +}; + +// This hook is responsible for swapping CreatureAI's +template<typename Base> +class ScriptRegistrySwapHooks<CreatureScript, Base> + : public CreatureGameObjectScriptRegistrySwapHooks< + Creature, CreatureScript, Base + > { }; + +// This hook is responsible for swapping GameObjectAI's +template<typename Base> +class ScriptRegistrySwapHooks<GameObjectScript, Base> + : public CreatureGameObjectScriptRegistrySwapHooks< + GameObject, GameObjectScript, Base + > { }; + +/// This hook is responsible for swapping BattlegroundScript's +template<typename Base> +class ScriptRegistrySwapHooks<BattlegroundScript, Base> + : public UnsupportedScriptRegistrySwapHooks<Base> { }; + +/// This hook is responsible for swapping OutdoorPvP's +template<typename Base> +class ScriptRegistrySwapHooks<OutdoorPvPScript, Base> + : public ScriptRegistrySwapHookBase +{ +public: + ScriptRegistrySwapHooks() : swapped(false) { } + + void BeforeReleaseContext(std::string const& context) final override + { + auto const bounds = static_cast<Base*>(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<typename Base> +class ScriptRegistrySwapHooks<InstanceMapScript, Base> + : public ScriptRegistrySwapHookBase +{ +public: + ScriptRegistrySwapHooks() : swapped(false) { } + + void BeforeReleaseContext(std::string const& context) final override + { + auto const bounds = static_cast<Base*>(this)->_ids_of_contexts.equal_range(context); + if (bounds.first != bounds.second) + swapped = true; + } + + void BeforeSwapContext() final override + { + swapped = false; + } - return NULL; + void BeforeUnload() final override + { + ASSERT(!swapped); + } + +private: + bool swapped; +}; + +/// This hook is responsible for swapping SpellScriptLoader's +template<typename Base> +class ScriptRegistrySwapHooks<SpellScriptLoader, Base> + : public ScriptRegistrySwapHookBase +{ +public: + ScriptRegistrySwapHooks() : swapped(false) { } + + void BeforeReleaseContext(std::string const& context) final override + { + auto const bounds = static_cast<Base*>(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<typename ScriptType> +class SpecializedScriptRegistry<ScriptType, true> + : public ScriptRegistryInterface, + public ScriptRegistrySwapHooks<ScriptType, ScriptRegistry<ScriptType>> +{ + template<typename> + friend class UnsupportedScriptRegistrySwapHooks; + + template<typename, typename> + friend class ScriptRegistrySwapHooks; + + template<typename, typename, typename> + friend class CreatureGameObjectScriptRegistrySwapHooks; + +public: + SpecializedScriptRegistry() { } - // Adds a database bound script - static void AddScript(std::true_type, TScript* const script) + typedef std::unordered_map< + uint32 /*script id*/, + std::unique_ptr<ScriptType> + > 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<std::string>& 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!"); + + std::unique_ptr<ScriptType> 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; + } + +protected: + // Returns the script id's which are registered to a certain context + std::unordered_set<uint32> GetScriptIDsToRemove(std::string const& context) const + { + // Create a set of all ids which are removed + std::unordered_set<uint32> 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<uint32> const& GetRecentlyAddedScriptIDs() const + { + return _recently_added_ids; + } + +private: + ScriptStoreType _scripts; + + // Scripts of a specific context + std::unordered_multimap<std::string /*context*/, uint32 /*id*/> _ids_of_contexts; + + // Script id's which were registered recently + std::unordered_set<uint32> _recently_added_ids; +}; + +/// This hook is responsible for swapping CommandScript's +template<typename Base> +class ScriptRegistrySwapHooks<CommandScript, Base> + : 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<typename ScriptType> +class SpecializedScriptRegistry<ScriptType, false> + : public ScriptRegistryInterface, + public ScriptRegistrySwapHooks<ScriptType, ScriptRegistry<ScriptType>> +{ + template<typename, typename> + friend class ScriptRegistrySwapHooks; + +public: + typedef std::unordered_multimap<std::string /*context*/, std::unique_ptr<ScriptType>> 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<std::string>& 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<ScriptType> script_ptr(script); + + for (auto const& entry : _scripts) + if (entry.second.get() == script) + { + static_cast<ScriptRegistry<ScriptType>*>(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))); + } - // Counter used for code-only scripts. - static uint32 _scriptIdCounter; + ScriptStoreType& GetScripts() + { + return _scripts; + } + +private: + ScriptStoreType _scripts; }; // Utility macros to refer to the script registry. -#define SCR_REG_MAP(T) ScriptRegistry<T>::ScriptMap -#define SCR_REG_ITR(T) ScriptRegistry<T>::ScriptMapIterator -#define SCR_REG_LST(T) ScriptRegistry<T>::ScriptPointerList -#define SCR_REG_VEC(T) ScriptRegistry<T>::Scripts +#define SCR_REG_MAP(T) ScriptRegistry<T>::ScriptStoreType +#define SCR_REG_ITR(T) ScriptRegistry<T>::ScriptStoreIteratorType +#define SCR_REG_LST(T) ScriptRegistry<T>::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<T>::GetScriptById(I); \ + T* V = ScriptRegistry<T>::Instance()->GetScriptById(I); \ if (!V) \ return; + #define GET_SCRIPT_RET(T, I, V, R) \ - T* V = ScriptRegistry<T>::GetScriptById(I); \ + T* V = ScriptRegistry<T>::Instance()->GetScriptById(I); \ if (!V) \ return R; @@ -240,6 +938,16 @@ 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), _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,80 +974,84 @@ void ScriptMgr::Initialize() FillSpellSummary(); - // Load core script systems + // Load core scripts + SetScriptContext("___static___"); + // SmartAI - BeginScriptContext("core scripts"); AddSC_SmartScripts(); - FinishScriptContext(); + + // LFGScripts + lfg::AddSC_LFGScripts(); // Load all static linked scripts through the script loader function. - BeginScriptContext("static scripts"); ASSERT(_script_loader_callback, "Script loader callback wasn't registered!"); _script_loader_callback(); - FinishScriptContext(); -#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<std::string> 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::BeginScriptContext(std::string const& context) +void ScriptMgr::SetScriptContext(std::string const& context) { _currentContext = context; } -void ScriptMgr::FinishScriptContext() +void ScriptMgr::SwapScriptContext(bool initialize) { + sScriptRegistryCompositum->SwapContext(initialize); _currentContext.clear(); } -void ScriptMgr::ReleaseScriptContext(std::string const& /*context*/) +void ScriptMgr::ReleaseScriptContext(std::string const& context) { - // ToDo + sScriptRegistryCompositum->ReleaseContext(context); +} + +std::shared_ptr<ModuleReference> + 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; @@ -434,38 +1149,22 @@ void ScriptMgr::FillSpellSummary() } } -void ScriptMgr::CreateSpellScripts(uint32 spellId, std::list<SpellScript*>& scriptVector) +template<typename T, typename F> +void CreateSpellOrAuraScripts(uint32 spellId, std::list<T*>& scriptVector, F&& extractor) { SpellScriptsBounds bounds = sObjectMgr->GetSpellScriptsBounds(spellId); for (SpellScriptsContainer::iterator itr = bounds.first; itr != bounds.second; ++itr) { - SpellScriptLoader* tmpscript = ScriptRegistry<SpellScriptLoader>::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<AuraScript*>& scriptVector) -{ - SpellScriptsBounds bounds = sObjectMgr->GetSpellScriptsBounds(spellId); - - for (SpellScriptsContainer::iterator itr = bounds.first; itr != bounds.second; ++itr) - { - SpellScriptLoader* tmpscript = ScriptRegistry<SpellScriptLoader>::GetScriptById(itr->second); + SpellScriptLoader* tmpscript = ScriptRegistry<SpellScriptLoader>::Instance()->GetScriptById(itr->second.first); if (!tmpscript) continue; - AuraScript* script = tmpscript->GetAuraScript(); + T* script = (*tmpscript.*extractor)(); if (!script) continue; @@ -476,19 +1175,19 @@ void ScriptMgr::CreateAuraScripts(uint32 spellId, std::list<AuraScript*>& script } } -void ScriptMgr::CreateSpellScriptLoaders(uint32 spellId, std::vector<std::pair<SpellScriptLoader*, SpellScriptsContainer::iterator> >& scriptVector) +void ScriptMgr::CreateSpellScripts(uint32 spellId, std::list<SpellScript*>& 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<SpellScriptLoader>::GetScriptById(itr->second); - if (!tmpscript) - continue; +void ScriptMgr::CreateAuraScripts(uint32 spellId, std::list<AuraScript*>& scriptVector) +{ + CreateSpellOrAuraScripts(spellId, scriptVector, &SpellScriptLoader::GetAuraScript); +} - scriptVector.push_back(std::make_pair(tmpscript, itr)); - } +SpellScriptLoader* ScriptMgr::GetSpellScriptLoader(uint32 scriptId) +{ + return ScriptRegistry<SpellScriptLoader>::Instance()->GetScriptById(scriptId); } void ScriptMgr::OnNetworkStart() @@ -1516,56 +2215,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<SpellScriptLoader>::AddScript(this); + ScriptRegistry<SpellScriptLoader>::Instance()->AddScript(this); } ServerScript::ServerScript(const char* name) : ScriptObject(name) { - ScriptRegistry<ServerScript>::AddScript(this); + ScriptRegistry<ServerScript>::Instance()->AddScript(this); } WorldScript::WorldScript(const char* name) : ScriptObject(name) { - ScriptRegistry<WorldScript>::AddScript(this); + ScriptRegistry<WorldScript>::Instance()->AddScript(this); } FormulaScript::FormulaScript(const char* name) : ScriptObject(name) { - ScriptRegistry<FormulaScript>::AddScript(this); + ScriptRegistry<FormulaScript>::Instance()->AddScript(this); } UnitScript::UnitScript(const char* name, bool addToScripts) : ScriptObject(name) { - ScriptRegistry<UnitScript>::AddScript(this, addToScripts); + if (addToScripts) + ScriptRegistry<UnitScript>::Instance()->AddScript(this); } WorldMapScript::WorldMapScript(const char* name, uint32 mapId) @@ -1574,7 +2279,7 @@ WorldMapScript::WorldMapScript(const char* name, uint32 mapId) if (GetEntry() && !GetEntry()->IsWorldMap()) TC_LOG_ERROR("scripts", "WorldMapScript for map %u is invalid.", mapId); - ScriptRegistry<WorldMapScript>::AddScript(this); + ScriptRegistry<WorldMapScript>::Instance()->AddScript(this); } InstanceMapScript::InstanceMapScript(const char* name, uint32 mapId) @@ -1583,7 +2288,7 @@ InstanceMapScript::InstanceMapScript(const char* name, uint32 mapId) if (GetEntry() && !GetEntry()->IsDungeon()) TC_LOG_ERROR("scripts", "InstanceMapScript for map %u is invalid.", mapId); - ScriptRegistry<InstanceMapScript>::AddScript(this); + ScriptRegistry<InstanceMapScript>::Instance()->AddScript(this); } BattlegroundMapScript::BattlegroundMapScript(const char* name, uint32 mapId) @@ -1592,122 +2297,117 @@ BattlegroundMapScript::BattlegroundMapScript(const char* name, uint32 mapId) if (GetEntry() && !GetEntry()->IsBattleground()) TC_LOG_ERROR("scripts", "BattlegroundMapScript for map %u is invalid.", mapId); - ScriptRegistry<BattlegroundMapScript>::AddScript(this); + ScriptRegistry<BattlegroundMapScript>::Instance()->AddScript(this); } ItemScript::ItemScript(const char* name) : ScriptObject(name) { - ScriptRegistry<ItemScript>::AddScript(this); + ScriptRegistry<ItemScript>::Instance()->AddScript(this); } CreatureScript::CreatureScript(const char* name) : UnitScript(name, false) { - ScriptRegistry<CreatureScript>::AddScript(this); + ScriptRegistry<CreatureScript>::Instance()->AddScript(this); } GameObjectScript::GameObjectScript(const char* name) : ScriptObject(name) { - ScriptRegistry<GameObjectScript>::AddScript(this); + ScriptRegistry<GameObjectScript>::Instance()->AddScript(this); } AreaTriggerScript::AreaTriggerScript(const char* name) : ScriptObject(name) { - ScriptRegistry<AreaTriggerScript>::AddScript(this); + ScriptRegistry<AreaTriggerScript>::Instance()->AddScript(this); } BattlegroundScript::BattlegroundScript(const char* name) : ScriptObject(name) { - ScriptRegistry<BattlegroundScript>::AddScript(this); + ScriptRegistry<BattlegroundScript>::Instance()->AddScript(this); } OutdoorPvPScript::OutdoorPvPScript(const char* name) : ScriptObject(name) { - ScriptRegistry<OutdoorPvPScript>::AddScript(this); + ScriptRegistry<OutdoorPvPScript>::Instance()->AddScript(this); } CommandScript::CommandScript(const char* name) : ScriptObject(name) { - ScriptRegistry<CommandScript>::AddScript(this); + ScriptRegistry<CommandScript>::Instance()->AddScript(this); } WeatherScript::WeatherScript(const char* name) : ScriptObject(name) { - ScriptRegistry<WeatherScript>::AddScript(this); + ScriptRegistry<WeatherScript>::Instance()->AddScript(this); } AuctionHouseScript::AuctionHouseScript(const char* name) : ScriptObject(name) { - ScriptRegistry<AuctionHouseScript>::AddScript(this); + ScriptRegistry<AuctionHouseScript>::Instance()->AddScript(this); } ConditionScript::ConditionScript(const char* name) : ScriptObject(name) { - ScriptRegistry<ConditionScript>::AddScript(this); + ScriptRegistry<ConditionScript>::Instance()->AddScript(this); } VehicleScript::VehicleScript(const char* name) : ScriptObject(name) { - ScriptRegistry<VehicleScript>::AddScript(this); + ScriptRegistry<VehicleScript>::Instance()->AddScript(this); } DynamicObjectScript::DynamicObjectScript(const char* name) : ScriptObject(name) { - ScriptRegistry<DynamicObjectScript>::AddScript(this); + ScriptRegistry<DynamicObjectScript>::Instance()->AddScript(this); } TransportScript::TransportScript(const char* name) : ScriptObject(name) { - ScriptRegistry<TransportScript>::AddScript(this); + ScriptRegistry<TransportScript>::Instance()->AddScript(this); } AchievementCriteriaScript::AchievementCriteriaScript(const char* name) : ScriptObject(name) { - ScriptRegistry<AchievementCriteriaScript>::AddScript(this); + ScriptRegistry<AchievementCriteriaScript>::Instance()->AddScript(this); } PlayerScript::PlayerScript(const char* name) : UnitScript(name, false) { - ScriptRegistry<PlayerScript>::AddScript(this); + ScriptRegistry<PlayerScript>::Instance()->AddScript(this); } AccountScript::AccountScript(const char* name) : ScriptObject(name) { - ScriptRegistry<AccountScript>::AddScript(this); + ScriptRegistry<AccountScript>::Instance()->AddScript(this); } GuildScript::GuildScript(const char* name) : ScriptObject(name) { - ScriptRegistry<GuildScript>::AddScript(this); + ScriptRegistry<GuildScript>::Instance()->AddScript(this); } GroupScript::GroupScript(const char* name) : ScriptObject(name) { - ScriptRegistry<GroupScript>::AddScript(this); + ScriptRegistry<GroupScript>::Instance()->AddScript(this); } -// Instantiate static members of ScriptRegistry. -template<class TScript> std::map<uint32, TScript*> ScriptRegistry<TScript>::ScriptPointerList; -template<class TScript> std::vector<TScript*> ScriptRegistry<TScript>::Scripts; -template<class TScript> uint32 ScriptRegistry<TScript>::_scriptIdCounter = 0; - // Specialize for each script type class like so: template class TC_GAME_API ScriptRegistry<SpellScriptLoader>; template class TC_GAME_API ScriptRegistry<ServerScript>; @@ -1735,13 +2435,3 @@ template class TC_GAME_API ScriptRegistry<GuildScript>; template class TC_GAME_API ScriptRegistry<GroupScript>; template class TC_GAME_API ScriptRegistry<UnitScript>; template class TC_GAME_API ScriptRegistry<AccountScript>; - -// 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 fb4f798bcc9..cc1b65fa593 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; @@ -157,14 +158,8 @@ class TC_GAME_API ScriptObject protected: - ScriptObject(const char* name) - : _name(name) - { - } - - virtual ~ScriptObject() - { - } + ScriptObject(const char* name); + virtual ~ScriptObject(); private: @@ -338,7 +333,8 @@ class TC_GAME_API WorldMapScript : public ScriptObject, public MapScript<Map> WorldMapScript(const char* name, uint32 mapId); }; -class TC_GAME_API InstanceMapScript : public ScriptObject, public MapScript<InstanceMap> +class TC_GAME_API InstanceMapScript + : public ScriptObject, public MapScript<InstanceMap> { protected: @@ -834,13 +830,6 @@ class TC_GAME_API GroupScript : public ScriptObject virtual void OnDisband(Group* /*group*/) { } }; - -// namespace -// { - typedef std::list<std::string> UnusedScriptNamesContainer; - TC_GAME_API extern UnusedScriptNamesContainer UnusedScriptNames; -// } - // Manages registration, loading, and execution of scripts. class TC_GAME_API ScriptMgr { @@ -853,14 +842,14 @@ class TC_GAME_API ScriptMgr void FillSpellSummary(); void LoadDatabase(); + void IncreaseScriptCount() { ++_scriptCount; } + void DecreaseScriptCount() { --_scriptCount; } + public: /* Initialization */ static ScriptMgr* instance(); void Initialize(); - const char* ScriptsVersion() const { return "Integrated Trinity Scripts"; } - - void IncrementScriptCount() { ++_scriptCount; } uint32 GetScriptCount() const { return _scriptCount; } typedef void(*ScriptLoaderCallbackType)(); @@ -873,10 +862,25 @@ class TC_GAME_API ScriptMgr } public: /* Script contexts */ - void BeginScriptContext(std::string const& context); - void FinishScriptContext(); - + /// 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<ModuleReference> AcquireModuleReferenceOfScriptName( + std::string const& scriptname) const; public: /* Unloading */ @@ -886,7 +890,7 @@ class TC_GAME_API ScriptMgr void CreateSpellScripts(uint32 spellId, std::list<SpellScript*>& scriptVector); void CreateAuraScripts(uint32 spellId, std::list<AuraScript*>& scriptVector); - void CreateSpellScriptLoaders(uint32 spellId, std::vector<std::pair<SpellScriptLoader*, std::multimap<uint32, uint32>::iterator> >& scriptVector); + SpellScriptLoader* GetSpellScriptLoader(uint32 scriptId); public: /* ServerScript */ diff --git a/src/server/game/Spells/SpellScript.cpp b/src/server/game/Spells/SpellScript.cpp index 6876f8fa7ef..e2598386466 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" #include "SpellMgr.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 1d4efd8073d..539bc54cc94 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 <stack> 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<ModuleReference> 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 cd6d016ffa3..4b64ef0bbd8 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -1800,7 +1800,6 @@ void World::SetInitialWorldSettings() TC_LOG_INFO("server.loading", "Initializing Scripts..."); sScriptMgr->Initialize(); sScriptMgr->OnConfigLoad(false); // must be done after the ScriptMgr has been properly initialized - sScriptReloadMgr->Initialize(); TC_LOG_INFO("server.loading", "Validating spell scripts..."); sObjectMgr->ValidateSpellScripts(); diff --git a/src/server/scripts/Commands/cs_reload.cpp b/src/server/scripts/Commands/cs_reload.cpp index cd91ac1a5ce..eb28a8adae4 100644 --- a/src/server/scripts/Commands/cs_reload.cpp +++ b/src/server/scripts/Commands/cs_reload.cpp @@ -393,7 +393,7 @@ public: 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; } |