1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2
3 #include "System/Platform/Win/win32.h"
4
5 #include "PathEstimator.h"
6
7 #include <fstream>
8 #include <boost/bind.hpp>
9 #include <boost/thread/barrier.hpp>
10 #include <boost/thread/thread.hpp>
11
12 #include "minizip/zip.h"
13
14 #include "PathFinder.h"
15 #include "PathFinderDef.h"
16 #include "PathFlowMap.hpp"
17 #include "PathLog.h"
18 #include "Game/LoadScreen.h"
19 #include "Sim/Misc/ModInfo.h"
20 #include "Sim/MoveTypes/MoveDefHandler.h"
21 #include "Sim/MoveTypes/MoveMath/MoveMath.h"
22 #include "Net/Protocol/NetProtocol.h"
23 #include "System/ThreadPool.h"
24 #include "System/TimeProfiler.h"
25 #include "System/Config/ConfigHandler.h"
26 #include "System/FileSystem/Archives/IArchive.h"
27 #include "System/FileSystem/ArchiveLoader.h"
28 #include "System/FileSystem/DataDirsAccess.h"
29 #include "System/FileSystem/FileSystem.h"
30 #include "System/FileSystem/FileQueryFlags.h"
31
32
33 CONFIG(int, MaxPathCostsMemoryFootPrint).defaultValue(512).minimumValue(64).description("Maximum memusage (in MByte) of mutlithreaded pathcache generator at loading time.");
34
35
36
GetPathCacheDir()37 static const std::string GetPathCacheDir() {
38 return (FileSystem::GetCacheDir() + "/paths/");
39 }
40
GetNumThreads()41 static size_t GetNumThreads() {
42 const size_t numThreads = std::max(0, configHandler->GetInt("PathingThreadCount"));
43 const size_t numCores = Threading::GetLogicalCpuCores();
44 return ((numThreads == 0)? numCores: numThreads);
45 }
46
47
48
CPathEstimator(IPathFinder * pf,unsigned int BLOCK_SIZE,const std::string & cacheFileName,const std::string & mapFileName)49 CPathEstimator::CPathEstimator(IPathFinder* pf, unsigned int BLOCK_SIZE, const std::string& cacheFileName, const std::string& mapFileName)
50 : IPathFinder(BLOCK_SIZE)
51 , BLOCKS_TO_UPDATE(SQUARES_TO_UPDATE / (BLOCK_SIZE * BLOCK_SIZE) + 1)
52 , nextOffsetMessageIdx(0)
53 , nextCostMessageIdx(0)
54 , pathChecksum(0)
55 , offsetBlockNum(nbrOfBlocks.x * nbrOfBlocks.y)
56 , costBlockNum(nbrOfBlocks.x * nbrOfBlocks.y)
57 , pathFinder(pf)
58 , nextPathEstimator(nullptr)
59 , blockUpdatePenalty(0)
60 {
61 vertexCosts.resize(moveDefHandler->GetNumMoveDefs() * blockStates.GetSize() * PATH_DIRECTION_VERTICES, PATHCOST_INFINITY);
62
63 if (dynamic_cast<CPathEstimator*>(pf) != nullptr) {
64 dynamic_cast<CPathEstimator*>(pf)->nextPathEstimator = this;
65 }
66
67 // load precalculated data if it exists
68 InitEstimator(cacheFileName, mapFileName);
69 }
70
71
~CPathEstimator()72 CPathEstimator::~CPathEstimator()
73 {
74 delete pathCache[0]; pathCache[0] = NULL;
75 delete pathCache[1]; pathCache[1] = NULL;
76 }
77
78
GetDirectionVectorsTable()79 const int2* CPathEstimator::GetDirectionVectorsTable() {
80 return (&PE_DIRECTION_VECTORS[0]);
81 }
82
83
InitEstimator(const std::string & cacheFileName,const std::string & map)84 void CPathEstimator::InitEstimator(const std::string& cacheFileName, const std::string& map)
85 {
86 const unsigned int numThreads = GetNumThreads();
87
88 if (threads.size() != numThreads) {
89 threads.resize(numThreads);
90 pathFinders.resize(numThreads);
91 }
92
93 // always use PF for initialization, later PE maybe used
94 pathFinders[0] = new CPathFinder();
95
96 // Not much point in multithreading these...
97 InitBlocks();
98
99 if (!ReadFile(cacheFileName, map)) {
100 // start extra threads if applicable, but always keep the total
101 // memory-footprint made by CPathFinder instances within bounds
102 const unsigned int minMemFootPrint = sizeof(CPathFinder) + pathFinder->GetMemFootPrint();
103 const unsigned int maxMemFootPrint = configHandler->GetInt("MaxPathCostsMemoryFootPrint") * 1024 * 1024;
104 const unsigned int numExtraThreads = Clamp(int(maxMemFootPrint / minMemFootPrint) - 1, 0, int(numThreads) - 1);
105 const unsigned int reqMemFootPrint = minMemFootPrint * (numExtraThreads + 1);
106
107 {
108 char calcMsg[512];
109 const char* fmtString = (numExtraThreads > 0)?
110 "PathCosts: creating PE%u cache with %u PF threads (%u MB)":
111 "PathCosts: creating PE%u cache with %u PF thread (%u MB)";
112
113 sprintf(calcMsg, fmtString, BLOCK_SIZE, numExtraThreads + 1, reqMemFootPrint / (1024 * 1024));
114 loadscreen->SetLoadMessage(calcMsg);
115 }
116
117 // note: only really needed if numExtraThreads > 0
118 pathBarrier = new boost::barrier(numExtraThreads + 1);
119
120 for (unsigned int i = 1; i <= numExtraThreads; i++) {
121 pathFinders[i] = new CPathFinder();
122 threads[i] = new boost::thread(boost::bind(&CPathEstimator::CalcOffsetsAndPathCosts, this, i));
123 }
124
125 // Use the current thread as thread zero
126 CalcOffsetsAndPathCosts(0);
127
128 for (unsigned int i = 1; i <= numExtraThreads; i++) {
129 threads[i]->join();
130 delete threads[i];
131 delete pathFinders[i];
132 }
133
134 delete pathBarrier;
135
136 loadscreen->SetLoadMessage("PathCosts: writing", true);
137 WriteFile(cacheFileName, map);
138 loadscreen->SetLoadMessage("PathCosts: written", true);
139 }
140
141 // switch to runtime wanted IPathFinder (maybe PF or PE)
142 delete pathFinders[0];
143 pathFinders[0] = pathFinder;
144
145 pathCache[0] = new CPathCache(nbrOfBlocks.x, nbrOfBlocks.y);
146 pathCache[1] = new CPathCache(nbrOfBlocks.x, nbrOfBlocks.y);
147 }
148
149
InitBlocks()150 void CPathEstimator::InitBlocks()
151 {
152 blockStates.peNodeOffsets.resize(moveDefHandler->GetNumMoveDefs());
153 for (unsigned int idx = 0; idx < moveDefHandler->GetNumMoveDefs(); idx++) {
154 blockStates.peNodeOffsets[idx].resize(nbrOfBlocks.x * nbrOfBlocks.y);
155 }
156 }
157
158
159 __FORCE_ALIGN_STACK__
CalcOffsetsAndPathCosts(unsigned int threadNum)160 void CPathEstimator::CalcOffsetsAndPathCosts(unsigned int threadNum)
161 {
162 // reset FPU state for synced computations
163 streflop::streflop_init<streflop::Simple>();
164
165 if (threadNum > 0) {
166 Threading::SetAffinity(~0);
167 Threading::SetThreadName(IntToString(threadNum, "pathhelper%i"));
168 }
169
170 // NOTE: EstimatePathCosts() [B] is temporally dependent on CalculateBlockOffsets() [A],
171 // A must be completely finished before B_i can be safely called. This means we cannot
172 // let thread i execute (A_i, B_i), but instead have to split the work such that every
173 // thread finishes its part of A before any starts B_i.
174 const unsigned int maxBlockIdx = blockStates.GetSize() - 1;
175 int i;
176
177 while ((i = --offsetBlockNum) >= 0)
178 CalculateBlockOffsets(maxBlockIdx - i, threadNum);
179
180 pathBarrier->wait();
181
182 while ((i = --costBlockNum) >= 0)
183 EstimatePathCosts(maxBlockIdx - i, threadNum);
184 }
185
186
CalculateBlockOffsets(unsigned int blockIdx,unsigned int threadNum)187 void CPathEstimator::CalculateBlockOffsets(unsigned int blockIdx, unsigned int threadNum)
188 {
189 const int2 blockPos = BlockIdxToPos(blockIdx);
190
191 if (threadNum == 0 && blockIdx >= nextOffsetMessageIdx) {
192 nextOffsetMessageIdx = blockIdx + blockStates.GetSize() / 16;
193 net->Send(CBaseNetProtocol::Get().SendCPUUsage(BLOCK_SIZE | (blockIdx << 8)));
194 }
195
196 for (unsigned int i = 0; i < moveDefHandler->GetNumMoveDefs(); i++) {
197 const MoveDef* md = moveDefHandler->GetMoveDefByPathType(i);
198
199 if (md->udRefCount > 0) {
200 blockStates.peNodeOffsets[md->pathType][blockIdx] = FindOffset(*md, blockPos.x, blockPos.y);
201 }
202 }
203 }
204
205
EstimatePathCosts(unsigned int blockIdx,unsigned int threadNum)206 void CPathEstimator::EstimatePathCosts(unsigned int blockIdx, unsigned int threadNum)
207 {
208 const int2 blockPos = BlockIdxToPos(blockIdx);
209
210 if (threadNum == 0 && blockIdx >= nextCostMessageIdx) {
211 nextCostMessageIdx = blockIdx + blockStates.GetSize() / 16;
212
213 char calcMsg[128];
214 sprintf(calcMsg, "PathCosts: precached %d of %d blocks", blockIdx, blockStates.GetSize());
215
216 net->Send(CBaseNetProtocol::Get().SendCPUUsage(0x1 | BLOCK_SIZE | (blockIdx << 8)));
217 loadscreen->SetLoadMessage(calcMsg, (blockIdx != 0));
218 }
219
220 for (unsigned int i = 0; i < moveDefHandler->GetNumMoveDefs(); i++) {
221 const MoveDef* md = moveDefHandler->GetMoveDefByPathType(i);
222
223 if (md->udRefCount > 0) {
224 CalculateVertices(*md, blockPos, threadNum);
225 }
226 }
227 }
228
229
230 /**
231 * Finds a square accessable by the given MoveDef within the given block
232 */
FindOffset(const MoveDef & moveDef,unsigned int blockX,unsigned int blockZ) const233 int2 CPathEstimator::FindOffset(const MoveDef& moveDef, unsigned int blockX, unsigned int blockZ) const
234 {
235 // lower corner position of block
236 const unsigned int lowerX = blockX * BLOCK_SIZE;
237 const unsigned int lowerZ = blockZ * BLOCK_SIZE;
238 const unsigned int blockArea = (BLOCK_SIZE * BLOCK_SIZE) / SQUARE_SIZE;
239
240 unsigned int bestPosX = BLOCK_SIZE >> 1;
241 unsigned int bestPosZ = BLOCK_SIZE >> 1;
242
243 float bestCost = std::numeric_limits<float>::max();
244 float speedMod = CMoveMath::GetPosSpeedMod(moveDef, lowerX, lowerZ);
245 bool curblock = (speedMod == 0.0f) || CMoveMath::IsBlockedStructure(moveDef, lowerX, lowerZ, NULL);
246
247 // search for an accessible position within this block
248 for (unsigned int z = 0; z < BLOCK_SIZE; ++z) {
249 bool zcurblock = curblock;
250
251 for (unsigned int x = 0; x < BLOCK_SIZE; ++x) {
252 if (!curblock) {
253 const float dx = x - (float)(BLOCK_SIZE - 1) / 2.0f;
254 const float dz = z - (float)(BLOCK_SIZE - 1) / 2.0f;
255 const float cost = (dx * dx + dz * dz) + (blockArea / (0.001f + speedMod));
256
257 if (cost < bestCost) {
258 bestCost = cost;
259 bestPosX = x;
260 bestPosZ = z;
261 }
262 }
263
264 // if last position was not blocked, then we do not need to check the entire square
265 speedMod = CMoveMath::GetPosSpeedMod(moveDef, lowerX + x, lowerZ + z);
266 curblock = (speedMod == 0.0f) || (curblock ?
267 CMoveMath::IsBlockedStructure(moveDef, lowerX + x, lowerZ + z, NULL) :
268 CMoveMath::IsBlockedStructureXmax(moveDef, lowerX + x, lowerZ + z, NULL));
269 }
270
271 speedMod = CMoveMath::GetPosSpeedMod(moveDef, lowerX, lowerZ + z);
272 curblock = (speedMod == 0.0f) || (zcurblock ?
273 CMoveMath::IsBlockedStructure(moveDef, lowerX, lowerZ + z, NULL) :
274 CMoveMath::IsBlockedStructureZmax(moveDef, lowerX, lowerZ + z, NULL));
275 }
276
277 // return the offset found
278 return int2(blockX * BLOCK_SIZE + bestPosX, blockZ * BLOCK_SIZE + bestPosZ);
279 }
280
281
282 /**
283 * Calculate all vertices connected from the given block
284 */
CalculateVertices(const MoveDef & moveDef,int2 block,unsigned int thread)285 void CPathEstimator::CalculateVertices(const MoveDef& moveDef, int2 block, unsigned int thread)
286 {
287 // see code comment of GetBlockVertexOffset() for more info why those directions are choosen
288 CalculateVertex(moveDef, block, PATHDIR_LEFT, thread);
289 CalculateVertex(moveDef, block, PATHDIR_LEFT_UP, thread);
290 CalculateVertex(moveDef, block, PATHDIR_UP, thread);
291 CalculateVertex(moveDef, block, PATHDIR_RIGHT_UP, thread);
292 }
293
294
295 /**
296 * Calculate requested vertex
297 */
CalculateVertex(const MoveDef & moveDef,int2 parentBlock,unsigned int direction,unsigned int threadNum)298 void CPathEstimator::CalculateVertex(
299 const MoveDef& moveDef,
300 int2 parentBlock,
301 unsigned int direction,
302 unsigned int threadNum)
303 {
304 const int2 childBlock = parentBlock + PE_DIRECTION_VECTORS[direction];
305 const unsigned int parentBlockNbr = BlockPosToIdx(parentBlock);
306 const unsigned int childBlockNbr = BlockPosToIdx(childBlock);
307 const unsigned int vertexNbr =
308 moveDef.pathType * blockStates.GetSize() * PATH_DIRECTION_VERTICES +
309 parentBlockNbr * PATH_DIRECTION_VERTICES +
310 direction;
311
312 // outside map?
313 if ((unsigned)childBlock.x >= nbrOfBlocks.x || (unsigned)childBlock.y >= nbrOfBlocks.y) {
314 vertexCosts[vertexNbr] = PATHCOST_INFINITY;
315 return;
316 }
317
318
319 // start position within parent block
320 const int2 parentSquare = blockStates.peNodeOffsets[moveDef.pathType][parentBlockNbr];
321
322 // goal position within child block
323 const int2 childSquare = blockStates.peNodeOffsets[moveDef.pathType][childBlockNbr];
324 const float3 startPos = SquareToFloat3(parentSquare.x, parentSquare.y);
325 const float3 goalPos = SquareToFloat3(childSquare.x, childSquare.y);
326
327 // keep search exactly contained within the two blocks
328 CRectangularSearchConstraint pfDef(startPos, goalPos, BLOCK_SIZE);
329
330 // we never want to allow searches from
331 // any blocked starting positions (otherwise PE and PF can disagree)
332 // note: PE itself should ensure this never happens to begin with?
333 //
334 // be more lenient for normal searches so players can "unstuck" units
335 //
336 // blocked goal positions are always early-outs (no searching needed)
337 const bool strtBlocked = ((CMoveMath::IsBlocked(moveDef, startPos, nullptr) & CMoveMath::BLOCK_STRUCTURE) != 0);
338 const bool goalBlocked = pfDef.IsGoalBlocked(moveDef, CMoveMath::BLOCK_STRUCTURE, nullptr);
339 if (strtBlocked || goalBlocked) {
340 vertexCosts[vertexNbr] = PATHCOST_INFINITY;
341 return;
342 }
343
344 // find path from parent to child block
345 //
346 // since CPathFinder::GetPath() is not thread-safe, use
347 // this thread's "private" CPathFinder instance (rather
348 // than locking pathFinder->GetPath()) if we are in one
349 pfDef.testMobile = false;
350 pfDef.needPath = false;
351 pfDef.exactPath = true;
352 pfDef.dirIndependent = true;
353 IPath::Path path;
354 IPath::SearchResult result = pathFinders[threadNum]->GetPath(moveDef, pfDef, nullptr, startPos, path, MAX_SEARCHED_NODES_PF >> 2);
355
356 // store the result
357 if (result == IPath::Ok) {
358 vertexCosts[vertexNbr] = path.pathCost;
359 } else {
360 vertexCosts[vertexNbr] = PATHCOST_INFINITY;
361 }
362 }
363
364
365 /**
366 * Mark affected blocks as obsolete
367 */
MapChanged(unsigned int x1,unsigned int z1,unsigned int x2,unsigned z2)368 void CPathEstimator::MapChanged(unsigned int x1, unsigned int z1, unsigned int x2, unsigned z2)
369 {
370 // find the upper and lower corner of the rectangular area
371 const auto mmx = std::minmax(x1, x2);
372 const auto mmz = std::minmax(z1, z2);
373 const int lowerX = Clamp(int(mmx.first / BLOCK_SIZE) - 1, 0, int(nbrOfBlocks.x - 1));
374 const int upperX = Clamp(int(mmx.second / BLOCK_SIZE) + 1, 0, int(nbrOfBlocks.x - 1));
375 const int lowerZ = Clamp(int(mmz.first / BLOCK_SIZE) - 1, 0, int(nbrOfBlocks.y - 1));
376 const int upperZ = Clamp(int(mmz.second / BLOCK_SIZE) + 1, 0, int(nbrOfBlocks.y - 1));
377
378 // mark the blocks inside the rectangle, enqueue them
379 // from upper to lower because of the placement of the
380 // bi-directional vertices
381 for (int z = upperZ; z >= lowerZ; z--) {
382 for (int x = upperX; x >= lowerX; x--) {
383 const int idx = BlockPosToIdx(int2(x,z));
384 if ((blockStates.nodeMask[idx] & PATHOPT_OBSOLETE) != 0)
385 continue;
386
387 updatedBlocks.emplace_back(x, z);
388 blockStates.nodeMask[idx] |= PATHOPT_OBSOLETE;
389 }
390 }
391 }
392
393
394 /**
395 * Update some obsolete blocks using the FIFO-principle
396 */
Update()397 void CPathEstimator::Update()
398 {
399 pathCache[0]->Update();
400 pathCache[1]->Update();
401
402 const auto numMoveDefs = moveDefHandler->GetNumMoveDefs();
403
404 // determine how many blocks we should update
405 int blocksToUpdate = 0;
406 int consumeBlocks = 0;
407 {
408 const int progressiveUpdates = updatedBlocks.size() * numMoveDefs * modInfo.pfUpdateRate;
409 const int MIN_BLOCKS_TO_UPDATE = std::max<int>(BLOCKS_TO_UPDATE >> 1, 4U);
410 const int MAX_BLOCKS_TO_UPDATE = std::max<int>(BLOCKS_TO_UPDATE << 1, MIN_BLOCKS_TO_UPDATE);
411 blocksToUpdate = Clamp(progressiveUpdates, MIN_BLOCKS_TO_UPDATE, MAX_BLOCKS_TO_UPDATE);
412
413 blockUpdatePenalty = std::max(0, blockUpdatePenalty - blocksToUpdate);
414
415 if (blockUpdatePenalty > 0)
416 blocksToUpdate = std::max(0, blocksToUpdate - blockUpdatePenalty);
417
418 // we have to update blocks for all movedefs (cause PATHOPT_OBSOLETE is per block and not movedef)
419 consumeBlocks = int(progressiveUpdates != 0) * int(ceil(float(blocksToUpdate) / numMoveDefs)) * numMoveDefs;
420
421 blockUpdatePenalty += consumeBlocks;
422 }
423
424 if (blocksToUpdate == 0)
425 return;
426
427 if (updatedBlocks.empty())
428 return;
429
430 struct SingleBlock {
431 int2 blockPos;
432 const MoveDef* moveDef;
433 SingleBlock(const int2& pos, const MoveDef* md) : blockPos(pos), moveDef(md) {}
434 };
435 std::vector<SingleBlock> consumedBlocks;
436 consumedBlocks.reserve(consumeBlocks);
437
438 // get blocks to update
439 while (!updatedBlocks.empty()) {
440 int2& pos = updatedBlocks.front();
441 const int idx = BlockPosToIdx(pos);
442
443 if ((blockStates.nodeMask[idx] & PATHOPT_OBSOLETE) == 0) {
444 updatedBlocks.pop_front();
445 continue;
446 }
447
448 if (consumedBlocks.size() >= blocksToUpdate) {
449 break;
450 }
451
452 // issue repathing for all active movedefs
453 for (unsigned int i = 0; i < numMoveDefs; i++) {
454 const MoveDef* md = moveDefHandler->GetMoveDefByPathType(i);
455 if (md->udRefCount > 0)
456 consumedBlocks.emplace_back(pos, md);
457 }
458
459 // inform dependent pathEstimator that we change vertex cost of those blocks
460 if (nextPathEstimator)
461 nextPathEstimator->MapChanged(pos.x * BLOCK_SIZE, pos.y * BLOCK_SIZE, pos.x * BLOCK_SIZE, pos.y * BLOCK_SIZE);
462
463 updatedBlocks.pop_front(); // must happen _after_ last usage of the `pos` reference!
464 blockStates.nodeMask[idx] &= ~PATHOPT_OBSOLETE;
465 }
466
467 // FindOffset (threadsafe)
468 {
469 SCOPED_TIMER("CPathEstimator::FindOffset");
470 for_mt(0, consumedBlocks.size(), [&](const int n) {
471 // copy the next block in line
472 const SingleBlock sb = consumedBlocks[n];
473 const int blockN = BlockPosToIdx(sb.blockPos);
474 const MoveDef* currBlockMD = sb.moveDef;
475 blockStates.peNodeOffsets[currBlockMD->pathType][blockN] = FindOffset(*currBlockMD, sb.blockPos.x, sb.blockPos.y);
476 });
477 }
478
479 // CalculateVertices (not threadsafe)
480 {
481 SCOPED_TIMER("CPathEstimator::CalculateVertices");
482 for (unsigned int n = 0; n < consumedBlocks.size(); ++n) {
483 // copy the next block in line
484 const SingleBlock sb = consumedBlocks[n];
485 CalculateVertices(*sb.moveDef, sb.blockPos);
486 }
487 }
488 }
489
490
GetCache(const int2 strtBlock,const int2 goalBlock,float goalRadius,int pathType,const bool synced) const491 const CPathCache::CacheItem* CPathEstimator::GetCache(const int2 strtBlock, const int2 goalBlock, float goalRadius, int pathType, const bool synced) const
492 {
493 return pathCache[synced]->GetCachedPath(strtBlock, goalBlock, goalRadius, pathType);
494 }
495
496
AddCache(const IPath::Path * path,const IPath::SearchResult result,const int2 strtBlock,const int2 goalBlock,float goalRadius,int pathType,const bool synced)497 void CPathEstimator::AddCache(const IPath::Path* path, const IPath::SearchResult result, const int2 strtBlock, const int2 goalBlock, float goalRadius, int pathType, const bool synced)
498 {
499 pathCache[synced]->AddPath(path, result, strtBlock, goalBlock, goalRadius, pathType);
500 }
501
502
503 /**
504 * Performs the actual search.
505 */
DoSearch(const MoveDef & moveDef,const CPathFinderDef & peDef,const CSolidObject * owner)506 IPath::SearchResult CPathEstimator::DoSearch(const MoveDef& moveDef, const CPathFinderDef& peDef, const CSolidObject* owner)
507 {
508 bool foundGoal = false;
509
510 // get the goal square offset
511 const int2 goalSqrOffset = peDef.GoalSquareOffset(BLOCK_SIZE);
512
513 while (!openBlocks.empty() && (openBlockBuffer.GetSize() < maxBlocksToBeSearched)) {
514 // get the open block with lowest cost
515 PathNode* ob = const_cast<PathNode*>(openBlocks.top());
516 openBlocks.pop();
517
518 // check if the block has been marked as unaccessible during its time in the queue
519 if (blockStates.nodeMask[ob->nodeNum] & (PATHOPT_BLOCKED | PATHOPT_CLOSED))
520 continue;
521
522 // no, check if the goal is already reached
523 const int2 bSquare = blockStates.peNodeOffsets[moveDef.pathType][ob->nodeNum];
524 const int2 gSquare = ob->nodePos * BLOCK_SIZE + goalSqrOffset;
525 if (peDef.IsGoal(bSquare.x, bSquare.y) || peDef.IsGoal(gSquare.x, gSquare.y)) {
526 mGoalBlockIdx = ob->nodeNum;
527 mGoalHeuristic = 0.0f;
528 foundGoal = true;
529 break;
530 }
531
532 // no, test the 8 surrounding blocks
533 // NOTE: each of these calls increments openBlockBuffer.idx by 1, so
534 // maxBlocksToBeSearched is always less than <MAX_SEARCHED_NODES_PE - 8>
535 TestBlock(moveDef, peDef, ob, owner, PATHDIR_LEFT, PATHOPT_OPEN, 1.f, true);
536 TestBlock(moveDef, peDef, ob, owner, PATHDIR_LEFT_UP, PATHOPT_OPEN, 1.f, true);
537 TestBlock(moveDef, peDef, ob, owner, PATHDIR_UP, PATHOPT_OPEN, 1.f, true);
538 TestBlock(moveDef, peDef, ob, owner, PATHDIR_RIGHT_UP, PATHOPT_OPEN, 1.f, true);
539 TestBlock(moveDef, peDef, ob, owner, PATHDIR_RIGHT, PATHOPT_OPEN, 1.f, true);
540 TestBlock(moveDef, peDef, ob, owner, PATHDIR_RIGHT_DOWN, PATHOPT_OPEN, 1.f, true);
541 TestBlock(moveDef, peDef, ob, owner, PATHDIR_DOWN, PATHOPT_OPEN, 1.f, true);
542 TestBlock(moveDef, peDef, ob, owner, PATHDIR_LEFT_DOWN, PATHOPT_OPEN, 1.f, true);
543
544 // mark this block as closed
545 blockStates.nodeMask[ob->nodeNum] |= PATHOPT_CLOSED;
546 }
547
548 // we found our goal
549 if (foundGoal)
550 return IPath::Ok;
551
552 // we could not reach the goal
553 if (openBlockBuffer.GetSize() >= maxBlocksToBeSearched)
554 return IPath::GoalOutOfRange;
555
556 // search could not reach the goal due to the unit being locked in
557 if (openBlocks.empty())
558 return IPath::GoalOutOfRange;
559
560 // should never happen
561 LOG_L(L_ERROR, "%s - Unhandled end of search!", __FUNCTION__);
562 return IPath::Error;
563 }
564
565
566 /**
567 * Test the accessability of a block and its value,
568 * possibly also add it to the open-blocks pqueue.
569 */
TestBlock(const MoveDef & moveDef,const CPathFinderDef & peDef,const PathNode * parentOpenBlock,const CSolidObject *,const unsigned int pathDir,const unsigned int,float,bool)570 bool CPathEstimator::TestBlock(
571 const MoveDef& moveDef,
572 const CPathFinderDef& peDef,
573 const PathNode* parentOpenBlock,
574 const CSolidObject* /*owner*/,
575 const unsigned int pathDir,
576 const unsigned int /*blockStatus*/,
577 float /*speedMod*/,
578 bool /*withinConstraints*/
579 ) {
580 testedBlocks++;
581
582 // initial calculations of the new block
583 const int2 block = parentOpenBlock->nodePos + PE_DIRECTION_VECTORS[pathDir];
584 const unsigned int blockIdx = BlockPosToIdx(block);
585
586 // bounds-check
587 if ((unsigned)block.x >= nbrOfBlocks.x) return false;
588 if ((unsigned)block.y >= nbrOfBlocks.y) return false;
589
590 // check if the block is unavailable
591 if (blockStates.nodeMask[blockIdx] & (PATHOPT_BLOCKED | PATHOPT_CLOSED))
592 return false;
593
594 // read precached vertex costs
595 const unsigned int vertexIdx =
596 moveDef.pathType * blockStates.GetSize() * PATH_DIRECTION_VERTICES +
597 parentOpenBlock->nodeNum * PATH_DIRECTION_VERTICES +
598 GetBlockVertexOffset(pathDir, nbrOfBlocks.x);
599 assert((unsigned)vertexIdx < vertexCosts.size());
600 if (vertexCosts[vertexIdx] >= PATHCOST_INFINITY) {
601 // warning:
602 // we cannot naively set PATHOPT_BLOCKED here
603 // cause vertexCosts[] depends on the direction and nodeMask doesn't
604 // so we would have to save the direction via PATHOPT_LEFT etc. in the nodeMask
605 // but that's complicated and not worth it.
606 // Performance gain is low, cause we would just save the vertexCosts[] lookup
607 //blockStates.nodeMask[blockIdx] |= (PathDir2PathOpt(pathDir) | PATHOPT_BLOCKED);
608 //dirtyBlocks.push_back(blockIdx);
609 return false;
610 }
611
612 // check if the block is out of constraints
613 const int2 square = blockStates.peNodeOffsets[moveDef.pathType][blockIdx];
614 if (!peDef.WithinConstraints(square.x, square.y)) {
615 blockStates.nodeMask[blockIdx] |= PATHOPT_BLOCKED;
616 dirtyBlocks.push_back(blockIdx);
617 return false;
618 }
619
620
621 // evaluate this node (NOTE the max-resolution indexing for {flow,extra}Cost)
622 const float flowCost = (peDef.testMobile) ? (PathFlowMap::GetInstance())->GetFlowCost(square.x, square.y, moveDef, PathDir2PathOpt(pathDir)) : 0.0f;
623 const float extraCost = blockStates.GetNodeExtraCost(square.x, square.y, peDef.synced);
624 const float nodeCost = vertexCosts[vertexIdx] + flowCost + extraCost;
625
626 const float gCost = parentOpenBlock->gCost + nodeCost;
627 const float hCost = peDef.Heuristic(square.x, square.y);
628 const float fCost = gCost + hCost;
629
630 // already in the open set?
631 if (blockStates.nodeMask[blockIdx] & PATHOPT_OPEN) {
632 // check if new found path is better or worse than the old one
633 if (blockStates.fCost[blockIdx] <= fCost)
634 return true;
635
636 // no, clear old path data
637 blockStates.nodeMask[blockIdx] &= ~PATHOPT_CARDINALS;
638 }
639
640 // look for improvements
641 if (hCost < mGoalHeuristic) {
642 mGoalBlockIdx = blockIdx;
643 mGoalHeuristic = hCost;
644 }
645
646 // store this block as open
647 openBlockBuffer.SetSize(openBlockBuffer.GetSize() + 1);
648 assert(openBlockBuffer.GetSize() < MAX_SEARCHED_NODES_PE);
649
650 PathNode* ob = openBlockBuffer.GetNode(openBlockBuffer.GetSize());
651 ob->fCost = fCost;
652 ob->gCost = gCost;
653 ob->nodePos = block;
654 ob->nodeNum = blockIdx;
655 openBlocks.push(ob);
656
657 blockStates.SetMaxCost(NODE_COST_F, std::max(blockStates.GetMaxCost(NODE_COST_F), fCost));
658 blockStates.SetMaxCost(NODE_COST_G, std::max(blockStates.GetMaxCost(NODE_COST_G), gCost));
659
660 // mark this block as open
661 blockStates.fCost[blockIdx] = fCost;
662 blockStates.gCost[blockIdx] = gCost;
663 blockStates.nodeMask[blockIdx] |= (PathDir2PathOpt(pathDir) | PATHOPT_OPEN);
664
665 dirtyBlocks.push_back(blockIdx);
666 return true;
667 }
668
669
670 /**
671 * Recreate the path taken to the goal
672 */
FinishSearch(const MoveDef & moveDef,const CPathFinderDef & pfDef,IPath::Path & foundPath) const673 IPath::SearchResult CPathEstimator::FinishSearch(const MoveDef& moveDef, const CPathFinderDef& pfDef, IPath::Path& foundPath) const
674 {
675 // set some additional information
676 foundPath.pathCost = blockStates.fCost[mGoalBlockIdx] - mGoalHeuristic;
677
678 if (pfDef.needPath) {
679 unsigned int blockIdx = mGoalBlockIdx;
680
681 while (true) {
682 // use offset defined by the block
683 const int2 square = blockStates.peNodeOffsets[moveDef.pathType][blockIdx];
684 float3 pos(square.x * SQUARE_SIZE, 0.0f, square.y * SQUARE_SIZE);
685 pos.y = CMoveMath::yLevel(moveDef, square.x, square.y);
686
687 foundPath.path.push_back(pos);
688
689 if (blockIdx == mStartBlockIdx)
690 break;
691
692 // next step backwards
693 auto pathDir = PathOpt2PathDir(blockStates.nodeMask[blockIdx] & PATHOPT_CARDINALS);
694 int2 blockPos = BlockIdxToPos(blockIdx) - PE_DIRECTION_VECTORS[pathDir];
695 blockIdx = BlockPosToIdx(blockPos);
696 }
697
698 if (!foundPath.path.empty()) {
699 foundPath.pathGoal = foundPath.path.front();
700 }
701 }
702
703 return IPath::Ok;
704 }
705
706
707 /**
708 * Try to read offset and vertices data from file, return false on failure
709 * TODO: Read-error-check.
710 */
ReadFile(const std::string & cacheFileName,const std::string & map)711 bool CPathEstimator::ReadFile(const std::string& cacheFileName, const std::string& map)
712 {
713 const unsigned int hash = Hash();
714 char hashString[64] = {0};
715
716 sprintf(hashString, "%u", hash);
717 LOG("[PathEstimator::%s] hash=%s", __FUNCTION__, hashString);
718
719 std::string filename = GetPathCacheDir() + map + hashString + "." + cacheFileName + ".zip";
720 if (!FileSystem::FileExists(filename))
721 return false;
722 // open file for reading from a suitable location (where the file exists)
723 IArchive* pfile = archiveLoader.OpenArchive(dataDirsAccess.LocateFile(filename), "sdz");
724
725 if (!pfile || !pfile->IsOpen()) {
726 delete pfile;
727 return false;
728 }
729
730 char calcMsg[512];
731 sprintf(calcMsg, "Reading Estimate PathCosts [%d]", BLOCK_SIZE);
732 loadscreen->SetLoadMessage(calcMsg);
733
734 std::auto_ptr<IArchive> auto_pfile(pfile);
735 IArchive& file(*pfile);
736
737 const unsigned fid = file.FindFile("pathinfo");
738
739 if (fid < file.NumFiles()) {
740 pathChecksum = file.GetCrc32(fid);
741
742 std::vector<boost::uint8_t> buffer;
743 file.GetFile(fid, buffer);
744
745 if (buffer.size() < 4)
746 return false;
747
748 unsigned pos = 0;
749 unsigned filehash = *((unsigned*)&buffer[pos]);
750 pos += sizeof(unsigned);
751 if (filehash != hash)
752 return false;
753
754 // Read block-center-offset data.
755 const unsigned blockSize = blockStates.GetSize() * sizeof(int2);
756 if (buffer.size() < pos + blockSize * moveDefHandler->GetNumMoveDefs())
757 return false;
758
759 for (int pathType = 0; pathType < moveDefHandler->GetNumMoveDefs(); ++pathType) {
760 std::memcpy(&blockStates.peNodeOffsets[pathType][0], &buffer[pos], blockSize);
761 pos += blockSize;
762 }
763
764 // Read vertices data.
765 if (buffer.size() < pos + vertexCosts.size() * sizeof(float))
766 return false;
767 std::memcpy(&vertexCosts[0], &buffer[pos], vertexCosts.size() * sizeof(float));
768
769 // File read successful.
770 return true;
771 }
772
773 return false;
774 }
775
776
777 /**
778 * Try to write offset and vertex data to file.
779 */
WriteFile(const std::string & cacheFileName,const std::string & map)780 void CPathEstimator::WriteFile(const std::string& cacheFileName, const std::string& map)
781 {
782 // We need this directory to exist
783 if (!FileSystem::CreateDirectory(GetPathCacheDir()))
784 return;
785
786 const unsigned int hash = Hash();
787 char hashString[64] = {0};
788
789 sprintf(hashString, "%u", hash);
790 LOG("[PathEstimator::%s] hash=%s", __FUNCTION__, hashString);
791
792 const std::string filename = GetPathCacheDir() + map + hashString + "." + cacheFileName + ".zip";
793
794 // open file for writing in a suitable location
795 zipFile file = zipOpen(dataDirsAccess.LocateFile(filename, FileQueryFlags::WRITE).c_str(), APPEND_STATUS_CREATE);
796
797 if (file == NULL)
798 return;
799
800 zipOpenNewFileInZip(file, "pathinfo", NULL, NULL, 0, NULL, 0, NULL, Z_DEFLATED, Z_BEST_COMPRESSION);
801
802 // Write hash. (NOTE: this also affects the CRC!)
803 zipWriteInFileInZip(file, (void*) &hash, 4);
804
805 // Write block-center-offsets.
806 for (int pathType = 0; pathType < moveDefHandler->GetNumMoveDefs(); ++pathType)
807 zipWriteInFileInZip(file, (void*) &blockStates.peNodeOffsets[pathType][0], blockStates.GetSize() * sizeof(int2));
808
809 // Write vertices.
810 zipWriteInFileInZip(file, &vertexCosts[0], vertexCosts.size() * sizeof(float));
811
812 zipCloseFileInZip(file);
813 zipClose(file, NULL);
814
815
816 // get the CRC over the written path data
817 IArchive* pfile = archiveLoader.OpenArchive(dataDirsAccess.LocateFile(filename), "sdz");
818
819 if (pfile == NULL)
820 return;
821
822 if (pfile->IsOpen()) {
823 assert(pfile->FindFile("pathinfo") < pfile->NumFiles());
824 pathChecksum = pfile->GetCrc32(pfile->FindFile("pathinfo"));
825 }
826
827 delete pfile;
828 }
829
830
831 /**
832 * Returns a hash-code identifying the dataset of this estimator.
833 * FIXME: uses checksum of raw heightmap (before Lua has seen it)
834 */
Hash() const835 unsigned int CPathEstimator::Hash() const
836 {
837 return (readMap->GetMapChecksum() + moveDefHandler->GetCheckSum() + BLOCK_SIZE + PATHESTIMATOR_VERSION);
838 }
839