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