Refactor Component registering with cherry-picked additions
This commit is contained in:
parent
6e096fa3cb
commit
217bd711e7
6 changed files with 194 additions and 174 deletions
|
|
@ -7,23 +7,16 @@ This helps writing isolated code and reusing it over multiple objects.
|
||||||
|
|
||||||
See the docs for more information.
|
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.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 (
|
from evennia.contrib.base_systems.components.holder import (
|
||||||
ComponentHolderMixin,
|
ComponentHolderMixin,
|
||||||
ComponentProperty,
|
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,32 @@ Components - ChrisLR 2022
|
||||||
|
|
||||||
This file contains the base class to inherit for creating new components.
|
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):
|
||||||
|
@classmethod
|
||||||
|
def __new__(cls, *args):
|
||||||
|
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.
|
This is the base class for components.
|
||||||
Any component must inherit from this class to be considered for usage.
|
Any component must inherit from this class to be considered for usage.
|
||||||
|
|
@ -14,10 +36,17 @@ class Component:
|
||||||
Each Component must supply the name, it is used as a slot name but also part of the attribute key.
|
Each Component must supply the name, it is used as a slot name but also part of the attribute key.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
__slots__ = ('host',)
|
||||||
|
|
||||||
name = ""
|
name = ""
|
||||||
|
slot = None
|
||||||
|
|
||||||
|
cmd_set: CmdSet = None
|
||||||
|
|
||||||
|
_fields = {}
|
||||||
|
|
||||||
def __init__(self, host=None):
|
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
|
self.host = host
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -61,8 +90,8 @@ class Component:
|
||||||
"""
|
"""
|
||||||
This deletes all component attributes from the host's db
|
This deletes all component attributes from the host's db
|
||||||
"""
|
"""
|
||||||
for attribute in self._all_db_field_names:
|
for name in self._fields.keys():
|
||||||
delattr(self, attribute)
|
delattr(self, name)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, host):
|
def load(cls, host):
|
||||||
|
|
@ -88,12 +117,11 @@ class Component:
|
||||||
host (object): The host typeclass instance
|
host (object): The host typeclass instance
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if self.host and self.host != host:
|
||||||
|
raise exceptions.InvalidComponentError("Components must not register twice!")
|
||||||
|
|
||||||
if self.host:
|
if self.cmd_set:
|
||||||
if self.host == host:
|
self.host.cmdset.add(self.cmd_set)
|
||||||
return
|
|
||||||
else:
|
|
||||||
raise ComponentRegisterError("Components must not register twice!")
|
|
||||||
|
|
||||||
self.host = host
|
self.host = host
|
||||||
|
|
||||||
|
|
@ -106,7 +134,11 @@ class Component:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if host != self.host:
|
if host != self.host:
|
||||||
raise ComponentRegisterError("Component attempted to remove from the wrong host.")
|
raise ValueError("Component attempted to remove from the wrong host.")
|
||||||
|
|
||||||
|
if self.cmd_set:
|
||||||
|
self.host.cmdset.remove(self.cmd_set)
|
||||||
|
|
||||||
self.host = None
|
self.host = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -131,25 +163,10 @@ class Component:
|
||||||
"""
|
"""
|
||||||
return self.host.nattributes
|
return self.host.nattributes
|
||||||
|
|
||||||
@property
|
@classmethod
|
||||||
def _all_db_field_names(self):
|
def add_field(cls, name, field):
|
||||||
return itertools.chain(self.db_field_names, self.ndb_field_names)
|
cls._fields[name] = field
|
||||||
|
|
||||||
@property
|
@classmethod
|
||||||
def db_field_names(self):
|
def get_fields(cls):
|
||||||
db_fields = getattr(self, "_db_fields", {})
|
return tuple(cls._fields.values())
|
||||||
return db_fields.keys()
|
|
||||||
|
|
||||||
@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
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,13 @@ Components - ChrisLR 2022
|
||||||
|
|
||||||
This file contains the Descriptors used to set Fields in Components
|
This file contains the Descriptors used to set Fields in Components
|
||||||
"""
|
"""
|
||||||
|
import typing
|
||||||
|
|
||||||
from evennia.typeclasses.attributes import AttributeProperty, NAttributeProperty
|
from evennia.typeclasses.attributes import AttributeProperty, NAttributeProperty
|
||||||
|
|
||||||
|
if typing.TYPE_CHECKING:
|
||||||
|
from evennia.contrib.base_systems.components import Component
|
||||||
|
|
||||||
|
|
||||||
class DBField(AttributeProperty):
|
class DBField(AttributeProperty):
|
||||||
"""
|
"""
|
||||||
|
|
@ -13,7 +18,10 @@ class DBField(AttributeProperty):
|
||||||
It uses AttributeProperty under the hood but prefixes the key with the component name.
|
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.
|
Called when descriptor is first assigned to the class.
|
||||||
|
|
||||||
|
|
@ -21,13 +29,15 @@ class DBField(AttributeProperty):
|
||||||
owner (object): The component classF on which this is set
|
owner (object): The component classF on which this is set
|
||||||
name (str): The name that was used to set the DBField.
|
name (str): The name that was used to set the DBField.
|
||||||
"""
|
"""
|
||||||
key = f"{owner.name}::{name}"
|
self._key = f"{owner.slot or owner.name}::{name}"
|
||||||
self._key = key
|
owner.add_field(name, self)
|
||||||
db_fields = getattr(owner, "_db_fields", None)
|
|
||||||
if db_fields is None:
|
def at_added(self, instance):
|
||||||
db_fields = {}
|
if self._autocreate:
|
||||||
setattr(owner, "_db_fields", db_fields)
|
self.__set__(instance, self._default)
|
||||||
db_fields[name] = self
|
|
||||||
|
def at_removed(self, instance):
|
||||||
|
self.__delete__(instance)
|
||||||
|
|
||||||
|
|
||||||
class NDBField(NAttributeProperty):
|
class NDBField(NAttributeProperty):
|
||||||
|
|
@ -37,7 +47,7 @@ class NDBField(NAttributeProperty):
|
||||||
It uses NAttributeProperty under the hood but prefixes the key with the component name.
|
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.
|
Called when descriptor is first assigned to the class.
|
||||||
|
|
||||||
|
|
@ -45,13 +55,15 @@ class NDBField(NAttributeProperty):
|
||||||
owner (object): The component class on which this is set
|
owner (object): The component class on which this is set
|
||||||
name (str): The name that was used to set the DBField.
|
name (str): The name that was used to set the DBField.
|
||||||
"""
|
"""
|
||||||
key = f"{owner.name}::{name}"
|
self._key = f"{owner.slot or owner.name}::{name}"
|
||||||
self._key = key
|
owner.add_field(name, self)
|
||||||
ndb_fields = getattr(owner, "_ndb_fields", None)
|
|
||||||
if ndb_fields is None:
|
def at_added(self, instance):
|
||||||
ndb_fields = {}
|
if self._autocreate:
|
||||||
setattr(owner, "_ndb_fields", ndb_fields)
|
self.__set__(instance, self._default)
|
||||||
ndb_fields[name] = self
|
|
||||||
|
def at_removed(self, instance):
|
||||||
|
self.__delete__(instance)
|
||||||
|
|
||||||
|
|
||||||
class TagField:
|
class TagField:
|
||||||
|
|
@ -70,17 +82,13 @@ class TagField:
|
||||||
self._default = default
|
self._default = default
|
||||||
self._enforce_single = enforce_single
|
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.
|
Called when TagField is first assigned to the class.
|
||||||
It is called with the component class and the name of the field.
|
It is called with the component class and the name of the field.
|
||||||
"""
|
"""
|
||||||
self._category_key = f"{owner.name}::{name}"
|
self._category_key = f"{owner.slot or owner.name}::{name}"
|
||||||
tag_fields = getattr(owner, "_tag_fields", None)
|
owner.add_field(name, self)
|
||||||
if tag_fields is None:
|
|
||||||
tag_fields = {}
|
|
||||||
setattr(owner, "_tag_fields", tag_fields)
|
|
||||||
tag_fields[name] = self
|
|
||||||
|
|
||||||
def __get__(self, instance, owner):
|
def __get__(self, instance, owner):
|
||||||
"""
|
"""
|
||||||
|
|
@ -114,3 +122,10 @@ class TagField:
|
||||||
It is called with the component instance.
|
It is called with the component instance.
|
||||||
"""
|
"""
|
||||||
instance.host.tags.clear(category=self._category_key)
|
instance.host.tags.clear(category=self._category_key)
|
||||||
|
|
||||||
|
def at_added(self, instance):
|
||||||
|
if self._default:
|
||||||
|
self.__set__(instance, self._default)
|
||||||
|
|
||||||
|
def at_removed(self, instance):
|
||||||
|
self.__delete__(instance)
|
||||||
|
|
|
||||||
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 import components
|
||||||
from evennia.contrib.base_systems.components import signals
|
from evennia.contrib.base_systems.components import signals, exceptions
|
||||||
|
|
||||||
|
|
||||||
class ComponentProperty:
|
class ComponentProperty:
|
||||||
|
|
@ -17,19 +17,19 @@ class ComponentProperty:
|
||||||
Defaults can be overridden for this typeclass by passing kwargs
|
Defaults can be overridden for this typeclass by passing kwargs
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, component_name, **kwargs):
|
def __init__(self, name, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initializes the descriptor
|
Initializes the descriptor
|
||||||
|
|
||||||
Args:
|
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
|
**kwargs (any): Key=Values overriding default values of the component
|
||||||
"""
|
"""
|
||||||
self.component_name = component_name
|
self.name = name
|
||||||
self.values = kwargs
|
self.values = kwargs
|
||||||
|
|
||||||
def __get__(self, instance, owner):
|
def __get__(self, instance, owner):
|
||||||
component = instance.components.get(self.component_name)
|
component = instance.components.get(self.name)
|
||||||
return component
|
return component
|
||||||
|
|
||||||
def __set__(self, instance, value):
|
def __set__(self, instance, value):
|
||||||
|
|
@ -37,13 +37,11 @@ class ComponentProperty:
|
||||||
|
|
||||||
def __set_name__(self, owner, name):
|
def __set_name__(self, owner, name):
|
||||||
# Retrieve the class_components set on the direct class only
|
# 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:
|
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)
|
setattr(owner, "_class_components", class_components)
|
||||||
|
|
||||||
class_components.append((self.component_name, self.values))
|
class_components.append((self.name, self.values))
|
||||||
|
|
||||||
|
|
||||||
class ComponentHandler:
|
class ComponentHandler:
|
||||||
|
|
@ -57,7 +55,7 @@ class ComponentHandler:
|
||||||
self.host = host
|
self.host = host
|
||||||
self._loaded_components = {}
|
self._loaded_components = {}
|
||||||
|
|
||||||
def add(self, component):
|
def add(self, component: components.Component):
|
||||||
"""
|
"""
|
||||||
Method to add a Component to a host.
|
Method to add a Component to a host.
|
||||||
It caches the loaded component and appends its name to the host's component name list.
|
It caches the loaded component and appends its name to the host's component name list.
|
||||||
|
|
@ -67,16 +65,19 @@ class ComponentHandler:
|
||||||
component (object): The 'loaded' component instance to add.
|
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._set_component(component)
|
||||||
self.db_names.append(component.name)
|
for field in component.get_fields():
|
||||||
self._add_component_tags(component)
|
field.at_added(self.host)
|
||||||
|
|
||||||
component.at_added(self.host)
|
component.at_added(self.host)
|
||||||
self.host.signals.add_object_listeners_and_responders(component)
|
|
||||||
|
|
||||||
def add_default(self, name):
|
def add_default(self, name):
|
||||||
"""
|
"""
|
||||||
Method to add a Component initialized to default values on a host.
|
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 cache this new component and add it to its list.
|
||||||
It will also call the component's 'at_added' method, passing its host.
|
It will also call the component's 'at_added' method, passing its host.
|
||||||
|
|
||||||
|
|
@ -84,33 +85,11 @@ class ComponentHandler:
|
||||||
name (str): The name of the component class to add.
|
name (str): The name of the component class to add.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
component = components.get_component_class(name)
|
component_class = components.get_component_class(name)
|
||||||
if not component:
|
component_instance = component_class.default_create(self.host)
|
||||||
raise ComponentDoesNotExist(f"Component {name} does not exist.")
|
self.add(component_instance)
|
||||||
|
|
||||||
new_component = component.default_create(self.host)
|
def remove(self, component: components.Component):
|
||||||
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):
|
|
||||||
"""
|
"""
|
||||||
Method to remove a component instance from a host.
|
Method to remove a component instance from a host.
|
||||||
It removes the component from the cache and listing.
|
It removes the component from the cache and listing.
|
||||||
|
|
@ -120,18 +99,26 @@ class ComponentHandler:
|
||||||
component (object): The component instance to remove.
|
component (object): The component instance to remove.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
component_name = component.name
|
name = component.name
|
||||||
if component_name in self._loaded_components:
|
slot_name = component.slot or name
|
||||||
self._remove_component_tags(component)
|
if not self.has(slot_name):
|
||||||
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:
|
|
||||||
message = (
|
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(self.host)
|
||||||
|
|
||||||
|
component.at_removed(self.host)
|
||||||
|
if component.cmd_set:
|
||||||
|
self.host.cmdset.remove(component.cmd_set)
|
||||||
|
|
||||||
|
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):
|
def remove_by_name(self, name):
|
||||||
"""
|
"""
|
||||||
|
|
@ -140,49 +127,25 @@ class ComponentHandler:
|
||||||
It will call the component's 'at_removed' method.
|
It will call the component's 'at_removed' method.
|
||||||
|
|
||||||
Args:
|
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)
|
instance = self.get(name)
|
||||||
if not instance:
|
if not instance:
|
||||||
message = f"Cannot remove {name} from {self.host.name} as it is not registered."
|
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)
|
self.remove(instance)
|
||||||
instance.at_removed(self.host)
|
|
||||||
self.host.signals.remove_object_listeners_and_responders(instance)
|
|
||||||
self.db_names.remove(name)
|
|
||||||
|
|
||||||
del self._loaded_components[name]
|
def get(self, name: str) -> components.Component | None:
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return self._loaded_components.get(name)
|
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.
|
Method to check if a component is registered and ready.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name (str): The name of the component.
|
name (str): The name of the component or the slot.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return name in self._loaded_components
|
return name in self._loaded_components
|
||||||
|
|
@ -203,26 +166,35 @@ class ComponentHandler:
|
||||||
if component:
|
if component:
|
||||||
component_instance = component.load(self.host)
|
component_instance = component.load(self.host)
|
||||||
self._set_component(component_instance)
|
self._set_component(component_instance)
|
||||||
self.host.signals.add_object_listeners_and_responders(component_instance)
|
|
||||||
else:
|
else:
|
||||||
message = (
|
message = (
|
||||||
f"Could not initialize runtime component {component_name} of {self.host.name}"
|
f"Could not initialize runtime component {component_name} of {self.host.name}"
|
||||||
)
|
)
|
||||||
raise ComponentDoesNotExist(message)
|
raise exceptions.ComponentDoesNotExist(message)
|
||||||
|
|
||||||
def _set_component(self, component):
|
def _set_component(self, component):
|
||||||
self._loaded_components[component.name] = component
|
"""
|
||||||
|
Sets the loaded component in this instance.
|
||||||
|
"""
|
||||||
|
slot_name = component.slot or component.name
|
||||||
|
self._loaded_components[slot_name] = component
|
||||||
|
self.host.signals.add_object_listeners_and_responders(component)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def db_names(self):
|
def db_names(self):
|
||||||
"""
|
"""
|
||||||
Property shortcut to retrieve the registered component names
|
Property shortcut to retrieve the registered component keys
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
component_names (iterable): The name of each component that is registered
|
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):
|
def __getattr__(self, name):
|
||||||
return self.get(name)
|
return self.get(name)
|
||||||
|
|
@ -236,7 +208,6 @@ class ComponentHolderMixin:
|
||||||
All registered components are initialized on the typeclass.
|
All registered components are initialized on the typeclass.
|
||||||
They will be of None value if not present in the class components or runtime components.
|
They will be of None value if not present in the class components or runtime components.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def at_init(self):
|
def at_init(self):
|
||||||
"""
|
"""
|
||||||
Method that initializes the ComponentHandler.
|
Method that initializes the ComponentHandler.
|
||||||
|
|
@ -261,28 +232,16 @@ class ComponentHolderMixin:
|
||||||
components that were set on the typeclass using ComponentProperty.
|
components that were set on the typeclass using ComponentProperty.
|
||||||
"""
|
"""
|
||||||
super().basetype_setup()
|
super().basetype_setup()
|
||||||
component_names = []
|
|
||||||
setattr(self, "_component_handler", ComponentHandler(self))
|
setattr(self, "_component_handler", ComponentHandler(self))
|
||||||
setattr(self, "_signal_handler", signals.SignalsHandler(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:
|
for component_name, values in class_components:
|
||||||
component_class = components.get_component_class(component_name)
|
component_class = components.get_component_class(component_name)
|
||||||
component = component_class.create(self, **values)
|
component = component_class.create(self, **values)
|
||||||
component_names.append(component_name)
|
self.components.add(component)
|
||||||
self.components._loaded_components[component_name] = component
|
|
||||||
self.signals.add_object_listeners_and_responders(component)
|
|
||||||
|
|
||||||
self.db.component_names = component_names
|
|
||||||
self.signals.trigger("at_basetype_setup")
|
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
|
@property
|
||||||
def components(self) -> ComponentHandler:
|
def components(self) -> ComponentHandler:
|
||||||
"""
|
"""
|
||||||
|
|
@ -305,10 +264,21 @@ class ComponentHolderMixin:
|
||||||
def signals(self) -> signals.SignalsHandler:
|
def signals(self) -> signals.SignalsHandler:
|
||||||
return getattr(self, "_signal_handler", None)
|
return getattr(self, "_signal_handler", None)
|
||||||
|
|
||||||
|
def _get_class_components(self):
|
||||||
|
class_components = {}
|
||||||
|
|
||||||
class ComponentDoesNotExist(Exception):
|
def base_type_iterator():
|
||||||
pass
|
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", ())
|
||||||
|
class_components.update({cmp[0]: cmp[1] for cmp in base_class_components})
|
||||||
|
|
||||||
class ComponentIsNotRegistered(Exception):
|
instance_components = getattr(self, "_class_components", ())
|
||||||
pass
|
class_components.update({cmp[0]: cmp[1] for cmp in instance_components})
|
||||||
|
|
||||||
|
return tuple(class_components.items())
|
||||||
|
|
|
||||||
15
evennia/contrib/base_systems/components/listing.py
Normal file
15
evennia/contrib/base_systems/components/listing.py
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
from evennia.contrib.base_systems.components import exceptions
|
||||||
|
|
||||||
|
COMPONENT_LISTING = {}
|
||||||
|
|
||||||
|
|
||||||
|
def get_component_class(name):
|
||||||
|
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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue