Implemented contents_cache handler for a speed boost for many situations, as per #620.

This commit is contained in:
Griatch 2015-02-28 11:29:05 +01:00
parent 06fe2e5a9c
commit b94bb17576
6 changed files with 118 additions and 10 deletions

View file

@ -171,7 +171,7 @@ def get_and_merge_cmdsets(caller, session, player, obj,
# Gather all cmdsets stored on objects in the room and
# also in the caller's inventory and the location itself
local_objlist = yield (location.contents_get(exclude=obj) +
obj.contents + [location])
obj.contents_get() + [location])
local_objlist = [o for o in local_objlist if not o._is_deleted]
for lobj in local_objlist:
try:

View file

@ -171,8 +171,7 @@ class ObjectDBManager(TypedObjectManager):
@returns_typeclass_list
def get_contents(self, location, excludeobj=None):
"""
Get all objects that has a location
set to this one.
Get all objects that has a location set to this one.
excludeobj - one or more object keys to exclude from the match
"""

View file

@ -20,9 +20,82 @@ from django.core.exceptions import ObjectDoesNotExist
from evennia.typeclasses.models import TypedObject
from evennia.objects.manager import ObjectDBManager
from evennia.utils import logger
from evennia.utils.utils import (make_iter, dbref)
from evennia.utils.utils import (make_iter, dbref, lazy_property)
class ContentsHandler(object):
"""
Handles and caches the contents of an object
to avoid excessive lookups (this is done very
often due to cmdhandler needing to look for
object-cmdsets). It is stored on the 'contents_cache'
property of the ObjectDB.
"""
def __init__(self, obj):
"""
Sets up the contents handler.
Args:
obj (Object): The object on which the
handler is defined
"""
self.obj = obj
self._cache = {}
self.init()
def init(self):
"""
Re-initialize the content cache
"""
self._cache.update(dict((obj.pk, obj) for obj in
ObjectDB.objects.filter(db_location=self.obj)))
def get(self, exclude=None):
"""
Return the contents of the cache.
Args:
exclude (Object or list of Object): object(s) to ignore
Returns:
objects (list): the Objects inside this location
"""
if exclude:
exclude = [excl.pk for excl in make_iter(exclude)]
return [obj for key, obj in self._cache.items() if key not in exclude]
return self._cache.values()
def add(self, obj):
"""
Add a new object to this location
Args:
obj (Object): object to add
"""
self._cache[obj.pk] = obj
def remove(self, obj):
"""
Remove object from this location
Args:
obj (Object): object to remove
"""
self._cache.pop(obj.pk, None)
def clear(self):
"""
Clear the contents cache and re-initialize
"""
self._cache = {}
self._init()
#------------------------------------------------------------
#
# ObjectDB
@ -105,6 +178,10 @@ class ObjectDB(TypedObject):
# Database manager
objects = ObjectDBManager()
@lazy_property
def contents_cache(self):
return ContentsHandler(self)
# cmdset_storage property handling
def __cmdset_storage_get(self):
"getter"
@ -152,9 +229,27 @@ class ObjectDB(TypedObject):
is_loc_loop(location)
except RuntimeWarning:
pass
# actually set the field
# if we get to this point we are ready to change location
old_location = self.db_location
# this is checked in _db_db_location_post_save below
self._safe_contents_update = True
# actually set the field (this will error if location is invalid)
self.db_location = location
self.save(update_fields=["db_location"])
# remove the safe flag
del self._safe_contents_update
# update the contents cache
if old_location:
old_location.contents_cache.remove(self)
if self.db_location:
self.db_location.contents_cache.add(self)
except RuntimeError:
errmsg = "Error: %s.location = %s creates a location loop." % (self.key, location)
logger.log_errmsg(errmsg)
@ -170,6 +265,20 @@ class ObjectDB(TypedObject):
self.save(update_fields=["db_location"])
location = property(__location_get, __location_set, __location_del)
def _db_location_post_save(self):
"""
This is called automatically after the location field was saved,
no matter how. It checks for a variable _safe_contents_update to
know if the save was triggered via the proper handler or not.
Since we cannot know at this point was old_location was, we
trigger a full-on contents_cache update here.
"""
if not hasattr(self, "_safe_contents_update"):
logger.log_warn("db_location direct save triggered contents_cache.init() for all objects!")
[o.contents_cache.init() for o in self.__dbclass__.get_all_cached_instances()]
class Meta:
"Define Django meta options"
verbose_name = "Object"

View file

@ -294,7 +294,7 @@ class DefaultObject(ObjectDB):
exclude is one or more objects to not return
"""
return ObjectDB.objects.get_contents(self, excludeobj=exclude)
return self.contents_cache.get(exclude=exclude)
contents = property(contents_get)
@ -709,7 +709,6 @@ class DefaultObject(ObjectDB):
location or to default home.
"""
# Gather up everything that thinks this is its location.
objs = ObjectDB.objects.filter(db_location=self)
default_home_id = int(settings.DEFAULT_HOME.lstrip("#"))
try:
default_home = ObjectDB.objects.get(id=default_home_id)
@ -721,7 +720,7 @@ class DefaultObject(ObjectDB):
log_errmsg(string % default_home_id)
default_home = None
for obj in objs:
for obj in self.contents:
home = obj.home
# Obviously, we can't send it back to here.
if not home or (home and home.dbid == self.dbid):
@ -824,6 +823,7 @@ class DefaultObject(ObjectDB):
self.attributes.clear()
self.nicks.clear()
self.aliases.clear()
self.location = None # this updates contents_cache for our location
# Perform the deletion of the object
super(ObjectDB, self).delete()

View file

@ -73,7 +73,7 @@ class PortalSessionHandler(SessionHandler):
if not self.portal.amp_protocol:
# if amp is not yet ready (usually because the server is
# booting up), try again a little later
reactor.CallLater(0.5, self.connect, session)
reactor.callLater(0.5, self.connect, session)
return
# sync with server-side

View file

@ -63,7 +63,7 @@ CHANCE_OF_ACTION = 0.5
# Chance of a currently unlogged-in dummy performing its login
# action every tick. This emulates not all players logging in
# at exactly the same time.
CHANCE_OF_LOGIN = 1.0#0.5
CHANCE_OF_LOGIN = 1.0
# Which telnet port to connect to. If set to None, uses the first
# default telnet port of the running server.