Migrate. Made Exits work differently, by use of commands directly instead of an exithandler assigning commands on-the-fly. This solution is a lot cleaner and also solves an issue where @reload would kill typeclasses in situations where an exit was painting to an object whose typeclass was reloaded (same issue occured if the exit typeclass itself was reloaded). As part of these fixes I cleaned up the merging of cmdsets to now merge in strict priority order, as one would expect them to do. Many small bug-fixes and cleanups all over. Resolves issue 164. Resolves issue 163.

This commit is contained in:
Griatch 2011-05-01 18:04:15 +00:00
parent 4bcd5239b5
commit b8a13a2389
17 changed files with 323 additions and 298 deletions

View file

@ -133,18 +133,18 @@ class Room(BaseRoom):
class Exit(BaseExit):
"""
Exits are connectors between rooms. They are identified by the
engine by having an attribute "_destination" defined on themselves,
pointing to a valid room object. That is usually defined when
the exit is created (in, say, @dig or @link-type commands), not
hard-coded in their typeclass. Exits do have to make sure they
clean up a bit after themselves though, easiest accomplished
by letting by_object_delete() call the object's parent.
Note that exits need to do cache-cleanups after they are
deleted, so if you re-implement at_object_delete() for some
reason, make sure to call the parent class-method too!
Exits are connectors between rooms. Exits defines the
'destination' property and sets up a command on itself with the
same name as the Exit object - this command allows the player to
traverse the exit to the destination just by writing the name of
the object on the command line.
Relevant hooks:
at_before_traverse(traveller) - called just before traversing
at_after_traverse(traveller, source_loc) - called just after traversing
at_failed_traverse(traveller) - called if traversal failed for some reason. Will
not be called if the attribute 'err_traverse' is
defined, in which case that will simply be echoed.
"""
pass

View file

@ -53,7 +53,6 @@ from traceback import format_exc
from django.conf import settings
from src.comms.channelhandler import CHANNELHANDLER
from src.commands.cmdsethandler import import_cmdset
from src.objects.exithandler import EXITHANDLER
from src.utils import logger, utils
#This switches the command parser to a user-defined one.
@ -70,7 +69,6 @@ CMD_NOMATCH = "__nomatch_command"
CMD_MULTIMATCH = "__multimatch_command"
CMD_NOPERM = "__noperm_command"
CMD_CHANNEL = "__send_to_channel"
CMD_EXIT = "__move_to_exit"
class NoCmdSets(Exception):
"No cmdsets found. Critical error."
@ -92,63 +90,45 @@ def get_and_merge_cmdsets(caller):
caller_cmdset = caller.cmdset.current
except AttributeError:
caller_cmdset = None
# All surrounding cmdsets
# Create cmdset for all player's available channels
channel_cmdset = None
exit_cmdset = None
local_objects_cmdsets = [None]
# Player object's commandsets
try:
player_cmdset = caller.player.cmdset.current
except AttributeError:
player_cmdset = None
if not caller_cmdset.no_channels:
# Make cmdsets out of all valid channels
channel_cmdset = CHANNELHANDLER.get_cmdset(caller)
if not caller_cmdset.no_exits:
# Make cmdsets out of all valid exits in the room
exit_cmdset = EXITHANDLER.get_cmdset(caller)
# Gather cmdsets from location, objects in location or carried
local_objects_cmdsets = [None]
location = None
if hasattr(caller, "location"):
location = caller.location
if location and not caller_cmdset.no_objs:
# Gather all cmdsets stored on objects in the room and
# also in the caller's inventory and the location itself
local_objlist = location.contents + caller.contents + [location]
local_objects_cmdsets = [obj.cmdset.current
for obj in local_objlist
local_objlist = location.contents_get(exclude=caller.dbobj) + caller.contents + [location]
local_objects_cmdsets = [obj.cmdset.current for obj in local_objlist
if obj.locks.check(caller, 'call', no_superuser_bypass=True)]
# Merge all command sets into one
# (the order matters, the higher-prio cmdsets are merged last)
cmdset = caller_cmdset
for obj_cmdset in [obj_cmdset for obj_cmdset in local_objects_cmdsets if obj_cmdset]:
# Here only, object cmdsets are merged with duplicates=True
# (or we would never be able to differentiate between same-prio objects)
try:
old_duplicate_flag = obj_cmdset.duplicates
obj_cmdset.duplicates = True
cmdset = obj_cmdset + cmdset
obj_cmdset.duplicates = old_duplicate_flag
except TypeError:
pass
# Exits and channels automatically has duplicates=True.
try:
cmdset = exit_cmdset + cmdset
except TypeError:
pass
try:
cmdset = channel_cmdset + cmdset
except TypeError:
pass
# finally merge on the player cmdset. This should have a low priority
try:
cmdset = player_cmdset + cmdset
except TypeError:
pass
return cmdset
# Player object's commandsets
try:
player_cmdset = caller.player.cmdset.current
except AttributeError:
player_cmdset = None
cmdsets = [caller_cmdset] + [player_cmdset] + [channel_cmdset] + local_objects_cmdsets
# weed out all non-found sets
cmdsets = [cmdset for cmdset in cmdsets if cmdset]
# sort cmdsets after reverse priority (highest prio are merged in last)
cmdsets = sorted(cmdsets, key=lambda x: x.priority)
if cmdsets:
# Merge all command sets into one, beginning with the lowest-prio one
cmdset = cmdsets.pop(0)
for merging_cmdset in cmdsets:
#print "<%s(%s,%s)> onto <%s(%s,%s)>" % (merging_cmdset.key, merging_cmdset.priority, merging_cmdset.mergetype,
# cmdset.key, cmdset.priority, cmdset.mergetype)
cmdset = merging_cmdset + cmdset
else:
cmdset = None
return cmdset
def match_command(cmd_candidates, cmdset, logged_caller=None):
"""
@ -365,17 +345,6 @@ def cmdhandler(caller, raw_string, unloggedin=False, testing=False):
cmd_candidate.args)
raise ExecSystemCommand(cmd, sysarg)
# Check if this is an Exit match.
if hasattr(cmd, 'is_exit') and cmd.is_exit:
# even if a user-defined syscmd is not defined, the
# found cmd is already a system command in its own right.
syscmd = cmdset.get(CMD_EXIT)
if syscmd:
# replace system command with custom version
cmd = syscmd
sysarg = raw_string
raise ExecSystemCommand(cmd, sysarg)
# A normal command.
# Assign useful variables to the instance

View file

@ -237,7 +237,6 @@ class CmdSet(object):
cmds = [instantiate(cmd)]
else:
cmds = instantiate(cmd)
for cmd in cmds:
# add all commands
if not hasattr(cmd, 'obj'):
@ -247,8 +246,9 @@ class CmdSet(object):
self.commands[ic] = cmd # replace
except ValueError:
self.commands.append(cmd)
# extra run to make sure to avoid doublets
# extra run to make sure to avoid doublets
self.commands = list(set(self.commands))
#print "In cmdset.add(cmd):", self.key, cmd
def remove(self, cmd):
"""

View file

@ -136,6 +136,8 @@ class CmdSetObjAlias(MuxCommand):
aliases = list(set(old_aliases))
# save back to object.
obj.aliases = aliases
# we treat this as a re-caching (relevant for exits to re-build their exit commands with the correct aliases)
obj.at_cache()
caller.msg("Aliases for '%s' are now set to %s." % (obj.key, ", ".join(obj.aliases)))
class CmdCopy(ObjManipCommand):
@ -175,7 +177,7 @@ class CmdCopy(ObjManipCommand):
return
to_obj_name = "%s_copy" % from_obj_name
to_obj_aliases = ["%s_copy" % alias for alias in from_obj.aliases]
copiedobj = ObjectDB.objects.copy_object(from_obj, new_name=to_obj_name,
copiedobj = ObjectDB.objects.copy_object(from_obj, new_key=to_obj_name,
new_aliases=to_obj_aliases)
if copiedobj:
string = "Identical copy of %s, named '%s' was created." % (from_obj_name, to_obj_name)
@ -633,6 +635,8 @@ class CmdDig(ObjManipCommand):
typeclass = "%s.%s" % (settings.BASE_TYPECLASS_PATH,
typeclass)
# create room
lockstring = "control:id(%s) or perm(Immortal); delete:id(%s) or perm(Wizard); edit:id(%s) or perm(Wizard)"
lockstring = lockstring % (caller.dbref, caller.dbref, caller.dbref)
@ -644,6 +648,9 @@ class CmdDig(ObjManipCommand):
alias_string = " (%s)" % ", ".join(new_room.aliases)
room_string = "Created room %s(%s)%s of type %s." % (new_room, new_room.dbref, alias_string, typeclass)
# create exit to room
exit_to_string = ""
exit_back_string = ""
@ -667,17 +674,17 @@ class CmdDig(ObjManipCommand):
typeclass.startswith('game.')):
typeclass = "%s.%s" % (settings.BASE_TYPECLASS_PATH,
typeclass)
new_to_exit = create.create_object(typeclass, to_exit["name"],
location,
aliases=to_exit["aliases"])
new_to_exit.destination = new_room
new_to_exit.locks.add(lockstring)
new_to_exit = create.create_object(typeclass, to_exit["name"], location,
aliases=to_exit["aliases"],
locks=lockstring, destination=new_room)
alias_string = ""
if new_to_exit.aliases:
alias_string = " (%s)" % ", ".join(new_to_exit.aliases)
exit_to_string = "\nCreated Exit from %s to %s: %s(%s)%s."
exit_to_string = exit_to_string % (location.name, new_room.name, new_to_exit,
new_to_exit.dbref, alias_string)
# Create exit back from new room
if len(self.rhs_objs) > 1:
# Building the exit back to the current room
@ -700,10 +707,8 @@ class CmdDig(ObjManipCommand):
typeclass = "%s.%s" % (settings.BASE_TYPECLASS_PATH,
typeclass)
new_back_exit = create.create_object(typeclass, back_exit["name"],
new_room,
aliases=back_exit["aliases"])
new_back_exit.destination = location
new_back_exit.locks.add(lockstring)
new_room, aliases=back_exit["aliases"],
locks=lockstring, destination=location)
alias_string = ""
if new_back_exit.aliases:
alias_string = " (%s)" % ", ".join(new_back_exit.aliases)
@ -1534,7 +1539,7 @@ class CmdExamine(ObjManipCommand):
perms = ["<Superuser>"]
elif not perms:
perms = ["<None>"]
string += headers["perms"] % (", ".join(perms))
string += headers["playerperms"] % (", ".join(perms))
string += headers["typeclass"] % (obj.typeclass, obj.typeclass_path)
if hasattr(obj, "location"):

View file

@ -11,6 +11,7 @@ class UnloggedinCmdSet(CmdSet):
Sets up the unlogged cmdset.
"""
key = "Unloggedin"
priority = 0
def at_cmdset_creation(self):
"Populate the cmdset"

View file

@ -613,13 +613,15 @@ class CmdOOCLook(CmdLook):
help_cateogory = "General"
def func(self):
"implement the command"
"implement the ooc look command"
self.character = None
self.character = None
if utils.inherits_from(self.caller, "src.objects.objects.Object"):
# An object of some type is calling. Convert to player.
print self.caller, self.caller.__class__
self.character = self.caller
self.caller = self.caller.player
if hasattr(self.caller, "player"):
self.caller = self.caller.player
if not self.character:
string = "You are out-of-character (OOC). "
@ -627,7 +629,7 @@ class CmdOOCLook(CmdLook):
self.caller.msg(string)
else:
self.caller = self.character # we have to put this back for normal look to work.
super(CmdLook, self).func()
super(CmdOOCLook, self).func()
class CmdIC(MuxCommand):
"""

View file

@ -136,7 +136,7 @@ class MuxCommand(Command):
string += "-" * 50
string += "\nname of cmd (self.key): {w%s{n\n" % self.key
string += "cmd aliases (self.aliases): {w%s{n\n" % self.aliases
string += "cmd perms (self.permissions): {w%s{n\n" % self.permissions
string += "cmd locks (self.locks): {w%s{n\n" % self.locks
string += "help category (self.help_category): {w%s{n\n" % self.help_category
string += "object calling (self.caller): {w%s{n\n" % self.caller
string += "object storing cmdset (self.obj): {w%s{n\n" % self.obj

View file

@ -28,7 +28,6 @@ from src.commands.cmdhandler import CMD_NOMATCH
from src.commands.cmdhandler import CMD_MULTIMATCH
from src.commands.cmdhandler import CMD_NOPERM
from src.commands.cmdhandler import CMD_CHANNEL
from src.commands.cmdhandler import CMD_EXIT
from src.commands.default.muxcommand import MuxCommand
@ -188,37 +187,3 @@ class SystemSendToChannel(MuxCommand):
msg = "[%s] %s: %s" % (channel.key, caller.name, msg)
msgobj = create.create_message(caller, msg, channels=[channel])
channel.msg(msgobj)
#
# Command called when the system recognizes the command given
# as matching an exit from the room. E.g. if there is an exit called 'door'
# and the user gives the command
# > door
# the exit 'door' should be traversed to its destination.
class SystemUseExit(MuxCommand):
"""
Handles what happens when user gives a valid exit
as a command. It receives the raw string as input.
"""
key = CMD_EXIT
locks = "cmd:all()"
def func(self):
"""
Handle traversing an exit
"""
caller = self.caller
if not self.args:
return
exit_name = self.args
exi = caller.search(exit_name)
if not exi:
return
destination = exi.destination
if not destination:
return
if exit.access(caller, 'traverse'):
caller.move_to(destination)
else:
caller.msg("You cannot enter")

View file

@ -1,104 +0,0 @@
"""
This handler creates cmdsets on the fly, by searching
an object's location for valid exit objects.
"""
from src.commands import cmdset, command
class ExitCommand(command.Command):
"Simple identifier command"
is_exit = True
locks = "cmd:all()" # should always be set to this.
destination = None
obj = None
def func(self):
"Default exit traverse if no syscommand is defined."
if self.obj.access(self.caller, 'traverse'):
# we may traverse the exit.
old_location = None
if hasattr(self.caller, "location"):
old_location = self.caller.location
# call pre/post hooks and move object.
self.obj.at_before_traverse(self.caller)
self.caller.move_to(self.destination)
self.obj.at_after_traverse(self.caller, old_location)
else:
if self.obj.db.err_traverse:
# if exit has a better error message, let's use it.
self.caller.msg(self.obj.db.err_traverse)
else:
# No shorthand error message. Call hook.
self.obj.at_fail_traverse(self.caller)
class ExitHandler(object):
"""
The exithandler auto-creates 'commands' to represent exits in the
room. It is called by cmdhandler when building its index of all
viable commands. This allows for exits to be processed along with
all other inputs the player gives to the game. The handler tries
to intelligently cache exit objects to cut down on processing.
"""
def __init__(self):
"Setup cache storage"
self.cached_exit_cmds = {}
def clear(self, exitcmd=None):
"""
Reset cache storage. If obj is given, only remove
that object from cache.
"""
if exitcmd:
# delete only a certain object from cache
try:
del self.cached_exit_cmds[exitcmd.id]
except KeyError:
pass
return
# reset entire cache
self.cached_exit_cmds = {}
def get_cmdset(self, srcobj):
"""
Search srcobjs location for valid exits, and
return objects as stored in command set
"""
# create a quick "throw away" cmdset
exit_cmdset = cmdset.CmdSet(None)
exit_cmdset.key = '_exitset'
exit_cmdset.priority = 9
exit_cmdset.duplicates = True
try:
location = srcobj.location
except Exception:
location = None
if not location:
# there can be no exits if there's no location
return exit_cmdset
# use exits to create searchable "commands" for the cmdhandler
for exi in location.exits:
if exi.id in self.cached_exit_cmds:
# retrieve from cache
exit_cmdset.add(self.cached_exit_cmds[exi.id])
else:
# not in cache, create a new exit command
cmd = ExitCommand()
cmd.key = exi.name.strip().lower()
cmd.obj = exi
if exi.aliases:
cmd.aliases = exi.aliases
cmd.destination = exi.destination
exit_cmdset.add(cmd)
self.cached_exit_cmds[exi.id] = cmd
return exit_cmdset
# The actual handler - call this to get exits
EXITHANDLER = ExitHandler()

View file

@ -274,25 +274,26 @@ class ObjectManager(TypedObjectManager):
# ObjectManager Copy method
#
def copy_object(self, original_object, new_name=None,
def copy_object(self, original_object, new_key=None,
new_location=None, new_player=None, new_home=None,
new_permissions=None, new_locks=None, new_aliases=None):
new_permissions=None, new_locks=None, new_aliases=None, new_destination=None):
"""
Create and return a new object as a copy of the source object. All will
Create and return a new object as a copy of the original object. All will
be identical to the original except for the arguments given specifically
to this method.
original_object (obj) - the object to make a copy from
new_name (str) - name the copy differently from the original.
new_key (str) - name the copy differently from the original.
new_location (obj) - if not None, change the location
new_home (obj) - if not None, change the Home
new_aliases (list of strings) - if not None, change object aliases.
new_destination (obj) - if not None, change destination
"""
# get all the object's stats
typeclass_path = original_object.typeclass_path
if not new_name:
new_name = original_object.key
if not new_key:
new_key = original_object.key
if not new_location:
new_location = original_object.location
if not new_home:
@ -305,13 +306,15 @@ class ObjectManager(TypedObjectManager):
new_locks = original_object.db_lock_storage
if not new_permissions:
new_permissions = original_object.permissions
if not new_destination:
new_destination = original_object.destination
# create new object
from src.utils import create
from src.scripts.models import ScriptDB
new_object = create.create_object(typeclass_path, new_name, new_location,
new_home, new_player, new_permissions,
new_locks, new_aliases)
new_object = create.create_object(typeclass_path, key=new_key, location=new_location,
home=new_home, player=new_player, permissions=new_permissions,
locks=new_locks, aliases=new_aliases, destination=new_destination)
if not new_object:
return None

View file

@ -449,12 +449,12 @@ class ObjectDB(TypedObject):
has_player = property(has_player_get)
#@property
def contents_get(self):
def contents_get(self, exclude=None):
"""
Returns the contents of this object, i.e. all
objects that has this object set as its location.
"""
return ObjectDB.objects.get_contents(self)
return ObjectDB.objects.get_contents(self, excludeobj=exclude)
contents = property(contents_get)
#@property
@ -748,6 +748,17 @@ class ObjectDB(TypedObject):
obj.msg(string)
obj.move_to(home)
def copy(self, new_key=None):
"""
Makes an identical copy of this object and returns
it. The copy will be named <key>_copy by default. If you
want to customize the copy by changing some settings, use
the manager method copy_object directly.
"""
if not new_key:
new_key = "%s_copy" % self.key
return ObjectDB.objects.copy_object(self, new_key=new_key)
def delete(self):
"""
Deletes this object.

View file

@ -17,8 +17,6 @@ they control by simply linking to a new object's user property.
from src.typeclasses.typeclass import TypeClass
from src.commands import cmdset, command
from src.objects.exithandler import EXITHANDLER
#
# Base class to inherit from.
@ -77,6 +75,12 @@ class Object(TypeClass):
"""
pass
def at_cache(self):
"""
Called whenever this object is cached or reloaded.
"""
pass
def at_first_login(self):
"""
Only called once, the very first
@ -401,49 +405,113 @@ class Room(Object):
class Exit(Object):
"""
This is the base exit object - it connects a location
to another. What separates it from other objects
is that it has the 'destination' property defined.
Note that property is the only identifier to
separate an exit from normal objects, so if the property
is removed, it will be treated like any other object. This
also means that any object can be made an exit by setting
the property destination to a valid location
('Quack like a duck...' and so forth).
This is the base exit object - it connects a location to
another. This is done by the exit assigning a "command" on itself
with the same name as the exit object (to do this we need to
remember to re-create the command when the object is cached since it must be
created dynamically depending on what the exit is called). This
command (which has a high priority) will thus allow us to traverse exits
simply by giving the exit-object's name on its own.
"""
# Helper classes and methods to implement the Exit. These need not
# be overloaded unless one want to change the foundation for how
# Exits work. See the end of the class for hook methods to overload.
def create_exit_cmdset(self, exidbobj):
"""
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.
"""
class ExitCommand(command.Command):
"""
This is a command that simply cause the caller
to traverse the object it is attached to.
"""
locks = "cmd:all()" # should always be set to this.
obj = None
def func(self):
"Default exit traverse if no syscommand is defined."
if self.obj.access(self.caller, 'traverse'):
# we may traverse the exit.
old_location = None
if hasattr(self.caller, "location"):
old_location = self.caller.location
# call pre/post hooks and move object.
self.obj.at_before_traverse(self.caller)
self.caller.move_to(self.obj.destination)
self.obj.at_after_traverse(self.caller, old_location)
else:
if self.obj.db.err_traverse:
# if exit has a better error message, let's use it.
self.caller.msg(self.obj.db.err_traverse)
else:
# No shorthand error message. Call hook.
self.obj.at_failed_traverse(self.caller)
# create an exit command.
cmd = ExitCommand()
cmd.key = exidbobj.db_key.strip().lower()
cmd.obj = exidbobj
cmd.aliases = exidbobj.aliases
cmd.destination = exidbobj.db_destination
# create a cmdset
exit_cmdset = cmdset.CmdSet(None)
exit_cmdset.key = '_exitset'
exit_cmdset.priority = 9
exit_cmdset.duplicates = True
# add command to cmdset
exit_cmdset.add(cmd)
return exit_cmdset
# Command hooks
def basetype_setup(self):
"""
Setup exit-security
Don't change this, instead edit at_object_creation() to
overload the defaults (it is called after this one).
overload the default locks (it is called after this one).
"""
# the lock is open to all by default
super(Exit, self).basetype_setup()
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
# 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_object_delete(self):
"""
We have to make sure to clean the exithandler cache
when deleting the exit, or a new exit could be created
out of sync with the cache. You should do this also if
overloading this function in a child class.
"""
EXITHANDLER.clear(self.dbobj)
return True
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)
# this and other hooks are what usually can be modified safely.
def at_object_creation(self):
"Called once, when object is first created (after basetype_setup)."
pass
def at_failed_traverse(self, traversing_object):
"""
This is called if an object fails to traverse this object for some
reason. It will not be called if the attribute "err_traverse" is defined,
that attribute will then be echoed back instead as a convenient shortcut.
(See also hooks at_before_traverse and at_after_traverse).
"""
traversing_object.msg("You cannot enter %s." % self.key)
traversing_object.msg("You cannot go there.")

View file

@ -0,0 +1,108 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models, utils
from django.conf import settings
class Migration(DataMigration):
def forwards(self, orm):
"Write your forwards methods here."
try:
for player in orm.PlayerDB.objects.all():
if not player.db_cmdset_storage:
player.db_cmdset_storage = settings.CMDSET_OOC
player.save()
except utils.DatabaseError:
# this will happen if we start db from scratch (ignore in that case)
pass
def backwards(self, orm):
"Write your backwards methods here."
raise RuntimeError("This migration cannot be reverted.")
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'objects.objectdb': {
'Meta': {'object_name': 'ObjectDB'},
'db_cmdset_storage': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'db_destination': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'destinations_set'", 'null': 'True', 'to': "orm['objects.ObjectDB']"}),
'db_home': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'homes_set'", 'null': 'True', 'to': "orm['objects.ObjectDB']"}),
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'db_location': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'locations_set'", 'null': 'True', 'to': "orm['objects.ObjectDB']"}),
'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'db_permissions': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}),
'db_player': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['players.PlayerDB']", 'null': 'True', 'blank': 'True'}),
'db_typeclass_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'players.playerattribute': {
'Meta': {'object_name': 'PlayerAttribute'},
'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['players.PlayerDB']"}),
'db_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'players.playerdb': {
'Meta': {'object_name': 'PlayerDB'},
'db_cmdset_storage': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['objects.ObjectDB']", 'null': 'True'}),
'db_permissions': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}),
'db_typeclass_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'})
},
'players.playernick': {
'Meta': {'unique_together': "(('db_nick', 'db_type', 'db_obj'),)", 'object_name': 'PlayerNick'},
'db_nick': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['players.PlayerDB']"}),
'db_real': ('django.db.models.fields.TextField', [], {}),
'db_type': ('django.db.models.fields.CharField', [], {'default': "'inputline'", 'max_length': '16', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
}
}
complete_apps = ['players']

View file

@ -57,7 +57,7 @@ def create_objects():
user=god_user)
god_character.id = 1
god_character.db.desc = 'This is User #1.'
god_character.locks.add("examine:perm(Immortals);edit:false();delete:false();boot:false();msg:all()")
god_character.locks.add("examine:perm(Immortals);edit:false();delete:false();boot:false();msg:all();puppet:false()")
god_character.save()

View file

@ -29,7 +29,8 @@ from src.utils.utils import is_iter, has_parent
#
def create_object(typeclass, key=None, location=None,
home=None, player=None, permissions=None, locks=None, aliases=None):
home=None, player=None, permissions=None, locks=None,
aliases=None, destination=None):
"""
Create a new in-game object. Any game object is a combination
of a database object that stores data persistently to
@ -89,6 +90,8 @@ def create_object(typeclass, key=None, location=None,
# link a player and the object together
new_object.player = player
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

View file

@ -101,6 +101,8 @@ class SharedMemoryModel(Model):
"""
if instance._get_pk_val() is not None:
cls.__instance_cache__[instance._get_pk_val()] = instance
if hasattr(instance, 'at_cache') and callable(instance.at_cache):
instance.at_cache()
#key = "%s-%s" % (cls, instance.pk)
#TCACHE[key] = instance
#print "cached: %s (%s: %s) (total cached: %s)" % (instance, cls.__name__, len(cls.__instance_cache__), len(TCACHE))

View file

@ -17,7 +17,6 @@ from src.comms.models import Channel, Msg
from src.help.models import HelpEntry
from src.typeclasses import models as typeclassmodels
from src.objects import exithandler
from src.comms import channelhandler
from src.comms.models import Channel
from src.utils import reimport, utils, logger
@ -34,12 +33,7 @@ def start_reload_loop():
""
cemit_info('-'*50)
cemit_info(" Starting asynchronous server reload.")
reload_modules() # this must be given time to finish
wait_time = 5
cemit_info(" Waiting %ss to give modules time to fully re-cache ..." % wait_time)
time.sleep(wait_time)
reload_modules()
reload_scripts()
reload_commands()
reset_loop()
@ -49,7 +43,7 @@ def start_reload_loop():
cemit_info(" Asynchronous server reload finished.\n" + '-'*50)
def at_err(e):
"error callback"
string = "Reload: Asynchronous reset exited with an error:\n{r%s{n" % e.getErrorMessage()
string = " Reload: Asynchronous reset exited with an error:\n {r%s{n" % e.getErrorMessage()
cemit_info(string)
utils.run_async(run_loop, at_return, at_err)
@ -89,13 +83,13 @@ def reload_modules():
"Check so modpath is not in an unsafe module"
return not any(mpath.startswith(modpath) for mpath in unsafe_modules)
cemit_info(" Cleaning module caches ...")
#cemit_info(" Cleaning module caches ...")
# clean as much of the caches as we can
cache = AppCache()
cache.app_store = SortedDict()
#cache.app_models = SortedDict() # cannot clean this, it resets ContentTypes!
cache.app_errors = {}
cache.app_errors = {}
cache.handled = {}
cache.loaded = False
@ -108,29 +102,28 @@ def reload_modules():
string = ""
if unsafe_dir_modified or unsafe_mod_modified:
if unsafe_dir_modified:
string += "\n-{rThe following changed module(s) can only be reloaded{n"
string += "\n {rby a server reboot:{n\n %s\n"
string = string % unsafe_dir_modified
if unsafe_mod_modified:
string += "\n-{rThe following modules contains at least one Script class with a timer{n"
string += "\n {rcomponent and has already spawned instances - these cannot be{n "
string += "\n {rsafely cleaned from memory on the fly. Stop all the affected scripts{n "
string += "\n {ror restart the server to safely reload:{n\n %s\n"
string = string % unsafe_mod_modified
string += "\n {rModules containing Script classes with a timer component"
string += "\n and which has already spawned instances cannot be reloaded safely.{n"
string += "\n {rThese module(s) can only be reloaded by server reboot:{n\n %s\n"
string = string % ", ".join(unsafe_dir_modified + unsafe_mod_modified)
if string:
cemit_info(string)
if safe_modified:
cemit_info(" Reloading module(s):\n %s ..." % safe_modified)
cemit_info(" Reloading safe module(s):{n\n %s" % "\n ".join(safe_modified))
reimport.reimport(*safe_modified)
wait_time = 5
cemit_info(" Waiting %s secs to give modules time to re-cache ..." % wait_time)
time.sleep(wait_time)
cemit_info(" ... all safe modules reloaded.")
else:
cemit_info(" ... no modules could be (or needed to be) reloaded.")
cemit_info(" No modules reloaded.")
# clean out cache dictionary of typeclasses, exits and channels
typeclassmodels.reset()
exithandler.EXITHANDLER.clear()
typeclassmodels.reset()
channelhandler.CHANNELHANDLER.update()
# run through all objects in database, forcing re-caching.
@ -161,23 +154,22 @@ def reload_scripts(scripts=None, obj=None, key=None,
def reload_commands():
from src.commands import cmdsethandler
cmdsethandler.CACHED_CMDSETS = {}
cemit_info(" Cleaned cmdset cache.")
#cemit_info(" Cleaned cmdset cache.")
def reset_loop():
"Reload and restart all entities that can be reloaded."
# run the reset loop on all objects
cemit_info(" Running resets on database entities ...")
cemit_info(" Resetting all cached database entities ...")
t1 = time.time()
[h.locks.reset() for h in HelpEntry.objects.all()]
[m.locks.reset() for m in Msg.objects.all()]
[c.locks.reset() for c in Channel.objects.all()]
[s.locks.reset() for s in ScriptDB.objects.all()]
[(o.typeclass(o), o.cmdset.reset(), o.locks.reset()) for o in ObjectDB.get_all_cached_instances()]
[(o.typeclass(o), o.cmdset.reset(), o.locks.reset(), o.at_cache()) for o in ObjectDB.get_all_cached_instances()]
[(p.typeclass(p), p.cmdset.reset(), p.locks.reset()) for p in PlayerDB.get_all_cached_instances()]
t2 = time.time()
cemit_info(" ... Loop finished in %g seconds." % (t2-t1))
cemit_info(" ... Reset finished in %g seconds." % (t2-t1))
def cemit_info(message):
"""