Makes CmdFind querying use iterators to minimize memory ballooning. Refactors some redundant rendering code.
This commit is contained in:
parent
716a0b20e3
commit
5339b80743
2 changed files with 73 additions and 39 deletions
|
|
@ -3,7 +3,7 @@ Building and world design commands
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import Q
|
from django.db.models import Q, Min, Max
|
||||||
from evennia.objects.models import ObjectDB
|
from evennia.objects.models import ObjectDB
|
||||||
from evennia.locks.lockhandler import LockException
|
from evennia.locks.lockhandler import LockException
|
||||||
from evennia.commands.cmdhandler import get_and_merge_cmdsets
|
from evennia.commands.cmdhandler import get_and_merge_cmdsets
|
||||||
|
|
@ -2778,8 +2778,35 @@ class CmdFind(COMMAND_DEFAULT_CLASS):
|
||||||
switches.append("loc")
|
switches.append("loc")
|
||||||
|
|
||||||
searchstring = self.lhs
|
searchstring = self.lhs
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Try grabbing the actual min/max id values by database aggregation
|
||||||
|
qs = ObjectDB.objects.values('id').aggregate(low=Min('id'), high=Max('id'))
|
||||||
|
low, high = sorted(qs.values())
|
||||||
|
if not (low and high):
|
||||||
|
raise ValueError(f"{self.__class__.__name__}: Min and max ID not returned by aggregation; falling back to queryset slicing.")
|
||||||
|
except Exception as e:
|
||||||
|
logger.log_trace(e)
|
||||||
|
# If that doesn't work for some reason (empty DB?), guess the lower
|
||||||
|
# bound and do a less-efficient query to find the upper.
|
||||||
low, high = 1, ObjectDB.objects.all().order_by("-id")[0].id
|
low, high = 1, ObjectDB.objects.all().order_by("-id")[0].id
|
||||||
|
|
||||||
if self.rhs:
|
if self.rhs:
|
||||||
|
# Check that rhs is either a valid dbref or dbref range
|
||||||
|
try:
|
||||||
|
# Get rid of # signs, split on hyphen or space and cast all to int.
|
||||||
|
# Then sort by number to get the lowest and highest values
|
||||||
|
# comprising the bounds.
|
||||||
|
bounds = sorted(int(x) for x in re.split('[-\s]+', self.rhs.strip().replace('#', '')))
|
||||||
|
except ValueError:
|
||||||
|
caller.msg("Invalid dbref range provided (not a number).")
|
||||||
|
return
|
||||||
|
|
||||||
|
low = bounds[0]
|
||||||
|
if len(bounds) > 1:
|
||||||
|
high = bounds[-1]
|
||||||
|
|
||||||
|
"""
|
||||||
if "-" in self.rhs:
|
if "-" in self.rhs:
|
||||||
# also support low-high syntax
|
# also support low-high syntax
|
||||||
limlist = [part.lstrip("#").strip() for part in self.rhs.split("-", 1)]
|
limlist = [part.lstrip("#").strip() for part in self.rhs.split("-", 1)]
|
||||||
|
|
@ -2790,6 +2817,7 @@ class CmdFind(COMMAND_DEFAULT_CLASS):
|
||||||
low = max(low, int(limlist[0]))
|
low = max(low, int(limlist[0]))
|
||||||
if len(limlist) > 1 and limlist[1].isdigit():
|
if len(limlist) > 1 and limlist[1].isdigit():
|
||||||
high = min(high, int(limlist[1]))
|
high = min(high, int(limlist[1]))
|
||||||
|
"""
|
||||||
low = min(low, high)
|
low = min(low, high)
|
||||||
high = max(low, high)
|
high = max(low, high)
|
||||||
|
|
||||||
|
|
@ -2869,50 +2897,44 @@ class CmdFind(COMMAND_DEFAULT_CLASS):
|
||||||
results = ObjectDB.objects.filter(keyquery | aliasquery).distinct()
|
results = ObjectDB.objects.filter(keyquery | aliasquery).distinct()
|
||||||
nresults = results.count()
|
nresults = results.count()
|
||||||
|
|
||||||
|
# Use iterator to minimize memory ballooning on large result sets
|
||||||
|
results = results.iterator()
|
||||||
|
|
||||||
if nresults:
|
if nresults:
|
||||||
# convert result to typeclasses.
|
# filter results by typeclasses, if requested
|
||||||
results = [result for result in results]
|
obj_ids = []
|
||||||
if "room" in switches:
|
if "room" in switches:
|
||||||
results = [
|
obj_ids.extend([
|
||||||
obj for obj in results if inherits_from(obj, ROOM_TYPECLASS)
|
obj.id for obj in results if inherits_from(obj, ROOM_TYPECLASS)
|
||||||
]
|
])
|
||||||
if "exit" in switches:
|
if "exit" in switches:
|
||||||
results = [
|
obj_ids.extend([
|
||||||
obj for obj in results if inherits_from(obj, EXIT_TYPECLASS)
|
obj.id for obj in results if inherits_from(obj, EXIT_TYPECLASS)
|
||||||
]
|
])
|
||||||
if "char" in switches:
|
if "char" in switches:
|
||||||
results = [
|
obj_ids.extend([
|
||||||
obj for obj in results if inherits_from(obj, CHAR_TYPECLASS)
|
obj.id for obj in results if inherits_from(obj, CHAR_TYPECLASS)
|
||||||
]
|
])
|
||||||
nresults = len(results)
|
if obj_ids:
|
||||||
|
filtered_result_qs = ObjectDB.objects.filter(id__in=set(obj_ids)).distinct()
|
||||||
|
nresults = filtered_result_qs.count()
|
||||||
|
|
||||||
|
# Keep using iterator to minimize memory ballooning
|
||||||
|
results = filtered_result_qs.iterator()
|
||||||
|
|
||||||
# still results after type filtering?
|
# still results after type filtering?
|
||||||
if nresults:
|
if nresults:
|
||||||
if nresults > 1:
|
if nresults > 1: header = f'{nresults} Matches'
|
||||||
string = "|w%i Matches|n(#%i-#%i%s):" % (
|
else: header = 'One Match'
|
||||||
nresults,
|
|
||||||
low,
|
string = f"|w{header}|n(#{low}-#{high}{restrictions}):"
|
||||||
high,
|
|
||||||
restrictions,
|
|
||||||
)
|
|
||||||
for res in results:
|
for res in results:
|
||||||
string += "\n |g%s - %s|n" % (
|
string += f"\n |g{res.get_display_name(caller)} - {res.path}|n"
|
||||||
res.get_display_name(caller),
|
if "loc" in self.switches and nresults == 1 and res and res.location:
|
||||||
res.path,
|
string += f" (|wlocation|n: |g{res.location.get_display_name(caller)}|n)"
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
string = "|wOne Match|n(#%i-#%i%s):" % (low, high, restrictions)
|
string = "|wMatch|n(#{low}-#{high}{restrictions}):"
|
||||||
string += "\n |g%s - %s|n" % (
|
string += "\n |RNo matches found for '{searchstring}'|n"
|
||||||
results[0].get_display_name(caller),
|
|
||||||
results[0].path,
|
|
||||||
)
|
|
||||||
if "loc" in self.switches and nresults == 1 and results[0].location:
|
|
||||||
string += " (|wlocation|n: |g{}|n)".format(
|
|
||||||
results[0].location.get_display_name(caller)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
string = "|wMatch|n(#%i-#%i%s):" % (low, high, restrictions)
|
|
||||||
string += "\n |RNo matches found for '%s'|n" % searchstring
|
|
||||||
|
|
||||||
# send result
|
# send result
|
||||||
caller.msg(string.strip())
|
caller.msg(string.strip())
|
||||||
|
|
|
||||||
|
|
@ -1215,6 +1215,18 @@ class TestBuilding(CommandTest):
|
||||||
self.call(building.CmdFind(), "/exit Obj")
|
self.call(building.CmdFind(), "/exit Obj")
|
||||||
self.call(building.CmdFind(), "/exact Obj", "One Match")
|
self.call(building.CmdFind(), "/exact Obj", "One Match")
|
||||||
|
|
||||||
|
# Test bogus dbref range with no search term
|
||||||
|
self.call(building.CmdFind(), "= obj", "Invalid dbref range provided (not a number).")
|
||||||
|
self.call(building.CmdFind(), "= #1a", "Invalid dbref range provided (not a number).")
|
||||||
|
|
||||||
|
# Test valid dbref ranges with no search term
|
||||||
|
self.call(building.CmdFind(), "=#1", "7 Matches(#1-#7)")
|
||||||
|
self.call(building.CmdFind(), "=1-2", "2 Matches(#1-#2):")
|
||||||
|
self.call(building.CmdFind(), "=1 - 2", "2 Matches(#1-#2):")
|
||||||
|
self.call(building.CmdFind(), "=1- #2", "2 Matches(#1-#2):")
|
||||||
|
self.call(building.CmdFind(), "=1-#2", "2 Matches(#1-#2):")
|
||||||
|
self.call(building.CmdFind(), "=#1-2", "2 Matches(#1-#2):")
|
||||||
|
|
||||||
def test_script(self):
|
def test_script(self):
|
||||||
self.call(building.CmdScript(), "Obj = ", "No scripts defined on Obj")
|
self.call(building.CmdScript(), "Obj = ", "No scripts defined on Obj")
|
||||||
self.call(
|
self.call(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue