mirror of
https://github.com/evennia/evennia.git
synced 2026-03-22 15:56:30 +01:00
Add caching of pathfinder data
This commit is contained in:
parent
8767fb3ddd
commit
f63c155eaf
3 changed files with 71 additions and 22 deletions
|
|
@ -81,7 +81,10 @@ See `./example_maps.py` for some empty grid areas to start from.
|
|||
|
||||
----
|
||||
"""
|
||||
import pickle
|
||||
from collections import defaultdict
|
||||
from os import mkdir
|
||||
from os.path import isdir, isfile, join as pathjoin
|
||||
|
||||
try:
|
||||
from scipy.sparse.csgraph import dijkstra
|
||||
|
|
@ -91,7 +94,11 @@ except ImportError as err:
|
|||
raise ImportError(
|
||||
f"{err}\nThe MapSystem contrib requires "
|
||||
"the SciPy package. Install with `pip install scipy'.")
|
||||
from django.conf import settings
|
||||
from evennia.utils.utils import variable_from_module, mod_import
|
||||
from evennia.utils import logger
|
||||
|
||||
_CACHE_DIR = settings.CACHE_DIR
|
||||
|
||||
_BIG = 999999999999
|
||||
|
||||
|
|
@ -1180,6 +1187,10 @@ class Map:
|
|||
Y = y // 2
|
||||
|
||||
"""
|
||||
self.name = name
|
||||
|
||||
mapstring = ""
|
||||
|
||||
# store so we can reload
|
||||
self.map_module_or_dict = map_module_or_dict
|
||||
|
||||
|
|
@ -1197,6 +1208,12 @@ class Map:
|
|||
self.dist_matrix = None
|
||||
self.pathfinding_routes = None
|
||||
|
||||
self.pathfinder_baked_filename = None
|
||||
if name:
|
||||
if not isdir(_CACHE_DIR):
|
||||
mkdir(_CACHE_DIR)
|
||||
self.pathfinder_baked_filename = pathjoin(_CACHE_DIR, f"{name}.P")
|
||||
|
||||
# load data and parse it
|
||||
self.reload()
|
||||
|
||||
|
|
@ -1262,13 +1279,32 @@ class Map:
|
|||
|
||||
def _calculate_path_matrix(self):
|
||||
"""
|
||||
Solve the pathfinding problem using Dijkstra's algorithm.
|
||||
Solve the pathfinding problem using Dijkstra's algorithm. This will try to
|
||||
load the solution from disk if possible.
|
||||
|
||||
"""
|
||||
nnodes = len(self.node_index_map)
|
||||
if self.pathfinder_baked_filename and isfile(self.pathfinder_baked_filename):
|
||||
# check if the solution for this grid was already solved previously.
|
||||
|
||||
mapstr, dist_matrix, pathfinding_routes = "", None, None
|
||||
with open(self.pathfinder_baked_filename, 'rb') as fil:
|
||||
try:
|
||||
mapstr, dist_matrix, pathfinding_routes = pickle.load(fil)
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
if (mapstr == self.mapstring
|
||||
and dist_matrix is not None
|
||||
and pathfinding_routes is not None):
|
||||
# this is important - it means the map hasn't changed so
|
||||
# we can re-use the stored data!
|
||||
self.dist_matrix = dist_matrix
|
||||
self.pathfinding_routes = pathfinding_routes
|
||||
return
|
||||
|
||||
pathfinding_graph = zeros((nnodes, nnodes))
|
||||
# build a matrix representing the map graph, with 0s as impassable areas
|
||||
|
||||
nnodes = len(self.node_index_map)
|
||||
pathfinding_graph = zeros((nnodes, nnodes))
|
||||
for inode, node in self.node_index_map.items():
|
||||
pathfinding_graph[inode, :] = node.linkweights(nnodes)
|
||||
|
||||
|
|
@ -1280,6 +1316,12 @@ class Map:
|
|||
pathfinding_matrix, directed=True,
|
||||
return_predecessors=True, limit=self.max_pathfinding_length)
|
||||
|
||||
if self.pathfinder_baked_filename:
|
||||
# try to cache the results
|
||||
with open(self.pathfinder_baked_filename, 'wb') as fil:
|
||||
pickle.dump((self.mapstring, self.dist_matrix, self.pathfinding_routes),
|
||||
fil, protocol=4)
|
||||
|
||||
def _parse(self):
|
||||
"""
|
||||
Parses the numerical grid from the string. The result of this is a 2D array
|
||||
|
|
@ -1665,7 +1707,8 @@ class Map:
|
|||
# stylize path to target
|
||||
|
||||
def _default_callable(node):
|
||||
return target_path_style.format(display_symbol=node)
|
||||
return target_path_style.format(
|
||||
display_symbol=node.get_display_symbol(self.xygrid))
|
||||
|
||||
if callable(target_path_style):
|
||||
_target_path_style = target_path_style
|
||||
|
|
|
|||
|
|
@ -320,7 +320,7 @@ class TestMap1(TestCase):
|
|||
|
||||
"""
|
||||
def setUp(self):
|
||||
self.map = mapsystem.Map({"map": MAP1})
|
||||
self.map = mapsystem.Map({"map": MAP1}, name="testmap")
|
||||
|
||||
def test_str_output(self):
|
||||
"""Check the display_map"""
|
||||
|
|
@ -404,7 +404,7 @@ class TestMap2(TestCase):
|
|||
|
||||
"""
|
||||
def setUp(self):
|
||||
self.map = mapsystem.Map({"map": MAP2})
|
||||
self.map = mapsystem.Map({"map": MAP2}, name="testmap")
|
||||
|
||||
def test_str_output(self):
|
||||
"""Check the display_map"""
|
||||
|
|
@ -514,7 +514,7 @@ class TestMap3(TestCase):
|
|||
|
||||
"""
|
||||
def setUp(self):
|
||||
self.map = mapsystem.Map({"map": MAP3})
|
||||
self.map = mapsystem.Map({"map": MAP3}, name="testmap")
|
||||
|
||||
def test_str_output(self):
|
||||
"""Check the display_map"""
|
||||
|
|
@ -563,7 +563,7 @@ class TestMap4(TestCase):
|
|||
|
||||
"""
|
||||
def setUp(self):
|
||||
self.map = mapsystem.Map({"map": MAP4})
|
||||
self.map = mapsystem.Map({"map": MAP4}, name="testmap")
|
||||
|
||||
def test_str_output(self):
|
||||
"""Check the display_map"""
|
||||
|
|
@ -593,7 +593,7 @@ class TestMap5(TestCase):
|
|||
|
||||
"""
|
||||
def setUp(self):
|
||||
self.map = mapsystem.Map({"map": MAP5})
|
||||
self.map = mapsystem.Map({"map": MAP5}, name="testmap")
|
||||
|
||||
def test_str_output(self):
|
||||
"""Check the display_map"""
|
||||
|
|
@ -621,7 +621,7 @@ class TestMap6(TestCase):
|
|||
|
||||
"""
|
||||
def setUp(self):
|
||||
self.map = mapsystem.Map({"map": MAP6})
|
||||
self.map = mapsystem.Map({"map": MAP6}, name="testmap")
|
||||
|
||||
def test_str_output(self):
|
||||
"""Check the display_map"""
|
||||
|
|
@ -653,7 +653,7 @@ class TestMap7(TestCase):
|
|||
|
||||
"""
|
||||
def setUp(self):
|
||||
self.map = mapsystem.Map({"map": MAP7})
|
||||
self.map = mapsystem.Map({"map": MAP7}, name="testmap")
|
||||
|
||||
def test_str_output(self):
|
||||
"""Check the display_map"""
|
||||
|
|
@ -681,7 +681,7 @@ class TestMap8(TestCase):
|
|||
|
||||
"""
|
||||
def setUp(self):
|
||||
self.map = mapsystem.Map({"map": MAP8})
|
||||
self.map = mapsystem.Map({"map": MAP8}, name="testmap")
|
||||
|
||||
def test_str_output(self):
|
||||
"""Check the display_map"""
|
||||
|
|
@ -747,7 +747,7 @@ class TestMap9(TestCase):
|
|||
|
||||
"""
|
||||
def setUp(self):
|
||||
self.map = mapsystem.Map({"map": MAP9})
|
||||
self.map = mapsystem.Map({"map": MAP9}, name="testmap")
|
||||
|
||||
def test_str_output(self):
|
||||
"""Check the display_map"""
|
||||
|
|
@ -776,7 +776,7 @@ class TestMap10(TestCase):
|
|||
|
||||
"""
|
||||
def setUp(self):
|
||||
self.map = mapsystem.Map({"map": MAP10})
|
||||
self.map = mapsystem.Map({"map": MAP10}, name="testmap")
|
||||
|
||||
def test_str_output(self):
|
||||
"""Check the display_map"""
|
||||
|
|
@ -824,7 +824,7 @@ class TestMap11(TestCase):
|
|||
|
||||
"""
|
||||
def setUp(self):
|
||||
self.map = mapsystem.Map({"map": MAP11})
|
||||
self.map = mapsystem.Map({"map": MAP11}, name="testmap")
|
||||
|
||||
def test_str_output(self):
|
||||
"""Check the display_map"""
|
||||
|
|
@ -914,14 +914,14 @@ class TestMapStressTest(TestCase):
|
|||
grid = self._get_grid(Xmax, Ymax)
|
||||
# print(f"\n\n{grid}\n")
|
||||
t0 = time()
|
||||
mapsystem.Map({'map': grid})
|
||||
mapsystem.Map({'map': grid}, name="testmap")
|
||||
t1 = time()
|
||||
self.assertLess(t1 - t0, max_time, f"Map creation of ({Xmax}x{Ymax}) grid slower "
|
||||
f"than expected {max_time}s.")
|
||||
|
||||
@parameterized.expand([
|
||||
((10, 10), 10**-4),
|
||||
((20, 20), 10**-4),
|
||||
((10, 10), 10**-3),
|
||||
((20, 20), 10**-3),
|
||||
])
|
||||
def test_grid_pathfind(self, gridsize, max_time):
|
||||
"""
|
||||
|
|
@ -930,7 +930,7 @@ class TestMapStressTest(TestCase):
|
|||
"""
|
||||
Xmax, Ymax = gridsize
|
||||
grid = self._get_grid(Xmax, Ymax)
|
||||
mapobj = mapsystem.Map({'map': grid})
|
||||
mapobj = mapsystem.Map({'map': grid}, name="testmap")
|
||||
|
||||
t0 = time()
|
||||
mapobj._calculate_path_matrix()
|
||||
|
|
@ -962,7 +962,7 @@ class TestMapStressTest(TestCase):
|
|||
"""
|
||||
Xmax, Ymax = gridsize
|
||||
grid = self._get_grid(Xmax, Ymax)
|
||||
mapobj = mapsystem.Map({'map': grid})
|
||||
mapobj = mapsystem.Map({'map': grid}, name="testmap")
|
||||
|
||||
t0 = time()
|
||||
mapobj._calculate_path_matrix()
|
||||
|
|
@ -978,8 +978,11 @@ class TestMapStressTest(TestCase):
|
|||
|
||||
t0 = time()
|
||||
for coord, target in start_end_points:
|
||||
mapobj.get_visual_range(coord, dist=dist, mode='nodes', character='@', target=target)
|
||||
mapobj.get_visual_range(coord, dist=dist, mode='nodes',
|
||||
character='@', target=target)
|
||||
t1 = time()
|
||||
self.assertLess((t1 - t0) / 10, max_time,
|
||||
f"Visual Range calculation for ({Xmax}x{Ymax}) grid "
|
||||
f"slower than expected {max_time}s.")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -134,7 +134,6 @@ else:
|
|||
GAME_DIR = gpath
|
||||
break
|
||||
os.chdir(os.pardir)
|
||||
|
||||
# Place to put log files, how often to rotate the log and how big each log file
|
||||
# may become before rotating.
|
||||
LOG_DIR = os.path.join(GAME_DIR, "server", "logs")
|
||||
|
|
@ -152,6 +151,10 @@ LOCKWARNING_LOG_FILE = os.path.join(LOG_DIR, "lockwarnings.log")
|
|||
CHANNEL_LOG_NUM_TAIL_LINES = 20
|
||||
# Max size (in bytes) of channel log files before they rotate
|
||||
CHANNEL_LOG_ROTATE_SIZE = 1000000
|
||||
# Unused by default, but used by e.g. the MapSystem contrib. A place for storing
|
||||
# semi-permanent data and avoid it being rebuilt over and over. It is created
|
||||
# on-demand only.
|
||||
CACHE_DIR = os.path.join(GAME_DIR, "server", ".cache")
|
||||
# Local time zone for this installation. All choices can be found here:
|
||||
# http://www.postgresql.org/docs/8.0/interactive/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
|
||||
TIME_ZONE = "UTC"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue