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.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):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue