diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Bindings/LuaState.cpp | 4 | ||||
-rw-r--r-- | src/Bindings/ManualBindings.cpp | 94 | ||||
-rw-r--r-- | src/Bindings/Plugin.h | 15 | ||||
-rw-r--r-- | src/Bindings/PluginLua.cpp | 109 | ||||
-rw-r--r-- | src/Bindings/PluginLua.h | 23 | ||||
-rw-r--r-- | src/Bindings/PluginManager.cpp | 47 | ||||
-rw-r--r-- | src/Bindings/PluginManager.h | 49 | ||||
-rw-r--r-- | src/Server.cpp | 35 |
8 files changed, 167 insertions, 209 deletions
diff --git a/src/Bindings/LuaState.cpp b/src/Bindings/LuaState.cpp index 85c6c2e12..0d958cc20 100644 --- a/src/Bindings/LuaState.cpp +++ b/src/Bindings/LuaState.cpp @@ -1021,6 +1021,10 @@ bool cLuaState::GetStackValue(int a_StackPos, cCallback & a_Callback) bool cLuaState::GetStackValue(int a_StackPos, cCallbackPtr & a_Callback) { + if (a_Callback == nullptr) + { + a_Callback = std::make_shared<cCallback>(); + } return a_Callback->RefStack(*this, a_StackPos); } diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp index 28120fdc5..12cdd3d71 100644 --- a/src/Bindings/ManualBindings.cpp +++ b/src/Bindings/ManualBindings.cpp @@ -43,6 +43,50 @@ +//////////////////////////////////////////////////////////////////////////////// +// LuaCommandHandler: + +/** Defines a bridge between cPluginManager::cCommandHandler and cLuaState::cCallback. */ +class LuaCommandHandler: + public cPluginManager::cCommandHandler +{ +public: + LuaCommandHandler(cLuaState::cCallbackPtr a_Callback): + m_Callback(a_Callback) + { + } + + virtual bool ExecuteCommand( + const AStringVector & a_Split, + cPlayer * a_Player, + const AString & a_Command, + cCommandOutputCallback * a_Output + ) override + { + bool res = false; + AString s; + if (!m_Callback->Call(a_Split, a_Player, a_Command, cLuaState::Return, res, s)) + { + return false; + } + if (res && (a_Output != nullptr) && !s.empty()) + { + a_Output->Out(s); + } + return res; + } + +protected: + cLuaState::cCallbackPtr m_Callback; +}; + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cManualBindings: + // Better error reporting for Lua int cManualBindings::tolua_do_error(lua_State * L, const char * a_pMsg, tolua_Error * a_pToLuaError) { @@ -1268,12 +1312,13 @@ static int tolua_cPluginManager_ForEachConsoleCommand(lua_State * tolua_S) -static int tolua_cPluginManager_BindCommand(lua_State * L) +static int tolua_cPluginManager_BindCommand(lua_State * a_LuaState) { /* Function signatures: cPluginManager:BindCommand(Command, Permission, Function, HelpString) cPluginManager.BindCommand(Command, Permission, Function, HelpString) -- without the "self" param */ + cLuaState L(a_LuaState); cPluginLua * Plugin = cManualBindings::GetLuaPlugin(L); if (Plugin == nullptr) { @@ -1306,29 +1351,24 @@ static int tolua_cPluginManager_BindCommand(lua_State * L) return 0; } cPluginManager * self = cPluginManager::Get(); - AString Command (tolua_tocppstring(L, idx, "")); - AString Permission(tolua_tocppstring(L, idx + 1, "")); - AString HelpString(tolua_tocppstring(L, idx + 3, "")); - - // Store the function reference: - lua_pop(L, 1); // Pop the help string off the stack - int FnRef = luaL_ref(L, LUA_REGISTRYINDEX); // Store function reference - if (FnRef == LUA_REFNIL) + AString Command, Permission, HelpString; + cLuaState::cCallbackPtr Handler; + L.GetStackValues(idx, Command, Permission, Handler, HelpString); + if (!Handler->IsValid()) { LOGERROR("\"BindCommand\": Cannot create a function reference. Command \"%s\" not bound.", Command.c_str()); return 0; } - if (!self->BindCommand(Command, Plugin, Permission, HelpString)) + auto CommandHandler = std::make_shared<LuaCommandHandler>(Handler); + if (!self->BindCommand(Command, Plugin, CommandHandler, Permission, HelpString)) { // Refused. Possibly already bound. Error message has been given, display the callstack: - cLuaState LS(L); - LS.LogStackTrace(); + L.LogStackTrace(); return 0; } - Plugin->BindCommand(Command, FnRef); - lua_pushboolean(L, true); + L.Push(true); return 1; } @@ -1336,7 +1376,7 @@ static int tolua_cPluginManager_BindCommand(lua_State * L) -static int tolua_cPluginManager_BindConsoleCommand(lua_State * L) +static int tolua_cPluginManager_BindConsoleCommand(lua_State * a_LuaState) { /* Function signatures: cPluginManager:BindConsoleCommand(Command, Function, HelpString) @@ -1344,6 +1384,7 @@ static int tolua_cPluginManager_BindConsoleCommand(lua_State * L) */ // Get the plugin identification out of LuaState: + cLuaState L(a_LuaState); cPluginLua * Plugin = cManualBindings::GetLuaPlugin(L); if (Plugin == nullptr) { @@ -1375,28 +1416,23 @@ static int tolua_cPluginManager_BindConsoleCommand(lua_State * L) return 0; } cPluginManager * self = cPluginManager::Get(); - AString Command (tolua_tocppstring(L, idx, "")); - AString HelpString(tolua_tocppstring(L, idx + 2, "")); - - // Store the function reference: - lua_pop(L, 1); // Pop the help string off the stack - int FnRef = luaL_ref(L, LUA_REGISTRYINDEX); // Store function reference - if (FnRef == LUA_REFNIL) + AString Command, HelpString; + cLuaState::cCallbackPtr Handler; + L.GetStackValues(idx, Command, Handler, HelpString); + if (!Handler->IsValid()) { - LOGERROR("\"BindConsoleCommand\": Cannot create a function reference. Console Command \"%s\" not bound.", Command.c_str()); + LOGERROR("\"BindConsoleCommand\": Cannot create a function reference. Console command \"%s\" not bound.", Command.c_str()); return 0; } - if (!self->BindConsoleCommand(Command, Plugin, HelpString)) + auto CommandHandler = std::make_shared<LuaCommandHandler>(Handler); + if (!self->BindConsoleCommand(Command, Plugin, CommandHandler, HelpString)) { // Refused. Possibly already bound. Error message has been given, display the callstack: - cLuaState LS(L); - LS.LogStackTrace(); + L.LogStackTrace(); return 0; } - - Plugin->BindConsoleCommand(Command, FnRef); - lua_pushboolean(L, true); + L.Push(true); return 1; } diff --git a/src/Bindings/Plugin.h b/src/Bindings/Plugin.h index 3ee417361..3276fde67 100644 --- a/src/Bindings/Plugin.h +++ b/src/Bindings/Plugin.h @@ -110,21 +110,6 @@ public: virtual bool OnWorldStarted (cWorld & a_World) = 0; virtual bool OnWorldTick (cWorld & a_World, std::chrono::milliseconds a_Dt, std::chrono::milliseconds a_LastTickDurationMSec) = 0; - /** Handles the command split into a_Split, issued by player a_Player. - Command permissions have already been checked. - Returns true if command handled successfully. */ - virtual bool HandleCommand(const AStringVector & a_Split, cPlayer & a_Player, const AString & a_FullCommand) = 0; - - /** Handles the console command split into a_Split. - Returns true if command handled successfully. Output is to be sent to the a_Output callback. */ - virtual bool HandleConsoleCommand(const AStringVector & a_Split, cCommandOutputCallback & a_Output, const AString & a_FullCommand) = 0; - - /** All bound commands are to be removed, do any language-dependent cleanup here */ - virtual void ClearCommands(void) {} - - /** All bound console commands are to be removed, do any language-dependent cleanup here */ - virtual void ClearConsoleCommands(void) {} - // tolua_begin const AString & GetName(void) const { return m_Name; } void SetName(const AString & a_Name) { m_Name = a_Name; } diff --git a/src/Bindings/PluginLua.cpp b/src/Bindings/PluginLua.cpp index 04ea76f0a..ee0d289a1 100644 --- a/src/Bindings/PluginLua.cpp +++ b/src/Bindings/PluginLua.cpp @@ -62,9 +62,7 @@ void cPluginLua::Close(void) return; } - // Remove the command bindings and web tabs: - ClearCommands(); - ClearConsoleCommands(); + // Remove the web tabs: ClearWebTabs(); // Release all the references in the hook map: @@ -994,91 +992,6 @@ bool cPluginLua::OnWorldTick(cWorld & a_World, std::chrono::milliseconds a_Dt, s -bool cPluginLua::HandleCommand(const AStringVector & a_Split, cPlayer & a_Player, const AString & a_FullCommand) -{ - ASSERT(!a_Split.empty()); - cOperation op(*this); - CommandMap::iterator cmd = m_Commands.find(a_Split[0]); - if (cmd == m_Commands.end()) - { - LOGWARNING("Command handler is registered in cPluginManager but not in cPlugin, wtf? Command \"%s\".", a_Split[0].c_str()); - return false; - } - - bool res = false; - op().Call(cmd->second, a_Split, &a_Player, a_FullCommand, cLuaState::Return, res); - return res; -} - - - - - -bool cPluginLua::HandleConsoleCommand(const AStringVector & a_Split, cCommandOutputCallback & a_Output, const AString & a_FullCommand) -{ - ASSERT(!a_Split.empty()); - cOperation op(*this); - CommandMap::iterator cmd = m_ConsoleCommands.find(a_Split[0]); - if (cmd == m_ConsoleCommands.end()) - { - LOGWARNING("Console command handler is registered in cPluginManager but not in cPlugin, wtf? Console command \"%s\", plugin \"%s\".", - a_Split[0].c_str(), GetName().c_str() - ); - return false; - } - - bool res = false; - AString str; - op().Call(cmd->second, a_Split, a_FullCommand, cLuaState::Return, res, str); - if (res && !str.empty()) - { - a_Output.Out(str); - } - return res; -} - - - - - -void cPluginLua::ClearCommands(void) -{ - cOperation op(*this); - - // Unreference the bound functions so that Lua can GC them - if (m_LuaState != nullptr) - { - for (CommandMap::iterator itr = m_Commands.begin(), end = m_Commands.end(); itr != end; ++itr) - { - luaL_unref(m_LuaState, LUA_REGISTRYINDEX, itr->second); - } - } - m_Commands.clear(); -} - - - - - -void cPluginLua::ClearConsoleCommands(void) -{ - cOperation op(*this); - - // Unreference the bound functions so that Lua can GC them - if (m_LuaState != nullptr) - { - for (CommandMap::iterator itr = m_ConsoleCommands.begin(), end = m_ConsoleCommands.end(); itr != end; ++itr) - { - luaL_unref(m_LuaState, LUA_REGISTRYINDEX, itr->second); - } - } - m_ConsoleCommands.clear(); -} - - - - - bool cPluginLua::CanAddOldStyleHook(int a_HookType) { const char * FnName = GetHookFnName(a_HookType); @@ -1227,26 +1140,6 @@ int cPluginLua::CallFunctionFromForeignState( -void cPluginLua::BindCommand(const AString & a_Command, int a_FnRef) -{ - ASSERT(m_Commands.find(a_Command) == m_Commands.end()); - m_Commands[a_Command] = a_FnRef; -} - - - - - -void cPluginLua::BindConsoleCommand(const AString & a_Command, int a_FnRef) -{ - ASSERT(m_ConsoleCommands.find(a_Command) == m_ConsoleCommands.end()); - m_ConsoleCommands[a_Command] = a_FnRef; -} - - - - - bool cPluginLua::CallbackWindowClosing(int a_FnRef, cWindow & a_Window, cPlayer & a_Player, bool a_CanRefuse) { ASSERT(a_FnRef != LUA_REFNIL); diff --git a/src/Bindings/PluginLua.h b/src/Bindings/PluginLua.h index 8e40ff584..9e6b53e75 100644 --- a/src/Bindings/PluginLua.h +++ b/src/Bindings/PluginLua.h @@ -138,23 +138,9 @@ public: virtual bool OnWorldStarted (cWorld & a_World) override; virtual bool OnWorldTick (cWorld & a_World, std::chrono::milliseconds a_Dt, std::chrono::milliseconds a_LastTickDurationMSec) override; - virtual bool HandleCommand(const AStringVector & a_Split, cPlayer & a_Player, const AString & a_FullCommand) override; - - virtual bool HandleConsoleCommand(const AStringVector & a_Split, cCommandOutputCallback & a_Output, const AString & a_FullCommand) override; - - virtual void ClearCommands(void) override; - - virtual void ClearConsoleCommands(void) override; - /** Returns true if the plugin contains the function for the specified hook type, using the old-style registration (#121) */ bool CanAddOldStyleHook(int a_HookType); - /** Binds the command to call the function specified by a Lua function reference. Simply adds to CommandMap. */ - void BindCommand(const AString & a_Command, int a_FnRef); - - /** Binds the console command to call the function specified by a Lua function reference. Simply adds to CommandMap. */ - void BindConsoleCommand(const AString & a_Command, int a_FnRef); - /** Calls the plugin-specified "cLuaWindow closing" callback. Returns true only if the callback returned true */ bool CallbackWindowClosing(int a_FnRef, cWindow & a_Window, cPlayer & a_Player, bool a_CanRefuse); @@ -186,9 +172,6 @@ public: } protected: - /** Maps command name into Lua function reference */ - typedef std::map<AString, int> CommandMap; - /** Provides an array of Lua function references */ typedef std::vector<cLuaState::cCallbackPtr> cLuaCallbacks; @@ -199,12 +182,6 @@ protected: /** The plugin's Lua state. */ cLuaState m_LuaState; - /** In-game commands that the plugin has registered. */ - CommandMap m_Commands; - - /** Console commands that the plugin has registered. */ - CommandMap m_ConsoleCommands; - /** Hooks that the plugin has registered. */ cHookMap m_HookMap; diff --git a/src/Bindings/PluginManager.cpp b/src/Bindings/PluginManager.cpp index 203450505..19d2e8b4d 100644 --- a/src/Bindings/PluginManager.cpp +++ b/src/Bindings/PluginManager.cpp @@ -1569,9 +1569,9 @@ cPluginManager::CommandResult cPluginManager::HandleCommand(cPlayer & a_Player, return crNoPermission; } - ASSERT(cmd->second.m_Plugin != nullptr); + ASSERT(cmd->second.m_Handler != nullptr); - if (!cmd->second.m_Plugin->HandleCommand(Split, a_Player, a_Command)) + if (!cmd->second.m_Handler->ExecuteCommand(Split, &a_Player, a_Command, nullptr)) { return crError; } @@ -1654,11 +1654,6 @@ void cPluginManager::RemoveHooks(cPlugin * a_Plugin) void cPluginManager::RemovePluginCommands(cPlugin * a_Plugin) { - if (a_Plugin != nullptr) - { - a_Plugin->ClearCommands(); - } - for (CommandMap::iterator itr = m_Commands.begin(); itr != m_Commands.end();) { if (itr->second.m_Plugin == a_Plugin) @@ -1694,7 +1689,13 @@ bool cPluginManager::IsPluginLoaded(const AString & a_PluginName) -bool cPluginManager::BindCommand(const AString & a_Command, cPlugin * a_Plugin, const AString & a_Permission, const AString & a_HelpString) +bool cPluginManager::BindCommand( + const AString & a_Command, + cPlugin * a_Plugin, + cCommandHandlerPtr a_Handler, + const AString & a_Permission, + const AString & a_HelpString +) { CommandMap::iterator cmd = m_Commands.find(a_Command); if (cmd != m_Commands.end()) @@ -1703,9 +1704,11 @@ bool cPluginManager::BindCommand(const AString & a_Command, cPlugin * a_Plugin, return false; } - m_Commands[a_Command].m_Plugin = a_Plugin; - m_Commands[a_Command].m_Permission = a_Permission; - m_Commands[a_Command].m_HelpString = a_HelpString; + auto & reg = m_Commands[a_Command]; + reg.m_Plugin = a_Plugin; + reg.m_Handler = a_Handler; + reg.m_Permission = a_Permission; + reg.m_HelpString = a_HelpString; return true; } @@ -1768,11 +1771,6 @@ cPluginManager::CommandResult cPluginManager::ForceExecuteCommand(cPlayer & a_Pl void cPluginManager::RemovePluginConsoleCommands(cPlugin * a_Plugin) { - if (a_Plugin != nullptr) - { - a_Plugin->ClearConsoleCommands(); - } - for (CommandMap::iterator itr = m_ConsoleCommands.begin(); itr != m_ConsoleCommands.end();) { if (itr->second.m_Plugin == a_Plugin) @@ -1792,7 +1790,12 @@ void cPluginManager::RemovePluginConsoleCommands(cPlugin * a_Plugin) -bool cPluginManager::BindConsoleCommand(const AString & a_Command, cPlugin * a_Plugin, const AString & a_HelpString) +bool cPluginManager::BindConsoleCommand( + const AString & a_Command, + cPlugin * a_Plugin, + cCommandHandlerPtr a_Handler, + const AString & a_HelpString +) { CommandMap::iterator cmd = m_ConsoleCommands.find(a_Command); if (cmd != m_ConsoleCommands.end()) @@ -1808,9 +1811,11 @@ bool cPluginManager::BindConsoleCommand(const AString & a_Command, cPlugin * a_P return false; } - m_ConsoleCommands[a_Command].m_Plugin = a_Plugin; - m_ConsoleCommands[a_Command].m_Permission = ""; - m_ConsoleCommands[a_Command].m_HelpString = a_HelpString; + auto & reg = m_ConsoleCommands[a_Command]; + reg.m_Plugin = a_Plugin; + reg.m_Handler = a_Handler; + reg.m_Permission = ""; + reg.m_HelpString = a_HelpString; return true; } @@ -1873,7 +1878,7 @@ bool cPluginManager::ExecuteConsoleCommand(const AStringVector & a_Split, cComma return (res == crExecuted); } - return cmd->second.m_Plugin->HandleConsoleCommand(a_Split, a_Output, a_Command); + return cmd->second.m_Handler->ExecuteCommand(a_Split, nullptr, a_Command, &a_Output); } diff --git a/src/Bindings/PluginManager.h b/src/Bindings/PluginManager.h index a97582fbe..0423d6af1 100644 --- a/src/Bindings/PluginManager.h +++ b/src/Bindings/PluginManager.h @@ -152,6 +152,7 @@ public: HOOK_MAX = HOOK_NUM_HOOKS - 1, } ; // tolua_export + /** Used as a callback for enumerating bound commands */ class cCommandEnumCallback { @@ -164,6 +165,30 @@ public: virtual bool Command(const AString & a_Command, const cPlugin * a_Plugin, const AString & a_Permission, const AString & a_HelpString) = 0; } ; + + /** Interface that must be provided by any class that implements a command handler, either in-game or console command. */ + class cCommandHandler + { + public: + // Force a virtual destructor in descendants + virtual ~cCommandHandler() {} + + /** Executes the specified in-game command. + a_Split is the command string, split at the spaces. + a_Player is the player executing the command, nullptr in case of the console. + a_Command is the entire command string. + a_Output is the sink into which the additional text returned by the command handler should be sent; only used for console commands. */ + virtual bool ExecuteCommand( + const AStringVector & a_Split, + cPlayer * a_Player, + const AString & a_Command, + cCommandOutputCallback * a_Output = nullptr + ) = 0; + }; + + typedef SharedPtr<cCommandHandler> cCommandHandlerPtr; + + /** The interface used for enumerating and extern-calling plugins */ typedef cItemCallback<cPlugin> cPluginCallback; @@ -281,8 +306,16 @@ public: /** Returns true if the specified plugin is loaded. */ bool IsPluginLoaded(const AString & a_PluginName); // tolua_export - /** Binds a command to the specified plugin. Returns true if successful, false if command already bound. */ - bool BindCommand(const AString & a_Command, cPlugin * a_Plugin, const AString & a_Permission, const AString & a_HelpString); // Exported in ManualBindings.cpp, without the a_Plugin param + /** Binds a command to the specified handler. + Returns true if successful, false if command already bound. + Exported in ManualBindings.cpp. */ + bool BindCommand( + const AString & a_Command, + cPlugin * a_Plugin, + cCommandHandlerPtr a_Handler, + const AString & a_Permission, + const AString & a_HelpString + ); /** Calls a_Callback for each bound command, returns true if all commands were enumerated */ bool ForEachCommand(cCommandEnumCallback & a_Callback); // Exported in ManualBindings.cpp @@ -302,8 +335,15 @@ public: /** Removes all console command bindings that the specified plugin has made */ void RemovePluginConsoleCommands(cPlugin * a_Plugin); - /** Binds a console command to the specified plugin. Returns true if successful, false if command already bound. */ - bool BindConsoleCommand(const AString & a_Command, cPlugin * a_Plugin, const AString & a_HelpString); // Exported in ManualBindings.cpp, without the a_Plugin param + /** Binds a console command to the specified handler. + Returns true if successful, false if command already bound. + Exported in ManualBindings.cpp. */ + bool BindConsoleCommand( + const AString & a_Command, + cPlugin * a_Plugin, + cCommandHandlerPtr a_Handler, + const AString & a_HelpString + ); /** Calls a_Callback for each bound console command, returns true if all commands were enumerated */ bool ForEachConsoleCommand(cCommandEnumCallback & a_Callback); // Exported in ManualBindings.cpp @@ -348,6 +388,7 @@ private: cPlugin * m_Plugin; AString m_Permission; // Not used for console commands AString m_HelpString; + cCommandHandlerPtr m_Handler; } ; typedef std::map<int, cPluginManager::PluginList> HookMap; diff --git a/src/Server.cpp b/src/Server.cpp index 5548e77d1..8405109de 100644 --- a/src/Server.cpp +++ b/src/Server.cpp @@ -607,18 +607,35 @@ void cServer::PrintHelp(const AStringVector & a_Split, cCommandOutputCallback & void cServer::BindBuiltInConsoleCommands(void) { + // Create an empty handler - the actual handling for the commands is performed before they are handed off to cPluginManager + class cEmptyHandler: + public cPluginManager::cCommandHandler + { + virtual bool ExecuteCommand( + const AStringVector & a_Split, + cPlayer * a_Player, + const AString & a_Command, + cCommandOutputCallback * a_Output = nullptr + ) override + { + return false; + } + }; + auto handler = std::make_shared<cEmptyHandler>(); + + // Register internal commands: cPluginManager * PlgMgr = cPluginManager::Get(); - PlgMgr->BindConsoleCommand("help", nullptr, "Shows the available commands"); - PlgMgr->BindConsoleCommand("reload", nullptr, "Reloads all plugins"); - PlgMgr->BindConsoleCommand("restart", nullptr, "Restarts the server cleanly"); - PlgMgr->BindConsoleCommand("stop", nullptr, "Stops the server cleanly"); - PlgMgr->BindConsoleCommand("chunkstats", nullptr, "Displays detailed chunk memory statistics"); - PlgMgr->BindConsoleCommand("load <pluginname>", nullptr, "Adds and enables the specified plugin"); - PlgMgr->BindConsoleCommand("unload <pluginname>", nullptr, "Disables the specified plugin"); - PlgMgr->BindConsoleCommand("destroyentities", nullptr, "Destroys all entities in all worlds"); + PlgMgr->BindConsoleCommand("help", nullptr, handler, "Shows the available commands"); + PlgMgr->BindConsoleCommand("reload", nullptr, handler, "Reloads all plugins"); + PlgMgr->BindConsoleCommand("restart", nullptr, handler, "Restarts the server cleanly"); + PlgMgr->BindConsoleCommand("stop", nullptr, handler, "Stops the server cleanly"); + PlgMgr->BindConsoleCommand("chunkstats", nullptr, handler, "Displays detailed chunk memory statistics"); + PlgMgr->BindConsoleCommand("load", nullptr, handler, "Adds and enables the specified plugin"); + PlgMgr->BindConsoleCommand("unload", nullptr, handler, "Disables the specified plugin"); + PlgMgr->BindConsoleCommand("destroyentities", nullptr, handler, "Destroys all entities in all worlds"); #if defined(_MSC_VER) && defined(_DEBUG) && defined(ENABLE_LEAK_FINDER) - PlgMgr->BindConsoleCommand("dumpmem", nullptr, " - Dumps all used memory blocks together with their callstacks into memdump.xml"); + PlgMgr->BindConsoleCommand("dumpmem", nullptr, handler, " - Dumps all used memory blocks together with their callstacks into memdump.xml"); #endif } |