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 # Gather all cmdsets stored on objects in the room and
# also in the caller's inventory and the location itself # also in the caller's inventory and the location itself
local_objlist = yield (location.contents_get(exclude=obj) + 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] local_objlist = [o for o in local_objlist if not o._is_deleted]
for lobj in local_objlist: for lobj in local_objlist:
try: try:

View file

@ -171,8 +171,7 @@ class ObjectDBManager(TypedObjectManager):
@returns_typeclass_list @returns_typeclass_list
def get_contents(self, location, excludeobj=None): def get_contents(self, location, excludeobj=None):
""" """
Get all objects that has a location Get all objects that has a location set to this one.
set to this one.
excludeobj - one or more object keys to exclude from the match 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.typeclasses.models import TypedObject
from evennia.objects.manager import ObjectDBManager from evennia.objects.manager import ObjectDBManager
from evennia.utils import logger 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 # ObjectDB
@ -105,6 +178,10 @@ class ObjectDB(TypedObject):
# Database manager # Database manager
objects = ObjectDBManager() objects = ObjectDBManager()
@lazy_property
def contents_cache(self):
return ContentsHandler(self)
# cmdset_storage property handling # cmdset_storage property handling
def __cmdset_storage_get(self): def __cmdset_storage_get(self):
"getter" "getter"
@ -152,9 +229,27 @@ class ObjectDB(TypedObject):
is_loc_loop(location) is_loc_loop(location)
except RuntimeWarning: except RuntimeWarning:
pass 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.db_location = location
self.save(update_fields=["db_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: except RuntimeError:
errmsg = "Error: %s.location = %s creates a location loop." % (self.key, location) errmsg = "Error: %s.location = %s creates a location loop." % (self.key, location)
logger.log_errmsg(errmsg) logger.log_errmsg(errmsg)
@ -170,6 +265,20 @@ class ObjectDB(TypedObject):
self.save(update_fields=["db_location"]) self.save(update_fields=["db_location"])
location = property(__location_get, __location_set, __location_del) 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: class Meta:
"Define Django meta options" "Define Django meta options"
verbose_name = "Object" verbose_name = "Object"

View file

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

View file

@ -73,7 +73,7 @@ class PortalSessionHandler(SessionHandler):
if not self.portal.amp_protocol: if not self.portal.amp_protocol:
# if amp is not yet ready (usually because the server is # if amp is not yet ready (usually because the server is
# booting up), try again a little later # booting up), try again a little later
reactor.CallLater(0.5, self.connect, session) reactor.callLater(0.5, self.connect, session)
return return
# sync with server-side # 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 # Chance of a currently unlogged-in dummy performing its login
# action every tick. This emulates not all players logging in # action every tick. This emulates not all players logging in
# at exactly the same time. # 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 # Which telnet port to connect to. If set to None, uses the first
# default telnet port of the running server. # default telnet port of the running server.