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
This commit is contained in:
Naios
2016-03-11 17:09:26 +01:00
parent bc0f2b6e5a
commit 9cc97f226d
20 changed files with 1084 additions and 304 deletions
+16 -10
View File
@@ -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<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);
@@ -55,13 +56,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
+1 -3
View File
@@ -89,6 +89,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);
@@ -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<ChatCommand> 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;
};
@@ -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()
@@ -241,4 +241,10 @@ void LFGGroupScript::OnInviteMember(Group* group, ObjectGuid guid)
sLFGMgr->LeaveLfg(leader);
}
void AddSC_LFGScripts()
{
new LFGPlayerScript();
new LFGGroupScript();
}
} // namespace lfg
@@ -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
+20 -2
View File
@@ -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
@@ -519,6 +519,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();
@@ -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);
@@ -1086,8 +1086,10 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject>
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;
+46 -41
View File
@@ -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<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)
if (SpellScriptLoader* spellScriptLoader = sScriptMgr->GetSpellScriptLoader(itr->second.first))
{
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)
{
_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));
@@ -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)
+2 -1
View File
@@ -374,7 +374,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;
@@ -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<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);
@@ -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();
+7 -1
View File
@@ -37,6 +37,7 @@ class Unit;
class Player;
class GameObject;
class Creature;
class ModuleReference;
enum EncounterFrameType
{
@@ -141,7 +142,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() { }
@@ -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<ModuleReference> module_reference;
#endif // #ifndef TRINITY_API_USE_DYNAMIC_LINKING
};
template<class AI, class T>
@@ -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()
File diff suppressed because it is too large Load Diff
+27 -23
View File
@@ -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<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:
@@ -833,13 +829,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
{
@@ -852,14 +841,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)();
@@ -872,10 +861,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 */
@@ -885,7 +889,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 */
+7
View File
@@ -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
+11
View File
@@ -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
-1
View File
@@ -1971,7 +1971,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();
+1 -1
View File
@@ -391,7 +391,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;
}