From 794584ea1117dfba65712ac616b14c704b2dbc28 Mon Sep 17 00:00:00 2001 From: Ykkrosh Date: Mon, 28 Feb 2011 00:35:53 +0000 Subject: [PATCH] Optimise tile-based pathfinder, particularly for large maps. This was SVN commit r9000. --- .../components/CCmpPathfinder_Common.h | 4 +- .../components/CCmpPathfinder_Tile.cpp | 6 +- .../components/tests/test_Pathfinder.h | 29 ++++++-- source/simulation2/helpers/Grid.h | 72 ++++++++++++++++++- 4 files changed, 100 insertions(+), 11 deletions(-) diff --git a/source/simulation2/components/CCmpPathfinder_Common.h b/source/simulation2/components/CCmpPathfinder_Common.h index 1980a72cc7..607db1e5d9 100644 --- a/source/simulation2/components/CCmpPathfinder_Common.h +++ b/source/simulation2/components/CCmpPathfinder_Common.h @@ -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 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* m_DebugGrid; + PathfindTileGrid* m_DebugGrid; u32 m_DebugSteps; Path* m_DebugPath; PathfinderOverlay* m_DebugOverlay; diff --git a/source/simulation2/components/CCmpPathfinder_Tile.cpp b/source/simulation2/components/CCmpPathfinder_Tile.cpp index b7352559ec..0434f64b0a 100644 --- a/source/simulation2/components/CCmpPathfinder_Tile.cpp +++ b/source/simulation2/components/CCmpPathfinder_Tile.cpp @@ -191,7 +191,7 @@ struct PathfinderState PriorityQueue open; // (there's no explicit closed list; it's encoded in PathfindTile) - Grid* tiles; + PathfindTileGrid* tiles; Grid* 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* 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(m_MapSize, m_MapSize); + state.tiles = new PathfindTileGrid(m_MapSize, m_MapSize); state.terrain = m_Grid; state.iBest = i0; diff --git a/source/simulation2/components/tests/test_Pathfinder.h b/source/simulation2/components/tests/test_Pathfinder.h index 05a74ecb81..f4720a097b 100644 --- a/source/simulation2/components/tests/test_Pathfinder.h +++ b/source/simulation2/components/tests/test_Pathfinder.h @@ -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() diff --git a/source/simulation2/helpers/Grid.h b/source/simulation2/helpers/Grid.h index a14bd2daf6..eda4531d37 100644 --- a/source/simulation2/helpers/Grid.h +++ b/source/simulation2/helpers/Grid.h @@ -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 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 +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