Implemented contents_cache handler for a speed boost for many situations, as per #620.
This commit is contained in:
parent
06fe2e5a9c
commit
b94bb17576
6 changed files with 118 additions and 10 deletions
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue