Skip to content
Merged
68 changes: 66 additions & 2 deletions addons/source-python/packages/source-python/entities/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from collections import defaultdict
# Contextlib
from contextlib import suppress
# WeakRef
from weakref import WeakSet

# Source.Python Imports
# Core
Expand Down Expand Up @@ -45,6 +47,7 @@
from filters.weapons import WeaponClassIter
# Listeners
from listeners import OnEntityDeleted
from listeners import on_entity_deleted_listener_manager
from listeners.tick import Delay
# Mathlib
from mathlib import NULL_VECTOR
Expand All @@ -66,11 +69,58 @@
# Get a dictionary to store the delays
_entity_delays = defaultdict(set)

# Get a set to store the registered entity classes
_entity_classes = WeakSet()


# =============================================================================
# >> CLASSES
# =============================================================================
class Entity(BaseEntity):
class _EntityCaching(BaseEntity.__class__):
"""Metaclass used to cache entity instances."""

def __init__(cls, classname, bases, attributes):
"""Initializes the class."""
# New instances of this class will be cached in that dictionary
cls._cache = {}

# Add the class to the registered classes
_entity_classes.add(cls)

def __call__(cls, index, caching=True):
"""Called when a new instance of this class is requested.

:param int index:
The index of the entity instance requested.
:param bool caching:
Whether to lookup the cache for an existing instance or not.
"""
# Let's first lookup for a cached instance
if caching:
obj = cls._cache.get(index, None)
if obj is not None:
return obj

# Nothing in cache, let's create a new instance
obj = super().__call__(index)

# Let's cache the new instance we just created
if caching:
cls._cache[index] = obj

# We are done, let's return the instance
return obj

@property
def cache(cls):
"""Returns the cached instances of this class.

:rtype: dict
"""
return cls._cache


class Entity(BaseEntity, metaclass=_EntityCaching):
"""Class used to interact directly with entities.

Beside the standard way of doing stuff via methods and properties this
Expand All @@ -82,13 +132,23 @@ class also provides dynamic attributes that depend on the entity that is
2. :attr:`inputs`
3. :attr:`outputs`
4. :attr:`keyvalues`

:var cache:
A read-only attribute that returns a dictionary containing the cached
instances of this class.

.. note::
This is not an instance property, so it can only be
accessed through the class itself.
"""

def __init__(self, index):
def __init__(self, index, caching=True):
"""Initialize the Entity instance.

:param int index:
The entity index to wrap.
:param bool caching:
Whether to lookup the cache for an existing instance or not.
"""
# Initialize the object
super().__init__(index)
Expand Down Expand Up @@ -1031,6 +1091,10 @@ def _on_entity_deleted(base_entity):
# 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
Expand Down
4 changes: 3 additions & 1 deletion addons/source-python/packages/source-python/players/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,13 @@
class Player(PlayerMixin, Entity):
"""Class used to interact directly with players."""

def __init__(self, index):
def __init__(self, index, caching=True):
"""Initialize the object.

:param int index:
A valid player index.
:param bool caching:
Whether to lookup the cache for an existing instance or not.
:raise ValueError:
Raised if the index is invalid.
"""
Expand Down
4 changes: 3 additions & 1 deletion addons/source-python/packages/source-python/weapons/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@
class Weapon(WeaponMixin, Entity):
"""Allows easy usage of the weapon's attributes."""

def __init__(self, index):
def __init__(self, index, caching=True):
"""Initialize the object.

:param int index:
A valid weapon index.
:param bool caching:
Whether to lookup the cache for an existing instance or not.
:raise ValueError:
Raised if the index is invalid.
"""
Expand Down