Add a contents indexer to optimize inventory lookups.

This commit is contained in:
Andrew Bastien 2020-04-10 15:28:13 -07:00
parent a8a5453a97
commit 2fcb6e9466
2 changed files with 44 additions and 24 deletions

View file

@ -13,6 +13,7 @@ Attributes are separate objects that store values persistently onto
the database object. Like everything else, they can be accessed the database object. Like everything else, they can be accessed
transparently through the decorating TypeClass. transparently through the decorating TypeClass.
""" """
from collections import defaultdict
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
@ -42,8 +43,9 @@ class ContentsHandler(object):
""" """
self.obj = obj self.obj = obj
self._pkcache = {} self._pkcache = set()
self._idcache = obj.__class__.__instance_cache__ self._idcache = obj.__class__.__instance_cache__
self._typecache = defaultdict(set)
self.init() self.init()
def init(self): def init(self):
@ -51,25 +53,30 @@ class ContentsHandler(object):
Re-initialize the content cache Re-initialize the content cache
""" """
self._pkcache.update( objects = [obj for obj in ObjectDB.objects.filter(db_location=self.obj) if obj.pk]
dict((obj.pk, None) for obj in ObjectDB.objects.filter(db_location=self.obj) if obj.pk) self._pkcache = {obj.pk for obj in objects}
) for obj in objects:
for ctype in obj._content_types:
self._typecache[ctype].add(obj.pk)
def get(self, exclude=None): def get(self, exclude=None, category=None):
""" """
Return the contents of the cache. Return the contents of the cache.
Args: Args:
exclude (Object or list of Object): object(s) to ignore exclude (Object or list of Object): object(s) to ignore
category (str or None): Filter list by a content-type. If None, don't filter.
Returns: Returns:
objects (list): the Objects inside this location objects (list): the Objects inside this location
""" """
if exclude: if category is not None:
pks = [pk for pk in self._pkcache if pk not in [excl.pk for excl in make_iter(exclude)]] pks = self._typecache[category]
else: else:
pks = self._pkcache pks = self._pkcache
if exclude:
pks = pks - {excl.pk for excl in make_iter(exclude)}
try: try:
return [self._idcache[pk] for pk in pks] return [self._idcache[pk] for pk in pks]
except KeyError: except KeyError:
@ -91,7 +98,9 @@ class ContentsHandler(object):
obj (Object): object to add obj (Object): object to add
""" """
self._pkcache[obj.pk] = None self._pkcache.add(obj.pk)
for ctype in obj._content_types:
self._typecache[ctype].add(obj.pk)
def remove(self, obj): def remove(self, obj):
""" """
@ -101,7 +110,9 @@ class ContentsHandler(object):
obj (Object): object to remove obj (Object): object to remove
""" """
self._pkcache.pop(obj.pk, None) self._pkcache.remove(obj.pk)
for ctype in obj._content_types:
self._typecache[ctype].discard(obj.pk)
def clear(self): def clear(self):
""" """
@ -109,6 +120,7 @@ class ContentsHandler(object):
""" """
self._pkcache = {} self._pkcache = {}
self._typecache = defaultdict(set)
self.init() self.init()

View file

@ -203,6 +203,8 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
without `obj.save()` having to be called explicitly. without `obj.save()` having to be called explicitly.
""" """
# Used for sorting / filtering.
_content_types = ("object")
# lockstring of newly created objects, for easy overloading. # lockstring of newly created objects, for easy overloading.
# Will be formatted with the appropriate attributes. # Will be formatted with the appropriate attributes.
@ -257,7 +259,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
and not self.db_account.attributes.get("_quell") and not self.db_account.attributes.get("_quell")
) )
def contents_get(self, exclude=None): def contents_get(self, exclude=None, category=None):
""" """
Returns the contents of this object, i.e. all Returns the contents of this object, i.e. all
objects that has this object set as its location. objects that has this object set as its location.
@ -266,6 +268,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
Args: Args:
exclude (Object): Object to exclude from returned exclude (Object): Object to exclude from returned
contents list contents list
category (str): A category to filter by. None for no filtering.
Returns: Returns:
contents (list): List of contents of this Object. contents (list): List of contents of this Object.
@ -1656,20 +1659,25 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
**kwargs (dict): Arbitrary, optional arguments for users **kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default). overriding the call (unused by default).
""" """
def filter_visible(obj_list):
# Helper method to determine if objects are visible to the looker.
return [obj for obj in obj_list if obj != looker and obj.access(looker, "view")]
if not looker: if not looker:
return "" return ""
# get and identify all objects # get and identify all objects
visible = (con for con in self.contents if con != looker and con.access(looker, "view")) exits_list = filter_visible(self.contents_get(category='exit'))
exits, users, things = [], [], defaultdict(list) users_list = filter_visible(self.contents_get(category='character'))
for con in visible: things_list = filter_visible(self.contents_get(category="object"))
key = con.get_display_name(looker)
if con.destination: things = defaultdict(list)
exits.append(key)
elif con.has_account: for thing in things_list:
users.append("|c%s|n" % key) things[thing.key].append(thing)
else: users = [f"|c{user.key}|n" for user in users_list]
# things can be pluralized exits = [ex.key for ex in exits_list]
things[key].append(con)
# get description, build string # get description, build string
string = "|c%s|n\n" % self.get_display_name(looker) string = "|c%s|n\n" % self.get_display_name(looker)
desc = self.db.desc desc = self.db.desc
@ -2026,7 +2034,7 @@ class DefaultCharacter(DefaultObject):
a character avatar controlled by an account. a character avatar controlled by an account.
""" """
_content_types = ("character")
# lockstring of newly created rooms, for easy overloading. # lockstring of newly created rooms, for easy overloading.
# Will be formatted with the appropriate attributes. # Will be formatted with the appropriate attributes.
lockstring = "puppet:id({character_id}) or pid({account_id}) or perm(Developer) or pperm(Developer);delete:id({account_id}) or perm(Admin)" lockstring = "puppet:id({character_id}) or pid({account_id}) or perm(Developer) or pperm(Developer);delete:id({account_id}) or perm(Admin)"
@ -2277,7 +2285,7 @@ class DefaultRoom(DefaultObject):
This is the base room object. It's just like any Object except its This is the base room object. It's just like any Object except its
location is always `None`. location is always `None`.
""" """
_content_types = ("room", "object")
# lockstring of newly created rooms, for easy overloading. # lockstring of newly created rooms, for easy overloading.
# Will be formatted with the {id} of the creating object. # Will be formatted with the {id} of the creating object.
lockstring = ( lockstring = (
@ -2427,7 +2435,7 @@ class DefaultExit(DefaultObject):
exits simply by giving the exit-object's name on its own. exits simply by giving the exit-object's name on its own.
""" """
_content_types = ("exit")
exit_command = ExitCommand exit_command = ExitCommand
priority = 101 priority = 101