mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Move globalhandler into utils.containers, make settings format more flexible, default to base typeclass, add helper functions and some more lenient error handling
This commit is contained in:
parent
7c416280a8
commit
098af3caba
8 changed files with 134 additions and 86 deletions
|
|
@ -214,7 +214,7 @@ def _init():
|
|||
from .server.sessionhandler import SESSION_HANDLER
|
||||
from .comms.channelhandler import CHANNEL_HANDLER
|
||||
from .scripts.monitorhandler import MONITOR_HANDLER
|
||||
from .scripts.globalhandler import GLOBAL_SCRIPTS
|
||||
from .utils.containers import GLOBAL_SCRIPTS
|
||||
|
||||
# initialize the doc string
|
||||
global __doc__
|
||||
|
|
|
|||
|
|
@ -645,7 +645,7 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)):
|
|||
guest = kwargs.get('guest', False)
|
||||
|
||||
permissions = kwargs.get('permissions', settings.PERMISSION_ACCOUNT_DEFAULT)
|
||||
typeclass = kwargs.get('typeclass', settings.BASE_ACCOUNT_TYPECLASS)
|
||||
typeclass = kwargs.get('typeclass', cls)
|
||||
|
||||
ip = kwargs.get('ip', '')
|
||||
if ip and CREATION_THROTTLE.check(ip):
|
||||
|
|
|
|||
|
|
@ -256,6 +256,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
|||
|
||||
try:
|
||||
kwargs['desc'] = kwargs.pop('description', '')
|
||||
kwargs['typeclass'] = kwargs.get('typeclass', cls)
|
||||
obj = create.create_channel(key, *args, **kwargs)
|
||||
|
||||
# Record creator id and creation IP
|
||||
|
|
|
|||
|
|
@ -1,69 +0,0 @@
|
|||
from django.conf import settings
|
||||
from evennia.utils.utils import class_from_module
|
||||
|
||||
|
||||
class GlobalContainer(object):
|
||||
"""
|
||||
Simple Handler object loaded by the Evennia API to contain and manage a game's Global Scripts.
|
||||
|
||||
This is accessed like a dictionary. Alternatively you can access Properties on it.
|
||||
|
||||
Example:
|
||||
import evennia
|
||||
evennia.GLOBAL_SCRIPTS['key']
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.script_data = dict()
|
||||
self.script_storage = dict()
|
||||
self.script_data.update(settings.GLOBAL_SCRIPTS)
|
||||
self.typeclass_storage = dict()
|
||||
|
||||
for key, data in settings.GLOBAL_SCRIPTS.items():
|
||||
self.typeclass_storage[key] = class_from_module(data['typeclass'])
|
||||
for key in self.script_data.keys():
|
||||
self._load_script(key)
|
||||
|
||||
def __getitem__(self, item):
|
||||
|
||||
# Likely to only reach this if someone called the API wrong.
|
||||
if item not in self.typeclass_storage:
|
||||
return None
|
||||
|
||||
# The most common outcome next!
|
||||
if self.script_storage[item]:
|
||||
return self.script_storage[item]
|
||||
else:
|
||||
# Oops, something happened to our Global Script. Let's re-create it.
|
||||
return self._load_script(item)
|
||||
|
||||
def __getattr__(self, item):
|
||||
return self[item]
|
||||
|
||||
def _load_script(self, item):
|
||||
typeclass = self.typeclass_storage[item]
|
||||
found = typeclass.objects.filter(db_key=item).first()
|
||||
interval = self.script_data[item].get('interval', None)
|
||||
start_delay = self.script_data[item].get('start_delay', None)
|
||||
repeats = self.script_data[item].get('repeats', 0)
|
||||
desc = self.script_data[item].get('desc', '')
|
||||
|
||||
if not found:
|
||||
new_script, errors = typeclass.create(key=item, persistent=True, interval=interval, start_delay=start_delay,
|
||||
repeats=repeats, desc=desc)
|
||||
new_script.start()
|
||||
self.script_storage[item] = new_script
|
||||
return new_script
|
||||
|
||||
if (found.interval != interval) or (found.start_delay != start_delay) or (found.repeats != repeats):
|
||||
found.restart(interval=interval, start_delay=start_delay, repeats=repeats)
|
||||
if found.desc != desc:
|
||||
found.desc = desc
|
||||
self.script_storage[item] = found
|
||||
return found
|
||||
|
||||
|
||||
|
||||
|
||||
# Create singleton of the GlobalHandler for the API.
|
||||
GLOBAL_SCRIPTS = GlobalContainer()
|
||||
|
|
@ -342,6 +342,9 @@ class DefaultScript(ScriptBase):
|
|||
|
||||
kwargs['key'] = key
|
||||
|
||||
# If no typeclass supplied, use this class
|
||||
kwargs['typeclass'] = kwargs.pop('typeclass', cls)
|
||||
|
||||
try:
|
||||
obj = create.create_script(**kwargs)
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -542,20 +542,13 @@ PROTOTYPEFUNC_MODULES = ["evennia.utils.prototypefuncs",
|
|||
# Global Scripts
|
||||
######################################################################
|
||||
|
||||
# While any script that is not attached to any object is considered
|
||||
# Global, any listed here will be started by Evennia during boot
|
||||
# and attached to its API for an easy-lookup. This ensures the Script
|
||||
# is always accessible, and re-created if it is somehow deleted. Use
|
||||
# this for Scripts that absolutely MUST be running for your game as a
|
||||
# simple way to get them launched.
|
||||
|
||||
# The 'key' is a way to quickly index them, and it will also be the
|
||||
# Script Typeclasss's key so it can be quickly retrieved.
|
||||
|
||||
# Values are a dictionary that uses the example format. Available keys
|
||||
# are typeclass (required), interval, repeats, start_delay, and desc
|
||||
# only typeclass is required.
|
||||
|
||||
# Global scripts started here will be available through
|
||||
# 'evennia.GLOBAL_SCRIPTS.key'. The scripts will survive a reload and be
|
||||
# recreated automatically if deleted. Each entry must have the script keys,
|
||||
# whereas all other fields in the specification are optional. If 'typeclass' is
|
||||
# not given, BASE_SCRIPT_TYPECLASS will be assumed. Note that if you change
|
||||
# typeclass for the same key, a new Script will replace the old one on
|
||||
# `evennia.GLOBAL_SCRIPTS`.
|
||||
GLOBAL_SCRIPTS = {
|
||||
# 'key': {'typeclass': 'typeclass.path.here',
|
||||
# 'repeats': -1, 'interval': 50, 'desc': 'Example script'},
|
||||
|
|
|
|||
110
evennia/utils/containers.py
Normal file
110
evennia/utils/containers.py
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
"""
|
||||
Containers
|
||||
|
||||
"""
|
||||
|
||||
|
||||
from django.conf import settings
|
||||
from evennia.utils.utils import class_from_module
|
||||
from evennia.utils import logger
|
||||
|
||||
|
||||
class GlobalScriptContainer(object):
|
||||
"""
|
||||
Simple Handler object loaded by the Evennia API to contain and manage a
|
||||
game's Global Scripts. Scripts to start are defined by
|
||||
`settings.GLOBAL_SCRIPTS`.
|
||||
|
||||
Example:
|
||||
import evennia
|
||||
evennia.GLOBAL_SCRIPTS.scriptname
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize the container by preparing scripts. Lazy-load only when the
|
||||
script is requested.
|
||||
|
||||
"""
|
||||
self.script_data = {key: {} if data is None else data
|
||||
for key, data in settings.GLOBAL_SCRIPTS.items()}
|
||||
self.script_storage = {}
|
||||
self.typeclass_storage = {}
|
||||
|
||||
for key, data in self.script_data.items():
|
||||
try:
|
||||
typeclass = data.get('typeclass', settings.BASE_SCRIPT_TYPECLASS)
|
||||
self.typeclass_storage[key] = class_from_module(typeclass)
|
||||
except ImportError as err:
|
||||
logger.log_err(f"GlobalContainer could not start global script {key}: {err}")
|
||||
|
||||
def __getitem__(self, key):
|
||||
|
||||
if key not in self.typeclass_storage:
|
||||
# this script is unknown to the container
|
||||
return None
|
||||
|
||||
# (re)create script on-demand
|
||||
return self.script_storage.get(key) or self._load_script(key)
|
||||
|
||||
def __getattr__(self, key):
|
||||
return self[key]
|
||||
|
||||
def _load_script(self, key):
|
||||
typeclass = self.typeclass_storage[key]
|
||||
found = typeclass.objects.filter(db_key=key).first()
|
||||
interval = self.script_data[key].get('interval', None)
|
||||
start_delay = self.script_data[key].get('start_delay', None)
|
||||
repeats = self.script_data[key].get('repeats', 0)
|
||||
desc = self.script_data[key].get('desc', '')
|
||||
|
||||
if not found:
|
||||
new_script, errors = typeclass.create(key=key, persistent=True,
|
||||
interval=interval,
|
||||
start_delay=start_delay,
|
||||
repeats=repeats, desc=desc)
|
||||
if errors:
|
||||
logger.log_err("\n".join(errors))
|
||||
return None
|
||||
|
||||
new_script.start()
|
||||
self.script_storage[key] = new_script
|
||||
return new_script
|
||||
|
||||
if ((found.interval != interval) or
|
||||
(found.start_delay != start_delay) or
|
||||
(found.repeats != repeats)):
|
||||
found.restart(interval=interval, start_delay=start_delay, repeats=repeats)
|
||||
if found.desc != desc:
|
||||
found.desc = desc
|
||||
self.script_storage[key] = found
|
||||
return found
|
||||
|
||||
def get(self, key):
|
||||
"""
|
||||
Retrive script by key (in case of not knowing it beforehand).
|
||||
|
||||
Args:
|
||||
key (str): The name of the script.
|
||||
|
||||
Returns:
|
||||
script (Script): The named global script.
|
||||
|
||||
"""
|
||||
# note that this will recreate the script if it doesn't exist/was lost
|
||||
return self[key]
|
||||
|
||||
def all(self):
|
||||
"""
|
||||
Get all scripts.
|
||||
|
||||
Returns:
|
||||
scripts (list): All global script objects stored on the container.
|
||||
|
||||
"""
|
||||
return list(self.script_storage.values())
|
||||
|
||||
|
||||
# Create singleton of the GlobalHandler for the API.
|
||||
GLOBAL_SCRIPTS = GlobalScriptContainer()
|
||||
|
|
@ -20,6 +20,7 @@ from django.core.exceptions import ObjectDoesNotExist, FieldError
|
|||
from django.db.models.signals import post_save
|
||||
from django.db.models.base import Model, ModelBase
|
||||
from django.db.models.signals import pre_delete, post_migrate
|
||||
from django.db.utils import DatabaseError
|
||||
from evennia.utils import logger
|
||||
from evennia.utils.utils import dbref, get_evennia_pids, to_str
|
||||
|
||||
|
|
@ -391,7 +392,16 @@ class SharedMemoryModel(with_metaclass(SharedMemoryModelBase, Model)):
|
|||
|
||||
if _IS_MAIN_THREAD:
|
||||
# in main thread - normal operation
|
||||
super().save(*args, **kwargs)
|
||||
try:
|
||||
super().save(*args, **kwargs)
|
||||
except DatabaseError:
|
||||
# we handle the 'update_fields did not update any rows' error that
|
||||
# may happen due to timing issues with attributes
|
||||
ufields_removed = kwargs.pop('update_fields', None)
|
||||
if ufields_removed:
|
||||
super().save(*args, **kwargs)
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
# in another thread; make sure to save in reactor thread
|
||||
def _save_callback(cls, *args, **kwargs):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue