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
transparently through the decorating TypeClass.
"""
from collections import defaultdict
from django.conf import settings
from django.db import models
from django.core.exceptions import ObjectDoesNotExist
@ -42,8 +43,9 @@ class ContentsHandler(object):
"""
self.obj = obj
self._pkcache = {}
self._pkcache = set()
self._idcache = obj.__class__.__instance_cache__
self._typecache = defaultdict(set)
self.init()
def init(self):
@ -51,25 +53,30 @@ class ContentsHandler(object):
Re-initialize the content cache
"""
self._pkcache.update(
dict((obj.pk, None) for obj in ObjectDB.objects.filter(db_location=self.obj) if obj.pk)
)
objects = [obj 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.
Args:
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:
objects (list): the Objects inside this location
"""
if exclude:
pks = [pk for pk in self._pkcache if pk not in [excl.pk for excl in make_iter(exclude)]]
if category is not None:
pks = self._typecache[category]
else:
pks = self._pkcache
if exclude:
pks = pks - {excl.pk for excl in make_iter(exclude)}
try:
return [self._idcache[pk] for pk in pks]
except KeyError:
@ -91,7 +98,9 @@ class ContentsHandler(object):
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):
"""
@ -101,7 +110,9 @@ class ContentsHandler(object):
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):
"""
@ -109,6 +120,7 @@ class ContentsHandler(object):
"""
self._pkcache = {}
self._typecache = defaultdict(set)
self.init()

View file

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