Optimise tile-based pathfinder, particularly for large maps.
This was SVN commit r9000.
This commit is contained in:
parent
cb0e322a61
commit
794584ea11
@ -123,6 +123,8 @@ const int COST_CLASS_BITS = 16 - (PASS_CLASS_BITS + 2);
|
||||
#define GET_COST_CLASS(item) ((item) >> (PASS_CLASS_BITS + 2))
|
||||
#define COST_CLASS_MASK(id) ( (TerrainTile) ((id) << (PASS_CLASS_BITS + 2)) )
|
||||
|
||||
typedef SparseGrid<PathfindTile> PathfindTileGrid;
|
||||
|
||||
struct AsyncLongPathRequest
|
||||
{
|
||||
u32 ticket;
|
||||
@ -187,7 +189,7 @@ public:
|
||||
bool m_TerrainDirty; // indicates if m_Grid has been updated since terrain changed
|
||||
|
||||
// Debugging - output from last pathfind operation:
|
||||
Grid<PathfindTile>* m_DebugGrid;
|
||||
PathfindTileGrid* m_DebugGrid;
|
||||
u32 m_DebugSteps;
|
||||
Path* m_DebugPath;
|
||||
PathfinderOverlay* m_DebugOverlay;
|
||||
|
@ -191,7 +191,7 @@ struct PathfinderState
|
||||
PriorityQueue open;
|
||||
// (there's no explicit closed list; it's encoded in PathfindTile)
|
||||
|
||||
Grid<PathfindTile>* tiles;
|
||||
PathfindTileGrid* tiles;
|
||||
Grid<TerrainTile>* terrain;
|
||||
|
||||
bool ignoreImpassable; // allows us to escape if stuck in patches of impassability
|
||||
@ -249,7 +249,7 @@ static u32 CalculateHeuristic(u16 i, u16 j, u16 iGoal, u16 jGoal, u16 rGoal)
|
||||
}
|
||||
|
||||
// Calculate movement cost from predecessor tile pi,pj to tile i,j
|
||||
static u32 CalculateCostDelta(u16 pi, u16 pj, u16 i, u16 j, Grid<PathfindTile>* tempGrid, u32 tileCost)
|
||||
static u32 CalculateCostDelta(u16 pi, u16 pj, u16 i, u16 j, PathfindTileGrid* tempGrid, u32 tileCost)
|
||||
{
|
||||
u32 dg = tileCost;
|
||||
|
||||
@ -391,7 +391,7 @@ void CCmpPathfinder::ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& g
|
||||
|
||||
state.steps = 0;
|
||||
|
||||
state.tiles = new Grid<PathfindTile>(m_MapSize, m_MapSize);
|
||||
state.tiles = new PathfindTileGrid(m_MapSize, m_MapSize);
|
||||
state.terrain = m_Grid;
|
||||
|
||||
state.iBest = i0;
|
||||
|
@ -22,6 +22,9 @@
|
||||
|
||||
#include "graphics/MapReader.h"
|
||||
#include "graphics/Terrain.h"
|
||||
#include "graphics/TerrainTextureManager.h"
|
||||
#include "lib/timer.h"
|
||||
#include "lib/tex/tex.h"
|
||||
#include "ps/Loader.h"
|
||||
#include "simulation2/Simulation2.h"
|
||||
|
||||
@ -35,10 +38,19 @@ public:
|
||||
g_VFS = CreateVfs(20 * MiB);
|
||||
TS_ASSERT_OK(g_VFS->Mount(L"", DataDir()/L"mods/public", VFS_MOUNT_MUST_EXIST));
|
||||
TS_ASSERT_OK(g_VFS->Mount(L"cache/", DataDir()/L"cache"));
|
||||
|
||||
// Need some stuff for terrain movement costs:
|
||||
// (TODO: this ought to be independent of any graphics code)
|
||||
tex_codec_register_all();
|
||||
new CTerrainTextureManager;
|
||||
g_TexMan.LoadTerrainTextures();
|
||||
}
|
||||
|
||||
void tearDown()
|
||||
{
|
||||
delete &g_TexMan;
|
||||
tex_codec_unregister_all();
|
||||
|
||||
g_VFS.reset();
|
||||
|
||||
CXeromyces::Terminate();
|
||||
@ -56,7 +68,7 @@ public:
|
||||
CMapReader* mapReader = new CMapReader(); // it'll call "delete this" itself
|
||||
|
||||
LDR_BeginRegistering();
|
||||
mapReader->LoadMap(L"maps/scenarios/Latium.pmp", &terrain, NULL, NULL, NULL, NULL, NULL, NULL, &sim2, -1);
|
||||
mapReader->LoadMap(L"maps/scenarios/Median Oasis.pmp", &terrain, NULL, NULL, NULL, NULL, NULL, NULL, &sim2, -1);
|
||||
LDR_EndRegistering();
|
||||
TS_ASSERT_OK(LDR_NonprogressiveLoad());
|
||||
|
||||
@ -69,26 +81,31 @@ public:
|
||||
entity_pos_t z0 = entity_pos_t::FromInt(495);
|
||||
entity_pos_t x1 = entity_pos_t::FromInt(500);
|
||||
entity_pos_t z1 = entity_pos_t::FromInt(495);
|
||||
ICmpPathfinder::Goal goal = { x1, z1, entity_pos_t::FromInt(0), entity_pos_t::FromInt(0) };
|
||||
ICmpPathfinder::Goal goal = { ICmpPathfinder::Goal::POINT, x1, z1 };
|
||||
|
||||
ICmpPathfinder::Path path;
|
||||
cmp->ComputePath(x0, z0, goal, path);
|
||||
cmp->ComputePath(x0, z0, goal, cmp->GetPassabilityClass("default"), cmp->GetCostClass("default"), path);
|
||||
for (size_t i = 0; i < path.m_Waypoints.size(); ++i)
|
||||
printf("%d: %f %f\n", (int)i, path.m_Waypoints[i].x.ToDouble(), path.m_Waypoints[i].z.ToDouble());
|
||||
#endif
|
||||
|
||||
double t = timer_Time();
|
||||
|
||||
srand(1234);
|
||||
for (size_t j = 0; j < 2560; ++j)
|
||||
for (size_t j = 0; j < 1024*2; ++j)
|
||||
{
|
||||
entity_pos_t x0 = entity_pos_t::FromInt(rand() % 512);
|
||||
entity_pos_t z0 = entity_pos_t::FromInt(rand() % 512);
|
||||
entity_pos_t x1 = entity_pos_t::FromInt(rand() % 512);
|
||||
entity_pos_t z1 = entity_pos_t::FromInt(rand() % 512);
|
||||
entity_pos_t x1 = x0 + entity_pos_t::FromInt(rand() % 64);
|
||||
entity_pos_t z1 = z0 + entity_pos_t::FromInt(rand() % 64);
|
||||
ICmpPathfinder::Goal goal = { ICmpPathfinder::Goal::POINT, x1, z1 };
|
||||
|
||||
ICmpPathfinder::Path path;
|
||||
cmp->ComputePath(x0, z0, goal, cmp->GetPassabilityClass("default"), cmp->GetCostClass("default"), path);
|
||||
}
|
||||
|
||||
t = timer_Time() - t;
|
||||
printf("[%f]", t);
|
||||
}
|
||||
|
||||
void test_performance_short_DISABLED()
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
/* Copyright (C) 2011 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -34,6 +34,7 @@
|
||||
template<typename T>
|
||||
class Grid
|
||||
{
|
||||
NONCOPYABLE(Grid);
|
||||
public:
|
||||
Grid(u16 w, u16 h) : m_W(w), m_H(h), m_DirtyID(0)
|
||||
{
|
||||
@ -73,4 +74,73 @@ public:
|
||||
size_t m_DirtyID; // if this is < the id maintained by ICmpObstructionManager then it needs to be updated
|
||||
};
|
||||
|
||||
/**
|
||||
* Similar to Grid, except optimised for sparse usage (the grid is subdivided into
|
||||
* buckets whose contents are only initialised on demand, to save on memset cost).
|
||||
*/
|
||||
template<typename T>
|
||||
class SparseGrid
|
||||
{
|
||||
NONCOPYABLE(SparseGrid);
|
||||
|
||||
enum { BucketBits = 4, BucketSize = 1 << BucketBits };
|
||||
|
||||
T* GetBucket(size_t i, size_t j)
|
||||
{
|
||||
size_t b = (j >> BucketBits) * m_BW + (i >> BucketBits);
|
||||
if (!m_Data[b])
|
||||
{
|
||||
m_Data[b] = new T[BucketSize*BucketSize];
|
||||
memset(m_Data[b], 0, BucketSize*BucketSize*sizeof(T));
|
||||
}
|
||||
return m_Data[b];
|
||||
}
|
||||
|
||||
public:
|
||||
SparseGrid(u16 w, u16 h) : m_W(w), m_H(h), m_DirtyID(0)
|
||||
{
|
||||
m_BW = (m_W + BucketSize-1) >> BucketBits;
|
||||
m_BH = (m_H + BucketSize-1) >> BucketBits;
|
||||
|
||||
m_Data = new T*[m_BW*m_BH];
|
||||
memset(m_Data, 0, m_BW*m_BH*sizeof(T*));
|
||||
}
|
||||
|
||||
~SparseGrid()
|
||||
{
|
||||
reset();
|
||||
delete[] m_Data;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
for (size_t i = 0; i < (size_t)(m_BW*m_BH); ++i)
|
||||
delete[] m_Data[i];
|
||||
|
||||
memset(m_Data, 0, m_BW*m_BH*sizeof(T*));
|
||||
}
|
||||
|
||||
void set(size_t i, size_t j, const T& value)
|
||||
{
|
||||
#if GRID_BOUNDS_DEBUG
|
||||
debug_assert(i < m_W && j < m_H);
|
||||
#endif
|
||||
GetBucket(i, j)[(j % BucketSize)*BucketSize + (i % BucketSize)] = value;
|
||||
}
|
||||
|
||||
T& get(size_t i, size_t j)
|
||||
{
|
||||
#if GRID_BOUNDS_DEBUG
|
||||
debug_assert(i < m_W && j < m_H);
|
||||
#endif
|
||||
return GetBucket(i, j)[(j % BucketSize)*BucketSize + (i % BucketSize)];
|
||||
}
|
||||
|
||||
u16 m_W, m_H;
|
||||
u16 m_BW, m_BH;
|
||||
T** m_Data;
|
||||
|
||||
size_t m_DirtyID; // if this is < the id maintained by ICmpObstructionManager then it needs to be updated
|
||||
};
|
||||
|
||||
#endif // INCLUDED_GRID
|
||||
|
Loading…
Reference in New Issue
Block a user