diff --git a/addons/source-python/packages/source-python/cvars/__init__.py b/addons/source-python/packages/source-python/cvars/__init__.py old mode 100644 new mode 100755 index 66177aede..516a2a380 --- a/addons/source-python/packages/source-python/cvars/__init__.py +++ b/addons/source-python/packages/source-python/cvars/__init__.py @@ -6,6 +6,8 @@ # >> IMPORTS # ============================================================================= # Source.Python Imports +# Core +from core import AutoUnload # Cvars from _cvars import ConVar from _cvars import _Cvar @@ -24,5 +26,68 @@ # >> ALL DECLARATION # ============================================================================= __all__ = ('ConVar', + 'ConVarChanged', 'cvar', ) + + +# ============================================================================= +# >> CLASSES +# ============================================================================= +class ConVarChanged(AutoUnload): + """ConVarChanged decorator class.""" + + def __init__(self, *convars): + """Store the convars.""" + self._convars = () + self.callback = None + + # Validate convars + if not convars: + raise ValueError('At least one convar is required.') + + _convars = [] + for convar in convars: + if not isinstance(convar, (str, ConVar)): + raise ValueError('Given convar is not ConVar or ConVar name.') + + elif isinstance(convar, str): + convar_name = convar + convar = cvar.find_var(convar_name) + if convar is None: + raise ValueError( + f'"{convar_name}" is not a valid ConVar name.') + + _convars.append(convar) + + self._convars = tuple(_convars) + + def __call__(self, callback): + """Store the callback and add it to convars.""" + # Is the callback callable? + if not callable(callback): + raise ValueError('Given callback is not callable.') + + # Store the callback + self.callback = callback + + # Loop through all convars + for convar in self._convars: + + # Add the callback + convar.add_changed_callback(self.callback) + + # Return the callback + return self.callback + + def _unload_instance(self): + """Remove the callback from convars.""" + # Was no callback registered? + if self.callback is None: + return + + # Loop through all convars + for convar in self._convars: + + # Remove the callback + convar.remove_changed_callback(self.callback) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt old mode 100644 new mode 100755 index 9bb21297a..889a15441 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -171,6 +171,7 @@ Set(SOURCEPYTHON_CVARS_MODULE_HEADERS ) Set(SOURCEPYTHON_CVARS_MODULE_SOURCES + core/modules/cvars/cvars.cpp core/modules/cvars/cvars_wrap.cpp ) diff --git a/src/core/modules/cvars/cvars.cpp b/src/core/modules/cvars/cvars.cpp new file mode 100755 index 000000000..0dc945a32 --- /dev/null +++ b/src/core/modules/cvars/cvars.cpp @@ -0,0 +1,217 @@ +/** +* ============================================================================= +* Source Python +* Copyright (C) 2012-2015 Source Python Development Team. 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, the Source Python Team gives you permission +* to link the code of this program (as well as its derivative works) to +* "Half-Life 2," the "Source Engine," 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, the Source.Python +* Development Team grants this exception to all derivative works. +*/ + +//----------------------------------------------------------------------------- +// Includes. +//----------------------------------------------------------------------------- +#include "cvars.h" + + +//----------------------------------------------------------------------------- +// Global ConVar changed callback mapping. +//----------------------------------------------------------------------------- +typedef std::vector ChangedCallbacks; +typedef std::unordered_map ConVarMap; +ConVarMap g_ConVarMap; + + +//----------------------------------------------------------------------------- +// ConVar extension class. +//----------------------------------------------------------------------------- +boost::shared_ptr ConVarExt::__init__(const char* name, const char* value, + const char* description, int flags, object min_value, object max_value) +{ + if (!name || name[0] == '\0') + BOOST_RAISE_EXCEPTION(PyExc_ValueError, "An empty string is not a valid ConVar name.") + + float fMin = 0; + float fMax = 0; + + try { + fMin = extract(min_value); + } + catch (...) { + PyErr_Clear(); + } + + try { + fMax = extract(max_value); + } + catch (...) { + PyErr_Clear(); + } + + ConVar *pConVar = g_pCVar->FindVar(name); + if (!pConVar) + { + // Find if the command already exists + ConCommand* pConCommand = g_pCVar->FindCommand(name); + if (pConCommand) + BOOST_RAISE_EXCEPTION(PyExc_ValueError, "ConCommand already exists.") + + ConVar* pConVar = new ConVar(strdup(name), strdup(value), flags, + strdup(description), !min_value.is_none(), fMin, !max_value.is_none(), fMax); + + return boost::shared_ptr(pConVar, &NeverDeleteDeleter); + } + + return boost::shared_ptr(pConVar, &NeverDeleteDeleter); +} + +bool ConVarExt::HasMin(ConVar* pConVar) +{ + float fMin; + return pConVar->GetMin(fMin); +} + +bool ConVarExt::HasMax(ConVar* pConVar) +{ + float fMax; + return pConVar->GetMax(fMax); +} + +float ConVarExt::GetMin(ConVar* pConVar) +{ + float fMin; + pConVar->GetMin(fMin); + return fMin; +} + +float ConVarExt::GetMax(ConVar* pConVar) +{ + float fMax; + pConVar->GetMax(fMax); + return fMax; +} + +void ConVarExt::SetValue(ConVar* pConVar, bool bValue) +{ + pConVar->SetValue(bValue); +} + +void ConVarExt::MakePublic(ConVar* pConVar) +{ + pConVar->m_nFlags |= FCVAR_NOTIFY; + g_pCVar->CallGlobalChangeCallbacks(pConVar, pConVar->GetString(), pConVar->GetFloat()); +} + +void ConVarExt::RemovePublic(ConVar* pConVar) +{ + pConVar->m_nFlags &= ~FCVAR_NOTIFY; + g_pCVar->CallGlobalChangeCallbacks(pConVar, pConVar->GetString(), pConVar->GetFloat()); +} + +void ConVarExt::ChangedCallback(IConVar* var, const char* pOldValue, float flOldValue) +{ + ConVarMap::iterator map_it = g_ConVarMap.find(var->GetName()); + if (map_it == g_ConVarMap.end()) + return; + + ConVar* pConVar = static_cast(var); + + ChangedCallbacks& callables = map_it->second; + for (ChangedCallbacks::iterator it = callables.begin(); it != callables.end(); ++it) + { + BEGIN_BOOST_PY() + (*it)(ptr(pConVar), pOldValue, pConVar->GetString()); + END_BOOST_PY_NORET() + } +} + +void ConVarExt::AddChangedCallback(ConVar* pConVar, PyObject* pCallable) +{ + // Get the object instance of the callable + object oCallable = object(handle<>(borrowed(pCallable))); + + ChangedCallbacks& callables = g_ConVarMap[pConVar->GetName()]; + if (!callables.size()) + { + if (!installed) + { + g_pCVar->InstallGlobalChangeCallback(ChangedCallback); + installed = true; + } + } + else + { + for (ChangedCallbacks::iterator it = callables.begin(); it != callables.end(); ++it) + { + if (is_same_func(oCallable, *it)) + BOOST_RAISE_EXCEPTION(PyExc_ValueError, "Callback already registered.") + } + } + + callables.push_back(oCallable); +} + +void ConVarExt::RemoveChangedCallback(ConVar* pConVar, PyObject* pCallable) +{ + ConVarMap::iterator map_it = g_ConVarMap.find(pConVar->GetName()); + if (map_it == g_ConVarMap.end()) + BOOST_RAISE_EXCEPTION(PyExc_ValueError, "Callback not registered.") + + // Get the object instance of the callable + object oCallable = object(handle<>(borrowed(pCallable))); + + ChangedCallbacks& callables = map_it->second; + for (ChangedCallbacks::iterator it = callables.begin();;) + { + if(it == callables.end()) + BOOST_RAISE_EXCEPTION(PyExc_ValueError, "Callback not registered.") + + if (is_same_func(oCallable, *it)) + { + callables.erase(it); + break; + } + else + { + ++it; + } + } + + if (!callables.size()) + { + g_ConVarMap.erase(map_it); + if (!g_ConVarMap.size()) + { + g_pCVar->RemoveGlobalChangeCallback(ChangedCallback); + installed = false; + } + } +} + +void ConVarExt::ClearCallback() +{ + if (installed) + { + g_ConVarMap.clear(); + g_pCVar->RemoveGlobalChangeCallback(ChangedCallback); + installed = false; + } +} + +bool ConVarExt::installed = false; diff --git a/src/core/modules/cvars/cvars.h b/src/core/modules/cvars/cvars.h old mode 100644 new mode 100755 index bfa2079f2..8c9f1e4bb --- a/src/core/modules/cvars/cvars.h +++ b/src/core/modules/cvars/cvars.h @@ -30,8 +30,17 @@ //----------------------------------------------------------------------------- // Includes. //----------------------------------------------------------------------------- +// C++ +#include +#include + +// This is required for accessing m_nFlags without patching convar.h +#define private public #include "convar.h" +#undef private + #include "utilities/sp_util.h" +#include "modules/listeners/listeners_manager.h" //----------------------------------------------------------------------------- @@ -41,82 +50,25 @@ class ConVarExt { public: static boost::shared_ptr __init__(const char* name, const char* value, - const char* description, int flags, object min_value, object max_value) - { - if (!name || name[0] == '\0') - BOOST_RAISE_EXCEPTION(PyExc_ValueError, "An empty string is not a valid ConVar name.") - - float fMin = 0; - float fMax = 0; - - try { - fMin = extract(min_value); - } - catch (...) { - PyErr_Clear(); - } - - try { - fMax = extract(max_value); - } - catch (...) { - PyErr_Clear(); - } - - ConVar *pConVar = g_pCVar->FindVar(name); - if (!pConVar) - { - ConVar* pConVar = new ConVar(strdup(name), strdup(value), flags, - strdup(description), !min_value.is_none(), fMin, !max_value.is_none(), fMax); - - return boost::shared_ptr(pConVar, &NeverDeleteDeleter); - } - - return boost::shared_ptr(pConVar, &NeverDeleteDeleter); - } - - static bool HasMin(ConVar* pConVar) - { - float fMin; - return pConVar->GetMin(fMin); - } + const char* description, int flags, object min_value, object max_value); - static bool HasMax(ConVar* pConVar) - { - float fMax; - return pConVar->GetMax(fMax); - } + static bool HasMin(ConVar* pConVar); + static bool HasMax(ConVar* pConVar); - static float GetMin(ConVar* pConVar) - { - float fMin; - pConVar->GetMin(fMin); - return fMin; - } + static float GetMin(ConVar* pConVar); + static float GetMax(ConVar* pConVar); - static bool GetMax(ConVar* pConVar) - { - float fMax; - pConVar->GetMax(fMax); - return fMax; - } + static void SetValue(ConVar* pConVar, bool bValue); - static void SetValue(ConVar* pConVar, bool bValue) - { - pConVar->SetValue(bValue); - } + static void MakePublic(ConVar* pConVar); + static void RemovePublic(ConVar* pConVar); - static void MakePublic(ConVar* pConVar) - { - pConVar->m_nFlags |= FCVAR_NOTIFY; - g_pCVar->CallGlobalChangeCallbacks(pConVar, pConVar->GetString(), pConVar->GetFloat()); - } + static void ChangedCallback(IConVar* var, const char* pOldValue, float flOldValue); + static void AddChangedCallback(ConVar* pConVar, PyObject* pCallable); + static void RemoveChangedCallback(ConVar* pConVar, PyObject* pCallable); + static void ClearCallback(); - static void RemovePublic(ConVar* pConVar) - { - pConVar->m_nFlags &= ~FCVAR_NOTIFY; - g_pCVar->CallGlobalChangeCallbacks(pConVar, pConVar->GetString(), pConVar->GetFloat()); - } + static bool installed; }; diff --git a/src/core/modules/cvars/cvars_wrap.cpp b/src/core/modules/cvars/cvars_wrap.cpp old mode 100644 new mode 100755 index 997d410f6..f4d66b954 --- a/src/core/modules/cvars/cvars_wrap.cpp +++ b/src/core/modules/cvars/cvars_wrap.cpp @@ -299,7 +299,19 @@ void export_convar(scope _cvars) &ConVarExt::RemovePublic, "Remove the notify flag and make the console variable no longer public." ) - + + .def("add_changed_callback", + &ConVarExt::AddChangedCallback, + "Add a callable object that will be called when the ConVar is changed.", + args("callable") + ) + + .def("remove_changed_callback", + &ConVarExt::RemoveChangedCallback, + "Remove a callable object that will be called when the ConVar is changed.", + args("callable") + ) + // Special methods... .def("__float__", &ConVar::GetFloat, diff --git a/src/core/sp_main.cpp b/src/core/sp_main.cpp index 6f1be1dcb..5b0fdcf1b 100755 --- a/src/core/sp_main.cpp +++ b/src/core/sp_main.cpp @@ -58,6 +58,7 @@ #include "manager.h" #include "modules/listeners/listeners_manager.h" +#include "modules/cvars/cvars.h" #include "utilities/conversions.h" #include "modules/entities/entities_entity.h" #include "modules/core/core.h" @@ -403,6 +404,9 @@ void CSourcePython::Unload( void ) DevMsg(1, MSG_PREFIX "Clearing convar changed listener...\n"); GetOnConVarChangedListenerManager()->clear(); + DevMsg(1, MSG_PREFIX "Clearing convar changed callbacks...\n"); + ConVarExt::ClearCallback(); + DevMsg(1, MSG_PREFIX "Unhooking all functions...\n"); GetHookManager()->UnhookAllFunctions(); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy