Index: ps/trunk/source/simulation2/components/CCmpPathfinder_Common.h =================================================================== --- ps/trunk/source/simulation2/components/CCmpPathfinder_Common.h (revision 9664) +++ ps/trunk/source/simulation2/components/CCmpPathfinder_Common.h (revision 9665) @@ -1,273 +1,286 @@ /* 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 * 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 . */ #ifndef INCLUDED_CCMPPATHFINDER_COMMON #define INCLUDED_CCMPPATHFINDER_COMMON /** * @file * Declares CCmpPathfinder, whose implementation is split into multiple source files, * and provides common code needed for more than one of those files. * CCmpPathfinder includes two pathfinding algorithms (one tile-based, one vertex-based) * with some shared state and functionality, so the code is split into * CCmpPathfinder_Vertex.cpp, CCmpPathfinder_Tile.cpp and CCmpPathfinder.cpp */ #include "simulation2/system/Component.h" #include "ICmpPathfinder.h" #include "graphics/Overlay.h" #include "graphics/Terrain.h" #include "maths/MathUtil.h" #include "simulation2/helpers/Geometry.h" #include "simulation2/helpers/Grid.h" class PathfinderOverlay; class SceneCollector; struct PathfindTile; #ifdef NDEBUG #define PATHFIND_DEBUG 0 #else #define PATHFIND_DEBUG 1 #endif /* * For efficient pathfinding we want to try hard to minimise the per-tile search cost, * so we precompute the tile passability flags and movement costs for the various different * types of unit. * We also want to minimise memory usage (there can easily be 100K tiles so we don't want * to store many bytes for each). * * To handle passability efficiently, we have a small number of passability classes * (e.g. "infantry", "ship"). Each unit belongs to a single passability class, and * uses that for all its pathfinding. * Passability is determined by water depth, terrain slope, forestness, buildingness. * We need at least one bit per class per tile to represent passability. * * We use a separate bit to indicate building obstructions (instead of folding it into * the class passabilities) so that it can be ignored when doing the accurate short paths. * We use another bit to indicate tiles near obstructions that block construction, * for the AI to plan safe building spots. * * To handle movement costs, we have an arbitrary number of unit cost classes (e.g. "infantry", "camel"), * and a small number of terrain cost classes (e.g. "grass", "steep grass", "road", "sand"), * and a cost mapping table between the classes (e.g. camels are fast on sand). * We need log2(|terrain cost classes|) bits per tile to represent costs. * * We could have one passability bitmap per class, and another array for cost classes, * but instead (for no particular reason) we'll pack them all into a single u16 array. * * We handle dynamic updates currently by recomputing the entire array, which is stupid; * it should only bother updating the region that has changed. */ class PathfinderPassability { public: PathfinderPassability(ICmpPathfinder::pass_class_t mask, const CParamNode& node) : m_Mask(mask) { if (node.GetChild("MinWaterDepth").IsOk()) m_MinDepth = node.GetChild("MinWaterDepth").ToFixed(); else m_MinDepth = std::numeric_limits::min(); if (node.GetChild("MaxWaterDepth").IsOk()) m_MaxDepth = node.GetChild("MaxWaterDepth").ToFixed(); else m_MaxDepth = std::numeric_limits::max(); if (node.GetChild("MaxTerrainSlope").IsOk()) m_MaxSlope = node.GetChild("MaxTerrainSlope").ToFixed(); else m_MaxSlope = std::numeric_limits::max(); } bool IsPassable(fixed waterdepth, fixed steepness) { return ((m_MinDepth <= waterdepth && waterdepth <= m_MaxDepth) && (steepness < m_MaxSlope)); } ICmpPathfinder::pass_class_t m_Mask; private: fixed m_MinDepth; fixed m_MaxDepth; fixed m_MaxSlope; }; typedef u16 TerrainTile; // 1 bit for pathfinding obstructions, // 1 bit for construction obstructions (used by AI), // PASS_CLASS_BITS for terrain passability (allowing PASS_CLASS_BITS classes), // COST_CLASS_BITS for movement costs (allowing 2^COST_CLASS_BITS classes) const int PASS_CLASS_BITS = 10; const int COST_CLASS_BITS = 16 - (PASS_CLASS_BITS + 2); #define IS_TERRAIN_PASSABLE(item, classmask) (((item) & (classmask)) == 0) #define IS_PASSABLE(item, classmask) (((item) & ((classmask) | 1)) == 0) #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; entity_pos_t x0; entity_pos_t z0; ICmpPathfinder::Goal goal; ICmpPathfinder::pass_class_t passClass; ICmpPathfinder::cost_class_t costClass; entity_id_t notify; }; struct AsyncShortPathRequest { u32 ticket; entity_pos_t x0; entity_pos_t z0; entity_pos_t r; entity_pos_t range; ICmpPathfinder::Goal goal; ICmpPathfinder::pass_class_t passClass; bool avoidMovingUnits; entity_id_t group; entity_id_t notify; }; /** * Implementation of ICmpPathfinder */ class CCmpPathfinder : public ICmpPathfinder { public: static void ClassInit(CComponentManager& componentManager) { componentManager.SubscribeToMessageType(MT_Update); componentManager.SubscribeToMessageType(MT_RenderSubmit); // for debug overlays componentManager.SubscribeToMessageType(MT_TerrainChanged); + componentManager.SubscribeToMessageType(MT_TurnStart); } DEFAULT_COMPONENT_ALLOCATOR(Pathfinder) // Template state: std::map m_PassClassMasks; std::vector m_PassClasses; std::map m_TerrainCostClassTags; std::map m_UnitCostClassTags; std::vector > m_MoveCosts; // costs[unitClass][terrainClass] std::vector > m_MoveSpeeds; // speeds[unitClass][terrainClass] // Dynamic state: std::vector m_AsyncLongPathRequests; std::vector m_AsyncShortPathRequests; u32 m_NextAsyncTicket; // unique IDs for asynchronous path requests // Lazily-constructed dynamic state (not serialized): u16 m_MapSize; // tiles per side Grid* m_Grid; // terrain/passability information Grid* m_ObstructionGrid; // cached obstruction information (TODO: we shouldn't bother storing this, it's redundant with LSBs of m_Grid) bool m_TerrainDirty; // indicates if m_Grid has been updated since terrain changed + + // For responsiveness we will procees some moves in the same turn they were generated in + + u16 m_MaxSameTurnMoves; // max number of moves that can be created and processed in the same turn + u16 m_SameTurnMovesCount; // current number of same turn moves we have processed this turn // Debugging - output from last pathfind operation: + PathfindTileGrid* m_DebugGrid; u32 m_DebugSteps; Path* m_DebugPath; PathfinderOverlay* m_DebugOverlay; pass_class_t m_DebugPassClass; std::vector m_DebugOverlayShortPathLines; static std::string GetSchema() { return ""; } virtual void Init(const CParamNode& paramNode); virtual void Deinit(); virtual void Serialize(ISerializer& serialize); virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize); virtual void HandleMessage(const CMessage& msg, bool global); virtual pass_class_t GetPassabilityClass(const std::string& name); virtual std::map GetPassabilityClasses(); virtual cost_class_t GetCostClass(const std::string& name); virtual const Grid& GetPassabilityGrid(); virtual void ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass, Path& ret); virtual u32 ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass, entity_id_t notify); virtual void ComputeShortPath(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal& goal, pass_class_t passClass, Path& ret); virtual u32 ComputeShortPathAsync(entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal& goal, pass_class_t passClass, bool avoidMovingUnits, entity_id_t controller, entity_id_t notify); virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass); virtual void ResetDebugPath(); virtual void SetDebugOverlay(bool enabled); virtual fixed GetMovementSpeed(entity_pos_t x0, entity_pos_t z0, cost_class_t costClass); virtual CFixedVector2D GetNearestPointOnGoal(CFixedVector2D pos, const Goal& goal); virtual bool CheckMovement(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r, pass_class_t passClass); virtual void FinishAsyncRequests(); + virtual void ProcessLongRequests(const std::vector& longRequests); + + virtual void ProcessShortRequests(const std::vector& shortRequests); + + virtual void ProcessSameTurnMoves(); + /** * Returns the tile containing the given position */ void NearestTile(entity_pos_t x, entity_pos_t z, u16& i, u16& j) { i = clamp((x / (int)CELL_SIZE).ToInt_RoundToZero(), 0, m_MapSize-1); j = clamp((z / (int)CELL_SIZE).ToInt_RoundToZero(), 0, m_MapSize-1); } /** * Returns the position of the center of the given tile */ static void TileCenter(u16 i, u16 j, entity_pos_t& x, entity_pos_t& z) { x = entity_pos_t::FromInt(i*(int)CELL_SIZE + CELL_SIZE/2); z = entity_pos_t::FromInt(j*(int)CELL_SIZE + CELL_SIZE/2); } static fixed DistanceToGoal(CFixedVector2D pos, const CCmpPathfinder::Goal& goal); /** * Regenerates the grid based on the current obstruction list, if necessary */ void UpdateGrid(); void RenderSubmit(SceneCollector& collector); }; #endif // INCLUDED_CCMPPATHFINDER_COMMON Index: ps/trunk/source/simulation2/components/CCmpPathfinder.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpPathfinder.cpp (revision 9664) +++ ps/trunk/source/simulation2/components/CCmpPathfinder.cpp (revision 9665) @@ -1,456 +1,533 @@ /* 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 * 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 . */ /** * @file * Common code and setup code for CCmpPathfinder. */ #include "precompiled.h" #include "CCmpPathfinder_Common.h" #include "ps/CLogger.h" #include "ps/CStr.h" #include "ps/Profile.h" #include "renderer/Scene.h" #include "simulation2/MessageTypes.h" #include "simulation2/components/ICmpObstructionManager.h" #include "simulation2/components/ICmpWaterManager.h" #include "simulation2/serialization/SerializeTemplates.h" // Default cost to move a single tile is a fairly arbitrary number, which should be big // enough to be precise when multiplied/divided and small enough to never overflow when // summing the cost of a whole path. const int DEFAULT_MOVE_COST = 256; REGISTER_COMPONENT_TYPE(Pathfinder) void CCmpPathfinder::Init(const CParamNode& UNUSED(paramNode)) { m_MapSize = 0; m_Grid = NULL; m_ObstructionGrid = NULL; m_TerrainDirty = true; m_NextAsyncTicket = 1; m_DebugOverlay = NULL; m_DebugGrid = NULL; m_DebugPath = NULL; // Since this is used as a system component (not loaded from an entity template), // we can't use the real paramNode (it won't get handled properly when deserializing), // so load the data from a special XML file. CParamNode externalParamNode; CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml"); + const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder"); + m_MaxSameTurnMoves = pathingSettings.GetChild("MaxSameTurnMoves").ToInt(); + const CParamNode::ChildrenMap& passClasses = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses").GetChildren(); for (CParamNode::ChildrenMap::const_iterator it = passClasses.begin(); it != passClasses.end(); ++it) { std::string name = it->first; ENSURE((int)m_PassClasses.size() <= PASS_CLASS_BITS); pass_class_t mask = (pass_class_t)(1u << (m_PassClasses.size() + 2)); m_PassClasses.push_back(PathfinderPassability(mask, it->second)); m_PassClassMasks[name] = mask; } const CParamNode::ChildrenMap& moveClasses = externalParamNode.GetChild("Pathfinder").GetChild("MovementClasses").GetChildren(); // First find the set of unit classes used by any terrain classes, // and assign unique tags to terrain classes std::set unitClassNames; unitClassNames.insert("default"); // must always have costs for default { size_t i = 0; for (CParamNode::ChildrenMap::const_iterator it = moveClasses.begin(); it != moveClasses.end(); ++it) { std::string terrainClassName = it->first; m_TerrainCostClassTags[terrainClassName] = (cost_class_t)i; ++i; const CParamNode::ChildrenMap& unitClasses = it->second.GetChild("UnitClasses").GetChildren(); for (CParamNode::ChildrenMap::const_iterator uit = unitClasses.begin(); uit != unitClasses.end(); ++uit) unitClassNames.insert(uit->first); } } // For each terrain class, set the costs for every unit class, // and assign unique tags to unit classes { size_t i = 0; for (std::set::const_iterator nit = unitClassNames.begin(); nit != unitClassNames.end(); ++nit) { m_UnitCostClassTags[*nit] = (cost_class_t)i; ++i; std::vector costs; std::vector speeds; for (CParamNode::ChildrenMap::const_iterator it = moveClasses.begin(); it != moveClasses.end(); ++it) { // Default to the general costs for this terrain class fixed cost = it->second.GetChild("@Cost").ToFixed(); fixed speed = it->second.GetChild("@Speed").ToFixed(); // Check for specific cost overrides for this unit class const CParamNode& unitClass = it->second.GetChild("UnitClasses").GetChild(nit->c_str()); if (unitClass.IsOk()) { cost = unitClass.GetChild("@Cost").ToFixed(); speed = unitClass.GetChild("@Speed").ToFixed(); } costs.push_back((cost * DEFAULT_MOVE_COST).ToInt_RoundToZero()); speeds.push_back(speed); } m_MoveCosts.push_back(costs); m_MoveSpeeds.push_back(speeds); } } } void CCmpPathfinder::Deinit() { SetDebugOverlay(false); // cleans up memory ResetDebugPath(); delete m_Grid; delete m_ObstructionGrid; } struct SerializeLongRequest { template void operator()(S& serialize, const char* UNUSED(name), AsyncLongPathRequest& value) { serialize.NumberU32_Unbounded("ticket", value.ticket); serialize.NumberFixed_Unbounded("x0", value.x0); serialize.NumberFixed_Unbounded("z0", value.z0); SerializeGoal()(serialize, "goal", value.goal); serialize.NumberU16_Unbounded("pass class", value.passClass); serialize.NumberU8_Unbounded("cost class", value.costClass); serialize.NumberU32_Unbounded("notify", value.notify); } }; struct SerializeShortRequest { template void operator()(S& serialize, const char* UNUSED(name), AsyncShortPathRequest& value) { serialize.NumberU32_Unbounded("ticket", value.ticket); serialize.NumberFixed_Unbounded("x0", value.x0); serialize.NumberFixed_Unbounded("z0", value.z0); serialize.NumberFixed_Unbounded("r", value.r); serialize.NumberFixed_Unbounded("range", value.range); SerializeGoal()(serialize, "goal", value.goal); serialize.NumberU16_Unbounded("pass class", value.passClass); serialize.Bool("avoid moving units", value.avoidMovingUnits); serialize.NumberU32_Unbounded("group", value.group); serialize.NumberU32_Unbounded("notify", value.notify); } }; void CCmpPathfinder::Serialize(ISerializer& serialize) { SerializeVector()(serialize, "long requests", m_AsyncLongPathRequests); SerializeVector()(serialize, "short requests", m_AsyncShortPathRequests); serialize.NumberU32_Unbounded("next ticket", m_NextAsyncTicket); } void CCmpPathfinder::Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) { Init(paramNode); SerializeVector()(deserialize, "long requests", m_AsyncLongPathRequests); SerializeVector()(deserialize, "short requests", m_AsyncShortPathRequests); deserialize.NumberU32_Unbounded("next ticket", m_NextAsyncTicket); } void CCmpPathfinder::HandleMessage(const CMessage& msg, bool UNUSED(global)) { switch (msg.GetType()) { case MT_RenderSubmit: { const CMessageRenderSubmit& msgData = static_cast (msg); RenderSubmit(msgData.collector); break; } case MT_TerrainChanged: { // TODO: we ought to only bother updating the dirtied region m_TerrainDirty = true; break; } + case MT_TurnStart: + { + m_SameTurnMovesCount = 0; + break; + } } } void CCmpPathfinder::RenderSubmit(SceneCollector& collector) { for (size_t i = 0; i < m_DebugOverlayShortPathLines.size(); ++i) collector.Submit(&m_DebugOverlayShortPathLines[i]); } fixed CCmpPathfinder::GetMovementSpeed(entity_pos_t x0, entity_pos_t z0, u8 costClass) { UpdateGrid(); u16 i, j; NearestTile(x0, z0, i, j); TerrainTile tileTag = m_Grid->get(i, j); return m_MoveSpeeds.at(costClass).at(GET_COST_CLASS(tileTag)); } ICmpPathfinder::pass_class_t CCmpPathfinder::GetPassabilityClass(const std::string& name) { if (m_PassClassMasks.find(name) == m_PassClassMasks.end()) { LOGERROR(L"Invalid passability class name '%hs'", name.c_str()); return 0; } return m_PassClassMasks[name]; } std::map CCmpPathfinder::GetPassabilityClasses() { return m_PassClassMasks; } ICmpPathfinder::cost_class_t CCmpPathfinder::GetCostClass(const std::string& name) { if (m_UnitCostClassTags.find(name) == m_UnitCostClassTags.end()) { LOGERROR(L"Invalid unit cost class name '%hs'", name.c_str()); return m_UnitCostClassTags["default"]; } return m_UnitCostClassTags[name]; } fixed CCmpPathfinder::DistanceToGoal(CFixedVector2D pos, const CCmpPathfinder::Goal& goal) { switch (goal.type) { case CCmpPathfinder::Goal::POINT: return (pos - CFixedVector2D(goal.x, goal.z)).Length(); case CCmpPathfinder::Goal::CIRCLE: return ((pos - CFixedVector2D(goal.x, goal.z)).Length() - goal.hw).Absolute(); case CCmpPathfinder::Goal::SQUARE: { CFixedVector2D halfSize(goal.hw, goal.hh); CFixedVector2D d(pos.X - goal.x, pos.Y - goal.z); return Geometry::DistanceToSquare(d, goal.u, goal.v, halfSize); } default: debug_warn(L"invalid type"); return fixed::Zero(); } } const Grid& CCmpPathfinder::GetPassabilityGrid() { UpdateGrid(); return *m_Grid; } void CCmpPathfinder::UpdateGrid() { // If the terrain was resized then delete the old grid data if (m_Grid && m_MapSize != GetSimContext().GetTerrain().GetTilesPerSide()) { SAFE_DELETE(m_Grid); SAFE_DELETE(m_ObstructionGrid); m_TerrainDirty = true; } // Initialise the terrain data when first needed if (!m_Grid) { // TOOD: these bits should come from ICmpTerrain ssize_t size = GetSimContext().GetTerrain().GetTilesPerSide(); ENSURE(size >= 1 && size <= 0xffff); // must fit in 16 bits m_MapSize = size; m_Grid = new Grid(m_MapSize, m_MapSize); m_ObstructionGrid = new Grid(m_MapSize, m_MapSize); } CmpPtr cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); bool obstructionsDirty = cmpObstructionManager->Rasterise(*m_ObstructionGrid); if (obstructionsDirty && !m_TerrainDirty) { PROFILE("UpdateGrid obstructions"); // Obstructions changed - we need to recompute passability // Since terrain hasn't changed we only need to update the obstruction bits // and can skip the rest of the data // TODO: if ObstructionManager::SetPassabilityCircular was called at runtime // (which should probably never happen, but that's not guaranteed), // then TILE_OUTOFBOUNDS will change and we can't use this fast path, but // currently it'll just set obstructionsDirty and we won't notice for (u16 j = 0; j < m_MapSize; ++j) { for (u16 i = 0; i < m_MapSize; ++i) { TerrainTile& t = m_Grid->get(i, j); u8 obstruct = m_ObstructionGrid->get(i, j); if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_PATHFINDING) t |= 1; else t &= ~1; if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_FOUNDATION) t |= 2; else t &= ~2; } } ++m_Grid->m_DirtyID; } else if (obstructionsDirty || m_TerrainDirty) { PROFILE("UpdateGrid full"); // Obstructions or terrain changed - we need to recompute passability // TODO: only bother recomputing the region that has actually changed CmpPtr cmpWaterMan(GetSimContext(), SYSTEM_ENTITY); CTerrain& terrain = GetSimContext().GetTerrain(); for (u16 j = 0; j < m_MapSize; ++j) { for (u16 i = 0; i < m_MapSize; ++i) { fixed x, z; TileCenter(i, j, x, z); TerrainTile t = 0; u8 obstruct = m_ObstructionGrid->get(i, j); fixed height = terrain.GetVertexGroundLevelFixed(i, j); // TODO: should use tile centre fixed water; if (!cmpWaterMan.null()) water = cmpWaterMan->GetWaterLevel(x, z); fixed depth = water - height; fixed slope = terrain.GetSlopeFixed(i, j); if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_PATHFINDING) t |= 1; if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_FOUNDATION) t |= 2; if (obstruct & ICmpObstructionManager::TILE_OUTOFBOUNDS) { // If out of bounds, nobody is allowed to pass for (size_t n = 0; n < m_PassClasses.size(); ++n) t |= m_PassClasses[n].m_Mask; } else { for (size_t n = 0; n < m_PassClasses.size(); ++n) { if (!m_PassClasses[n].IsPassable(depth, slope)) t |= m_PassClasses[n].m_Mask; } } std::string moveClass = terrain.GetMovementClass(i, j); if (m_TerrainCostClassTags.find(moveClass) != m_TerrainCostClassTags.end()) t |= COST_CLASS_MASK(m_TerrainCostClassTags[moveClass]); m_Grid->set(i, j, t); } } m_TerrainDirty = false; ++m_Grid->m_DirtyID; } } ////////////////////////////////////////////////////////// // Async path requests: u32 CCmpPathfinder::ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass, entity_id_t notify) { AsyncLongPathRequest req = { m_NextAsyncTicket++, x0, z0, goal, passClass, costClass, notify }; m_AsyncLongPathRequests.push_back(req); return req.ticket; } u32 CCmpPathfinder::ComputeShortPathAsync(entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal& goal, pass_class_t passClass, bool avoidMovingUnits, entity_id_t group, entity_id_t notify) { AsyncShortPathRequest req = { m_NextAsyncTicket++, x0, z0, r, range, goal, passClass, avoidMovingUnits, group, notify }; m_AsyncShortPathRequests.push_back(req); return req.ticket; } void CCmpPathfinder::FinishAsyncRequests() { // Save the request queue in case it gets modified while iterating std::vector longRequests; m_AsyncLongPathRequests.swap(longRequests); std::vector shortRequests; m_AsyncShortPathRequests.swap(shortRequests); // TODO: we should only compute one path per entity per turn // TODO: this computation should be done incrementally, spread // across multiple frames (or even multiple turns) + ProcessLongRequests(longRequests); + ProcessShortRequests(shortRequests); +} + +void CCmpPathfinder::ProcessLongRequests(const std::vector& longRequests) +{ for (size_t i = 0; i < longRequests.size(); ++i) { const AsyncLongPathRequest& req = longRequests[i]; Path path; ComputePath(req.x0, req.z0, req.goal, req.passClass, req.costClass, path); CMessagePathResult msg(req.ticket, path); GetSimContext().GetComponentManager().PostMessage(req.notify, msg); } +} +void CCmpPathfinder::ProcessShortRequests(const std::vector& shortRequests) +{ for (size_t i = 0; i < shortRequests.size(); ++i) { const AsyncShortPathRequest& req = shortRequests[i]; Path path; ControlGroupMovementObstructionFilter filter(req.avoidMovingUnits, req.group); ComputeShortPath(filter, req.x0, req.z0, req.r, req.range, req.goal, req.passClass, path); CMessagePathResult msg(req.ticket, path); GetSimContext().GetComponentManager().PostMessage(req.notify, msg); } } + +void CCmpPathfinder::ProcessSameTurnMoves() +{ + u32 moveCount; + + if (m_AsyncLongPathRequests.size() > 0) + { + // Figure out how many moves we can do this time + moveCount = m_MaxSameTurnMoves - m_SameTurnMovesCount; + + if (moveCount <= 0) + return; + + // Copy the long request elements we are going to process into a new array + std::vector longRequests; + if (m_AsyncLongPathRequests.size() <= moveCount) + { + m_AsyncLongPathRequests.swap(longRequests); + moveCount = longRequests.size(); + } + else + { + longRequests.resize(moveCount); + copy(m_AsyncLongPathRequests.begin(), m_AsyncLongPathRequests.begin() + moveCount, longRequests.begin()); + m_AsyncLongPathRequests.erase(m_AsyncLongPathRequests.begin(), m_AsyncLongPathRequests.begin() + moveCount); + } + + ProcessLongRequests(longRequests); + + m_SameTurnMovesCount += moveCount; + } + + if (m_AsyncShortPathRequests.size() > 0) + { + // Figure out how many moves we can do now + moveCount = m_MaxSameTurnMoves - m_SameTurnMovesCount; + + if (moveCount <= 0) + return; + + // Copy the short request elements we are going to process into a new array + std::vector shortRequests; + if (m_AsyncShortPathRequests.size() <= moveCount) + { + m_AsyncShortPathRequests.swap(shortRequests); + moveCount = shortRequests.size(); + } + else + { + shortRequests.resize(moveCount); + copy(m_AsyncShortPathRequests.begin(), m_AsyncShortPathRequests.begin() + moveCount, shortRequests.begin()); + m_AsyncShortPathRequests.erase(m_AsyncShortPathRequests.begin(), m_AsyncShortPathRequests.begin() + moveCount); + } + + ProcessShortRequests(shortRequests); + + m_SameTurnMovesCount += moveCount; + } +} + Index: ps/trunk/source/simulation2/components/ICmpPathfinder.h =================================================================== --- ps/trunk/source/simulation2/components/ICmpPathfinder.h (revision 9664) +++ ps/trunk/source/simulation2/components/ICmpPathfinder.h (revision 9665) @@ -1,165 +1,170 @@ /* 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 * 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 . */ #ifndef INCLUDED_ICMPPATHFINDER #define INCLUDED_ICMPPATHFINDER #include "simulation2/system/Interface.h" #include "simulation2/helpers/Position.h" #include "maths/FixedVector2D.h" #include #include class IObstructionTestFilter; template class Grid; /** * Pathfinder algorithms. * * There are two different modes: a tile-based pathfinder that works over long distances and * accounts for terrain costs but ignore units, and a 'short' vertex-based pathfinder that * provides precise paths and avoids other units. * * Both use the same concept of a Goal: either a point, circle or square. * (If the starting point is inside the goal shape then the path will move outwards * to reach the shape's outline.) * * The output is a list of waypoints. */ class ICmpPathfinder : public IComponent { public: typedef u16 pass_class_t; typedef u8 cost_class_t; struct Goal { enum Type { POINT, CIRCLE, SQUARE } type; entity_pos_t x, z; // position of center CFixedVector2D u, v; // if SQUARE, then orthogonal unit axes entity_pos_t hw, hh; // if SQUARE, then half width & height; if CIRCLE, then hw is radius }; struct Waypoint { entity_pos_t x, z; }; /** * Returned path. * Waypoints are in *reverse* order (the earliest is at the back of the list) */ struct Path { std::vector m_Waypoints; }; /** * Get the list of all known passability classes. */ virtual std::map GetPassabilityClasses() = 0; /** * Get the tag for a given passability class name. * Logs an error and returns something acceptable if the name is unrecognised. */ virtual pass_class_t GetPassabilityClass(const std::string& name) = 0; /** * Get the tag for a given movement cost class name. * Logs an error and returns something acceptable if the name is unrecognised. */ virtual cost_class_t GetCostClass(const std::string& name) = 0; virtual const Grid& GetPassabilityGrid() = 0; /** * Compute a tile-based path from the given point to the goal, and return the set of waypoints. * The waypoints correspond to the centers of horizontally/vertically adjacent tiles * along the path. */ virtual void ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass, Path& ret) = 0; /** * Asynchronous version of ComputePath. * The result will be sent as CMessagePathResult to 'notify'. * Returns a unique non-zero number, which will match the 'ticket' in the result, * so callers can recognise each individual request they make. */ virtual u32 ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass, entity_id_t notify) = 0; /** * If the debug overlay is enabled, render the path that will computed by ComputePath. */ virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass) = 0; /** * Compute a precise path from the given point to the goal, and return the set of waypoints. * The path is based on the full set of obstructions that pass the filter, such that * a unit of radius 'r' will be able to follow the path with no collisions. * The path is restricted to a box of radius 'range' from the starting point. */ virtual void ComputeShortPath(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal& goal, pass_class_t passClass, Path& ret) = 0; /** * Asynchronous version of ComputeShortPath (using ControlGroupObstructionFilter). * The result will be sent as CMessagePathResult to 'notify'. * Returns a unique non-zero number, which will match the 'ticket' in the result, * so callers can recognise each individual request they make. */ virtual u32 ComputeShortPathAsync(entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal& goal, pass_class_t passClass, bool avoidMovingUnits, entity_id_t group, entity_id_t notify) = 0; /** * Find the speed factor (typically around 1.0) for a unit of the given cost class * at the given position. */ virtual fixed GetMovementSpeed(entity_pos_t x0, entity_pos_t z0, cost_class_t costClass) = 0; /** * Returns the coordinates of the point on the goal that is closest to pos in a straight line. */ virtual CFixedVector2D GetNearestPointOnGoal(CFixedVector2D pos, const Goal& goal) = 0; /** * Check whether the given movement line is valid and doesn't hit any obstructions * or impassable terrain. * Returns true if the movement is okay. */ virtual bool CheckMovement(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r, pass_class_t passClass) = 0; /** * Toggle the storage and rendering of debug info. */ virtual void SetDebugOverlay(bool enabled) = 0; /** * Finish computing asynchronous path requests and send the CMessagePathResult messages. */ virtual void FinishAsyncRequests() = 0; + /** + * Process moves during the same turn they were created in to improve responsiveness. + */ + virtual void ProcessSameTurnMoves() = 0; + DECLARE_INTERFACE_TYPE(Pathfinder) }; #endif // INCLUDED_ICMPPATHFINDER Index: ps/trunk/source/simulation2/Simulation2.cpp =================================================================== --- ps/trunk/source/simulation2/Simulation2.cpp (revision 9664) +++ ps/trunk/source/simulation2/Simulation2.cpp (revision 9665) @@ -1,640 +1,654 @@ /* 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 * 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 . */ #include "precompiled.h" #include "Simulation2.h" #include "simulation2/MessageTypes.h" #include "simulation2/system/ComponentManager.h" #include "simulation2/system/ParamNode.h" #include "simulation2/system/SimContext.h" #include "simulation2/components/ICmpAIManager.h" #include "simulation2/components/ICmpCommandQueue.h" #include "simulation2/components/ICmpTemplateManager.h" #include "lib/timer.h" #include "lib/file/vfs/vfs_util.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/Profile.h" #include "ps/Pyrogenesis.h" #include "ps/XML/Xeromyces.h" #include #if MSC_VERSION #include #define getpid _getpid // use the non-deprecated function name #endif class CSimulation2Impl { public: CSimulation2Impl(CUnitManager* unitManager, CTerrain* terrain) : m_SimContext(), m_ComponentManager(m_SimContext), m_EnableOOSLog(false) { m_SimContext.m_UnitManager = unitManager; m_SimContext.m_Terrain = terrain; m_ComponentManager.LoadComponentTypes(); RegisterFileReloadFunc(ReloadChangedFileCB, this); // m_EnableOOSLog = true; // TODO: this should be a command-line flag or similar } ~CSimulation2Impl() { UnregisterFileReloadFunc(ReloadChangedFileCB, this); } void ResetState(bool skipScriptedComponents, bool skipAI) { m_ComponentManager.ResetState(); m_DeltaTime = 0.0; m_LastFrameOffset = 0.0f; m_TurnNumber = 0; CParamNode noParam; CComponentManager::ComponentTypeId cid; // Add native system components: m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_TemplateManager, noParam); m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_CommandQueue, noParam); m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_ObstructionManager, noParam); m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_Pathfinder, noParam); m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_ProjectileManager, noParam); m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_RangeManager, noParam); m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_SoundManager, noParam); m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_Terrain, noParam); m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_WaterManager, noParam); if (!skipAI) { m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_AIManager, noParam); } // Add scripted system components: if (!skipScriptedComponents) { #define LOAD_SCRIPTED_COMPONENT(name) \ cid = m_ComponentManager.LookupCID(name); \ if (cid == CID__Invalid) \ LOGERROR(L"Can't find component type " L##name); \ m_ComponentManager.AddComponent(SYSTEM_ENTITY, cid, noParam) LOAD_SCRIPTED_COMPONENT("AIInterface"); LOAD_SCRIPTED_COMPONENT("EndGameManager"); LOAD_SCRIPTED_COMPONENT("GuiInterface"); LOAD_SCRIPTED_COMPONENT("PlayerManager"); LOAD_SCRIPTED_COMPONENT("Timer"); #undef LOAD_SCRIPTED_COMPONENT } } bool LoadScripts(const VfsPath& path); Status ReloadChangedFile(const VfsPath& path); static Status ReloadChangedFileCB(void* param, const VfsPath& path) { return static_cast(param)->ReloadChangedFile(path); } int ProgressiveLoad(); bool Update(int turnLength, const std::vector& commands); void Interpolate(float frameLength, float frameOffset); void DumpState(); CSimContext m_SimContext; CComponentManager m_ComponentManager; double m_DeltaTime; float m_LastFrameOffset; std::wstring m_StartupScript; CScriptValRooted m_MapSettings; std::set m_LoadedScripts; uint32_t m_TurnNumber; bool m_EnableOOSLog; }; bool CSimulation2Impl::LoadScripts(const VfsPath& path) { VfsPaths pathnames; if (vfs::GetPathnames(g_VFS, path, L"*.js", pathnames) < 0) return false; bool ok = true; for (VfsPaths::iterator it = pathnames.begin(); it != pathnames.end(); ++it) { VfsPath filename = *it; m_LoadedScripts.insert(filename); LOGMESSAGE(L"Loading simulation script '%ls'", filename.string().c_str()); if (! m_ComponentManager.LoadScript(filename)) ok = false; } return ok; } Status CSimulation2Impl::ReloadChangedFile(const VfsPath& path) { const VfsPath& filename = path; // Ignore if this file wasn't loaded as a script // (TODO: Maybe we ought to load in any new .js files that are created in the right directories) if (m_LoadedScripts.find(filename) == m_LoadedScripts.end()) return INFO::OK; // If the file doesn't exist (e.g. it was deleted), don't bother loading it since that'll give an error message. // (Also don't bother trying to 'unload' it from the component manager, because that's not possible) if (!VfsFileExists(path)) return INFO::OK; LOGMESSAGE(L"Reloading simulation script '%ls'", filename.string().c_str()); if (!m_ComponentManager.LoadScript(filename, true)) return ERR::FAIL; return INFO::OK; } int CSimulation2Impl::ProgressiveLoad() { // yield after this time is reached. balances increased progress bar // smoothness vs. slowing down loading. const double end_time = timer_Time() + 200e-3; int ret; do { bool progressed = false; int total = 0; int progress = 0; CMessageProgressiveLoad msg(&progressed, &total, &progress); m_ComponentManager.BroadcastMessage(msg); if (!progressed || total == 0) return 0; // we have nothing left to load ret = Clamp(100*progress / total, 1, 100); } while (timer_Time() < end_time); return ret; } bool CSimulation2Impl::Update(int turnLength, const std::vector& commands) { fixed turnLengthFixed = fixed::FromInt(turnLength) / 1000; // TODO: the update process is pretty ugly, with lots of messages and dependencies // between different components. Ought to work out a nicer way to do this. CMessageTurnStart msgTurnStart; m_ComponentManager.BroadcastMessage(msgTurnStart); CmpPtr cmpPathfinder(m_SimContext, SYSTEM_ENTITY); if (!cmpPathfinder.null()) cmpPathfinder->FinishAsyncRequests(); // Push AI commands onto the queue before we use them CmpPtr cmpAIManager(m_SimContext, SYSTEM_ENTITY); if (!cmpAIManager.null()) cmpAIManager->PushCommands(); CmpPtr cmpCommandQueue(m_SimContext, SYSTEM_ENTITY); if (!cmpCommandQueue.null()) cmpCommandQueue->FlushTurn(commands); + // Process newly generated move commands so the UI feels snappy + if (!cmpPathfinder.null()) + cmpPathfinder->ProcessSameTurnMoves(); + // Send all the update phases { CMessageUpdate msgUpdate(turnLengthFixed); m_ComponentManager.BroadcastMessage(msgUpdate); } { CMessageUpdate_MotionFormation msgUpdate(turnLengthFixed); m_ComponentManager.BroadcastMessage(msgUpdate); } + + // Process move commands for formations (group proxy) + if (!cmpPathfinder.null()) + cmpPathfinder->ProcessSameTurnMoves(); + { CMessageUpdate_MotionUnit msgUpdate(turnLengthFixed); m_ComponentManager.BroadcastMessage(msgUpdate); } { CMessageUpdate_Final msgUpdate(turnLengthFixed); m_ComponentManager.BroadcastMessage(msgUpdate); } + // Process moves resulting from group proxy movement (unit needs to catch up or realign) and any others + if (!cmpPathfinder.null()) + cmpPathfinder->ProcessSameTurnMoves(); + + // Clean up any entities destroyed during the simulation update m_ComponentManager.FlushDestroyedComponents(); // if (m_TurnNumber == 0) // m_ComponentManager.GetScriptInterface().DumpHeap(); // Run the GC occasionally // (TODO: we ought to schedule this for a frame where we're not // running the sim update, to spread the load) if (m_TurnNumber % 10 == 0) m_ComponentManager.GetScriptInterface().MaybeGC(); if (m_EnableOOSLog) DumpState(); // Start computing AI for the next turn if (!cmpAIManager.null()) cmpAIManager->StartComputation(); ++m_TurnNumber; return true; // TODO: don't bother with bool return } void CSimulation2Impl::Interpolate(float frameLength, float frameOffset) { m_LastFrameOffset = frameOffset; CMessageInterpolate msg(frameLength, frameOffset); m_ComponentManager.BroadcastMessage(msg); // Clean up any entities destroyed during interpolate (e.g. local corpses) m_ComponentManager.FlushDestroyedComponents(); } void CSimulation2Impl::DumpState() { PROFILE("DumpState"); std::wstringstream name; name << L"sim_log/" << getpid() << L"/" << std::setw(5) << std::setfill(L'0') << m_TurnNumber << L".txt"; OsPath path = psLogDir() / name.str(); CreateDirectories(path.Parent(), 0700); std::ofstream file (OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc); file << "State hash: " << std::hex; std::string hashRaw; m_ComponentManager.ComputeStateHash(hashRaw, false); for (size_t i = 0; i < hashRaw.size(); ++i) file << std::setfill('0') << std::setw(2) << (int)(unsigned char)hashRaw[i]; file << std::dec << "\n"; file << "\n"; m_ComponentManager.DumpDebugState(file); std::ofstream binfile (OsString(path.ChangeExtension(L".dat")).c_str(), std::ofstream::out | std::ofstream::trunc | std::ofstream::binary); m_ComponentManager.SerializeState(binfile); } //////////////////////////////////////////////////////////////// CSimulation2::CSimulation2(CUnitManager* unitManager, CTerrain* terrain) : m(new CSimulation2Impl(unitManager, terrain)) { } CSimulation2::~CSimulation2() { delete m; } // Forward all method calls to the appropriate CSimulation2Impl/CComponentManager methods: void CSimulation2::EnableOOSLog() { m->m_EnableOOSLog = true; } entity_id_t CSimulation2::AddEntity(const std::wstring& templateName) { return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewEntity()); } entity_id_t CSimulation2::AddEntity(const std::wstring& templateName, entity_id_t preferredId) { return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewEntity(preferredId)); } entity_id_t CSimulation2::AddLocalEntity(const std::wstring& templateName) { return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewLocalEntity()); } void CSimulation2::DestroyEntity(entity_id_t ent) { m->m_ComponentManager.DestroyComponentsSoon(ent); } void CSimulation2::FlushDestroyedEntities() { m->m_ComponentManager.FlushDestroyedComponents(); } IComponent* CSimulation2::QueryInterface(entity_id_t ent, int iid) const { return m->m_ComponentManager.QueryInterface(ent, iid); } void CSimulation2::PostMessage(entity_id_t ent, const CMessage& msg) const { m->m_ComponentManager.PostMessage(ent, msg); } void CSimulation2::BroadcastMessage(const CMessage& msg) const { m->m_ComponentManager.BroadcastMessage(msg); } CSimulation2::InterfaceList CSimulation2::GetEntitiesWithInterface(int iid) { return m->m_ComponentManager.GetEntitiesWithInterface(iid); } const CSimulation2::InterfaceListUnordered& CSimulation2::GetEntitiesWithInterfaceUnordered(int iid) { return m->m_ComponentManager.GetEntitiesWithInterfaceUnordered(iid); } const CSimContext& CSimulation2::GetSimContext() const { return m->m_SimContext; } ScriptInterface& CSimulation2::GetScriptInterface() const { return m->m_ComponentManager.GetScriptInterface(); } void CSimulation2::InitGame(const CScriptVal& data) { CScriptVal ret; // ignored GetScriptInterface().CallFunction(GetScriptInterface().GetGlobalObject(), "InitGame", data, ret); } bool CSimulation2::Update(int turnLength) { std::vector commands; return m->Update(turnLength, commands); } bool CSimulation2::Update(int turnLength, const std::vector& commands) { return m->Update(turnLength, commands); } void CSimulation2::Interpolate(float frameLength, float frameOffset) { m->Interpolate(frameLength, frameOffset); } void CSimulation2::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling) { CMessageRenderSubmit msg(collector, frustum, culling); m->m_ComponentManager.BroadcastMessage(msg); } float CSimulation2::GetLastFrameOffset() const { return m->m_LastFrameOffset; } bool CSimulation2::LoadScripts(const VfsPath& path) { return m->LoadScripts(path); } bool CSimulation2::LoadDefaultScripts() { return ( m->LoadScripts(L"simulation/components/interfaces/") && m->LoadScripts(L"simulation/helpers/") && m->LoadScripts(L"simulation/components/") ); } void CSimulation2::SetStartupScript(const std::wstring& code) { m->m_StartupScript = code; } const std::wstring& CSimulation2::GetStartupScript() { return m->m_StartupScript; } void CSimulation2::SetMapSettings(const std::string& settings) { m->m_MapSettings = m->m_ComponentManager.GetScriptInterface().ParseJSON(settings); } void CSimulation2::SetMapSettings(const CScriptValRooted& settings) { m->m_MapSettings = settings; } std::string CSimulation2::GetMapSettingsString() { return m->m_ComponentManager.GetScriptInterface().StringifyJSON(m->m_MapSettings.get()); } CScriptVal CSimulation2::GetMapSettings() { return m->m_MapSettings.get(); } void CSimulation2::LoadPlayerSettings() { GetScriptInterface().CallFunctionVoid(GetScriptInterface().GetGlobalObject(), "LoadPlayerSettings", m->m_MapSettings); } void CSimulation2::LoadMapSettings() { // Initialize here instead of in Update() GetScriptInterface().CallFunctionVoid(GetScriptInterface().GetGlobalObject(), "LoadMapSettings", m->m_MapSettings); if (!m->m_StartupScript.empty()) GetScriptInterface().LoadScript(L"map startup script", m->m_StartupScript); } int CSimulation2::ProgressiveLoad() { return m->ProgressiveLoad(); } Status CSimulation2::ReloadChangedFile(const VfsPath& path) { return m->ReloadChangedFile(path); } void CSimulation2::ResetState(bool skipScriptedComponents, bool skipAI) { m->ResetState(skipScriptedComponents, skipAI); } bool CSimulation2::ComputeStateHash(std::string& outHash, bool quick) { return m->m_ComponentManager.ComputeStateHash(outHash, quick); } bool CSimulation2::DumpDebugState(std::ostream& stream) { return m->m_ComponentManager.DumpDebugState(stream); } bool CSimulation2::SerializeState(std::ostream& stream) { return m->m_ComponentManager.SerializeState(stream); } bool CSimulation2::DeserializeState(std::istream& stream) { // TODO: need to make sure the required SYSTEM_ENTITY components get constructed return m->m_ComponentManager.DeserializeState(stream); } std::string CSimulation2::GenerateSchema() { return m->m_ComponentManager.GenerateSchema(); } std::vector CSimulation2::GetRMSData() { VfsPath path(L"maps/random/"); VfsPaths pathnames; std::vector data; // Find all ../maps/random/*.json Status ret = vfs::GetPathnames(g_VFS, path, L"*.json", pathnames); if (ret == INFO::OK) { for (VfsPaths::iterator it = pathnames.begin(); it != pathnames.end(); ++it) { // Load JSON file CVFSFile file; PSRETURN ret = file.Load(g_VFS, *it); if (ret != PSRETURN_OK) { LOGERROR(L"Failed to load file '%ls': %hs", path.string().c_str(), GetErrorString(ret)); } else { data.push_back(std::string(file.GetBuffer(), file.GetBuffer() + file.GetBufferSize())); } } } else { // Some error reading directory wchar_t error[200]; LOGERROR(L"Error reading directory '%ls': %ls", path.string().c_str(), StatusDescription(ret, error, ARRAY_SIZE(error))); } return data; } std::vector CSimulation2::GetCivData() { VfsPath path(L"civs/"); VfsPaths pathnames; std::vector data; // Load all JSON files in civs directory Status ret = vfs::GetPathnames(g_VFS, path, L"*.json", pathnames); if (ret == INFO::OK) { for (VfsPaths::iterator it = pathnames.begin(); it != pathnames.end(); ++it) { // Load JSON file CVFSFile file; PSRETURN ret = file.Load(g_VFS, *it); if (ret != PSRETURN_OK) { LOGERROR(L"CSimulation2::GetCivData: Failed to load file '%ls': %hs", path.string().c_str(), GetErrorString(ret)); } else { data.push_back(std::string(file.GetBuffer(), file.GetBuffer() + file.GetBufferSize())); } } } else { // Some error reading directory wchar_t error[200]; LOGERROR(L"CSimulation2::GetCivData: Error reading directory '%ls': %ls", path.string().c_str(), StatusDescription(ret, error, ARRAY_SIZE(error))); } return data; } std::string CSimulation2::GetPlayerDefaults() { return ReadJSON(L"simulation/data/player_defaults.json"); } std::string CSimulation2::GetMapSizes() { return ReadJSON(L"simulation/data/map_sizes.json"); } std::string CSimulation2::ReadJSON(VfsPath path) { std::string data; if (!VfsFileExists(path)) { LOGERROR(L"File '%ls' does not exist", path.string().c_str()); } else { // Load JSON file CVFSFile file; PSRETURN ret = file.Load(g_VFS, path); if (ret != PSRETURN_OK) { LOGERROR(L"Failed to load file '%ls': %hs", path.string().c_str(), GetErrorString(ret)); } else { data = std::string(file.GetBuffer(), file.GetBuffer() + file.GetBufferSize()); } } return data; } std::string CSimulation2::GetAIData() { ScriptInterface& scriptInterface = GetScriptInterface(); std::vector aiData = ICmpAIManager::GetAIs(scriptInterface); // Build single JSON string with array of AI data CScriptValRooted ais; if (!scriptInterface.Eval("({})", ais) || !scriptInterface.SetProperty(ais.get(), "AIData", aiData)) return std::string(); return scriptInterface.StringifyJSON(ais.get()); } Index: ps/trunk/binaries/data/mods/public/simulation/data/pathfinder.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/pathfinder.xml (revision 9664) +++ ps/trunk/binaries/data/mods/public/simulation/data/pathfinder.xml (revision 9665) @@ -1,37 +1,56 @@ + + 64 + 2 1.0 1 0 1.0