Restructured the way typeclasses are loaded. This makes it possible to run at_init() hooks at initiation also for objects without any custom cases for character/players. at_init() hooks are called only when an object is initiated. This means that a room's at_init() hook is only called when someone looks or enters it or a script operates on it, for example, rest of the time these objects are dormant, most efficiently.

This commit is contained in:
Griatch 2011-10-01 22:00:22 +02:00
parent 0a1bcd36c2
commit 23cd9e31b1
14 changed files with 142 additions and 109 deletions

View file

@ -123,7 +123,7 @@ class CommandTest(TestCase):
self.room1 = create.create_object(settings.BASE_ROOM_TYPECLASS, key="room1")
self.room2 = create.create_object(settings.BASE_ROOM_TYPECLASS, key="room2")
# create a faux player/character for testing.
self.char1 = create.create_player("TestChar", "testplayer@test.com", "testpassword", location=self.room1)
self.char1 = create.create_player("TestChar", "testplayer@test.com", "testpassword", character_location=self.room1)
self.char1.player.user.is_superuser = True
self.char1.lock_storage = ""
self.char1.locks = LockHandler(self.char1)
@ -132,7 +132,7 @@ class CommandTest(TestCase):
sess.connectionMade()
sess.session_login(self.char1.player)
# create second player
self.char2 = create.create_player("TestChar2", "testplayer2@test.com", "testpassword2", location=self.room1)
self.char2 = create.create_player("TestChar2", "testplayer2@test.com", "testpassword2", character_location=self.room1)
self.char2.player.user.is_superuser = False
self.char2.lock_storage = ""
self.char2.locks = LockHandler(self.char2)

View file

@ -161,9 +161,9 @@ class CmdCreate(MuxCommand):
new_character = create.create_player(playername, email, password,
permissions=permissions,
location=default_home,
typeclass=typeclass,
home=default_home)
character_typeclass=typeclass,
character_location=default_home,
character_home=default_home)
new_player = new_character.player
# character safety features

View file

@ -252,7 +252,7 @@ class ObjectDB(TypedObject):
"Getter. Allows for value = self.location."
loc = self.db_location
if loc:
return loc.typeclass(loc)
return loc.typeclass
return None
#@location.setter
def location_set(self, location):
@ -291,7 +291,7 @@ class ObjectDB(TypedObject):
"Getter. Allows for value = self.home"
home = self.db_home
if home:
return home.typeclass(home)
return home.typeclass
return None
#@home.setter
def home_set(self, home):
@ -329,7 +329,7 @@ class ObjectDB(TypedObject):
"Getter. Allows for value = self.destination."
dest = self.db_destination
if dest:
return dest.typeclass(dest)
return dest.typeclass
return None
#@destination.setter
def destination_set(self, destination):
@ -565,7 +565,7 @@ class ObjectDB(TypedObject):
if nick.db_nick in raw_list:
raw_string = raw_string.replace(nick.db_nick, nick.db_real, 1)
break
cmdhandler.cmdhandler(self.typeclass(self), raw_string)
cmdhandler.cmdhandler(self.typeclass, raw_string)
def msg(self, message, from_obj=None, data=None):
"""

View file

@ -76,9 +76,7 @@ class Object(TypeClass):
pass
def at_init(self):
"""
OBS: CURRENTLY NOT CALLED!
"""
This is always called whenever this
object initiated -- both when the object
is first created as well as after each restart.
@ -86,8 +84,7 @@ class Object(TypeClass):
if something should survive a warm-reboot (rebooting
the server without the players logging out), put it here.
"""
pass
pass
def basetype_posthook_setup(self):
"""

View file

@ -358,7 +358,7 @@ class PlayerDB(TypedObject):
if nick.db_nick in raw_list:
raw_string = raw_string.replace(nick.db_nick, nick.db_real, 1)
break
cmdhandler.cmdhandler(self.typeclass(self), raw_string)
cmdhandler.cmdhandler(self.typeclass, raw_string)
def search(self, ostring, global_search=False, attribute_name=None, use_nicks=False,
location=None, ignore_errors=False, player=False):

View file

@ -243,14 +243,14 @@ class ScriptDB(TypedObject):
#
#
# this is required to properly handle attrubutes and typeclass loading
# this is required to properly handle attributes and typeclass loading
attribute_model_path = "src.scripts.models"
attribute_model_name = "ScriptAttribute"
typeclass_paths = settings.SCRIPT_TYPECLASS_PATHS
# this is used by all typedobjects as a fallback
try:
default_typeclass_path = settings.DEFAULT_SCRIPT_TYPECLASS
default_typeclass_path = settings.BASE_SCRIPT_TYPECLASS
except:
default_typeclass_path = "src.scripts.scripts.DoNothing"

View file

@ -221,6 +221,9 @@ class ScriptClass(TypeClass):
return True
# hooks
def at_init(self):
"called every typeclass initiation (reloads/restarts). not much use for scripts."
pass
def at_script_creation(self):
"placeholder"
pass

View file

@ -54,9 +54,10 @@ def create_objects():
# is inherited from the user so we don't specify it again here.
god_character = create.create_player(god_user.username, None, None,
user=god_user,
create_character=True,
typeclass=character_typeclass,
user=god_user)
character_typeclass=character_typeclass)
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();puppet:false()")

View file

@ -150,8 +150,8 @@ class Evennia(object):
from src.players.models import PlayerDB
#print "run_init_hooks:", ObjectDB.get_all_cached_instances()
[(o.typeclass(o), o.at_init()) for o in ObjectDB.get_all_cached_instances()]
[(p.typeclass(p), p.at_init()) for p in PlayerDB.get_all_cached_instances()]
[(o.typeclass, o.at_init()) for o in ObjectDB.get_all_cached_instances()]
[(p.typeclass, p.at_init()) for p in PlayerDB.get_all_cached_instances()]
def terminal_output(self):
"""
@ -204,20 +204,20 @@ class Evennia(object):
if mode == 'reload':
# call restart hooks
[(o.typeclass(o), o.at_server_reload()) for o in ObjectDB.get_all_cached_instances()]
[(p.typeclass(p), p.at_server_reload()) for p in PlayerDB.get_all_cached_instances()]
[(s.typeclass(s), s.pause(), s.at_server_reload()) for s in ScriptDB.get_all_cached_instances()]
[(o.typeclass, o.at_server_reload()) for o in ObjectDB.get_all_cached_instances()]
[(p.typeclass, p.at_server_reload()) for p in PlayerDB.get_all_cached_instances()]
[(s.typeclass, s.pause(), s.at_server_reload()) for s in ScriptDB.get_all_cached_instances()]
ServerConfig.objects.conf("server_restart_mode", "reload")
else:
if mode == 'reset':
# don't call disconnect hooks on reset
[(o.typeclass(o), o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()]
[(o.typeclass, o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()]
else: # shutdown
[(o.typeclass(o), o.at_disconnect(), o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()]
[(p.typeclass(p), p.at_server_shutdown()) for p in PlayerDB.get_all_cached_instances()]
[(s.typeclass(s), s.at_server_shutdown()) for s in ScriptDB.get_all_cached_instances()]
[(o.typeclass, o.at_disconnect(), o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()]
[(p.typeclass, p.at_server_shutdown()) for p in PlayerDB.get_all_cached_instances()]
[(s.typeclass, s.at_server_shutdown()) for s in ScriptDB.get_all_cached_instances()]
ServerConfig.objects.conf("server_restart_mode", "reset")

View file

@ -48,16 +48,9 @@ class ServerSession(Session):
the session as it was.
"""
if not self.logged_in:
return
player = self.get_player()
return
character = self.get_character()
if player:
#print "sync: at_init() - player"
player.at_init()
if character:
#print "sync: at_init() - character"
character.at_init()
# start (persistent) scripts on this object
ScriptDB.objects.validate(obj=character)

View file

@ -192,6 +192,8 @@ BASE_CHARACTER_TYPECLASS = "game.gamesrc.objects.baseobjects.Character"
BASE_ROOM_TYPECLASS = "game.gamesrc.objects.baseobjects.Room"
# Typeclass for Exit objects (fallback)
BASE_EXIT_TYPECLASS = "game.gamesrc.objects.baseobjects.Exit"
# Typeclass for Scripts (fallback)
BASE_SCRIPT_TYPECLASS = "src.scripts.scripts.DoNothing"
# The home location for new characters. This must be a unique
# dbref (default is Limbo #2). If you want more advanced control over
# start locations, copy the "create" command from

View file

@ -55,7 +55,7 @@ def returns_typeclass_list(method):
obj_classes = []
for dbobj in match:
try:
obj_classes.append(dbobj.typeclass(dbobj))
obj_classes.append(dbobj.typeclass)
except Exception:
obj_classes.append(dbobj)
#logger.log_trace()

View file

@ -730,7 +730,7 @@ class TypedObject(SharedMemoryModel):
# try to look back to this very database object.)
typeclass = object.__getattribute__(self, 'typeclass')
if typeclass:
return object.__getattribute__(typeclass(self), propname)
return object.__getattribute__(typeclass, propname)
else:
raise AttributeError
@ -765,9 +765,10 @@ class TypedObject(SharedMemoryModel):
typeclass = object.__getattribute__(self, "cached_typeclass")
try:
if typeclass and object.__getattribute__(typeclass, "path") == path:
# don't call at_init() when returning from cache
return typeclass
except AttributeError:
pass
pass
errstring = ""
if not path:
@ -789,7 +790,12 @@ class TypedObject(SharedMemoryModel):
object.__setattr__(self, 'db_typeclass_path', tpath)
object.__getattribute__(self, 'save')()
object.__setattr__(self, "cached_typeclass_path", tpath)
object.__setattr__(self, "cached_typeclass", typeclass)
typeclass = typeclass(self)
object.__setattr__(self, "cached_typeclass", typeclass)
try:
typeclass.at_init()
except Exception:
logger.log_trace()
return typeclass
elif hasattr(typeclass, '__file__'):
errstring += "\n%s seems to be just the path to a module. You need" % tpath
@ -894,13 +900,19 @@ class TypedObject(SharedMemoryModel):
if not callable(typeclass):
# if this is still giving an error, Evennia is wrongly configured or buggy
raise Exception("CRITICAL ERROR: The final fallback typeclass %s cannot load!!" % defpath)
typeclass = typeclass(self)
if save:
object.__setattr__(self, 'db_typeclass_path', defpath)
object.__getattribute__(self, 'save')()
if cache:
object.__setattr__(self, "cached_typeclass_path", defpath)
object.__setattr__(self, "cached_typeclass", typeclass)
return typeclass
try:
typeclass.at_init()
except Exception:
logger.log_trace()
return typeclass
def is_typeclass(self, typeclass, exact=False):
"""
@ -919,13 +931,14 @@ class TypedObject(SharedMemoryModel):
typeclass = typeclass.path
except AttributeError:
pass
typeclasses = [typeclass] + ["%s.%s" % (path, typeclass) for path in self.typeclass_paths]
if exact:
current_path = object.__getattribute__(self, "cached_typeclass_path")
return typeclass and current_path == typeclass
return typeclass and any([current_path == typec for typec in typeclasses])
else:
# check parent chain
return any([cls for cls in self.typeclass.mro()
if "%s.%s" % (cls.__module__, cls.__name__) == typeclass])
if any(["%s.%s" % (cls.__module__, cls.__name__) == typec for typec in typeclasses])])
#
# Object manipulation methods
@ -967,7 +980,7 @@ class TypedObject(SharedMemoryModel):
self.save()
# this will automatically use a default class if
# there is an error with the given typeclass.
new_typeclass = self.typeclass(self)
new_typeclass = self.typeclass
if clean_attributes:
# Clean out old attributes

View file

@ -47,9 +47,11 @@ def create_object(typeclass, key=None, location=None,
from src.objects.objects import Object
from src.objects.models import ObjectDB
if isinstance(typeclass, ObjectDB):
if not typeclass:
typeclass = settings.BASE_OBJECT_TYPECLASS
elif isinstance(typeclass, ObjectDB):
# this is already an objectdb instance, extract its typeclass
typeclass = new_db_object.typeclass.path
typeclass = typeclass.typeclass.path
elif isinstance(typeclass, Object) or utils.inherits_from(typeclass, Object):
# this is already an object typeclass, extract its path
typeclass = typeclass.path
@ -60,28 +62,22 @@ def create_object(typeclass, key=None, location=None,
# assign the typeclass
typeclass = utils.to_unicode(typeclass)
new_db_object.typeclass_path = typeclass
# the name/key is often set later in the typeclass. This
# is set here as a failsafe.
if key:
new_db_object.key = key
else:
new_db_object.key = "#%i" % new_db_object.id
# this will either load the typeclass or the default one
typeclass = new_db_object.typeclass
new_object = new_db_object.typeclass
if not object.__getattribute__(new_db_object, "is_typeclass")(typeclass, exact=True):
# this will fail if we gave a typeclass as input and it still gave us a default
SharedMemoryModel.delete(new_db_object)
return None
# the name/key is often set later in the typeclass. This
# is set here as a failsafe.
if key:
new_db_object.name = key
else:
dbref = new_db_object.id
if typeclass and hasattr(typeclass, '__name__'):
new_db_object.name = "%s%i" % (typeclass.__name__, dbref)
else:
new_db_object.name = "#%i" % dbref
# initialize an object of this typeclass.
new_object = typeclass(new_db_object)
# from now on we can use the typeclass object
# as if it was the database object.
@ -151,39 +147,37 @@ def create_script(typeclass, key=None, obj=None, locks=None, autostart=True):
#print "in create_script", typeclass
from src.scripts.models import ScriptDB
if isinstance(typeclass, ScriptDB):
# this is already an objectdb instance, extract its typeclass
if not typeclass:
typeclass = settings.BASE_SCRIPT_TYPECLASS
elif isinstance(typeclass, ScriptDB):
# this is already an scriptdb instance, extract its typeclass
typeclass = new_db_object.typeclass.path
elif isinstance(typeclass, Script) or utils.inherits_from(typeclass, Script):
# this is already an object typeclass, extract its path
typeclass = typeclass.path
# create new database object
new_db_object = ScriptDB()
# create new database script
new_db_script = ScriptDB()
# assign the typeclass
typeclass = utils.to_unicode(typeclass)
new_db_object.typeclass_path = typeclass
new_db_script.typeclass_path = typeclass
# the name/key is often set later in the typeclass. This
# is set here as a failsafe.
if key:
new_db_script.key = key
else:
new_db_script.key = "#%i" % new_db_script.id
# this will either load the typeclass or the default one
typeclass = new_db_object.typeclass
new_script = new_db_script.typeclass
if not object.__getattribute__(new_db_object, "is_typeclass")(typeclass, exact=True):
# this can happen if the default was loaded (due to
# inability to load given typeclass), which we
# don't accept during creation.
SharedMemoryModel.delete(new_db_object)
if not object.__getattribute__(new_db_script, "is_typeclass")(typeclass, exact=True):
# this will fail if we gave a typeclass as input and it still gave us a default
print "failure:", new_db_script, typeclass
SharedMemoryModel.delete(new_db_script)
return None
# the typeclass is initialized
new_script = typeclass(new_db_object)
# store variables on the typeclass (which means
# it's actually transparently stored on the db object)
if not key:
if typeclass and hasattr(typeclass, '__name__'):
new_script.key = "%s" % typeclass.__name__
else:
new_script.key = "#%i" % new_db_object.id
if obj:
try:
@ -206,6 +200,8 @@ def create_script(typeclass, key=None, obj=None, locks=None, autostart=True):
# a new created script should usually be started.
if autostart:
new_script.start()
new_db_script.save()
return new_script
#
@ -345,22 +341,29 @@ def create_channel(key, aliases=None, desc=None,
#
def create_player(name, email, password,
permissions=None,
create_character=True,
location=None, typeclass=None, home=None,
is_superuser=False, user=None, locks=None):
user=None,
typeclass=None,
is_superuser=False,
locks=None, permissions=None,
create_character=True, character_typeclass=None,
character_location=None, character_home=None):
"""
This creates a new player, handling the creation of the User
object and its associated Player object. If create_character is
object and its associated Player object.
If create_character is
True, a game player object with the same name as the User/Player will
also be created. Returns the new game character, or the Player obj if no
also be created. Its typeclass and base properties can also be given.
Returns the new game character, or the Player obj if no
character is created. For more info about the typeclass argument,
see create_objects() above.
Note: if user is supplied, it will NOT be modified (args name, email,
passw and is_superuser will be ignored). Change those properties
explicitly instead.
directly on the User instead.
If no permissions are given (None), the default permission group
as defined in settings.PERMISSION_PLAYER_DEFAULT will be
@ -368,16 +371,15 @@ def create_player(name, email, password,
occur.
Concerning is_superuser:
A superuser should have access to everything
in the game and on the server/web interface. The very first user
created in the database is always a superuser (that's using
django's own creation, not this one).
Usually only the server admin should need to be superuser, all
other access levels can be handled with more fine-grained
permissions or groups.
Since superuser overrules all permissions, we don't
set any here.
A superuser should have access to everything
in the game and on the server/web interface. The very first user
created in the database is always a superuser (that's using
django's own creation, not this one).
Usually only the server admin should need to be superuser, all
other access levels can be handled with more fine-grained
permissions or groups.
Since superuser overrules all permissions, we don't
set any in this case.
"""
# The system should already have checked so the name/email
@ -385,6 +387,7 @@ def create_player(name, email, password,
# getting here.
from src.players.models import PlayerDB
from src.players.player import Player
if user:
new_user = user
@ -394,9 +397,30 @@ def create_player(name, email, password,
else:
new_user = User.objects.create_user(name, email, password)
# create the associated Player for this User, and tie them together
new_player = PlayerDB(db_key=name, user=new_user)
new_player.save()
if not typeclass:
typeclass = settings.BASE_PLAYER_TYPECLASS
elif isinstance(typeclass, PlayerDB):
# this is already an objectdb instance, extract its typeclass
typeclass = typeclass.typeclass.path
elif isinstance(typeclass, Player) or utils.inherits_from(typeclass, Player):
# this is already an object typeclass, extract its path
typeclass = typeclass.path
# create new database object
new_db_player = PlayerDB(db_key=name, user=new_user)
new_db_player.save()
# assign the typeclass
typeclass = utils.to_unicode(typeclass)
new_db_player.typeclass_path = typeclass
# this will either load the typeclass or the default one
new_player = new_db_player.typeclass
if not object.__getattribute__(new_db_player, "is_typeclass")(typeclass, exact=True):
# this will fail if we gave a typeclass as input and it still gave us a default
SharedMemoryModel.delete(new_db_player)
return None
new_player.basetype_setup() # setup the basic locks and cmdset
# call hook method (may override default permissions)
@ -413,12 +437,12 @@ def create_player(name, email, password,
# create *in-game* 'player' object
if create_character:
if not typeclass:
typeclass = settings.BASE_CHARACTER_TYPECLASS
if not character_typeclass:
character_typeclass = settings.BASE_CHARACTER_TYPECLASS
# creating the object automatically links the player
# and object together by player.obj <-> obj.player
new_character = create_object(typeclass, name,
location, home,
new_character = create_object(character_typeclass, key=name,
location=character_location, home=character_location,
permissions=permissions,
player=new_player)
return new_character