/** This runs in a separate thread */ static int fpathThreadFunc(void *) { wzMutexLock(fpathMutex); while (!fpathQuit) { if (pathJobs.empty()) { ASSERT(!waitingForResult, "Waiting for a result (id %u) that doesn't exist.", waitingForResultId); wzMutexUnlock(fpathMutex); wzSemaphoreWait(fpathSemaphore); // Go to sleep until needed. wzMutexLock(fpathMutex); continue; } // Copy the first job from the queue. Don't pop yet, since the main thread may want to set .deleted = true. PATHJOB job = pathJobs.front(); wzMutexUnlock(fpathMutex); // Execute path-finding for this job using our local temporaries PATHRESULT result; result.droidID = job.droidID; memset(&result.sMove, 0, sizeof(result.sMove)); result.retval = FPR_FAILED; result.originalDest = Vector2i(job.destX, job.destY); // we need to lock BEFORE we fiddle with the data, or we get ugly data race conditions. wzMutexLock(fpathMutex); fpathExecute(&job, &result); ASSERT(pathJobs.front().droidID == job.droidID, "Bug"); // The front of pathJobs may have .deleted set to true, but should not otherwise have been modified or deleted. if (!pathJobs.front().deleted) { pathResults.push_back(result); } pathJobs.pop_front(); // Unblock the main thread, if it was waiting for this particular result. if (waitingForResult && waitingForResultId == job.droidID) { waitingForResult = false; objTrace(waitingForResultId, "These are the droids you are looking for."); wzSemaphorePost(waitingForResultSemaphore); } } wzMutexUnlock(fpathMutex); return 0; }
static FPATH_RETVAL fpathRoute(MOVE_CONTROL *psMove, unsigned id, int startX, int startY, int tX, int tY, PROPULSION_TYPE propulsionType, DROID_TYPE droidType, FPATH_MOVETYPE moveType, int owner, bool acceptNearest, StructureBounds const &dstStructure) { objTrace(id, "called(*,id=%d,sx=%d,sy=%d,ex=%d,ey=%d,prop=%d,type=%d,move=%d,owner=%d)", id, startX, startY, tX, tY, (int)propulsionType, (int)droidType, (int)moveType, owner); if (!worldOnMap(startX, startY) || !worldOnMap(tX, tY)) { debug(LOG_ERROR, "Droid trying to find path to/from invalid location (%d %d) -> (%d %d).", startX, startY, tX, tY); objTrace(id, "Invalid start/end."); syncDebug("fpathRoute(..., %d, %d, %d, %d, %d, %d, %d, %d, %d) = FPR_FAILED", id, startX, startY, tX, tY, propulsionType, droidType, moveType, owner); return FPR_FAILED; } // don't have to do anything if already there if (startX == tX && startY == tY) { // return failed to stop them moving anywhere objTrace(id, "Tried to move nowhere"); syncDebug("fpathRoute(..., %d, %d, %d, %d, %d, %d, %d, %d, %d) = FPR_FAILED", id, startX, startY, tX, tY, propulsionType, droidType, moveType, owner); return FPR_FAILED; } // Check if waiting for a result while (psMove->Status == MOVEWAITROUTE) { objTrace(id, "Checking if we have a path yet"); auto const &I = pathResults.find(id); ASSERT(I != pathResults.end(), "Missing path result promise"); PATHRESULT result = I->second.get(); ASSERT(result.retval != FPR_OK || result.sMove.asPath, "Ok result but no path in list"); // Copy over select fields - preserve others psMove->destination = result.sMove.destination; psMove->numPoints = result.sMove.numPoints; bool correctDestination = tX == result.originalDest.x && tY == result.originalDest.y; psMove->pathIndex = 0; psMove->Status = MOVENAVIGATE; free(psMove->asPath); psMove->asPath = result.sMove.asPath; FPATH_RETVAL retval = result.retval; ASSERT(retval != FPR_OK || psMove->asPath, "Ok result but no path after copy"); ASSERT(retval != FPR_OK || psMove->numPoints > 0, "Ok result but path empty after copy"); // Remove it from the result list pathResults.erase(id); objTrace(id, "Got a path to (%d, %d)! Length=%d Retval=%d", psMove->destination.x, psMove->destination.y, psMove->numPoints, (int)retval); syncDebug("fpathRoute(..., %d, %d, %d, %d, %d, %d, %d, %d, %d) = %d, path[%d] = %08X->(%d, %d)", id, startX, startY, tX, tY, propulsionType, droidType, moveType, owner, retval, psMove->numPoints, ~crcSumVector2i(0, psMove->asPath, psMove->numPoints), psMove->destination.x, psMove->destination.y); if (!correctDestination) { goto queuePathfinding; // Seems we got the result of an old pathfinding job for this droid, so need to pathfind again. } return retval; } queuePathfinding: // We were not waiting for a result, and found no trivial path, so create new job and start waiting PATHJOB job; job.origX = startX; job.origY = startY; job.droidID = id; job.destX = tX; job.destY = tY; job.dstStructure = dstStructure; job.droidType = droidType; job.propulsion = propulsionType; job.moveType = moveType; job.owner = owner; job.acceptNearest = acceptNearest; job.deleted = false; fpathSetBlockingMap(&job); debug(LOG_NEVER, "starting new job for droid %d 0x%x", id, id); // Clear any results or jobs waiting already. It is a vital assumption that there is only one // job or result for each droid in the system at any time. fpathRemoveDroidData(id); packagedPathJob task([job]() { return fpathExecute(job); }); pathResults[id] = task.get_future(); // Add to end of list wzMutexLock(fpathMutex); bool isFirstJob = pathJobs.empty(); pathJobs.push_back(std::move(task)); wzMutexUnlock(fpathMutex); if (isFirstJob) { wzSemaphorePost(fpathSemaphore); // Wake up processing thread. } objTrace(id, "Queued up a path-finding request to (%d, %d), at least %d items earlier in queue", tX, tY, isFirstJob); syncDebug("fpathRoute(..., %d, %d, %d, %d, %d, %d, %d, %d, %d) = FPR_WAIT", id, startX, startY, tX, tY, propulsionType, droidType, moveType, owner); return FPR_WAIT; // wait while polling result queue }