Add an error handler if errors occurr during event execution

Optimize time events with fewer restarts
This commit is contained in:
Vincent Le Goff 2017-03-27 13:41:42 -07:00 committed by Griatch
parent 44a73acd94
commit 1cfaf77df7
2 changed files with 89 additions and 33 deletions

View file

@ -14,6 +14,7 @@ from evennia import ScriptDB
from evennia.utils.create import create_script from evennia.utils.create import create_script
from evennia.utils.gametime import real_seconds_until as standard_rsu from evennia.utils.gametime import real_seconds_until as standard_rsu
from evennia.contrib.custom_gametime import UNITS from evennia.contrib.custom_gametime import UNITS
from evennia.contrib.custom_gametime import gametime_to_realtime
from evennia.contrib.custom_gametime import real_seconds_until as custom_rsu from evennia.contrib.custom_gametime import real_seconds_until as custom_rsu
hooks = [] hooks = []
@ -141,6 +142,11 @@ def get_next_wait(format):
number of units set in the calendar affects the way seconds are number of units set in the calendar affects the way seconds are
calculated. calculated.
Returns:
until (int or float): the number of seconds until the event.
usual (int or float): the usual number of seconds between events.
format (str): a string format representing the time.
""" """
calendar = getattr(settings, "EVENTS_CALENDAR", None) calendar = getattr(settings, "EVENTS_CALENDAR", None)
if calendar is None: if calendar is None:
@ -179,12 +185,20 @@ def get_next_wait(format):
piece = int(piece) piece = int(piece)
params[uname] = piece params[uname] = piece
details.append("{}={}".format(uname, piece)) details.append("{}={}".format(uname, piece))
if i < len(units):
next_unit = units[i + 1]
else:
next_unit = None
i += 1 i += 1
params["sec"] = 0 params["sec"] = 0
details = " ".join(details) details = " ".join(details)
seconds = rsu(**params) until = rsu(**params)
return seconds, details usual = -1
if next_unit:
kwargs = {next_unit: 1}
usual = gametime_to_realtime(**kwargs)
return until, usual, details
def create_time_event(obj, event_name, number, parameters): def create_time_event(obj, event_name, number, parameters):
""" """
@ -197,12 +211,13 @@ def create_time_event(obj, event_name, number, parameters):
parameters (str): the parameter of the event. parameters (str): the parameter of the event.
""" """
seconds, key = get_next_wait(parameters) seconds, usual, key = get_next_wait(parameters)
script = create_script("evennia.contrib.events.scripts.TimeEventScript", interval=seconds, obj=obj) script = create_script("evennia.contrib.events.scripts.TimeEventScript", interval=seconds, obj=obj)
script.key = key script.key = key
script.desc = "event on {}".format(key) script.desc = "event on {}".format(key)
script.db.time_format = parameters script.db.time_format = parameters
script.db.number = number script.db.number = number
script.ndb.usual = usual
def keyword_event(events, parameters): def keyword_event(events, parameters):
""" """

View file

@ -4,10 +4,14 @@ Scripts for the event system.
from datetime import datetime, timedelta from datetime import datetime, timedelta
from Queue import Queue from Queue import Queue
import re
import sys
import traceback
from django.conf import settings from django.conf import settings
from evennia import DefaultObject, DefaultScript, ScriptDB from evennia import DefaultObject, DefaultScript, ChannelDB, ScriptDB
from evennia import logger from evennia import logger
from evennia.utils.create import create_channel
from evennia.utils.dbserialize import dbserialize from evennia.utils.dbserialize import dbserialize
from evennia.utils.utils import all_from_module, delay from evennia.utils.utils import all_from_module, delay
from evennia.contrib.events.custom import ( from evennia.contrib.events.custom import (
@ -16,6 +20,9 @@ from evennia.contrib.events.exceptions import InterruptEvent
from evennia.contrib.events.handler import EventsHandler as Handler from evennia.contrib.events.handler import EventsHandler as Handler
from evennia.contrib.events import typeclasses from evennia.contrib.events import typeclasses
# Constants
RE_LINE_ERROR = re.compile(r'^ File "\<string\>", line (\d+)')
class EventHandler(DefaultScript): class EventHandler(DefaultScript):
""" """
@ -70,6 +77,13 @@ class EventHandler(DefaultScript):
Handler.script = self Handler.script = self
DefaultObject.events = typeclasses.PatchedObject.events DefaultObject.events = typeclasses.PatchedObject.events
# Create the channel if non-existent
try:
self.ndb.channel = ChannelDB.objects.get(db_key="everror")
except ChannelDB.DoesNotExist:
self.ndb.channel = create_channel("everror", desc="Event errors",
locks="control:false();listen:perm(Builders);send:false()")
def get_events(self, obj): def get_events(self, obj):
""" """
Return a dictionary of the object's events. Return a dictionary of the object's events.
@ -393,9 +407,7 @@ class EventHandler(DefaultScript):
else: else:
locals = {key: value for key, value in locals.items()} locals = {key: value for key, value in locals.items()}
events = self.db.events.get(obj, {}).get(event_name, []) events = self.get_events(obj).get(event_name, [])
# Filter down of events if there is a custom call
if event_type: if event_type:
custom_call = event_type[3] custom_call = event_type[3]
if custom_call: if custom_call:
@ -407,13 +419,41 @@ class EventHandler(DefaultScript):
if not event["valid"]: if not event["valid"]:
continue continue
if number is not None and i != number: if number is not None and event["number"] != number:
continue continue
try: try:
exec(event["code"], locals, locals) exec(event["code"], locals, locals)
except InterruptEvent: except InterruptEvent:
return False return False
except Exception:
etype, evalue, tb = sys.exc_info()
trace = traceback.format_exception(etype, evalue, tb)
number = event["number"]
logger.log_err("An error occurred during the event {} of " \
"{}, number {}\n{}".format(event_name, obj,
number + 1, "\n".join(trace)))
# Inform the 'everror' channel
line = "|runknown|n"
lineno = "|runknown|n"
for error in trace:
if error.startswith(' File "<string>", line '):
res = RE_LINE_ERROR.search(error)
if res:
lineno = int(res.group(1))
# Try to extract the line
try:
line = event["code"].splitlines()[lineno - 1]
except IndexError:
continue
else:
break
self.ndb.channel.msg("Error in {} of {}[{}], line {}:" \
" {}\n {}".format(event_name, obj,
number + 1, lineno, line, repr(evalue)))
return True return True
@ -474,36 +514,37 @@ class TimeEventScript(DefaultScript):
self.db.number = None self.db.number = None
def at_repeat(self): def at_repeat(self):
"""Call the event and reset interval.""" """
# Get the event handler and call the script Call the event and reset interval.
try:
script = ScriptDB.objects.get(db_key="event_handler") It is necessary to restart the script to reset its interval
except ScriptDB.DoesNotExist: only twice after a reload. When the script has undergone
logger.log_trace("Can't get the event handler.") down time, there's usually a slight shift in game time. Once
return the script restarts once, it will set the average time it
needs for all its future intervals and should not need to be
restarted. In short, a script that is created shouldn't need
to restart more than once, and a script that is reloaded should
restart only twice.
"""
if self.db.time_format:
# If the 'usual' time is set, use it
seconds = self.ndb.usual
if seconds is None:
seconds, usual, details = get_next_wait(self.db.time_format)
self.ndb.usual = usual
if self.interval != seconds:
self.restart(interval=seconds)
if self.db.event_name and self.db.number is not None: if self.db.event_name and self.db.number is not None:
obj = self.obj obj = self.obj
if not obj.events:
return
event_name = self.db.event_name event_name = self.db.event_name
number = self.db.number number = self.db.number
events = script.db.events.get(obj, {}).get(event_name) obj.events.call(event_name, obj, number=number)
if events is None:
logger.log_err("Cannot find the event {} on {}".format(
event_name, obj))
return
try:
event = events[number]
except IndexError:
logger.log_err("Cannot find the event {} {} on {}".format(
event_name, number, obj))
return
script.call_event(obj, event_name, obj, number=number)
if self.db.time_format:
seconds, details = get_next_wait(self.db.time_format)
self.restart(interval=seconds)
# Functions to manipulate tasks # Functions to manipulate tasks