mirror of
https://github.com/evennia/evennia.git
synced 2026-03-17 05:16:31 +01:00
Scripts and Exits updated. Fixed some deep issues with Scripts that caused object-based scripts to not properly shut down in some situations, as well as spawn multiple instances of themselves. I think this should resolve all "at_repeat doubling" issues reported. Due to optimizations in the typeclass cache loader in a previous update, the Exit cmdsets were not properly loaded (they were loaded at cache time, which now doesn't happen as often). So Exits instead rely on the new "at_cmdset_get" hook called by the cmdhandler. It allows dynamic modification of cmdsets just before they are accessed. Resolves issue173 (I hope). Resolves issue180. Resolves issue 181.
This commit is contained in:
parent
16affc284b
commit
2b4e008d18
13 changed files with 135 additions and 68 deletions
|
|
@ -84,6 +84,10 @@ def get_and_merge_cmdsets(caller):
|
|||
that this is only relevant for logged-in callers.
|
||||
"""
|
||||
# The calling object's cmdset
|
||||
try:
|
||||
caller.at_cmdset_get()
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
try:
|
||||
caller_cmdset = caller.cmdset.current
|
||||
except AttributeError:
|
||||
|
|
@ -103,6 +107,12 @@ def get_and_merge_cmdsets(caller):
|
|||
# Gather all cmdsets stored on objects in the room and
|
||||
# also in the caller's inventory and the location itself
|
||||
local_objlist = location.contents_get(exclude=caller.dbobj) + caller.contents + [location]
|
||||
for obj in local_objlist:
|
||||
try:
|
||||
# call hook in case we need to do dynamic changing to cmdset
|
||||
obj.at_cmdset_get()
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
local_objects_cmdsets = [obj.cmdset.current for obj in local_objlist
|
||||
if (obj.cmdset.current and obj.locks.check(caller, 'call', no_superuser_bypass=True))]
|
||||
for cset in local_objects_cmdsets:
|
||||
|
|
|
|||
|
|
@ -409,6 +409,17 @@ class CmdSetHandler(object):
|
|||
self.obj.cmdset_storage = storage
|
||||
self.update()
|
||||
|
||||
def has_cmdset(self, cmdset_key, must_be_default=False):
|
||||
"""
|
||||
checks so the cmdsethandler contains a cmdset with the given key.
|
||||
must_be_default - only match against the default cmdset.
|
||||
"""
|
||||
if must_be_default:
|
||||
return self.cmdset_stack and self.cmdset_stack[0].key == cmdset_key
|
||||
else:
|
||||
return any([cmdset.key == cmdset_key for cmdset in self.cmdset_stack])
|
||||
|
||||
|
||||
def all(self):
|
||||
"""
|
||||
Returns all cmdsets.
|
||||
|
|
|
|||
|
|
@ -226,6 +226,8 @@ class CmdScripts(MuxCommand):
|
|||
else:
|
||||
string = "Stopping script '%s'." % scripts[0].key
|
||||
scripts[0].stop()
|
||||
#import pdb
|
||||
#pdb.set_trace()
|
||||
ScriptDB.objects.validate() #just to be sure all is synced
|
||||
else:
|
||||
# multiple matches.
|
||||
|
|
|
|||
|
|
@ -191,7 +191,6 @@ class ObjectDB(TypedObject):
|
|||
self.cmdset = CmdSetHandler(self)
|
||||
self.cmdset.update(init_mode=True)
|
||||
self.scripts = ScriptHandler(self)
|
||||
self.scripts.validate(init_mode=True)
|
||||
self.nicks = ObjectNickHandler(self)
|
||||
|
||||
# Wrapper properties to easily set database fields. These are
|
||||
|
|
@ -804,6 +803,9 @@ class ObjectDB(TypedObject):
|
|||
if object.__getattribute__(self, 'player') and self.player:
|
||||
self.player.character = None
|
||||
self.player = None
|
||||
|
||||
for script in self.scripts.all():
|
||||
script.stop()
|
||||
|
||||
# if self.player:
|
||||
# self.player.user.is_active = False
|
||||
|
|
|
|||
|
|
@ -75,12 +75,30 @@ class Object(TypeClass):
|
|||
"""
|
||||
pass
|
||||
|
||||
def basetype_posthook_setup(self):
|
||||
"""
|
||||
Called once, after basetype_setup and at_object_creation. This should generally not be overloaded unless
|
||||
you are redefining how a room/exit/object works. It allows for basetype-like setup
|
||||
after the object is created. An example of this is EXITs, who need to know keys, aliases, locks
|
||||
etc to set up their exit-cmdsets.
|
||||
"""
|
||||
pass
|
||||
|
||||
def at_cache(self):
|
||||
"""
|
||||
Called whenever this object is cached or reloaded.
|
||||
Called whenever this object is cached to the idmapper backend.
|
||||
"""
|
||||
pass
|
||||
|
||||
def at_cmdset_get(self):
|
||||
"""
|
||||
Called just before cmdsets on this object are requested by the
|
||||
command handler. If changes need to be done on the fly to the cmdset
|
||||
before passing them on to the cmdhandler, this is the place to do it.
|
||||
This is called also if the object currently have no cmdsets.
|
||||
"""
|
||||
pass
|
||||
|
||||
def at_first_login(self):
|
||||
"""
|
||||
Only called once, the very first
|
||||
|
|
@ -423,11 +441,15 @@ class Exit(Object):
|
|||
"""
|
||||
Helper function for creating an exit command set + command.
|
||||
|
||||
Note that exitdbobj is an ObjectDB instance. This is necessary for
|
||||
handling reloads and avoid tracebacks while the typeclass system
|
||||
is rebooting.
|
||||
"""
|
||||
The command of this cmdset has the same name as the Exit object
|
||||
and allows the exit to react when the player enter the exit's name,
|
||||
triggering the movement between rooms.
|
||||
|
||||
Note that exitdbobj is an ObjectDB instance. This is necessary
|
||||
for handling reloads and avoid tracebacks if this is called while
|
||||
the typeclass system is rebooting.
|
||||
"""
|
||||
#print "Exit:create_exit_cmdset "
|
||||
class ExitCommand(command.Command):
|
||||
"""
|
||||
This is a command that simply cause the caller
|
||||
|
|
@ -476,7 +498,6 @@ class Exit(Object):
|
|||
return exit_cmdset
|
||||
|
||||
# Command hooks
|
||||
|
||||
def basetype_setup(self):
|
||||
"""
|
||||
Setup exit-security
|
||||
|
|
@ -485,21 +506,26 @@ class Exit(Object):
|
|||
overload the default locks (it is called after this one).
|
||||
"""
|
||||
super(Exit, self).basetype_setup()
|
||||
|
||||
# this is the fundamental thing for making the Exit work:
|
||||
self.cmdset.add_default(self.create_exit_cmdset(self.dbobj), permanent=False)
|
||||
# an exit should have a destination (this is replaced at creation time)
|
||||
if self.dbobj.location:
|
||||
self.destination = self.dbobj.location
|
||||
|
||||
# setting default locks (overload these in at_object_creation()
|
||||
self.locks.add("puppet:false()") # would be weird to puppet an exit ...
|
||||
self.locks.add("traverse:all()") # who can pass through exit by default
|
||||
self.locks.add("get:false()") # noone can pick up the exit
|
||||
|
||||
def at_cache(self):
|
||||
"Called when the typeclass is re-cached or reloaded. Should usually not be edited."
|
||||
self.cmdset.add_default(self.create_exit_cmdset(self.dbobj), permanent=False)
|
||||
self.locks.add("get:false()") # noone can pick up the exit
|
||||
|
||||
# an exit should have a destination (this is replaced at creation time)
|
||||
if self.dbobj.location:
|
||||
self.destination = self.dbobj.location
|
||||
|
||||
def at_cmdset_get(self):
|
||||
"""
|
||||
Called when the cmdset is requested from this object, just before the cmdset is
|
||||
actually extracted. If no Exit-cmdset is cached, create it now.
|
||||
"""
|
||||
|
||||
if self.ndb.exit_reset or not self.cmdset.has_cmdset("_exitset", must_be_default=True):
|
||||
# we are resetting, or no exit-cmdset was set. Create one dynamically.
|
||||
self.cmdset.add_default(self.create_exit_cmdset(self.dbobj), permanent=False)
|
||||
self.ndb.exit_reset = False
|
||||
|
||||
# this and other hooks are what usually can be modified safely.
|
||||
|
||||
|
|
|
|||
|
|
@ -88,8 +88,8 @@ class ScriptManager(TypedObjectManager):
|
|||
key = validate only scripts with a particular key
|
||||
dbref = validate only the single script with this particular id.
|
||||
|
||||
init_mode - When this mode is active, non-persistent scripts
|
||||
will be removed and persistent scripts will be
|
||||
init_mode - This is used during server upstart. It causes non-persistent
|
||||
scripts to be removed and persistent scripts to be
|
||||
force-restarted.
|
||||
|
||||
This method also makes sure start any scripts it validates,
|
||||
|
|
@ -117,26 +117,30 @@ class ScriptManager(TypedObjectManager):
|
|||
# This deletes all non-persistent scripts from database
|
||||
nr_stopped += self.remove_non_persistent(obj=obj)
|
||||
# turn off the activity flag for all remaining scripts
|
||||
for script in self.all():
|
||||
script.is_active = False
|
||||
scripts = self.get_all_scripts()
|
||||
for script in scripts:
|
||||
script.dbobj.is_active = False
|
||||
|
||||
elif not scripts:
|
||||
# normal operation
|
||||
if dbref and self.dbref(dbref):
|
||||
scripts = self.get_id(dbref)
|
||||
elif obj:
|
||||
scripts = self.get_all_scripts_on_obj(obj, key=key)
|
||||
else:
|
||||
scripts = self.get_all_scripts(key=key) #self.model.get_all_cached_instances()
|
||||
|
||||
if dbref and self.dbref(dbref):
|
||||
scripts = self.get_id(dbref)
|
||||
elif scripts:
|
||||
pass
|
||||
elif obj:
|
||||
scripts = self.get_all_scripts_on_obj(obj, key=key)
|
||||
else:
|
||||
scripts = self.model.get_all_cached_instances()#get_all_scripts(key=key)
|
||||
if not scripts:
|
||||
# no scripts available to validate
|
||||
VALIDATE_ITERATION -= 1
|
||||
return None, None
|
||||
|
||||
#print "scripts to validate: [%s]" % (", ".join(script.key for script in scripts))
|
||||
for script in scripts:
|
||||
if script.is_valid():
|
||||
#print "validating %s (%i)" % (script.key, id(script.dbobj))
|
||||
#print "validating %s (%i) (init_mode=%s)" % (script.key, id(script.dbobj), init_mode)
|
||||
nr_started += script.start(force_restart=init_mode)
|
||||
#print "back from start."
|
||||
#print "back from start. nr_started=", nr_started
|
||||
else:
|
||||
script.stop()
|
||||
nr_stopped += 1
|
||||
|
|
|
|||
|
|
@ -44,9 +44,10 @@ class ScriptClass(TypeClass):
|
|||
def _stop_task(self):
|
||||
"stop task runner"
|
||||
try:
|
||||
self.ndb.twisted_task.stop()
|
||||
#print "stopping twisted task:", id(self.ndb.twisted_task), self.obj
|
||||
self.ndb.twisted_task.stop()
|
||||
except Exception:
|
||||
pass
|
||||
logger.log_trace()
|
||||
def _step_err_callback(self, e):
|
||||
"callback for runner errors"
|
||||
cname = self.__class__.__name__
|
||||
|
|
@ -74,7 +75,7 @@ class ScriptClass(TypeClass):
|
|||
self.save()
|
||||
def _step_task(self):
|
||||
"step task"
|
||||
try:
|
||||
try:
|
||||
d = maybeDeferred(self._step_succ_callback)
|
||||
d.addErrback(self._step_err_callback)
|
||||
return d
|
||||
|
|
@ -107,30 +108,30 @@ class ScriptClass(TypeClass):
|
|||
"""
|
||||
#print "Script %s (%s) start (active:%s, force:%s) ..." % (self.key, id(self.dbobj),
|
||||
# self.is_active, force_restart)
|
||||
if self.dbobj.db_is_active and not force_restart:
|
||||
# script already runs.
|
||||
|
||||
if self.dbobj.is_active and not force_restart:
|
||||
# script already runs and should not be restarted.
|
||||
return 0
|
||||
|
||||
if self.obj:
|
||||
|
||||
obj = self.obj
|
||||
if obj:
|
||||
# check so the scripted object is valid and initalized
|
||||
try:
|
||||
dummy = object.__getattribute__(self.obj, 'cmdset')
|
||||
dummy = object.__getattribute__(obj, 'cmdset')
|
||||
except AttributeError:
|
||||
# this means the object is not initialized.
|
||||
self.dbobj.db_is_active = False
|
||||
self.dbobj.is_active = False
|
||||
return 0
|
||||
# try to start the script
|
||||
try:
|
||||
self.dbobj.db_is_active = True
|
||||
self.dbobj.save()
|
||||
self.dbobj.is_active = True
|
||||
self.at_start()
|
||||
if self.dbobj.db_interval > 0:
|
||||
self._start_task()
|
||||
return 1
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
self.dbobj.db_is_active = False
|
||||
self.dbobj.save()
|
||||
self.dbobj.is_active = False
|
||||
return 0
|
||||
|
||||
def stop(self, kill=False):
|
||||
|
|
@ -141,6 +142,8 @@ class ScriptClass(TypeClass):
|
|||
kill - don't call finishing hooks.
|
||||
"""
|
||||
#print "stopping script %s" % self.key
|
||||
#import pdb
|
||||
#pdb.set_trace()
|
||||
if not kill:
|
||||
try:
|
||||
self.at_stop()
|
||||
|
|
@ -149,11 +152,13 @@ class ScriptClass(TypeClass):
|
|||
if self.dbobj.db_interval > 0:
|
||||
try:
|
||||
self._stop_task()
|
||||
except Exception:
|
||||
except Exception, e:
|
||||
logger.log_trace("Stopping script %s(%s)" % (self.key, self.id))
|
||||
pass
|
||||
try:
|
||||
self.dbobj.delete()
|
||||
except AssertionError:
|
||||
logger.log_trace()
|
||||
return 0
|
||||
return 1
|
||||
|
||||
|
|
@ -175,7 +180,7 @@ class ScriptClass(TypeClass):
|
|||
pass
|
||||
|
||||
|
||||
# class ScriptClassOld(TypeClass):
|
||||
# class ScriptClass(TypeClass):
|
||||
# """
|
||||
# Base class for all Scripts.
|
||||
# """
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ from twisted.internet import protocol, reactor, defer
|
|||
from twisted.web import server, static
|
||||
from django.db import connection
|
||||
from django.conf import settings
|
||||
from src.utils import reloads
|
||||
from src.scripts.models import ScriptDB
|
||||
from src.server.models import ServerConfig
|
||||
from src.server.sessionhandler import SESSIONS
|
||||
from src.server import initial_setup
|
||||
|
|
@ -99,7 +99,7 @@ class Evennia(object):
|
|||
channelhandler.CHANNELHANDLER.update()
|
||||
|
||||
# init all global scripts
|
||||
reloads.reload_scripts(init_mode=True)
|
||||
ScriptDB.objects.validate(init_mode=True)
|
||||
|
||||
# Make info output to the terminal.
|
||||
self.terminal_output()
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ class SessionBase(object):
|
|||
self.log('Logged in: %s' % self)
|
||||
|
||||
# start (persistent) scripts on this object
|
||||
reloads.reload_scripts(obj=self.player.character, init_mode=True)
|
||||
reloads.reload_scripts(obj=self.player.character)
|
||||
|
||||
#add session to connected list
|
||||
SESSIONS.add_loggedin_session(self)
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ class Attribute(SharedMemoryModel):
|
|||
"Deleter is disabled. Use the lockhandler.delete (self.lock.delete) instead"""
|
||||
logger.log_errmsg("Lock_Storage (on %s) cannot be deleted. Use obj.lock.delete() instead." % self)
|
||||
lock_storage = property(lock_storage_get, lock_storage_set, lock_storage_del)
|
||||
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
|
|
@ -747,8 +747,8 @@ class TypedObject(SharedMemoryModel):
|
|||
defpath = "src.objects.objects.Object"
|
||||
typeclass = object.__getattribute__(self, "_path_import")(defpath)
|
||||
if not silent:
|
||||
errstring += " %s\n%s" % (typeclass, errstring)
|
||||
errstring += " Default class '%s' failed to load." % failpath
|
||||
#errstring = " %s\n%s" % (typeclass, errstring)
|
||||
errstring = " Default class '%s' failed to load." % failpath
|
||||
errstring += "\n Using Evennia's default class '%s'." % defpath
|
||||
object.__getattribute__(self, "_display_errmsg")(errstring)
|
||||
if not callable(typeclass):
|
||||
|
|
@ -1083,7 +1083,12 @@ class TypedObject(SharedMemoryModel):
|
|||
def all(self):
|
||||
return [val for val in self.__dict__.keys()
|
||||
if not val.startswith['_']]
|
||||
pass
|
||||
def __getattribute__(self, key):
|
||||
# return None if no matching attribute was found.
|
||||
try:
|
||||
return object.__getattribute__(self, key)
|
||||
except AttributeError:
|
||||
return None
|
||||
self._ndb_holder = NdbHolder()
|
||||
return self._ndb_holder
|
||||
#@ndb.setter
|
||||
|
|
|
|||
|
|
@ -91,20 +91,20 @@ def create_object(typeclass, key=None, location=None,
|
|||
player.obj = new_object
|
||||
|
||||
new_object.destination = destination
|
||||
|
||||
|
||||
# call the hook method. This is where all at_creation
|
||||
# customization happens as the typeclass stores custom
|
||||
# things on its database object.
|
||||
# things on its database object.
|
||||
new_object.basetype_setup() # setup the basics of Exits, Characters etc.
|
||||
new_object.at_object_creation()
|
||||
|
||||
# custom-given variables override the hook
|
||||
|
||||
# custom-given perms/locks overwrite hooks
|
||||
if permissions:
|
||||
new_object.permissions = permissions
|
||||
if aliases:
|
||||
new_object.aliases = aliases
|
||||
if locks:
|
||||
new_object.locks.add(locks)
|
||||
if aliases:
|
||||
new_object.aliases = aliases
|
||||
|
||||
# perform a move_to in order to display eventual messages.
|
||||
if home:
|
||||
|
|
@ -114,6 +114,10 @@ def create_object(typeclass, key=None, location=None,
|
|||
else:
|
||||
# rooms would have location=None.
|
||||
new_object.location = None
|
||||
|
||||
# post-hook setup (mainly used by Exits)
|
||||
new_object.basetype_posthook_setup()
|
||||
|
||||
new_object.save()
|
||||
return new_object
|
||||
|
||||
|
|
|
|||
|
|
@ -102,8 +102,9 @@ class SharedMemoryModel(Model):
|
|||
if instance._get_pk_val() is not None:
|
||||
cls.__instance_cache__[instance._get_pk_val()] = instance
|
||||
try:
|
||||
object.__getattribute__(instance, "at_cache")()
|
||||
except (TypeError, AttributeError):
|
||||
object.__getattribute__(instance, "at_cache")()
|
||||
except (TypeError, AttributeError), e:
|
||||
#print e, instance._get_pk_val()
|
||||
pass
|
||||
|
||||
#key = "%s-%s" % (cls, instance.pk)
|
||||
|
|
@ -118,7 +119,7 @@ class SharedMemoryModel(Model):
|
|||
|
||||
|
||||
def _flush_cached_by_key(cls, key):
|
||||
del cls.__instance_cache__[key]
|
||||
del cls.__instance_cache__[key]
|
||||
_flush_cached_by_key = classmethod(_flush_cached_by_key)
|
||||
|
||||
def flush_cached_instance(cls, instance):
|
||||
|
|
|
|||
|
|
@ -128,23 +128,20 @@ def reload_modules():
|
|||
# run through all objects in database, forcing re-caching.
|
||||
|
||||
|
||||
def reload_scripts(scripts=None, obj=None, key=None,
|
||||
dbref=None, init_mode=False):
|
||||
def reload_scripts(scripts=None, obj=None, key=None, dbref=None):
|
||||
"""
|
||||
Run a validation of the script database.
|
||||
obj - only validate scripts on this object
|
||||
key - only validate scripts with this key
|
||||
dbref - only validate the script with this unique idref
|
||||
emit_to_obj - which object to receive error message
|
||||
init_mode - during init-mode, non-persistent scripts are
|
||||
cleaned out. All persistent scripts are force-started.
|
||||
|
||||
"""
|
||||
|
||||
nr_started, nr_stopped = ScriptDB.objects.validate(scripts=scripts,
|
||||
obj=obj, key=key,
|
||||
dbref=dbref,
|
||||
init_mode=init_mode)
|
||||
init_mode=False)
|
||||
if nr_started or nr_stopped:
|
||||
string = " Started %s script(s). Stopped %s invalid script(s)." % \
|
||||
(nr_started, nr_stopped)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue