From 23cd9e31b165bccc73bf7ba08200b4ad93afaa0b Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 1 Oct 2011 22:00:22 +0200 Subject: [PATCH] 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. --- src/commands/default/tests.py | 4 +- src/commands/default/unloggedin.py | 6 +- src/objects/models.py | 8 +- src/objects/objects.py | 7 +- src/players/models.py | 2 +- src/scripts/models.py | 4 +- src/scripts/scripts.py | 3 + src/server/initial_setup.py | 5 +- src/server/server.py | 18 ++-- src/server/serversession.py | 9 +- src/settings_default.py | 2 + src/typeclasses/managers.py | 2 +- src/typeclasses/models.py | 27 +++-- src/utils/create.py | 154 +++++++++++++++++------------ 14 files changed, 142 insertions(+), 109 deletions(-) diff --git a/src/commands/default/tests.py b/src/commands/default/tests.py index a402ccf948..d475bf9308 100644 --- a/src/commands/default/tests.py +++ b/src/commands/default/tests.py @@ -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) diff --git a/src/commands/default/unloggedin.py b/src/commands/default/unloggedin.py index 2e6a9bb8fb..efdd2e4c1e 100644 --- a/src/commands/default/unloggedin.py +++ b/src/commands/default/unloggedin.py @@ -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 diff --git a/src/objects/models.py b/src/objects/models.py index 5ce79fbd66..d57c6f3922 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -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): """ diff --git a/src/objects/objects.py b/src/objects/objects.py index 847b894367..2c5264b7c8 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -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): """ diff --git a/src/players/models.py b/src/players/models.py index cbcc7c4c37..04f93a31f5 100644 --- a/src/players/models.py +++ b/src/players/models.py @@ -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): diff --git a/src/scripts/models.py b/src/scripts/models.py index f06c0f6320..b44a276035 100644 --- a/src/scripts/models.py +++ b/src/scripts/models.py @@ -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" diff --git a/src/scripts/scripts.py b/src/scripts/scripts.py index 1502533a9a..2ff9f1b8e4 100644 --- a/src/scripts/scripts.py +++ b/src/scripts/scripts.py @@ -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 diff --git a/src/server/initial_setup.py b/src/server/initial_setup.py index 33984c2259..d8a6d71b19 100644 --- a/src/server/initial_setup.py +++ b/src/server/initial_setup.py @@ -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()") diff --git a/src/server/server.py b/src/server/server.py index b2883da4cb..abf9f893b7 100644 --- a/src/server/server.py +++ b/src/server/server.py @@ -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") diff --git a/src/server/serversession.py b/src/server/serversession.py index 1be24a401c..e6eae3ac2f 100644 --- a/src/server/serversession.py +++ b/src/server/serversession.py @@ -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) diff --git a/src/settings_default.py b/src/settings_default.py index 64f747a651..52ee2933ff 100644 --- a/src/settings_default.py +++ b/src/settings_default.py @@ -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 diff --git a/src/typeclasses/managers.py b/src/typeclasses/managers.py index d2ea973ac0..aed7efdc6b 100644 --- a/src/typeclasses/managers.py +++ b/src/typeclasses/managers.py @@ -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() diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 93c9e609ab..e6f8436f89 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -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 diff --git a/src/utils/create.py b/src/utils/create.py index 58d5c55dbb..cd3d020b2a 100644 --- a/src/utils/create.py +++ b/src/utils/create.py @@ -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