diff --git a/bridge/include/IProviderCallbacks.h b/bridge/include/IProviderCallbacks.h index 1ddfd0ae..80a7bdac 100644 --- a/bridge/include/IProviderCallbacks.h +++ b/bridge/include/IProviderCallbacks.h @@ -35,6 +35,9 @@ class IProviderCallbacks public: // Called when a log message is printed. Return true to supercede. virtual bool OnLogPrint(const char *msg) = 0; + + // Called each frame tick. + virtual void OnThink(bool simulating) = 0; }; } // namespace SourceMod diff --git a/core/logic/AMBuilder b/core/logic/AMBuilder index bbc9310f..3098675d 100644 --- a/core/logic/AMBuilder +++ b/core/logic/AMBuilder @@ -80,6 +80,7 @@ binary.sources += [ 'LibrarySys.cpp', 'RootConsoleMenu.cpp', 'CDataPack.cpp', + 'frame_tasks.cpp', ] if builder.target_platform == 'windows': binary.sources += ['thread/WinThreads.cpp'] diff --git a/core/logic/PluginSys.cpp b/core/logic/PluginSys.cpp index 3ad689bc..d1a3caf5 100644 --- a/core/logic/PluginSys.cpp +++ b/core/logic/PluginSys.cpp @@ -43,6 +43,7 @@ #include "common_logic.h" #include "Translator.h" #include "Logger.h" +#include "frame_tasks.h" #include #include #include @@ -54,6 +55,7 @@ IdentityType_t g_PluginIdent = 0; CPlugin::CPlugin(const char *file) : m_serial(0), m_status(Plugin_Uncompiled), + m_WaitingToUnload(false), m_SilentFailure(false), m_FakeNativesMissing(false), m_LibraryMissing(false), @@ -1422,7 +1424,13 @@ void CPluginManager::TryRefreshDependencies(CPlugin *pPlugin) bool CPluginManager::UnloadPlugin(IPlugin *plugin) { - return ScheduleUnload((CPlugin *)plugin); + CPlugin *pPlugin = (CPlugin *)plugin; + + // If we're already in the unload queue, just wait. + if (pPlugin->WaitingToUnload()) + return false; + + return ScheduleUnload(pPlugin); } bool CPluginManager::ScheduleUnload(CPlugin *pPlugin) @@ -1431,11 +1439,12 @@ bool CPluginManager::ScheduleUnload(CPlugin *pPlugin) assert(m_plugins.find(pPlugin) != m_plugins.end()); IPluginContext *pContext = pPlugin->GetBaseContext(); - if (pContext && pContext->IsInExec()) - { - char buffer[255]; - ke::SafeSprintf(buffer, sizeof(buffer), "sm plugins unload %s\n", pPlugin->GetFilename()); - engine->ServerCommand(buffer); + if (pContext && pContext->IsInExec()) { + ke::Lambda callback = [this, pPlugin]() { + this->ScheduleUnload(pPlugin); + }; + pPlugin->SetWaitingToUnload(); + ScheduleTaskForNextFrame(ke::Move(callback)); return false; } diff --git a/core/logic/PluginSys.h b/core/logic/PluginSys.h index f237d477..ec70f709 100644 --- a/core/logic/PluginSys.h +++ b/core/logic/PluginSys.h @@ -222,29 +222,29 @@ public: */ IPhraseCollection *GetPhrases(); public: - /** - * Returns the modification time during last plugin load. - */ + // Returns the modification time during last plugin load. time_t GetTimeStamp(); - /** - * Returns the current modification time of the plugin file. - */ + // Returns the current modification time of the plugin file. time_t GetFileTimeStamp(); - /** - * Returns true if the plugin was running, but is now invalid. - */ + // Returns true if the plugin was running, but is now invalid. bool WasRunning(); + bool WaitingToUnload() const { + return m_WaitingToUnload; + } + void SetWaitingToUnload() { + m_WaitingToUnload = true; + } + Handle_t GetMyHandle(); bool AddFakeNative(IPluginFunction *pFunc, const char *name, SPVM_FAKENATIVE_FUNC func); void AddConfig(bool autoCreate, const char *cfg, const char *folder); size_t GetConfigCount(); AutoConfig *GetConfig(size_t i); - inline void AddLibrary(const char *name) - { + inline void AddLibrary(const char *name) { m_Libraries.push_back(name); } void LibraryActions(LibraryAction action); @@ -260,6 +260,7 @@ private: unsigned int m_serial; PluginStatus m_status; + bool m_WaitingToUnload; // Statuses that are set during failure. bool m_SilentFailure; diff --git a/core/logic/common_logic.cpp b/core/logic/common_logic.cpp index 807edad1..5f3c6a8f 100644 --- a/core/logic/common_logic.cpp +++ b/core/logic/common_logic.cpp @@ -51,6 +51,7 @@ #include "AdminCache.h" #include "ProfileTools.h" #include "Logger.h" +#include "frame_tasks.h" #include "sprintf.h" #include "LibrarySys.h" #include "RootConsoleMenu.h" @@ -137,6 +138,9 @@ public: bool OnLogPrint(const char *msg) override { return ::OnLogPrint(msg); } + void OnThink(bool simulating) override { + RunScheduledFrameTasks(simulating); + } } sProviderCallbackListener; static sm_logic_t logic = diff --git a/core/logic/frame_tasks.cpp b/core/logic/frame_tasks.cpp new file mode 100644 index 00000000..d2b98962 --- /dev/null +++ b/core/logic/frame_tasks.cpp @@ -0,0 +1,55 @@ +// vim: set ts=4 sw=4 tw=99 noet : +// ============================================================================= +// SourceMod +// Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. +// ============================================================================= + +// This program is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License, version 3.0, as published by the +// Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. + +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . + +// As a special exception, AlliedModders LLC gives you permission to link the +// code of this program (as well as its derivative works) to "Half-Life 2," the +// "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software +// by the Valve Corporation. You must obey the GNU General Public License in +// all respects for all other code used. Additionally, AlliedModders LLC grants +// this exception to all derivative works. AlliedModders LLC defines further +// exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), +// or . +#include "frame_tasks.h" +#include + +using namespace SourceMod; + +ke::Vector> sNextTasks; +ke::Vector> sWorkTasks; + +void +SourceMod::ScheduleTaskForNextFrame(ke::Lambda&& task) +{ + sNextTasks.append(ke::Forward(task)); +} + +void +SourceMod::RunScheduledFrameTasks(bool simulating) +{ + if (sNextTasks.empty()) + return; + + // Swap. + ke::Vector> temp(ke::Move(sNextTasks)); + sNextTasks = ke::Move(sWorkTasks); + sWorkTasks = ke::Move(temp); + + for (size_t i = 0; i < sWorkTasks.length(); i++) + sWorkTasks[i](); + sWorkTasks.clear(); +} \ No newline at end of file diff --git a/core/logic/frame_tasks.h b/core/logic/frame_tasks.h new file mode 100644 index 00000000..270417f7 --- /dev/null +++ b/core/logic/frame_tasks.h @@ -0,0 +1,40 @@ +// vim: set ts=4 sw=4 tw=99 noet : +// ============================================================================= +// SourceMod +// Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. +// ============================================================================= + +// This program is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License, version 3.0, as published by the +// Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. + +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . + +// As a special exception, AlliedModders LLC gives you permission to link the +// code of this program (as well as its derivative works) to "Half-Life 2," the +// "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software +// by the Valve Corporation. You must obey the GNU General Public License in +// all respects for all other code used. Additionally, AlliedModders LLC grants +// this exception to all derivative works. AlliedModders LLC defines further +// exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), +// or . +#ifndef _include_sourcemod_logic_frame_tasks_h_ +#define _include_sourcemod_logic_frame_tasks_h_ + +#include + +namespace SourceMod { + +void ScheduleTaskForNextFrame(ke::Lambda&& task); + +void RunScheduledFrameTasks(bool simulating); + +} + +#endif // _include_sourcemod_logic_frame_tasks_h_ \ No newline at end of file diff --git a/core/sourcemod.cpp b/core/sourcemod.cpp index 52d231fb..c9d9d231 100644 --- a/core/sourcemod.cpp +++ b/core/sourcemod.cpp @@ -46,11 +46,13 @@ #include #include #include +#include #include SH_DECL_HOOK6(IServerGameDLL, LevelInit, SH_NOATTRIB, false, bool, const char *, const char *, const char *, const char *, bool, bool); SH_DECL_HOOK0_void(IServerGameDLL, LevelShutdown, SH_NOATTRIB, false); SH_DECL_HOOK1_void(IServerGameDLL, GameFrame, SH_NOATTRIB, false, bool); +SH_DECL_HOOK1_void(IServerGameDLL, Think, SH_NOATTRIB, false, bool); SH_DECL_HOOK1_void(IVEngineServer, ServerCommand, SH_NOATTRIB, false, const char *); SourceModBase g_SourceMod; @@ -322,6 +324,8 @@ void SourceModBase::StartSourceMod(bool late) { g_pSourcePawn2->InstallWatchdogTimer(atoi(timeout) * 1000); } + + SH_ADD_HOOK(IServerGameDLL, Think, gamedll, SH_MEMBER(logicore.callbacks, &IProviderCallbacks::OnThink), false); } static bool g_LevelEndBarrier = false; @@ -539,6 +543,7 @@ void SourceModBase::ShutdownServices() SH_REMOVE_HOOK(IServerGameDLL, LevelShutdown, gamedll, SH_MEMBER(this, &SourceModBase::LevelShutdown), false); SH_REMOVE_HOOK(IServerGameDLL, GameFrame, gamedll, SH_MEMBER(&g_Timers, &TimerSystem::GameFrame), false); + SH_REMOVE_HOOK(IServerGameDLL, Think, gamedll, SH_MEMBER(logicore.callbacks, &IProviderCallbacks::OnThink), false); } void SourceModBase::LogMessage(IExtension *pExt, const char *format, ...)