Optimised pathfinder, particularly to avoid slow MSVC debug STL.

Added rough pathfinder test system.
MapReader: support loading maps into a minimal non-graphical environment
(for tests).

This was SVN commit r7317.
This commit is contained in:
Ykkrosh 2010-02-08 22:05:05 +00:00
parent c2fd939898
commit 02ffe82ab7
12 changed files with 383 additions and 102 deletions

View File

@ -62,7 +62,8 @@ CMapReader::CMapReader()
// LoadMap: try to load the map from given file; reinitialise the scene to new data if successful
void CMapReader::LoadMap(const VfsPath& pathname, CTerrain *pTerrain_,
CUnitManager *pUnitMan_, WaterManager* pWaterMan_, SkyManager* pSkyMan_,
CLightEnv *pLightEnv_, CCamera *pCamera_, CCinemaManager* pCinema_)
CLightEnv *pLightEnv_, CCamera *pCamera_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_,
CSimulation2 *pSimulation2_, CEntityManager *pEntityMan_)
{
// latch parameters (held until DelayedLoadFinished)
pTerrain = pTerrain_;
@ -72,6 +73,9 @@ void CMapReader::LoadMap(const VfsPath& pathname, CTerrain *pTerrain_,
pWaterMan = pWaterMan_;
pSkyMan = pSkyMan_;
pCinema = pCinema_;
pTrigMan = pTrigMan_;
pSimulation2 = pSimulation2_;
pEntityMan = pEntityMan_;
// [25ms]
unpacker.Read(pathname, "PSMP");
@ -82,17 +86,17 @@ void CMapReader::LoadMap(const VfsPath& pathname, CTerrain *pTerrain_,
}
// delete all existing entities
if (g_UseSimulation2)
{
g_Game->GetSimulation2()->ResetState();
}
else
{
g_EntityManager.DeleteAll();
}
if (pSimulation2)
pSimulation2->ResetState();
if (pEntityMan)
pEntityMan->DeleteAll();
// delete all remaining non-entity units
pUnitMan->DeleteAll();
pUnitMan->SetNextID(0);
if (pUnitMan)
{
pUnitMan->DeleteAll();
pUnitMan->SetNextID(0);
}
// unpack the data
RegMemFun(this, &CMapReader::UnpackMap, L"CMapReader::UnpackMap", 1200);
@ -204,13 +208,14 @@ int CMapReader::ApplyData()
if (! g_UseSimulation2)
{
// Make units start out conforming correctly
g_EntityManager.ConformAll();
pEntityMan->ConformAll();
}
if (unpacker.GetVersion() >= 4)
{
// copy over the lighting parameters
*pLightEnv = m_LightEnv;
if (pLightEnv)
*pLightEnv = m_LightEnv;
}
return 0;
}
@ -325,7 +330,7 @@ void CXMLReader::Init(const VfsPath& xml_filename)
// Initialise player data
CmpPtr<ICmpPlayerManager> cmpPlayerMan(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
CmpPtr<ICmpPlayerManager> cmpPlayerMan(*m_MapReader.pSimulation2, SYSTEM_ENTITY);
debug_assert(!cmpPlayerMan.null());
// TODO: this should be loaded from the XML instead
@ -333,7 +338,7 @@ void CXMLReader::Init(const VfsPath& xml_filename)
for (size_t i = 0; i < numPlayers; ++i)
{
int uid = ++max_uid;
entity_id_t ent = g_Game->GetSimulation2()->AddEntity(L"special/player", uid);
entity_id_t ent = m_MapReader.pSimulation2->AddEntity(L"special/player", uid);
cmpPlayerMan->AddPlayer(ent);
}
}
@ -374,7 +379,8 @@ void CXMLReader::ReadEnvironment(XMBElement parent)
if (element_name == el_skyset)
{
m_MapReader.pSkyMan->SetSkySet(element.GetText());
if (m_MapReader.pSkyMan)
m_MapReader.pSkyMan->SetSkySet(element.GetText());
}
else if (element_name == el_suncolour)
{
@ -416,6 +422,9 @@ void CXMLReader::ReadEnvironment(XMBElement parent)
debug_assert(waterbody.GetNodeName() == el_waterbody);
XERO_ITER_EL(waterbody, waterelement)
{
if (!m_MapReader.pWaterMan)
continue;
int element_name = waterelement.GetNodeName();
if (element_name == el_type)
{
@ -503,10 +512,13 @@ void CXMLReader::ReadCamera(XMBElement parent)
debug_warn(L"Invalid map XML data");
}
m_MapReader.pCamera->m_Orientation.SetXRotation(declination);
m_MapReader.pCamera->m_Orientation.RotateY(rotation);
m_MapReader.pCamera->m_Orientation.Translate(translation);
m_MapReader.pCamera->UpdateFrustum();
if (m_MapReader.pCamera)
{
m_MapReader.pCamera->m_Orientation.SetXRotation(declination);
m_MapReader.pCamera->m_Orientation.RotateY(rotation);
m_MapReader.pCamera->m_Orientation.Translate(translation);
m_MapReader.pCamera->UpdateFrustum();
}
}
void CXMLReader::ReadCinema(XMBElement parent)
@ -620,13 +632,16 @@ void CXMLReader::ReadCinema(XMBElement parent)
else
debug_assert("Invalid cinema child");
}
g_Game->GetView()->GetCinema()->SetAllPaths(pathList);
if (m_MapReader.pCinema)
m_MapReader.pCinema->SetAllPaths(pathList);
}
void CXMLReader::ReadTriggers(XMBElement parent)
{
MapTriggerGroup rootGroup( L"Triggers", L"" );
g_TriggerManager.DestroyEngineTriggers();
if (m_MapReader.pTrigMan)
m_MapReader.pTrigMan->DestroyEngineTriggers();
ReadTriggerGroup(parent, rootGroup);
}
@ -782,13 +797,14 @@ void CXMLReader::ReadTriggerGroup(XMBElement parent, MapTriggerGroup& group)
debug_warn(L"Invalid trigger node child in trigger XML file");
} //Read trigger children
g_TriggerManager.AddTrigger(mapGroup, mapTrigger);
m_MapReader.pTrigMan->AddTrigger(mapGroup, mapTrigger);
}
else
debug_warn(L"Invalid group node child in XML file");
} //Read group children
g_TriggerManager.AddGroup(mapGroup);
if (m_MapReader.pTrigMan)
m_MapReader.pTrigMan->AddGroup(mapGroup);
}
int CXMLReader::ReadEntities(XMBElement parent, double end_time)
@ -852,7 +868,7 @@ int CXMLReader::ReadEntities(XMBElement parent, double end_time)
debug_warn(L"Invalid map XML data");
}
CSimulation2& sim = *g_Game->GetSimulation2();
CSimulation2& sim = *m_MapReader.pSimulation2;
entity_id_t ent = sim.AddEntity(TemplateName, EntityUid);
if (ent == INVALID_ENTITY)
LOGERROR(L"Failed to load entity template '%ls'", TemplateName.c_str());
@ -898,7 +914,8 @@ int CXMLReader::ReadOldEntities(XMBElement parent, double end_time)
maxUnitID = std::max(maxUnitID, unitId);
}
m_MapReader.pUnitMan->SetNextID(maxUnitID + 1);
if (m_MapReader.pUnitMan)
m_MapReader.pUnitMan->SetNextID(maxUnitID + 1);
}
while (entity_idx < entities.Count)
@ -951,7 +968,11 @@ int CXMLReader::ReadOldEntities(XMBElement parent, double end_time)
debug_warn(L"Invalid map XML data");
}
CEntityTemplate* base = g_EntityTemplateCollection.GetTemplate(TemplateName, g_Game->GetPlayer(PlayerID));
CPlayer* player = NULL;
if (g_Game)
player = g_Game->GetPlayer(PlayerID);
CEntityTemplate* base = g_EntityTemplateCollection.GetTemplate(TemplateName, player);
if (! base)
LOG(CLogger::Error, LOG_CATEGORY, L"Failed to load entity template '%ls'", TemplateName.c_str());
else
@ -978,10 +999,10 @@ int CXMLReader::ReadOldEntities(XMBElement parent, double end_time)
else if (TemplateName.Find(L"camp") == 0 || TemplateName.Find(L"fence") == 0 || TemplateName.Find(L"temp") == 0)
TemplateName = L"other/" + TemplateName;
entity_id_t ent = g_Game->GetSimulation2()->AddEntity(TemplateName);
entity_id_t ent = m_MapReader.pSimulation2->AddEntity(TemplateName);
if (ent != INVALID_ENTITY)
{
CmpPtr<ICmpPosition> cmpPos(*g_Game->GetSimulation2(), ent);
CmpPtr<ICmpPosition> cmpPos(*m_MapReader.pSimulation2, ent);
if (!cmpPos.null())
{
entity_pos_t x = entity_pos_t::FromFloat(Position.X);
@ -990,21 +1011,21 @@ int CXMLReader::ReadOldEntities(XMBElement parent, double end_time)
cmpPos->SetYRotation(CFixed_23_8::FromFloat(Orientation));
}
CmpPtr<ICmpOwnership> cmpOwner(*g_Game->GetSimulation2(), ent);
CmpPtr<ICmpOwnership> cmpOwner(*m_MapReader.pSimulation2, ent);
if (!cmpOwner.null())
cmpOwner->SetOwner(PlayerID);
}
}
else
{
HEntity ent = g_EntityManager.Create(base, Position, Orientation, selections);
HEntity ent = m_MapReader.pEntityMan->Create(base, Position, Orientation, selections);
if (! ent)
LOG(CLogger::Error, LOG_CATEGORY, L"Failed to create entity of type '%ls'", TemplateName.c_str());
else
{
ent->m_actor->SetPlayerID(PlayerID);
g_EntityManager.AddEntityClassData(ent);
m_MapReader.pEntityMan->AddEntityClassData(ent);
if (unitId < 0)
ent->m_actor->SetID(m_MapReader.pUnitMan->GetNewID());
@ -1069,10 +1090,10 @@ int CXMLReader::ReadNonEntities(XMBElement parent, double end_time)
if (g_UseSimulation2)
{
entity_id_t ent = g_Game->GetSimulation2()->AddEntity(L"actor|" + ActorName);
entity_id_t ent = m_MapReader.pSimulation2->AddEntity(L"actor|" + ActorName);
if (ent != INVALID_ENTITY)
{
CmpPtr<ICmpPosition> cmpPos(*g_Game->GetSimulation2(), ent);
CmpPtr<ICmpPosition> cmpPos(*m_MapReader.pSimulation2, ent);
if (!cmpPos.null())
{
entity_pos_t x = entity_pos_t::FromFloat(Position.X); // TODO: these should all be parsed as fixeds probably

View File

@ -33,6 +33,8 @@ class CLightEnv;
class CCamera;
class CCinemaManager;
class CTriggerManager;
class CSimulation2;
class CEntityManager;
class CXMLReader;
@ -44,9 +46,9 @@ public:
// constructor
CMapReader();
// LoadMap: try to load the map from given file; reinitialise the scene to new data if successful
void LoadMap(const VfsPath& pathname, CTerrain *pTerrain, CUnitManager *pUnitMan,
WaterManager* pWaterMan, SkyManager* pSkyMan, CLightEnv *pLightEnv, CCamera *pCamera,
CCinemaManager* pCinema);
void LoadMap(const VfsPath& pathname, CTerrain*, CUnitManager*,
WaterManager*, SkyManager*, CLightEnv*, CCamera*,
CCinemaManager*, CTriggerManager*, CSimulation2*, CEntityManager*);
private:
// UnpackTerrain: unpack the terrain from the input stream
@ -87,6 +89,8 @@ private:
CCamera* pCamera;
CCinemaManager* pCinema;
CTriggerManager* pTrigMan;
CSimulation2* pSimulation2;
CEntityManager* pEntityMan;
VfsPath filename_xml;
// UnpackTerrain generator state

View File

@ -582,11 +582,13 @@ void CMapWriter::WriteTrigger(XMLWriter_File& xml_file_, const MapTrigger& trigg
}
} //Effects' scope
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// RewriteAllMaps
void CMapWriter::RewriteAllMaps(CTerrain* pTerrain, CUnitManager* pUnitMan,
WaterManager* pWaterMan, SkyManager* pSkyMan,
CLightEnv* pLightEnv, CCamera* pCamera, CCinemaManager* pCinema)
CLightEnv* pLightEnv, CCamera* pCamera, CCinemaManager* pCinema,
CTriggerManager* pTrigMan, CSimulation2* pSimulation2, CEntityManager* pEntityMan)
{
VfsPaths pathnames;
(void)fs_util::GetPathnames(g_VFS, L"maps/scenarios", L"*.pmp", pathnames);
@ -594,7 +596,7 @@ void CMapWriter::RewriteAllMaps(CTerrain* pTerrain, CUnitManager* pUnitMan,
{
CMapReader* reader = new CMapReader;
LDR_BeginRegistering();
reader->LoadMap(pathnames[i], pTerrain, pUnitMan, pWaterMan, pSkyMan, pLightEnv, pCamera, pCinema);
reader->LoadMap(pathnames[i], pTerrain, pUnitMan, pWaterMan, pSkyMan, pLightEnv, pCamera, pCinema, pTrigMan, pSimulation2, pEntityMan);
LDR_EndRegistering();
LDR_NonprogressiveLoad();

View File

@ -33,6 +33,8 @@ class CCinemaManager;
class CTriggerManager;
class WaterManager;
class SkyManager;
class CSimulation2;
class CEntityManager;
struct MapTrigger;
struct MapTriggerGroup;
class XMLWriter_File;
@ -52,7 +54,8 @@ public:
// update them to the newest format.
static void RewriteAllMaps(CTerrain* pTerrain, CUnitManager* pUnitMan, WaterManager* pWaterMan,
SkyManager* pSkyMan, CLightEnv* pLightEnv, CCamera* pCamera,
CCinemaManager* pCinema);
CCinemaManager* pCinema, CTriggerManager* pTrigMan,
CSimulation2* pSimulation2, CEntityManager* pEntityMan);
private:
// PackMap: pack the current world into a raw data stream

View File

@ -45,6 +45,7 @@
#include "simulation/EntityManager.h"
#include "simulation/LOSManager.h"
#include "simulation/TerritoryManager.h"
#include "simulation/TriggerManager.h"
#include "simulation/Projectile.h"
#define LOG_CATEGORY L"world"
@ -92,7 +93,8 @@ void CWorld::Initialize(CGameAttributes *pAttribs)
try {
reader = new CMapReader;
reader->LoadMap(mapfilename, m_Terrain, m_UnitManager, g_Renderer.GetWaterManager(),
g_Renderer.GetSkyManager(), &g_LightEnv, m_pGame->GetView()->GetCamera(), m_pGame->GetView()->GetCinema());
g_Renderer.GetSkyManager(), &g_LightEnv, m_pGame->GetView()->GetCamera(), m_pGame->GetView()->GetCinema(),
&g_TriggerManager, m_pGame->GetSimulation2(), m_EntityManager);
// fails immediately, or registers for delay loading
} catch (PSERROR_File&) {
delete reader;
@ -139,5 +141,6 @@ void CWorld::RewriteMap()
CMapWriter::RewriteAllMaps(m_Terrain, m_UnitManager,
g_Renderer.GetWaterManager(), g_Renderer.GetSkyManager(),
&g_LightEnv, m_pGame->GetView()->GetCamera(),
m_pGame->GetView()->GetCinema());
m_pGame->GetView()->GetCinema(), &g_TriggerManager,
m_pGame->GetSimulation2(), m_EntityManager);
}

View File

@ -29,6 +29,14 @@
#include "ps/Profile.h"
#include "renderer/TerrainOverlay.h"
#ifdef NDEBUG
#define PATHFIND_DEBUG 0
#else
#define PATHFIND_DEBUG 1
#endif
#define PATHFIND_STATS 0
class CCmpPathfinder;
struct PathfindTile;
@ -56,13 +64,17 @@ public:
void set(size_t i, size_t j, const T& value)
{
#if PATHFIND_DEBUG
debug_assert(i < m_W && j < m_H);
#endif
m_Data[j*m_W + i] = value;
}
T& get(size_t i, size_t j)
{
#if PATHFIND_DEBUG
debug_assert(i < m_W && j < m_H);
#endif
return m_Data[j*m_W + i];
}
@ -313,7 +325,7 @@ public:
REGISTER_COMPONENT_TYPE(Pathfinder)
u32 g_CostPerTile = 256; // base cost to move between adjacent tiles
const u32 g_CostPerTile = 256; // base cost to move between adjacent tiles
// Detect intersection between ray (0,0)-L and circle with center M radius r
// (Only counts intersections from the outside to the inside)
@ -384,6 +396,7 @@ struct PathfindTile
u8 status; // (TODO: this only needs 2 bits)
u16 pi, pj; // predecessor on best path (TODO: this only needs 2 bits)
u32 cost; // g (cost to this tile)
u32 h; // h (TODO: is it really better for performance to store this instead of recomputing?)
u32 step; // step at which this tile was last processed (TODO: this should only be present for debugging)
};
@ -452,14 +465,19 @@ struct QueueItemPriority
return true;
if (a.j > b.j)
return false;
#if PATHFIND_DEBUG
debug_warn(L"duplicate tiles in queue");
#endif
return false;
}
};
// Priority queue implementation, based on std::priority_queue but with O(n) find/update functions
// TODO: this is all a bit rubbish and slow
class PriorityQueue
/**
* Priority queue implemented as a binary heap.
* This is quite dreadfully slow in MSVC's debug STL implementation,
* so we shouldn't use it unless we reimplement the heap functions more efficiently.
*/
class PriorityQueueHeap
{
public:
void push(const QueueItem& item)
@ -468,11 +486,6 @@ public:
push_heap(m_Heap.begin(), m_Heap.end(), QueueItemPriority());
}
void fixheap()
{
make_heap(m_Heap.begin(), m_Heap.end(), QueueItemPriority());
}
QueueItem* find(u16 i, u16 j)
{
for (size_t n = 0; n < m_Heap.size(); ++n)
@ -483,30 +496,31 @@ public:
return NULL;
}
void remove(u16 i, u16 j)
void promote(u16 i, u16 j, u32 newrank)
{
for (size_t n = 0; n < m_Heap.size(); ++n)
{
if (m_Heap[n].i == i && m_Heap[n].j == j)
{
m_Heap.erase(m_Heap.begin() + n);
fixheap(); // XXX: this is slow
#if PATHFIND_DEBUG
debug_assert(m_Heap[n].rank > newrank);
#endif
m_Heap[n].rank = newrank;
push_heap(m_Heap.begin(), m_Heap.begin()+n+1, QueueItemPriority());
return;
}
}
}
const QueueItem& top()
{
debug_assert(m_Heap.size());
return m_Heap.front();
}
void pop()
QueueItem pop()
{
#if PATHFIND_DEBUG
debug_assert(m_Heap.size());
#endif
QueueItem r = m_Heap.front();
pop_heap(m_Heap.begin(), m_Heap.end(), QueueItemPriority());
m_Heap.pop_back();
return r;
}
bool empty()
@ -514,9 +528,82 @@ public:
return m_Heap.empty();
}
size_t size()
{
return m_Heap.size();
}
std::vector<QueueItem> m_Heap;
};
/**
* Priority queue implemented as an unsorted array.
* This means pop() is O(n), but push and promote are O(1), and n is typically small
* (average around 50-100 in some rough tests).
* It seems fractionally slower than a binary heap in optimised builds, but is
* much simpler and less susceptible to MSVC's painfully slow debug STL.
*/
class PriorityQueueList
{
public:
void push(const QueueItem& item)
{
m_List.push_back(item);
}
QueueItem* find(u16 i, u16 j)
{
for (size_t n = 0; n < m_List.size(); ++n)
{
if (m_List[n].i == i && m_List[n].j == j)
return &m_List[n];
}
return NULL;
}
void promote(u16 i, u16 j, u32 newrank)
{
find(i, j)->rank = newrank;
}
QueueItem pop()
{
#if PATHFIND_DEBUG
debug_assert(m_List.size());
#endif
// Loop backwards looking for the best (it's most likely to be one
// we've recently pushed, so going backwards saves a bit of copying)
QueueItem best = m_List.back();
size_t bestidx = m_List.size()-1;
for (ssize_t i = (ssize_t)bestidx-1; i >= 0; --i)
{
if (QueueItemPriority()(best, m_List[i]))
{
bestidx = i;
best = m_List[i];
}
}
// Swap the matched element with the last in the list, then pop the new last
m_List[bestidx] = m_List[m_List.size()-1];
m_List.pop_back();
return best;
}
bool empty()
{
return m_List.empty();
}
size_t size()
{
return m_List.size();
}
std::vector<QueueItem> m_List;
};
typedef PriorityQueueList PriorityQueue;
#define USE_DIAGONAL_MOVEMENT
@ -560,14 +647,14 @@ static u32 CalculateCostDelta(u16 pi, u16 pj, u16 i, u16 j, Grid<PathfindTile>*
PathfindTile& p = tempGrid->get(pi, pj);
if (p.pi != i && p.pj != j)
dg = dg*(sqrt(2.0)/2.0); // XXX: shouldn't use floats here
dg = (dg << 16) / 92682; // dg*sqrt(2)/2
else
{
PathfindTile& pp = tempGrid->get(p.pi, p.pj);
int di = abs(i - pp.pi);
int dj = abs(j - pp.pj);
if ((di == 1 && dj == 2) || (di == 2 && dj == 1))
dg = dg*(sqrt(5.0)-sqrt(2.0)); // XXX: shouldn't use floats here
dg = (dg << 16) / 79742; // dg*(sqrt(5)-sqrt(2))
}
#endif
@ -590,58 +677,79 @@ struct PathfinderState
u32 hBest; // heuristic of closest discovered tile to goal
u16 iBest, jBest; // closest tile
#if PATHFIND_STATS
// Performance debug counters
size_t numProcessed;
size_t numImproveOpen;
size_t numImproveClosed;
size_t numAddToOpen;
size_t sumOpenSize;
#endif
};
// Do the A* processing for a neighbour tile i,j.
static void ProcessNeighbour(u16 pi, u16 pj, u16 i, u16 j, u32 pg, PathfinderState& state)
{
#if PATHFIND_STATS
state.numProcessed++;
#endif
// Reject impassable tiles
if (state.terrain->get(i, j))
return;
u32 h = CalculateHeuristic(i, j, state.iGoal, state.jGoal, state.rGoal, state.aimingInwards);
u32 dg = CalculateCostDelta(pi, pj, i, j, state.tiles);
u32 g = pg + dg; // cost to this tile = cost to predecessor + delta from predecessor
// Remember the best tile we've seen so far, in case we never actually reach the target
if (h < state.hBest)
{
state.hBest = h;
state.iBest = i;
state.jBest = j;
}
PathfindTile& n = state.tiles->get(i, j);
// If we've already added this tile to the open list:
if (n.status == PathfindTile::STATUS_OPEN)
// If this is a new tile, compute the heuristic distance
if (n.status == PathfindTile::STATUS_UNEXPLORED)
{
// If this a better path, replace the old one with the new cost/parent
if (g < n.cost)
n.h = CalculateHeuristic(i, j, state.iGoal, state.jGoal, state.rGoal, state.aimingInwards);
// Remember the best tile we've seen so far, in case we never actually reach the target
if (n.h < state.hBest)
{
state.hBest = n.h;
state.iBest = i;
state.jBest = j;
}
}
else
{
// If we've already seen this tile, and the new path to this tile does not have a
// better cost, then stop now
if (g >= n.cost)
return;
// Otherwise, we have a better path.
// If we've already added this tile to the open list:
if (n.status == PathfindTile::STATUS_OPEN)
{
// This is a better path, so replace the old one with the new cost/parent
n.cost = g;
n.pi = pi;
n.pj = pj;
n.step = state.steps;
state.open.find(i, j)->rank = g + h;
state.open.fixheap(); // XXX: this is slow
}
return;
}
// If we've already found the 'best' path to this tile:
if (n.status == PathfindTile::STATUS_CLOSED)
{
// If this is a better path (possible when we use inadmissible heuristics), reopen it
if (g < n.cost)
{
// (don't return yet)
}
else
{
state.open.promote(i, j, g + n.h);
#if PATHFIND_STATS
state.numImproveOpen++;
#endif
return;
}
// If we've already found the 'best' path to this tile:
if (n.status == PathfindTile::STATUS_CLOSED)
{
// This is a better path (possible when we use inadmissible heuristics), so reopen it
#if PATHFIND_STATS
state.numImproveClosed++;
#endif
// (fall through)
}
}
// Add it to the open list:
@ -650,8 +758,11 @@ static void ProcessNeighbour(u16 pi, u16 pj, u16 i, u16 j, u32 pg, PathfinderSta
n.pi = pi;
n.pj = pj;
n.step = state.steps;
QueueItem t = { i, j, g + h };
QueueItem t = { i, j, g + n.h };
state.open.push(t);
#if PATHFIND_STATS
state.numAddToOpen++;
#endif
}
static bool AtGoal(u16 i, u16 j, u16 iGoal, u16 jGoal, u16 rGoal, bool aimingInwards)
@ -676,7 +787,7 @@ void CCmpPathfinder::ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& g
PROFILE("ComputePath");
PathfinderState state;
PathfinderState state = { 0 };
// Convert the start/end coordinates to tile indexes
u16 i0, j0;
@ -739,9 +850,12 @@ void CCmpPathfinder::ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& g
if (state.open.empty())
break;
#if PATHFIND_STATS
state.sumOpenSize += state.open.size();
#endif
// Move best tile from open to closed
QueueItem curr = state.open.top();
state.open.pop();
QueueItem curr = state.open.pop();
state.tiles->get(curr.i, curr.j).status = PathfindTile::STATUS_CLOSED;
// If we've reached the destination, stop
@ -792,4 +906,8 @@ void CCmpPathfinder::ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& g
delete m_DebugGrid;
m_DebugGrid = state.tiles;
m_DebugSteps = state.steps;
#if PATHFIND_STATS
printf("PATHFINDER: steps=%d avgo=%d proc=%d impc=%d impo=%d addo=%d\n", state.steps, state.sumOpenSize/state.steps, state.numProcessed, state.numImproveClosed, state.numImproveOpen, state.numAddToOpen);
#endif
}

View File

@ -61,6 +61,9 @@ public:
virtual void Init(const CSimContext& context, const CParamNode& paramNode)
{
if (!context.HasUnitManager())
return; // do nothing if graphics are disabled
std::set<CStr> selections;
std::string name = utf8_from_wstring(paramNode.GetChild("Actor").ToString());
m_Unit = context.GetUnitManager().CreateUnit(name, NULL, selections);

View File

@ -0,0 +1,116 @@
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "simulation2/system/ComponentTest.h"
#include "simulation2/components/ICmpPathfinder.h"
#include "graphics/MapReader.h"
#include "graphics/Terrain.h"
#include "graphics/TextureManager.h"
#include "ps/Loader.h"
#include "ps/Profile.h"
#include "ps/ProfileViewer.h"
#include "scripting/ScriptingHost.h"
#include "simulation/EntityTemplateCollection.h"
#include "simulation2/Simulation2.h"
class TestCmpPathfinder : public CxxTest::TestSuite
{
public:
void setUp()
{
g_UseSimulation2 = true;
CXeromyces::Startup();
g_VFS = CreateVfs(20 * MiB);
TS_ASSERT_OK(g_VFS->Mount(L"", DataDir()/L"mods/public"));
// Set up loads of stuff that's needed in order to load a map
new CTextureManager();
new CProfileViewer();
new CProfileManager();
new ScriptingHost();
new CEntityTemplateCollection();
g_EntityTemplateCollection.LoadTemplates();
}
void tearDown()
{
delete &g_EntityTemplateCollection;
delete &g_ScriptingHost;
delete &g_Profiler;
delete &g_ProfileViewer;
delete &g_TexMan;
g_VFS.reset();
CXeromyces::Terminate();
g_UseSimulation2 = false;
}
// disabled by default; run tests with the "-test TestCmpPathfinder" flag to enable
void test_performance_DISABLED()
{
CTerrain terrain;
CSimulation2 sim2(NULL, &terrain);
sim2.LoadScripts(L"simulation/components/interfaces/");
sim2.LoadScripts(L"simulation/helpers/");
sim2.LoadScripts(L"simulation/components/");
sim2.ResetState();
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, NULL, &sim2, NULL);
LDR_EndRegistering();
TS_ASSERT_OK(LDR_NonprogressiveLoad());
sim2.Update(0);
CmpPtr<ICmpPathfinder> cmp(sim2, SYSTEM_ENTITY);
#if 0
entity_pos_t x0 = entity_pos_t::FromInt(10);
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::Path path;
cmp->ComputePath(x0, z0, goal, 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
srand(1234);
for (size_t j = 0; j < 256; ++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);
ICmpPathfinder::Goal goal = { x1, z1, entity_pos_t::FromInt(0), entity_pos_t::FromInt(0) };
ICmpPathfinder::Path path;
cmp->ComputePath(x0, z0, goal, path);
}
}
};

View File

@ -64,14 +64,18 @@ public:
return m_ComponentManager.GetScriptInterface();
}
CSimContext& GetSimContext()
{
return m_Context;
}
/**
* Call this once to initialise the test helper with a component.
*/
template<typename T>
T* Add(EComponentTypeId cid, const std::string& xml)
T* Add(EComponentTypeId cid, const std::string& xml, entity_id_t ent = 10)
{
TS_ASSERT(m_Cmp == NULL);
entity_id_t ent = 1;
m_Cid = cid;
TS_ASSERT_EQUALS(CParamNode::LoadXMLString(m_Param, ("<test>" + xml + "</test>").c_str()), PSRETURN_OK);

View File

@ -34,6 +34,11 @@ CComponentManager& CSimContext::GetComponentManager() const
return *m_ComponentManager;
}
bool CSimContext::HasUnitManager() const
{
return m_UnitManager != NULL;
}
CUnitManager& CSimContext::GetUnitManager() const
{
debug_assert(m_UnitManager);

View File

@ -33,11 +33,13 @@ public:
~CSimContext();
CComponentManager& GetComponentManager() const;
CUnitManager& GetUnitManager() const;
CTerrain& GetTerrain() const;
void SetComponentManager(CComponentManager* man);
bool HasUnitManager() const;
CUnitManager& GetUnitManager() const;
CTerrain& GetTerrain() const;
private:
CComponentManager* m_ComponentManager;
CUnitManager* m_UnitManager;

View File

@ -156,7 +156,7 @@ public:
CMessageTurnStart msg1;
CMessageUpdate msg2(CFixed_23_8::FromInt(100));
CMessageInterpolate msg3(0);
CMessageInterpolate msg3(0, 0);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent1, IID_Test1))->GetX(), 11000);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent2, IID_Test1))->GetX(), 12000);