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..6285f101f --- /dev/null +++ b/addons/source-python/packages/source-python/core/cache.py @@ -0,0 +1,77 @@ +# ../core/cache.py + +"""Provides caching functionality. + +.. data:: cached_property + An alias of :class:`core.cache.CachedProperty`. +""" + + +# ============================================================================= +# >> IMPORTS +# ============================================================================= +# Python Imports +# FuncTools +from functools import wraps +# Types +from types import MethodType + + +# ============================================================================= +# >> FORWARD IMPORTS +# ============================================================================= +# Source.Python Imports +# Core +from _core._cache import CachedProperty +from _core._cache import cached_property + + +# ============================================================================= +# >> ALL DECLARATION +# ============================================================================= +__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/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/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 diff --git a/addons/source-python/packages/source-python/engines/trace.py b/addons/source-python/packages/source-python/engines/trace.py index fc07a252a..450763437 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() not in self.ignore def get_trace_type(self): """Return the trace type. diff --git a/addons/source-python/packages/source-python/entities/_base.py b/addons/source-python/packages/source-python/entities/_base.py index 47528eead..eec4b211a 100644 --- a/addons/source-python/packages/source-python/entities/_base.py +++ b/addons/source-python/packages/source-python/entities/_base.py @@ -19,12 +19,12 @@ # 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 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 @@ -37,20 +37,16 @@ from engines.trace import Ray from engines.trace import TraceFilterSimple # Entities -from _entities._entity import BaseEntity -from entities import BaseEntityGenerator 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 # 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 @@ -58,13 +54,21 @@ from mathlib import NULL_VECTOR # Memory 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 +# ============================================================================= +# >> FORWARD IMPORTS +# ============================================================================= +# Source.Python Imports +# Entities +from _entities._entity import BaseEntity + + # ============================================================================= # >> GLOBAL VARIABLES # ============================================================================= @@ -119,16 +123,23 @@ def __call__(cls, index, caching=None): # 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) # 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.is_marked_for_deletion(): + cls._cache[index] = obj # We are done, let's return the instance return obj @@ -188,29 +199,39 @@ def __init__(self, index, caching=True): super().__init__(index) # Set the entity's base attributes - object.__setattr__(self, '_index', index) + type(self).index.set_cached_value(self, 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.index) + return hash(self.inthandle) 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: + for instance in self.server_classes.values(): - # Does the current server class contain the given attribute? - if hasattr(server_class, attr): + try: + # Get the attribute's value + value = getattr(instance, attr) + except AttributeError: + continue + + # Is the value a dynamic function? + if isinstance(value, MemberFunction): - # Return the attribute's value - return getattr(make_object(server_class, self.pointer), attr) + # Cache the value + with suppress(AttributeError): + object.__setattr__(self, attr, 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)) 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)): @@ -222,13 +243,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 @@ -254,58 +275,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. @@ -330,14 +299,6 @@ def is_networked(self): """ return True - @property - def index(self): - """Return the entity's index. - - :rtype: int - """ - return self._index - @property def owner(self): """Return the entity's owner. @@ -351,30 +312,47 @@ 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) + return { + server_class: make_object(server_class, self.pointer) for + server_class in server_classes.get_entity_server_classes(self) + } - @property + @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 - @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 + 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 - @property + @cached_property def keyvalues(self): """Iterate over all entity keyvalues available for the entity. @@ -383,8 +361,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. @@ -393,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. @@ -447,365 +429,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 - """ - return self._get_property(name, 'bool') - - def get_property_color(self, name): - """Return the Color property. - - :param str name: - Name of the property to retrieve. - :rtype: Color - """ - return self._get_property(name, 'Color') - - def get_property_edict(self, name): - """Return the Edict property. - - :param str name: - Name of the property to retrieve. - :rtype: Edict - """ - return self._get_property(name, 'Edict') - - def get_property_float(self, name): - """Return the float property. - - :param str name: - Name of the property to retrieve. - :rtype: float - """ - return self._get_property(name, 'float') - - def get_property_int(self, name): - """Return the integer property. - - :param str name: - Name of the property to retrieve. - :rtype: int - """ - return self._get_property(name, 'int') - - def get_property_interval(self, name): - """Return the Interval property. - - :param str name: - Name of the property to retrieve. - :rtype: Interval - """ - return self._get_property(name, 'Interval') - - def get_property_pointer(self, name): - """Return the pointer property. - - :param str name: - Name of the property to retrieve. - :rtype: Pointer - """ - return self._get_property(name, 'pointer') - - def get_property_quaternion(self, name): - """Return the Quaternion property. - - :param str name: - Name of the property to retrieve. - :rtype: Quaternion - """ - return self._get_property(name, 'Quaternion') - - def get_property_short(self, name): - """Return the short property. - - :param str name: - Name of the property to retrieve. - :rtype: int - """ - return self._get_property(name, 'short') - - def get_property_ushort(self, name): - """Return the ushort property. - - :param str name: - Name of the property to retrieve. - :rtype: int - """ - return self._get_property(name, 'ushort') - - def get_property_string(self, name): - """Return the string property. - - :param str name: - Name of the property to retrieve. - :rtype: str - """ - return self._get_property(name, 'string_array') - - def get_property_string_pointer(self, name): - """Return the string property. - - :param str name: - Name of the property to retrieve. - :rtype: str - """ - return self._get_property(name, 'string_pointer') - - def get_property_char(self, name): - """Return the char property. - - :param str name: - Name of the property to retrieve. - :rtype: str - """ - return self._get_property(name, 'char') - - def get_property_uchar(self, name): - """Return the uchar property. - - :param str name: - Name of the property to retrieve. - :rtype: int - """ - return self._get_property(name, 'uchar') - - def get_property_uint(self, name): - """Return the uint property. - - :param str name: - Name of the property to retrieve. - :rtype: int - """ - return self._get_property(name, 'uint') - - def get_property_vector(self, name): - """Return the Vector property. - - :param str name: - Name of the property to retrieve. - :rtype: Vector - """ - return self._get_property(name, 'Vector') - - 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. - - :param str name: - Name of the property to set. - :param bool value: - The value to set. - """ - self._set_property(name, 'bool', 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. - """ - self._set_property(name, 'Color', 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. - """ - self._set_property(name, 'Edict', 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. - """ - self._set_property(name, 'float', 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. - """ - self._set_property(name, 'int', 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. - """ - self._set_property(name, 'Interval', 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. - """ - self._set_property(name, 'pointer', 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. - """ - self._set_property(name, 'Quaternion', 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. - """ - self._set_property(name, 'short', 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. - """ - self._set_property(name, 'ushort', 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. - """ - self._set_property(name, 'string_array', 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. - """ - self._set_property(name, 'string_pointer', 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. - """ - self._set_property(name, 'char', 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. - """ - self._set_property(name, 'uchar', 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. - """ - self._set_property(name, 'uint', 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. - """ - self._set_property(name, 'Vector', 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): @@ -827,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) @@ -847,14 +473,15 @@ 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 - def repeat(self, callback, args=(), kwargs=None, cancel_on_level_end=False): + def repeat( + self, callback, args=(), kwargs=None, + cancel_on_level_end=False): """Create the repeat which will be stopped after removing the entity. - :param callback: A callable object that should be called at the end of each loop. :param tuple args: @@ -879,29 +506,27 @@ def repeat(self, callback, args=(), kwargs=None, cancel_on_level_end=False): # Return the repeat instance... return repeat + 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. @@ -979,7 +604,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: @@ -989,15 +614,26 @@ 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() # 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() @@ -1147,48 +783,34 @@ 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. :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) + # Loop through all delays... + for delay in _entity_delays.pop(index, ()): - # Was delay registered for this entity? - if index in _entity_delays: - # Loop through all delays... - for delay in _entity_delays[index]: - - # Make sure the delay is still running... - if not delay.running: - continue - - # Cancel the delay... + # Cancel the delay... + with suppress(ValueError): delay.cancel() - # Remove the entity from the dictionary... - del _entity_delays[index] + # Loop through all repeats... + for repeat in _entity_repeats.pop(index, ()): - # Was repeat registered for this entity? - if index in _entity_repeats: - # Loop through all repeats... - for repeat in _entity_repeats[index]: + # Stop the repeat if running + if repeat.status is RepeatStatus.RUNNING: + repeat.stop() - # Stop the repeat if running - if repeat.status is RepeatStatus.RUNNING: - repeat.stop() - - # Remove the entity from the dictionary... - del _entity_repeats[index] - + # Invalidate the internal entity caches for this entity + for cls in _entity_classes: + cls.cache.pop(index, None) diff --git a/addons/source-python/packages/source-python/entities/classes.py b/addons/source-python/packages/source-python/entities/classes.py index 72dcc57c4..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 @@ -375,7 +377,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 @@ -456,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: @@ -516,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 @@ -536,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 fa417a231..f1a88b2ca 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 @@ -48,8 +49,10 @@ # ============================================================================= # Set all to an empty list __all__ = ('DataMap', + 'EntityProperty', 'FieldType', 'InputData', + 'InputFunction', 'Interval', 'TypeDescription', 'TypeDescriptionFlags', @@ -120,51 +123,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.""" - super().__init__(function) - - 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/addons/source-python/packages/source-python/entities/dictionary.py b/addons/source-python/packages/source-python/entities/dictionary.py index 8cbbb7827..922e361dd 100644 --- a/addons/source-python/packages/source-python/entities/dictionary.py +++ b/addons/source-python/packages/source-python/entities/dictionary.py @@ -5,6 +5,10 @@ # ============================================================================ # >> IMPORTS # ============================================================================ +# Python Imports +# ContextLib +from contextlib import suppress + # Source.Python Imports # Core from core import AutoUnload @@ -46,20 +50,22 @@ 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.is_marked_for_deletion(): + self[index] = instance + return instance 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. @@ -75,26 +81,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) + if index in self: + # 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 ccc9e9b9e..1d67fbf7b 100644 --- a/addons/source-python/packages/source-python/entities/helpers.py +++ b/addons/source-python/packages/source-python/entities/helpers.py @@ -2,6 +2,18 @@ """Provides helper functions to convert from one type to another.""" +# ============================================================================= +# >> IMPORTS +# ============================================================================= +# Python Imports +# WeakRef +from weakref import proxy + +# Source.Python Imports +# Core +from core.cache import CachedProperty + + # ============================================================================= # >> FORWARD IMPORTS # ============================================================================= @@ -89,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__( @@ -110,7 +122,7 @@ 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 CachedProperty( + lambda self: EntityMemFuncWrapper(self, wrapper), + doc=wrapper.__doc__ + ) 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/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 80c918133..3cfcda061 --- a/addons/source-python/packages/source-python/players/_base.py +++ b/addons/source-python/packages/source-python/players/_base.py @@ -14,6 +14,8 @@ from bitbuffers import BitBufferWrite # 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 @@ -89,7 +91,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, caching=None): @@ -103,10 +104,9 @@ def from_userid(cls, userid, caching=None): """ return cls(index_from_userid(userid), caching=caching) - @property + @cached_property def net_info(self): """Return the player's network channel information. - :return: ``None`` if no network channel information exists. E. g. if the player is a bot. @@ -114,7 +114,7 @@ def net_info(self): """ return engine_server.get_player_net_info(self.index) - @property + @cached_property def raw_steamid(self): """Return the player's unformatted SteamID. @@ -130,18 +130,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. @@ -149,7 +146,7 @@ def userid(self): """ return self.playerinfo.userid - @property + @cached_property def steamid(self): """Return the player's SteamID. @@ -170,7 +167,7 @@ def set_name(self, name): name = property(get_name, set_name) - @property + @cached_property def client(self): """Return the player's client instance. @@ -178,7 +175,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. @@ -187,7 +184,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. @@ -195,7 +192,7 @@ def uniqueid(self): """ return uniqueid_from_playerinfo(self.playerinfo) - @property + @cached_property def address(self): """Return the player's IP address and port. @@ -221,6 +218,7 @@ def is_fake_client(self): """ return self.playerinfo.is_fake_client() + @cached_result def is_hltv(self): """Return whether the player is HLTV. @@ -228,6 +226,7 @@ def is_hltv(self): """ return self.playerinfo.is_hltv() + @cached_result def is_bot(self): """Return whether the player is a bot. @@ -262,8 +261,7 @@ def set_team(self, value): team = property(get_team, set_team) - @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. @@ -272,6 +270,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. 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') 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..dae5ea823 --- /dev/null +++ b/src/core/modules/core/core_cache.cpp @@ -0,0 +1,339 @@ +/** +* ============================================================================= +* 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(), 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_doc = doc; + m_bUnbound = unbound; + + 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::_prepare_value(object 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(...) + { + if (!PyErr_ExceptionMatches(PyExc_StopIteration)) + throw_error_already_set(); + + PyErr_Clear(); + break; + } + } + + 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::_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() +{ + return m_fget; +} + +object CCachedProperty::set_getter(object fget) +{ + m_fget = _callable_check(fget, "getter"); + return fget; +} + + +object CCachedProperty::get_setter() +{ + return m_fset; +} + +object CCachedProperty::set_setter(object fset) +{ + m_fset = _callable_check(fset, "setter"); + return fset; +} + + +object CCachedProperty::get_deleter() +{ + return m_fdel; +} + +object CCachedProperty::set_deleter(object fdel) +{ + m_fdel = _callable_check(fdel, "deleter"); + return fdel; +} + + +str CCachedProperty::get_name() +{ + return m_name; +} + +object CCachedProperty::get_owner() +{ + return m_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) + 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_name) + BOOST_RAISE_EXCEPTION( + PyExc_AttributeError, + "Unable to assign the value of an unbound property." + ); + + 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() + ) + )] = _prepare_value(value); + else + { + dict cache = extract(instance.attr("__dict__")); + cache[m_name] = _prepare_value(value); + } +} + + +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()) +{ + if (instance.is_none()) + return self; + + CCachedProperty &pSelf = extract(self); + object value; + + try + { + value = pSelf.get_cached_value(instance); + } + catch (...) + { + if (!PyErr_ExceptionMatches(PyExc_KeyError)) + throw_error_already_set(); + + PyErr_Clear(); + + 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." + ); + + value = getter( + *(make_tuple(handle<>(borrowed(instance.ptr()))) + pSelf.m_args), + **pSelf.m_kwargs + ); + + pSelf.set_cached_value(instance, value); + } + + return value; +} + + +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 + ); + + if (!result.is_none()) + set_cached_value(instance, result); + else + _delete_cache(instance); +} + +void CCachedProperty::__delete__(object instance) +{ + if (!m_fdel.is_none()) + m_fdel( + *(make_tuple(handle<>(borrowed(instance.ptr()))) + m_args), + **m_kwargs + ); + + _delete_cache(instance); +} + +object CCachedProperty::__call__(object self, object fget) +{ + CCachedProperty &pSelf = extract(self); + pSelf.set_getter(fget); + return self; +} + +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, str name, + bool unbound, boost::python::tuple args, dict kwargs) +{ + CCachedProperty *pProperty = new CCachedProperty( + descriptor.attr("__get__"), descriptor.attr("__set__"), descriptor.attr("__delete__"), + descriptor.attr("__doc__"), unbound, args, kwargs + ); + + 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..3ed44bbea --- /dev/null +++ b/src/core/modules/core/core_cache.h @@ -0,0 +1,100 @@ +/** +* ============================================================================= +* 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, object 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 _delete_cache(object instance); + + object get_getter(); + object set_getter(object fget); + + object get_setter(); + object set_setter(object fget); + + object get_deleter(); + object set_deleter(object fget); + + 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); + void __delete__(object instance); + static object __call__(object self, object fget); + object __getitem__(str item); + void __setitem__(str item, object value); + + 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; + object m_fset; + object m_fdel; + + str m_name; + object m_owner; + + bool m_bUnbound; + dict m_cache; + +public: + object m_doc; + + 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..4fd3f5bb8 --- /dev/null +++ b/src/core/modules/core/core_cache_wrap.cpp @@ -0,0 +1,412 @@ +/** +* ============================================================================= +* 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("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" + " 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 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 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" + " 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.\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." + "\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" + ) + ); + + + 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", + args("self", "fget") + ); + + 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", + args("self", "fset") + ); + + 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", + args("self", "fdel") + ); + + 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_doc, + "Documentation string for this property.\n" + "\n" + ":rtype:\n" + " str" + ); + + + CachedProperty.add_property( + "name", + &CCachedProperty::get_name, + "The name this property is registered as.\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.def_readwrite( + "args", + &CCachedProperty::m_args, + "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.\n" + "\n" + ":rtype:\n" + " dict" + ); + + CachedProperty.def( + "__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( + "__get__", + &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", + ("self", "instance", arg("owner")=object()) + ); + + CachedProperty.def( + "__set__", + &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", + args("self", "instance", "value") + ); + + CachedProperty.def( + "__delete__", + &CCachedProperty::__delete__, + "Deletes this property and invalidates its cached value.\n" + "\n" + ":param object instance:\n" + " The instance for which this property is being deleted.", + args("self", "instance") + ); + + CachedProperty.def( + "__call__", + &CCachedProperty::__call__, + "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", + args("self", "fget") + ); + + 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", + args("self", "item") + ); + + 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 assign to the given keyword.", + 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, + manage_new_object_policy(), + "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" + ":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, arg("args")=boost::python::tuple(), arg("kwargs")=dict() + ) + ) + .staticmethod("wrap_descriptor"); + + scope().attr("cached_property") = scope().attr("CachedProperty"); +} 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); } diff --git a/src/core/modules/entities/entities_datamaps.cpp b/src/core/modules/entities/entities_datamaps.cpp index db030d3c6..04d892651 100644 --- a/src/core/modules/entities/entities_datamaps.cpp +++ b/src/core/modules/entities/entities_datamaps.cpp @@ -206,6 +206,72 @@ 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) + ) +{ + if (!(pTypeDesc.flags & FTYPEDESC_INPUT)) + BOOST_RAISE_EXCEPTION(PyExc_TypeError, "\"%s\" is not an input.", pTypeDesc.fieldName); + + 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..bedcd2d5d 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,48 @@ void export_type_description(scope _datamaps) } +//----------------------------------------------------------------------------- +// Export CInputFunction. +//----------------------------------------------------------------------------- +void export_input_function(scope _datamaps) +{ + class_, boost::noncopyable> InputFunction( + "InputFunction", + init( + args("self", "desc", "entity"), + "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.\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()) + ); +} + + //----------------------------------------------------------------------------- // Expose inputdata_t. //----------------------------------------------------------------------------- diff --git a/src/core/modules/entities/entities_entity.cpp b/src/core/modules/entities/entities_entity.cpp index 592730d8e..3470d9690 100644 --- a/src/core/modules/entities/entities_entity.cpp +++ b/src/core/modules/entities/entities_entity.cpp @@ -73,6 +73,35 @@ 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 (...) + { + try + { + entity = MakeObject(cls, &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; +} + CBaseEntity* CBaseEntityWrapper::find(const char* name) { CBaseEntity* pEntity = (CBaseEntity *) servertools->FirstEntity(); @@ -86,6 +115,30 @@ CBaseEntity* CBaseEntityWrapper::find(const char* name) return NULL; } +object CBaseEntityWrapper::find(object cls, const char *name) +{ + CBaseEntityWrapper *pEntity = (CBaseEntityWrapper *)find(name); + if (pEntity) + { + try + { + return cls(pEntity->GetIndex()); + } + catch (...) + { + try + { + return MakeObject(cls, &pEntity->GetPointer()); + } + catch (...) + { + PyErr_Clear(); + } + } + } + return object(); +} + CBaseEntity* CBaseEntityWrapper::find_or_create(const char* name) { CBaseEntity* entity = find(name); @@ -95,6 +148,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? @@ -170,6 +232,11 @@ void CBaseEntityWrapper::remove() (pEntity->*pInputKillFunc)(data); } +bool CBaseEntityWrapper::is_marked_for_deletion() +{ + return GetEntityFlags() & EFL_KILLME; +} + int CBaseEntityWrapper::get_size() { return get_factory()->GetEntitySize(); @@ -187,7 +254,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 +266,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; } @@ -434,6 +506,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 a2a6e4080..6ceb77920 100644 --- a/src/core/modules/entities/entities_entity.h +++ b/src/core/modules/entities/entities_entity.h @@ -96,13 +96,17 @@ 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); IEntityFactory* get_factory(); void remove(); + bool is_marked_for_deletion(); int get_size(); void spawn(); @@ -206,6 +210,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); @@ -251,6 +324,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 ef40a2c3c..4810fe80f 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", @@ -130,6 +133,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, @@ -448,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." @@ -459,12 +476,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 +498,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 +513,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", @@ -726,6 +751,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, @@ -827,6 +859,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, @@ -949,6 +986,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, @@ -1051,6 +1095,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); @@ -1060,4 +1351,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/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(); 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_function.cpp b/src/core/modules/memory/memory_function.cpp index 314d8031b..897878ea3 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); @@ -353,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 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_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/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_tools.h b/src/core/modules/memory/memory_tools.h index 97e26e75d..1edc832fc 100644 --- a/src/core/modules/memory/memory_tools.h +++ b/src/core/modules/memory/memory_tools.h @@ -41,26 +41,44 @@ 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(); } // ============================================================================ // >> MakeObject // ============================================================================ -inline object MakeObject(object cls, object oPtr) +inline object MakeObject(object cls, CPointer *pPtr) { - 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); +} + +inline object MakeObject(object cls, object oPtr) +{ + return MakeObject(cls, ExtractPointer(oPtr)); } @@ -69,10 +87,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; } 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; diff --git a/src/core/modules/memory/memory_wrap.cpp b/src/core/modules/memory/memory_wrap.cpp index 516755a35..2dad81f88 100644 --- a/src/core/modules/memory/memory_wrap.cpp +++ b/src/core/modules/memory/memory_wrap.cpp @@ -454,7 +454,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." @@ -819,7 +820,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" @@ -934,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" diff --git a/src/core/sp_main.cpp b/src/core/sp_main.cpp index b3fada5b9..2e582c3fa 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); @@ -630,7 +628,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 _on_entity_deleted = import("entities").attr("_base").attr("_on_entity_deleted"); + _on_entity_deleted(oEntity); } void CSourcePython::OnDataLoaded( MDLCacheDataType_t type, MDLHandle_t handle ) 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. //----------------------------------------------------------------------------- diff --git a/src/core/utilities/wrap_macros.h b/src/core/utilities/wrap_macros.h index fbd551992..d70c420b3 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 @@ -124,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. //--------------------------------------------------------------------------------- @@ -133,10 +145,26 @@ 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) = transfer_ownership_to_python( + CCachedProperty::wrap_descriptor(cls.attr(szName), cls, szName) ); return cls; };