From 38421494cb95c83701b6f58199d9f9d2528479dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Thu, 28 Nov 2019 08:32:50 -0500 Subject: [PATCH 01/59] Added cached properties implementation. --- .../source/developing/modules/core.cache.rst | 12 + .../packages/source-python/core/cache.py | 25 ++ .../packages/source-python/entities/_base.py | 13 +- .../packages/source-python/players/_base.py | 27 +- src/CMakeLists.txt | 12 + src/core/modules/core/core_cache.cpp | 211 +++++++++++++ src/core/modules/core/core_cache.h | 84 ++++++ src/core/modules/core/core_cache_wrap.cpp | 281 ++++++++++++++++++ .../modules/entities/entities_entity_wrap.cpp | 9 + src/core/utilities/wrap_macros.h | 21 +- 10 files changed, 672 insertions(+), 23 deletions(-) create mode 100644 addons/source-python/docs/source-python/source/developing/modules/core.cache.rst create mode 100644 addons/source-python/packages/source-python/core/cache.py create mode 100644 src/core/modules/core/core_cache.cpp create mode 100644 src/core/modules/core/core_cache.h create mode 100644 src/core/modules/core/core_cache_wrap.cpp diff --git a/addons/source-python/docs/source-python/source/developing/modules/core.cache.rst b/addons/source-python/docs/source-python/source/developing/modules/core.cache.rst new file mode 100644 index 000000000..9e0fcdebf --- /dev/null +++ b/addons/source-python/docs/source-python/source/developing/modules/core.cache.rst @@ -0,0 +1,12 @@ +core.cache module +================== + +.. automodule:: core.cache + :members: + :undoc-members: + :show-inheritance: + +.. autoclass:: _core._cache.CachedProperty + :members: + :undoc-members: + :show-inheritance: \ No newline at end of file diff --git a/addons/source-python/packages/source-python/core/cache.py b/addons/source-python/packages/source-python/core/cache.py new file mode 100644 index 000000000..5bef6d4e4 --- /dev/null +++ b/addons/source-python/packages/source-python/core/cache.py @@ -0,0 +1,25 @@ +# ../core/cache.py + +"""Provides caching functionality. + +.. data:: cached_property + An alias of :class:`core.cache.CachedProperty`. +""" + + +# ============================================================================= +# >> FORWARD IMPORTS +# ============================================================================= +# Source.Python Imports +# Core +from _core._cache import CachedProperty +from _core._cache import cached_property + + +# ============================================================================= +# >> ALL DECLARATION +# ============================================================================= +__all__ = [ + 'CachedProperty', + 'cached_property' +] diff --git a/addons/source-python/packages/source-python/entities/_base.py b/addons/source-python/packages/source-python/entities/_base.py index cbced0a01..bafd08dcd 100644 --- a/addons/source-python/packages/source-python/entities/_base.py +++ b/addons/source-python/packages/source-python/entities/_base.py @@ -17,6 +17,7 @@ # Core from core import GAME_NAME from core import BoostPythonClass +from core.cache import cached_property # Entities from entities.constants import INVALID_ENTITY_INDEX # Engines @@ -295,7 +296,7 @@ def is_networked(self): """ return True - @property + @cached_property def index(self): """Return the entity's index. @@ -316,30 +317,30 @@ def owner(self): except (ValueError, OverflowError): return None - @property + @cached_property def server_classes(self): """Yield all server classes for the entity.""" yield from server_classes.get_entity_server_classes(self) - @property + @cached_property def properties(self): """Iterate over all descriptors available for the entity.""" for server_class in self.server_classes: yield from server_class.properties - @property + @cached_property def inputs(self): """Iterate over all inputs available for the entity.""" for server_class in self.server_classes: yield from server_class.inputs - @property + @cached_property def outputs(self): """Iterate over all outputs available for the entity.""" for server_class in self.server_classes: yield from server_class.outputs - @property + @cached_property def keyvalues(self): """Iterate over all entity keyvalues available for the entity. diff --git a/addons/source-python/packages/source-python/players/_base.py b/addons/source-python/packages/source-python/players/_base.py index 3ef264893..4b73b6955 100644 --- a/addons/source-python/packages/source-python/players/_base.py +++ b/addons/source-python/packages/source-python/players/_base.py @@ -14,6 +14,7 @@ from bitbuffers import BitBufferWrite # Core from core import GAME_NAME +from core.cache import cached_property # Engines from engines.server import server from engines.server import engine_server @@ -89,7 +90,6 @@ def __init__(self, index, caching=True): """ PlayerMixin.__init__(self, index) Entity.__init__(self, index) - object.__setattr__(self, '_playerinfo', None) @classmethod def from_userid(cls, userid): @@ -101,7 +101,7 @@ def from_userid(cls, userid): """ return cls(index_from_userid(userid)) - @property + @cached_property def raw_steamid(self): """Return the player's unformatted SteamID. @@ -117,18 +117,15 @@ def permissions(self): """ return auth_manager.get_player_permissions_from_steamid(self.steamid) - @property + @cached_property def playerinfo(self): """Return player information. :rtype: PlayerInfo """ - if self._playerinfo is None: - playerinfo = playerinfo_from_index(self.index) - object.__setattr__(self, '_playerinfo', playerinfo) - return self._playerinfo + return playerinfo_from_index(self.index) - @property + @cached_property def userid(self): """Return the player's userid. @@ -136,7 +133,7 @@ def userid(self): """ return self.playerinfo.userid - @property + @cached_property def steamid(self): """Return the player's SteamID. @@ -157,7 +154,7 @@ def set_name(self, name): name = property(get_name, set_name) - @property + @cached_property def client(self): """Return the player's client instance. @@ -165,7 +162,7 @@ def client(self): """ return server.get_client(self.index - 1) - @property + @cached_property def base_client(self): """Return the player's base client instance. @@ -174,7 +171,7 @@ def base_client(self): from players import BaseClient return make_object(BaseClient, get_object_pointer(self.client) - 4) - @property + @cached_property def uniqueid(self): """Return the player's unique ID. @@ -182,7 +179,7 @@ def uniqueid(self): """ return uniqueid_from_playerinfo(self.playerinfo) - @property + @cached_property def address(self): """Return the player's IP address and port. @@ -208,6 +205,7 @@ def is_fake_client(self): """ return self.playerinfo.is_fake_client() + @cached_property def is_hltv(self): """Return whether the player is HLTV. @@ -215,6 +213,7 @@ def is_hltv(self): """ return self.playerinfo.is_hltv() + @cached_property def is_bot(self): """Return whether the player is a bot. @@ -249,7 +248,7 @@ def set_team(self, value): team = property(get_team, set_team) - @property + @cached_property def language(self): """Return the player's language. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a58d6d205..c6df2fd77 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -153,6 +153,15 @@ Set(SOURCEPYTHON_CORE_MODULE_SOURCES core/modules/core/core_wrap.cpp ) +Set(SOURCEPYTHON_CORE_CACHE_MODULE_HEADERS + core/modules/core/core_cache.h +) + +Set(SOURCEPYTHON_CORE_CACHE_MODULE_SOURCES + core/modules/core/core_cache.cpp + core/modules/core/core_cache_wrap.cpp +) + # ------------------------------------------------------------------ # Cvars module. # ------------------------------------------------------------------ @@ -466,6 +475,9 @@ Set(SOURCEPYTHON_WEAPONS_MODULE_SOURCES # All module source files # ------------------------------------------------------------------ Set(SOURCEPYTHON_MODULE_FILES + ${SOURCEPYTHON_CORE_CACHE_MODULE_HEADERS} + ${SOURCEPYTHON_CORE_CACHE_MODULE_SOURCES} + # CFunctionInfo must be exposed at first ${SOURCEPYTHON_MEMORY_MODULE_HEADERS} ${SOURCEPYTHON_MEMORY_MODULE_SOURCES} diff --git a/src/core/modules/core/core_cache.cpp b/src/core/modules/core/core_cache.cpp new file mode 100644 index 000000000..0a780adc6 --- /dev/null +++ b/src/core/modules/core/core_cache.cpp @@ -0,0 +1,211 @@ +/** +* ============================================================================= +* Source Python +* Copyright (C) 2012-2019 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 "core_cache.h" +#include "export_main.h" + + +//----------------------------------------------------------------------------- +// CCachedProperty class. +//----------------------------------------------------------------------------- +CCachedProperty::CCachedProperty( + object fget=object(), object fset=object(), object fdel=object(), const char *doc=NULL, + boost::python::tuple args=boost::python::tuple(), dict kwargs=dict()) +{ + set_getter(fget); + set_setter(fset); + set_deleter(fdel); + + m_szDocString = doc; + + m_args = args; + m_kwargs = kwargs; +} + + +object CCachedProperty::_callable_check(object function, const char *szName) +{ + if (!function.is_none() && !PyCallable_Check(function.ptr())) + BOOST_RAISE_EXCEPTION( + PyExc_TypeError, + "The given %s function is not callable.", szName + ); + + return function; +} + + +object CCachedProperty::get_getter() +{ + return m_fget; +} + +object CCachedProperty::set_getter(object fget) +{ + m_fget = _callable_check(fget, "getter"); + return object(ptr(this)); +} + + +object CCachedProperty::get_setter() +{ + return m_fset; +} + +object CCachedProperty::set_setter(object fset) +{ + m_fset = _callable_check(fset, "setter"); + return object(ptr(this)); +} + + +object CCachedProperty::get_deleter() +{ + return m_fdel; +} + +object CCachedProperty::set_deleter(object fdel) +{ + m_fdel = _callable_check(fdel, "deleter"); + return object(ptr(this)); +} + + +object CCachedProperty::get_owner() +{ + return m_owner; +} + +str CCachedProperty::get_name() +{ + return m_name; +} + + +void CCachedProperty::__set_name__(object owner, str name) +{ + m_owner = owner; + m_name = name; +} + + +object CCachedProperty::__get__(object instance, object owner=object()) +{ + if (instance.is_none()) + return object(ptr(this)); + + if (!m_name) + BOOST_RAISE_EXCEPTION( + PyExc_AttributeError, + "Unable to retrieve the value of an unbound property." + ); + + PyObject *pCache = PyObject_GetAttrString(instance.ptr(), "__dict__"); + + if (!PyDict_Check(pCache)) + BOOST_RAISE_EXCEPTION( + PyExc_ValueError, + "Cache dictionary is invalid." + ); + + Py_DECREF(pCache); + PyObject *pValue = PyDict_GetItemString(pCache, extract(m_name)); + if (pValue) + return object(handle<>(borrowed(pValue))); + + if (m_fget.is_none()) + BOOST_RAISE_EXCEPTION( + PyExc_AttributeError, + "Unable to retrieve the value of a property that have no getter function." + ); + + dict cache = extract(pCache); + cache[m_name] = m_fget( + *(make_tuple(handle<>(borrowed(instance.ptr()))) + m_args), + **m_kwargs + ); + + return cache[m_name]; +} + + +void CCachedProperty::__set__(object instance, object value) +{ + if (m_fset.is_none()) + BOOST_RAISE_EXCEPTION( + PyExc_AttributeError, + "Unable to assign the value of a property that have no setter function." + ); + + object result = m_fset( + *(make_tuple(handle<>(borrowed(instance.ptr())), value) + m_args), + **m_kwargs + ); + + dict cache = extract(instance.attr("__dict__")); + if (!result.is_none()) + cache[m_name] = result; + else + PyDict_DelItemString(cache.ptr(), extract(m_name)); +} + +void CCachedProperty::__delete__(object instance) +{ + if (!m_fdel.is_none()) + m_fdel( + *(make_tuple(handle<>(borrowed(instance.ptr()))) + m_args), + **m_kwargs + ); + + dict cache = extract(instance.attr("__dict__")); + PyDict_DelItemString(cache.ptr(), extract(m_name)); +} + +object CCachedProperty::__getitem__(str item) +{ + return m_kwargs[item]; +} + +void CCachedProperty::__setitem__(str item, object value) +{ + m_kwargs[item] = value; +} + + +CCachedProperty *CCachedProperty::wrap_descriptor(object descriptor, object owner=object(), str name=str()) +{ + CCachedProperty *pProperty = new CCachedProperty( + descriptor.attr("__get__"), descriptor.attr("__set__"), descriptor.attr("__delete__") + ); + + pProperty->m_szDocString = extract(descriptor.attr("__doc__")); + pProperty->__set_name__(owner, name); + + return pProperty; +} diff --git a/src/core/modules/core/core_cache.h b/src/core/modules/core/core_cache.h new file mode 100644 index 000000000..201ee7c99 --- /dev/null +++ b/src/core/modules/core/core_cache.h @@ -0,0 +1,84 @@ +/** +* ============================================================================= +* Source Python +* Copyright (C) 2012-2019 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. +*/ + +#ifndef _CORE_CACHE_H +#define _CORE_CACHE_H + +//----------------------------------------------------------------------------- +// Includes. +//----------------------------------------------------------------------------- +#include "boost/python.hpp" +using namespace boost::python; + + +//----------------------------------------------------------------------------- +// CCachedProperty class. +//----------------------------------------------------------------------------- +class CCachedProperty +{ +public: + CCachedProperty(object fget, object fset, object fdel, const char *doc, boost::python::tuple args, dict kwargs); + + static object _callable_check(object function, const char *szName); + + object get_getter(); + object set_getter(object fget); + + object get_setter(); + object set_setter(object fget); + + object get_deleter(); + object set_deleter(object fget); + + object get_owner(); + str get_name(); + + void __set_name__(object owner, str name); + object __get__(object instance, object owner); + void __set__(object instance, object value); + void __delete__(object instance); + object __getitem__(str item); + void __setitem__(str item, object value); + + static CCachedProperty *wrap_descriptor(object descriptor, object owner, str name); + +private: + object m_fget; + object m_fset; + object m_fdel; + + object m_owner; + str m_name; + +public: + const char *m_szDocString; + + boost::python::tuple m_args; + dict m_kwargs; +}; + + +#endif // _CORE_CACHE_H diff --git a/src/core/modules/core/core_cache_wrap.cpp b/src/core/modules/core/core_cache_wrap.cpp new file mode 100644 index 000000000..981b62805 --- /dev/null +++ b/src/core/modules/core/core_cache_wrap.cpp @@ -0,0 +1,281 @@ +/** +* ============================================================================= +* Source Python +* Copyright (C) 2012-2019 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 "export_main.h" +#include "sp_main.h" +#include "core_cache.h" + + +//----------------------------------------------------------------------------- +// Forward declarations. +//----------------------------------------------------------------------------- +static void export_cached_property(scope); + + +//----------------------------------------------------------------------------- +// Declare the _core._cache module. +//----------------------------------------------------------------------------- +DECLARE_SP_SUBMODULE(_core, _cache) +{ + export_cached_property(_cache); +} + + +//----------------------------------------------------------------------------- +// Exports CachedProperty. +//----------------------------------------------------------------------------- +void export_cached_property(scope _cache) +{ + class_ CachedProperty( + "CachedProperty", + init( + ( + arg("fget")=object(), arg("fset")=object(), arg("fdel")=object(), arg("doc")=object(), + arg("args")=boost::python::tuple(), arg("kwargs")=dict() + ), + "Represents a property attribute that is only" + " computed once and cached.\n" + "\n" + ":param function fget:\n" + " Optional getter function.\n" + " Once the value has been computed, the result is cached and" + " returned if this property is requested again.\n" + "\n" + " Getter signature: self, *args, **kwargs\n" + ":param function fset:\n" + " Optional setter function.\n" + " When a new value is assigned to this property, that" + " function is called and the cache is invalidated if nothing" + " was returned otherwise the cached value is updated accordingly.\n" + "\n" + " Setter signature: self, value, *args, **kwargs\n" + ":param function fdel:\n" + " Optional deleter function.\n" + " When this property is deleted, that function is called and the" + " cached value (if any) is invalidated.\n" + "\n" + " Deleter signature: self, *args, **kwargs\n" + ":param str doc:\n" + " Documentation string for this property.\n" + ":param tuple args:\n" + " Extra arguments passed to the getter, setter and deleter functions.\n" + ":param dict kwargs:\n" + " Extra keyword arguments passed to the getter, setter and deleter functions.\n" + "\n" + ":raises TypeError:\n" + " If the given getter, setter or deleter is not callable." + ) + ); + + + CachedProperty.def( + "getter", + &CCachedProperty::set_getter, + "Decorator used to register the getter function for this property.\n" + "\n" + ":param function fget:\n" + " The function to register as getter function.\n" + "\n" + ":rtype:\n" + " function" + ); + + CachedProperty.add_property( + "fget", + &CCachedProperty::get_getter, + &CCachedProperty::set_getter, + "The getter function used to compute the value of this property.\n" + "\n" + ":rtype:\n" + " function" + ); + + + CachedProperty.def( + "setter", + &CCachedProperty::set_setter, + "Decorator used to register the setter function for this property.\n" + "\n" + ":param function fset:\n" + " The function to register as setter function.\n" + "\n" + ":rtype:\n" + " function" + ); + + CachedProperty.add_property( + "fset", + &CCachedProperty::get_setter, + &CCachedProperty::set_setter, + "The setter function used to assign the value of this property.\n" + "\n" + ":rtype:\n" + " function" + ); + + + CachedProperty.def( + "deleter", + &CCachedProperty::set_deleter, + "Decorator used to register the deleter function for this property.\n" + "\n" + ":param function fdel:\n" + " The function to register as deleter function.\n" + "\n" + ":rtype:\n" + " function" + ); + + CachedProperty.add_property( + "fdel", + &CCachedProperty::get_deleter, + &CCachedProperty::set_deleter, + "The deleter function used to delete this property.\n" + "\n" + ":rtype:\n" + " function" + ); + + + CachedProperty.def_readwrite( + "__doc__", + &CCachedProperty::m_szDocString, + "Documentation string for this property.\n" + "\n" + ":rtype:\n" + " str" + ); + + + CachedProperty.add_property( + "owner", + &CCachedProperty::get_owner, + "The owner class this property attribute was bound to.\n" + "\n" + ":rtype:\n" + " type" + ); + + CachedProperty.add_property( + "name", + &CCachedProperty::get_name, + "The name this property is registered as.\n" + "\n" + ":rtype:\n" + " str" + ); + + CachedProperty.def_readwrite( + "args", + &CCachedProperty::m_args, + "The extra arguments passed to the getter, setter and deleter functions." + ); + + CachedProperty.def_readwrite( + "kwargs", + &CCachedProperty::m_kwargs, + "The extra keyword arguments passed to the getter, setter and deleter functions." + ); + + CachedProperty.def( + "__set_name__", + &CCachedProperty::__set_name__, + "Called when this property is being bound to a class.\n" + ); + + CachedProperty.def( + "__get__", + &CCachedProperty::__get__, + "Retrieves the value of this property.\n" + "\n" + ":rtype:\n" + " object" + ); + + CachedProperty.def( + "__set__", + &CCachedProperty::__set__, + "Assigns the value of this property.\n" + "\n" + ":rtype:\n" + " object" + ); + + CachedProperty.def( + "__delete__", + &CCachedProperty::__delete__, + "Deletes this property and invalidates its cached value." + ); + + CachedProperty.def( + "__call__", + &CCachedProperty::set_getter, + "Decorator used to register the getter function for this property.\n" + "\n" + ":param function fget:\n" + " The function to register as getter function.\n" + "\n" + ":rtype:\n" + " function" + ); + + CachedProperty.def( + "__getitem__", + &CCachedProperty::__getitem__, + "Returns the value of a keyword argument.\n" + "\n" + ":param str item:\n" + " The name of the keyword.\n" + "\n" + ":rtype:" + " object" + ); + + CachedProperty.def( + "__setitem__", + &CCachedProperty::__setitem__, + "Sets the value of a keyword argument.\n" + "\n" + ":param str item:\n" + " The name of the keyword.\n" + ":param object value:\n" + " The value to assigne to the given keyword." + ); + + CachedProperty.def( + "wrap_descriptor", + &CCachedProperty::wrap_descriptor, + manage_new_object_policy(), + "Wraps a descriptor as a cached property.", + ("descriptor", arg("owner")=object(), arg("name")=str()) + ) + .staticmethod("wrap_descriptor"); + + scope().attr("cached_property") = scope().attr("CachedProperty"); +} diff --git a/src/core/modules/entities/entities_entity_wrap.cpp b/src/core/modules/entities/entities_entity_wrap.cpp index ef40a2c3c..a14283abb 100644 --- a/src/core/modules/entities/entities_entity_wrap.cpp +++ b/src/core/modules/entities/entities_entity_wrap.cpp @@ -459,12 +459,14 @@ void export_base_entity(scope _entity) "The server class of this entity (read-only).\n\n" ":rtype: ServerClass" ); + cached_property(BaseEntity, "server_class"); BaseEntity.add_property("datamap", make_function(&CBaseEntityWrapper::GetDataDescMap, reference_existing_object_policy()), "The data map of this entity (read-only).\n\n" ":rtype: DataMap" ); + cached_property(BaseEntity, "datamap"); BaseEntity.def("get_output", &CBaseEntityWrapper::get_output, @@ -479,12 +481,14 @@ void export_base_entity(scope _entity) "Return the entity's factory.\n\n" ":rtype: EntityFactory" ); + cached_property(BaseEntity, "factory"); BaseEntity.add_property( "edict", make_function(&CBaseEntityWrapper::GetEdict, reference_existing_object_policy()), "Return the edict of the entity.\n\n" ":rtype: Edict"); + cached_property(BaseEntity, "edict"); BaseEntity.add_property( "index", @@ -492,24 +496,28 @@ void export_base_entity(scope _entity) "Return the index of the entity.\n\n" ":raise ValueError: Raised if the entity does not have an index.\n" ":rtype: int"); + cached_property(BaseEntity, "index"); BaseEntity.add_property( "pointer", make_function(&CBaseEntityWrapper::GetPointer), "Return the pointer of the entity.\n\n" ":rtype: Pointer"); + cached_property(BaseEntity, "pointer"); BaseEntity.add_property( "inthandle", &CBaseEntityWrapper::GetIntHandle, "Return the handle of the entity.\n\n" ":rtype: int"); + cached_property(BaseEntity, "inthandle"); BaseEntity.add_property( "physics_object", make_function(&CBaseEntityWrapper::GetPhysicsObject, manage_new_object_policy()), "Return the physics object of the entity.\n\n" ":rtype: PhysicsObject"); + cached_property(BaseEntity, "physics_object"); // KeyValue getter methods BaseEntity.def("get_key_value_string", @@ -1060,4 +1068,5 @@ void export_base_entity(scope _entity) "Return the entity's size.\n\n" ":rtype: int" ); + cached_property(BaseEntity, "_size"); } diff --git a/src/core/utilities/wrap_macros.h b/src/core/utilities/wrap_macros.h index fbd551992..294bb5ddb 100644 --- a/src/core/utilities/wrap_macros.h +++ b/src/core/utilities/wrap_macros.h @@ -33,6 +33,7 @@ #include "boost/python.hpp" using namespace boost::python; +#include "modules/core/core_cache.h" //--------------------------------------------------------------------------------- // Define checks @@ -133,15 +134,29 @@ inline void* GetFuncPtr(Function func) template T classmethod(T cls, const char *szName) { - PyTypeObject *self = downcast(cls.ptr()); + PyTypeObject *type = downcast(cls.ptr()); PyDict_SetItemString( - self->tp_dict, szName, - PyClassMethod_New(PyDict_GetItemString(self->tp_dict, szName)) + type->tp_dict, szName, + PyClassMethod_New(PyDict_GetItemString(type->tp_dict, szName)) ); return cls; }; +//--------------------------------------------------------------------------------- +// Use these to declare cached properties. +//--------------------------------------------------------------------------------- +#define CACHED_PROPERTY(cls, name, ...) \ + cached_property(cls.add_property(name, __VA_ARGS__), name) + +template +T cached_property(T cls, const char *szName) +{ + cls.attr(szName) = ptr(CCachedProperty::wrap_descriptor(cls.attr(szName), cls, szName)); + return cls; +}; + + //--------------------------------------------------------------------------------- // Use this template to create variadic class methods //--------------------------------------------------------------------------------- From d90de7c53fde92ed4e9c7ded2d38cd081133284a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Fri, 29 Nov 2019 08:15:12 -0500 Subject: [PATCH 02/59] Fixed cached generators being exhausted after the first iteration. --- .../packages/source-python/core/cache.py | 2 + src/core/modules/core/core_cache.cpp | 71 +++++++++++++++++-- src/core/modules/core/core_cache.h | 20 ++++++ src/core/modules/core/core_cache_wrap.cpp | 42 +++++++++++ 4 files changed, 131 insertions(+), 4 deletions(-) diff --git a/addons/source-python/packages/source-python/core/cache.py b/addons/source-python/packages/source-python/core/cache.py index 5bef6d4e4..6dd813d63 100644 --- a/addons/source-python/packages/source-python/core/cache.py +++ b/addons/source-python/packages/source-python/core/cache.py @@ -12,6 +12,7 @@ # ============================================================================= # Source.Python Imports # Core +from _core._cache import CachedGenerator from _core._cache import CachedProperty from _core._cache import cached_property @@ -20,6 +21,7 @@ # >> ALL DECLARATION # ============================================================================= __all__ = [ + 'CachedGenerator', 'CachedProperty', 'cached_property' ] diff --git a/src/core/modules/core/core_cache.cpp b/src/core/modules/core/core_cache.cpp index 0a780adc6..2e793fa56 100644 --- a/src/core/modules/core/core_cache.cpp +++ b/src/core/modules/core/core_cache.cpp @@ -60,6 +60,13 @@ object CCachedProperty::_callable_check(object function, const char *szName) return function; } +object CCachedProperty::_prepare_value(object value) +{ + if (PyGen_Check(value.ptr())) + value = object(CCachedGenerator(value)); + return value; +} + object CCachedProperty::get_getter() { @@ -146,9 +153,11 @@ object CCachedProperty::__get__(object instance, object owner=object()) ); dict cache = extract(pCache); - cache[m_name] = m_fget( - *(make_tuple(handle<>(borrowed(instance.ptr()))) + m_args), - **m_kwargs + cache[m_name] = _prepare_value( + m_fget( + *(make_tuple(handle<>(borrowed(instance.ptr()))) + m_args), + **m_kwargs + ) ); return cache[m_name]; @@ -170,7 +179,7 @@ void CCachedProperty::__set__(object instance, object value) dict cache = extract(instance.attr("__dict__")); if (!result.is_none()) - cache[m_name] = result; + cache[m_name] = _prepare_value(result); else PyDict_DelItemString(cache.ptr(), extract(m_name)); } @@ -209,3 +218,57 @@ CCachedProperty *CCachedProperty::wrap_descriptor(object descriptor, object owne return pProperty; } + + +//----------------------------------------------------------------------------- +// CCachedGenerator class. +//----------------------------------------------------------------------------- +CCachedGenerator::CCachedGenerator(object generator) +{ + if (!PyGen_Check(generator.ptr())) + BOOST_RAISE_EXCEPTION( + PyExc_TypeError, + "The given generator is invalid." + ); + + object frame = generator.attr("gi_frame"); + if (frame.is_none()) + BOOST_RAISE_EXCEPTION( + PyExc_ValueError, + "The given generator is exhausted." + ); + + m_generator = generator; +} + + +object CCachedGenerator::get_generator() +{ + return m_generator; +} + + +object CCachedGenerator::__iter__() +{ + return m_generator.is_none() ? m_generated_values.attr("__iter__")() : object(ptr(this)); +} + + +object CCachedGenerator::__next__() +{ + object value; + if (!m_generator.is_none()) + { + try + { + value = m_generator.attr("__next__")(); + m_generated_values.append(value); + } + catch(...) + { + m_generator = object(); + BOOST_RAISE_EXCEPTION(PyExc_StopIteration, "StopIteration"); + } + } + return value; +} diff --git a/src/core/modules/core/core_cache.h b/src/core/modules/core/core_cache.h index 201ee7c99..da1f6e26e 100644 --- a/src/core/modules/core/core_cache.h +++ b/src/core/modules/core/core_cache.h @@ -43,6 +43,7 @@ class CCachedProperty CCachedProperty(object fget, object fset, object fdel, const char *doc, boost::python::tuple args, dict kwargs); static object _callable_check(object function, const char *szName); + static object _prepare_value(object value); object get_getter(); object set_getter(object fget); @@ -81,4 +82,23 @@ class CCachedProperty }; +//----------------------------------------------------------------------------- +// CCachedGenerator class. +//----------------------------------------------------------------------------- +class CCachedGenerator +{ +public: + CCachedGenerator(object generator); + + object get_generator(); + + object __iter__(); + object __next__(); + +private: + object m_generator; + list m_generated_values; +}; + + #endif // _CORE_CACHE_H diff --git a/src/core/modules/core/core_cache_wrap.cpp b/src/core/modules/core/core_cache_wrap.cpp index 981b62805..899637f64 100644 --- a/src/core/modules/core/core_cache_wrap.cpp +++ b/src/core/modules/core/core_cache_wrap.cpp @@ -36,6 +36,7 @@ // Forward declarations. //----------------------------------------------------------------------------- static void export_cached_property(scope); +static void export_cached_generator(scope); //----------------------------------------------------------------------------- @@ -44,6 +45,7 @@ static void export_cached_property(scope); DECLARE_SP_SUBMODULE(_core, _cache) { export_cached_property(_cache); + export_cached_generator(_cache); } @@ -279,3 +281,43 @@ void export_cached_property(scope _cache) scope().attr("cached_property") = scope().attr("CachedProperty"); } + + +//----------------------------------------------------------------------------- +// Exports CCachedGenerator. +//----------------------------------------------------------------------------- +void export_cached_generator(scope _cache) +{ + class_ CachedGenerator("CachedGenerator", + init( + ( + arg("generator") + ), + "Represents a cached generator.\n" + "If a :class:`core.cache.CachedProperty` returns a generator, it" + " is being cached as an instance of this class. Then, when" + " this instance is iterated over for the first time, the original" + " generator is processed and the generated values are cached.\n" + "\n" + ":param generator generator:\n" + " The wrapped generator instance.\n" + "\n" + ":raises TypeError:\n" + " If the given generator is invalid.\n" + ":raises ValueError:\n" + " If the given generator is exhausted." + ) + ); + + CachedGenerator.def( + "__iter__", + &CCachedGenerator::__iter__, + "Returns an iterator iterating over the generated values of the wrapped generator." + ); + + CachedGenerator.def( + "__next__", + &CCachedGenerator::__next__, + "Returns the next value from the current iteration." + ); +} From 7aefd2af0051962903cb9af93b15682a310a7cc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Fri, 29 Nov 2019 12:11:54 -0500 Subject: [PATCH 03/59] Fixed CachedGenerator not caching all the generated values if the first to request the property was stopping the iteration early. --- src/core/modules/core/core_cache.cpp | 16 ++++------------ src/core/modules/core/core_cache.h | 1 - src/core/modules/core/core_cache_wrap.cpp | 6 ------ 3 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/core/modules/core/core_cache.cpp b/src/core/modules/core/core_cache.cpp index 2e793fa56..ff2a9f003 100644 --- a/src/core/modules/core/core_cache.cpp +++ b/src/core/modules/core/core_cache.cpp @@ -250,25 +250,17 @@ object CCachedGenerator::get_generator() object CCachedGenerator::__iter__() { - return m_generator.is_none() ? m_generated_values.attr("__iter__")() : object(ptr(this)); -} - - -object CCachedGenerator::__next__() -{ - object value; - if (!m_generator.is_none()) + while(!m_generator.is_none()) { try { - value = m_generator.attr("__next__")(); - m_generated_values.append(value); + m_generated_values.append(m_generator.attr("__next__")()); } catch(...) { m_generator = object(); - BOOST_RAISE_EXCEPTION(PyExc_StopIteration, "StopIteration"); + PyErr_Clear(); } } - return value; + return m_generated_values.attr("__iter__")(); } diff --git a/src/core/modules/core/core_cache.h b/src/core/modules/core/core_cache.h index da1f6e26e..6ea8a67f3 100644 --- a/src/core/modules/core/core_cache.h +++ b/src/core/modules/core/core_cache.h @@ -93,7 +93,6 @@ class CCachedGenerator object get_generator(); object __iter__(); - object __next__(); private: object m_generator; diff --git a/src/core/modules/core/core_cache_wrap.cpp b/src/core/modules/core/core_cache_wrap.cpp index 899637f64..1c9939bda 100644 --- a/src/core/modules/core/core_cache_wrap.cpp +++ b/src/core/modules/core/core_cache_wrap.cpp @@ -314,10 +314,4 @@ void export_cached_generator(scope _cache) &CCachedGenerator::__iter__, "Returns an iterator iterating over the generated values of the wrapped generator." ); - - CachedGenerator.def( - "__next__", - &CCachedGenerator::__next__, - "Returns the next value from the current iteration." - ); } From fe034abea5adedb925256ea344b5b621ab072d30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Fri, 29 Nov 2019 13:30:07 -0500 Subject: [PATCH 04/59] Dynamic entity functions are now cached. --- .../packages/source-python/entities/_base.py | 13 +++++- .../source-python/entities/helpers.py | 44 +++++++++++++------ 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/_base.py b/addons/source-python/packages/source-python/entities/_base.py index bafd08dcd..c65337e9e 100644 --- a/addons/source-python/packages/source-python/entities/_base.py +++ b/addons/source-python/packages/source-python/entities/_base.py @@ -55,6 +55,7 @@ from mathlib import NULL_VECTOR # Memory from memory import make_object +from memory.helpers import MemberFunction # Players from players.constants import HitGroup # Studio @@ -171,8 +172,18 @@ def __getattr__(self, attr): # Does the current server class contain the given attribute? if hasattr(server_class, attr): + # Get the attribute's value + value = getattr(make_object(server_class, self.pointer), attr) + + # Is the value a dynamic function? + if isinstance(value, MemberFunction): + + # Cache the value + with suppress(AttributeError): + object.__setattr__(self, attr, value) + # Return the attribute's value - return getattr(make_object(server_class, self.pointer), attr) + return value # If the attribute is not found, raise an error raise AttributeError('Attribute "{0}" not found'.format(attr)) diff --git a/addons/source-python/packages/source-python/entities/helpers.py b/addons/source-python/packages/source-python/entities/helpers.py index ccc9e9b9e..e95c74772 100644 --- a/addons/source-python/packages/source-python/entities/helpers.py +++ b/addons/source-python/packages/source-python/entities/helpers.py @@ -2,6 +2,14 @@ """Provides helper functions to convert from one type to another.""" +# ============================================================================= +# >> IMPORTS +# ============================================================================= +# Source.Python Imports +# Core +from core.cache import CachedProperty + + # ============================================================================= # >> FORWARD IMPORTS # ============================================================================= @@ -84,24 +92,38 @@ # ============================================================================= # >> CLASSES # ============================================================================= -class EntityMemFuncWrapper(MemberFunction): - def __init__(self, wrapped_self, wrapper): - func = wrapped_self.__getattr__(wrapper.__name__) - super().__init__(func._manager, func._type_name, func, func._this) - self.wrapper = wrapper +class EntityMemFuncWrapper(MemberFunction, CachedProperty): + def __init__(self, wrapper): + self.__func__ = wrapper + CachedProperty.__init__(self) + + def __get__(self, wrapped_self, objtype): + if wrapped_self is None: + return self.__func__ + func = wrapped_self.__dict__.get(self.name, None) + if func is not None: + return func self.wrapped_self = wrapped_self + func = wrapped_self.__getattr__(self.__func__.__name__) + MemberFunction.__init__( + self, func._manager, func._type_name, func, func._this + ) + return self + + def __set__(self, wrapped_self, value): + wrapped_self.__dict__[self.name] = value def __call__(self, *args, **kwargs): return super().__call__( - *self.wrapper(self.wrapped_self, *args, **kwargs)) + *self.__func__(self.wrapped_self, *args, **kwargs)) def call_trampoline(self, *args, **kwargs): return super().call_trampoline( - *self.wrapper(self.wrapped_self, *args, **kwargs)) + *self.__func__(self.wrapped_self, *args, **kwargs)) def skip_hooks(self, *args, **kwargs): return super().skip_hooks( - *self.wrapper(self.wrapped_self, *args, **kwargs)) + *self.__func__(self.wrapped_self, *args, **kwargs)) # ============================================================================= @@ -109,8 +131,4 @@ def skip_hooks(self, *args, **kwargs): # ============================================================================= def wrap_entity_mem_func(wrapper): """A decorator to wrap an entity memory function.""" - - def inner(wrapped_self): - return EntityMemFuncWrapper(wrapped_self, wrapper) - - return property(inner, doc=wrapper.__doc__) + return EntityMemFuncWrapper(wrapper) From af24025a510cc619ee59fa0350095d96f480bc6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Fri, 29 Nov 2019 23:26:24 -0500 Subject: [PATCH 05/59] Added Entity.instances cached property. --- .../packages/source-python/entities/_base.py | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/_base.py b/addons/source-python/packages/source-python/entities/_base.py index c65337e9e..8babb2f2a 100644 --- a/addons/source-python/packages/source-python/entities/_base.py +++ b/addons/source-python/packages/source-python/entities/_base.py @@ -167,23 +167,23 @@ def __hash__(self): def __getattr__(self, attr): """Find if the attribute is valid and returns the appropriate value.""" # Loop through all of the entity's server classes - for server_class in self.server_classes: - - # Does the current server class contain the given attribute? - if hasattr(server_class, attr): + for instance in self.instances.values(): + try: # Get the attribute's value - value = getattr(make_object(server_class, self.pointer), attr) + value = getattr(instance, attr) + except AttributeError: + continue - # Is the value a dynamic function? - if isinstance(value, MemberFunction): + # Is the value a dynamic function? + if isinstance(value, MemberFunction): - # Cache the value - with suppress(AttributeError): - object.__setattr__(self, attr, value) + # Cache the value + with suppress(AttributeError): + object.__setattr__(self, attr, value) - # Return the attribute's value - return value + # Return the attribute's value + return value # If the attribute is not found, raise an error raise AttributeError('Attribute "{0}" not found'.format(attr)) @@ -328,6 +328,17 @@ def owner(self): except (ValueError, OverflowError): return None + @cached_property + def instances(self): + """Return the cached instances of this entity. + + :rtype: dict + """ + instances = {} + for server_class in self.server_classes: + instances[server_class] = make_object(server_class, self.pointer) + return instances + @cached_property def server_classes(self): """Yield all server classes for the entity.""" From e06bde9599ed0f9b96fa6fda54a454b54a49e454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Sat, 30 Nov 2019 05:22:37 -0500 Subject: [PATCH 06/59] Entity.inputs and Entity.outputs instances are now cached. Removed Entity.instances. Entity.inputs, Entity.outputs and Entity.server_classes are now returning dictionaries mapping their instances rather than iterators generating their names. --- .../packages/source-python/entities/_base.py | 63 ++++++++++--------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/_base.py b/addons/source-python/packages/source-python/entities/_base.py index 8babb2f2a..2b292d64a 100644 --- a/addons/source-python/packages/source-python/entities/_base.py +++ b/addons/source-python/packages/source-python/entities/_base.py @@ -167,7 +167,7 @@ def __hash__(self): def __getattr__(self, attr): """Find if the attribute is valid and returns the appropriate value.""" # Loop through all of the entity's server classes - for instance in self.instances.values(): + for instance in self.server_classes.values(): try: # Get the attribute's value @@ -328,21 +328,13 @@ def owner(self): except (ValueError, OverflowError): return None - @cached_property - def instances(self): - """Return the cached instances of this entity. - - :rtype: dict - """ - instances = {} - for server_class in self.server_classes: - instances[server_class] = make_object(server_class, self.pointer) - return instances - @cached_property def server_classes(self): """Yield all server classes for the entity.""" - yield from server_classes.get_entity_server_classes(self) + return { + server_class: make_object(server_class, self.pointer) for + server_class in server_classes.get_entity_server_classes(self) + } @cached_property def properties(self): @@ -353,14 +345,25 @@ def properties(self): @cached_property def inputs(self): """Iterate over all inputs available for the entity.""" - for server_class in self.server_classes: - yield from server_class.inputs + inputs = {} + for server_class in self.server_classes.values(): + for input in server_class.inputs: + inputs[input] = getattr( + make_object( + server_class._inputs, self.pointer + ), + input + ) + return inputs @cached_property def outputs(self): """Iterate over all outputs available for the entity.""" + outputs = {} for server_class in self.server_classes: - yield from server_class.outputs + for output in server_class.outputs: + outputs[output] = super().get_output(output) + return outputs @cached_property def keyvalues(self): @@ -840,29 +843,27 @@ def _callback(*args, **kwargs): # Return the delay instance... return delay + def get_output(self, name): + """Return the output instance matching the given name. + + :parma str name: + Name of the output. + :rtype: BaseEntityOutput + :raise KeyError: + Raised if the output instance wasn't found. + """ + return self.outputs[name] + def get_input(self, name): """Return the input function matching the given name. :parma str name: Name of the input function. :rtype: InputFunction - :raise ValueError: + :raise KeyError: Raised if the input function wasn't found. """ - # Loop through each server class for the entity - for server_class in self.server_classes: - - # Does the current server class contain the input? - if name in server_class.inputs: - - # Return the InputFunction instance for the given input name - return getattr( - make_object(server_class._inputs, self.pointer), name) - - # If no server class contains the input, raise an error - raise ValueError( - 'Unknown input "{0}" for entity type "{1}".'.format( - name, self.classname)) + return self.inputs[name] def call_input(self, name, *args, **kwargs): """Call the input function matching the given name. From 70717dacad548900b3dfcee1799b4a73014beed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Sun, 1 Dec 2019 03:37:17 -0500 Subject: [PATCH 07/59] Improved performance of Entity._property_ methods. Fixed BaseEntity._network_property_ from reading/writing from a NULL pointer. Fixed excluded properties/collapsible tables not being skipped. --- .../packages/source-python/core/dumps.py | 6 +- .../packages/source-python/entities/_base.py | 150 ++++++++++++++---- .../source-python/entities/classes.py | 5 +- src/core/modules/entities/entities_entity.cpp | 9 +- src/core/modules/entities/entities_props.cpp | 5 +- 5 files changed, 140 insertions(+), 35 deletions(-) diff --git a/addons/source-python/packages/source-python/core/dumps.py b/addons/source-python/packages/source-python/core/dumps.py index dcec9052a..48f85b68b 100644 --- a/addons/source-python/packages/source-python/core/dumps.py +++ b/addons/source-python/packages/source-python/core/dumps.py @@ -17,6 +17,7 @@ from entities import ServerClassGenerator from entities.datamaps import FieldType from entities.entity import BaseEntity +from entities.props import SendPropFlags from entities.props import SendPropType # Memory from memory import CLASS_INFO @@ -259,7 +260,10 @@ def _dump_server_class_table(table, open_file, level=1, offset=0): for prop in table: # Skip all baseclasses - if prop.name == 'baseclass': + if (prop.name == 'baseclass' or + prop.is_exclude_prop() or + prop.flags & SendPropFlags.COLLAPSIBLE + ): continue # Get the current offset in case this diff --git a/addons/source-python/packages/source-python/entities/_base.py b/addons/source-python/packages/source-python/entities/_base.py index 2b292d64a..0b143751a 100644 --- a/addons/source-python/packages/source-python/entities/_base.py +++ b/addons/source-python/packages/source-python/entities/_base.py @@ -445,7 +445,10 @@ def get_property_bool(self, name): Name of the property to retrieve. :rtype: bool """ - return self._get_property(name, 'bool') + try: + return self.get_network_property_bool(name) + except ValueError: + return self.get_datamap_property_bool(name) def get_property_color(self, name): """Return the Color property. @@ -454,7 +457,10 @@ def get_property_color(self, name): Name of the property to retrieve. :rtype: Color """ - return self._get_property(name, 'Color') + try: + return self.get_network_property_color(name) + except ValueError: + return self.get_datamap_property_color(name) def get_property_edict(self, name): """Return the Edict property. @@ -472,7 +478,10 @@ def get_property_float(self, name): Name of the property to retrieve. :rtype: float """ - return self._get_property(name, 'float') + try: + return self.get_network_property_float(name) + except ValueError: + return self.get_datamap_property_float(name) def get_property_int(self, name): """Return the integer property. @@ -481,7 +490,10 @@ def get_property_int(self, name): Name of the property to retrieve. :rtype: int """ - return self._get_property(name, 'int') + try: + return self.get_network_property_int(name) + except ValueError: + return self.get_datamap_property_int(name) def get_property_interval(self, name): """Return the Interval property. @@ -490,7 +502,10 @@ def get_property_interval(self, name): Name of the property to retrieve. :rtype: Interval """ - return self._get_property(name, 'Interval') + try: + return self.get_network_property_interval(name) + except ValueError: + return self.get_datamap_property_interval(name) def get_property_pointer(self, name): """Return the pointer property. @@ -499,7 +514,10 @@ def get_property_pointer(self, name): Name of the property to retrieve. :rtype: Pointer """ - return self._get_property(name, 'pointer') + try: + return self.get_network_property_pointer(name) + except ValueError: + return self.get_datamap_property_pointer(name) def get_property_quaternion(self, name): """Return the Quaternion property. @@ -508,7 +526,10 @@ def get_property_quaternion(self, name): Name of the property to retrieve. :rtype: Quaternion """ - return self._get_property(name, 'Quaternion') + try: + return self.get_network_property_quaternion(name) + except ValueError: + return self.get_datamap_property_quaternion(name) def get_property_short(self, name): """Return the short property. @@ -517,7 +538,10 @@ def get_property_short(self, name): Name of the property to retrieve. :rtype: int """ - return self._get_property(name, 'short') + try: + return self.get_network_property_short(name) + except ValueError: + return self.get_datamap_property_short(name) def get_property_ushort(self, name): """Return the ushort property. @@ -526,7 +550,10 @@ def get_property_ushort(self, name): Name of the property to retrieve. :rtype: int """ - return self._get_property(name, 'ushort') + try: + return self.get_network_property_ushort(name) + except ValueError: + return self.get_datamap_property_ushort(name) def get_property_string(self, name): """Return the string property. @@ -535,7 +562,10 @@ def get_property_string(self, name): Name of the property to retrieve. :rtype: str """ - return self._get_property(name, 'string_array') + try: + return self.get_network_property_string_array(name) + except ValueError: + return self.get_datamap_property_string_array(name) def get_property_string_pointer(self, name): """Return the string property. @@ -544,7 +574,10 @@ def get_property_string_pointer(self, name): Name of the property to retrieve. :rtype: str """ - return self._get_property(name, 'string_pointer') + try: + return self.get_network_property_pointer(name) + except ValueError: + return self.get_datamap_property_pointer(name) def get_property_char(self, name): """Return the char property. @@ -553,7 +586,10 @@ def get_property_char(self, name): Name of the property to retrieve. :rtype: str """ - return self._get_property(name, 'char') + try: + return self.get_network_property_char(name) + except ValueError: + return self.get_datamap_property_char(name) def get_property_uchar(self, name): """Return the uchar property. @@ -562,7 +598,10 @@ def get_property_uchar(self, name): Name of the property to retrieve. :rtype: int """ - return self._get_property(name, 'uchar') + try: + return self.get_network_property_uchar(name) + except ValueError: + return self.get_datamap_property_uchar(name) def get_property_uint(self, name): """Return the uint property. @@ -571,7 +610,10 @@ def get_property_uint(self, name): Name of the property to retrieve. :rtype: int """ - return self._get_property(name, 'uint') + try: + return self.get_network_property_uint(name) + except ValueError: + return self.get_datamap_property_uint(name) def get_property_vector(self, name): """Return the Vector property. @@ -580,7 +622,10 @@ def get_property_vector(self, name): Name of the property to retrieve. :rtype: Vector """ - return self._get_property(name, 'Vector') + try: + return self.get_network_property_vector(name) + except ValueError: + return self.get_datamap_property_vector(name) def _get_property(self, name, prop_type): """Verify the type and return the property.""" @@ -613,7 +658,10 @@ def set_property_bool(self, name, value): :param bool value: The value to set. """ - self._set_property(name, 'bool', value) + try: + self.set_network_property_bool(name, value) + except ValueError: + self.set_datamap_property_bool(name, value) def set_property_color(self, name, value): """Set the Color property. @@ -623,7 +671,10 @@ def set_property_color(self, name, value): :param Color value: The value to set. """ - self._set_property(name, 'Color', value) + try: + self.set_network_property_color(name, value) + except ValueError: + self.set_datamap_property_color(name, value) def set_property_edict(self, name, value): """Set the Edict property. @@ -643,7 +694,10 @@ def set_property_float(self, name, value): :param float value: The value to set. """ - self._set_property(name, 'float', value) + try: + self.set_network_property_float(name, value) + except ValueError: + self.set_datamap_property_float(name, value) def set_property_int(self, name, value): """Set the integer property. @@ -653,7 +707,10 @@ def set_property_int(self, name, value): :param int value: The value to set. """ - self._set_property(name, 'int', value) + try: + self.set_network_property_int(name, value) + except ValueError: + self.set_datamap_property_int(name, value) def set_property_interval(self, name, value): """Set the Interval property. @@ -663,7 +720,10 @@ def set_property_interval(self, name, value): :param Interval value: The value to set. """ - self._set_property(name, 'Interval', value) + try: + self.set_network_property_interval(name, value) + except ValueError: + self.set_datamap_property_interval(name, value) def set_property_pointer(self, name, value): """Set the pointer property. @@ -673,7 +733,10 @@ def set_property_pointer(self, name, value): :param Pointer value: The value to set. """ - self._set_property(name, 'pointer', value) + try: + self.set_network_property_pointer(name, value) + except ValueError: + self.set_datamap_property_pointer(name, value) def set_property_quaternion(self, name, value): """Set the Quaternion property. @@ -683,7 +746,10 @@ def set_property_quaternion(self, name, value): :param Quaternion value: The value to set. """ - self._set_property(name, 'Quaternion', value) + try: + self.set_network_property_quaternion(name, value) + except ValueError: + self.set_datamap_property_quaternion(name, value) def set_property_short(self, name, value): """Set the short property. @@ -693,7 +759,10 @@ def set_property_short(self, name, value): :param int value: The value to set. """ - self._set_property(name, 'short', value) + try: + self.set_network_property_short(name, value) + except ValueError: + self.set_datamap_property_short(name, value) def set_property_ushort(self, name, value): """Set the ushort property. @@ -703,7 +772,10 @@ def set_property_ushort(self, name, value): :param int value: The value to set. """ - self._set_property(name, 'ushort', value) + try: + self.set_network_property_ushort(name, value) + except ValueError: + self.set_datamap_property_ushort(name, value) def set_property_string(self, name, value): """Set the string property. @@ -713,7 +785,10 @@ def set_property_string(self, name, value): :param str value: The value to set. """ - self._set_property(name, 'string_array', value) + try: + self.set_network_property_string_array(name, value) + except ValueError: + self.set_datamap_property_string_array(name, value) def set_property_string_pointer(self, name, value): """Set the string property. @@ -723,7 +798,10 @@ def set_property_string_pointer(self, name, value): :param str value: The value to set. """ - self._set_property(name, 'string_pointer', value) + try: + self.set_network_property_string_pointer(name, value) + except ValueError: + self.set_datamap_property_string_pointer(name, value) def set_property_char(self, name, value): """Set the char property. @@ -733,7 +811,10 @@ def set_property_char(self, name, value): :param str value: The value to set. """ - self._set_property(name, 'char', value) + try: + self.set_network_property_char(name, value) + except ValueError: + self.set_datamap_property_char(name, value) def set_property_uchar(self, name, value): """Set the uchar property. @@ -743,7 +824,10 @@ def set_property_uchar(self, name, value): :param int value: The value to set. """ - self._set_property(name, 'uchar', value) + try: + self.set_network_property_uchar(name, value) + except ValueError: + self.set_datamap_property_uchar(name, value) def set_property_uint(self, name, value): """Set the uint property. @@ -753,7 +837,10 @@ def set_property_uint(self, name, value): :param int value: The value to set. """ - self._set_property(name, 'uint', value) + try: + self.set_network_property_uint(name, value) + except ValueError: + self.set_datamap_property_uint(name, value) def set_property_vector(self, name, value): """Set the Vector property. @@ -763,7 +850,10 @@ def set_property_vector(self, name, value): :param Vector value: The value to set. """ - self._set_property(name, 'Vector', value) + try: + self.set_network_property_vector(name, value) + except ValueError: + self.set_datamap_property_vector(name, value) def _set_property(self, name, prop_type, value): """Verify the type and set the property.""" diff --git a/addons/source-python/packages/source-python/entities/classes.py b/addons/source-python/packages/source-python/entities/classes.py index 72dcc57c4..1283e5fa4 100644 --- a/addons/source-python/packages/source-python/entities/classes.py +++ b/addons/source-python/packages/source-python/entities/classes.py @@ -375,7 +375,10 @@ def _find_properties(self, table, base_name='', base_offset=0): for prop in table: # Is the property a base class or excluded? - if prop.name == 'baseclass' or prop.flags & SendPropFlags.EXCLUDE: + if (prop.name == 'baseclass' or + prop.is_exclude_prop() or + prop.flags & SendPropFlags.COLLAPSIBLE + ): continue # Get the name of the property using the given base name diff --git a/src/core/modules/entities/entities_entity.cpp b/src/core/modules/entities/entities_entity.cpp index 377e6bc5c..eee4544e5 100644 --- a/src/core/modules/entities/entities_entity.cpp +++ b/src/core/modules/entities/entities_entity.cpp @@ -187,7 +187,7 @@ int CBaseEntityWrapper::FindDatamapPropertyOffset(const char* name) BOOST_RAISE_EXCEPTION(PyExc_ValueError, "Failed to retrieve the datamap.") int offset = DataMapSharedExt::find_offset(datamap, name); - if (offset == -1) + if (offset == -1 || offset == 0) BOOST_RAISE_EXCEPTION(PyExc_ValueError, "Unable to find property '%s'.", name) return offset; @@ -199,10 +199,15 @@ int CBaseEntityWrapper::FindNetworkPropertyOffset(const char* name) if (!pServerClass) BOOST_RAISE_EXCEPTION(PyExc_ValueError, "Failed to retrieve the server class.") - int offset = SendTableSharedExt::find_offset(pServerClass->m_pTable, name); + int offset; + offset = SendTableSharedExt::find_offset(pServerClass->m_pTable, name); if (offset == -1) BOOST_RAISE_EXCEPTION(PyExc_ValueError, "Unable to find property '%s'.", name) + // TODO: Proxied RecvTables/Arrays + if (offset == 0) + offset = FindDatamapPropertyOffset(name); + return offset; } diff --git a/src/core/modules/entities/entities_props.cpp b/src/core/modules/entities/entities_props.cpp index f54735ef5..d5c553eb1 100644 --- a/src/core/modules/entities/entities_props.cpp +++ b/src/core/modules/entities/entities_props.cpp @@ -70,7 +70,10 @@ void AddSendTable(SendTable* pTable, OffsetsMap& offsets, int offset=0, const ch for (int i=0; i < pTable->GetNumProps(); ++i) { SendProp* pProp = pTable->GetProp(i); - if (strcmp(pProp->GetName(), "baseclass") == 0) + if (strcmp(pProp->GetName(), "baseclass") == 0 || + pProp->IsExcludeProp() || + pProp->GetFlags() & SPROP_COLLAPSIBLE + ) continue; int currentOffset = offset + pProp->GetOffset(); From a4642ccb0cf412de723c0998ce3092a7ffd83b41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Sun, 1 Dec 2019 17:59:56 -0500 Subject: [PATCH 08/59] Fixed some memory leaks. --- .../packages/source-python/entities/_base.py | 10 +++++-- .../source-python/entities/datamaps.py | 6 +++- .../packages/source-python/memory/helpers.py | 5 +++- src/core/modules/memory/memory_function.cpp | 28 +++++++++++++++++++ src/core/modules/memory/memory_function.h | 5 +++- src/core/modules/memory/memory_pointer.cpp | 13 +++------ src/core/modules/memory/memory_pointer.h | 12 ++++++++ src/core/modules/memory/memory_scanner.cpp | 2 +- src/core/modules/memory/memory_wrap.cpp | 5 ++-- 9 files changed, 69 insertions(+), 17 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/_base.py b/addons/source-python/packages/source-python/entities/_base.py index 0b143751a..07f2df1b3 100644 --- a/addons/source-python/packages/source-python/entities/_base.py +++ b/addons/source-python/packages/source-python/entities/_base.py @@ -339,8 +339,11 @@ def server_classes(self): @cached_property def properties(self): """Iterate over all descriptors available for the entity.""" + properties = {} for server_class in self.server_classes: - yield from server_class.properties + for prop, data in server_class.properties.items(): + properties[prop] = data + return properties @cached_property def inputs(self): @@ -374,8 +377,11 @@ def keyvalues(self): An entity might also have hardcoded keyvalues that can't be listed with this property. """ + keyvalues = {} for server_class in self.server_classes: - yield from server_class.keyvalues + for keyvalue, data in server_class.keyvalues.items(): + keyvalues[keyvalue] = data + return keyvalues def get_model(self): """Return the entity's model. diff --git a/addons/source-python/packages/source-python/entities/datamaps.py b/addons/source-python/packages/source-python/entities/datamaps.py index fa417a231..cabe8bc76 100644 --- a/addons/source-python/packages/source-python/entities/datamaps.py +++ b/addons/source-python/packages/source-python/entities/datamaps.py @@ -127,7 +127,11 @@ class InputFunction(Function): def __init__(self, name, argument_type, function, this): """Instantiate the function instance and store the base attributes.""" - super().__init__(function) + self._function = function + super().__init__( + function.address, function.convention, function.arguments, + function.return_type + ) self._name = name self._argument_type = argument_type diff --git a/addons/source-python/packages/source-python/memory/helpers.py b/addons/source-python/packages/source-python/memory/helpers.py index 4b5514034..4f7ec86c4 100644 --- a/addons/source-python/packages/source-python/memory/helpers.py +++ b/addons/source-python/packages/source-python/memory/helpers.py @@ -315,7 +315,10 @@ class MemberFunction(Function): def __init__(self, manager, return_type, func, this): """Initialize the instance.""" - super().__init__(func) + self._function = func + super().__init__( + func.address, func.convention, func.arguments, func.return_type + ) # This should always hold a TypeManager instance self._manager = manager diff --git a/src/core/modules/memory/memory_function.cpp b/src/core/modules/memory/memory_function.cpp index 314d8031b..85ed20a1e 100644 --- a/src/core/modules/memory/memory_function.cpp +++ b/src/core/modules/memory/memory_function.cpp @@ -142,6 +142,9 @@ CFunction::CFunction(unsigned long ulAddr, object oCallingConvention, object oAr // If this line succeeds the user wants to create a function with the built-in calling conventions m_eCallingConvention = extract(oCallingConvention); m_pCallingConvention = MakeDynamicHooksConvention(m_eCallingConvention, ObjectToDataTypeVector(m_tArgs), m_eReturnType); + + // We allocated the calling convention, we are responsible to cleanup. + m_bAllocatedCallingConvention = true; } catch( ... ) { @@ -154,8 +157,12 @@ CFunction::CFunction(unsigned long ulAddr, object oCallingConvention, object oAr // FIXME: // This is required to fix a crash, but it will also cause a memory leak, // because no calling convention object that is created via this method will ever be deleted. + // TODO: Pretty sure this was required due to the missing held type definition. It was added, but wasn't tested yet. Py_INCREF(_oCallingConvention.ptr()); m_pCallingConvention = extract(_oCallingConvention); + + // We didn't allocate the calling convention, someone else is responsible for it. + m_bAllocatedCallingConvention = false; } // Step 4: Get the DynCall calling convention @@ -170,11 +177,32 @@ CFunction::CFunction(unsigned long ulAddr, Convention_t eCallingConvention, m_eCallingConvention = eCallingConvention; m_iCallingConvention = iCallingConvention; m_pCallingConvention = pCallingConvention; + + // We didn't allocate the calling convention, someone else is responsible for it. + m_bAllocatedCallingConvention = false; + m_tArgs = tArgs; m_eReturnType = eReturnType; m_oConverter = oConverter; } +CFunction::~CFunction() +{ + // If we didn't allocate the calling convention, then it is not our responsibility. + if (!m_bAllocatedCallingConvention) + return; + + CHook* pHook = GetHookManager()->FindHook((void *) m_ulAddr); + + // DynamicHooks will take care of it for us from there. + if (pHook && pHook->m_pCallingConvention == m_pCallingConvention) + return; + + // Cleanup. + delete m_pCallingConvention; + m_pCallingConvention = NULL; +} + bool CFunction::IsCallable() { return (m_eCallingConvention != CONV_CUSTOM) && (m_iCallingConvention != -1); diff --git a/src/core/modules/memory/memory_function.h b/src/core/modules/memory/memory_function.h index ea1d3a32d..0d25b0e7e 100644 --- a/src/core/modules/memory/memory_function.h +++ b/src/core/modules/memory/memory_function.h @@ -52,7 +52,7 @@ enum Convention_t // ============================================================================ // >> CFunction // ============================================================================ -class CFunction: public CPointer +class CFunction: public CPointer, private boost::noncopyable { public: CFunction(unsigned long ulAddr, object oCallingConvention, object oArgs, object oReturnType); @@ -60,6 +60,8 @@ class CFunction: public CPointer ICallingConvention* pCallingConvention, boost::python::tuple tArgs, DataType_t eReturnType, object oConverter); + ~CFunction(); + bool IsCallable(); bool IsHookable(); @@ -99,6 +101,7 @@ class CFunction: public CPointer // DynamicHooks calling convention (built-in and custom) ICallingConvention* m_pCallingConvention; + bool m_bAllocatedCallingConvention; }; diff --git a/src/core/modules/memory/memory_pointer.cpp b/src/core/modules/memory/memory_pointer.cpp index 4c316ad3c..89ba3b078 100644 --- a/src/core/modules/memory/memory_pointer.cpp +++ b/src/core/modules/memory/memory_pointer.cpp @@ -109,14 +109,6 @@ void CPointer::SetStringArray(char* szText, int iOffset /* = 0 */) EXCEPT_SEGV() } -unsigned long GetPtrHelper(unsigned long addr) -{ - TRY_SEGV() - return *(unsigned long *) addr; - EXCEPT_SEGV() - return 0; -} - CPointer* CPointer::GetPtr(int iOffset /* = 0 */) { Validate(); @@ -286,7 +278,10 @@ CFunction* CPointer::MakeFunction(object oCallingConvention, object oArgs, objec CFunction* CPointer::MakeVirtualFunction(int iIndex, object oCallingConvention, object args, object return_type) { - return GetVirtualFunc(iIndex)->MakeFunction(oCallingConvention, args, return_type); + CPointer *pPtr = GetVirtualFunc(iIndex); + CFunction *pFunc = pPtr->MakeFunction(oCallingConvention, args, return_type); + delete pPtr; + return pFunc; } CFunction* CPointer::MakeVirtualFunction(CFunctionInfo& info) diff --git a/src/core/modules/memory/memory_pointer.h b/src/core/modules/memory/memory_pointer.h index a903a1da3..99d9b26a5 100644 --- a/src/core/modules/memory/memory_pointer.h +++ b/src/core/modules/memory/memory_pointer.h @@ -190,4 +190,16 @@ inline CPointer* Alloc(int iSize, bool bAutoDealloc = true) return new CPointer((unsigned long) UTIL_Alloc(iSize), bAutoDealloc); } + +// ============================================================================ +// >> GetPtrHelper +// ============================================================================ +inline unsigned long GetPtrHelper(unsigned long addr) +{ + TRY_SEGV() + return *(unsigned long *) addr; + EXCEPT_SEGV() + return 0; +} + #endif // _MEMORY_POINTER_H \ No newline at end of file diff --git a/src/core/modules/memory/memory_scanner.cpp b/src/core/modules/memory/memory_scanner.cpp index d5347f450..140edd0ee 100644 --- a/src/core/modules/memory/memory_scanner.cpp +++ b/src/core/modules/memory/memory_scanner.cpp @@ -328,7 +328,7 @@ CPointer* CBinaryFile::FindPointer(object oIdentifier, int iOffset, unsigned int ptr->m_ulAddr += iOffset; while (iLevel > 0) { - ptr = ptr->GetPtr(); + ptr->m_ulAddr = GetPtrHelper(ptr->m_ulAddr); iLevel = iLevel - 1; } } diff --git a/src/core/modules/memory/memory_wrap.cpp b/src/core/modules/memory/memory_wrap.cpp index 6b69c11c0..a2fad55c3 100644 --- a/src/core/modules/memory/memory_wrap.cpp +++ b/src/core/modules/memory/memory_wrap.cpp @@ -452,7 +452,8 @@ void export_type_info_iter(scope _memory) void export_function(scope _memory) { class_, boost::noncopyable >("Function", init()) - .def(init()) + // Don't allow copies, because they will hold references to our calling convention. + // .def(init()) .def("__call__", raw_method(&CFunction::Call), "Calls the function dynamically." @@ -817,7 +818,7 @@ void export_registers(scope _memory) // ============================================================================ void export_calling_convention(scope _memory) { - class_( + class_( "CallingConvention", "An an abstract class that is used to create custom calling " "conventions (only available for hooking function and not for" From afde4a1aa14246b2b11843dc94bb12328fce600e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Mon, 2 Dec 2019 16:32:54 -0500 Subject: [PATCH 09/59] Fixed CachedProperty's decorators overriding the main descriptor name. Removed CachedGenerator as caching it was leaking a shallow copy of the entire frame. The generated values are now computed and cached on retrieval, rather than iteration. Improved the logic of CachedProperty.__get__. Removed some redundant cast/extract between CPython and Boost. Fixed some exceptions being silenced under certain conditions within the load/unload process. --- .../packages/source-python/core/cache.py | 2 - src/core/modules/core/core_cache.cpp | 140 ++++++++---------- src/core/modules/core/core_cache.h | 19 +-- src/core/modules/core/core_cache_wrap.cpp | 40 +---- src/core/sp_python.cpp | 25 +--- src/core/utilities/sp_util.h | 27 ++++ 6 files changed, 93 insertions(+), 160 deletions(-) diff --git a/addons/source-python/packages/source-python/core/cache.py b/addons/source-python/packages/source-python/core/cache.py index 6dd813d63..5bef6d4e4 100644 --- a/addons/source-python/packages/source-python/core/cache.py +++ b/addons/source-python/packages/source-python/core/cache.py @@ -12,7 +12,6 @@ # ============================================================================= # Source.Python Imports # Core -from _core._cache import CachedGenerator from _core._cache import CachedProperty from _core._cache import cached_property @@ -21,7 +20,6 @@ # >> ALL DECLARATION # ============================================================================= __all__ = [ - 'CachedGenerator', 'CachedProperty', 'cached_property' ] diff --git a/src/core/modules/core/core_cache.cpp b/src/core/modules/core/core_cache.cpp index ff2a9f003..3c003f874 100644 --- a/src/core/modules/core/core_cache.cpp +++ b/src/core/modules/core/core_cache.cpp @@ -62,9 +62,30 @@ object CCachedProperty::_callable_check(object function, const char *szName) object CCachedProperty::_prepare_value(object value) { - if (PyGen_Check(value.ptr())) - value = object(CCachedGenerator(value)); - return value; + if (!PyGen_Check(value.ptr())) + return value; + + if (getattr(value, "gi_frame").is_none()) + BOOST_RAISE_EXCEPTION( + PyExc_ValueError, + "The given generator is exhausted." + ); + + list values; + while (true) + { + try + { + values.append(value.attr("__next__")()); + } + catch(...) + { + PyErr_Clear(); + break; + } + } + + return values; } @@ -76,7 +97,7 @@ object CCachedProperty::get_getter() object CCachedProperty::set_getter(object fget) { m_fget = _callable_check(fget, "getter"); - return object(ptr(this)); + return fget; } @@ -88,7 +109,7 @@ object CCachedProperty::get_setter() object CCachedProperty::set_setter(object fset) { m_fset = _callable_check(fset, "setter"); - return object(ptr(this)); + return fset; } @@ -100,7 +121,7 @@ object CCachedProperty::get_deleter() object CCachedProperty::set_deleter(object fdel) { m_fdel = _callable_check(fdel, "deleter"); - return object(ptr(this)); + return fdel; } @@ -121,44 +142,43 @@ void CCachedProperty::__set_name__(object owner, str name) m_name = name; } - object CCachedProperty::__get__(object instance, object owner=object()) { if (instance.is_none()) return object(ptr(this)); - if (!m_name) + if (m_name.is_none()) BOOST_RAISE_EXCEPTION( PyExc_AttributeError, "Unable to retrieve the value of an unbound property." ); - PyObject *pCache = PyObject_GetAttrString(instance.ptr(), "__dict__"); + object cache = extract(instance.attr("__dict__")); - if (!PyDict_Check(pCache)) - BOOST_RAISE_EXCEPTION( - PyExc_ValueError, - "Cache dictionary is invalid." - ); - - Py_DECREF(pCache); - PyObject *pValue = PyDict_GetItemString(pCache, extract(m_name)); - if (pValue) - return object(handle<>(borrowed(pValue))); - - if (m_fget.is_none()) - BOOST_RAISE_EXCEPTION( - PyExc_AttributeError, - "Unable to retrieve the value of a property that have no getter function." + try + { + return cache[m_name]; + } + catch (...) + { + if (!PyErr_ExceptionMatches(PyExc_KeyError)) + throw_error_already_set(); + + PyErr_Clear(); + + if (m_fget.is_none()) + BOOST_RAISE_EXCEPTION( + PyExc_AttributeError, + "Unable to retrieve the value of a property that have no getter function." + ); + + cache[m_name] = _prepare_value( + m_fget( + *(make_tuple(handle<>(borrowed(instance.ptr()))) + m_args), + **m_kwargs + ) ); - - dict cache = extract(pCache); - cache[m_name] = _prepare_value( - m_fget( - *(make_tuple(handle<>(borrowed(instance.ptr()))) + m_args), - **m_kwargs - ) - ); + } return cache[m_name]; } @@ -181,7 +201,7 @@ void CCachedProperty::__set__(object instance, object value) if (!result.is_none()) cache[m_name] = _prepare_value(result); else - PyDict_DelItemString(cache.ptr(), extract(m_name)); + cache[m_name].del(); } void CCachedProperty::__delete__(object instance) @@ -193,7 +213,13 @@ void CCachedProperty::__delete__(object instance) ); dict cache = extract(instance.attr("__dict__")); - PyDict_DelItemString(cache.ptr(), extract(m_name)); + cache[m_name].del(); +} + +object CCachedProperty::__call__(object fget) +{ + m_fget = _callable_check(fget, "getter"); + return object(ptr(this)); } object CCachedProperty::__getitem__(str item) @@ -218,49 +244,3 @@ CCachedProperty *CCachedProperty::wrap_descriptor(object descriptor, object owne return pProperty; } - - -//----------------------------------------------------------------------------- -// CCachedGenerator class. -//----------------------------------------------------------------------------- -CCachedGenerator::CCachedGenerator(object generator) -{ - if (!PyGen_Check(generator.ptr())) - BOOST_RAISE_EXCEPTION( - PyExc_TypeError, - "The given generator is invalid." - ); - - object frame = generator.attr("gi_frame"); - if (frame.is_none()) - BOOST_RAISE_EXCEPTION( - PyExc_ValueError, - "The given generator is exhausted." - ); - - m_generator = generator; -} - - -object CCachedGenerator::get_generator() -{ - return m_generator; -} - - -object CCachedGenerator::__iter__() -{ - while(!m_generator.is_none()) - { - try - { - m_generated_values.append(m_generator.attr("__next__")()); - } - catch(...) - { - m_generator = object(); - PyErr_Clear(); - } - } - return m_generated_values.attr("__iter__")(); -} diff --git a/src/core/modules/core/core_cache.h b/src/core/modules/core/core_cache.h index 6ea8a67f3..6f8575f90 100644 --- a/src/core/modules/core/core_cache.h +++ b/src/core/modules/core/core_cache.h @@ -61,6 +61,7 @@ class CCachedProperty object __get__(object instance, object owner); void __set__(object instance, object value); void __delete__(object instance); + object __call__(object fget); object __getitem__(str item); void __setitem__(str item, object value); @@ -82,22 +83,4 @@ class CCachedProperty }; -//----------------------------------------------------------------------------- -// CCachedGenerator class. -//----------------------------------------------------------------------------- -class CCachedGenerator -{ -public: - CCachedGenerator(object generator); - - object get_generator(); - - object __iter__(); - -private: - object m_generator; - list m_generated_values; -}; - - #endif // _CORE_CACHE_H diff --git a/src/core/modules/core/core_cache_wrap.cpp b/src/core/modules/core/core_cache_wrap.cpp index 1c9939bda..dc2048f69 100644 --- a/src/core/modules/core/core_cache_wrap.cpp +++ b/src/core/modules/core/core_cache_wrap.cpp @@ -36,7 +36,6 @@ // Forward declarations. //----------------------------------------------------------------------------- static void export_cached_property(scope); -static void export_cached_generator(scope); //----------------------------------------------------------------------------- @@ -45,7 +44,6 @@ static void export_cached_generator(scope); DECLARE_SP_SUBMODULE(_core, _cache) { export_cached_property(_cache); - export_cached_generator(_cache); } @@ -237,7 +235,7 @@ void export_cached_property(scope _cache) CachedProperty.def( "__call__", - &CCachedProperty::set_getter, + &CCachedProperty::__call__, "Decorator used to register the getter function for this property.\n" "\n" ":param function fget:\n" @@ -267,7 +265,7 @@ void export_cached_property(scope _cache) ":param str item:\n" " The name of the keyword.\n" ":param object value:\n" - " The value to assigne to the given keyword." + " The value to assign to the given keyword." ); CachedProperty.def( @@ -281,37 +279,3 @@ void export_cached_property(scope _cache) scope().attr("cached_property") = scope().attr("CachedProperty"); } - - -//----------------------------------------------------------------------------- -// Exports CCachedGenerator. -//----------------------------------------------------------------------------- -void export_cached_generator(scope _cache) -{ - class_ CachedGenerator("CachedGenerator", - init( - ( - arg("generator") - ), - "Represents a cached generator.\n" - "If a :class:`core.cache.CachedProperty` returns a generator, it" - " is being cached as an instance of this class. Then, when" - " this instance is iterated over for the first time, the original" - " generator is processed and the generated values are cached.\n" - "\n" - ":param generator generator:\n" - " The wrapped generator instance.\n" - "\n" - ":raises TypeError:\n" - " If the given generator is invalid.\n" - ":raises ValueError:\n" - " If the given generator is exhausted." - ) - ); - - CachedGenerator.def( - "__iter__", - &CCachedGenerator::__iter__, - "Returns an iterator iterating over the generated values of the wrapped generator." - ); -} diff --git a/src/core/sp_python.cpp b/src/core/sp_python.cpp index ee43c06a7..15e61df30 100644 --- a/src/core/sp_python.cpp +++ b/src/core/sp_python.cpp @@ -175,6 +175,7 @@ bool CPythonManager::Initialize( void ) if (!modulsp_init()) { Msg(MSG_PREFIX "Failed to initialize internal modules.\n"); + PrintCurrentException(); return false; } @@ -230,26 +231,7 @@ bool CPythonManager::Initialize( void ) // Don't use PyErr_Print() here because our sys.excepthook (might) has not been installed // yet so let's just format and output to the console ourself. - if (PyErr_Occurred()) - { - PyObject *pType; - PyObject *pValue; - PyObject *pTraceback; - PyErr_Fetch(&pType, &pValue, &pTraceback); - PyErr_NormalizeException(&pType, &pValue, &pTraceback); - - handle<> hType(pType); - handle<> hValue(allow_null(pValue)); - handle<> hTraceback(allow_null(pTraceback)); - - object format_exception = import("traceback").attr("format_exception"); - const char* pMsg = extract(str("\n").join(format_exception(hType, hValue, hTraceback))); - - // Send the message in chunks, because it can get quite big. - ChunkedMsg(pMsg); - - PyErr_Clear(); - } + PrintCurrentException(); return false; } @@ -268,9 +250,8 @@ bool CPythonManager::Shutdown( void ) python::import("__init__").attr("unload")(); } catch( ... ) { - PyErr_Print(); - PyErr_Clear(); Msg(MSG_PREFIX "Failed to unload the main module.\n"); + PrintCurrentException(); return false; } return true; diff --git a/src/core/utilities/sp_util.h b/src/core/utilities/sp_util.h index 2484fb582..dbebe8a7e 100644 --- a/src/core/utilities/sp_util.h +++ b/src/core/utilities/sp_util.h @@ -58,6 +58,33 @@ inline void ChunkedMsg(const char* msg) } } +//----------------------------------------------------------------------------- +// Prints and clear the current exception. +//----------------------------------------------------------------------------- +inline void PrintCurrentException() +{ + if (PyErr_Occurred()) + { + PyObject *pType; + PyObject *pValue; + PyObject *pTraceback; + PyErr_Fetch(&pType, &pValue, &pTraceback); + PyErr_NormalizeException(&pType, &pValue, &pTraceback); + + handle<> hType(pType); + handle<> hValue(allow_null(pValue)); + handle<> hTraceback(allow_null(pTraceback)); + + object format_exception = import("traceback").attr("format_exception"); + const char* pMsg = extract(str("\n").join(format_exception(hType, hValue, hTraceback))); + + // Send the message in chunks, because it can get quite big. + ChunkedMsg(pMsg); + + PyErr_Clear(); + } +} + //----------------------------------------------------------------------------- // Returns True if the class name of the given object equals the given string. //----------------------------------------------------------------------------- From 388e71e948c3037a941c9a60f56d81bfeda9b097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Mon, 2 Dec 2019 19:45:01 -0500 Subject: [PATCH 10/59] EntityMemFuncWrapper is no longer caching itself; the Entity class already takes care of it. --- .../packages/source-python/entities/helpers.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/helpers.py b/addons/source-python/packages/source-python/entities/helpers.py index e95c74772..5c82d8df1 100644 --- a/addons/source-python/packages/source-python/entities/helpers.py +++ b/addons/source-python/packages/source-python/entities/helpers.py @@ -92,17 +92,13 @@ # ============================================================================= # >> CLASSES # ============================================================================= -class EntityMemFuncWrapper(MemberFunction, CachedProperty): +class EntityMemFuncWrapper(MemberFunction): def __init__(self, wrapper): self.__func__ = wrapper - CachedProperty.__init__(self) def __get__(self, wrapped_self, objtype): if wrapped_self is None: return self.__func__ - func = wrapped_self.__dict__.get(self.name, None) - if func is not None: - return func self.wrapped_self = wrapped_self func = wrapped_self.__getattr__(self.__func__.__name__) MemberFunction.__init__( @@ -110,9 +106,6 @@ def __get__(self, wrapped_self, objtype): ) return self - def __set__(self, wrapped_self, value): - wrapped_self.__dict__[self.name] = value - def __call__(self, *args, **kwargs): return super().__call__( *self.__func__(self.wrapped_self, *args, **kwargs)) From 9c7e4a22d5d465f705379457d5ce704ca0aa3eb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Tue, 3 Dec 2019 10:26:15 -0500 Subject: [PATCH 11/59] Fixed Player.is_bot and Player.is_hltv now being properties rather than function which was breaking existing code. Added BaseEntity.entity_flags. Fixed EntityMemFuncWrapper no longer overwriting the cache resulting into them no longer being called. Fixed an issue when entities instances were cached again after we invalidated the cache. --- .../packages/source-python/entities/_base.py | 9 ++++++++- .../packages/source-python/entities/helpers.py | 4 ++++ .../packages/source-python/players/_base.py | 16 +++++++++++++++- src/core/modules/entities/entities_entity.cpp | 11 +++++++++++ src/core/modules/entities/entities_entity.h | 3 +++ .../modules/entities/entities_entity_wrap.cpp | 8 ++++++++ 6 files changed, 49 insertions(+), 2 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/_base.py b/addons/source-python/packages/source-python/entities/_base.py index 07f2df1b3..1e1e582bc 100644 --- a/addons/source-python/packages/source-python/entities/_base.py +++ b/addons/source-python/packages/source-python/entities/_base.py @@ -20,6 +20,7 @@ from core.cache import cached_property # Entities from entities.constants import INVALID_ENTITY_INDEX +from entities.constants import EntityFlags # Engines from engines.precache import Model from engines.sound import Attenuation @@ -109,7 +110,13 @@ def __call__(cls, index, caching=True): # Let's cache the new instance we just created if caching: - cls._cache[index] = obj + + # Only cache entities that are not marked for deletion. + # This is required, because if someone request an entity instance + # after we invalidated our cache but before the engine processed + # the deletion we would now have an invalid instance in the cache. + if not obj.entity_flags & EntityFlags.KILLME: + cls._cache[index] = obj # We are done, let's return the instance return obj diff --git a/addons/source-python/packages/source-python/entities/helpers.py b/addons/source-python/packages/source-python/entities/helpers.py index 5c82d8df1..ad332a564 100644 --- a/addons/source-python/packages/source-python/entities/helpers.py +++ b/addons/source-python/packages/source-python/entities/helpers.py @@ -96,6 +96,9 @@ class EntityMemFuncWrapper(MemberFunction): def __init__(self, wrapper): self.__func__ = wrapper + def __set_name__(self, owner, name): + self.name = name + def __get__(self, wrapped_self, objtype): if wrapped_self is None: return self.__func__ @@ -104,6 +107,7 @@ def __get__(self, wrapped_self, objtype): MemberFunction.__init__( self, func._manager, func._type_name, func, func._this ) + wrapped_self.__dict__[self.name] = self return self def __call__(self, *args, **kwargs): diff --git a/addons/source-python/packages/source-python/players/_base.py b/addons/source-python/packages/source-python/players/_base.py index 4b73b6955..54c78dc6c 100644 --- a/addons/source-python/packages/source-python/players/_base.py +++ b/addons/source-python/packages/source-python/players/_base.py @@ -206,14 +206,28 @@ def is_fake_client(self): return self.playerinfo.is_fake_client() @cached_property - def is_hltv(self): + def _is_hltv(self): """Return whether the player is HLTV. :rtype: bool """ return self.playerinfo.is_hltv() + def is_hltv(self): + """Return whether the player is HLTV. + + :rtype: bool + """ + return self._is_hltv + @cached_property + def _is_bot(self): + """Return whether the player is a bot. + + :rtype: bool + """ + return self._is_bot + def is_bot(self): """Return whether the player is a bot. diff --git a/src/core/modules/entities/entities_entity.cpp b/src/core/modules/entities/entities_entity.cpp index eee4544e5..da828ee30 100644 --- a/src/core/modules/entities/entities_entity.cpp +++ b/src/core/modules/entities/entities_entity.cpp @@ -428,6 +428,17 @@ void CBaseEntityWrapper::SetMins(Vector& vec) SetNetworkPropertyByOffset(offset, vec); } +int CBaseEntityWrapper::GetEntityFlags() +{ + static int offset = FindDatamapPropertyOffset("m_iEFlags"); + return GetDatamapPropertyByOffset(offset); +} + +void CBaseEntityWrapper::SetEntityFlags(int flags) +{ + static int offset = FindDatamapPropertyOffset("m_iEFlags"); + SetDatamapPropertyByOffset(offset, flags); +} SolidType_t CBaseEntityWrapper::GetSolidType() { diff --git a/src/core/modules/entities/entities_entity.h b/src/core/modules/entities/entities_entity.h index 1fb33cdb5..742ecf1e8 100644 --- a/src/core/modules/entities/entities_entity.h +++ b/src/core/modules/entities/entities_entity.h @@ -251,6 +251,9 @@ class CBaseEntityWrapper: public IServerEntity Vector GetMins(); void SetMins(Vector& mins); + int GetEntityFlags(); + void SetEntityFlags(int flags); + SolidType_t GetSolidType(); void SetSolidType(SolidType_t type); diff --git a/src/core/modules/entities/entities_entity_wrap.cpp b/src/core/modules/entities/entities_entity_wrap.cpp index a14283abb..ab2851b9a 100644 --- a/src/core/modules/entities/entities_entity_wrap.cpp +++ b/src/core/modules/entities/entities_entity_wrap.cpp @@ -130,6 +130,14 @@ void export_base_entity(scope _entity) ":rtype: Vector" ); + BaseEntity.add_property( + "entity_flags", + &CBaseEntityWrapper::GetEntityFlags, + &CBaseEntityWrapper::SetEntityFlags, + "Get/set the entity's flags.\n\n" + ":rtype: int" + ); + BaseEntity.add_property( "solid_type", &CBaseEntityWrapper::GetSolidType, From 9066933d08e18b0f03c7a8b3ec55a28e0ade8885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Tue, 3 Dec 2019 12:04:19 -0500 Subject: [PATCH 12/59] Improved the caching of dynamic function wrappers. --- .../source-python/entities/helpers.py | 28 ++++++------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/helpers.py b/addons/source-python/packages/source-python/entities/helpers.py index ad332a564..33d1617c6 100644 --- a/addons/source-python/packages/source-python/entities/helpers.py +++ b/addons/source-python/packages/source-python/entities/helpers.py @@ -93,34 +93,23 @@ # >> CLASSES # ============================================================================= class EntityMemFuncWrapper(MemberFunction): - def __init__(self, wrapper): - self.__func__ = wrapper - - def __set_name__(self, owner, name): - self.name = name - - def __get__(self, wrapped_self, objtype): - if wrapped_self is None: - return self.__func__ + def __init__(self, wrapped_self, wrapper): + func = wrapped_self.__getattr__(wrapper.__name__) + super().__init__(func._manager, func._type_name, func, func._this) + self.wrapper = wrapper self.wrapped_self = wrapped_self - func = wrapped_self.__getattr__(self.__func__.__name__) - MemberFunction.__init__( - self, func._manager, func._type_name, func, func._this - ) - wrapped_self.__dict__[self.name] = self - return self def __call__(self, *args, **kwargs): return super().__call__( - *self.__func__(self.wrapped_self, *args, **kwargs)) + *self.wrapper(self.wrapped_self, *args, **kwargs)) def call_trampoline(self, *args, **kwargs): return super().call_trampoline( - *self.__func__(self.wrapped_self, *args, **kwargs)) + *self.wrapper(self.wrapped_self, *args, **kwargs)) def skip_hooks(self, *args, **kwargs): return super().skip_hooks( - *self.__func__(self.wrapped_self, *args, **kwargs)) + *self.wrapper(self.wrapped_self, *args, **kwargs)) # ============================================================================= @@ -128,4 +117,5 @@ def skip_hooks(self, *args, **kwargs): # ============================================================================= def wrap_entity_mem_func(wrapper): """A decorator to wrap an entity memory function.""" - return EntityMemFuncWrapper(wrapper) + + return CachedProperty(lambda self: EntityMemFuncWrapper(self, wrapper)) From b1873910fb009483a4033bc5a7a73d74b919e123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Wed, 4 Dec 2019 12:47:52 -0500 Subject: [PATCH 13/59] Fixed a circular reference. --- .../packages/source-python/entities/helpers.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/addons/source-python/packages/source-python/entities/helpers.py b/addons/source-python/packages/source-python/entities/helpers.py index 33d1617c6..b1a69cb60 100644 --- a/addons/source-python/packages/source-python/entities/helpers.py +++ b/addons/source-python/packages/source-python/entities/helpers.py @@ -5,6 +5,10 @@ # ============================================================================= # >> IMPORTS # ============================================================================= +# Python Imports +# WeakRef +from weakref import ref + # Source.Python Imports # Core from core.cache import CachedProperty @@ -97,7 +101,11 @@ def __init__(self, wrapped_self, wrapper): func = wrapped_self.__getattr__(wrapper.__name__) super().__init__(func._manager, func._type_name, func, func._this) self.wrapper = wrapper - self.wrapped_self = wrapped_self + + # Don't store a strong reference to the wrapped instance. + # If we do, we will ends with a circular reference preventing itself, + # along with everything it refers, to ever be garbage collected. + self.wrapped_self = ref(wrapped_self) def __call__(self, *args, **kwargs): return super().__call__( From 682ee746c28c4a48615ee84f53f8c1cbbd9994fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Wed, 4 Dec 2019 13:19:05 -0500 Subject: [PATCH 14/59] Fixed cached properties declared on the c++ side not being managed by Python. --- src/core/utilities/wrap_macros.h | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/core/utilities/wrap_macros.h b/src/core/utilities/wrap_macros.h index 294bb5ddb..d70c420b3 100644 --- a/src/core/utilities/wrap_macros.h +++ b/src/core/utilities/wrap_macros.h @@ -125,6 +125,17 @@ inline void* GetFuncPtr(Function func) static_cast< return_type(*)( __VA_ARGS__ ) >(&function) +//--------------------------------------------------------------------------------- +// Use this to transfer ownership of a pointer to Python. +//--------------------------------------------------------------------------------- +template +object transfer_ownership_to_python(T *pPtr) +{ + typename manage_new_object::apply::type holder; + return object(handle<>(holder(*pPtr))); +}; + + //--------------------------------------------------------------------------------- // Use these to declare classmethod wrappers. //--------------------------------------------------------------------------------- @@ -152,7 +163,9 @@ T classmethod(T cls, const char *szName) template T cached_property(T cls, const char *szName) { - cls.attr(szName) = ptr(CCachedProperty::wrap_descriptor(cls.attr(szName), cls, szName)); + cls.attr(szName) = transfer_ownership_to_python( + CCachedProperty::wrap_descriptor(cls.attr(szName), cls, szName) + ); return cls; }; From 0e29616870f7d8c12d74138830c4b9c958351904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Wed, 4 Dec 2019 13:26:39 -0500 Subject: [PATCH 15/59] Removed CachedProperty.owner, which was causing another circular reference because it was referring to the class the property itself is bound to. --- src/core/modules/core/core_cache.cpp | 6 ------ src/core/modules/core/core_cache.h | 2 -- src/core/modules/core/core_cache_wrap.cpp | 9 --------- 3 files changed, 17 deletions(-) diff --git a/src/core/modules/core/core_cache.cpp b/src/core/modules/core/core_cache.cpp index 3c003f874..7dce8d783 100644 --- a/src/core/modules/core/core_cache.cpp +++ b/src/core/modules/core/core_cache.cpp @@ -125,11 +125,6 @@ object CCachedProperty::set_deleter(object fdel) } -object CCachedProperty::get_owner() -{ - return m_owner; -} - str CCachedProperty::get_name() { return m_name; @@ -138,7 +133,6 @@ str CCachedProperty::get_name() void CCachedProperty::__set_name__(object owner, str name) { - m_owner = owner; m_name = name; } diff --git a/src/core/modules/core/core_cache.h b/src/core/modules/core/core_cache.h index 6f8575f90..081ba6bf4 100644 --- a/src/core/modules/core/core_cache.h +++ b/src/core/modules/core/core_cache.h @@ -54,7 +54,6 @@ class CCachedProperty object get_deleter(); object set_deleter(object fget); - object get_owner(); str get_name(); void __set_name__(object owner, str name); @@ -72,7 +71,6 @@ class CCachedProperty object m_fset; object m_fdel; - object m_owner; str m_name; public: diff --git a/src/core/modules/core/core_cache_wrap.cpp b/src/core/modules/core/core_cache_wrap.cpp index dc2048f69..44cbf9a26 100644 --- a/src/core/modules/core/core_cache_wrap.cpp +++ b/src/core/modules/core/core_cache_wrap.cpp @@ -173,15 +173,6 @@ void export_cached_property(scope _cache) ); - CachedProperty.add_property( - "owner", - &CCachedProperty::get_owner, - "The owner class this property attribute was bound to.\n" - "\n" - ":rtype:\n" - " type" - ); - CachedProperty.add_property( "name", &CCachedProperty::get_name, From 258c105ad77937e442bea0dbec974594265e4117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Wed, 4 Dec 2019 13:37:22 -0500 Subject: [PATCH 16/59] Fixed back reference issues. --- src/core/modules/core/core_cache.cpp | 30 ++++++++++++++++------------ src/core/modules/core/core_cache.h | 4 ++-- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/core/modules/core/core_cache.cpp b/src/core/modules/core/core_cache.cpp index 7dce8d783..d4f92e472 100644 --- a/src/core/modules/core/core_cache.cpp +++ b/src/core/modules/core/core_cache.cpp @@ -136,12 +136,14 @@ void CCachedProperty::__set_name__(object owner, str name) m_name = name; } -object CCachedProperty::__get__(object instance, object owner=object()) +object CCachedProperty::__get__(object self, object instance, object owner=object()) { + CCachedProperty &pSelf = extract(self); if (instance.is_none()) - return object(ptr(this)); + return self; - if (m_name.is_none()) + object name = pSelf.get_name(); + if (name.is_none()) BOOST_RAISE_EXCEPTION( PyExc_AttributeError, "Unable to retrieve the value of an unbound property." @@ -151,7 +153,7 @@ object CCachedProperty::__get__(object instance, object owner=object()) try { - return cache[m_name]; + return cache[name]; } catch (...) { @@ -160,21 +162,22 @@ object CCachedProperty::__get__(object instance, object owner=object()) PyErr_Clear(); - if (m_fget.is_none()) + object getter = pSelf.get_getter(); + if (getter.is_none()) BOOST_RAISE_EXCEPTION( PyExc_AttributeError, "Unable to retrieve the value of a property that have no getter function." ); - cache[m_name] = _prepare_value( - m_fget( - *(make_tuple(handle<>(borrowed(instance.ptr()))) + m_args), - **m_kwargs + cache[name] = pSelf._prepare_value( + getter( + *(make_tuple(handle<>(borrowed(instance.ptr()))) + pSelf.m_args), + **pSelf.m_kwargs ) ); } - return cache[m_name]; + return cache[name]; } @@ -210,10 +213,11 @@ void CCachedProperty::__delete__(object instance) cache[m_name].del(); } -object CCachedProperty::__call__(object fget) +object CCachedProperty::__call__(object self, object fget) { - m_fget = _callable_check(fget, "getter"); - return object(ptr(this)); + CCachedProperty &pSelf = extract(self); + pSelf.set_getter(fget); + return self; } object CCachedProperty::__getitem__(str item) diff --git a/src/core/modules/core/core_cache.h b/src/core/modules/core/core_cache.h index 081ba6bf4..a3296d3da 100644 --- a/src/core/modules/core/core_cache.h +++ b/src/core/modules/core/core_cache.h @@ -57,10 +57,10 @@ class CCachedProperty str get_name(); void __set_name__(object owner, str name); - object __get__(object instance, object owner); + static object __get__(object self, object instance, object owner); void __set__(object instance, object value); void __delete__(object instance); - object __call__(object fget); + static object __call__(object self, object fget); object __getitem__(str item); void __setitem__(str item, object value); From fc618e17be6373b9066dcf88b863d852026e0d53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Wed, 4 Dec 2019 13:56:58 -0500 Subject: [PATCH 17/59] Fixed KeyError when invalidating the cache for field that were not previously requested. --- src/core/modules/core/core_cache.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/core/modules/core/core_cache.cpp b/src/core/modules/core/core_cache.cpp index d4f92e472..65347fcc3 100644 --- a/src/core/modules/core/core_cache.cpp +++ b/src/core/modules/core/core_cache.cpp @@ -198,7 +198,16 @@ void CCachedProperty::__set__(object instance, object value) if (!result.is_none()) cache[m_name] = _prepare_value(result); else - cache[m_name].del(); + { + try + { + cache[m_name].del(); + } + catch (...) + { + PyErr_Clear(); + } + } } void CCachedProperty::__delete__(object instance) @@ -210,7 +219,14 @@ void CCachedProperty::__delete__(object instance) ); dict cache = extract(instance.attr("__dict__")); - cache[m_name].del(); + try + { + cache[m_name].del(); + } + catch (...) + { + PyErr_Clear(); + } } object CCachedProperty::__call__(object self, object fget) From de055b852ed3fa71eed7f10ceaa86d22a0bab15e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Wed, 4 Dec 2019 14:44:57 -0500 Subject: [PATCH 18/59] Fixed EntityDictionary potentially caching soon-to-be removed entities. --- .../source-python/entities/dictionary.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/dictionary.py b/addons/source-python/packages/source-python/entities/dictionary.py index 8cbbb7827..cd33382f5 100644 --- a/addons/source-python/packages/source-python/entities/dictionary.py +++ b/addons/source-python/packages/source-python/entities/dictionary.py @@ -5,10 +5,15 @@ # ============================================================================ # >> IMPORTS # ============================================================================ +# Python Imports +# ContextLib +from contextlib import suppress + # Source.Python Imports # Core from core import AutoUnload # Entities +from entities.constants import EntityFlags from entities.entity import Entity from entities.helpers import index_from_inthandle # Listeners @@ -46,8 +51,15 @@ def __init__(self, factory=Entity, *args, **kwargs): def __missing__(self, index): """Add and return the entity instance for the given index.""" - instance = self[index] = self._factory(index, *self._args, - **self._kwargs) + instance = self._factory(index, *self._args, **self._kwargs) + + # Only cache entities that are not marked for deletion. + # This is required, because if someone request an entity instance + # after we invalidated our cache but before the engine processed + # the deletion we would now have an invalid instance in the cache. + if not instance.entity_flags & EntityFlags.KILLME: + self[index] = instance + return instance def __delitem__(self, index): From b664ef080ac3bbaac04b317169e2be9349cfad4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Wed, 4 Dec 2019 15:14:54 -0500 Subject: [PATCH 19/59] Added a warning to CachedProperty's documentation regarding circular references. Updated some docstrings. --- src/core/modules/core/core_cache_wrap.cpp | 32 ++++++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/core/modules/core/core_cache_wrap.cpp b/src/core/modules/core/core_cache_wrap.cpp index 44cbf9a26..022fc4b63 100644 --- a/src/core/modules/core/core_cache_wrap.cpp +++ b/src/core/modules/core/core_cache_wrap.cpp @@ -89,7 +89,11 @@ void export_cached_property(scope _cache) " Extra keyword arguments passed to the getter, setter and deleter functions.\n" "\n" ":raises TypeError:\n" - " If the given getter, setter or deleter is not callable." + " If the given getter, setter or deleter is not callable.\n" + "\n" + ".. warning ::\n" + " If a cached object hold a strong reference of the instance it belongs to," + " this will result in a circular reference preventing their garbage collection." ) ); @@ -185,13 +189,19 @@ void export_cached_property(scope _cache) CachedProperty.def_readwrite( "args", &CCachedProperty::m_args, - "The extra arguments passed to the getter, setter and deleter functions." + "The extra arguments passed to the getter, setter and deleter functions.\n" + "\n" + ":rtype:\n" + " tuple" ); CachedProperty.def_readwrite( "kwargs", &CCachedProperty::m_kwargs, - "The extra keyword arguments passed to the getter, setter and deleter functions." + "The extra keyword arguments passed to the getter, setter and deleter functions.\n" + "\n" + ":rtype:\n" + " dict" ); CachedProperty.def( @@ -263,7 +273,21 @@ void export_cached_property(scope _cache) "wrap_descriptor", &CCachedProperty::wrap_descriptor, manage_new_object_policy(), - "Wraps a descriptor as a cached property.", + "Wraps a descriptor as a cached property.\n" + "\n" + ":param property descriptor:\n" + " Property descriptor to wrap.\n" + " Must have a __get__, __set__ and a __del__ methods bound to it, either" + " callable or set to None.\n" + ":param class owner:\n" + " The class the wrapped property should be bound to.\n" + ":param str name:\n" + " The name of this property.\n" + "\n" + ":raises AttributeError:\n" + " If the given descriptor doesn't have the required methods.\n" + ":raises TypeError:\n" + " If the getter, setter or deleter are not callable.", ("descriptor", arg("owner")=object(), arg("name")=str()) ) .staticmethod("wrap_descriptor"); From be366260de004c33eb0025792c62cab3acd57e24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Wed, 4 Dec 2019 17:16:45 -0500 Subject: [PATCH 20/59] Fixed dynamic function wrappers from no longer being documented. --- .../source-python/packages/source-python/entities/helpers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/addons/source-python/packages/source-python/entities/helpers.py b/addons/source-python/packages/source-python/entities/helpers.py index b1a69cb60..29ecb4a77 100644 --- a/addons/source-python/packages/source-python/entities/helpers.py +++ b/addons/source-python/packages/source-python/entities/helpers.py @@ -126,4 +126,7 @@ def skip_hooks(self, *args, **kwargs): def wrap_entity_mem_func(wrapper): """A decorator to wrap an entity memory function.""" - return CachedProperty(lambda self: EntityMemFuncWrapper(self, wrapper)) + return CachedProperty( + lambda self: EntityMemFuncWrapper(self, wrapper), + doc=wrapper.__doc__ + ) From 7747961a08794586fb48ccb78958fc6d73125d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Thu, 5 Dec 2019 10:22:37 -0500 Subject: [PATCH 21/59] Fixed Entity.__setattr__ not properly iterating over the entity's server classes. Fixed dynamic function wrappers from no longer being able to access the instance. Some various improvements to some redundant logics. --- .../packages/source-python/entities/_base.py | 35 ++++++++----------- .../source-python/entities/dictionary.py | 35 ++++++------------- .../source-python/entities/helpers.py | 4 +-- 3 files changed, 27 insertions(+), 47 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/_base.py b/addons/source-python/packages/source-python/entities/_base.py index 1e1e582bc..2dd482a58 100644 --- a/addons/source-python/packages/source-python/entities/_base.py +++ b/addons/source-python/packages/source-python/entities/_base.py @@ -208,13 +208,13 @@ def __setattr__(self, attr, value): return # Loop through all of the entity's server classes - for server_class in self.server_classes: + for server_class, instance in self.server_classes.items(): # Does the current server class contain the given attribute? if hasattr(server_class, attr): # Set the attribute's value - setattr(server_class(self.pointer, wrap=True), attr, value) + setattr(instance, attr, value) # No need to go further return @@ -1219,30 +1219,23 @@ def _on_entity_deleted(base_entity): :param BaseEntity base_entity: The removed entity. """ - # Make sure the entity is networkable... - if not base_entity.is_networked(): + try: + # Get the index of the entity... + index = base_entity.index + except ValueError: return - # Get the index of the entity... - index = base_entity.index - # Cleanup the cache for cls in _entity_classes: cls.cache.pop(index, None) - # Was no delay registered for this entity? - if index not in _entity_delays: - return - - # Loop through all delays... - for delay in _entity_delays[index]: - - # Make sure the delay is still running... - if not delay.running: - continue + with suppress(KeyError): + # Loop through all delays... + for delay in _entity_delays[index]: - # Cancel the delay... - delay.cancel() + # Cancel the delay... + with suppress(ValueError): + delay.cancel() - # Remove the entity from the dictionary... - del _entity_delays[index] + # Remove the entity from the dictionary... + del _entity_delays[index] diff --git a/addons/source-python/packages/source-python/entities/dictionary.py b/addons/source-python/packages/source-python/entities/dictionary.py index cd33382f5..696040a02 100644 --- a/addons/source-python/packages/source-python/entities/dictionary.py +++ b/addons/source-python/packages/source-python/entities/dictionary.py @@ -64,14 +64,9 @@ def __missing__(self, index): def __delitem__(self, index): """Remove the given index from the dictionary.""" - # Is the given index not in the dictionary? - if index not in self: - - # If so, no need to go further... - return - # Remove the given index from the dictionary... - super().__delitem__(index) + with suppress(KeyError): + super().__delitem__(index) def from_inthandle(self, inthandle): """Get an entity instance from an inthandle. @@ -87,26 +82,18 @@ def on_automatically_removed(self, index): def _on_entity_deleted(self, base_entity): """OnEntityDeleted listener callback.""" - # Is the entity networkable? - if not base_entity.is_networked(): - - # No, so skip it... - return - - # Get the index of the entity... - index = base_entity.index - - # Is the index not in the dictionary? - if index not in self: - - # No need to go further... + try: + # Get the index of the entity... + index = base_entity.index + except ValueError: return - # Call the deletion callback for the index... - self.on_automatically_removed(index) + with suppress(KeyError): + # Call the deletion callback for the index... + self.on_automatically_removed(index) - # Remove the index from the dictionary... - super().__delitem__(index) + # Remove the index from the dictionary... + super().__delitem__(index) def _unload_instance(self): """Unregister our OnEntityDeleted listener.""" diff --git a/addons/source-python/packages/source-python/entities/helpers.py b/addons/source-python/packages/source-python/entities/helpers.py index 29ecb4a77..24d94944b 100644 --- a/addons/source-python/packages/source-python/entities/helpers.py +++ b/addons/source-python/packages/source-python/entities/helpers.py @@ -7,7 +7,7 @@ # ============================================================================= # Python Imports # WeakRef -from weakref import ref +from weakref import proxy # Source.Python Imports # Core @@ -105,7 +105,7 @@ def __init__(self, wrapped_self, wrapper): # Don't store a strong reference to the wrapped instance. # If we do, we will ends with a circular reference preventing itself, # along with everything it refers, to ever be garbage collected. - self.wrapped_self = ref(wrapped_self) + self.wrapped_self = proxy(wrapped_self) def __call__(self, *args, **kwargs): return super().__call__( From 88bec549b78d4ea25d125f3828fbce36e820d5fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Thu, 5 Dec 2019 23:23:13 -0500 Subject: [PATCH 22/59] Added "unbound" parameter to CachedProperty as a workaround for circular references. Entity.__hash__ now hashes its inthandle rather than its index (the later can be reused and match a new entity later on, while the former doesn't seems to be ever re-used from my testing). --- .../packages/source-python/entities/_base.py | 2 +- .../source-python/entities/helpers.py | 13 +-- src/core/modules/core/core_cache.cpp | 107 +++++++++++++----- src/core/modules/core/core_cache.h | 11 +- src/core/modules/core/core_cache_wrap.cpp | 8 +- 5 files changed, 101 insertions(+), 40 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/_base.py b/addons/source-python/packages/source-python/entities/_base.py index 2dd482a58..de5ddb2a4 100644 --- a/addons/source-python/packages/source-python/entities/_base.py +++ b/addons/source-python/packages/source-python/entities/_base.py @@ -169,7 +169,7 @@ def __init__(self, index, caching=True): def __hash__(self): """Return a hash value based on the entity index.""" # Required for sets, because we have implemented __eq__ - return hash(self.index) + return hash(self.inthandle) def __getattr__(self, attr): """Find if the attribute is valid and returns the appropriate value.""" diff --git a/addons/source-python/packages/source-python/entities/helpers.py b/addons/source-python/packages/source-python/entities/helpers.py index 24d94944b..3c1f79476 100644 --- a/addons/source-python/packages/source-python/entities/helpers.py +++ b/addons/source-python/packages/source-python/entities/helpers.py @@ -5,10 +5,6 @@ # ============================================================================= # >> IMPORTS # ============================================================================= -# Python Imports -# WeakRef -from weakref import proxy - # Source.Python Imports # Core from core.cache import CachedProperty @@ -101,11 +97,7 @@ def __init__(self, wrapped_self, wrapper): func = wrapped_self.__getattr__(wrapper.__name__) super().__init__(func._manager, func._type_name, func, func._this) self.wrapper = wrapper - - # Don't store a strong reference to the wrapped instance. - # If we do, we will ends with a circular reference preventing itself, - # along with everything it refers, to ever be garbage collected. - self.wrapped_self = proxy(wrapped_self) + self.wrapped_self = wrapped_self def __call__(self, *args, **kwargs): return super().__call__( @@ -128,5 +120,6 @@ def wrap_entity_mem_func(wrapper): return CachedProperty( lambda self: EntityMemFuncWrapper(self, wrapper), - doc=wrapper.__doc__ + doc=wrapper.__doc__, + unbound=True ) diff --git a/src/core/modules/core/core_cache.cpp b/src/core/modules/core/core_cache.cpp index 65347fcc3..bd25e6271 100644 --- a/src/core/modules/core/core_cache.cpp +++ b/src/core/modules/core/core_cache.cpp @@ -36,13 +36,14 @@ //----------------------------------------------------------------------------- CCachedProperty::CCachedProperty( object fget=object(), object fset=object(), object fdel=object(), const char *doc=NULL, - boost::python::tuple args=boost::python::tuple(), dict kwargs=dict()) + bool unbound=false, boost::python::tuple args=boost::python::tuple(), dict kwargs=dict()) { set_getter(fget); set_setter(fset); set_deleter(fdel); m_szDocString = doc; + m_bUnbound = unbound; m_args = args; m_kwargs = kwargs; @@ -88,6 +89,66 @@ object CCachedProperty::_prepare_value(object value) return values; } +void CCachedProperty::_invalidate_cache(PyObject *pRef) +{ + try + { + m_cache[handle<>(pRef)].del(); + } + catch (...) + { + if (!PyErr_ExceptionMatches(PyExc_KeyError)) + throw_error_already_set(); + + PyErr_Clear(); + } +} + +void CCachedProperty::_update_cache(object instance, object value) +{ + if (m_bUnbound) + m_cache[handle<>( + PyWeakref_NewRef( + instance.ptr(), + make_function( + boost::bind(&CCachedProperty::_invalidate_cache, this, _1), + default_call_policies(), + boost::mpl::vector2() + ).ptr() + ) + )] = value; + else + { + dict cache = extract(instance.attr("__dict__")); + cache[m_name] = value; + } +} + +void CCachedProperty::_delete_cache(object instance) +{ + try + { + if (m_bUnbound) + m_cache[ + handle<>( + PyWeakref_NewRef(instance.ptr(), NULL) + ) + ].del(); + else + { + dict cache = extract(instance.attr("__dict__")); + cache[m_name].del(); + } + } + catch (...) + { + if (!PyErr_ExceptionMatches(PyExc_KeyError)) + throw_error_already_set(); + + PyErr_Clear(); + } +} + object CCachedProperty::get_getter() { @@ -149,11 +210,21 @@ object CCachedProperty::__get__(object self, object instance, object owner=objec "Unable to retrieve the value of an unbound property." ); - object cache = extract(instance.attr("__dict__")); + object value; try { - return cache[name]; + if (pSelf.m_bUnbound) + return pSelf.m_cache[ + handle<>( + PyWeakref_NewRef(instance.ptr(), NULL) + ) + ]; + else + { + dict cache = extract(instance.attr("__dict__")); + return cache[name]; + } } catch (...) { @@ -169,15 +240,17 @@ object CCachedProperty::__get__(object self, object instance, object owner=objec "Unable to retrieve the value of a property that have no getter function." ); - cache[name] = pSelf._prepare_value( + value = pSelf._prepare_value( getter( *(make_tuple(handle<>(borrowed(instance.ptr()))) + pSelf.m_args), **pSelf.m_kwargs ) ); + + pSelf._update_cache(instance, value); } - return cache[name]; + return value; } @@ -194,20 +267,10 @@ void CCachedProperty::__set__(object instance, object value) **m_kwargs ); - dict cache = extract(instance.attr("__dict__")); if (!result.is_none()) - cache[m_name] = _prepare_value(result); + _update_cache(instance, _prepare_value(result)); else - { - try - { - cache[m_name].del(); - } - catch (...) - { - PyErr_Clear(); - } - } + _delete_cache(instance); } void CCachedProperty::__delete__(object instance) @@ -218,15 +281,7 @@ void CCachedProperty::__delete__(object instance) **m_kwargs ); - dict cache = extract(instance.attr("__dict__")); - try - { - cache[m_name].del(); - } - catch (...) - { - PyErr_Clear(); - } + _delete_cache(instance); } object CCachedProperty::__call__(object self, object fget) diff --git a/src/core/modules/core/core_cache.h b/src/core/modules/core/core_cache.h index a3296d3da..a599a1bb6 100644 --- a/src/core/modules/core/core_cache.h +++ b/src/core/modules/core/core_cache.h @@ -40,10 +40,16 @@ using namespace boost::python; class CCachedProperty { public: - CCachedProperty(object fget, object fset, object fdel, const char *doc, boost::python::tuple args, dict kwargs); + CCachedProperty( + object fget, object fset, object fdel, const char *doc, bool unbound, + boost::python::tuple args, dict kwargs + ); static object _callable_check(object function, const char *szName); static object _prepare_value(object value); + void _invalidate_cache(PyObject *pRef); + void _update_cache(object instance, object value); + void _delete_cache(object instance); object get_getter(); object set_getter(object fget); @@ -73,6 +79,9 @@ class CCachedProperty str m_name; + bool m_bUnbound; + dict m_cache; + public: const char *m_szDocString; diff --git a/src/core/modules/core/core_cache_wrap.cpp b/src/core/modules/core/core_cache_wrap.cpp index 022fc4b63..7abf17541 100644 --- a/src/core/modules/core/core_cache_wrap.cpp +++ b/src/core/modules/core/core_cache_wrap.cpp @@ -54,10 +54,10 @@ void export_cached_property(scope _cache) { class_ CachedProperty( "CachedProperty", - init( + init( ( arg("fget")=object(), arg("fset")=object(), arg("fdel")=object(), arg("doc")=object(), - arg("args")=boost::python::tuple(), arg("kwargs")=dict() + arg("unbound")=false, arg("args")=boost::python::tuple(), arg("kwargs")=dict() ), "Represents a property attribute that is only" " computed once and cached.\n" @@ -83,6 +83,10 @@ void export_cached_property(scope _cache) " Deleter signature: self, *args, **kwargs\n" ":param str doc:\n" " Documentation string for this property.\n" + ":param bool unbound\n" + " Whether the cached objects should be independently maintained rather than bound to" + " the instance they belong to. The cache will be slightly slower to lookup, but this can" + " be required in certain cases to prevent circular references/memory leak.\n" ":param tuple args:\n" " Extra arguments passed to the getter, setter and deleter functions.\n" ":param dict kwargs:\n" From 45a4060f7a14f89bb0634381c73994c48144788c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Fri, 6 Dec 2019 13:57:30 -0500 Subject: [PATCH 23/59] Added back CachedProperty.owner as a weak reference. Reverted EntityMemFuncWrapper back to be bound and store a proxy of the wrapped instance. My tests were not leaking because the hash was matching so the cache was re-used but no matter where we cache the value, it will still keep the instance hostage if it strongly refers it. Left the unbound implementation in place, as it can be useful in other cases. Added unbound parameter to CachedProperty.wrap_descriptor. Updated docstrings and signatures. --- .../source-python/entities/helpers.py | 9 ++- src/core/modules/core/core_cache.cpp | 13 +++- src/core/modules/core/core_cache.h | 4 +- src/core/modules/core/core_cache_wrap.cpp | 63 +++++++++++++++---- src/core/utilities/wrap_macros.h | 2 +- 5 files changed, 70 insertions(+), 21 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/helpers.py b/addons/source-python/packages/source-python/entities/helpers.py index 3c1f79476..1d67fbf7b 100644 --- a/addons/source-python/packages/source-python/entities/helpers.py +++ b/addons/source-python/packages/source-python/entities/helpers.py @@ -5,6 +5,10 @@ # ============================================================================= # >> IMPORTS # ============================================================================= +# Python Imports +# WeakRef +from weakref import proxy + # Source.Python Imports # Core from core.cache import CachedProperty @@ -97,7 +101,7 @@ def __init__(self, wrapped_self, wrapper): func = wrapped_self.__getattr__(wrapper.__name__) super().__init__(func._manager, func._type_name, func, func._this) self.wrapper = wrapper - self.wrapped_self = wrapped_self + self.wrapped_self = proxy(wrapped_self) def __call__(self, *args, **kwargs): return super().__call__( @@ -120,6 +124,5 @@ def wrap_entity_mem_func(wrapper): return CachedProperty( lambda self: EntityMemFuncWrapper(self, wrapper), - doc=wrapper.__doc__, - unbound=True + doc=wrapper.__doc__ ) diff --git a/src/core/modules/core/core_cache.cpp b/src/core/modules/core/core_cache.cpp index bd25e6271..897a45dac 100644 --- a/src/core/modules/core/core_cache.cpp +++ b/src/core/modules/core/core_cache.cpp @@ -191,10 +191,16 @@ str CCachedProperty::get_name() return m_name; } +object CCachedProperty::get_owner() +{ + return m_owner(); +} + void CCachedProperty::__set_name__(object owner, str name) { m_name = name; + m_owner = object(handle<>(PyWeakref_NewRef(owner.ptr(), NULL))); } object CCachedProperty::__get__(object self, object instance, object owner=object()) @@ -302,13 +308,14 @@ void CCachedProperty::__setitem__(str item, object value) } -CCachedProperty *CCachedProperty::wrap_descriptor(object descriptor, object owner=object(), str name=str()) +CCachedProperty *CCachedProperty::wrap_descriptor( + object descriptor, object owner=object(), str name=str(), bool unbound=false) { CCachedProperty *pProperty = new CCachedProperty( - descriptor.attr("__get__"), descriptor.attr("__set__"), descriptor.attr("__delete__") + descriptor.attr("__get__"), descriptor.attr("__set__"), descriptor.attr("__delete__"), + extract(descriptor.attr("__doc__")), unbound ); - pProperty->m_szDocString = extract(descriptor.attr("__doc__")); pProperty->__set_name__(owner, name); return pProperty; diff --git a/src/core/modules/core/core_cache.h b/src/core/modules/core/core_cache.h index a599a1bb6..f57837a54 100644 --- a/src/core/modules/core/core_cache.h +++ b/src/core/modules/core/core_cache.h @@ -61,6 +61,7 @@ class CCachedProperty object set_deleter(object fget); str get_name(); + object get_owner(); void __set_name__(object owner, str name); static object __get__(object self, object instance, object owner); @@ -70,7 +71,7 @@ class CCachedProperty object __getitem__(str item); void __setitem__(str item, object value); - static CCachedProperty *wrap_descriptor(object descriptor, object owner, str name); + static CCachedProperty *wrap_descriptor(object descriptor, object owner, str name, bool unbound); private: object m_fget; @@ -78,6 +79,7 @@ class CCachedProperty object m_fdel; str m_name; + object m_owner; bool m_bUnbound; dict m_cache; diff --git a/src/core/modules/core/core_cache_wrap.cpp b/src/core/modules/core/core_cache_wrap.cpp index 7abf17541..9a6f49ec8 100644 --- a/src/core/modules/core/core_cache_wrap.cpp +++ b/src/core/modules/core/core_cache_wrap.cpp @@ -56,7 +56,7 @@ void export_cached_property(scope _cache) "CachedProperty", init( ( - arg("fget")=object(), arg("fset")=object(), arg("fdel")=object(), arg("doc")=object(), + arg("self"), arg("fget")=object(), arg("fset")=object(), arg("fdel")=object(), arg("doc")=object(), arg("unbound")=false, arg("args")=boost::python::tuple(), arg("kwargs")=dict() ), "Represents a property attribute that is only" @@ -83,10 +83,10 @@ void export_cached_property(scope _cache) " Deleter signature: self, *args, **kwargs\n" ":param str doc:\n" " Documentation string for this property.\n" - ":param bool unbound\n" + ":param bool unbound:\n" " Whether the cached objects should be independently maintained rather than bound to" " the instance they belong to. The cache will be slightly slower to lookup, but this can" - " be required in certain cases to prevent circular references/memory leak.\n" + " be required for instances that do not have a `__dict__` attribute.\n" ":param tuple args:\n" " Extra arguments passed to the getter, setter and deleter functions.\n" ":param dict kwargs:\n" @@ -111,7 +111,8 @@ void export_cached_property(scope _cache) " The function to register as getter function.\n" "\n" ":rtype:\n" - " function" + " function", + args("self", "fget") ); CachedProperty.add_property( @@ -134,7 +135,8 @@ void export_cached_property(scope _cache) " The function to register as setter function.\n" "\n" ":rtype:\n" - " function" + " function", + args("self", "fset") ); CachedProperty.add_property( @@ -157,7 +159,8 @@ void export_cached_property(scope _cache) " The function to register as deleter function.\n" "\n" ":rtype:\n" - " function" + " function", + args("self", "fdel") ); CachedProperty.add_property( @@ -190,6 +193,16 @@ void export_cached_property(scope _cache) " str" ); + CachedProperty.add_property( + "owner", + &CCachedProperty::get_owner, + "The owner class this property attribute was bound to.\n" + "\n" + ":rtype:\n" + " type" + ); + + CachedProperty.def_readwrite( "args", &CCachedProperty::m_args, @@ -212,6 +225,12 @@ void export_cached_property(scope _cache) "__set_name__", &CCachedProperty::__set_name__, "Called when this property is being bound to a class.\n" + "\n" + ":param class owner:\n" + " The class this property is being bound to.\n" + ":param str name:\n" + " The name this property is being bound as.", + args("self", "owner", "name") ); CachedProperty.def( @@ -219,8 +238,14 @@ void export_cached_property(scope _cache) &CCachedProperty::__get__, "Retrieves the value of this property.\n" "\n" + ":param object instance:\n" + " The instance for which this property is retrieved.\n" + ":param class owner:\n" + " The class for which this property is retrieved.\n" + "\n" ":rtype:\n" - " object" + " object", + ("self", "instance", arg("owner")=object()) ); CachedProperty.def( @@ -228,14 +253,23 @@ void export_cached_property(scope _cache) &CCachedProperty::__set__, "Assigns the value of this property.\n" "\n" + ":param object instance:\n" + " The instance this property is being assigned to.\n" + ":param object value:\n" + " The value assigned to this property.\n" ":rtype:\n" - " object" + " object", + args("self", "instance", "value") ); CachedProperty.def( "__delete__", &CCachedProperty::__delete__, - "Deletes this property and invalidates its cached value." + "Deletes this property and invalidates its cached value.\n" + "\n" + ":param object instance:\n" + " The instance for which this property if being deleted.", + args("self", "instance") ); CachedProperty.def( @@ -247,7 +281,8 @@ void export_cached_property(scope _cache) " The function to register as getter function.\n" "\n" ":rtype:\n" - " function" + " function", + args("self", "fget") ); CachedProperty.def( @@ -259,7 +294,8 @@ void export_cached_property(scope _cache) " The name of the keyword.\n" "\n" ":rtype:" - " object" + " object", + args("self", "item") ); CachedProperty.def( @@ -270,7 +306,8 @@ void export_cached_property(scope _cache) ":param str item:\n" " The name of the keyword.\n" ":param object value:\n" - " The value to assign to the given keyword." + " The value to assign to the given keyword.", + args("self", "item", "value") ); CachedProperty.def( @@ -292,7 +329,7 @@ void export_cached_property(scope _cache) " If the given descriptor doesn't have the required methods.\n" ":raises TypeError:\n" " If the getter, setter or deleter are not callable.", - ("descriptor", arg("owner")=object(), arg("name")=str()) + ("descriptor", arg("owner")=object(), arg("name")=str(), arg("unbound")=false) ) .staticmethod("wrap_descriptor"); diff --git a/src/core/utilities/wrap_macros.h b/src/core/utilities/wrap_macros.h index d70c420b3..9f9bfa055 100644 --- a/src/core/utilities/wrap_macros.h +++ b/src/core/utilities/wrap_macros.h @@ -164,7 +164,7 @@ template T cached_property(T cls, const char *szName) { cls.attr(szName) = transfer_ownership_to_python( - CCachedProperty::wrap_descriptor(cls.attr(szName), cls, szName) + CCachedProperty::wrap_descriptor(cls.attr(szName), cls, szName, false) ); return cls; }; From 8502e1bbb16faa0578b302b0efde95dbf1408ce8 Mon Sep 17 00:00:00 2001 From: Jonathan <30329245+CookStar@users.noreply.github.com> Date: Wed, 11 Dec 2019 18:04:21 +0900 Subject: [PATCH 24/59] Fixed Player.is_bot to be cached correctly. (#294) Fixed Player.is_bot to be cached correctly. --- addons/source-python/packages/source-python/players/_base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) mode change 100644 => 100755 addons/source-python/packages/source-python/players/_base.py diff --git a/addons/source-python/packages/source-python/players/_base.py b/addons/source-python/packages/source-python/players/_base.py old mode 100644 new mode 100755 index 54c78dc6c..3755ff632 --- a/addons/source-python/packages/source-python/players/_base.py +++ b/addons/source-python/packages/source-python/players/_base.py @@ -226,14 +226,14 @@ def _is_bot(self): :rtype: bool """ - return self._is_bot + return self.is_fake_client() or self.steamid == 'BOT' def is_bot(self): """Return whether the player is a bot. :rtype: bool """ - return self.is_fake_client() or self.steamid == 'BOT' + return self._is_bot def is_in_a_vehicle(self): """Return whether the player is in a vehicle. From debc63bf4a641476014b6105b6a333e608645410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Sat, 14 Dec 2019 00:44:56 -0500 Subject: [PATCH 25/59] Fixed a crash caused by Function's convention being freed twice when Source.Python is unloading. --- src/core/modules/memory/memory_function.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/modules/memory/memory_function.cpp b/src/core/modules/memory/memory_function.cpp index 85ed20a1e..897878ea3 100644 --- a/src/core/modules/memory/memory_function.cpp +++ b/src/core/modules/memory/memory_function.cpp @@ -381,6 +381,9 @@ void CFunction::AddHook(HookType_t eType, PyObject* pCallable) if (!pHook) { pHook = HookFunctionHelper((void *) m_ulAddr, m_pCallingConvention); + + // DynamicHooks will handle our convention from there, regardless if we allocated it or not. + m_bAllocatedCallingConvention = false; } // Add the hook handler. If it's already added, it won't be added twice From 02ea4e123f52bd69fafda534a834d1dfce05497d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Wed, 25 Dec 2019 17:59:38 -0500 Subject: [PATCH 26/59] Fixed the invalidation of the internal entity cache before all entity deletion listeners were called. Fixed a KeyError being silenced in EntityDictionary.on_automatically_removed overrides. Slightly improved the retrieval speed of the cached entity instances. --- .../packages/source-python/entities/_base.py | 22 +++++++++++++------ .../source-python/entities/dictionary.py | 2 +- src/core/sp_main.cpp | 4 ++++ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/_base.py b/addons/source-python/packages/source-python/entities/_base.py index de5ddb2a4..d547b46a7 100644 --- a/addons/source-python/packages/source-python/entities/_base.py +++ b/addons/source-python/packages/source-python/entities/_base.py @@ -101,9 +101,10 @@ def __call__(cls, index, caching=True): """ # Let's first lookup for a cached instance if caching: - obj = cls._cache.get(index, None) - if obj is not None: - return obj + try: + return cls._cache[index] + except KeyError: + pass # Nothing in cache, let's create a new instance obj = super().__call__(index) @@ -129,6 +130,17 @@ def cache(cls): """ return cls._cache + @staticmethod + def _invalidate_cache(base_entity): + """Invalidates the cache for the given entity.""" + try: + index = base_entity.index + except ValueError: + return + + for cls in _entity_classes: + cls.cache.pop(index, None) + class Entity(BaseEntity, metaclass=_EntityCaching): """Class used to interact directly with entities. @@ -1225,10 +1237,6 @@ def _on_entity_deleted(base_entity): except ValueError: return - # Cleanup the cache - for cls in _entity_classes: - cls.cache.pop(index, None) - with suppress(KeyError): # Loop through all delays... for delay in _entity_delays[index]: diff --git a/addons/source-python/packages/source-python/entities/dictionary.py b/addons/source-python/packages/source-python/entities/dictionary.py index 696040a02..0ace4cbf1 100644 --- a/addons/source-python/packages/source-python/entities/dictionary.py +++ b/addons/source-python/packages/source-python/entities/dictionary.py @@ -88,7 +88,7 @@ def _on_entity_deleted(self, base_entity): except ValueError: return - with suppress(KeyError): + if index in self: # Call the deletion callback for the index... self.on_automatically_removed(index) diff --git a/src/core/sp_main.cpp b/src/core/sp_main.cpp index ac2593dc6..18acdc3b2 100644 --- a/src/core/sp_main.cpp +++ b/src/core/sp_main.cpp @@ -631,6 +631,10 @@ void CSourcePython::OnEntitySpawned( CBaseEntity *pEntity ) void CSourcePython::OnEntityDeleted( CBaseEntity *pEntity ) { CALL_LISTENERS(OnEntityDeleted, ptr((CBaseEntityWrapper*) pEntity)); + + // Invalidate the internal entity cache once all callbacks have been called. + static object oCacheInvalidator = import("entities").attr("_base").attr("_EntityCaching").attr("_invalidate_cache"); + oCacheInvalidator(ptr((CBaseEntityWrapper*) pEntity)); } void CSourcePython::OnDataLoaded( MDLCacheDataType_t type, MDLHandle_t handle ) From 44f89f63dba3eac7bca69db73897e9e4a044d980 Mon Sep 17 00:00:00 2001 From: satoon101 Date: Sat, 4 Jan 2020 12:16:26 -0500 Subject: [PATCH 27/59] Added caching boolean argument for Player.from_userid. --- addons/source-python/packages/source-python/players/_base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/source-python/packages/source-python/players/_base.py b/addons/source-python/packages/source-python/players/_base.py index cf3b5b57c..739890357 100644 --- a/addons/source-python/packages/source-python/players/_base.py +++ b/addons/source-python/packages/source-python/players/_base.py @@ -92,14 +92,14 @@ def __init__(self, index, caching=True): object.__setattr__(self, '_playerinfo', None) @classmethod - def from_userid(cls, userid): + def from_userid(cls, userid, caching=True): """Create an instance from a userid. :param int userid: The userid. :rtype: Player """ - return cls(index_from_userid(userid)) + return cls(index_from_userid(userid), caching=caching) @property def net_info(self): From f90078c980b9306f49fd3b53ebe5766ca20218d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Wed, 8 Jan 2020 20:55:20 -0500 Subject: [PATCH 28/59] Disabled instance caching for Entity's subclasses that do not explicitly enable it (to retain backward compatibility for existing classes that were not implemented with caching in mind). Added caching keyword to Entity.from_inthandle. Fixed Player.from_userid not following the caching state of the current class unless explicitly specified. Added missing documentation. --- .../packages/source-python/entities/_base.py | 16 ++++++++++------ .../packages/source-python/players/_base.py | 4 +++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/_base.py b/addons/source-python/packages/source-python/entities/_base.py index 03520443a..8aa2b756d 100644 --- a/addons/source-python/packages/source-python/entities/_base.py +++ b/addons/source-python/packages/source-python/entities/_base.py @@ -89,11 +89,13 @@ def __init__(cls, classname, bases, attributes): # Set whether or not this class is caching its instances by default try: - cls._caching = signature( - cls.__init__ - ).parameters['caching'].default + cls._caching = bool( + signature( + vars(cls)['__init__'] + ).parameters['caching'].default + ) except KeyError: - cls._caching = True + cls._caching = bool(vars(cls).get('caching', False)) # Add the class to the registered classes _entity_classes.add(cls) @@ -300,14 +302,16 @@ def find_or_create(cls, classname): return entity @classmethod - def from_inthandle(cls, inthandle): + def from_inthandle(cls, inthandle, caching=None): """Create an entity instance from an inthandle. :param int inthandle: The inthandle. + :param bool caching: + Whether to lookup the cache for an existing instance or not. :rtype: Entity """ - return cls(index_from_inthandle(inthandle)) + return cls(index_from_inthandle(inthandle), caching=caching) @classmethod def _obj(cls, ptr): diff --git a/addons/source-python/packages/source-python/players/_base.py b/addons/source-python/packages/source-python/players/_base.py index 739890357..eda5700e2 100644 --- a/addons/source-python/packages/source-python/players/_base.py +++ b/addons/source-python/packages/source-python/players/_base.py @@ -92,11 +92,13 @@ def __init__(self, index, caching=True): object.__setattr__(self, '_playerinfo', None) @classmethod - def from_userid(cls, userid, caching=True): + def from_userid(cls, userid, caching=None): """Create an instance from a userid. :param int userid: The userid. + :param bool caching: + Whether to lookup the cache for an existing instance or not. :rtype: Player """ return cls(index_from_userid(userid), caching=caching) From 545ef0d37705b397c5596decbb029d0abab21f35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Wed, 8 Jan 2020 21:27:15 -0500 Subject: [PATCH 29/59] Improved performance of memory tools (approximately 2.5 times faster). --- src/core/modules/memory/memory_tools.h | 33 +++++++++++++++++++++----- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/core/modules/memory/memory_tools.h b/src/core/modules/memory/memory_tools.h index 97e26e75d..ffade609a 100644 --- a/src/core/modules/memory/memory_tools.h +++ b/src/core/modules/memory/memory_tools.h @@ -41,10 +41,17 @@ CPointer* ExtractPointer(object oPtr); // ============================================================================ inline object GetObjectPointer(object obj) { - if (!PyObject_HasAttrString(obj.ptr(), GET_PTR_NAME)) + object _ptr; + try + { + _ptr = obj.attr(GET_PTR_NAME); + } + catch (...) + { BOOST_RAISE_EXCEPTION(PyExc_ValueError, "Unable to retrieve a pointer of this object."); + } - return obj.attr(GET_PTR_NAME)(); + return _ptr(); } @@ -53,14 +60,21 @@ inline object GetObjectPointer(object obj) // ============================================================================ inline object MakeObject(object cls, object oPtr) { - if (!PyObject_HasAttrString(cls.ptr(), GET_OBJ_NAME)) + object _obj; + try + { + _obj = cls.attr(GET_OBJ_NAME); + } + catch (...) + { BOOST_RAISE_EXCEPTION(PyExc_ValueError, "Unable to make an object using this class."); + } CPointer* pPtr = ExtractPointer(oPtr); if (!pPtr->IsValid()) BOOST_RAISE_EXCEPTION(PyExc_ValueError, "Pointer is NULL."); - return cls.attr(GET_OBJ_NAME)(pPtr); + return _obj(pPtr); } @@ -69,10 +83,17 @@ inline object MakeObject(object cls, object oPtr) // ============================================================================ inline object GetSize(object cls) { - if (!PyObject_HasAttrString(cls.ptr(), GET_SIZE_NAME)) + object _size; + try + { + _size = cls.attr(GET_SIZE_NAME); + } + catch (...) + { BOOST_RAISE_EXCEPTION(PyExc_ValueError, "Unable to retrieve the size of this class."); + } - return cls.attr(GET_SIZE_NAME); + return _size; } From 5130d5c6f8aeeb1dedc7a4c5b647b1b24394229a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Thu, 9 Apr 2020 02:53:13 -0400 Subject: [PATCH 30/59] Fixed exceptions potentially being silenced when caching generators. --- src/core/modules/core/core_cache.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/modules/core/core_cache.cpp b/src/core/modules/core/core_cache.cpp index 897a45dac..1315e49b6 100644 --- a/src/core/modules/core/core_cache.cpp +++ b/src/core/modules/core/core_cache.cpp @@ -81,6 +81,9 @@ object CCachedProperty::_prepare_value(object value) } catch(...) { + if (!PyErr_ExceptionMatches(PyExc_StopIteration)) + throw_error_already_set(); + PyErr_Clear(); break; } From abba9e7bdbe8705997bc33abc76c4a3236e0be12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Thu, 9 Apr 2020 03:24:53 -0400 Subject: [PATCH 31/59] Added missing args and kwargs parameters to CachedProperty.wrap_descriptor. --- src/core/modules/core/core_cache.cpp | 5 +++-- src/core/modules/core/core_cache.h | 5 ++++- src/core/modules/core/core_cache_wrap.cpp | 9 ++++++++- src/core/utilities/wrap_macros.h | 2 +- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/core/modules/core/core_cache.cpp b/src/core/modules/core/core_cache.cpp index 1315e49b6..9c1c2721f 100644 --- a/src/core/modules/core/core_cache.cpp +++ b/src/core/modules/core/core_cache.cpp @@ -312,11 +312,12 @@ void CCachedProperty::__setitem__(str item, object value) CCachedProperty *CCachedProperty::wrap_descriptor( - object descriptor, object owner=object(), str name=str(), bool unbound=false) + object descriptor, object owner, str name, + bool unbound, boost::python::tuple args, dict kwargs) { CCachedProperty *pProperty = new CCachedProperty( descriptor.attr("__get__"), descriptor.attr("__set__"), descriptor.attr("__delete__"), - extract(descriptor.attr("__doc__")), unbound + extract(descriptor.attr("__doc__")), unbound, args, kwargs ); pProperty->__set_name__(owner, name); diff --git a/src/core/modules/core/core_cache.h b/src/core/modules/core/core_cache.h index f57837a54..5383dde10 100644 --- a/src/core/modules/core/core_cache.h +++ b/src/core/modules/core/core_cache.h @@ -71,7 +71,10 @@ class CCachedProperty object __getitem__(str item); void __setitem__(str item, object value); - static CCachedProperty *wrap_descriptor(object descriptor, object owner, str name, bool unbound); + static CCachedProperty *wrap_descriptor( + object descriptor, object owner=object(), str name=str(), + bool unbound=false, boost::python::tuple args=boost::python::tuple(), dict kwargs=dict() + ); private: object m_fget; diff --git a/src/core/modules/core/core_cache_wrap.cpp b/src/core/modules/core/core_cache_wrap.cpp index 9a6f49ec8..8cebf7924 100644 --- a/src/core/modules/core/core_cache_wrap.cpp +++ b/src/core/modules/core/core_cache_wrap.cpp @@ -324,12 +324,19 @@ void export_cached_property(scope _cache) " The class the wrapped property should be bound to.\n" ":param str name:\n" " The name of this property.\n" + ":param tuple args:\n" + " Extra arguments passed to the getter, setter and deleter functions.\n" + ":param dict kwargs:\n" + " Extra keyword arguments passed to the getter, setter and deleter functions.\n" "\n" ":raises AttributeError:\n" " If the given descriptor doesn't have the required methods.\n" ":raises TypeError:\n" " If the getter, setter or deleter are not callable.", - ("descriptor", arg("owner")=object(), arg("name")=str(), arg("unbound")=false) + ( + "descriptor", arg("owner")=object(), arg("name")=str(), + arg("unbound")=false, arg("args")=boost::python::tuple(), arg("kwargs")=dict() + ) ) .staticmethod("wrap_descriptor"); diff --git a/src/core/utilities/wrap_macros.h b/src/core/utilities/wrap_macros.h index 9f9bfa055..d70c420b3 100644 --- a/src/core/utilities/wrap_macros.h +++ b/src/core/utilities/wrap_macros.h @@ -164,7 +164,7 @@ template T cached_property(T cls, const char *szName) { cls.attr(szName) = transfer_ownership_to_python( - CCachedProperty::wrap_descriptor(cls.attr(szName), cls, szName, false) + CCachedProperty::wrap_descriptor(cls.attr(szName), cls, szName) ); return cls; }; From 2c309b525d464a3b5503a1dbc1d12d514431cd94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Sat, 2 May 2020 08:51:34 -0400 Subject: [PATCH 32/59] Moved an extraction to avoid doing it when not necessary. --- src/core/modules/core/core_cache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/modules/core/core_cache.cpp b/src/core/modules/core/core_cache.cpp index 9c1c2721f..c6064fb51 100644 --- a/src/core/modules/core/core_cache.cpp +++ b/src/core/modules/core/core_cache.cpp @@ -208,10 +208,10 @@ void CCachedProperty::__set_name__(object owner, str name) object CCachedProperty::__get__(object self, object instance, object owner=object()) { - CCachedProperty &pSelf = extract(self); if (instance.is_none()) return self; + CCachedProperty &pSelf = extract(self); object name = pSelf.get_name(); if (name.is_none()) BOOST_RAISE_EXCEPTION( From e9c5a82b865e1cffedeee7b0bd49d865608a0c41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Sat, 2 May 2020 09:05:09 -0400 Subject: [PATCH 33/59] Optimized Entity._property_edict methods. Removed Entity.__property methods. --- .../packages/source-python/entities/_base.py | 74 ++++--------------- 1 file changed, 16 insertions(+), 58 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/_base.py b/addons/source-python/packages/source-python/entities/_base.py index 15abb053b..86c0b28f5 100644 --- a/addons/source-python/packages/source-python/entities/_base.py +++ b/addons/source-python/packages/source-python/entities/_base.py @@ -39,8 +39,8 @@ from engines.trace import Ray from engines.trace import TraceFilterSimple # Entities -from _entities._entity import BaseEntity from entities import BaseEntityGenerator +from entities import Edict from entities import TakeDamageInfo from entities.classes import server_classes from entities.constants import DamageTypes @@ -59,6 +59,7 @@ # Mathlib from mathlib import NULL_VECTOR # Memory +from memory import get_object_pointer from memory import make_object from memory.helpers import MemberFunction # Players @@ -68,6 +69,14 @@ from studio.constants import INVALID_ATTACHMENT_INDEX +# ============================================================================= +# >> FORWARD IMPORTS +# ============================================================================= +# Source.Python Imports +# Entities +from _entities._entity import BaseEntity + + # ============================================================================= # >> GLOBAL VARIABLES # ============================================================================= @@ -529,7 +538,7 @@ def get_property_edict(self, name): Name of the property to retrieve. :rtype: Edict """ - return self._get_property(name, 'Edict') + return make_object(Edict, self.get_property_pointer(name)) def get_property_float(self, name): """Return the float property. @@ -687,29 +696,6 @@ def get_property_vector(self, name): except ValueError: return self.get_datamap_property_vector(name) - def _get_property(self, name, prop_type): - """Verify the type and return the property.""" - # Loop through all entity server classes - for server_class in self.server_classes: - - # Is the name a member of the current server class? - if name not in server_class.properties: - continue - - # Is the type the correct type? - if prop_type != server_class.properties[name].prop_type: - raise TypeError('Property "{0}" is of type {1} not {2}'.format( - name, server_class.properties[name].prop_type, prop_type)) - - # Return the property for the entity - return getattr( - make_object(server_class._properties, self.pointer), name) - - # Raise an error if the property name was not found - raise ValueError( - 'Property "{0}" not found for entity type "{1}"'.format( - name, self.classname)) - def set_property_bool(self, name, value): """Set the boolean property. @@ -744,7 +730,11 @@ def set_property_edict(self, name, value): :param Edict value: The value to set. """ - self._set_property(name, 'Edict', value) + if not isinstance(value, Edict): + raise TypeError( + f'"{value}" of type "{type(value)}" is not a valid Edict.' + ) + self.set_property_pointer(name, get_object_pointer(value)) def set_property_float(self, name, value): """Set the float property. @@ -915,38 +905,6 @@ def set_property_vector(self, name, value): except ValueError: self.set_datamap_property_vector(name, value) - def _set_property(self, name, prop_type, value): - """Verify the type and set the property.""" - # Loop through all entity server classes - for server_class in self.server_classes: - - # Is the name a member of the current server class? - if name not in server_class.properties: - continue - - # Is the type the correct type? - if prop_type != server_class.properties[name].prop_type: - raise TypeError('Property "{0}" is of type {1} not {2}'.format( - name, server_class.properties[name].prop_type, prop_type)) - - # Set the property for the entity - setattr(make_object( - server_class._properties, self.pointer), name, value) - - # Is the property networked? - if server_class.properties[name].networked: - - # Notify the change of state - self.edict.state_changed() - - # No need to go further - return - - # Raise an error if the property name was not found - raise ValueError( - 'Property "{0}" not found for entity type "{1}"'.format( - name, self.classname)) - def delay( self, delay, callback, args=(), kwargs=None, cancel_on_level_end=False): From fa1926bd987e0c7f0727a9ca8097f54e64dfb728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Mon, 4 May 2020 01:58:00 -0400 Subject: [PATCH 34/59] Fixed entity delays/repeats not being cancelled if they were registered from an OnEntityDeleted listener called after the internal callback. --- .../packages/source-python/entities/_base.py | 30 +++++-------------- src/core/sp_main.cpp | 7 +++-- 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/_base.py b/addons/source-python/packages/source-python/entities/_base.py index 86c0b28f5..4834da03a 100644 --- a/addons/source-python/packages/source-python/entities/_base.py +++ b/addons/source-python/packages/source-python/entities/_base.py @@ -51,8 +51,6 @@ # Filters from filters.weapons import WeaponClassIter # Listeners -from listeners import OnEntityDeleted -from listeners import on_entity_deleted_listener_manager from listeners.tick import Delay from listeners.tick import Repeat from listeners.tick import RepeatStatus @@ -168,17 +166,6 @@ def cache(cls): """ return cls._cache - @staticmethod - def _invalidate_cache(base_entity): - """Invalidates the cache for the given entity.""" - try: - index = base_entity.index - except ValueError: - return - - for cls in _entity_classes: - cls.cache.pop(index, None) - class Entity(BaseEntity, metaclass=_EntityCaching): """Class used to interact directly with entities. @@ -1245,7 +1232,8 @@ def set_parent(self, parent, attachment=INVALID_ATTACHMENT_INDEX): # ============================================================================= # >> LISTENERS # ============================================================================= -@OnEntityDeleted +# NOTE: This callback is called by sp_main.cpp after all registered entity +# deletion listeners have been called. def _on_entity_deleted(base_entity): """Called when an entity is removed. @@ -1258,22 +1246,20 @@ def _on_entity_deleted(base_entity): except ValueError: return - # Get the registered delays for this entity - delays = _entity_delays.pop(index, ()) - # Loop through all delays... - for delay in delays: + for delay in _entity_delays.pop(index, ()): # Cancel the delay... with suppress(ValueError): delay.cancel() - # Get the registered repeats for this entity - repeats = _entity_repeats.pop(index, ()) - # Loop through all repeats... - for repeat in repeats: + for repeat in _entity_repeats.pop(index, ()): # Stop the repeat if running if repeat.status is RepeatStatus.RUNNING: repeat.stop() + + # Invalidate the internal entity caches for this entity + for cls in _entity_classes: + cls.cache.pop(index, None) diff --git a/src/core/sp_main.cpp b/src/core/sp_main.cpp index 2ebbdeedf..25550e202 100644 --- a/src/core/sp_main.cpp +++ b/src/core/sp_main.cpp @@ -630,11 +630,12 @@ void CSourcePython::OnEntitySpawned( CBaseEntity *pEntity ) void CSourcePython::OnEntityDeleted( CBaseEntity *pEntity ) { - CALL_LISTENERS(OnEntityDeleted, ptr((CBaseEntityWrapper*) pEntity)); + object oEntity(ptr((CBaseEntityWrapper*) pEntity)); + CALL_LISTENERS(OnEntityDeleted, oEntity); // Invalidate the internal entity cache once all callbacks have been called. - static object oCacheInvalidator = import("entities").attr("_base").attr("_EntityCaching").attr("_invalidate_cache"); - oCacheInvalidator(ptr((CBaseEntityWrapper*) pEntity)); + static object _on_entity_deleted = import("entities").attr("_base").attr("_on_entity_deleted"); + _on_entity_deleted(oEntity); } void CSourcePython::OnDataLoaded( MDLCacheDataType_t type, MDLHandle_t handle ) From 3cedd95e0b489837717d6cc429047d1feb1d5fcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Mon, 4 May 2020 03:29:42 -0400 Subject: [PATCH 35/59] Player.language property is now only cached for games it was already cached. --- .../packages/source-python/players/_base.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/addons/source-python/packages/source-python/players/_base.py b/addons/source-python/packages/source-python/players/_base.py index 57c291a49..fd2561e64 100755 --- a/addons/source-python/packages/source-python/players/_base.py +++ b/addons/source-python/packages/source-python/players/_base.py @@ -274,8 +274,7 @@ def set_team(self, value): team = property(get_team, set_team) - @cached_property - def language(self): + def get_language(self): """Return the player's language. If the player is a bot, an empty string will be returned. @@ -284,6 +283,12 @@ def language(self): """ return get_client_language(self.index) + # Only cache the language property for games it is already cached + if GAME_NAME in ('csgo',): + language = cached_property(get_language, doc=get_language.__doc__) + else: + language = property(get_language, doc=get_language.__doc__) + def get_trace_ray(self, mask=ContentMasks.ALL, trace_filter=None): """Return the player's current trace data. From b8afdb6b14a395484c967d3c9d7088debad68817 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Mon, 4 May 2020 03:34:01 -0400 Subject: [PATCH 36/59] Optimized non-cached get_client_language by using the internal Player cache instead of PlayerInfo. --- .../packages/source-python/players/_language/base.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/addons/source-python/packages/source-python/players/_language/base.py b/addons/source-python/packages/source-python/players/_language/base.py index 701af488a..a7a726632 100644 --- a/addons/source-python/packages/source-python/players/_language/base.py +++ b/addons/source-python/packages/source-python/players/_language/base.py @@ -18,9 +18,8 @@ def get_client_language(index): :param int index: Index of the client. """ - from players.helpers import playerinfo_from_index - playerinfo = playerinfo_from_index(index) - if playerinfo.is_fake_client() or 'BOT' in playerinfo.steamid: + from players.entity import Player + if Player(index).is_bot(): return '' return engine_server.get_client_convar_value(index, 'cl_language') From 24380ffd075fe8a6b136651889090c84282c0f8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Mon, 4 May 2020 04:28:27 -0400 Subject: [PATCH 37/59] Optimized various python calls from c++. --- src/core/modules/listeners/listeners_manager.h | 5 ++--- src/core/modules/memory/memory_hooks.cpp | 5 ++--- src/core/sp_main.cpp | 6 ++---- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/core/modules/listeners/listeners_manager.h b/src/core/modules/listeners/listeners_manager.h index 31dfa8e9a..a1f95d00d 100644 --- a/src/core/modules/listeners/listeners_manager.h +++ b/src/core/modules/listeners/listeners_manager.h @@ -31,7 +31,6 @@ // Includes. //----------------------------------------------------------------------------- #include "utilities/wrap_macros.h" -#include "utilities/call_python.h" #include "utlvector.h" @@ -54,7 +53,7 @@ for(int i = 0; i < mngr->m_vecCallables.Count(); i++) \ { \ BEGIN_BOOST_PY() \ - CALL_PY_FUNC(mngr->m_vecCallables[i].ptr(), ##__VA_ARGS__); \ + mngr->m_vecCallables[i](##__VA_ARGS__); \ END_BOOST_PY_NORET() \ } @@ -66,7 +65,7 @@ for(int i = 0; i < mngr->m_vecCallables.Count(); i++) \ { \ BEGIN_BOOST_PY() \ - return_var = CALL_PY_FUNC(mngr->m_vecCallables[i].ptr(), ##__VA_ARGS__); \ + return_var = mngr->m_vecCallables[i](##__VA_ARGS__); \ action \ END_BOOST_PY_NORET() \ } diff --git a/src/core/modules/memory/memory_hooks.cpp b/src/core/modules/memory/memory_hooks.cpp index 2f17074f5..f1e23acfe 100644 --- a/src/core/modules/memory/memory_hooks.cpp +++ b/src/core/modules/memory/memory_hooks.cpp @@ -31,7 +31,6 @@ #include "memory_utilities.h" #include "memory_pointer.h" #include "utilities/wrap_macros.h" -#include "utilities/call_python.h" #include "utilities/sp_util.h" #include "boost/python.hpp" @@ -123,9 +122,9 @@ bool SP_HookHandler(HookType_t eHookType, CHook* pHook) BEGIN_BOOST_PY() object pyretval; if (eHookType == HOOKTYPE_PRE) - pyretval = CALL_PY_FUNC((*it).ptr(), stackdata); + pyretval = (*it)(stackdata); else - pyretval = CALL_PY_FUNC((*it).ptr(), stackdata, retval); + pyretval = (*it)(stackdata, retval); if (!pyretval.is_none()) { diff --git a/src/core/sp_main.cpp b/src/core/sp_main.cpp index 25550e202..669af5047 100644 --- a/src/core/sp_main.cpp +++ b/src/core/sp_main.cpp @@ -196,8 +196,7 @@ SpewRetval_t SP_SpewOutput( SpewType_t spewType, const tchar *pMsg ) for(int i = 0; i < GetOnServerOutputListenerManager()->m_vecCallables.Count(); i++) { BEGIN_BOOST_PY() - object return_value = CALL_PY_FUNC( - GetOnServerOutputListenerManager()->m_vecCallables[i].ptr(), + object return_value = GetOnServerOutputListenerManager()->m_vecCallables[i]( (MessageSeverity) spewType, pMsg); @@ -224,8 +223,7 @@ class SPLoggingListener: public ILoggingListener for(int i = 0; i < GetOnServerOutputListenerManager()->m_vecCallables.Count(); i++) { BEGIN_BOOST_PY() - object return_value = CALL_PY_FUNC( - GetOnServerOutputListenerManager()->m_vecCallables[i].ptr(), + object return_value = GetOnServerOutputListenerManager()->m_vecCallables[i]( (MessageSeverity) pContext->m_Severity, pMessage); From ea77ce408cd9f528638e84577ec353af292d0818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Mon, 4 May 2020 05:43:17 -0400 Subject: [PATCH 38/59] Optimized pointer extraction. --- src/core/modules/memory/memory_utilities.h | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/core/modules/memory/memory_utilities.h b/src/core/modules/memory/memory_utilities.h index a83b962b6..2f3867afa 100644 --- a/src/core/modules/memory/memory_utilities.h +++ b/src/core/modules/memory/memory_utilities.h @@ -57,6 +57,13 @@ // Memory #include "memory_function_info.h" #include "memory_pointer.h" +#include "memory_tools.h" + + +// ============================================================================ +// >> FORWARD DECLARATIONS +// ============================================================================ +object GetObjectPointer(object obj); // ============================================================================ @@ -64,8 +71,14 @@ // ============================================================================ inline CPointer* ExtractPointer(object oPtr) { - if(PyObject_HasAttrString(oPtr.ptr(), GET_PTR_NAME)) - oPtr = oPtr.attr(GET_PTR_NAME)(); + try + { + oPtr = GetObjectPointer(oPtr); + } + catch (...) + { + PyErr_Clear(); + } CPointer* pPtr = extract(oPtr); return pPtr; From 0c4248e0aef3e8af83aac6ff40daf9cdbd8c5a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Mon, 4 May 2020 10:33:14 -0400 Subject: [PATCH 39/59] Fixed memory leaks caused by Sound/StreamSound instances never unloaded when instantiated from Entity.emit_sound. --- addons/source-python/packages/source-python/engines/sound.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/source-python/packages/source-python/engines/sound.py b/addons/source-python/packages/source-python/engines/sound.py index e0d1aa1e7..00a955183 100644 --- a/addons/source-python/packages/source-python/engines/sound.py +++ b/addons/source-python/packages/source-python/engines/sound.py @@ -22,7 +22,7 @@ # Source.Python Imports # Core -from core import AutoUnload +from core import WeakAutoUnload # Engines from engines import engines_logger # Entities @@ -100,7 +100,7 @@ class Attenuation(float, Enum): # ============================================================================= # >> CLASSES # ============================================================================= -class _BaseSound(AutoUnload): +class _BaseSound(WeakAutoUnload): """Base class for sound classes.""" # Set the base _downloads attribute to know whether From f3e8ce56dc51db3c8672c0b27e3a28a58114c12c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Mon, 4 May 2020 10:53:06 -0400 Subject: [PATCH 40/59] Fixed memory leaks into engine_sound.emit_sound caused by the sounds not being emitted through the reliable channel. --- src/core/modules/engines/blade/engines.h | 6 ++++-- src/core/modules/engines/bms/engines.h | 6 ++++-- src/core/modules/engines/csgo/engines.h | 6 ++++-- src/core/modules/engines/gmod/engines.h | 6 ++++-- src/core/modules/engines/l4d2/engines.h | 6 ++++-- src/core/modules/engines/orangebox/engines.h | 6 ++++-- 6 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/core/modules/engines/blade/engines.h b/src/core/modules/engines/blade/engines.h index 599c853c0..6a62ec611 100644 --- a/src/core/modules/engines/blade/engines.h +++ b/src/core/modules/engines/blade/engines.h @@ -31,6 +31,7 @@ // Includes. //----------------------------------------------------------------------------- #include "engine/IEngineSound.h" +#include "modules/filters/filters_recipients.h" //----------------------------------------------------------------------------- @@ -57,7 +58,7 @@ class GameIVEngineServerExt class IEngineSoundExt { public: - static void EmitSound(IEngineSound* pEngineSound, IRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample, + static void EmitSound(IEngineSound* pEngineSound, MRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample, float flVolume, float flAttenuation, int iFlags, int iPitch, const Vector *pOrigin, const Vector *pDirection, tuple origins, bool bUpdatePositions, float soundtime, int speakerentity) { @@ -71,7 +72,8 @@ class IEngineSoundExt vecOrigins.AddToTail(extract(origins[i])); } } - + + filter.m_bReliable = true; pEngineSound->EmitSound(filter, iEntIndex, iChannel, pSample, -1, pSample, flVolume, flAttenuation, 0, iFlags, iPitch, pOrigin, pDirection, pUtlVecOrigins, bUpdatePositions, soundtime, speakerentity); } diff --git a/src/core/modules/engines/bms/engines.h b/src/core/modules/engines/bms/engines.h index c76682bb5..73054d7bd 100644 --- a/src/core/modules/engines/bms/engines.h +++ b/src/core/modules/engines/bms/engines.h @@ -31,6 +31,7 @@ // Includes. //----------------------------------------------------------------------------- #include "engine/IEngineSound.h" +#include "modules/filters/filters_recipients.h" //----------------------------------------------------------------------------- @@ -57,7 +58,7 @@ class GameIVEngineServerExt class IEngineSoundExt { public: - static void EmitSound(IEngineSound* pEngineSound, IRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample, + static void EmitSound(IEngineSound* pEngineSound, MRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample, float flVolume, float flAttenuation, int iFlags, int iPitch, const Vector *pOrigin, const Vector *pDirection, tuple origins, bool bUpdatePositions, float soundtime, int speakerentity) { @@ -71,7 +72,8 @@ class IEngineSoundExt vecOrigins.AddToTail(extract(origins[i])); } } - + + filter.m_bReliable = true; pEngineSound->EmitSound(filter, iEntIndex, iChannel, pSample, flVolume, flAttenuation, iFlags, iPitch, 0, pOrigin, pDirection, pUtlVecOrigins, bUpdatePositions, soundtime, speakerentity); } diff --git a/src/core/modules/engines/csgo/engines.h b/src/core/modules/engines/csgo/engines.h index 64a646421..44a39b52d 100644 --- a/src/core/modules/engines/csgo/engines.h +++ b/src/core/modules/engines/csgo/engines.h @@ -31,6 +31,7 @@ // Includes. //----------------------------------------------------------------------------- #include "engine/IEngineSound.h" +#include "modules/filters/filters_recipients.h" //----------------------------------------------------------------------------- @@ -57,7 +58,7 @@ class GameIVEngineServerExt class IEngineSoundExt { public: - static void EmitSound(IEngineSound* pEngineSound, IRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample, + static void EmitSound(IEngineSound* pEngineSound, MRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample, float flVolume, float flAttenuation, int iFlags, int iPitch, const Vector *pOrigin, const Vector *pDirection, tuple origins, bool bUpdatePositions, float soundtime, int speakerentity) { @@ -71,7 +72,8 @@ class IEngineSoundExt vecOrigins.AddToTail(extract(origins[i])); } } - + + filter.m_bReliable = true; pEngineSound->EmitSound(filter, iEntIndex, iChannel, pSample, -1, pSample, flVolume, flAttenuation, 0, iFlags, iPitch, pOrigin, pDirection, pUtlVecOrigins, bUpdatePositions, soundtime, speakerentity); } diff --git a/src/core/modules/engines/gmod/engines.h b/src/core/modules/engines/gmod/engines.h index 4e09493f0..536c8fb3d 100644 --- a/src/core/modules/engines/gmod/engines.h +++ b/src/core/modules/engines/gmod/engines.h @@ -31,6 +31,7 @@ // Includes. //----------------------------------------------------------------------------- #include "engine/IEngineSound.h" +#include "modules/filters/filters_recipients.h" //----------------------------------------------------------------------------- @@ -57,7 +58,7 @@ class GameIVEngineServerExt class IEngineSoundExt { public: - static void EmitSound(IEngineSound* pEngineSound, IRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample, + static void EmitSound(IEngineSound* pEngineSound, MRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample, float flVolume, float flAttenuation, int iFlags, int iPitch, const Vector *pOrigin, const Vector *pDirection, tuple origins, bool bUpdatePositions, float soundtime, int speakerentity) { @@ -71,7 +72,8 @@ class IEngineSoundExt vecOrigins.AddToTail(extract(origins[i])); } } - + + filter.m_bReliable = true; pEngineSound->EmitSound(filter, iEntIndex, iChannel, pSample, flVolume, flAttenuation, iFlags, iPitch, 0, pOrigin, pDirection, pUtlVecOrigins, bUpdatePositions, soundtime, speakerentity); } diff --git a/src/core/modules/engines/l4d2/engines.h b/src/core/modules/engines/l4d2/engines.h index f7407ad28..4bc904a80 100644 --- a/src/core/modules/engines/l4d2/engines.h +++ b/src/core/modules/engines/l4d2/engines.h @@ -31,6 +31,7 @@ // Includes. //----------------------------------------------------------------------------- #include "engine/IEngineSound.h" +#include "modules/filters/filters_recipients.h" //----------------------------------------------------------------------------- @@ -57,7 +58,7 @@ class GameIVEngineServerExt class IEngineSoundExt { public: - static void EmitSound(IEngineSound* pEngineSound, IRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample, + static void EmitSound(IEngineSound* pEngineSound, MRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample, float flVolume, float flAttenuation, int iFlags, int iPitch, const Vector *pOrigin, const Vector *pDirection, tuple origins, bool bUpdatePositions, float soundtime, int speakerentity) { @@ -71,7 +72,8 @@ class IEngineSoundExt vecOrigins.AddToTail(extract(origins[i])); } } - + + filter.m_bReliable = true; pEngineSound->EmitSound(filter, iEntIndex, iChannel, pSample, flVolume, flAttenuation, iFlags, iPitch, pOrigin, pDirection, pUtlVecOrigins, bUpdatePositions, soundtime, speakerentity); } diff --git a/src/core/modules/engines/orangebox/engines.h b/src/core/modules/engines/orangebox/engines.h index 088ba1663..12b990054 100644 --- a/src/core/modules/engines/orangebox/engines.h +++ b/src/core/modules/engines/orangebox/engines.h @@ -32,6 +32,7 @@ //----------------------------------------------------------------------------- #include "engine/IEngineSound.h" #include "eiface.h" +#include "modules/filters/filters_recipients.h" //----------------------------------------------------------------------------- @@ -63,7 +64,7 @@ class GameIVEngineServerExt class IEngineSoundExt { public: - static void EmitSound(IEngineSound* pEngineSound, IRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample, + static void EmitSound(IEngineSound* pEngineSound, MRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample, float flVolume, float flAttenuation, int iFlags, int iPitch, const Vector *pOrigin, const Vector *pDirection, tuple origins, bool bUpdatePositions, float soundtime, int speakerentity) { @@ -77,7 +78,8 @@ class IEngineSoundExt vecOrigins.AddToTail(extract(origins[i])); } } - + + filter.m_bReliable = true; pEngineSound->EmitSound(filter, iEntIndex, iChannel, pSample, flVolume, flAttenuation, iFlags, iPitch, 0, pOrigin, pDirection, pUtlVecOrigins, bUpdatePositions, soundtime, speakerentity); } From a179e8cc109b0f5af8d3e55450f470329321f763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Tue, 5 May 2020 16:47:39 -0400 Subject: [PATCH 41/59] Improved performance of Entity.is_in_solid's default behaviour (approx. 19 times faster). --- .../packages/source-python/entities/_base.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/_base.py b/addons/source-python/packages/source-python/entities/_base.py index 4834da03a..7c38d98d6 100644 --- a/addons/source-python/packages/source-python/entities/_base.py +++ b/addons/source-python/packages/source-python/entities/_base.py @@ -43,6 +43,7 @@ from entities import Edict from entities import TakeDamageInfo from entities.classes import server_classes +from entities.constants import WORLD_ENTITY_INDEX from entities.constants import DamageTypes from entities.constants import RenderMode from entities.helpers import index_from_inthandle @@ -1064,7 +1065,7 @@ def emit_sound( sound.play(*recipients) def is_in_solid( - self, mask=ContentMasks.ALL, generator=BaseEntityGenerator): + self, mask=ContentMasks.ALL, generator=None): """Return whether or not the entity is in solid. :param ContentMasks mask: @@ -1081,8 +1082,16 @@ def is_in_solid( trace = GameTrace() # Do the trace - engine_trace.trace_ray(ray, mask, TraceFilterSimple( - generator()), trace) + if generator is None: + + # No need to trace against anything but the world if we are going + # to filter out everything regardless. + engine_trace.clip_ray_to_entity( + ray, mask, BaseEntity(WORLD_ENTITY_INDEX), trace + ) + else: + engine_trace.trace_ray(ray, mask, TraceFilterSimple( + generator()), trace) # Return whether or not the trace did hit return trace.did_hit() From a87f1213a5ff46973322e952d69aa354f1cad379 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Tue, 5 May 2020 17:52:41 -0400 Subject: [PATCH 42/59] Moved Entity._property_ methods to BaseEntity (approx. ~3 times faster). Added BaseEntity.__property_edict. Added BaseEntity.get_property_double. Added BaseEntity.get_property_long. Added BaseEntity.get_property_long_long. Added BaseEntity.get_property_string_array. Added BaseEntity.get_property_ulong. Added BaseEntity.get_property_ulong_long. Added BaseEntity.set_property_double. Added BaseEntity.set_property_long. Added BaseEntity.set_property_long_long. Added BaseEntity.set_property_string_array. Added BaseEntity.set_property_ulong. Added BaseEntity.set_property_ulong_long. --- src/core/modules/entities/entities_entity.h | 69 +++++ .../modules/entities/entities_entity_wrap.cpp | 266 ++++++++++++++++++ 2 files changed, 335 insertions(+) diff --git a/src/core/modules/entities/entities_entity.h b/src/core/modules/entities/entities_entity.h index 742ecf1e8..4f84b82fe 100644 --- a/src/core/modules/entities/entities_entity.h +++ b/src/core/modules/entities/entities_entity.h @@ -206,6 +206,75 @@ class CBaseEntityWrapper: public IServerEntity GetEdict()->StateChanged(); } + // Generic property getter/setter methods + template + T GetProperty(const char *name) + { + try + { + return GetNetworkProperty(name); + } + catch (...) + { + if (!PyErr_ExceptionMatches(PyExc_ValueError)) + throw_error_already_set(); + + PyErr_Clear(); + return GetDatamapProperty(name); + } + } + + const char* GetPropertyStringArray(const char* name) + { + try + { + return GetNetworkPropertyStringArray(name); + } + catch (...) + { + if (!PyErr_ExceptionMatches(PyExc_ValueError)) + throw_error_already_set(); + + PyErr_Clear(); + return GetDatamapPropertyStringArray(name); + } + } + + template + void SetProperty(const char *name, T value) + { + try + { + // Let's first lookup a networked property so we update its state, etc. + SetNetworkProperty(name, value); + } + catch (...) + { + if (!PyErr_ExceptionMatches(PyExc_ValueError)) + throw_error_already_set(); + + PyErr_Clear(); + SetDatamapProperty(name, value); + } + } + + void SetPropertyStringArray(const char* name, const char* value) + { + try + { + // Let's first lookup a networked property so we update its state, etc. + SetNetworkPropertyStringArray(name, value); + } + catch (...) + { + if (!PyErr_ExceptionMatches(PyExc_ValueError)) + throw_error_already_set(); + + PyErr_Clear(); + SetDatamapPropertyStringArray(name, value); + } + } + // KeyValue methods void GetKeyValueStringRaw(const char* szName, char* szOut, int iLength); str GetKeyValueString(const char* szName); diff --git a/src/core/modules/entities/entities_entity_wrap.cpp b/src/core/modules/entities/entities_entity_wrap.cpp index ab2851b9a..c78d091ee 100644 --- a/src/core/modules/entities/entities_entity_wrap.cpp +++ b/src/core/modules/entities/entities_entity_wrap.cpp @@ -742,6 +742,13 @@ void export_base_entity(scope _entity) ":rtype: Quaternion" ); + BaseEntity.def("get_datamap_property_edict", + &CBaseEntityWrapper::GetDatamapProperty, + return_by_value_policy(), + "Return the value of the given data map field name.\n\n" + ":rtype: Edict" + ); + // Datamap setter methods BaseEntity.def("set_datamap_property_bool", &CBaseEntityWrapper::SetDatamapProperty, @@ -843,6 +850,11 @@ void export_base_entity(scope _entity) "Set the value of the given data map field name." ); + BaseEntity.def("set_datamap_property_edict", + &CBaseEntityWrapper::SetDatamapProperty, + "Set the value of the given data map field name." + ); + // Network property getters BaseEntity.def("get_network_property_bool", &CBaseEntityWrapper::GetNetworkProperty, @@ -965,6 +977,13 @@ void export_base_entity(scope _entity) ":rtype: Quaternion" ); + BaseEntity.def("get_network_property_edict", + &CBaseEntityWrapper::GetNetworkProperty, + return_by_value_policy(), + "Return the value of the given server class field name.\n\n" + ":rtype: Edict" + ); + // Network property setters BaseEntity.def("set_network_property_bool", &CBaseEntityWrapper::SetNetworkProperty, @@ -1067,6 +1086,253 @@ void export_base_entity(scope _entity) "Set the value of the given server class field name." ); + BaseEntity.def("set_network_property_edict", + &CBaseEntityWrapper::SetNetworkProperty, + "Set the value of the given server class field name." + ); + + // Generic property getters + BaseEntity.def("get_property_bool", + &CBaseEntityWrapper::GetProperty, + "Return the value of the given field name.\n\n" + ":rtype: bool" + ); + + BaseEntity.def("get_property_char", + &CBaseEntityWrapper::GetProperty, + "Return the value of the given field name.\n\n" + ":rtype: str" + ); + + BaseEntity.def("get_property_uchar", + &CBaseEntityWrapper::GetProperty, + "Return the value of the given field name.\n\n" + ":rtype: int" + ); + + BaseEntity.def("get_property_short", + &CBaseEntityWrapper::GetProperty, + "Return the value of the given field name.\n\n" + ":rtype: int" + ); + + BaseEntity.def("get_property_ushort", + &CBaseEntityWrapper::GetProperty, + "Return the value of the given field name.\n\n" + ":rtype: int" + ); + + BaseEntity.def("get_property_int", + &CBaseEntityWrapper::GetProperty, + "Return the value of the given field name.\n\n" + ":rtype: int" + ); + + BaseEntity.def("get_property_uint", + &CBaseEntityWrapper::GetProperty, + "Return the value of the given field name.\n\n" + ":rtype: int" + ); + + BaseEntity.def("get_property_long", + &CBaseEntityWrapper::GetProperty, + "Return the value of the given field name.\n\n" + ":rtype: int" + ); + + BaseEntity.def("get_property_ulong", + &CBaseEntityWrapper::GetProperty, + "Return the value of the given field name.\n\n" + ":rtype: int" + ); + + BaseEntity.def("get_property_long_long", + &CBaseEntityWrapper::GetProperty, + "Return the value of the given field name.\n\n" + ":rtype: int" + ); + + BaseEntity.def("get_property_ulong_long", + &CBaseEntityWrapper::GetProperty, + "Return the value of the given field name.\n\n" + ":rtype: int" + ); + + BaseEntity.def("get_property_float", + &CBaseEntityWrapper::GetProperty, + "Return the value of the given field name.\n\n" + ":rtype: float" + ); + + BaseEntity.def("get_property_double", + &CBaseEntityWrapper::GetProperty, + "Return the value of the given field name.\n\n" + ":rtype: float" + ); + + BaseEntity.def("get_property_string_pointer", + &CBaseEntityWrapper::GetProperty, + "Return the value of the given field name.\n\n" + ":rtype: str" + ); + + BaseEntity.def("get_property_string_array", + &CBaseEntityWrapper::GetPropertyStringArray, + "Return the value of the given field name.\n\n" + ":rtype: str" + ); + + // Backward compatibility + BaseEntity.attr("get_property_string") = BaseEntity.attr("get_property_string_array"); + + BaseEntity.def("get_property_pointer", + &CBaseEntityWrapper::GetProperty, + return_by_value_policy(), + "Return the value of the given field name.\n\n" + ":rtype: Pointer" + ); + + BaseEntity.def("get_property_vector", + &CBaseEntityWrapper::GetProperty, + "Return the value of the given field name.\n\n" + ":rtype: Vector" + ); + + BaseEntity.def("get_property_color", + &CBaseEntityWrapper::GetProperty, + "Return the value of the given field name.\n\n" + ":rtype: Color" + ); + + BaseEntity.def("get_property_interval", + &CBaseEntityWrapper::GetProperty, + "Return the value of the given field name.\n\n" + ":rtype: Interval" + ); + + BaseEntity.def("get_property_quaternion", + &CBaseEntityWrapper::GetProperty, + "Return the value of the given field name.\n\n" + ":rtype: Quaternion" + ); + + BaseEntity.def("get_property_edict", + &CBaseEntityWrapper::GetProperty, + return_by_value_policy(), + "Return the value of the given field name.\n\n" + ":rtype: Edict" + ); + + // Generic property setters + BaseEntity.def("set_property_bool", + &CBaseEntityWrapper::SetProperty, + "Set the value of the given field name." + ); + + BaseEntity.def("set_property_char", + &CBaseEntityWrapper::SetProperty, + "Set the value of the given field name." + ); + + BaseEntity.def("set_property_uchar", + &CBaseEntityWrapper::SetProperty, + "Set the value of the given field name." + ); + + BaseEntity.def("set_property_short", + &CBaseEntityWrapper::SetProperty, + "Set the value of the given field name." + ); + + BaseEntity.def("set_property_ushort", + &CBaseEntityWrapper::SetProperty, + "Set the value of the given field name." + ); + + BaseEntity.def("set_property_int", + &CBaseEntityWrapper::SetProperty, + "Set the value of the given field name." + ); + + BaseEntity.def("set_property_uint", + &CBaseEntityWrapper::SetProperty, + "Set the value of the given field name." + ); + + BaseEntity.def("set_property_long", + &CBaseEntityWrapper::SetProperty, + "Set the value of the given field name." + ); + + BaseEntity.def("set_property_ulong", + &CBaseEntityWrapper::SetProperty, + "Set the value of the given field name." + ); + + BaseEntity.def("set_property_long_long", + &CBaseEntityWrapper::SetProperty, + "Set the value of the given field name." + ); + + BaseEntity.def("set_property_ulong_long", + &CBaseEntityWrapper::SetProperty, + "Set the value of the given field name." + ); + + BaseEntity.def("set_property_float", + &CBaseEntityWrapper::SetProperty, + "Set the value of the given field name." + ); + + BaseEntity.def("set_property_double", + &CBaseEntityWrapper::SetProperty, + "Set the value of the given field name." + ); + + BaseEntity.def("set_property_string_pointer", + &CBaseEntityWrapper::SetProperty, + "Set the value of the given field name." + ); + + BaseEntity.def("set_property_string_array", + &CBaseEntityWrapper::SetPropertyStringArray, + "Set the value of the given field name." + ); + + // Backward compatibility + BaseEntity.attr("set_property_string") = BaseEntity.attr("set_property_string_array"); + + BaseEntity.def("set_property_pointer", + &CBaseEntityWrapper::SetProperty, + return_by_value_policy(), + "Set the value of the given field name." + ); + + BaseEntity.def("set_property_vector", + &CBaseEntityWrapper::SetProperty, + "Set the value of the given field name." + ); + + BaseEntity.def("set_property_color", + &CBaseEntityWrapper::SetProperty, + "Set the value of the given field name." + ); + + BaseEntity.def("set_property_interval", + &CBaseEntityWrapper::SetProperty, + "Set the value of the given field name." + ); + + BaseEntity.def("set_property_quaternion", + &CBaseEntityWrapper::SetProperty, + "Set the value of the given field name." + ); + + BaseEntity.def("set_property_edict", + &CBaseEntityWrapper::SetProperty, + "Set the value of the given field name." + ); + // Add memory tools... BaseEntity ADD_MEM_TOOLS_WRAPPER(CBaseEntityWrapper, CBaseEntity); From ff4ec278d965a721d28bea4a7821e4a27f5dc0f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Tue, 5 May 2020 17:54:04 -0400 Subject: [PATCH 43/59] Removed Entity._property_ methods. --- .../packages/source-python/entities/_base.py | 398 ------------------ 1 file changed, 398 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/_base.py b/addons/source-python/packages/source-python/entities/_base.py index 7c38d98d6..0aff10cc0 100644 --- a/addons/source-python/packages/source-python/entities/_base.py +++ b/addons/source-python/packages/source-python/entities/_base.py @@ -495,404 +495,6 @@ def _set_parent(self, parent): .. seealso:: :meth:`get_parent` and :meth:`set_parent`""") - def get_property_bool(self, name): - """Return the boolean property. - - :param str name: - Name of the property to retrieve. - :rtype: bool - """ - try: - return self.get_network_property_bool(name) - except ValueError: - return self.get_datamap_property_bool(name) - - def get_property_color(self, name): - """Return the Color property. - - :param str name: - Name of the property to retrieve. - :rtype: Color - """ - try: - return self.get_network_property_color(name) - except ValueError: - return self.get_datamap_property_color(name) - - def get_property_edict(self, name): - """Return the Edict property. - - :param str name: - Name of the property to retrieve. - :rtype: Edict - """ - return make_object(Edict, self.get_property_pointer(name)) - - def get_property_float(self, name): - """Return the float property. - - :param str name: - Name of the property to retrieve. - :rtype: float - """ - try: - return self.get_network_property_float(name) - except ValueError: - return self.get_datamap_property_float(name) - - def get_property_int(self, name): - """Return the integer property. - - :param str name: - Name of the property to retrieve. - :rtype: int - """ - try: - return self.get_network_property_int(name) - except ValueError: - return self.get_datamap_property_int(name) - - def get_property_interval(self, name): - """Return the Interval property. - - :param str name: - Name of the property to retrieve. - :rtype: Interval - """ - try: - return self.get_network_property_interval(name) - except ValueError: - return self.get_datamap_property_interval(name) - - def get_property_pointer(self, name): - """Return the pointer property. - - :param str name: - Name of the property to retrieve. - :rtype: Pointer - """ - try: - return self.get_network_property_pointer(name) - except ValueError: - return self.get_datamap_property_pointer(name) - - def get_property_quaternion(self, name): - """Return the Quaternion property. - - :param str name: - Name of the property to retrieve. - :rtype: Quaternion - """ - try: - return self.get_network_property_quaternion(name) - except ValueError: - return self.get_datamap_property_quaternion(name) - - def get_property_short(self, name): - """Return the short property. - - :param str name: - Name of the property to retrieve. - :rtype: int - """ - try: - return self.get_network_property_short(name) - except ValueError: - return self.get_datamap_property_short(name) - - def get_property_ushort(self, name): - """Return the ushort property. - - :param str name: - Name of the property to retrieve. - :rtype: int - """ - try: - return self.get_network_property_ushort(name) - except ValueError: - return self.get_datamap_property_ushort(name) - - def get_property_string(self, name): - """Return the string property. - - :param str name: - Name of the property to retrieve. - :rtype: str - """ - try: - return self.get_network_property_string_array(name) - except ValueError: - return self.get_datamap_property_string_array(name) - - def get_property_string_pointer(self, name): - """Return the string property. - - :param str name: - Name of the property to retrieve. - :rtype: str - """ - try: - return self.get_network_property_pointer(name) - except ValueError: - return self.get_datamap_property_pointer(name) - - def get_property_char(self, name): - """Return the char property. - - :param str name: - Name of the property to retrieve. - :rtype: str - """ - try: - return self.get_network_property_char(name) - except ValueError: - return self.get_datamap_property_char(name) - - def get_property_uchar(self, name): - """Return the uchar property. - - :param str name: - Name of the property to retrieve. - :rtype: int - """ - try: - return self.get_network_property_uchar(name) - except ValueError: - return self.get_datamap_property_uchar(name) - - def get_property_uint(self, name): - """Return the uint property. - - :param str name: - Name of the property to retrieve. - :rtype: int - """ - try: - return self.get_network_property_uint(name) - except ValueError: - return self.get_datamap_property_uint(name) - - def get_property_vector(self, name): - """Return the Vector property. - - :param str name: - Name of the property to retrieve. - :rtype: Vector - """ - try: - return self.get_network_property_vector(name) - except ValueError: - return self.get_datamap_property_vector(name) - - def set_property_bool(self, name, value): - """Set the boolean property. - - :param str name: - Name of the property to set. - :param bool value: - The value to set. - """ - try: - self.set_network_property_bool(name, value) - except ValueError: - self.set_datamap_property_bool(name, value) - - def set_property_color(self, name, value): - """Set the Color property. - - :param str name: - Name of the property to set. - :param Color value: - The value to set. - """ - try: - self.set_network_property_color(name, value) - except ValueError: - self.set_datamap_property_color(name, value) - - def set_property_edict(self, name, value): - """Set the Edict property. - - :param str name: - Name of the property to set. - :param Edict value: - The value to set. - """ - if not isinstance(value, Edict): - raise TypeError( - f'"{value}" of type "{type(value)}" is not a valid Edict.' - ) - self.set_property_pointer(name, get_object_pointer(value)) - - def set_property_float(self, name, value): - """Set the float property. - - :param str name: - Name of the property to set. - :param float value: - The value to set. - """ - try: - self.set_network_property_float(name, value) - except ValueError: - self.set_datamap_property_float(name, value) - - def set_property_int(self, name, value): - """Set the integer property. - - :param str name: - Name of the property to set. - :param int value: - The value to set. - """ - try: - self.set_network_property_int(name, value) - except ValueError: - self.set_datamap_property_int(name, value) - - def set_property_interval(self, name, value): - """Set the Interval property. - - :param str name: - Name of the property to set. - :param Interval value: - The value to set. - """ - try: - self.set_network_property_interval(name, value) - except ValueError: - self.set_datamap_property_interval(name, value) - - def set_property_pointer(self, name, value): - """Set the pointer property. - - :param str name: - Name of the property to set. - :param Pointer value: - The value to set. - """ - try: - self.set_network_property_pointer(name, value) - except ValueError: - self.set_datamap_property_pointer(name, value) - - def set_property_quaternion(self, name, value): - """Set the Quaternion property. - - :param str name: - Name of the property to set. - :param Quaternion value: - The value to set. - """ - try: - self.set_network_property_quaternion(name, value) - except ValueError: - self.set_datamap_property_quaternion(name, value) - - def set_property_short(self, name, value): - """Set the short property. - - :param str name: - Name of the property to set. - :param int value: - The value to set. - """ - try: - self.set_network_property_short(name, value) - except ValueError: - self.set_datamap_property_short(name, value) - - def set_property_ushort(self, name, value): - """Set the ushort property. - - :param str name: - Name of the property to set. - :param int value: - The value to set. - """ - try: - self.set_network_property_ushort(name, value) - except ValueError: - self.set_datamap_property_ushort(name, value) - - def set_property_string(self, name, value): - """Set the string property. - - :param str name: - Name of the property to set. - :param str value: - The value to set. - """ - try: - self.set_network_property_string_array(name, value) - except ValueError: - self.set_datamap_property_string_array(name, value) - - def set_property_string_pointer(self, name, value): - """Set the string property. - - :param str name: - Name of the property to set. - :param str value: - The value to set. - """ - try: - self.set_network_property_string_pointer(name, value) - except ValueError: - self.set_datamap_property_string_pointer(name, value) - - def set_property_char(self, name, value): - """Set the char property. - - :param str name: - Name of the property to set. - :param str value: - The value to set. - """ - try: - self.set_network_property_char(name, value) - except ValueError: - self.set_datamap_property_char(name, value) - - def set_property_uchar(self, name, value): - """Set the uchar property. - - :param str name: - Name of the property to set. - :param int value: - The value to set. - """ - try: - self.set_network_property_uchar(name, value) - except ValueError: - self.set_datamap_property_uchar(name, value) - - def set_property_uint(self, name, value): - """Set the uint property. - - :param str name: - Name of the property to set. - :param int value: - The value to set. - """ - try: - self.set_network_property_uint(name, value) - except ValueError: - self.set_datamap_property_uint(name, value) - - def set_property_vector(self, name, value): - """Set the Vector property. - - :param str name: - Name of the property to set. - :param Vector value: - The value to set. - """ - try: - self.set_network_property_vector(name, value) - except ValueError: - self.set_datamap_property_vector(name, value) - def delay( self, delay, callback, args=(), kwargs=None, cancel_on_level_end=False): From 4269a14a33cdffbe96e951c9298aa0c55af75c7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Wed, 6 May 2020 14:05:20 -0400 Subject: [PATCH 44/59] Improved performance of TraceFilterSimple.should_hit_entity (approx. 42 times faster). --- .../packages/source-python/engines/trace.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/addons/source-python/packages/source-python/engines/trace.py b/addons/source-python/packages/source-python/engines/trace.py index fc07a252a..05d1d2c1d 100644 --- a/addons/source-python/packages/source-python/engines/trace.py +++ b/addons/source-python/packages/source-python/engines/trace.py @@ -204,7 +204,7 @@ def __init__(self, ignore=(), trace_type=TraceType.EVERYTHING): """ super().__init__() self.trace_type = trace_type - self.ignore = tuple(map(inthandle_from_baseentity, ignore)) + self.ignore = set(map(inthandle_from_baseentity, ignore)) def should_hit_entity(self, entity, mask): """Called when a trace is about to hit an entity. @@ -215,14 +215,7 @@ def should_hit_entity(self, entity, mask): The mask that was used to intialize the trace. :rtype: bool """ - entity_inthandle = entity.basehandle.to_int() - - # Check for entities to ignore - for ignore_inthandle in self.ignore: - if ignore_inthandle == entity_inthandle: - return False - - return True + return entity.basehandle.to_int() in self.ignore def get_trace_type(self): """Return the trace type. From 6fcf7b1d9af0c6e28558b4196d06292f51108ebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Wed, 6 May 2020 14:13:31 -0400 Subject: [PATCH 45/59] Oops, fixed a condition. --- addons/source-python/packages/source-python/engines/trace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/source-python/packages/source-python/engines/trace.py b/addons/source-python/packages/source-python/engines/trace.py index 05d1d2c1d..450763437 100644 --- a/addons/source-python/packages/source-python/engines/trace.py +++ b/addons/source-python/packages/source-python/engines/trace.py @@ -215,7 +215,7 @@ def should_hit_entity(self, entity, mask): The mask that was used to intialize the trace. :rtype: bool """ - return entity.basehandle.to_int() in self.ignore + return entity.basehandle.to_int() not in self.ignore def get_trace_type(self): """Return the trace type. From c613ac8da5c51a004667467556c634abe0dc48af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Wed, 6 May 2020 20:45:21 -0400 Subject: [PATCH 46/59] Added example to CachedProperty's docstring. Fixed a typo into CachedProperty.__delete__'s docstring. --- src/core/modules/core/core_cache_wrap.cpp | 42 ++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/core/modules/core/core_cache_wrap.cpp b/src/core/modules/core/core_cache_wrap.cpp index 8cebf7924..4910bcd4f 100644 --- a/src/core/modules/core/core_cache_wrap.cpp +++ b/src/core/modules/core/core_cache_wrap.cpp @@ -98,6 +98,46 @@ void export_cached_property(scope _cache) ".. warning ::\n" " If a cached object hold a strong reference of the instance it belongs to," " this will result in a circular reference preventing their garbage collection." + "\n" + "Example:\n" + "\n" + ".. code:: python\n" + "\n" + " from random import randint\n" + " from core.cache import cached_property\n" + "\n" + " class Test:\n" + " @cached_property(kwargs=dict(range=(0, 1000)))\n" + " def test(self, range):\n" + " return randint(*range)\n" + "\n" + " @test.setter\n" + " def set_test(self, value, range):\n" + " return int(value / 2)\n" + "\n" + " test = Test()\n" + "\n" + " # Compute and cache the value for the first time\n" + " i = test.test\n" + "\n" + " # The first computed value was cached, so it should always be the same\n" + " assert i is test.test\n" + " assert i is test.test\n" + " assert i is test.test\n" + " assert i is test.test\n" + "\n" + " # Deleting the property is invalidating the cache\n" + " del test.test\n" + " assert i is not test.test\n" + "\n" + " # The cache will be updated to 5, because our setter computes value / 2\n" + " test.test = 10\n" + " assert test.test is 5\n" + "\n" + " # The new value should be 1, because we updated our userdata\n" + " Test.test['range'] = (1, 1)\n" + " del test.test\n" + " assert test.test is 1\n" ) ); @@ -268,7 +308,7 @@ void export_cached_property(scope _cache) "Deletes this property and invalidates its cached value.\n" "\n" ":param object instance:\n" - " The instance for which this property if being deleted.", + " The instance for which this property is being deleted.", args("self", "instance") ); From 705a0885646edb7f5396c6f620c2beb5f0bc8936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Wed, 6 May 2020 23:13:46 -0400 Subject: [PATCH 47/59] Improved performance of Entity.create, find and find_or_create by moving them to BaseEntity directly. --- .../packages/source-python/entities/_base.py | 60 ++----------------- src/core/modules/entities/entities_entity.cpp | 44 ++++++++++++++ src/core/modules/entities/entities_entity.h | 3 + .../modules/entities/entities_entity_wrap.cpp | 27 +++++---- 4 files changed, 66 insertions(+), 68 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/_base.py b/addons/source-python/packages/source-python/entities/_base.py index 0aff10cc0..7b5fef7f1 100644 --- a/addons/source-python/packages/source-python/entities/_base.py +++ b/addons/source-python/packages/source-python/entities/_base.py @@ -282,58 +282,6 @@ def __dir__(self): # Return a sorted list of attributes return sorted(attributes) - @classmethod - def create(cls, classname): - """Create a new networked entity with the given classname. - - :param str classname: - Classname of the entity to create. - :raise ValueError: - Raised if the given classname is not a networked entity. - """ - entity = BaseEntity.create(classname) - if entity.is_networked(): - return cls(entity.index) - - entity.remove() - raise ValueError('"{}" is not a networked entity.'.format(classname)) - - @classmethod - def find(cls, classname): - """Try to find an entity with the given classname. - - If not entity has been found, None will be returned. - - :param str classname: - The classname of the entity. - :return: - Return the found entity. - :rtype: Entity - """ - entity = BaseEntity.find(classname) - if entity is not None and entity.is_networked(): - return cls(entity.index) - - return None - - @classmethod - def find_or_create(cls, classname): - """Try to find an entity with the given classname. - - If no entity has been found, it will be created. - - :param str classname: - The classname of the entity. - :return: - Return the found or created entity. - :rtype: Entity - """ - entity = cls.find(classname) - if entity is None: - entity = cls.create(classname) - - return entity - @classmethod def from_inthandle(cls, inthandle, caching=None): """Create an entity instance from an inthandle. @@ -346,10 +294,10 @@ def from_inthandle(cls, inthandle, caching=None): """ return cls(index_from_inthandle(inthandle), caching=caching) - @classmethod - def _obj(cls, ptr): - """Return an entity instance of the given pointer.""" - return cls(index_from_pointer(ptr)) + # @classmethod + # def _obj(cls, ptr): + # """Return an entity instance of the given pointer.""" + # return cls(index_from_pointer(ptr)) def is_networked(self): """Return True if the entity is networked. diff --git a/src/core/modules/entities/entities_entity.cpp b/src/core/modules/entities/entities_entity.cpp index 41b843fba..29b2ea414 100644 --- a/src/core/modules/entities/entities_entity.cpp +++ b/src/core/modules/entities/entities_entity.cpp @@ -73,6 +73,28 @@ CBaseEntity* CBaseEntityWrapper::create(const char* name) return pEntity->GetBaseEntity(); } +object CBaseEntityWrapper::create(object cls, const char *name) +{ + object entity = object(); + CBaseEntityWrapper *pEntity = (CBaseEntityWrapper *)create(name); + try + { + entity = cls(pEntity->GetIndex()); + } + catch (...) + { + pEntity->remove(); + + const char *classname = extract(cls.attr("__qualname__")); + BOOST_RAISE_EXCEPTION( + PyExc_ValueError, + "Unable to make a '%s' instance out of this '%s' entity.", + classname, name + ) + } + return entity; +} + CBaseEntity* CBaseEntityWrapper::find(const char* name) { CBaseEntity* pEntity = (CBaseEntity *) servertools->FirstEntity(); @@ -86,6 +108,19 @@ CBaseEntity* CBaseEntityWrapper::find(const char* name) return NULL; } +object CBaseEntityWrapper::find(object cls, const char *name) +{ + try + { + return cls(((CBaseEntityWrapper *)find(name))->GetIndex()); + } + catch (...) + { + PyErr_Clear(); + } + return object(); +} + CBaseEntity* CBaseEntityWrapper::find_or_create(const char* name) { CBaseEntity* entity = find(name); @@ -95,6 +130,15 @@ CBaseEntity* CBaseEntityWrapper::find_or_create(const char* name) return entity; } +object CBaseEntityWrapper::find_or_create(object cls, const char *name) +{ + object entity = find(cls, name); + if (entity.is_none()) + return create(cls, name); + + return entity; +} + CBaseEntityOutputWrapper* CBaseEntityWrapper::get_output(const char* name) { // TODO: Caching? diff --git a/src/core/modules/entities/entities_entity.h b/src/core/modules/entities/entities_entity.h index 4f84b82fe..1f138d00d 100644 --- a/src/core/modules/entities/entities_entity.h +++ b/src/core/modules/entities/entities_entity.h @@ -96,8 +96,11 @@ class CBaseEntityWrapper: public IServerEntity static boost::shared_ptr __init__(unsigned int uiEntityIndex); static boost::shared_ptr wrap(CBaseEntity* pEntity); static CBaseEntity* create(const char* name); + static object create(object cls, const char* name); static CBaseEntity* find(const char* name); + static object find(object cls, const char* name); static CBaseEntity* find_or_create(const char* name); + static object find_or_create(object cls, const char* name); CBaseEntityOutputWrapper* get_output(const char* name); static IEntityFactory* get_factory(const char* name); diff --git a/src/core/modules/entities/entities_entity_wrap.cpp b/src/core/modules/entities/entities_entity_wrap.cpp index c78d091ee..d2b0f6b27 100644 --- a/src/core/modules/entities/entities_entity_wrap.cpp +++ b/src/core/modules/entities/entities_entity_wrap.cpp @@ -72,26 +72,29 @@ void export_base_entity(scope _entity) "Return True if both entities are the same." ); - BaseEntity.def("create", - &CBaseEntityWrapper::create, - return_by_value_policy(), + CLASSMETHOD( + BaseEntity, + "create", + GET_FUNCTION(object, CBaseEntityWrapper::create, object, const char *), "Create an entity by its class name.\n\n" ":rtype: BaseEntity" - ).staticmethod("create"); + ); - BaseEntity.def("find", - &CBaseEntityWrapper::find, - return_by_value_policy(), + CLASSMETHOD( + BaseEntity, + "find", + GET_FUNCTION(object, CBaseEntityWrapper::find, object, const char *), "Return the first entity that has a matching class name.\n\n" ":rtype: BaseEntity" - ).staticmethod("find"); + ); - BaseEntity.def("find_or_create", - &CBaseEntityWrapper::find_or_create, - return_by_value_policy(), + CLASSMETHOD( + BaseEntity, + "find_or_create", + GET_FUNCTION(object, CBaseEntityWrapper::find_or_create, object, const char *), "Try to find an entity that has a matching class name. If no entity has been found, it will be created.\n\n" ":rtype: BaseEntity" - ).staticmethod("find_or_create"); + ); // Others BaseEntity.def("is_player", From 6fe0038908771dbadb85f8366a8486ff1c9ae3f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Wed, 6 May 2020 23:54:42 -0400 Subject: [PATCH 48/59] Fixed non-networked entities creation/lookup. --- .../packages/source-python/entities/_base.py | 8 ++-- src/core/modules/entities/entities_entity.cpp | 46 +++++++++++++------ 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/_base.py b/addons/source-python/packages/source-python/entities/_base.py index 7b5fef7f1..c96be9b6f 100644 --- a/addons/source-python/packages/source-python/entities/_base.py +++ b/addons/source-python/packages/source-python/entities/_base.py @@ -294,10 +294,10 @@ def from_inthandle(cls, inthandle, caching=None): """ return cls(index_from_inthandle(inthandle), caching=caching) - # @classmethod - # def _obj(cls, ptr): - # """Return an entity instance of the given pointer.""" - # return cls(index_from_pointer(ptr)) + @classmethod + def _obj(cls, ptr): + """Return an entity instance of the given pointer.""" + return cls(index_from_pointer(ptr)) def is_networked(self): """Return True if the entity is networked. diff --git a/src/core/modules/entities/entities_entity.cpp b/src/core/modules/entities/entities_entity.cpp index 29b2ea414..2fa03c147 100644 --- a/src/core/modules/entities/entities_entity.cpp +++ b/src/core/modules/entities/entities_entity.cpp @@ -83,14 +83,21 @@ object CBaseEntityWrapper::create(object cls, const char *name) } catch (...) { - pEntity->remove(); - - const char *classname = extract(cls.attr("__qualname__")); - BOOST_RAISE_EXCEPTION( - PyExc_ValueError, - "Unable to make a '%s' instance out of this '%s' entity.", - classname, name - ) + try + { + entity = MakeObject(cls, object(pEntity->GetPointer())); + } + catch (...) + { + pEntity->remove(); + + const char *classname = extract(cls.attr("__qualname__")); + BOOST_RAISE_EXCEPTION( + PyExc_ValueError, + "Unable to make a '%s' instance out of this '%s' entity.", + classname, name + ) + } } return entity; } @@ -110,13 +117,24 @@ CBaseEntity* CBaseEntityWrapper::find(const char* name) object CBaseEntityWrapper::find(object cls, const char *name) { - try + CBaseEntityWrapper *pEntity = (CBaseEntityWrapper *)find(name); + if (pEntity) { - return cls(((CBaseEntityWrapper *)find(name))->GetIndex()); - } - catch (...) - { - PyErr_Clear(); + try + { + return cls(pEntity->GetIndex()); + } + catch (...) + { + try + { + return MakeObject(cls, object(pEntity->GetPointer())); + } + catch (...) + { + PyErr_Clear(); + } + } } return object(); } From ec6ac9038dc444ff3a2f8cb1e2f8569cfde9f848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Thu, 7 May 2020 14:08:28 -0400 Subject: [PATCH 49/59] Added an overload to memory.make_object to avoid redundant extraction. --- src/core/modules/entities/entities_entity.cpp | 4 ++-- src/core/modules/memory/memory_tools.h | 8 ++++++-- src/core/modules/memory/memory_wrap.cpp | 11 ++++++++++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/core/modules/entities/entities_entity.cpp b/src/core/modules/entities/entities_entity.cpp index 2fa03c147..9c350f9c9 100644 --- a/src/core/modules/entities/entities_entity.cpp +++ b/src/core/modules/entities/entities_entity.cpp @@ -85,7 +85,7 @@ object CBaseEntityWrapper::create(object cls, const char *name) { try { - entity = MakeObject(cls, object(pEntity->GetPointer())); + entity = MakeObject(cls, &pEntity->GetPointer()); } catch (...) { @@ -128,7 +128,7 @@ object CBaseEntityWrapper::find(object cls, const char *name) { try { - return MakeObject(cls, object(pEntity->GetPointer())); + return MakeObject(cls, &pEntity->GetPointer()); } catch (...) { diff --git a/src/core/modules/memory/memory_tools.h b/src/core/modules/memory/memory_tools.h index ffade609a..1edc832fc 100644 --- a/src/core/modules/memory/memory_tools.h +++ b/src/core/modules/memory/memory_tools.h @@ -58,7 +58,7 @@ inline object GetObjectPointer(object obj) // ============================================================================ // >> MakeObject // ============================================================================ -inline object MakeObject(object cls, object oPtr) +inline object MakeObject(object cls, CPointer *pPtr) { object _obj; try @@ -70,13 +70,17 @@ inline object MakeObject(object cls, object oPtr) BOOST_RAISE_EXCEPTION(PyExc_ValueError, "Unable to make an object using this class."); } - CPointer* pPtr = ExtractPointer(oPtr); if (!pPtr->IsValid()) BOOST_RAISE_EXCEPTION(PyExc_ValueError, "Pointer is NULL."); return _obj(pPtr); } +inline object MakeObject(object cls, object oPtr) +{ + return MakeObject(cls, ExtractPointer(oPtr)); +} + // ============================================================================ // >> GetSize diff --git a/src/core/modules/memory/memory_wrap.cpp b/src/core/modules/memory/memory_wrap.cpp index 05f7715e0..2dad81f88 100644 --- a/src/core/modules/memory/memory_wrap.cpp +++ b/src/core/modules/memory/memory_wrap.cpp @@ -935,7 +935,16 @@ void export_functions(scope _memory) ); def("make_object", - &MakeObject, + GET_FUNCTION(object, MakeObject, object, object), + (arg("cls"), arg("ptr")), + "Wrap a pointer using an exposed class.\n" + "\n" + ":param cls: The class that should be used to wrap the pointer.\n" + ":param Pointer ptr: The pointer that should be wrapped." + ); + + def("make_object", + GET_FUNCTION(object, MakeObject, object, CPointer *), (arg("cls"), arg("ptr")), "Wrap a pointer using an exposed class.\n" "\n" From bb86856413180e9b63de4612a408aaa89eff20e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Thu, 7 May 2020 14:27:09 -0400 Subject: [PATCH 50/59] Added BaseEntity.is_marked_for_deletion (approx. 13 times faster than the manual check). --- .../source-python/packages/source-python/entities/_base.py | 3 +-- .../packages/source-python/entities/dictionary.py | 3 +-- src/core/modules/entities/entities_entity.cpp | 5 +++++ src/core/modules/entities/entities_entity.h | 1 + src/core/modules/entities/entities_entity_wrap.cpp | 6 ++++++ 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/_base.py b/addons/source-python/packages/source-python/entities/_base.py index c96be9b6f..784e5c285 100644 --- a/addons/source-python/packages/source-python/entities/_base.py +++ b/addons/source-python/packages/source-python/entities/_base.py @@ -22,7 +22,6 @@ from core.cache import cached_property # Entities from entities.constants import INVALID_ENTITY_INDEX -from entities.constants import EntityFlags # Engines from engines.precache import Model from engines.sound import Attenuation @@ -145,7 +144,7 @@ def __call__(cls, index, caching=None): # This is required, because if someone request an entity instance # after we invalidated our cache but before the engine processed # the deletion we would now have an invalid instance in the cache. - if not obj.entity_flags & EntityFlags.KILLME: + if not obj.is_marked_for_deletion(): cls._cache[index] = obj # We are done, let's return the instance diff --git a/addons/source-python/packages/source-python/entities/dictionary.py b/addons/source-python/packages/source-python/entities/dictionary.py index 0ace4cbf1..922e361dd 100644 --- a/addons/source-python/packages/source-python/entities/dictionary.py +++ b/addons/source-python/packages/source-python/entities/dictionary.py @@ -13,7 +13,6 @@ # Core from core import AutoUnload # Entities -from entities.constants import EntityFlags from entities.entity import Entity from entities.helpers import index_from_inthandle # Listeners @@ -57,7 +56,7 @@ def __missing__(self, index): # This is required, because if someone request an entity instance # after we invalidated our cache but before the engine processed # the deletion we would now have an invalid instance in the cache. - if not instance.entity_flags & EntityFlags.KILLME: + if not instance.is_marked_for_deletion(): self[index] = instance return instance diff --git a/src/core/modules/entities/entities_entity.cpp b/src/core/modules/entities/entities_entity.cpp index 9c350f9c9..5606f07c6 100644 --- a/src/core/modules/entities/entities_entity.cpp +++ b/src/core/modules/entities/entities_entity.cpp @@ -232,6 +232,11 @@ void CBaseEntityWrapper::remove() (pEntity->*pInputKillFunc)(data); } +bool CBaseEntityWrapper::is_marked_for_deletion() +{ + return GetEntityFlags() & FL_KILLME; +} + int CBaseEntityWrapper::get_size() { return get_factory()->GetEntitySize(); diff --git a/src/core/modules/entities/entities_entity.h b/src/core/modules/entities/entities_entity.h index 1f138d00d..f2f2e23b1 100644 --- a/src/core/modules/entities/entities_entity.h +++ b/src/core/modules/entities/entities_entity.h @@ -106,6 +106,7 @@ class CBaseEntityWrapper: public IServerEntity static IEntityFactory* get_factory(const char* name); IEntityFactory* get_factory(); void remove(); + bool is_marked_for_deletion(); int get_size(); void spawn(); diff --git a/src/core/modules/entities/entities_entity_wrap.cpp b/src/core/modules/entities/entities_entity_wrap.cpp index d2b0f6b27..4810fe80f 100644 --- a/src/core/modules/entities/entities_entity_wrap.cpp +++ b/src/core/modules/entities/entities_entity_wrap.cpp @@ -459,6 +459,12 @@ void export_base_entity(scope _entity) "Remove the entity." ); + BaseEntity.def("is_marked_for_deletion", + &CBaseEntityWrapper::is_marked_for_deletion, + "Returns whether the entity is marked for deletion.\n\n" + ":rtype: bool" + ); + BaseEntity.def("spawn", &CBaseEntityWrapper::spawn, "Spawn the entity." From 1b67129df4f531eb64e3624f6607d1d4f531526b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Fri, 8 May 2020 16:09:57 -0400 Subject: [PATCH 51/59] Fixed BaseEntity.is_marked_for_deletion from checking the wrong flag. --- src/core/modules/entities/entities_entity.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/modules/entities/entities_entity.cpp b/src/core/modules/entities/entities_entity.cpp index a45bf3035..46db5144b 100644 --- a/src/core/modules/entities/entities_entity.cpp +++ b/src/core/modules/entities/entities_entity.cpp @@ -234,7 +234,7 @@ void CBaseEntityWrapper::remove() bool CBaseEntityWrapper::is_marked_for_deletion() { - return GetEntityFlags() & FL_KILLME; + return GetEntityFlags() & EFL_KILLME; } int CBaseEntityWrapper::get_size() From 3e7b711de3b9a38e608317cc397c50078f767df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Sun, 10 May 2020 02:51:38 -0400 Subject: [PATCH 52/59] Moved InputFunction to c++ (~21x faster). --- .../source-python/entities/classes.py | 10 +-- .../source-python/entities/datamaps.py | 53 +--------------- .../modules/entities/entities_datamaps.cpp | 63 +++++++++++++++++++ src/core/modules/entities/entities_datamaps.h | 15 +++++ .../entities/entities_datamaps_wrap.cpp | 24 +++++++ 5 files changed, 108 insertions(+), 57 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/classes.py b/addons/source-python/packages/source-python/entities/classes.py index 1283e5fa4..3ce665fc7 100644 --- a/addons/source-python/packages/source-python/entities/classes.py +++ b/addons/source-python/packages/source-python/entities/classes.py @@ -21,6 +21,7 @@ from core import GameConfigObj from core import PLATFORM # Entities +from _entities._entity import BaseEntity from entities import ServerClassGenerator from entities.datamaps import _supported_input_types from entities.datamaps import EntityProperty @@ -35,6 +36,7 @@ from memory import Convention from memory import DataType from memory import get_object_pointer +from memory import make_object from memory.helpers import Type from memory.manager import CustomType from memory.manager import TypeManager @@ -459,7 +461,7 @@ def _add_input(self, instance, name, desc, contents): return # Add the input to the inputs dictionary - instance.inputs[name] = self.input(name, desc) + instance.inputs[name] = self.input(desc) # Is the input a named input? if name in contents: @@ -519,10 +521,8 @@ def fset(pointer, value): return property(fget, fset) @staticmethod - def input(name, desc): + def input(desc): """Input type DataMap object.""" - argument_type = desc.type - def fget(pointer): """Retrieve the InputFunction instance.""" func = desc.function @@ -539,7 +539,7 @@ def fget(pointer): (DataType.POINTER, DataType.POINTER), DataType.VOID) - return InputFunction(name, argument_type, function, pointer) + return InputFunction(desc, make_object(BaseEntity, pointer)) return property(fget) diff --git a/addons/source-python/packages/source-python/entities/datamaps.py b/addons/source-python/packages/source-python/entities/datamaps.py index cabe8bc76..df94a5e0d 100644 --- a/addons/source-python/packages/source-python/entities/datamaps.py +++ b/addons/source-python/packages/source-python/entities/datamaps.py @@ -38,6 +38,7 @@ from _entities._datamaps import FTYPEDESC_VIEW_OWN_TEAM from _entities._datamaps import FTYPEDESC_VIEW_NEVER from _entities._datamaps import InputData +from _entities._datamaps import InputFunction from _entities._datamaps import Interval from _entities._datamaps import TypeDescription from _entities._datamaps import Variant @@ -120,55 +121,3 @@ def prop_type(self): def networked(self): """Return whether the property is networked.""" return self._networked - - -class InputFunction(Function): - """Class used to create and call an Input type function.""" - - def __init__(self, name, argument_type, function, this): - """Instantiate the function instance and store the base attributes.""" - self._function = function - super().__init__( - function.address, function.convention, function.arguments, - function.return_type - ) - - self._name = name - self._argument_type = argument_type - self._this = this - - def __call__(self, value=None, caller=None, activator=None): - """Call the stored function with the values given.""" - # Is the type not VOID but no value was given? - if value is None and self._argument_type != FieldType.VOID: - raise ValueError( - 'Must provide a value for {0}'.format(self._name)) - - # Is the type VOID but a value was given? - if value is not None and self._argument_type == FieldType.VOID: - raise ValueError( - '{0} is type Void. Do not pass a value.'.format( - self._name)) - - # Get an InputData instance - inputdata = InputData() - - # Does the caller need set? - if caller is not None: - inputdata.caller = caller - - # Does the activator need set? - if activator is not None: - inputdata.activator = activator - - # Does the function require a value? - if self._argument_type != FieldType.VOID: - - # Set the value - getattr( - inputdata.value, - 'set_{0}'.format(_supported_input_types[ - self._argument_type]))(value) - - # Call the function - super().__call__(self._this, inputdata) diff --git a/src/core/modules/entities/entities_datamaps.cpp b/src/core/modules/entities/entities_datamaps.cpp index db030d3c6..d4835d0e9 100644 --- a/src/core/modules/entities/entities_datamaps.cpp +++ b/src/core/modules/entities/entities_datamaps.cpp @@ -206,6 +206,69 @@ Vector VariantExt::get_vector(variant_t *pVariant) } +// ============================================================================ +// >> CInputFunction +// ============================================================================ +CInputFunction::CInputFunction(typedescription_t pTypeDesc, CBaseEntity *pBaseEntity) + :CFunction( + (unsigned long)TypeDescriptionSharedExt::get_function(pTypeDesc), + object(CONV_THISCALL), + make_tuple(DATA_TYPE_POINTER, DATA_TYPE_POINTER), + object(DATA_TYPE_VOID) + ) +{ + m_pTypeDesc = pTypeDesc; + m_pBaseEntity = pBaseEntity; +} + + +void CInputFunction::__call__(object value, CBaseEntity *pActivator, CBaseEntity *pCaller) +{ + inputdata_t pInputData; + + if (m_pTypeDesc.fieldType != FIELD_VOID) + { + if (value.is_none()) + BOOST_RAISE_EXCEPTION( + PyExc_ValueError, + "Must provide a value for \"%s\".", m_pTypeDesc.externalName + ); + + switch (m_pTypeDesc.fieldType) + { + case FIELD_BOOLEAN: + pInputData.value.SetBool(extract(value)); + break; + case FIELD_COLOR32: + VariantExt::set_color(&pInputData.value, extract(value)); + break; + case FIELD_FLOAT: + pInputData.value.SetFloat(extract(value)); + break; + case FIELD_INTEGER: + pInputData.value.SetInt(extract(value)); + break; + case FIELD_STRING: + VariantExt::set_string(&pInputData.value, extract(value)); + break; + case FIELD_CLASSPTR: + pInputData.value.SetEntity(extract(value)); + break; + default: + BOOST_RAISE_EXCEPTION( + PyExc_TypeError, + "Unsupported type for input \"%s\".", m_pTypeDesc.externalName + ); + } + } + + pInputData.pActivator = pActivator; + pInputData.pCaller = pCaller; + + (m_pBaseEntity->*m_pTypeDesc.inputFunc)(pInputData); +} + + // ============================================================================ // >> InputDataExt // ============================================================================ diff --git a/src/core/modules/entities/entities_datamaps.h b/src/core/modules/entities/entities_datamaps.h index 19da4fead..ecde8962e 100644 --- a/src/core/modules/entities/entities_datamaps.h +++ b/src/core/modules/entities/entities_datamaps.h @@ -83,6 +83,21 @@ class VariantExt }; +//----------------------------------------------------------------------------- +// CInputFunction. +//----------------------------------------------------------------------------- +class CInputFunction: public CFunction +{ +public: + CInputFunction(typedescription_t pTypeDesc, CBaseEntity *pBaseEntity); + void CInputFunction::__call__(object value, CBaseEntity *pActivator, CBaseEntity *pCaller); + +public: + typedescription_t m_pTypeDesc; + CBaseEntity *m_pBaseEntity; +}; + + //----------------------------------------------------------------------------- // inputdata_t extension class. //----------------------------------------------------------------------------- diff --git a/src/core/modules/entities/entities_datamaps_wrap.cpp b/src/core/modules/entities/entities_datamaps_wrap.cpp index 4ca1f018f..e197e6327 100644 --- a/src/core/modules/entities/entities_datamaps_wrap.cpp +++ b/src/core/modules/entities/entities_datamaps_wrap.cpp @@ -41,6 +41,7 @@ void export_interval(scope); void export_datamap(scope); void export_type_description(scope); +void export_input_function(scope); void export_input_data(scope); void export_variant(scope); void export_field_types(scope); @@ -55,6 +56,7 @@ DECLARE_SP_SUBMODULE(_entities, _datamaps) export_interval(_datamaps); export_datamap(_datamaps); export_type_description(_datamaps); + export_input_function(_datamaps); export_input_data(_datamaps); export_variant(_datamaps); export_field_types(_datamaps); @@ -225,6 +227,28 @@ void export_type_description(scope _datamaps) } +//----------------------------------------------------------------------------- +// Export CInputFunction. +//----------------------------------------------------------------------------- +void export_input_function(scope _datamaps) +{ + class_, boost::noncopyable> InputFunction( + "InputFunction", + init( + args("self", "desc", "entity"), + "Represents a property attribute that is only" + ) + ); + + InputFunction.def( + "__call__", + &CInputFunction::__call__, + "Call the stored function with the values given.", + ("self", arg("value")=object(), arg("activator")=object(), arg("caller")=object()) + ); +} + + //----------------------------------------------------------------------------- // Expose inputdata_t. //----------------------------------------------------------------------------- From 067d6fff768404e42a5ceb5ff393769d05d4d028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Sun, 10 May 2020 16:56:00 -0400 Subject: [PATCH 53/59] Added sanity check to InputFunction's constructor. Added missing entries to entities.datamaps.__all__. Updated InputFunction's documentation. --- .../source-python/entities/datamaps.py | 2 ++ .../modules/entities/entities_datamaps.cpp | 3 +++ .../entities/entities_datamaps_wrap.cpp | 24 +++++++++++++++++-- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/datamaps.py b/addons/source-python/packages/source-python/entities/datamaps.py index df94a5e0d..f1a88b2ca 100644 --- a/addons/source-python/packages/source-python/entities/datamaps.py +++ b/addons/source-python/packages/source-python/entities/datamaps.py @@ -49,8 +49,10 @@ # ============================================================================= # Set all to an empty list __all__ = ('DataMap', + 'EntityProperty', 'FieldType', 'InputData', + 'InputFunction', 'Interval', 'TypeDescription', 'TypeDescriptionFlags', diff --git a/src/core/modules/entities/entities_datamaps.cpp b/src/core/modules/entities/entities_datamaps.cpp index d4835d0e9..04d892651 100644 --- a/src/core/modules/entities/entities_datamaps.cpp +++ b/src/core/modules/entities/entities_datamaps.cpp @@ -217,6 +217,9 @@ CInputFunction::CInputFunction(typedescription_t pTypeDesc, CBaseEntity *pBaseEn object(DATA_TYPE_VOID) ) { + if (!(pTypeDesc.flags & FTYPEDESC_INPUT)) + BOOST_RAISE_EXCEPTION(PyExc_TypeError, "\"%s\" is not an input.", pTypeDesc.fieldName); + m_pTypeDesc = pTypeDesc; m_pBaseEntity = pBaseEntity; } diff --git a/src/core/modules/entities/entities_datamaps_wrap.cpp b/src/core/modules/entities/entities_datamaps_wrap.cpp index e197e6327..bedcd2d5d 100644 --- a/src/core/modules/entities/entities_datamaps_wrap.cpp +++ b/src/core/modules/entities/entities_datamaps_wrap.cpp @@ -236,14 +236,34 @@ void export_input_function(scope _datamaps) "InputFunction", init( args("self", "desc", "entity"), - "Represents a property attribute that is only" + "Instantiate the function instance and store the base attributes.\n" + "\n" + ":param TypeDescription desc:\n" + " The descriptor of the input bound to this instance.\n" + ":param BaseEntity entity:\n" + " The entity this input is bound to.\n" + "\n" + ":raises TypeError:\n" + " If the given descriptor is not an input." ) ); InputFunction.def( "__call__", &CInputFunction::__call__, - "Call the stored function with the values given.", + "Call the stored function with the values given.\n" + "\n" + ":param object value:\n" + " The value to pass to the input function.\n" + ":param BaseEntity activator:\n" + " The activator entity.\n" + ":param BaseEntity caller:\n" + " The caller entity.\n" + "\n" + ":raises ValueError:\n" + " If the given value is not valid for that input.\n" + ":raises TypeError:\n" + " If the type of the input is unsupported.", ("self", arg("value")=object(), arg("activator")=object(), arg("caller")=object()) ); } From 38091395d92a6f25820c3ef99f8e03cbda897812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Sun, 10 May 2020 17:00:49 -0400 Subject: [PATCH 54/59] Removed no longer used imports into entities._base. --- .../source-python/packages/source-python/entities/_base.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/_base.py b/addons/source-python/packages/source-python/entities/_base.py index 784e5c285..3b07fa483 100644 --- a/addons/source-python/packages/source-python/entities/_base.py +++ b/addons/source-python/packages/source-python/entities/_base.py @@ -25,7 +25,6 @@ # Engines from engines.precache import Model from engines.sound import Attenuation -from engines.sound import engine_sound from engines.sound import Channel from engines.sound import Pitch from engines.sound import Sound @@ -38,13 +37,10 @@ from engines.trace import Ray from engines.trace import TraceFilterSimple # Entities -from entities import BaseEntityGenerator -from entities import Edict from entities import TakeDamageInfo from entities.classes import server_classes from entities.constants import WORLD_ENTITY_INDEX from entities.constants import DamageTypes -from entities.constants import RenderMode from entities.helpers import index_from_inthandle from entities.helpers import index_from_pointer from entities.helpers import wrap_entity_mem_func @@ -57,13 +53,11 @@ # Mathlib from mathlib import NULL_VECTOR # Memory -from memory import get_object_pointer from memory import make_object from memory.helpers import MemberFunction # Players from players.constants import HitGroup # Studio -from studio.cache import model_cache from studio.constants import INVALID_ATTACHMENT_INDEX From 126550840c0dc5adf7939138a0d43a1a9031b72c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Sat, 16 May 2020 00:55:33 -0400 Subject: [PATCH 55/59] Added cached_result decorator. --- .../packages/source-python/core/cache.py | 48 +++++++++++++++++++ .../packages/source-python/players/_base.py | 23 ++------- 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/addons/source-python/packages/source-python/core/cache.py b/addons/source-python/packages/source-python/core/cache.py index 5bef6d4e4..b1bd41b2a 100644 --- a/addons/source-python/packages/source-python/core/cache.py +++ b/addons/source-python/packages/source-python/core/cache.py @@ -10,6 +10,12 @@ # ============================================================================= # >> FORWARD IMPORTS # ============================================================================= +# Python Imports +# FuncTools +from functools import wraps +# Types +from types import MethodType + # Source.Python Imports # Core from _core._cache import CachedProperty @@ -22,4 +28,46 @@ __all__ = [ 'CachedProperty', 'cached_property' + 'cached_result' ] + + +# ============================================================================= +# >> FUNCTIONS +# ============================================================================= +def cached_result(fget): + """Decorator used to register a cached method. + + :param function fget: + Method that is only called once and its result cached for subsequent + calls. + :rtype: CachedProperty + """ + # Get a dummy object as default cache, so that we can cache None + NONE = object() + + def getter(self): + """Getter function that generates the cached method.""" + # Set our cache to the default value + cache = NONE + + # Wrap the decorated method as the inner function + @wraps(fget) + def wrapper(self, *args, **kwargs): + """Calls the decorated method and cache the result.""" + nonlocal cache + + # Did we cache a result already? + if cache is NONE: + + # No cache, let's call the wrapped method and cache the result + cache = fget(self, *args, **kwargs) + + # Return the cached result + return cache + + # Bind the wrapper function to the passed instance and return it + return MethodType(wrapper, self) + + # Return a cached property bound to the getter function + return CachedProperty(getter, doc=fget.__doc__) diff --git a/addons/source-python/packages/source-python/players/_base.py b/addons/source-python/packages/source-python/players/_base.py index fd2561e64..3cfcda061 100755 --- a/addons/source-python/packages/source-python/players/_base.py +++ b/addons/source-python/packages/source-python/players/_base.py @@ -15,6 +15,7 @@ # Core from core import GAME_NAME from core.cache import cached_property +from core.cache import cached_result # Engines from engines.server import server from engines.server import engine_server @@ -217,35 +218,21 @@ def is_fake_client(self): """ return self.playerinfo.is_fake_client() - @cached_property - def _is_hltv(self): - """Return whether the player is HLTV. - - :rtype: bool - """ - return self.playerinfo.is_hltv() - + @cached_result def is_hltv(self): """Return whether the player is HLTV. :rtype: bool """ - return self._is_hltv - - @cached_property - def _is_bot(self): - """Return whether the player is a bot. - - :rtype: bool - """ - return self.is_fake_client() or self.steamid == 'BOT' + return self.playerinfo.is_hltv() + @cached_result def is_bot(self): """Return whether the player is a bot. :rtype: bool """ - return self._is_bot + return self.is_fake_client() or self.steamid == 'BOT' def is_in_a_vehicle(self): """Return whether the player is in a vehicle. From 2ce7227b02a56471c7f69f10ca528bbd2fd667d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Sat, 16 May 2020 02:53:03 -0400 Subject: [PATCH 56/59] Removed a redundant layer to get Entity.index. Removed some redundant extractions to get/set CachedProperty.__doc__. Fixed some docstrings. --- .../packages/source-python/core/cache.py | 6 +++++- .../packages/source-python/entities/_base.py | 14 +++----------- src/core/modules/core/core_cache.cpp | 6 +++--- src/core/modules/core/core_cache.h | 4 ++-- src/core/modules/core/core_cache_wrap.cpp | 4 ++-- 5 files changed, 15 insertions(+), 19 deletions(-) diff --git a/addons/source-python/packages/source-python/core/cache.py b/addons/source-python/packages/source-python/core/cache.py index b1bd41b2a..6285f101f 100644 --- a/addons/source-python/packages/source-python/core/cache.py +++ b/addons/source-python/packages/source-python/core/cache.py @@ -8,7 +8,7 @@ # ============================================================================= -# >> FORWARD IMPORTS +# >> IMPORTS # ============================================================================= # Python Imports # FuncTools @@ -16,6 +16,10 @@ # Types from types import MethodType + +# ============================================================================= +# >> FORWARD IMPORTS +# ============================================================================= # Source.Python Imports # Core from _core._cache import CachedProperty diff --git a/addons/source-python/packages/source-python/entities/_base.py b/addons/source-python/packages/source-python/entities/_base.py index 3b07fa483..5e41c5b0d 100644 --- a/addons/source-python/packages/source-python/entities/_base.py +++ b/addons/source-python/packages/source-python/entities/_base.py @@ -199,10 +199,10 @@ def __init__(self, index, caching=True): super().__init__(index) # Set the entity's base attributes - object.__setattr__(self, '_index', index) + vars(self)['index'] = index def __hash__(self): - """Return a hash value based on the entity index.""" + """Return a hash value based on the entity inthandle.""" # Required for sets, because we have implemented __eq__ return hash(self.inthandle) @@ -231,7 +231,7 @@ def __getattr__(self, attr): raise AttributeError('Attribute "{0}" not found'.format(attr)) def __setattr__(self, attr, value): - """Find if the attribute is value and sets its value.""" + """Find if the attribute is valid and sets its value.""" # Is the given attribute a property? if (attr in super().__dir__() and isinstance( getattr(self.__class__, attr, None), property)): @@ -299,14 +299,6 @@ def is_networked(self): """ return True - @cached_property - def index(self): - """Return the entity's index. - - :rtype: int - """ - return self._index - @property def owner(self): """Return the entity's owner. diff --git a/src/core/modules/core/core_cache.cpp b/src/core/modules/core/core_cache.cpp index c6064fb51..9861e828d 100644 --- a/src/core/modules/core/core_cache.cpp +++ b/src/core/modules/core/core_cache.cpp @@ -35,14 +35,14 @@ // CCachedProperty class. //----------------------------------------------------------------------------- CCachedProperty::CCachedProperty( - object fget=object(), object fset=object(), object fdel=object(), const char *doc=NULL, + object fget=object(), object fset=object(), object fdel=object(), object doc=object(), bool unbound=false, boost::python::tuple args=boost::python::tuple(), dict kwargs=dict()) { set_getter(fget); set_setter(fset); set_deleter(fdel); - m_szDocString = doc; + m_doc = doc; m_bUnbound = unbound; m_args = args; @@ -317,7 +317,7 @@ CCachedProperty *CCachedProperty::wrap_descriptor( { CCachedProperty *pProperty = new CCachedProperty( descriptor.attr("__get__"), descriptor.attr("__set__"), descriptor.attr("__delete__"), - extract(descriptor.attr("__doc__")), unbound, args, kwargs + descriptor.attr("__doc__"), unbound, args, kwargs ); pProperty->__set_name__(owner, name); diff --git a/src/core/modules/core/core_cache.h b/src/core/modules/core/core_cache.h index 5383dde10..e7136c8dd 100644 --- a/src/core/modules/core/core_cache.h +++ b/src/core/modules/core/core_cache.h @@ -41,7 +41,7 @@ class CCachedProperty { public: CCachedProperty( - object fget, object fset, object fdel, const char *doc, bool unbound, + object fget, object fset, object fdel, object doc, bool unbound, boost::python::tuple args, dict kwargs ); @@ -88,7 +88,7 @@ class CCachedProperty dict m_cache; public: - const char *m_szDocString; + object m_doc; boost::python::tuple m_args; dict m_kwargs; diff --git a/src/core/modules/core/core_cache_wrap.cpp b/src/core/modules/core/core_cache_wrap.cpp index 4910bcd4f..d9925506f 100644 --- a/src/core/modules/core/core_cache_wrap.cpp +++ b/src/core/modules/core/core_cache_wrap.cpp @@ -54,7 +54,7 @@ void export_cached_property(scope _cache) { class_ CachedProperty( "CachedProperty", - init( + init( ( arg("self"), arg("fget")=object(), arg("fset")=object(), arg("fdel")=object(), arg("doc")=object(), arg("unbound")=false, arg("args")=boost::python::tuple(), arg("kwargs")=dict() @@ -216,7 +216,7 @@ void export_cached_property(scope _cache) CachedProperty.def_readwrite( "__doc__", - &CCachedProperty::m_szDocString, + &CCachedProperty::m_doc, "Documentation string for this property.\n" "\n" ":rtype:\n" From 79e62a7e78c9b732d58b390e40d3c7acf6762c2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Sun, 17 May 2020 00:12:56 -0400 Subject: [PATCH 57/59] Added CachedProperty._cached_value methods. --- .../packages/source-python/entities/_base.py | 2 +- src/core/modules/core/core_cache.cpp | 76 +++++++++++-------- src/core/modules/core/core_cache.h | 4 +- src/core/modules/core/core_cache_wrap.cpp | 28 +++++++ 4 files changed, 75 insertions(+), 35 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/_base.py b/addons/source-python/packages/source-python/entities/_base.py index 5e41c5b0d..94ace4386 100644 --- a/addons/source-python/packages/source-python/entities/_base.py +++ b/addons/source-python/packages/source-python/entities/_base.py @@ -199,7 +199,7 @@ def __init__(self, index, caching=True): super().__init__(index) # Set the entity's base attributes - vars(self)['index'] = index + type(self).index.set_cached_value(self, index) def __hash__(self): """Return a hash value based on the entity inthandle.""" diff --git a/src/core/modules/core/core_cache.cpp b/src/core/modules/core/core_cache.cpp index 9861e828d..0177ba119 100644 --- a/src/core/modules/core/core_cache.cpp +++ b/src/core/modules/core/core_cache.cpp @@ -107,26 +107,6 @@ void CCachedProperty::_invalidate_cache(PyObject *pRef) } } -void CCachedProperty::_update_cache(object instance, object value) -{ - if (m_bUnbound) - m_cache[handle<>( - PyWeakref_NewRef( - instance.ptr(), - make_function( - boost::bind(&CCachedProperty::_invalidate_cache, this, _1), - default_call_policies(), - boost::mpl::vector2() - ).ptr() - ) - )] = value; - else - { - dict cache = extract(instance.attr("__dict__")); - cache[m_name] = value; - } -} - void CCachedProperty::_delete_cache(object instance) { try @@ -200,6 +180,46 @@ object CCachedProperty::get_owner() } +object CCachedProperty::get_cached_value(object instance) +{ + object value; + + if (m_bUnbound) + value = m_cache[ + handle<>( + PyWeakref_NewRef(instance.ptr(), NULL) + ) + ]; + else + { + dict cache = extract(instance.attr("__dict__")); + value = cache[m_name]; + } + + return value; +} + +void CCachedProperty::set_cached_value(object instance, object value) +{ + if (m_bUnbound) + m_cache[handle<>( + PyWeakref_NewRef( + instance.ptr(), + make_function( + boost::bind(&CCachedProperty::_invalidate_cache, this, _1), + default_call_policies(), + boost::mpl::vector2() + ).ptr() + ) + )] = value; + else + { + dict cache = extract(instance.attr("__dict__")); + cache[m_name] = value; + } +} + + void CCachedProperty::__set_name__(object owner, str name) { m_name = name; @@ -223,17 +243,7 @@ object CCachedProperty::__get__(object self, object instance, object owner=objec try { - if (pSelf.m_bUnbound) - return pSelf.m_cache[ - handle<>( - PyWeakref_NewRef(instance.ptr(), NULL) - ) - ]; - else - { - dict cache = extract(instance.attr("__dict__")); - return cache[name]; - } + value = pSelf.get_cached_value(instance); } catch (...) { @@ -256,7 +266,7 @@ object CCachedProperty::__get__(object self, object instance, object owner=objec ) ); - pSelf._update_cache(instance, value); + pSelf.set_cached_value(instance, value); } return value; @@ -277,7 +287,7 @@ void CCachedProperty::__set__(object instance, object value) ); if (!result.is_none()) - _update_cache(instance, _prepare_value(result)); + set_cached_value(instance, _prepare_value(result)); else _delete_cache(instance); } diff --git a/src/core/modules/core/core_cache.h b/src/core/modules/core/core_cache.h index e7136c8dd..3ed44bbea 100644 --- a/src/core/modules/core/core_cache.h +++ b/src/core/modules/core/core_cache.h @@ -48,7 +48,6 @@ class CCachedProperty static object _callable_check(object function, const char *szName); static object _prepare_value(object value); void _invalidate_cache(PyObject *pRef); - void _update_cache(object instance, object value); void _delete_cache(object instance); object get_getter(); @@ -63,6 +62,9 @@ class CCachedProperty str get_name(); object get_owner(); + object get_cached_value(object instance); + void set_cached_value(object instance, object value); + void __set_name__(object owner, str name); static object __get__(object self, object instance, object owner); void __set__(object instance, object value); diff --git a/src/core/modules/core/core_cache_wrap.cpp b/src/core/modules/core/core_cache_wrap.cpp index d9925506f..4fd3f5bb8 100644 --- a/src/core/modules/core/core_cache_wrap.cpp +++ b/src/core/modules/core/core_cache_wrap.cpp @@ -350,6 +350,34 @@ void export_cached_property(scope _cache) args("self", "item", "value") ); + CachedProperty.def( + "get_cached_value", + &CCachedProperty::get_cached_value, + "Returns the cached value for the given instance.\n" + "\n" + ":param object instance:\n" + " The instance to get the cached value for.\n" + "\n" + ":raises KeyError:\n" + " If the given instance didn't have a cached value.\n" + "\n" + ":rtype:" + " object", + args("self", "instance") + ); + + CachedProperty.def( + "set_cached_value", + &CCachedProperty::set_cached_value, + "Sets the cached value for the given instance.\n" + "\n" + ":param object instance:\n" + " The instance to set the cached value for.\n" + ":param object value:\n" + " The value to set as cached value.\n", + args("self", "instance", "value") + ); + CachedProperty.def( "wrap_descriptor", &CCachedProperty::wrap_descriptor, From b9e88260fa823749e21468fbbccc573cab8f2107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Tue, 19 May 2020 04:46:54 -0400 Subject: [PATCH 58/59] Fixed CachedProperty._cached_value methods from possibly caching the value of an unbound property. Fixed CachedProperty.set_cached_value not properly handling generators. --- src/core/modules/core/core_cache.cpp | 33 +++++++++++++++------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/core/modules/core/core_cache.cpp b/src/core/modules/core/core_cache.cpp index 0177ba119..dae5ea823 100644 --- a/src/core/modules/core/core_cache.cpp +++ b/src/core/modules/core/core_cache.cpp @@ -182,6 +182,12 @@ object CCachedProperty::get_owner() object CCachedProperty::get_cached_value(object instance) { + if (!m_name) + BOOST_RAISE_EXCEPTION( + PyExc_AttributeError, + "Unable to retrieve the value of an unbound property." + ); + object value; if (m_bUnbound) @@ -201,6 +207,12 @@ object CCachedProperty::get_cached_value(object instance) void CCachedProperty::set_cached_value(object instance, object value) { + if (!m_name) + BOOST_RAISE_EXCEPTION( + PyExc_AttributeError, + "Unable to assign the value of an unbound property." + ); + if (m_bUnbound) m_cache[handle<>( PyWeakref_NewRef( @@ -211,11 +223,11 @@ void CCachedProperty::set_cached_value(object instance, object value) boost::mpl::vector2() ).ptr() ) - )] = value; + )] = _prepare_value(value); else { dict cache = extract(instance.attr("__dict__")); - cache[m_name] = value; + cache[m_name] = _prepare_value(value); } } @@ -232,13 +244,6 @@ object CCachedProperty::__get__(object self, object instance, object owner=objec return self; CCachedProperty &pSelf = extract(self); - object name = pSelf.get_name(); - if (name.is_none()) - BOOST_RAISE_EXCEPTION( - PyExc_AttributeError, - "Unable to retrieve the value of an unbound property." - ); - object value; try @@ -259,11 +264,9 @@ object CCachedProperty::__get__(object self, object instance, object owner=objec "Unable to retrieve the value of a property that have no getter function." ); - value = pSelf._prepare_value( - getter( - *(make_tuple(handle<>(borrowed(instance.ptr()))) + pSelf.m_args), - **pSelf.m_kwargs - ) + value = getter( + *(make_tuple(handle<>(borrowed(instance.ptr()))) + pSelf.m_args), + **pSelf.m_kwargs ); pSelf.set_cached_value(instance, value); @@ -287,7 +290,7 @@ void CCachedProperty::__set__(object instance, object value) ); if (!result.is_none()) - set_cached_value(instance, _prepare_value(result)); + set_cached_value(instance, result); else _delete_cache(instance); } From f730a62986e2b904e9cbacca772be4344af2eb76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordan=20Bri=C3=A8re?= Date: Tue, 19 May 2020 04:51:52 -0400 Subject: [PATCH 59/59] Improved some Entity's methods by avoiding repeated attribute retrievals. --- .../packages/source-python/entities/_base.py | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/addons/source-python/packages/source-python/entities/_base.py b/addons/source-python/packages/source-python/entities/_base.py index 94ace4386..eec4b211a 100644 --- a/addons/source-python/packages/source-python/entities/_base.py +++ b/addons/source-python/packages/source-python/entities/_base.py @@ -374,10 +374,11 @@ def get_model(self): ``None`` if the entity has no model. :rtype: Model """ - if not self.model_name: + model_name = self.model_name + if not model_name: return None - return Model(self.model_name) + return Model(model_name) def set_model(self, model): """Set the entity's model to the given model. @@ -449,18 +450,21 @@ def delay( The delay instance. :rtype: Delay """ + # Get the index of the entity + index = self.index + # TODO: Ideally, we want to subclass Delay and cleanup on cancel() too # in case the caller manually cancel the returned Delay. def _callback(*args, **kwargs): """Called when the delay is executed.""" # Remove the delay from the global dictionary... - _entity_delays[self.index].remove(delay) + _entity_delays[index].remove(delay) # Was this the last pending delay for the entity? - if not _entity_delays[self.index]: + if not _entity_delays[index]: # Remove the entity from the dictionary... - del _entity_delays[self.index] + del _entity_delays[index] # Call the callback... callback(*args, **kwargs) @@ -469,7 +473,7 @@ def _callback(*args, **kwargs): delay = Delay(delay, _callback, args, kwargs, cancel_on_level_end) # Add the delay to the dictionary... - _entity_delays[self.index].add(delay) + _entity_delays[index].add(delay) # Return the delay instance... return delay @@ -610,8 +614,11 @@ def is_in_solid( :class:`BaseEntity` instances that are ignored by the ray. :rtype: bool """ + # Get the entity's origin + origin = self.origin + # Get a Ray object of the entity physic box - ray = Ray(self.origin, self.origin, self.mins, self.maxs) + ray = Ray(origin, origin, self.mins, self.maxs) # Get a new GameTrace instance trace = GameTrace()