forked from 0ad/0ad
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:
parent
c2fd939898
commit
02ffe82ab7
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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);
|
||||
|
116
source/simulation2/components/tests/test_Pathfinder.h
Normal file
116
source/simulation2/components/tests/test_Pathfinder.h
Normal 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);
|
||||
}
|
||||
}
|
||||
};
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user