From 1a6ef5d9835bf49a1b94ef929fda938f90d59ef3 Mon Sep 17 00:00:00 2001 From: Griatch Date: Thu, 26 Apr 2012 13:38:34 +0200 Subject: [PATCH] Added more caching to channelhandler as well as players in order to cut back on unnecessary database calls. --- src/commands/default/comms.py | 1 + src/comms/channelhandler.py | 67 +++++++++++++++++++---------------- src/locks/lockhandler.py | 1 - src/objects/models.py | 2 ++ src/players/models.py | 47 +++++++++++++++++------- src/typeclasses/models.py | 2 +- 6 files changed, 75 insertions(+), 45 deletions(-) diff --git a/src/commands/default/comms.py b/src/commands/default/comms.py index 8551c06500..e9f969a2f0 100644 --- a/src/commands/default/comms.py +++ b/src/commands/default/comms.py @@ -410,6 +410,7 @@ class CmdCBoot(MuxCommand): nick.delete() # disconnect player channel.disconnect_from(player) + CHANNELHANDLER.update() class CmdCemit(MuxCommand): """ diff --git a/src/comms/channelhandler.py b/src/comms/channelhandler.py index 669f63b3db..cede1ff374 100644 --- a/src/comms/channelhandler.py +++ b/src/comms/channelhandler.py @@ -10,7 +10,7 @@ to just write For this to work, 'newbie', the name of the channel, must be identified by the cmdhandler as a command name. The channelhandler stores all channels as custom 'commands' -that the cmdhandler can import and look through. +that the cmdhandler can import and look through. Warning - channel names take precedence over command names, so make sure to not pick clashing channel names. @@ -20,7 +20,7 @@ the channelhandler at all - the create_channel method handles the update. To delete a channel cleanly, delete the channel object, then call update() on the channelhandler. Or use Channel.objects.delete() which -does this for you. +does this for you. """ from src.comms.models import Channel, Msg @@ -32,7 +32,7 @@ class ChannelCommand(command.Command): Channel Usage: - + This is a channel. If you have subscribed to it, you can send to it by entering its name or alias, followed by the text you want to @@ -41,12 +41,12 @@ class ChannelCommand(command.Command): # this flag is what identifies this cmd as a channel cmd # and branches off to the system send-to-channel command # (which is customizable by admin) - is_channel = True + is_channel = True key = "general" help_category = "Channel Names" locks = "cmd:all()" - obj = None - + obj = None + def parse(self): """ Simple parser @@ -63,12 +63,12 @@ class ChannelCommand(command.Command): caller = self.caller if not msg: caller.msg("Say what?") - return + return channel = Channel.objects.get_channel(channelkey) if not channel: caller.msg("Channel '%s' not found." % channelkey) - return + return if not channel.has_connection(caller): string = "You are not connected to channel '%s'." caller.msg(string % channelkey) @@ -77,9 +77,9 @@ class ChannelCommand(command.Command): string = "You are not permitted to send to channel '%s'." caller.msg(string % channelkey) return - msg = "[%s] %s: %s" % (channel.key, caller.name, msg) + msg = "[%s] %s: %s" % (channel.key, caller.name, msg) # we can't use the utils.create function to make the Msg, - # since that creates an import recursive loop. + # since that creates an import recursive loop. try: sender = caller.player except AttributeError: @@ -88,19 +88,20 @@ class ChannelCommand(command.Command): msgobj = Msg(db_sender=sender, db_message=msg) msgobj.save() msgobj.channels = channel - # send new message object to channel + # send new message object to channel channel.msg(msgobj, from_obj=sender) class ChannelHandler(object): """ Handles the set of commands related to channels. """ - def __init__(self): + def __init__(self): self.cached_channel_cmds = [] + self.cached_cmdsets = {} def __str__(self): return ", ".join(str(cmd) for cmd in self.cached_channel_cmds) - + def clear(self): """ Reset the cache storage. @@ -114,57 +115,61 @@ class ChannelHandler(object): if not utils.is_iter(aliases): aliases = [aliases] ustring = "%s " % key.lower() + "".join(["\n %s " % alias.lower() for alias in aliases]) - desc = channel.desc + desc = channel.desc string = \ """ - Channel '%s' + Channel '%s' Usage (not including your personal aliases): %s %s """ % (key, ustring, desc) - return string - + return string + def add_channel(self, channel): """ Add an individual channel to the handler. This should be called whenever a new channel is created. To remove a channel, simply delete the channel object - and run self.update on the handler. + and run self.update on the handler. """ # map the channel to a searchable command cmd = ChannelCommand() cmd.key = channel.key.strip().lower() cmd.obj = channel cmd.__doc__= self._format_help(channel) - if channel.aliases: + if channel.aliases: cmd.aliases = channel.aliases cmd.lock_storage = "cmd:all();%s" % channel.locks cmd.lockhandler.reset() - self.cached_channel_cmds.append(cmd) + self.cached_cmdsets = {} def update(self): "Updates the handler completely." self.cached_channel_cmds = [] + self.cached_cmdsets = {} for channel in Channel.objects.all(): self.add_channel(channel) def get_cmdset(self, source_object): """ Retrieve cmdset for channels this source_object has - access to send to. + access to send to. """ - # create a temporary cmdset holding all channels - chan_cmdset = cmdset.CmdSet() - chan_cmdset.key = '_channelset' - chan_cmdset.priority = 10 - chan_cmdset.duplicates = True - - for cmd in [cmd for cmd in self.cached_channel_cmds - if cmd.access(source_object, 'listen')]: - chan_cmdset.add(cmd) - return chan_cmdset + if source_object in self.cached_cmdsets: + return self.cached_cmdsets[source_object] + else: + # create a new cmdset holding all channels + chan_cmdset = cmdset.CmdSet() + chan_cmdset.key = '_channelset' + chan_cmdset.priority = 10 + chan_cmdset.duplicates = True + for cmd in [cmd for cmd in self.cached_channel_cmds + if cmd.access(source_object, 'send')]: + chan_cmdset.add(cmd) + self.cached_cmdsets[source_object] = chan_cmdset + return chan_cmdset CHANNELHANDLER = ChannelHandler() diff --git a/src/locks/lockhandler.py b/src/locks/lockhandler.py index d0987e6d1b..01dc2be53f 100644 --- a/src/locks/lockhandler.py +++ b/src/locks/lockhandler.py @@ -193,7 +193,6 @@ class LockHandler(object): locks = {} if not storage_lockstring: return locks - nlocks = storage_lockstring.count(';') + 1 duplicates = 0 elist = [] # errors wlist = [] # warnings diff --git a/src/objects/models.py b/src/objects/models.py index 18e7237348..63c81c67e2 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -247,6 +247,8 @@ class ObjectDB(TypedObject): "Setter. Allows for self.player = value" if isinstance(player, TypeClass): player = player.dbobj + self.db_player = player + self.save() _set_cache(self, "player", player) #@player.deleter def __player_del(self): diff --git a/src/players/models.py b/src/players/models.py index da90e74716..3dca8c617f 100644 --- a/src/players/models.py +++ b/src/players/models.py @@ -46,6 +46,7 @@ from django.contrib.auth.models import User from django.utils.encoding import smart_str from django.contrib.contenttypes.models import ContentType +from src.typeclasses.models import _get_cache, _set_cache, _del_cache from src.server.sessionhandler import SESSIONS from src.players import manager from src.typeclasses.models import Attribute, TypedObject, TypeNick, TypeNickHandler @@ -57,6 +58,10 @@ __all__ = ("PlayerAttribute", "PlayerNick", "PlayerDB") _AT_SEARCH_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1)) +_GA = object.__getattribute__ +_SA = object.__setattr__ +_DA = object.__delattr__ + #------------------------------------------------------------ # # PlayerAttribute @@ -186,7 +191,7 @@ class PlayerDB(TypedObject): #@property def obj_get(self): "Getter. Allows for value = self.obj" - return self.db_obj + return _get_cache(self, "obj") #@obj.setter def obj_set(self, value): "Setter. Allows for self.obj = value" @@ -194,16 +199,14 @@ class PlayerDB(TypedObject): if isinstance(value, TypeClass): value = value.dbobj try: - self.db_obj = value - self.save() + _set_cache(self, "obj", value) except Exception: logger.log_trace() raise Exception("Cannot assign %s as a player object!" % value) #@obj.deleter def obj_del(self): "Deleter. Allows for del self.obj" - self.db_obj = None - self.save() + _del_cache(self, "obj") obj = property(obj_get, obj_set, obj_del) # whereas the name 'obj' is consistent with the rest of the code, @@ -212,18 +215,18 @@ class PlayerDB(TypedObject): #@property def character_get(self): "Getter. Allows for value = self.character" - return self.db_obj + return _get_cache(self, "obj") #@character.setter def character_set(self, value): "Setter. Allows for self.character = value" - self.obj = value + _set_cache(self, "obj", value) #@character.deleter def character_del(self): "Deleter. Allows for del self.character" - self.db_obj = None - self.save() + _del_cache(self, "obj") character = property(character_get, character_set, character_del) # cmdset_storage property + # This seems very sensitive to caching, so leaving it be for now /Griatch #@property def cmdset_storage_get(self): "Getter. Allows for value = self.name. Returns a list of cmdset_storage." @@ -265,16 +268,20 @@ class PlayerDB(TypedObject): _db_model_name = "playerdb" # used by attributes to safely store objects _default_typeclass_path = settings.BASE_PLAYER_TYPECLASS or "src.players.player.Player" - # name property (wraps self.user.username) + _name_cache = None + # name property (wraps self.user.username) #@property def name_get(self): "Getter. Allows for value = self.name" - return self.user.username + if not self._name_cache: + self._name_cache = self.user.username + return self._name_cache #@name.setter def name_set(self, value): "Setter. Allows for player.name = newname" self.user.username = value self.user.save() # this might be stopped by Django? + self._name_cache = value #@name.deleter def name_del(self): "Deleter. Allows for del self.name" @@ -282,6 +289,19 @@ class PlayerDB(TypedObject): name = property(name_get, name_set, name_del) key = property(name_get, name_set, name_del) + _uid_cache = None + #@property + def uid_get(self): + "Getter. Retrieves the user id" + if not self._uid_cache: + self._uid_cache = self.user.id + return self._uid_cache + def uid_set(self, value): + raise Exception("User id cannot be set!") + def uid_del(self): + raise Exception("User id cannot be deleted!") + uid = property(uid_get, uid_set, uid_del) + # sessions property #@property def sessions_get(self): @@ -298,9 +318,12 @@ class PlayerDB(TypedObject): sessions = property(sessions_get, sessions_set, sessions_del) #@property + _is_superuser_cache = None def is_superuser_get(self): "Superusers have all permissions." - return self.user.is_superuser + if self._is_superuser_cache == None: + self._is_superuser_cache = self.user.is_superuser + return self._is_superuser_cache is_superuser = property(is_superuser_get) # diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 3d0e497bac..3b73f1f905 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -65,7 +65,7 @@ def _get_cache(obj, name): if val: _SA(obj, "_cached_db_%s" % name, val) return val def _set_cache(obj, name, val): - "On-model Cache setter" + "On-model Cache setter. Also updates database." _SA(obj, "db_%s" % name, val) _GA(obj, "save")() _SA(obj, "_cached_db_%s" % name, val)