Fix spawn issues in xyzgrid. Allow prototype_parent to be a dict itself. Resolve #2494.
This commit is contained in:
parent
fc323e1ca7
commit
ddaf22ea58
12 changed files with 207 additions and 97 deletions
|
|
@ -84,6 +84,8 @@ Up requirements to Django 3.2+
|
||||||
on-object Scripts. Moved `CmdScripts` and `CmdObjects` to `commands/default/building.py`.
|
on-object Scripts. Moved `CmdScripts` and `CmdObjects` to `commands/default/building.py`.
|
||||||
- Keep GMCP function case if outputfunc starts with capital letter (so `cmd_name` -> `Cmd.Name`
|
- Keep GMCP function case if outputfunc starts with capital letter (so `cmd_name` -> `Cmd.Name`
|
||||||
but `Cmd_nAmE` -> `Cmd.nAmE`). This helps e.g Mudlet's legacy `Client_GUI` implementation)
|
but `Cmd_nAmE` -> `Cmd.nAmE`). This helps e.g Mudlet's legacy `Client_GUI` implementation)
|
||||||
|
- Prototypes now allow setting `prototype_parent` directly to a prototype-dict.
|
||||||
|
This makes it easier when dynamically building in-module prototypes.
|
||||||
|
|
||||||
### Evennia 0.9.5 (2019-2020)
|
### Evennia 0.9.5 (2019-2020)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,12 @@ Exits: northeast and east
|
||||||
command line. It will also make the `xyz_room` and `xyz_exit` prototypes
|
command line. It will also make the `xyz_room` and `xyz_exit` prototypes
|
||||||
available for use as prototype-parents when spawning the grid.
|
available for use as prototype-parents when spawning the grid.
|
||||||
3. Run `evennia xyzgrid help` for available options.
|
3. Run `evennia xyzgrid help` for available options.
|
||||||
|
4. (Optional): By default, the xyzgrid will only spawn module-based
|
||||||
|
[prototypes](Prototypes). This is an optimization and usually makes sense
|
||||||
|
since the grid is entirely defined outside the game anyway. If you want to
|
||||||
|
also make use of in-game (db-) created prototypes, add
|
||||||
|
`XYZGRID_USE_DB_PROTOTYPES = True` to settings.
|
||||||
|
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
|
|
@ -1002,8 +1008,8 @@ should be included as `prototype_parents` for prototypes on the map. Would it
|
||||||
not be nice to be able to change these and have the change apply to all of the
|
not be nice to be able to change these and have the change apply to all of the
|
||||||
grid? You can, by adding the following to your `mygame/server/conf/settings.py`:
|
grid? You can, by adding the following to your `mygame/server/conf/settings.py`:
|
||||||
|
|
||||||
XYZROOM_PARENT_PROTOTYPE_OVERRIDE = {"typeclass": "myxyzroom.MyXYZRoom"}
|
XYZROOM_PROTOTYPE_OVERRIDE = {"typeclass": "myxyzroom.MyXYZRoom"}
|
||||||
XYZEXIT_PARENT_PROTOTYPE_OVERRIDE = {...}
|
XYZEXIT_PROTOTYPE_OVERRIDE = {...}
|
||||||
|
|
||||||
|
|
||||||
> If you override the typeclass in your prototypes, the typeclass used **MUST**
|
> If you override the typeclass in your prototypes, the typeclass used **MUST**
|
||||||
|
|
|
||||||
|
|
@ -23,16 +23,24 @@ from evennia.contrib.xyzgrid import xymap_legend
|
||||||
# the typeclass inherits from the XYZRoom (or XYZExit)
|
# the typeclass inherits from the XYZRoom (or XYZExit)
|
||||||
# if adding the evennia.contrib.xyzgrid.prototypes to
|
# if adding the evennia.contrib.xyzgrid.prototypes to
|
||||||
# settings.PROTOTYPE_MODULES, one could just set the
|
# settings.PROTOTYPE_MODULES, one could just set the
|
||||||
# prototype_parent to 'xyz_room' and 'xyz_exit' respectively
|
# prototype_parent to 'xyz_room' and 'xyz_exit' here
|
||||||
# instead.
|
# instead.
|
||||||
|
|
||||||
PARENT = {
|
ROOM_PARENT = {
|
||||||
"key": "An empty room",
|
"key": "An empty room",
|
||||||
"prototype_key": "xyzmap_room_map1",
|
"prototype_key": "xyz_exit_prototype",
|
||||||
"typeclass": "evennia.contrib.xyzgrid.xyzroom.XYZRoom",
|
"prototype_parent": "xyz_room",
|
||||||
|
# "typeclass": "evennia.contrib.xyzgrid.xyzroom.XYZRoom",
|
||||||
"desc": "An empty room.",
|
"desc": "An empty room.",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EXIT_PARENT = {
|
||||||
|
"prototype_key": "xyz_exit_prototype",
|
||||||
|
"prototype_parent": "xyz_exit",
|
||||||
|
# "typeclass": "evennia.contrib.xyzgrid.xyzroom.XYZExit",
|
||||||
|
"desc": "A path to the next location.",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------- map1
|
# ---------------------------------------- map1
|
||||||
# The large tree
|
# The large tree
|
||||||
|
|
@ -134,13 +142,17 @@ PROTOTYPES_MAP1 = {
|
||||||
"desc": "These branches are wide enough to easily walk on. There's green all around."
|
"desc": "These branches are wide enough to easily walk on. There's green all around."
|
||||||
},
|
},
|
||||||
# directional prototypes
|
# directional prototypes
|
||||||
(3, 0, 'w'): {
|
(3, 0, 'e'): {
|
||||||
"desc": "A dark passage into the underworld."
|
"desc": "A dark passage into the underworld."
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for prot in PROTOTYPES_MAP1.values():
|
for key, prot in PROTOTYPES_MAP1.items():
|
||||||
prot['prototype_parent'] = PARENT
|
if len(key) == 2:
|
||||||
|
# we don't want to give exits the room typeclass!
|
||||||
|
prot['prototype_parent'] = ROOM_PARENT
|
||||||
|
else:
|
||||||
|
prot['prototype_parent'] = EXIT_PARENT
|
||||||
|
|
||||||
|
|
||||||
XYMAP_DATA_MAP1 = {
|
XYMAP_DATA_MAP1 = {
|
||||||
|
|
@ -253,8 +265,12 @@ PROTOTYPES_MAP2 = {
|
||||||
|
|
||||||
# this is required by the prototypes, but we add it all at once so we don't
|
# this is required by the prototypes, but we add it all at once so we don't
|
||||||
# need to add it to every line above
|
# need to add it to every line above
|
||||||
for prot in PROTOTYPES_MAP2.values():
|
for key, prot in PROTOTYPES_MAP2.items():
|
||||||
prot['prototype_parent'] = PARENT
|
if len(key) == 2:
|
||||||
|
# we don't want to give exits the room typeclass!
|
||||||
|
prot['prototype_parent'] = ROOM_PARENT
|
||||||
|
else:
|
||||||
|
prot['prototype_parent'] = EXIT_PARENT
|
||||||
|
|
||||||
|
|
||||||
XYMAP_DATA_MAP2 = {
|
XYMAP_DATA_MAP2 = {
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,11 @@ Use `evennia xyzgrid help` for usage help.
|
||||||
|
|
||||||
from os.path import join as pathjoin
|
from os.path import join as pathjoin
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
import evennia
|
||||||
from evennia.utils import ansi
|
from evennia.utils import ansi
|
||||||
from evennia.contrib.xyzgrid.xyzgrid import get_xyzgrid
|
from evennia.contrib.xyzgrid.xyzgrid import get_xyzgrid
|
||||||
|
|
||||||
|
|
||||||
_HELP_SHORT = """
|
_HELP_SHORT = """
|
||||||
evennia xyzgrid help | list | init | add | spawn | initpath | delete [<options>]
|
evennia xyzgrid help | list | init | add | spawn | initpath | delete [<options>]
|
||||||
Manages the XYZ grid. Use 'xyzgrid help <option>' for documentation.
|
Manages the XYZ grid. Use 'xyzgrid help <option>' for documentation.
|
||||||
|
|
@ -161,6 +163,8 @@ _TOPICS_MAP = {
|
||||||
"delete": _HELP_DELETE
|
"delete": _HELP_DELETE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
evennia._init()
|
||||||
|
|
||||||
def _option_help(*suboptions):
|
def _option_help(*suboptions):
|
||||||
"""
|
"""
|
||||||
Show help <command> aid.
|
Show help <command> aid.
|
||||||
|
|
|
||||||
|
|
@ -1057,19 +1057,21 @@ class TestMapStressTest(TestCase):
|
||||||
return f"{edge}\n{(l1 + l2) * Ysize}{l1}\n\n{edge}"
|
return f"{edge}\n{(l1 + l2) * Ysize}{l1}\n\n{edge}"
|
||||||
|
|
||||||
@parameterized.expand([
|
@parameterized.expand([
|
||||||
((10, 10), 0.01),
|
((10, 10), 0.03),
|
||||||
((100, 100), 1),
|
((100, 100), 5),
|
||||||
])
|
])
|
||||||
def test_grid_creation(self, gridsize, max_time):
|
def test_grid_creation(self, gridsize, max_time):
|
||||||
"""
|
"""
|
||||||
Test of grid-creataion performance for Nx, Ny grid.
|
Test of grid-creataion performance for Nx, Ny grid.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
# import cProfile
|
||||||
Xmax, Ymax = gridsize
|
Xmax, Ymax = gridsize
|
||||||
grid = self._get_grid(Xmax, Ymax)
|
grid = self._get_grid(Xmax, Ymax)
|
||||||
t0 = time()
|
|
||||||
mapobj = xymap.XYMap({'map': grid}, Z="testmap")
|
mapobj = xymap.XYMap({'map': grid}, Z="testmap")
|
||||||
|
t0 = time()
|
||||||
mapobj.parse()
|
mapobj.parse()
|
||||||
|
# cProfile.runctx('mapobj.parse()', globals(), locals())
|
||||||
t1 = time()
|
t1 = time()
|
||||||
self.assertLess(t1 - t0, max_time, f"Map creation of ({Xmax}x{Ymax}) grid slower "
|
self.assertLess(t1 - t0, max_time, f"Map creation of ({Xmax}x{Ymax}) grid slower "
|
||||||
f"than expected {max_time}s.")
|
f"than expected {max_time}s.")
|
||||||
|
|
|
||||||
|
|
@ -109,10 +109,15 @@ from django.conf import settings
|
||||||
from evennia.utils.utils import variable_from_module, mod_import, is_iter
|
from evennia.utils.utils import variable_from_module, mod_import, is_iter
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
from evennia.prototypes import prototypes as protlib
|
from evennia.prototypes import prototypes as protlib
|
||||||
|
from evennia.prototypes.spawner import flatten_prototype
|
||||||
|
|
||||||
from .utils import MapError, MapParserError, BIGVAL
|
from .utils import MapError, MapParserError, BIGVAL
|
||||||
from . import xymap_legend
|
from . import xymap_legend
|
||||||
|
|
||||||
|
_NO_DB_PROTOTYPES = True
|
||||||
|
if hasattr(settings, "XYZGRID_USE_DB_PROTOTYPES"):
|
||||||
|
_NO_DB_PROTOTYPES = not settings.XYZGRID_USE_DB_PROTOTYPES
|
||||||
|
|
||||||
_CACHE_DIR = settings.CACHE_DIR
|
_CACHE_DIR = settings.CACHE_DIR
|
||||||
_LOADED_PROTOTYPES = None
|
_LOADED_PROTOTYPES = None
|
||||||
_XYZROOMCLASS = None
|
_XYZROOMCLASS = None
|
||||||
|
|
@ -351,8 +356,9 @@ class XYMap:
|
||||||
if not prototype or isinstance(prototype, dict):
|
if not prototype or isinstance(prototype, dict):
|
||||||
# nothing more to do
|
# nothing more to do
|
||||||
continue
|
continue
|
||||||
# we need to load the prototype dict onto each for ease of access
|
# we need to load the prototype dict onto each for ease of access. Note that
|
||||||
proto = protlib.search_prototype(prototype, require_single=True)[0]
|
proto = protlib.search_prototype(prototype, require_single=True,
|
||||||
|
no_db=_NO_DB_PROTOTYPES)[0]
|
||||||
node_or_link_class.prototype = proto
|
node_or_link_class.prototype = proto
|
||||||
|
|
||||||
def parse(self):
|
def parse(self):
|
||||||
|
|
@ -492,13 +498,24 @@ class XYMap:
|
||||||
if node.prototype:
|
if node.prototype:
|
||||||
node_coord = (node.X, node.Y)
|
node_coord = (node.X, node.Y)
|
||||||
# load prototype from override, or use default
|
# load prototype from override, or use default
|
||||||
node.prototype = self.prototypes.get(
|
try:
|
||||||
node_coord, self.prototypes.get(('*', '*'), node.prototype))
|
node.prototype = flatten_prototype(self.prototypes.get(
|
||||||
|
node_coord,
|
||||||
|
self.prototypes.get(('*', '*'), node.prototype)),
|
||||||
|
no_db=_NO_DB_PROTOTYPES
|
||||||
|
)
|
||||||
|
except Exception as err:
|
||||||
|
raise MapParserError(f"Room prototype malformed: {err}", node)
|
||||||
# do the same for links (x, y, direction) coords
|
# do the same for links (x, y, direction) coords
|
||||||
for direction, maplink in node.first_links.items():
|
for direction, maplink in node.first_links.items():
|
||||||
maplink.prototype = self.prototypes.get(
|
try:
|
||||||
|
maplink.prototype = flatten_prototype(self.prototypes.get(
|
||||||
node_coord + (direction,),
|
node_coord + (direction,),
|
||||||
self.prototypes.get(('*', '*', '*'), maplink.prototype))
|
self.prototypes.get(('*', '*', '*'), maplink.prototype)),
|
||||||
|
no_db=_NO_DB_PROTOTYPES
|
||||||
|
)
|
||||||
|
except Exception as err:
|
||||||
|
raise MapParserError(f"Exit prototype malformed: {err}", maplink)
|
||||||
|
|
||||||
# store
|
# store
|
||||||
self.display_map = display_map
|
self.display_map = display_map
|
||||||
|
|
@ -625,8 +642,8 @@ class XYMap:
|
||||||
spawned = []
|
spawned = []
|
||||||
|
|
||||||
# find existing nodes, in case some rooms need to be removed
|
# find existing nodes, in case some rooms need to be removed
|
||||||
map_coords = ((node.X, node.Y) for node in
|
map_coords = [(node.X, node.Y) for node in
|
||||||
sorted(self.node_index_map.values(), key=lambda n: (n.Y, n.X)))
|
sorted(self.node_index_map.values(), key=lambda n: (n.Y, n.X))]
|
||||||
for existing_room in _XYZROOMCLASS.objects.filter_xyz(xyz=(x, y, self.Z)):
|
for existing_room in _XYZROOMCLASS.objects.filter_xyz(xyz=(x, y, self.Z)):
|
||||||
roomX, roomY, _ = existing_room.xyz
|
roomX, roomY, _ = existing_room.xyz
|
||||||
if (roomX, roomY) not in map_coords:
|
if (roomX, roomY) not in map_coords:
|
||||||
|
|
|
||||||
|
|
@ -311,7 +311,11 @@ class MapNode:
|
||||||
nodeobj = NodeTypeclass.objects.get_xyz(xyz=xyz)
|
nodeobj = NodeTypeclass.objects.get_xyz(xyz=xyz)
|
||||||
except NodeTypeclass.DoesNotExist:
|
except NodeTypeclass.DoesNotExist:
|
||||||
# create a new entity with proper coordinates etc
|
# create a new entity with proper coordinates etc
|
||||||
self.log(f" spawning room at xyz={xyz}")
|
tclass = self.prototype['typeclass']
|
||||||
|
tclass = (f' ({tclass})'
|
||||||
|
if tclass != 'evennia.contrib.xyzgrid.xyzroom.XYZRoom'
|
||||||
|
else '')
|
||||||
|
self.log(f" spawning room at xyz={xyz}{tclass}")
|
||||||
nodeobj, err = NodeTypeclass.create(
|
nodeobj, err = NodeTypeclass.create(
|
||||||
self.prototype.get('key', 'An empty room'),
|
self.prototype.get('key', 'An empty room'),
|
||||||
xyz=xyz
|
xyz=xyz
|
||||||
|
|
@ -327,7 +331,6 @@ class MapNode:
|
||||||
|
|
||||||
# apply prototype to node. This will not override the XYZ tags since
|
# apply prototype to node. This will not override the XYZ tags since
|
||||||
# these are not in the prototype and exact=False
|
# these are not in the prototype and exact=False
|
||||||
|
|
||||||
spawner.batch_update_objects_with_prototype(
|
spawner.batch_update_objects_with_prototype(
|
||||||
self.prototype, objects=[nodeobj], exact=False)
|
self.prototype, objects=[nodeobj], exact=False)
|
||||||
|
|
||||||
|
|
@ -364,8 +367,6 @@ class MapNode:
|
||||||
link.prototype['prototype_key'] = self.generate_prototype_key()
|
link.prototype['prototype_key'] = self.generate_prototype_key()
|
||||||
maplinks[key.lower()] = (key, aliases, direction, link)
|
maplinks[key.lower()] = (key, aliases, direction, link)
|
||||||
|
|
||||||
# if xyz == (8, 1, 'the large tree'):
|
|
||||||
# from evennia import set_trace;set_trace()
|
|
||||||
# remove duplicates
|
# remove duplicates
|
||||||
linkobjs = defaultdict(list)
|
linkobjs = defaultdict(list)
|
||||||
for exitobj in ExitTypeclass.objects.filter_xyz(xyz=xyz):
|
for exitobj in ExitTypeclass.objects.filter_xyz(xyz=xyz):
|
||||||
|
|
@ -384,7 +385,6 @@ class MapNode:
|
||||||
# build all exits first run)
|
# build all exits first run)
|
||||||
differing_keys = set(maplinks.keys()).symmetric_difference(set(linkobjs.keys()))
|
differing_keys = set(maplinks.keys()).symmetric_difference(set(linkobjs.keys()))
|
||||||
for differing_key in differing_keys:
|
for differing_key in differing_keys:
|
||||||
# from evennia import set_trace;set_trace()
|
|
||||||
|
|
||||||
if differing_key not in maplinks:
|
if differing_key not in maplinks:
|
||||||
# an exit without a maplink - delete the exit-object
|
# an exit without a maplink - delete the exit-object
|
||||||
|
|
@ -408,7 +408,12 @@ class MapNode:
|
||||||
if err:
|
if err:
|
||||||
raise RuntimeError(err)
|
raise RuntimeError(err)
|
||||||
linkobjs[key.lower()] = exi
|
linkobjs[key.lower()] = exi
|
||||||
self.log(f" spawning/updating exit xyz={xyz}, direction={key}")
|
prot = maplinks[key.lower()][3].prototype
|
||||||
|
tclass = prot['typeclass']
|
||||||
|
tclass = (f' ({tclass})'
|
||||||
|
if tclass != 'evennia.contrib.xyzgrid.xyzroom.XYZExit'
|
||||||
|
else '')
|
||||||
|
self.log(f" spawning/updating exit xyz={xyz}, direction={key}{tclass}")
|
||||||
|
|
||||||
# apply prototypes to catch any changes
|
# apply prototypes to catch any changes
|
||||||
for key, linkobj in linkobjs.items():
|
for key, linkobj in linkobjs.items():
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,9 @@ class XYZGrid(DefaultScript):
|
||||||
map_data_list = [variable_from_module(module_path, "XYMAP_DATA")]
|
map_data_list = [variable_from_module(module_path, "XYMAP_DATA")]
|
||||||
# inject the python path in the map data
|
# inject the python path in the map data
|
||||||
for mapdata in map_data_list:
|
for mapdata in map_data_list:
|
||||||
|
if not mapdata:
|
||||||
|
self.log(f"Could not find or load map from {module_path}.")
|
||||||
|
return
|
||||||
mapdata['module_path'] = module_path
|
mapdata['module_path'] = module_path
|
||||||
return map_data_list
|
return map_data_list
|
||||||
|
|
||||||
|
|
@ -137,10 +140,14 @@ class XYZGrid(DefaultScript):
|
||||||
nmaps = 0
|
nmaps = 0
|
||||||
loaded_mapdata = {}
|
loaded_mapdata = {}
|
||||||
changed = []
|
changed = []
|
||||||
|
mapdata = self.db.map_data
|
||||||
|
|
||||||
|
if not mapdata:
|
||||||
|
self.db.mapdata = mapdata = {}
|
||||||
|
|
||||||
# generate all Maps - this will also initialize their components
|
# generate all Maps - this will also initialize their components
|
||||||
# and bake any pathfinding paths (or load from disk-cache)
|
# and bake any pathfinding paths (or load from disk-cache)
|
||||||
for zcoord, old_mapdata in self.db.map_data.items():
|
for zcoord, old_mapdata in mapdata.items():
|
||||||
|
|
||||||
self.log(f"Loading map '{zcoord}'...")
|
self.log(f"Loading map '{zcoord}'...")
|
||||||
|
|
||||||
|
|
@ -168,7 +175,7 @@ class XYZGrid(DefaultScript):
|
||||||
|
|
||||||
# re-store changed data
|
# re-store changed data
|
||||||
for zcoord in changed:
|
for zcoord in changed:
|
||||||
self.db.map_data[zcoord] = loaded_mapdata['zcoord']
|
self.db.map_data[zcoord] = loaded_mapdata[zcoord]
|
||||||
|
|
||||||
# store
|
# store
|
||||||
self.log(f"Loaded and linked {nmaps} map(s).")
|
self.log(f"Loaded and linked {nmaps} map(s).")
|
||||||
|
|
@ -222,6 +229,8 @@ class XYZGrid(DefaultScript):
|
||||||
Clear the entire grid, including database entities, then the grid too.
|
Clear the entire grid, including database entities, then the grid too.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
mapdata = self.db.map_data
|
||||||
|
if mapdata:
|
||||||
self.remove_map(*(zcoord for zcoord in self.db.map_data), remove_objects=True)
|
self.remove_map(*(zcoord for zcoord in self.db.map_data), remove_objects=True)
|
||||||
super().delete()
|
super().delete()
|
||||||
|
|
||||||
|
|
@ -291,6 +300,7 @@ def get_xyzgrid(print_errors=True):
|
||||||
if not xyzgrid.ndb.loaded:
|
if not xyzgrid.ndb.loaded:
|
||||||
xyzgrid.reload()
|
xyzgrid.reload()
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
|
raise
|
||||||
if print_errors:
|
if print_errors:
|
||||||
print(err)
|
print(err)
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -86,27 +86,20 @@ class XYZManager(ObjectManager):
|
||||||
possible with a unique combination of x,y,z).
|
possible with a unique combination of x,y,z).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
# filter by tags, then figure out of we got a single match or not
|
||||||
|
query = self.filter_xyz(xyz=xyz, **kwargs)
|
||||||
|
ncount = query.count()
|
||||||
|
if ncount == 1:
|
||||||
|
return query.first()
|
||||||
|
|
||||||
|
# error - mimic default get() behavior but with a little more info
|
||||||
x, y, z = xyz
|
x, y, z = xyz
|
||||||
|
inp = (f"Query: xyz=({x},{y},{z}), " +
|
||||||
# mimic get_family
|
|
||||||
paths = [self.model.path] + [
|
|
||||||
"%s.%s" % (cls.__module__, cls.__name__) for cls in self._get_subclasses(self.model)
|
|
||||||
]
|
|
||||||
kwargs["db_typeclass_path__in"] = paths
|
|
||||||
|
|
||||||
try:
|
|
||||||
return (
|
|
||||||
self
|
|
||||||
.filter(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY)
|
|
||||||
.filter(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY)
|
|
||||||
.filter(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY)
|
|
||||||
.get(**kwargs)
|
|
||||||
)
|
|
||||||
except self.model.DoesNotExist:
|
|
||||||
inp = (f"xyz=({x},{y},{z}), " +
|
|
||||||
",".join(f"{key}={val}" for key, val in kwargs.items()))
|
",".join(f"{key}={val}" for key, val in kwargs.items()))
|
||||||
raise self.model.DoesNotExist(f"{self.model.__name__} "
|
if ncount > 1:
|
||||||
f"matching query {inp} does not exist.")
|
raise self.model.MultipleObjectsReturned(inp)
|
||||||
|
else:
|
||||||
|
raise self.model.DoesNotExist(inp)
|
||||||
|
|
||||||
|
|
||||||
class XYZExitManager(XYZManager):
|
class XYZExitManager(XYZManager):
|
||||||
|
|
|
||||||
|
|
@ -2580,7 +2580,7 @@ def node_prototype_spawn(caller, **kwargs):
|
||||||
# prototype load node
|
# prototype load node
|
||||||
|
|
||||||
|
|
||||||
def _prototype_load_select(caller, prototype_key):
|
def _prototype_load_select(caller, prototype_key, **kwargs):
|
||||||
matches = protlib.search_prototype(key=prototype_key)
|
matches = protlib.search_prototype(key=prototype_key)
|
||||||
if matches:
|
if matches:
|
||||||
prototype = matches[0]
|
prototype = matches[0]
|
||||||
|
|
|
||||||
|
|
@ -105,17 +105,17 @@ def homogenize_prototype(prototype, custom_keys=None):
|
||||||
elif protkey in ("prototype_key", "prototype_desc"):
|
elif protkey in ("prototype_key", "prototype_desc"):
|
||||||
prototype[protkey] = ""
|
prototype[protkey] = ""
|
||||||
|
|
||||||
attrs = list(prototype.get("attrs", [])) # break reference
|
homogenized = {}
|
||||||
tags = make_iter(prototype.get("tags", []))
|
|
||||||
homogenized_tags = []
|
homogenized_tags = []
|
||||||
homogenized_attrs = []
|
homogenized_attrs = []
|
||||||
|
homogenized_parents = []
|
||||||
|
|
||||||
homogenized = {}
|
|
||||||
for key, val in prototype.items():
|
for key, val in prototype.items():
|
||||||
if key in reserved:
|
if key in reserved:
|
||||||
# check all reserved keys
|
# check all reserved keys
|
||||||
if key == "tags":
|
if key == "tags":
|
||||||
# tags must be on form [(tag, category, data), ...]
|
# tags must be on form [(tag, category, data), ...]
|
||||||
|
tags = make_iter(prototype.get("tags", []))
|
||||||
for tag in tags:
|
for tag in tags:
|
||||||
if not is_iter(tag):
|
if not is_iter(tag):
|
||||||
homogenized_tags.append((tag, None, None))
|
homogenized_tags.append((tag, None, None))
|
||||||
|
|
@ -127,7 +127,9 @@ def homogenize_prototype(prototype, custom_keys=None):
|
||||||
homogenized_tags.append((tag[0], tag[1], None))
|
homogenized_tags.append((tag[0], tag[1], None))
|
||||||
else:
|
else:
|
||||||
homogenized_tags.append(tag[:3])
|
homogenized_tags.append(tag[:3])
|
||||||
if key == "attrs":
|
|
||||||
|
elif key == "attrs":
|
||||||
|
attrs = list(prototype.get("attrs", [])) # break reference
|
||||||
for attr in attrs:
|
for attr in attrs:
|
||||||
# attrs must be on form [(key, value, category, lockstr)]
|
# attrs must be on form [(key, value, category, lockstr)]
|
||||||
if not is_iter(attr):
|
if not is_iter(attr):
|
||||||
|
|
@ -144,6 +146,21 @@ def homogenize_prototype(prototype, custom_keys=None):
|
||||||
homogenized_attrs.append(attr[0], attr[1], attr[2], "")
|
homogenized_attrs.append(attr[0], attr[1], attr[2], "")
|
||||||
else:
|
else:
|
||||||
homogenized_attrs.append(attr[:4])
|
homogenized_attrs.append(attr[:4])
|
||||||
|
|
||||||
|
elif key == "prototype_parent":
|
||||||
|
# homogenize any prototype-parents embedded directly as dicts
|
||||||
|
protparents = prototype.get('prototype_parent', [])
|
||||||
|
if isinstance(protparents, dict):
|
||||||
|
protparents = [protparents]
|
||||||
|
for parent in make_iter(protparents):
|
||||||
|
if isinstance(parent, dict):
|
||||||
|
# recursively homogenize directly embedded prototype parents
|
||||||
|
homogenized_parents.append(
|
||||||
|
homogenize_prototype(parent, custom_keys=custom_keys))
|
||||||
|
else:
|
||||||
|
# normal prototype-parent names are added as-is
|
||||||
|
homogenized_parents.append(parent)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# another reserved key
|
# another reserved key
|
||||||
homogenized[key] = val
|
homogenized[key] = val
|
||||||
|
|
@ -154,6 +171,8 @@ def homogenize_prototype(prototype, custom_keys=None):
|
||||||
homogenized["attrs"] = homogenized_attrs
|
homogenized["attrs"] = homogenized_attrs
|
||||||
if homogenized_tags:
|
if homogenized_tags:
|
||||||
homogenized["tags"] = homogenized_tags
|
homogenized["tags"] = homogenized_tags
|
||||||
|
if homogenized_parents:
|
||||||
|
homogenized['prototype_parent'] = homogenized_parents
|
||||||
|
|
||||||
# add required missing parts that had defaults before
|
# add required missing parts that had defaults before
|
||||||
|
|
||||||
|
|
@ -460,7 +479,8 @@ def delete_prototype(prototype_key, caller=None):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def search_prototype(key=None, tags=None, require_single=False, return_iterators=False):
|
def search_prototype(key=None, tags=None, require_single=False, return_iterators=False,
|
||||||
|
no_db=False):
|
||||||
"""
|
"""
|
||||||
Find prototypes based on key and/or tags, or all prototypes.
|
Find prototypes based on key and/or tags, or all prototypes.
|
||||||
|
|
||||||
|
|
@ -474,6 +494,9 @@ def search_prototype(key=None, tags=None, require_single=False, return_iterators
|
||||||
return_iterators (bool): Optimized return for large numbers of db-prototypes.
|
return_iterators (bool): Optimized return for large numbers of db-prototypes.
|
||||||
If set, separate returns of module based prototypes and paginate
|
If set, separate returns of module based prototypes and paginate
|
||||||
the db-prototype return.
|
the db-prototype return.
|
||||||
|
no_db (bool): Optimization. If set, skip querying for database-generated prototypes and only
|
||||||
|
include module-based prototypes. This can lead to a dramatic speedup since
|
||||||
|
module-prototypes are static and require no db-lookup.
|
||||||
|
|
||||||
Return:
|
Return:
|
||||||
matches (list): Default return, all found prototype dicts. Empty list if
|
matches (list): Default return, all found prototype dicts. Empty list if
|
||||||
|
|
@ -525,8 +548,10 @@ def search_prototype(key=None, tags=None, require_single=False, return_iterators
|
||||||
# prototype_from_object will modify the base prototype for every object
|
# prototype_from_object will modify the base prototype for every object
|
||||||
module_prototypes = [match.copy() for match in mod_matches.values()]
|
module_prototypes = [match.copy() for match in mod_matches.values()]
|
||||||
|
|
||||||
|
if no_db:
|
||||||
|
db_matches = []
|
||||||
|
else:
|
||||||
# search db-stored prototypes
|
# search db-stored prototypes
|
||||||
|
|
||||||
if tags:
|
if tags:
|
||||||
# exact match on tag(s)
|
# exact match on tag(s)
|
||||||
tags = make_iter(tags)
|
tags = make_iter(tags)
|
||||||
|
|
@ -551,9 +576,10 @@ def search_prototype(key=None, tags=None, require_single=False, return_iterators
|
||||||
.values_list("db_value", flat=True)
|
.values_list("db_value", flat=True)
|
||||||
.order_by("scriptdb__db_key")
|
.order_by("scriptdb__db_key")
|
||||||
)
|
)
|
||||||
|
|
||||||
if key and require_single:
|
if key and require_single:
|
||||||
nmodules = len(module_prototypes)
|
nmodules = len(module_prototypes)
|
||||||
ndbprots = db_matches.count()
|
ndbprots = db_matches.count() if db_matches else 0
|
||||||
if nmodules + ndbprots != 1:
|
if nmodules + ndbprots != 1:
|
||||||
raise KeyError(_(
|
raise KeyError(_(
|
||||||
"Found {num} matching prototypes among {module_prototypes}.").format(
|
"Found {num} matching prototypes among {module_prototypes}.").format(
|
||||||
|
|
@ -795,9 +821,17 @@ def validate_prototype(
|
||||||
err=err, protkey=protkey, typeclass=typeclass)
|
err=err, protkey=protkey, typeclass=typeclass)
|
||||||
)
|
)
|
||||||
|
|
||||||
# recursively traverse prototype_parent chain
|
if prototype_parent and isinstance(prototype_parent, dict):
|
||||||
|
# the protparent is already embedded as a dict;
|
||||||
|
prototype_parent = [prototype_parent]
|
||||||
|
|
||||||
|
# recursively traverse prototype_parent chain
|
||||||
for protstring in make_iter(prototype_parent):
|
for protstring in make_iter(prototype_parent):
|
||||||
|
if isinstance(protstring, dict):
|
||||||
|
# an already embedded prototype_parent
|
||||||
|
protparent = protstring
|
||||||
|
protstring = None
|
||||||
|
else:
|
||||||
protstring = protstring.lower()
|
protstring = protstring.lower()
|
||||||
if protkey is not None and protstring == protkey:
|
if protkey is not None and protstring == protkey:
|
||||||
_flags["errors"].append(_("Prototype {protkey} tries to parent itself.").format(
|
_flags["errors"].append(_("Prototype {protkey} tries to parent itself.").format(
|
||||||
|
|
@ -805,9 +839,11 @@ def validate_prototype(
|
||||||
protparent = protparents.get(protstring)
|
protparent = protparents.get(protstring)
|
||||||
if not protparent:
|
if not protparent:
|
||||||
_flags["errors"].append(
|
_flags["errors"].append(
|
||||||
_("Prototype {protkey}'s prototype_parent '{parent}' was not found.").format(
|
_("Prototype {protkey}'s `prototype_parent` (named '{parent}') "
|
||||||
protkey=protkey, parent=protstring)
|
"was not found.").format(protkey=protkey, parent=protstring)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# check for infinite recursion
|
||||||
if id(prototype) in _flags["visited"]:
|
if id(prototype) in _flags["visited"]:
|
||||||
_flags["errors"].append(
|
_flags["errors"].append(
|
||||||
_("{protkey} has infinite nesting of prototypes.").format(
|
_("{protkey} has infinite nesting of prototypes.").format(
|
||||||
|
|
@ -818,9 +854,12 @@ def validate_prototype(
|
||||||
raise RuntimeError(f"{_ERRSTR}: " + f"\n{_ERRSTR}: ".join(_flags["errors"]))
|
raise RuntimeError(f"{_ERRSTR}: " + f"\n{_ERRSTR}: ".join(_flags["errors"]))
|
||||||
_flags["visited"].append(id(prototype))
|
_flags["visited"].append(id(prototype))
|
||||||
_flags["depth"] += 1
|
_flags["depth"] += 1
|
||||||
|
|
||||||
|
# next step of recursive validation
|
||||||
validate_prototype(
|
validate_prototype(
|
||||||
protparent, protstring, protparents, is_prototype_base=is_prototype_base, _flags=_flags
|
protparent, protstring, protparents, is_prototype_base=is_prototype_base, _flags=_flags
|
||||||
)
|
)
|
||||||
|
|
||||||
_flags["visited"].pop()
|
_flags["visited"].pop()
|
||||||
_flags["depth"] -= 1
|
_flags["depth"] -= 1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -220,10 +220,23 @@ def _get_prototype(inprot, protparents, uninherited=None, _workprot=None):
|
||||||
_workprot = {} if _workprot is None else _workprot
|
_workprot = {} if _workprot is None else _workprot
|
||||||
if "prototype_parent" in inprot:
|
if "prototype_parent" in inprot:
|
||||||
# move backwards through the inheritance
|
# move backwards through the inheritance
|
||||||
for prototype in make_iter(inprot["prototype_parent"]):
|
|
||||||
|
prototype_parents = inprot["prototype_parent"]
|
||||||
|
if isinstance(prototype_parents, dict):
|
||||||
|
# protparent already embedded as-is
|
||||||
|
prototype_parents = [prototype_parents]
|
||||||
|
|
||||||
|
for prototype in make_iter(prototype_parents):
|
||||||
|
if isinstance(prototype, dict):
|
||||||
|
# protparent already embedded as-is
|
||||||
|
parent_prototype = prototype
|
||||||
|
else:
|
||||||
|
# protparent given by-name
|
||||||
|
parent_prototype = protparents.get(prototype.lower(), {})
|
||||||
|
|
||||||
# Build the prot dictionary in reverse order, overloading
|
# Build the prot dictionary in reverse order, overloading
|
||||||
new_prot = _get_prototype(
|
new_prot = _get_prototype(
|
||||||
protparents.get(prototype.lower(), {}), protparents, _workprot=_workprot
|
parent_prototype, protparents, _workprot=_workprot
|
||||||
)
|
)
|
||||||
|
|
||||||
# attrs, tags have internal structure that should be inherited separately
|
# attrs, tags have internal structure that should be inherited separately
|
||||||
|
|
@ -245,7 +258,7 @@ def _get_prototype(inprot, protparents, uninherited=None, _workprot=None):
|
||||||
return _workprot
|
return _workprot
|
||||||
|
|
||||||
|
|
||||||
def flatten_prototype(prototype, validate=False):
|
def flatten_prototype(prototype, validate=False, no_db=False):
|
||||||
"""
|
"""
|
||||||
Produce a 'flattened' prototype, where all prototype parents in the inheritance tree have been
|
Produce a 'flattened' prototype, where all prototype parents in the inheritance tree have been
|
||||||
merged into a final prototype.
|
merged into a final prototype.
|
||||||
|
|
@ -253,6 +266,8 @@ def flatten_prototype(prototype, validate=False):
|
||||||
Args:
|
Args:
|
||||||
prototype (dict): Prototype to flatten. Its `prototype_parent` field will be parsed.
|
prototype (dict): Prototype to flatten. Its `prototype_parent` field will be parsed.
|
||||||
validate (bool, optional): Validate for valid keys etc.
|
validate (bool, optional): Validate for valid keys etc.
|
||||||
|
no_db (bool, optional): Don't search db-based prototypes. This can speed up
|
||||||
|
searching dramatically since module-based prototypes are static.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
flattened (dict): The final, flattened prototype.
|
flattened (dict): The final, flattened prototype.
|
||||||
|
|
@ -261,7 +276,8 @@ def flatten_prototype(prototype, validate=False):
|
||||||
|
|
||||||
if prototype:
|
if prototype:
|
||||||
prototype = protlib.homogenize_prototype(prototype)
|
prototype = protlib.homogenize_prototype(prototype)
|
||||||
protparents = {prot["prototype_key"].lower(): prot for prot in protlib.search_prototype()}
|
protparents = {prot["prototype_key"].lower(): prot
|
||||||
|
for prot in protlib.search_prototype(no_db=no_db)}
|
||||||
protlib.validate_prototype(
|
protlib.validate_prototype(
|
||||||
prototype, None, protparents, is_prototype_base=validate, strict=validate
|
prototype, None, protparents, is_prototype_base=validate, strict=validate
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue