mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Merge pull request #3367 from ChrisLR/components-refactoring
[Components] Refactor Component registering with cherry-picked additions
This commit is contained in:
commit
0edcebea0f
9 changed files with 436 additions and 265 deletions
|
|
@ -30,12 +30,20 @@ class Character(ComponentHolderMixin, DefaultCharacter):
|
|||
# ...
|
||||
```
|
||||
|
||||
Components need to inherit the Component class directly and require a name.
|
||||
Components need to inherit the Component class and require a unique name.
|
||||
Components may inherit from other components but must specify another name.
|
||||
You can assign the same 'slot' to both components to have alternative implementations.
|
||||
```python
|
||||
from evennia.contrib.base_systems.components import Component
|
||||
|
||||
|
||||
class Health(Component):
|
||||
name = "health"
|
||||
|
||||
|
||||
class ItemHealth(Health):
|
||||
name = "item_health"
|
||||
slot = "health"
|
||||
```
|
||||
|
||||
Components may define DBFields or NDBFields at the class level.
|
||||
|
|
@ -103,7 +111,10 @@ character.components.add(vampirism)
|
|||
|
||||
...
|
||||
|
||||
vampirism_from_elsewhere = character.components.get("vampirism")
|
||||
vampirism = character.components.get("vampirism")
|
||||
|
||||
# Alternatively
|
||||
vampirism = character.cmp.vampirism
|
||||
```
|
||||
|
||||
Keep in mind that all components must be imported to be visible in the listing.
|
||||
|
|
@ -128,6 +139,14 @@ from typeclasses.components import health
|
|||
```
|
||||
Both of the above examples will work.
|
||||
|
||||
## Known Issues
|
||||
|
||||
Assigning mutable default values such as a list to a DBField will share it across instances.
|
||||
To avoid this, you must set autocreate=True on the field, like this.
|
||||
```python
|
||||
health = DBField(default=[], autocreate=True)
|
||||
```
|
||||
|
||||
## Full Example
|
||||
```python
|
||||
from evennia.contrib.base_systems import components
|
||||
|
|
|
|||
|
|
@ -7,23 +7,16 @@ This helps writing isolated code and reusing it over multiple objects.
|
|||
|
||||
See the docs for more information.
|
||||
"""
|
||||
|
||||
from evennia.contrib.base_systems.components import exceptions
|
||||
from evennia.contrib.base_systems.components.listing import COMPONENT_LISTING, get_component_class
|
||||
from evennia.contrib.base_systems.components.component import Component
|
||||
from evennia.contrib.base_systems.components.dbfield import DBField, NDBField, TagField
|
||||
from evennia.contrib.base_systems.components.dbfield import (
|
||||
DBField,
|
||||
NDBField,
|
||||
TagField
|
||||
)
|
||||
|
||||
from evennia.contrib.base_systems.components.holder import (
|
||||
ComponentHolderMixin,
|
||||
ComponentProperty,
|
||||
)
|
||||
|
||||
|
||||
def get_component_class(component_name):
|
||||
subclasses = Component.__subclasses__()
|
||||
component_class = next((sc for sc in subclasses if sc.name == component_name), None)
|
||||
if component_class is None:
|
||||
message = (
|
||||
f"Component named {component_name} has not been found. "
|
||||
f"Make sure it has been imported before being used."
|
||||
)
|
||||
raise Exception(message)
|
||||
|
||||
return component_class
|
||||
|
|
|
|||
|
|
@ -3,10 +3,41 @@ Components - ChrisLR 2022
|
|||
|
||||
This file contains the base class to inherit for creating new components.
|
||||
"""
|
||||
import itertools
|
||||
|
||||
from evennia.commands.cmdset import CmdSet
|
||||
from evennia.contrib.base_systems.components import COMPONENT_LISTING, exceptions
|
||||
|
||||
|
||||
class Component:
|
||||
class BaseComponent(type):
|
||||
"""
|
||||
This is the metaclass for components,
|
||||
responsible for registering components to the listing.
|
||||
"""
|
||||
@classmethod
|
||||
def __new__(cls, *args):
|
||||
"""
|
||||
Every class that uses this metaclass will be registered
|
||||
as a component in the Component Listing using its name.
|
||||
All of them require a unique name.
|
||||
"""
|
||||
new_type = super().__new__(*args)
|
||||
if new_type.__base__ == object:
|
||||
return new_type
|
||||
|
||||
name = getattr(new_type, "name", None)
|
||||
if not name:
|
||||
raise ValueError(f"Component {new_type} requires a name.")
|
||||
|
||||
if existing_type := COMPONENT_LISTING.get(name):
|
||||
if not str(new_type) == str(existing_type):
|
||||
raise ValueError(f"Component name {name} is a duplicate, must be unique.")
|
||||
else:
|
||||
COMPONENT_LISTING[name] = new_type
|
||||
|
||||
return new_type
|
||||
|
||||
|
||||
class Component(metaclass=BaseComponent):
|
||||
"""
|
||||
This is the base class for components.
|
||||
Any component must inherit from this class to be considered for usage.
|
||||
|
|
@ -14,10 +45,15 @@ class Component:
|
|||
Each Component must supply the name, it is used as a slot name but also part of the attribute key.
|
||||
"""
|
||||
|
||||
__slots__ = ('host',)
|
||||
|
||||
name = ""
|
||||
slot = None
|
||||
|
||||
_fields = {}
|
||||
|
||||
def __init__(self, host=None):
|
||||
assert self.name, "All Components must have a Name"
|
||||
assert self.name, "All Components must have a name"
|
||||
self.host = host
|
||||
|
||||
@classmethod
|
||||
|
|
@ -61,8 +97,8 @@ class Component:
|
|||
"""
|
||||
This deletes all component attributes from the host's db
|
||||
"""
|
||||
for attribute in self._all_db_field_names:
|
||||
delattr(self, attribute)
|
||||
for name in self._fields.keys():
|
||||
delattr(self, name)
|
||||
|
||||
@classmethod
|
||||
def load(cls, host):
|
||||
|
|
@ -77,7 +113,6 @@ class Component:
|
|||
Component: The loaded instance of the component
|
||||
|
||||
"""
|
||||
|
||||
return cls(host)
|
||||
|
||||
def at_added(self, host):
|
||||
|
|
@ -88,12 +123,8 @@ class Component:
|
|||
host (object): The host typeclass instance
|
||||
|
||||
"""
|
||||
|
||||
if self.host:
|
||||
if self.host == host:
|
||||
return
|
||||
else:
|
||||
raise ComponentRegisterError("Components must not register twice!")
|
||||
if self.host and self.host != host:
|
||||
raise exceptions.InvalidComponentError("Components must not register twice!")
|
||||
|
||||
self.host = host
|
||||
|
||||
|
|
@ -106,7 +137,8 @@ class Component:
|
|||
|
||||
"""
|
||||
if host != self.host:
|
||||
raise ComponentRegisterError("Component attempted to remove from the wrong host.")
|
||||
raise ValueError("Component attempted to remove from the wrong host.")
|
||||
|
||||
self.host = None
|
||||
|
||||
@property
|
||||
|
|
@ -131,25 +163,14 @@ class Component:
|
|||
"""
|
||||
return self.host.nattributes
|
||||
|
||||
@property
|
||||
def _all_db_field_names(self):
|
||||
return itertools.chain(self.db_field_names, self.ndb_field_names)
|
||||
@classmethod
|
||||
def add_field(cls, name, field):
|
||||
cls._fields[name] = field
|
||||
|
||||
@property
|
||||
def db_field_names(self):
|
||||
db_fields = getattr(self, "_db_fields", {})
|
||||
return db_fields.keys()
|
||||
@classmethod
|
||||
def get_fields(cls):
|
||||
return tuple(cls._fields.values())
|
||||
|
||||
@property
|
||||
def ndb_field_names(self):
|
||||
ndb_fields = getattr(self, "_ndb_fields", {})
|
||||
return ndb_fields.keys()
|
||||
|
||||
@property
|
||||
def tag_field_names(self):
|
||||
tag_fields = getattr(self, "_tag_fields", {})
|
||||
return tag_fields.keys()
|
||||
|
||||
|
||||
class ComponentRegisterError(Exception):
|
||||
pass
|
||||
@classmethod
|
||||
def get_component_slot(cls):
|
||||
return cls.slot or cls.name
|
||||
|
|
|
|||
|
|
@ -3,8 +3,13 @@ Components - ChrisLR 2022
|
|||
|
||||
This file contains the Descriptors used to set Fields in Components
|
||||
"""
|
||||
import typing
|
||||
|
||||
from evennia.typeclasses.attributes import AttributeProperty, NAttributeProperty
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from evennia.contrib.base_systems.components import Component
|
||||
|
||||
|
||||
class DBField(AttributeProperty):
|
||||
"""
|
||||
|
|
@ -13,21 +18,40 @@ class DBField(AttributeProperty):
|
|||
It uses AttributeProperty under the hood but prefixes the key with the component name.
|
||||
"""
|
||||
|
||||
def __set_name__(self, owner, name):
|
||||
def __init__(self, default=None, autocreate=False, **kwargs):
|
||||
super().__init__(default=default, autocreate=autocreate, **kwargs)
|
||||
|
||||
def __set_name__(self, owner: 'Component', name):
|
||||
"""
|
||||
Called when descriptor is first assigned to the class.
|
||||
|
||||
Args:
|
||||
owner (object): The component classF on which this is set
|
||||
owner (Component): The component classF on which this is set
|
||||
name (str): The name that was used to set the DBField.
|
||||
"""
|
||||
key = f"{owner.name}::{name}"
|
||||
self._key = key
|
||||
db_fields = getattr(owner, "_db_fields", None)
|
||||
if db_fields is None:
|
||||
db_fields = {}
|
||||
setattr(owner, "_db_fields", db_fields)
|
||||
db_fields[name] = self
|
||||
self._key = f"{owner.get_component_slot()}::{name}"
|
||||
owner.add_field(name, self)
|
||||
|
||||
def at_added(self, component):
|
||||
"""
|
||||
Called when the parent component is added to a host.
|
||||
|
||||
Args:
|
||||
component (Component): The component instance being added.
|
||||
"""
|
||||
|
||||
if self._autocreate:
|
||||
self.__get__(component, type(component))
|
||||
|
||||
def at_removed(self, component):
|
||||
"""
|
||||
Called when the parent component is removed from a host.
|
||||
|
||||
Args:
|
||||
component (Component): The component instance being removed.
|
||||
"""
|
||||
|
||||
self.__delete__(component)
|
||||
|
||||
|
||||
class NDBField(NAttributeProperty):
|
||||
|
|
@ -37,21 +61,35 @@ class NDBField(NAttributeProperty):
|
|||
It uses NAttributeProperty under the hood but prefixes the key with the component name.
|
||||
"""
|
||||
|
||||
def __set_name__(self, owner, name):
|
||||
def __set_name__(self, owner: 'Component', name):
|
||||
"""
|
||||
Called when descriptor is first assigned to the class.
|
||||
|
||||
Args:
|
||||
owner (object): The component class on which this is set
|
||||
owner (Component): The component class on which this is set
|
||||
name (str): The name that was used to set the DBField.
|
||||
"""
|
||||
key = f"{owner.name}::{name}"
|
||||
self._key = key
|
||||
ndb_fields = getattr(owner, "_ndb_fields", None)
|
||||
if ndb_fields is None:
|
||||
ndb_fields = {}
|
||||
setattr(owner, "_ndb_fields", ndb_fields)
|
||||
ndb_fields[name] = self
|
||||
self._key = f"{owner.get_component_slot()}::{name}"
|
||||
owner.add_field(name, self)
|
||||
|
||||
def at_added(self, component):
|
||||
"""
|
||||
Called when the parent component is added to a host.
|
||||
|
||||
Args:
|
||||
component (Component): The component instance being added.
|
||||
"""
|
||||
if self._autocreate:
|
||||
self.__set__(component, self._default)
|
||||
|
||||
def at_removed(self, component):
|
||||
"""
|
||||
Called when the parent component is removed from a host.
|
||||
|
||||
Args:
|
||||
component (Component): The component instance being removed.
|
||||
"""
|
||||
self.__delete__(component)
|
||||
|
||||
|
||||
class TagField:
|
||||
|
|
@ -70,17 +108,13 @@ class TagField:
|
|||
self._default = default
|
||||
self._enforce_single = enforce_single
|
||||
|
||||
def __set_name__(self, owner, name):
|
||||
def __set_name__(self, owner: 'Component', name):
|
||||
"""
|
||||
Called when TagField is first assigned to the class.
|
||||
It is called with the component class and the name of the field.
|
||||
"""
|
||||
self._category_key = f"{owner.name}::{name}"
|
||||
tag_fields = getattr(owner, "_tag_fields", None)
|
||||
if tag_fields is None:
|
||||
tag_fields = {}
|
||||
setattr(owner, "_tag_fields", tag_fields)
|
||||
tag_fields[name] = self
|
||||
self._category_key = f"{owner.get_component_slot()}::{name}"
|
||||
owner.add_field(name, self)
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
"""
|
||||
|
|
@ -114,3 +148,22 @@ class TagField:
|
|||
It is called with the component instance.
|
||||
"""
|
||||
instance.host.tags.clear(category=self._category_key)
|
||||
|
||||
def at_added(self, component):
|
||||
"""
|
||||
Called when the parent component is added to a host.
|
||||
|
||||
Args:
|
||||
component (Component): The component instance being added.
|
||||
"""
|
||||
if self._default:
|
||||
self.__set__(component, self._default)
|
||||
|
||||
def at_removed(self, component):
|
||||
"""
|
||||
Called when the parent component is removed from a host.
|
||||
|
||||
Args:
|
||||
component (Component): The component instance being removed.
|
||||
"""
|
||||
self.__delete__(component)
|
||||
|
|
|
|||
10
evennia/contrib/base_systems/components/exceptions.py
Normal file
10
evennia/contrib/base_systems/components/exceptions.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
class InvalidComponentError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class ComponentDoesNotExist(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class ComponentIsNotRegistered(ValueError):
|
||||
pass
|
||||
|
|
@ -5,7 +5,7 @@ This file contains the classes that allow a typeclass to use components.
|
|||
"""
|
||||
|
||||
from evennia.contrib.base_systems import components
|
||||
from evennia.contrib.base_systems.components import signals
|
||||
from evennia.contrib.base_systems.components import signals, exceptions, get_component_class
|
||||
|
||||
|
||||
class ComponentProperty:
|
||||
|
|
@ -17,19 +17,26 @@ class ComponentProperty:
|
|||
Defaults can be overridden for this typeclass by passing kwargs
|
||||
"""
|
||||
|
||||
def __init__(self, component_name, **kwargs):
|
||||
def __init__(self, name, **kwargs):
|
||||
"""
|
||||
Initializes the descriptor
|
||||
|
||||
Args:
|
||||
component_name (str): The name of the component
|
||||
name (str): The name of the component
|
||||
**kwargs (any): Key=Values overriding default values of the component
|
||||
"""
|
||||
self.component_name = component_name
|
||||
self.name = name
|
||||
self.values = kwargs
|
||||
self.component_class = None
|
||||
self.slot_name = None
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
component = instance.components.get(self.component_name)
|
||||
if not self.component_class:
|
||||
component_class = get_component_class(self.name)
|
||||
self.component_class = component_class
|
||||
self.slot_name = component_class.get_component_slot()
|
||||
|
||||
component = instance.components.get(self.slot_name)
|
||||
return component
|
||||
|
||||
def __set__(self, instance, value):
|
||||
|
|
@ -37,13 +44,11 @@ class ComponentProperty:
|
|||
|
||||
def __set_name__(self, owner, name):
|
||||
# Retrieve the class_components set on the direct class only
|
||||
class_components = owner.__dict__.get("_class_components")
|
||||
class_components = owner.__dict__.get("_class_components", [])
|
||||
if not class_components:
|
||||
# Create a new list, including inherited class components
|
||||
class_components = list(getattr(owner, "_class_components", []))
|
||||
setattr(owner, "_class_components", class_components)
|
||||
|
||||
class_components.append((self.component_name, self.values))
|
||||
class_components.append((self.name, self.values))
|
||||
|
||||
|
||||
class ComponentHandler:
|
||||
|
|
@ -57,7 +62,7 @@ class ComponentHandler:
|
|||
self.host = host
|
||||
self._loaded_components = {}
|
||||
|
||||
def add(self, component):
|
||||
def add(self, component: components.Component):
|
||||
"""
|
||||
Method to add a Component to a host.
|
||||
It caches the loaded component and appends its name to the host's component name list.
|
||||
|
|
@ -67,16 +72,19 @@ class ComponentHandler:
|
|||
component (object): The 'loaded' component instance to add.
|
||||
|
||||
"""
|
||||
component_name = component.name
|
||||
self.db_names.append(component_name)
|
||||
self.host.tags.add(component_name, category="components")
|
||||
self._set_component(component)
|
||||
self.db_names.append(component.name)
|
||||
self._add_component_tags(component)
|
||||
for field in component.get_fields():
|
||||
field.at_added(component)
|
||||
|
||||
component.at_added(self.host)
|
||||
self.host.signals.add_object_listeners_and_responders(component)
|
||||
|
||||
def add_default(self, name):
|
||||
"""
|
||||
Method to add a Component initialized to default values on a host.
|
||||
It will retrieve the proper component and instanciate it with 'default_create'.
|
||||
It will retrieve the proper component and instantiate it with 'default_create'.
|
||||
It will cache this new component and add it to its list.
|
||||
It will also call the component's 'at_added' method, passing its host.
|
||||
|
||||
|
|
@ -84,33 +92,11 @@ class ComponentHandler:
|
|||
name (str): The name of the component class to add.
|
||||
|
||||
"""
|
||||
component = components.get_component_class(name)
|
||||
if not component:
|
||||
raise ComponentDoesNotExist(f"Component {name} does not exist.")
|
||||
component_class = components.get_component_class(name)
|
||||
component_instance = component_class.default_create(self.host)
|
||||
self.add(component_instance)
|
||||
|
||||
new_component = component.default_create(self.host)
|
||||
self._set_component(new_component)
|
||||
self.db_names.append(name)
|
||||
self._add_component_tags(new_component)
|
||||
new_component.at_added(self.host)
|
||||
self.host.signals.add_object_listeners_and_responders(new_component)
|
||||
|
||||
def _add_component_tags(self, component):
|
||||
"""
|
||||
Private method that adds the Tags set on a Component via TagFields
|
||||
It will also add the name of the component so objects can be filtered
|
||||
by the components the implement.
|
||||
|
||||
Args:
|
||||
component (object): The component instance that is added.
|
||||
"""
|
||||
self.host.tags.add(component.name, category="components")
|
||||
for tag_field_name in component.tag_field_names:
|
||||
default_tag = type(component).__dict__[tag_field_name]._default
|
||||
if default_tag:
|
||||
setattr(component, tag_field_name, default_tag)
|
||||
|
||||
def remove(self, component):
|
||||
def remove(self, component: components.Component):
|
||||
"""
|
||||
Method to remove a component instance from a host.
|
||||
It removes the component from the cache and listing.
|
||||
|
|
@ -120,18 +106,24 @@ class ComponentHandler:
|
|||
component (object): The component instance to remove.
|
||||
|
||||
"""
|
||||
component_name = component.name
|
||||
if component_name in self._loaded_components:
|
||||
self._remove_component_tags(component)
|
||||
component.at_removed(self.host)
|
||||
self.db_names.remove(component_name)
|
||||
self.host.signals.remove_object_listeners_and_responders(component)
|
||||
del self._loaded_components[component_name]
|
||||
else:
|
||||
name = component.name
|
||||
slot_name = component.get_component_slot()
|
||||
if not self.has(slot_name):
|
||||
message = (
|
||||
f"Cannot remove {component_name} from {self.host.name} as it is not registered."
|
||||
f"Cannot remove {name} from {self.host.name} as it is not registered."
|
||||
)
|
||||
raise ComponentIsNotRegistered(message)
|
||||
raise exceptions.ComponentIsNotRegistered(message)
|
||||
|
||||
for field in component.get_fields():
|
||||
field.at_removed(component)
|
||||
|
||||
component.at_removed(self.host)
|
||||
|
||||
self.host.tags.remove(component.name, category="components")
|
||||
self.host.signals.remove_object_listeners_and_responders(component)
|
||||
|
||||
self.db_names.remove(name)
|
||||
del self._loaded_components[slot_name]
|
||||
|
||||
def remove_by_name(self, name):
|
||||
"""
|
||||
|
|
@ -140,49 +132,25 @@ class ComponentHandler:
|
|||
It will call the component's 'at_removed' method.
|
||||
|
||||
Args:
|
||||
name (str): The name of the component to remove.
|
||||
name (str): The name of the component to remove or its slot.
|
||||
|
||||
"""
|
||||
instance = self.get(name)
|
||||
if not instance:
|
||||
message = f"Cannot remove {name} from {self.host.name} as it is not registered."
|
||||
raise ComponentIsNotRegistered(message)
|
||||
raise exceptions.ComponentIsNotRegistered(message)
|
||||
|
||||
self._remove_component_tags(instance)
|
||||
instance.at_removed(self.host)
|
||||
self.host.signals.remove_object_listeners_and_responders(instance)
|
||||
self.db_names.remove(name)
|
||||
self.remove(instance)
|
||||
|
||||
del self._loaded_components[name]
|
||||
|
||||
def _remove_component_tags(self, component):
|
||||
"""
|
||||
Private method that will remove the Tags set on a Component via TagFields
|
||||
It will also remove the component name tag.
|
||||
|
||||
Args:
|
||||
component (object): The component instance that is removed.
|
||||
"""
|
||||
self.host.tags.remove(component.name, category="components")
|
||||
for tag_field_name in component.tag_field_names:
|
||||
delattr(component, tag_field_name)
|
||||
|
||||
def get(self, name):
|
||||
"""
|
||||
Method to retrieve a cached Component instance by its name.
|
||||
|
||||
Args:
|
||||
name (str): The name of the component to retrieve.
|
||||
|
||||
"""
|
||||
def get(self, name: str) -> components.Component | None:
|
||||
return self._loaded_components.get(name)
|
||||
|
||||
def has(self, name):
|
||||
def has(self, name: str) -> bool:
|
||||
"""
|
||||
Method to check if a component is registered and ready.
|
||||
|
||||
Args:
|
||||
name (str): The name of the component.
|
||||
name (str): The name of the component or the slot.
|
||||
|
||||
"""
|
||||
return name in self._loaded_components
|
||||
|
|
@ -203,26 +171,35 @@ class ComponentHandler:
|
|||
if component:
|
||||
component_instance = component.load(self.host)
|
||||
self._set_component(component_instance)
|
||||
self.host.signals.add_object_listeners_and_responders(component_instance)
|
||||
else:
|
||||
message = (
|
||||
f"Could not initialize runtime component {component_name} of {self.host.name}"
|
||||
)
|
||||
raise ComponentDoesNotExist(message)
|
||||
raise exceptions.ComponentDoesNotExist(message)
|
||||
|
||||
def _set_component(self, component):
|
||||
self._loaded_components[component.name] = component
|
||||
"""
|
||||
Sets the loaded component in this instance.
|
||||
"""
|
||||
slot_name = component.get_component_slot()
|
||||
self._loaded_components[slot_name] = component
|
||||
self.host.signals.add_object_listeners_and_responders(component)
|
||||
|
||||
@property
|
||||
def db_names(self):
|
||||
"""
|
||||
Property shortcut to retrieve the registered component names
|
||||
Property shortcut to retrieve the registered component keys
|
||||
|
||||
Returns:
|
||||
component_names (iterable): The name of each component that is registered
|
||||
|
||||
"""
|
||||
return self.host.attributes.get("component_names")
|
||||
names = self.host.attributes.get("component_names")
|
||||
if names is None:
|
||||
self.host.db.component_names = []
|
||||
names = self.host.db.component_names
|
||||
|
||||
return names
|
||||
|
||||
def __getattr__(self, name):
|
||||
return self.get(name)
|
||||
|
|
@ -236,7 +213,6 @@ class ComponentHolderMixin:
|
|||
All registered components are initialized on the typeclass.
|
||||
They will be of None value if not present in the class components or runtime components.
|
||||
"""
|
||||
|
||||
def at_init(self):
|
||||
"""
|
||||
Method that initializes the ComponentHandler.
|
||||
|
|
@ -261,28 +237,16 @@ class ComponentHolderMixin:
|
|||
components that were set on the typeclass using ComponentProperty.
|
||||
"""
|
||||
super().basetype_setup()
|
||||
component_names = []
|
||||
setattr(self, "_component_handler", ComponentHandler(self))
|
||||
setattr(self, "_signal_handler", signals.SignalsHandler(self))
|
||||
class_components = getattr(self, "_class_components", ())
|
||||
class_components = self._get_class_components()
|
||||
for component_name, values in class_components:
|
||||
component_class = components.get_component_class(component_name)
|
||||
component = component_class.create(self, **values)
|
||||
component_names.append(component_name)
|
||||
self.components._loaded_components[component_name] = component
|
||||
self.signals.add_object_listeners_and_responders(component)
|
||||
self.components.add(component)
|
||||
|
||||
self.db.component_names = component_names
|
||||
self.signals.trigger("at_basetype_setup")
|
||||
|
||||
def basetype_posthook_setup(self):
|
||||
"""
|
||||
Method that add component related tags that were set using ComponentProperty.
|
||||
"""
|
||||
super().basetype_posthook_setup()
|
||||
for component in self.components._loaded_components.values():
|
||||
self.components._add_component_tags(component)
|
||||
|
||||
@property
|
||||
def components(self) -> ComponentHandler:
|
||||
"""
|
||||
|
|
@ -305,10 +269,27 @@ class ComponentHolderMixin:
|
|||
def signals(self) -> signals.SignalsHandler:
|
||||
return getattr(self, "_signal_handler", None)
|
||||
|
||||
def _get_class_components(self):
|
||||
class_components = {}
|
||||
|
||||
class ComponentDoesNotExist(Exception):
|
||||
pass
|
||||
def base_type_iterator():
|
||||
base_stack = [type(self)]
|
||||
while base_stack:
|
||||
_base_type = base_stack.pop()
|
||||
yield _base_type
|
||||
base_stack.extend(_base_type.__bases__)
|
||||
|
||||
for base_type in base_type_iterator():
|
||||
base_class_components = getattr(base_type, "_class_components", ())
|
||||
for cmp_name, cmp_values in base_class_components:
|
||||
cmp_class = get_component_class(cmp_name)
|
||||
cmp_slot = cmp_class.get_component_slot()
|
||||
class_components[cmp_slot] = (cmp_name, cmp_values)
|
||||
|
||||
class ComponentIsNotRegistered(Exception):
|
||||
pass
|
||||
instance_components = getattr(self, "_class_components", ())
|
||||
for cmp_name, cmp_values in instance_components:
|
||||
cmp_class = get_component_class(cmp_name)
|
||||
cmp_slot = cmp_class.get_component_slot()
|
||||
class_components[cmp_slot] = (cmp_name, cmp_values)
|
||||
|
||||
return tuple(class_components.values())
|
||||
|
|
|
|||
20
evennia/contrib/base_systems/components/listing.py
Normal file
20
evennia/contrib/base_systems/components/listing.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
from evennia.contrib.base_systems.components import exceptions
|
||||
|
||||
COMPONENT_LISTING = {}
|
||||
|
||||
|
||||
def get_component_class(name):
|
||||
"""
|
||||
Retrieves a component from the listing using a name
|
||||
Args:
|
||||
name (str): The unique name of the component
|
||||
"""
|
||||
component_class = COMPONENT_LISTING.get(name)
|
||||
if component_class is None:
|
||||
message = (
|
||||
f"Component with name {name} has not been found. "
|
||||
f"Make sure it has been imported before being used."
|
||||
)
|
||||
raise exceptions.ComponentDoesNotExist(message)
|
||||
|
||||
return component_class
|
||||
|
|
@ -179,8 +179,15 @@ class SignalsHandler(object):
|
|||
Args:
|
||||
obj (object): The instance of an object to connect to this handler.
|
||||
"""
|
||||
type_host = type(obj)
|
||||
for att_name, att_obj in type_host.__dict__.items():
|
||||
obj_type = type(obj)
|
||||
for att_name in dir(obj_type):
|
||||
if att_name.startswith("__"):
|
||||
continue
|
||||
|
||||
att_obj = getattr(obj_type, att_name, None)
|
||||
if att_obj is None:
|
||||
continue
|
||||
|
||||
listener_signal_name = getattr(att_obj, "_listener_signal_name", None)
|
||||
if listener_signal_name:
|
||||
callback = getattr(obj, att_name)
|
||||
|
|
@ -198,8 +205,14 @@ class SignalsHandler(object):
|
|||
Args:
|
||||
obj (object): The instance of an object to disconnect from this handler.
|
||||
"""
|
||||
type_host = type(obj)
|
||||
for att_name, att_obj in type_host.__dict__.items():
|
||||
for att_name in dir(obj):
|
||||
if att_name.startswith("__"):
|
||||
continue
|
||||
|
||||
att_obj = getattr(obj, att_name, None)
|
||||
if att_obj is None:
|
||||
continue
|
||||
|
||||
listener_signal_name = getattr(att_obj, "_listener_signal_name", None)
|
||||
if listener_signal_name:
|
||||
callback = getattr(obj, att_name)
|
||||
|
|
|
|||
|
|
@ -17,13 +17,32 @@ from evennia.utils.test_resources import BaseEvenniaTest, EvenniaTest
|
|||
class ComponentTestA(Component):
|
||||
name = "test_a"
|
||||
my_int = DBField(default=1)
|
||||
my_list = DBField(default=[])
|
||||
my_list = DBField(default=[], autocreate=True)
|
||||
|
||||
|
||||
class ShadowedComponentTestA(ComponentTestA):
|
||||
name = "shadowed_test_a"
|
||||
slot = 'ic_a'
|
||||
|
||||
|
||||
class InheritedComponentTestA(ComponentTestA):
|
||||
name = "inherited_test_a"
|
||||
slot = 'ic_a'
|
||||
|
||||
my_other_int = DBField(default=2)
|
||||
|
||||
|
||||
class ReplacementComponentTestA(InheritedComponentTestA):
|
||||
name = "replacement_inherited_test_a"
|
||||
slot = "ic_a"
|
||||
|
||||
replacement_field = DBField(default=6)
|
||||
|
||||
|
||||
class ComponentTestB(Component):
|
||||
name = "test_b"
|
||||
my_int = DBField(default=1)
|
||||
my_list = DBField(default=[])
|
||||
my_list = DBField(default=[], autocreate=True)
|
||||
default_tag = TagField(default="initial_value")
|
||||
single_tag = TagField(enforce_single=True)
|
||||
multiple_tags = TagField()
|
||||
|
|
@ -33,13 +52,31 @@ class ComponentTestB(Component):
|
|||
class RuntimeComponentTestC(Component):
|
||||
name = "test_c"
|
||||
my_int = DBField(default=6)
|
||||
my_dict = DBField(default={})
|
||||
my_dict = DBField(default={}, autocreate=True)
|
||||
added_tag = TagField(default="added_value")
|
||||
|
||||
|
||||
class CharacterWithComponents(ComponentHolderMixin, DefaultCharacter):
|
||||
class ComponentTestD(Component):
|
||||
name = "test_d"
|
||||
|
||||
mixed_in = DBField(default=8)
|
||||
|
||||
|
||||
class ShadowedCharacterMixin:
|
||||
ic_a = ComponentProperty("shadowed_test_a")
|
||||
|
||||
|
||||
class CharacterMixinWithComponents:
|
||||
ic_a = ComponentProperty("inherited_test_a", my_other_int=33)
|
||||
test_d = ComponentProperty('test_d')
|
||||
|
||||
|
||||
class CharacterWithComponents(
|
||||
ComponentHolderMixin, ShadowedCharacterMixin, CharacterMixinWithComponents, DefaultCharacter
|
||||
):
|
||||
test_a = ComponentProperty("test_a")
|
||||
test_b = ComponentProperty("test_b", my_int=3, my_list=[1, 2, 3])
|
||||
ic_a = ComponentProperty("inherited_test_a", my_other_int=4)
|
||||
|
||||
|
||||
class InheritedTCWithComponents(CharacterWithComponents):
|
||||
|
|
@ -50,53 +87,69 @@ class TestComponents(EvenniaTest):
|
|||
character_typeclass = CharacterWithComponents
|
||||
|
||||
def test_character_has_class_components(self):
|
||||
assert self.char1.test_a
|
||||
assert self.char1.test_b
|
||||
self.assertTrue(self.char1.test_a)
|
||||
self.assertTrue(self.char1.test_b)
|
||||
|
||||
def test_inherited_typeclass_does_not_include_child_class_components(self):
|
||||
char_with_c = create.create_object(
|
||||
InheritedTCWithComponents, key="char_with_c", location=self.room1, home=self.room1
|
||||
)
|
||||
assert self.char1.test_a
|
||||
assert not self.char1.cmp.get("test_c")
|
||||
assert char_with_c.test_c
|
||||
self.assertTrue(self.char1.test_a)
|
||||
self.assertFalse(self.char1.cmp.get("test_c"))
|
||||
self.assertTrue(char_with_c.test_c)
|
||||
|
||||
def test_character_instances_components_properly(self):
|
||||
assert isinstance(self.char1.test_a, ComponentTestA)
|
||||
assert isinstance(self.char1.test_b, ComponentTestB)
|
||||
self.assertIsInstance(self.char1.test_a, ComponentTestA)
|
||||
self.assertIsInstance(self.char1.test_b, ComponentTestB)
|
||||
|
||||
def test_character_assigns_default_value(self):
|
||||
assert self.char1.test_a.my_int == 1
|
||||
assert self.char1.test_a.my_list == []
|
||||
self.assertEquals(self.char1.test_a.my_int, 1)
|
||||
self.assertEquals(self.char1.test_a.my_list, [])
|
||||
|
||||
def test_character_assigns_default_provided_values(self):
|
||||
assert self.char1.test_b.my_int == 3
|
||||
assert self.char1.test_b.my_list == [1, 2, 3]
|
||||
self.assertEquals(self.char1.test_b.my_int, 3)
|
||||
self.assertEquals(self.char1.test_b.my_list, [1, 2, 3])
|
||||
|
||||
def test_character_has_autocreated_values(self):
|
||||
att_name = "test_b::my_list"
|
||||
self.assertEquals(self.char1.attributes.get(att_name), [1, 2, 3])
|
||||
|
||||
def test_component_inheritance_properly_overrides_slots(self):
|
||||
self.assertEquals(self.char1.ic_a.name, "inherited_test_a")
|
||||
component_names = set(c[0] for c in self.char1._get_class_components())
|
||||
self.assertNotIn("shadowed_test_a", component_names)
|
||||
|
||||
def test_component_inheritance_assigns_proper_values(self):
|
||||
self.assertEquals(self.char1.ic_a.my_int, 1)
|
||||
self.assertEquals(self.char1.ic_a.my_other_int, 4)
|
||||
|
||||
def test_host_mixins_assigns_components(self):
|
||||
self.assertEquals(self.char1.test_d.mixed_in, 8)
|
||||
|
||||
def test_character_can_register_runtime_component(self):
|
||||
rct = RuntimeComponentTestC.create(self.char1)
|
||||
self.char1.components.add(rct)
|
||||
test_c = self.char1.components.get("test_c")
|
||||
|
||||
assert test_c
|
||||
assert test_c.my_int == 6
|
||||
assert test_c.my_dict == {}
|
||||
self.assertTrue(test_c)
|
||||
self.assertEquals(test_c.my_int, 6)
|
||||
self.assertEquals(test_c.my_dict, {})
|
||||
|
||||
def test_handler_can_add_default_component(self):
|
||||
self.char1.components.add_default("test_c")
|
||||
test_c = self.char1.components.get("test_c")
|
||||
|
||||
assert test_c
|
||||
assert test_c.my_int == 6
|
||||
self.assertTrue(test_c)
|
||||
self.assertEquals(test_c.my_int, 6)
|
||||
|
||||
def test_handler_has_returns_true_for_any_components(self):
|
||||
rct = RuntimeComponentTestC.create(self.char1)
|
||||
handler = self.char1.components
|
||||
handler.add(rct)
|
||||
|
||||
assert handler.has("test_a")
|
||||
assert handler.has("test_b")
|
||||
assert handler.has("test_c")
|
||||
self.assertTrue(handler.has("test_a"))
|
||||
self.assertTrue(handler.has("test_b"))
|
||||
self.assertTrue(handler.has("test_c"))
|
||||
|
||||
def test_can_remove_component(self):
|
||||
rct = RuntimeComponentTestC.create(self.char1)
|
||||
|
|
@ -104,9 +157,9 @@ class TestComponents(EvenniaTest):
|
|||
handler.add(rct)
|
||||
handler.remove(rct)
|
||||
|
||||
assert handler.has("test_a")
|
||||
assert handler.has("test_b")
|
||||
assert not handler.has("test_c")
|
||||
self.assertTrue(handler.has("test_a"))
|
||||
self.assertTrue(handler.has("test_b"))
|
||||
self.assertFalse(handler.has("test_c"))
|
||||
|
||||
def test_can_remove_component_by_name(self):
|
||||
rct = RuntimeComponentTestC.create(self.char1)
|
||||
|
|
@ -114,9 +167,9 @@ class TestComponents(EvenniaTest):
|
|||
handler.add(rct)
|
||||
handler.remove_by_name("test_c")
|
||||
|
||||
assert handler.has("test_a")
|
||||
assert handler.has("test_b")
|
||||
assert not handler.has("test_c")
|
||||
self.assertTrue(handler.has("test_a"))
|
||||
self.assertTrue(handler.has("test_b"))
|
||||
self.assertFalse(handler.has("test_c"))
|
||||
|
||||
def test_cannot_replace_component(self):
|
||||
with self.assertRaises(Exception):
|
||||
|
|
@ -127,76 +180,76 @@ class TestComponents(EvenniaTest):
|
|||
handler = self.char1.components
|
||||
handler.add(rct)
|
||||
|
||||
assert handler.get("test_c") is rct
|
||||
self.assertIs(handler.get("test_c"), rct)
|
||||
|
||||
def test_can_access_component_regular_get(self):
|
||||
assert self.char1.cmp.test_a is self.char1.components.get("test_a")
|
||||
self.assertIs(self.char1.cmp.test_a, self.char1.components.get("test_a"))
|
||||
|
||||
def test_returns_none_with_regular_get_when_no_attribute(self):
|
||||
assert self.char1.cmp.does_not_exist is None
|
||||
self.assertIs(self.char1.cmp.does_not_exist, None)
|
||||
|
||||
def test_host_has_class_component_tags(self):
|
||||
assert self.char1.tags.has(key="test_a", category="components")
|
||||
assert self.char1.tags.has(key="test_b", category="components")
|
||||
assert self.char1.tags.has(key="initial_value", category="test_b::default_tag")
|
||||
assert self.char1.test_b.default_tag == "initial_value"
|
||||
assert not self.char1.tags.has(key="test_c", category="components")
|
||||
assert not self.char1.tags.has(category="test_b::single_tag")
|
||||
assert not self.char1.tags.has(category="test_b::multiple_tags")
|
||||
self.assertTrue(self.char1.tags.has(key="test_a", category="components"))
|
||||
self.assertTrue(self.char1.tags.has(key="test_b", category="components"))
|
||||
self.assertTrue(self.char1.tags.has(key="initial_value", category="test_b::default_tag"))
|
||||
self.assertTrue(self.char1.test_b.default_tag == "initial_value")
|
||||
self.assertFalse(self.char1.tags.has(key="test_c", category="components"))
|
||||
self.assertFalse(self.char1.tags.has(category="test_b::single_tag"))
|
||||
self.assertFalse(self.char1.tags.has(category="test_b::multiple_tags"))
|
||||
|
||||
def test_host_has_added_component_tags(self):
|
||||
rct = RuntimeComponentTestC.create(self.char1)
|
||||
self.char1.components.add(rct)
|
||||
test_c = self.char1.components.get("test_c")
|
||||
|
||||
assert self.char1.tags.has(key="test_c", category="components")
|
||||
assert self.char1.tags.has(key="added_value", category="test_c::added_tag")
|
||||
assert test_c.added_tag == "added_value"
|
||||
self.assertTrue(self.char1.tags.has(key="test_c", category="components"))
|
||||
self.assertTrue(self.char1.tags.has(key="added_value", category="test_c::added_tag"))
|
||||
self.assertEquals(test_c.added_tag, "added_value")
|
||||
|
||||
def test_host_has_added_default_component_tags(self):
|
||||
self.char1.components.add_default("test_c")
|
||||
test_c = self.char1.components.get("test_c")
|
||||
|
||||
assert self.char1.tags.has(key="test_c", category="components")
|
||||
assert self.char1.tags.has(key="added_value", category="test_c::added_tag")
|
||||
assert test_c.added_tag == "added_value"
|
||||
self.assertTrue(self.char1.tags.has(key="test_c", category="components"))
|
||||
self.assertTrue(self.char1.tags.has(key="added_value", category="test_c::added_tag"))
|
||||
self.assertEquals(test_c.added_tag, "added_value")
|
||||
|
||||
def test_host_remove_component_tags(self):
|
||||
rct = RuntimeComponentTestC.create(self.char1)
|
||||
handler = self.char1.components
|
||||
handler.add(rct)
|
||||
assert self.char1.tags.has(key="test_c", category="components")
|
||||
self.assertTrue(self.char1.tags.has(key="test_c", category="components"))
|
||||
handler.remove(rct)
|
||||
|
||||
assert not self.char1.tags.has(key="test_c", category="components")
|
||||
assert not self.char1.tags.has(key="added_value", category="test_c::added_tag")
|
||||
self.assertFalse(self.char1.tags.has(key="test_c", category="components"))
|
||||
self.assertFalse(self.char1.tags.has(key="added_value", category="test_c::added_tag"))
|
||||
|
||||
def test_host_remove_by_name_component_tags(self):
|
||||
rct = RuntimeComponentTestC.create(self.char1)
|
||||
handler = self.char1.components
|
||||
handler.add(rct)
|
||||
assert self.char1.tags.has(key="test_c", category="components")
|
||||
self.assertTrue(self.char1.tags.has(key="test_c", category="components"))
|
||||
handler.remove_by_name("test_c")
|
||||
|
||||
assert not self.char1.tags.has(key="test_c", category="components")
|
||||
assert not self.char1.tags.has(key="added_value", category="test_c::added_tag")
|
||||
self.assertFalse(self.char1.tags.has(key="test_c", category="components"))
|
||||
self.assertFalse(self.char1.tags.has(key="added_value", category="test_c::added_tag"))
|
||||
|
||||
def test_component_tags_only_hold_one_value_when_enforce_single(self):
|
||||
test_b = self.char1.components.get("test_b")
|
||||
test_b.single_tag = "first_value"
|
||||
test_b.single_tag = "second value"
|
||||
|
||||
assert self.char1.tags.has(key="second value", category="test_b::single_tag")
|
||||
assert test_b.single_tag == "second value"
|
||||
assert not self.char1.tags.has(key="first_value", category="test_b::single_tag")
|
||||
self.assertTrue(self.char1.tags.has(key="second value", category="test_b::single_tag"))
|
||||
self.assertEquals(test_b.single_tag, "second value")
|
||||
self.assertFalse(self.char1.tags.has(key="first_value", category="test_b::single_tag"))
|
||||
|
||||
def test_component_tags_default_value_is_overridden_when_enforce_single(self):
|
||||
test_b = self.char1.components.get("test_b")
|
||||
test_b.default_single_tag = "second value"
|
||||
|
||||
assert self.char1.tags.has(key="second value", category="test_b::default_single_tag")
|
||||
assert test_b.default_single_tag == "second value"
|
||||
assert not self.char1.tags.has(key="first_value", category="test_b::default_single_tag")
|
||||
self.assertTrue(self.char1.tags.has(key="second value", category="test_b::default_single_tag"))
|
||||
self.assertTrue(test_b.default_single_tag == "second value")
|
||||
self.assertFalse(self.char1.tags.has(key="first_value", category="test_b::default_single_tag"))
|
||||
|
||||
def test_component_tags_support_multiple_values_by_default(self):
|
||||
test_b = self.char1.components.get("test_b")
|
||||
|
|
@ -204,12 +257,20 @@ class TestComponents(EvenniaTest):
|
|||
test_b.multiple_tags = "second value"
|
||||
test_b.multiple_tags = "third value"
|
||||
|
||||
assert all(
|
||||
self.assertTrue(all(
|
||||
val in test_b.multiple_tags for val in ("first value", "second value", "third value")
|
||||
)
|
||||
assert self.char1.tags.has(key="first value", category="test_b::multiple_tags")
|
||||
assert self.char1.tags.has(key="second value", category="test_b::multiple_tags")
|
||||
assert self.char1.tags.has(key="third value", category="test_b::multiple_tags")
|
||||
))
|
||||
self.assertTrue(self.char1.tags.has(key="first value", category="test_b::multiple_tags"))
|
||||
self.assertTrue(self.char1.tags.has(key="second value", category="test_b::multiple_tags"))
|
||||
self.assertTrue(self.char1.tags.has(key="third value", category="test_b::multiple_tags"))
|
||||
|
||||
def test_mutables_are_not_shared_when_autocreate(self):
|
||||
self.char1.test_a.my_list.append(1)
|
||||
self.assertNotEquals(self.char1.test_a.my_list, self.char2.test_a.my_list)
|
||||
|
||||
def test_replacing_class_component_slot_with_runtime_component(self):
|
||||
self.char1.components.add_default("replacement_inherited_test_a")
|
||||
self.assertEquals(self.char1.ic_a.replacement_field, 6)
|
||||
|
||||
|
||||
class CharWithSignal(ComponentHolderMixin, DefaultCharacter):
|
||||
|
|
@ -265,14 +326,14 @@ class TestComponentSignals(BaseEvenniaTest):
|
|||
def test_host_can_register_as_listener(self):
|
||||
self.char1.signals.trigger("my_signal")
|
||||
|
||||
assert self.char1.my_signal_is_called
|
||||
assert not getattr(self.char1, "my_other_signal_is_called", None)
|
||||
self.assertTrue(self.char1.my_signal_is_called)
|
||||
self.assertFalse(getattr(self.char1, "my_other_signal_is_called", None))
|
||||
|
||||
def test_host_can_register_as_responder(self):
|
||||
responses = self.char1.signals.query("my_response")
|
||||
|
||||
assert 1 in responses
|
||||
assert 2 not in responses
|
||||
self.assertIn(1, responses)
|
||||
self.assertNotIn(2, responses)
|
||||
|
||||
def test_component_can_register_as_listener(self):
|
||||
char = self.char1
|
||||
|
|
@ -280,16 +341,16 @@ class TestComponentSignals(BaseEvenniaTest):
|
|||
char.signals.trigger("my_signal")
|
||||
|
||||
component = char.cmp.test_signal_a
|
||||
assert component.my_signal_is_called
|
||||
assert not getattr(component, "my_other_signal_is_called", None)
|
||||
self.assertTrue(component.my_signal_is_called)
|
||||
self.assertFalse(getattr(component, "my_other_signal_is_called", None))
|
||||
|
||||
def test_component_can_register_as_responder(self):
|
||||
char = self.char1
|
||||
char.components.add(ComponentWithSignal.create(char))
|
||||
responses = char.signals.query("my_response")
|
||||
|
||||
assert 1 in responses
|
||||
assert 2 not in responses
|
||||
self.assertIn(1, responses)
|
||||
self.assertNotIn(2, responses)
|
||||
|
||||
def test_signals_can_add_listener(self):
|
||||
result = []
|
||||
|
|
@ -300,7 +361,7 @@ class TestComponentSignals(BaseEvenniaTest):
|
|||
self.char1.signals.add_listener("my_fake_signal", my_fake_listener)
|
||||
self.char1.signals.trigger("my_fake_signal")
|
||||
|
||||
assert result
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_signals_can_add_responder(self):
|
||||
def my_fake_responder():
|
||||
|
|
@ -309,7 +370,7 @@ class TestComponentSignals(BaseEvenniaTest):
|
|||
self.char1.signals.add_responder("my_fake_response", my_fake_responder)
|
||||
responses = self.char1.signals.query("my_fake_response")
|
||||
|
||||
assert 1 in responses
|
||||
self.assertIn(1, responses)
|
||||
|
||||
def test_signals_can_remove_listener(self):
|
||||
result = []
|
||||
|
|
@ -321,7 +382,7 @@ class TestComponentSignals(BaseEvenniaTest):
|
|||
self.char1.signals.remove_listener("my_fake_signal", my_fake_listener)
|
||||
self.char1.signals.trigger("my_fake_signal")
|
||||
|
||||
assert not result
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_signals_can_remove_responder(self):
|
||||
def my_fake_responder():
|
||||
|
|
@ -331,7 +392,7 @@ class TestComponentSignals(BaseEvenniaTest):
|
|||
self.char1.signals.remove_responder("my_fake_response", my_fake_responder)
|
||||
responses = self.char1.signals.query("my_fake_response")
|
||||
|
||||
assert not responses
|
||||
self.assertFalse(responses)
|
||||
|
||||
def test_signals_can_trigger_with_args(self):
|
||||
result = []
|
||||
|
|
@ -342,7 +403,7 @@ class TestComponentSignals(BaseEvenniaTest):
|
|||
self.char1.signals.add_listener("my_fake_signal", my_fake_listener)
|
||||
self.char1.signals.trigger("my_fake_signal", 1, kwarg1=2)
|
||||
|
||||
assert (1, 2) in result
|
||||
self.assertIn((1, 2), result)
|
||||
|
||||
def test_signals_can_query_with_args(self):
|
||||
def my_fake_responder(arg1, kwarg1):
|
||||
|
|
@ -351,7 +412,7 @@ class TestComponentSignals(BaseEvenniaTest):
|
|||
self.char1.signals.add_responder("my_fake_response", my_fake_responder)
|
||||
responses = self.char1.signals.query("my_fake_response", 1, kwarg1=2)
|
||||
|
||||
assert (1, 2) in responses
|
||||
self.assertIn((1, 2), responses)
|
||||
|
||||
def test_signals_trigger_does_not_fail_without_listener(self):
|
||||
self.char1.signals.trigger("some_unknown_signal")
|
||||
|
|
@ -366,7 +427,7 @@ class TestComponentSignals(BaseEvenniaTest):
|
|||
self.char1.signals.add_responder("my_fake_response", my_fake_responder)
|
||||
responses = self.char1.signals.query("my_fake_response", 1, kwarg1=2)
|
||||
|
||||
assert (1, 2) in responses
|
||||
self.assertIn((1, 2), responses)
|
||||
|
||||
def test_signals_can_add_object_listeners_and_responders(self):
|
||||
result = []
|
||||
|
|
@ -379,7 +440,7 @@ class TestComponentSignals(BaseEvenniaTest):
|
|||
self.char1.signals.add_object_listeners_and_responders(FakeObj())
|
||||
self.char1.signals.trigger("my_signal")
|
||||
|
||||
assert result
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_signals_can_remove_object_listeners_and_responders(self):
|
||||
result = []
|
||||
|
|
@ -394,14 +455,14 @@ class TestComponentSignals(BaseEvenniaTest):
|
|||
self.char1.signals.remove_object_listeners_and_responders(obj)
|
||||
self.char1.signals.trigger("my_signal")
|
||||
|
||||
assert not result
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_component_handler_signals_connected_when_adding_default_component(self):
|
||||
char = self.char1
|
||||
char.components.add_default("test_signal_a")
|
||||
responses = char.signals.query("my_component_response")
|
||||
|
||||
assert 3 in responses
|
||||
self.assertIn(3, responses)
|
||||
|
||||
def test_component_handler_signals_disconnected_when_removing_component(self):
|
||||
char = self.char1
|
||||
|
|
@ -410,7 +471,7 @@ class TestComponentSignals(BaseEvenniaTest):
|
|||
char.components.remove(comp)
|
||||
responses = char.signals.query("my_component_response")
|
||||
|
||||
assert not responses
|
||||
self.assertFalse(responses)
|
||||
|
||||
def test_component_handler_signals_disconnected_when_removing_component_by_name(self):
|
||||
char = self.char1
|
||||
|
|
@ -418,4 +479,4 @@ class TestComponentSignals(BaseEvenniaTest):
|
|||
char.components.remove_by_name("test_signal_a")
|
||||
responses = char.signals.query("my_component_response")
|
||||
|
||||
assert not responses
|
||||
self.assertFalse(responses)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue