From b899f2d4eee4fc103a9e4735638c5f0b8667695b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Tue, 3 Mar 2020 06:10:12 -0500 Subject: [PATCH 1/3] Added lazy-loading support to ListenerManager. Made OnEntityOutput lazy-loaded to fix loading conflict with SourceMod (see #306). --- .../packages/source-python/__init__.py | 7 ++-- .../source-python/listeners/__init__.py | 33 ++++++++++++++++++- .../modules/listeners/listeners_manager.cpp | 14 ++++++++ .../modules/listeners/listeners_manager.h | 6 ++++ src/core/modules/listeners/listeners_wrap.cpp | 14 ++++++-- 5 files changed, 67 insertions(+), 7 deletions(-) diff --git a/addons/source-python/packages/source-python/__init__.py b/addons/source-python/packages/source-python/__init__.py index 286dd4952..62e608f0f 100644 --- a/addons/source-python/packages/source-python/__init__.py +++ b/addons/source-python/packages/source-python/__init__.py @@ -158,10 +158,10 @@ def setup_data(): 'BaseEntityOutput', GameConfigObj(SP_DATA_PATH / 'entity_output' / 'CBaseEntityOutput.ini')) + from _entities import BaseEntityOutput try: _fire_output = entities._BaseEntityOutput.fire_output - from _entities import BaseEntityOutput BaseEntityOutput.fire_output = _fire_output except ValueError: from warnings import warn @@ -169,15 +169,14 @@ def setup_data(): 'Did not find address for BaseEntityOutput.fire_output. ' 'OnEntityOutput listener will not fire.' ) + BaseEntityOutput.fire_output = NotImplemented except AttributeError: from warnings import warn warn( 'BaseEntityOutput.fire_output not found. ' 'OnEntityOutput listener will not fire.' ) - else: - import listeners - _fire_output.add_pre_hook(listeners._pre_fire_output) + BaseEntityOutput.fire_output = NotImplemented # ============================================================================= diff --git a/addons/source-python/packages/source-python/listeners/__init__.py b/addons/source-python/packages/source-python/listeners/__init__.py index c7c19e220..d359bfb4f 100644 --- a/addons/source-python/packages/source-python/listeners/__init__.py +++ b/addons/source-python/packages/source-python/listeners/__init__.py @@ -24,6 +24,8 @@ from cvars import cvar # Engines from engines.server import server_game_dll +# Entities +from entities import BaseEntityOutput from entities.datamaps import Variant from entities.helpers import find_output_name # Memory @@ -157,7 +159,6 @@ on_plugin_loading_manager = ListenerManager() on_plugin_unloading_manager = ListenerManager() on_level_end_listener_manager = ListenerManager() -on_entity_output_listener_manager = ListenerManager() _check_for_update = ConVar( 'sp_check_for_update', @@ -281,6 +282,36 @@ class OnClientSettingsChanged(ListenerManagerDecorator): manager = on_client_settings_changed_listener_manager +class OnEntityOutpuListenerManager(ListenerManager): + """Register/unregister an EntityOutput listener.""" + + def initialize(self): + """Called when the first callback is being registered.""" + # Get the fire_output method + fire_output = BaseEntityOutput.fire_output + + # If the fire_output method is not implemented, exit the call + if BaseEntityOutput.fire_output is NotImplemented: + return + + # Register the hook on fire_output + fire_output.add_pre_hook(_pre_fire_output) + + def finalize(self): + """Called when the last callback is being unregistered.""" + # Get the fire_output method + fire_output = BaseEntityOutput.fire_output + + # If the fire_output method is not implemented, exit the call + if BaseEntityOutput.fire_output is NotImplemented: + return + + # Unregister the hook on fire_output + fire_output.remove_pre_hook(_pre_fire_output) + +on_entity_output_listener_manager = OnEntityOutpuListenerManager() + + class OnEntityOutput(ListenerManagerDecorator): """Register/unregister an EntityOutput listener.""" diff --git a/src/core/modules/listeners/listeners_manager.cpp b/src/core/modules/listeners/listeners_manager.cpp index 4beb53eba..7823586ba 100644 --- a/src/core/modules/listeners/listeners_manager.cpp +++ b/src/core/modules/listeners/listeners_manager.cpp @@ -48,6 +48,13 @@ void CListenerManager::RegisterListener(PyObject* pCallable) } } +void CListenerManager::register_listener(object self, PyObject *pCallable) +{ + CListenerManager &pSelf = extract(self); + if (!pSelf.GetCount()) self.attr("initialize")(); + pSelf.RegisterListener(pCallable); +} + //----------------------------------------------------------------------------- // Removes all instances of a callable from the CListenerManager vector. @@ -67,6 +74,13 @@ void CListenerManager::UnregisterListener(PyObject* pCallable) } } +void CListenerManager::unregister_listener(object self, PyObject *pCallable) +{ + CListenerManager &pSelf = extract(self); + pSelf.UnregisterListener(pCallable); + if (!pSelf.GetCount()) self.attr("finalize")(); +} + //----------------------------------------------------------------------------- // Notifies all registered callbacks. diff --git a/src/core/modules/listeners/listeners_manager.h b/src/core/modules/listeners/listeners_manager.h index ac10c87cc..70695e24c 100644 --- a/src/core/modules/listeners/listeners_manager.h +++ b/src/core/modules/listeners/listeners_manager.h @@ -92,6 +92,12 @@ class CListenerManager int FindCallback(object oCallback); + // Lazy-loading support + static void register_listener(object self, PyObject *pCallable); + static void unregister_listener(object self, PyObject *pCallable); + void initialize() {}; + void finalize() {}; + public: CUtlVector m_vecCallables; }; diff --git a/src/core/modules/listeners/listeners_wrap.cpp b/src/core/modules/listeners/listeners_wrap.cpp index 8df591e2a..302b51c32 100644 --- a/src/core/modules/listeners/listeners_wrap.cpp +++ b/src/core/modules/listeners/listeners_wrap.cpp @@ -84,13 +84,13 @@ void export_listener_managers(scope _listeners) { class_("ListenerManager") .def("register_listener", - &CListenerManager::RegisterListener, + &CListenerManager::register_listener, "Registers a callable object. If it was already registered it will be ignored.", args("callable") ) .def("unregister_listener", - &CListenerManager::UnregisterListener, + &CListenerManager::unregister_listener, "Removes a callable object. If it was not registered nothing will happen.", args("callable") ) @@ -119,6 +119,16 @@ void export_listener_managers(scope _listeners) &CListenerManager::clear, "Remove all registered callbacks." ) + + .def("initialize", + &CListenerManager::initialize, + "Called when the first callback is being registered." + ) + + .def("finalize", + &CListenerManager::finalize, + "Called when the last callback is being unregistered." + ) ; _listeners.attr("on_client_active_listener_manager") = object(ptr(GetOnClientActiveListenerManager())); From 97e56353cf12f3d93f5d87148e0edae6a5164a57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Tue, 3 Mar 2020 18:28:14 -0500 Subject: [PATCH 2/3] Update __init__.py Fixed a typo (thank @CookStar). Added missing entry to __all__ declaration. --- .../packages/source-python/listeners/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/addons/source-python/packages/source-python/listeners/__init__.py b/addons/source-python/packages/source-python/listeners/__init__.py index d359bfb4f..de347cadf 100644 --- a/addons/source-python/packages/source-python/listeners/__init__.py +++ b/addons/source-python/packages/source-python/listeners/__init__.py @@ -93,6 +93,7 @@ 'OnEntityCreated', 'OnEntityDeleted', 'OnEntityOutput', + 'OnEntityOutputListenerManager', 'OnEntityPreSpawned', 'OnEntitySpawned', 'OnLevelInit', @@ -282,7 +283,7 @@ class OnClientSettingsChanged(ListenerManagerDecorator): manager = on_client_settings_changed_listener_manager -class OnEntityOutpuListenerManager(ListenerManager): +class OnEntityOutputListenerManager(ListenerManager): """Register/unregister an EntityOutput listener.""" def initialize(self): @@ -309,7 +310,7 @@ def finalize(self): # Unregister the hook on fire_output fire_output.remove_pre_hook(_pre_fire_output) -on_entity_output_listener_manager = OnEntityOutpuListenerManager() +on_entity_output_listener_manager = OnEntityOutputListenerManager() class OnEntityOutput(ListenerManagerDecorator): From 56209cf5c5778503314fc85e2da2168adfa4920f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Fri, 6 Mar 2020 23:00:08 -0500 Subject: [PATCH 3/3] Replaced the static dispatchers with virtual overrides (thanks @Ayuto). Removed redundant attribute retrievals. --- .../source-python/listeners/__init__.py | 4 +- .../modules/listeners/listeners_manager.cpp | 42 ++++++++++++------- .../modules/listeners/listeners_manager.h | 11 ++--- src/core/modules/listeners/listeners_wrap.cpp | 8 ++-- 4 files changed, 38 insertions(+), 27 deletions(-) diff --git a/addons/source-python/packages/source-python/listeners/__init__.py b/addons/source-python/packages/source-python/listeners/__init__.py index de347cadf..d1682744e 100644 --- a/addons/source-python/packages/source-python/listeners/__init__.py +++ b/addons/source-python/packages/source-python/listeners/__init__.py @@ -292,7 +292,7 @@ def initialize(self): fire_output = BaseEntityOutput.fire_output # If the fire_output method is not implemented, exit the call - if BaseEntityOutput.fire_output is NotImplemented: + if fire_output is NotImplemented: return # Register the hook on fire_output @@ -304,7 +304,7 @@ def finalize(self): fire_output = BaseEntityOutput.fire_output # If the fire_output method is not implemented, exit the call - if BaseEntityOutput.fire_output is NotImplemented: + if fire_output is NotImplemented: return # Unregister the hook on fire_output diff --git a/src/core/modules/listeners/listeners_manager.cpp b/src/core/modules/listeners/listeners_manager.cpp index 7823586ba..e86966c6f 100644 --- a/src/core/modules/listeners/listeners_manager.cpp +++ b/src/core/modules/listeners/listeners_manager.cpp @@ -41,6 +41,9 @@ void CListenerManager::RegisterListener(PyObject* pCallable) // Is the callable already in the vector? if( !IsRegistered(oCallable) ) { + if (!GetCount()) + Initialize(); + m_vecCallables.AddToTail(oCallable); } else { @@ -48,13 +51,6 @@ void CListenerManager::RegisterListener(PyObject* pCallable) } } -void CListenerManager::register_listener(object self, PyObject *pCallable) -{ - CListenerManager &pSelf = extract(self); - if (!pSelf.GetCount()) self.attr("initialize")(); - pSelf.RegisterListener(pCallable); -} - //----------------------------------------------------------------------------- // Removes all instances of a callable from the CListenerManager vector. @@ -71,14 +67,10 @@ void CListenerManager::UnregisterListener(PyObject* pCallable) } else { m_vecCallables.Remove(index); - } -} -void CListenerManager::unregister_listener(object self, PyObject *pCallable) -{ - CListenerManager &pSelf = extract(self); - pSelf.UnregisterListener(pCallable); - if (!pSelf.GetCount()) self.attr("finalize")(); + if (!GetCount()) + Finalize(); + } } @@ -106,6 +98,28 @@ int CListenerManager::GetCount() } +//----------------------------------------------------------------------------- +// Called when the first callback is being registered. +//----------------------------------------------------------------------------- +void CListenerManager::Initialize() +{ + override initialize = get_override("initialize"); + if (!initialize.is_none()) + initialize(); +} + + +//----------------------------------------------------------------------------- +// Called when the last callback is being unregistered. +//----------------------------------------------------------------------------- +void CListenerManager::Finalize() +{ + override finalize = get_override("finalize"); + if (!finalize.is_none()) + finalize(); +} + + //----------------------------------------------------------------------------- // Return whether or not the given callback is registered. //----------------------------------------------------------------------------- diff --git a/src/core/modules/listeners/listeners_manager.h b/src/core/modules/listeners/listeners_manager.h index 70695e24c..31dfa8e9a 100644 --- a/src/core/modules/listeners/listeners_manager.h +++ b/src/core/modules/listeners/listeners_manager.h @@ -79,7 +79,7 @@ //----------------------------------------------------------------------------- // CListenerManager class. //----------------------------------------------------------------------------- -class CListenerManager +class CListenerManager: public wrapper { public: void RegisterListener(PyObject* pCallable); @@ -90,13 +90,10 @@ class CListenerManager object __getitem__(unsigned int index); void clear(); - int FindCallback(object oCallback); + virtual void Initialize(); + virtual void Finalize(); - // Lazy-loading support - static void register_listener(object self, PyObject *pCallable); - static void unregister_listener(object self, PyObject *pCallable); - void initialize() {}; - void finalize() {}; + int FindCallback(object oCallback); public: CUtlVector m_vecCallables; diff --git a/src/core/modules/listeners/listeners_wrap.cpp b/src/core/modules/listeners/listeners_wrap.cpp index 302b51c32..1bfc1546f 100644 --- a/src/core/modules/listeners/listeners_wrap.cpp +++ b/src/core/modules/listeners/listeners_wrap.cpp @@ -84,13 +84,13 @@ void export_listener_managers(scope _listeners) { class_("ListenerManager") .def("register_listener", - &CListenerManager::register_listener, + &CListenerManager::RegisterListener, "Registers a callable object. If it was already registered it will be ignored.", args("callable") ) .def("unregister_listener", - &CListenerManager::unregister_listener, + &CListenerManager::UnregisterListener, "Removes a callable object. If it was not registered nothing will happen.", args("callable") ) @@ -121,12 +121,12 @@ void export_listener_managers(scope _listeners) ) .def("initialize", - &CListenerManager::initialize, + &CListenerManager::Initialize, "Called when the first callback is being registered." ) .def("finalize", - &CListenerManager::finalize, + &CListenerManager::Finalize, "Called when the last callback is being unregistered." ) ;