mirror of
https://github.com/evennia/evennia.git
synced 2026-03-20 23:06:31 +01:00
Add an error handler if errors occurr during event execution
Optimize time events with fewer restarts
This commit is contained in:
parent
44a73acd94
commit
1cfaf77df7
2 changed files with 89 additions and 33 deletions
|
|
@ -14,6 +14,7 @@ from evennia import ScriptDB
|
|||
from evennia.utils.create import create_script
|
||||
from evennia.utils.gametime import real_seconds_until as standard_rsu
|
||||
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
|
||||
|
||||
hooks = []
|
||||
|
|
@ -141,6 +142,11 @@ def get_next_wait(format):
|
|||
number of units set in the calendar affects the way seconds are
|
||||
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)
|
||||
if calendar is None:
|
||||
|
|
@ -179,12 +185,20 @@ def get_next_wait(format):
|
|||
piece = int(piece)
|
||||
params[uname] = piece
|
||||
details.append("{}={}".format(uname, piece))
|
||||
if i < len(units):
|
||||
next_unit = units[i + 1]
|
||||
else:
|
||||
next_unit = None
|
||||
i += 1
|
||||
|
||||
params["sec"] = 0
|
||||
details = " ".join(details)
|
||||
seconds = rsu(**params)
|
||||
return seconds, details
|
||||
until = rsu(**params)
|
||||
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):
|
||||
"""
|
||||
|
|
@ -197,12 +211,13 @@ def create_time_event(obj, event_name, number, parameters):
|
|||
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.key = key
|
||||
script.desc = "event on {}".format(key)
|
||||
script.db.time_format = parameters
|
||||
script.db.number = number
|
||||
script.ndb.usual = usual
|
||||
|
||||
def keyword_event(events, parameters):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -4,10 +4,14 @@ Scripts for the event system.
|
|||
|
||||
from datetime import datetime, timedelta
|
||||
from Queue import Queue
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from django.conf import settings
|
||||
from evennia import DefaultObject, DefaultScript, ScriptDB
|
||||
from evennia import DefaultObject, DefaultScript, ChannelDB, ScriptDB
|
||||
from evennia import logger
|
||||
from evennia.utils.create import create_channel
|
||||
from evennia.utils.dbserialize import dbserialize
|
||||
from evennia.utils.utils import all_from_module, delay
|
||||
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 import typeclasses
|
||||
|
||||
# Constants
|
||||
RE_LINE_ERROR = re.compile(r'^ File "\<string\>", line (\d+)')
|
||||
|
||||
class EventHandler(DefaultScript):
|
||||
|
||||
"""
|
||||
|
|
@ -70,6 +77,13 @@ class EventHandler(DefaultScript):
|
|||
Handler.script = self
|
||||
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):
|
||||
"""
|
||||
Return a dictionary of the object's events.
|
||||
|
|
@ -393,9 +407,7 @@ class EventHandler(DefaultScript):
|
|||
else:
|
||||
locals = {key: value for key, value in locals.items()}
|
||||
|
||||
events = self.db.events.get(obj, {}).get(event_name, [])
|
||||
|
||||
# Filter down of events if there is a custom call
|
||||
events = self.get_events(obj).get(event_name, [])
|
||||
if event_type:
|
||||
custom_call = event_type[3]
|
||||
if custom_call:
|
||||
|
|
@ -407,13 +419,41 @@ class EventHandler(DefaultScript):
|
|||
if not event["valid"]:
|
||||
continue
|
||||
|
||||
if number is not None and i != number:
|
||||
if number is not None and event["number"] != number:
|
||||
continue
|
||||
|
||||
try:
|
||||
exec(event["code"], locals, locals)
|
||||
except InterruptEvent:
|
||||
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
|
||||
|
||||
|
|
@ -474,36 +514,37 @@ class TimeEventScript(DefaultScript):
|
|||
self.db.number = None
|
||||
|
||||
def at_repeat(self):
|
||||
"""Call the event and reset interval."""
|
||||
# Get the event handler and call the script
|
||||
try:
|
||||
script = ScriptDB.objects.get(db_key="event_handler")
|
||||
except ScriptDB.DoesNotExist:
|
||||
logger.log_trace("Can't get the event handler.")
|
||||
return
|
||||
"""
|
||||
Call the event and reset interval.
|
||||
|
||||
It is necessary to restart the script to reset its interval
|
||||
only twice after a reload. When the script has undergone
|
||||
down time, there's usually a slight shift in game time. Once
|
||||
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:
|
||||
obj = self.obj
|
||||
if not obj.events:
|
||||
return
|
||||
|
||||
event_name = self.db.event_name
|
||||
number = self.db.number
|
||||
events = script.db.events.get(obj, {}).get(event_name)
|
||||
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)
|
||||
obj.events.call(event_name, obj, number=number)
|
||||
|
||||
|
||||
# Functions to manipulate tasks
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue