patch #1064: Multi-threaded path-finding, this merges in the path-finding branch.

git-svn-id: svn+ssh://svn.gna.org/svn/warzone/trunk@5735 4a71c877-e1ca-e34f-864e-861f7616d084
master
Per Inge Mathisen 2008-08-02 14:37:53 +00:00
parent f6bf526248
commit eb01bc2b64
8 changed files with 506 additions and 432 deletions

View File

@ -37,8 +37,6 @@ static SDWORD astarOuter, astarRemove;
/** Keeps track of the amount of iterations done in the inner loop of our A*
* implementation.
*
* @see FPATH_LOOP_LIMIT
*
* @ingroup pathfinding
*/
int astarInner = 0;
@ -368,10 +366,10 @@ BOOL fpathTileLOS(SDWORD x1,SDWORD y1, SDWORD x2,SDWORD y2)
return !obstruction;
}
SDWORD fpathAStarRoute(SDWORD routeMode, MOVE_CONTROL *psMove, SDWORD sx, SDWORD sy, SDWORD fx, SDWORD fy, PROPULSION_TYPE propulsion)
SDWORD fpathAStarRoute(MOVE_CONTROL *psMove, SDWORD sx, SDWORD sy, SDWORD fx, SDWORD fy, PROPULSION_TYPE propulsion)
{
FP_NODE *psFound, *psCurr, *psNew, *psParent, *psNext;
static FP_NODE *psNearest, *psRoute;
FP_NODE *psFound, *psCurr, *psNew, *psParent, *psNext;
FP_NODE *psNearest, *psRoute;
SDWORD dir, x,y, currDist;
SDWORD retval = ASR_OK;
const int tileSX = map_coord(sx);
@ -379,8 +377,6 @@ static FP_NODE *psNearest, *psRoute;
const int tileFX = map_coord(fx);
const int tileFY = map_coord(fy);
if (routeMode == ASR_NEWROUTE)
{
fpathTableReset();
// Add the start point to the open list
@ -397,16 +393,10 @@ static FP_NODE *psNearest, *psRoute;
fpathAddNode(psCurr);
psRoute = NULL;
psNearest = NULL;
}
// search for a route
while (psOpen != NULL)
{
if (astarInner > FPATH_LOOP_LIMIT)
{
return ASR_PARTIAL;
}
psCurr = fpathOpenGet();
if (psCurr->x == tileFX && psCurr->y == tileFY)

View File

@ -21,22 +21,6 @@
#ifndef __INCLUDED_SRC_ASTART_H__
#define __INCLUDED_SRC_ASTART_H__
/** The maximum amount of iterations we want to spend in the inner loop of the
* A* algorithm.
*
* If this number is succeeded, we will return ASR_PARTIAL and continue the
* path finding during the next next frame. Function fpathRoute() will return
* FPR_WAIT if it was already working on the currently requested droid.
* FPR_RESCHEDULE indicates that fpathRoute() didn't even get started on the
* current droid and it'll require rescheduling in the next frame.
*
* @ingroup pathfinding
*/
#define FPATH_LOOP_LIMIT 600
// counters for A*
extern int astarInner;
/** Reset the A* counters
*
* This function resets astarInner among others.
@ -53,25 +37,14 @@ enum
{
ASR_OK, ///< found a route
ASR_FAILED, ///< no route could be found
ASR_PARTIAL, ///< routing cannot be finished this frame, and should be continued the next frame
ASR_NEAREST, ///< found a partial route to a nearby position
};
/** route modes for astar
*
* @ingroup pathfinding
*/
enum
{
ASR_NEWROUTE, ///< start a new route
ASR_CONTINUE, ///< continue a route that was partially completed the last frame
};
/** Use the A* algorithm to find a path
*
* @ingroup pathfinding
*/
SDWORD fpathAStarRoute(SDWORD routeMode, MOVE_CONTROL *psMove, SDWORD sx, SDWORD sy, SDWORD fx, SDWORD fy, PROPULSION_TYPE propulsion);
SDWORD fpathAStarRoute(MOVE_CONTROL *psMove, SDWORD sx, SDWORD sy, SDWORD fx, SDWORD fy, PROPULSION_TYPE propulsion);
/** Check LOS (Line Of Sight) between two tiles
*/

View File

@ -299,6 +299,8 @@ void droidRelease(DROID *psDroid)
}
}
fpathRemoveDroidData(psDroid->id);
// leave the current formation if any
if (psDroid->sMove.psFormation)
{

View File

@ -24,6 +24,9 @@
*
*/
#include <SDL.h>
#include <SDL_thread.h>
#include "lib/framework/frame.h"
#include "objects.h"
@ -51,6 +54,26 @@
#define NUM_DIR 8
typedef struct _jobNode
{
PROPULSION_TYPE propulsion;
DROID_TYPE droidType;
int destX, destY;
int origX, origY;
UDWORD droidID;
struct _jobNode *next;
} PATHJOB;
typedef struct _jobDone
{
UDWORD droidID; ///< Unique droid ID.
MOVE_CONTROL sMove; ///< New movement values for the droid.
struct _jobDone *next; ///< Next result in the list.
FPATH_RETVAL retval; ///< Result value from path-finding.
bool done; ///< If the result is finished and ready for use.
} PATHRESULT;
// Convert a direction into an offset
// dir 0 => x = 0, y = -1
static const Vector2i aDirOffset[NUM_DIR] =
@ -65,21 +88,136 @@ static const Vector2i aDirOffset[NUM_DIR] =
{ 1, 1},
};
// if a route is spread over a number of frames this stores the droid
// the route is being done for
static DROID* psPartialRouteDroid = NULL;
// threading stuff
static SDL_Thread *fpathThread = NULL;
static SDL_sem *fpathSemaphore = NULL;
static PATHJOB *firstJob = NULL;
static PATHRESULT *firstResult = NULL;
// coords of the partial route
static SDWORD partialSX,partialSY, partialTX,partialTY;
// the last frame on which the partial route was calculatated
static SDWORD lastPartialFrame;
static void fpathExecute(PATHJOB *psJob, PATHRESULT *psResult);
/** Find the length of the job queue. Function is thread-safe. */
static int fpathJobQueueLength(void)
{
PATHJOB *psJob;
int count = 0;
SDL_SemWait(fpathSemaphore);
psJob = firstJob;
while (psJob)
{
count++;
psJob = psJob->next;
}
SDL_SemPost(fpathSemaphore);
return count;
}
/** Find the length of the result queue, excepting future results. Function is thread-safe. */
static int fpathResultQueueLength(void)
{
PATHRESULT *psResult;
int count = 0;
SDL_SemWait(fpathSemaphore);
psResult = firstResult;
while (psResult)
{
if (psResult->done)
{
count++;
}
psResult = psResult->next;
}
SDL_SemPost(fpathSemaphore);
return count;
}
/** This runs in a separate thread */
static int fpathThreadFunc(WZ_DECL_UNUSED void *data)
{
bool finished = false;
SDL_SemWait(fpathSemaphore);
while (!finished)
{
PATHJOB job;
PATHRESULT *psResult, result;
bool gotWork = false;
// Pop the first job off the queue
if (firstJob)
{
PATHJOB *next = firstJob->next;
job = *firstJob; // struct copy
job.next = NULL;
free(firstJob);
firstJob = next;
gotWork = true;
}
if (!gotWork)
{
SDL_SemPost(fpathSemaphore);
SDL_Delay(100);
SDL_SemWait(fpathSemaphore);
continue;
}
// Create future result
psResult = malloc(sizeof(*psResult));
psResult->done = false;
psResult->droidID = job.droidID;
psResult->sMove.asPath = NULL;
psResult->retval = FPR_FAILED;
// Add to beginning of result list
psResult->next = firstResult;
firstResult = psResult;
psResult = NULL; // now hands off
SDL_SemPost(fpathSemaphore);
// Execute path-finding for this job using our local temporaries
memset(&result, 0, sizeof(result));
result.sMove.asPath = NULL;
fpathExecute(&job, &result);
SDL_SemWait(fpathSemaphore);
// Find our result again, and replace it with our local temporary
// We do it this way to avoid a race condition where a droid dies
// while we are generating its path, and we never free the result.
psResult = firstResult;
while (psResult && psResult->droidID != job.droidID)
{
psResult = psResult->next;
}
if (psResult)
{
psResult->sMove = result.sMove; // struct copy
psResult->retval = result.retval;
psResult->done = true;
}
}
SDL_SemPost(fpathSemaphore);
return 0;
}
// initialise the findpath module
BOOL fpathInitialise(void)
{
psPartialRouteDroid = NULL;
if (!fpathThread)
{
fpathSemaphore = SDL_CreateSemaphore(1);
fpathThread = SDL_CreateThread(fpathThreadFunc, NULL);
}
return true;
}
@ -88,25 +226,23 @@ BOOL fpathInitialise(void)
void fpathShutdown()
{
fpathHardTableReset();
if (fpathThread)
{
SDL_KillThread(fpathThread);
fpathThread = NULL;
SDL_DestroySemaphore(fpathSemaphore);
fpathSemaphore = NULL;
}
}
/** Updates the pathfinding system.
* @post Pathfinding jobs for DROID's that died, aren't waiting for a route
* anymore, or the currently calculated route is outdated for, are
* removed from the job queue.
*
* @ingroup pathfinding
/**
* Updates the pathfinding system.
* @ingroup pathfinding
*/
void fpathUpdate(void)
{
if (psPartialRouteDroid != NULL
&& (psPartialRouteDroid->died
|| psPartialRouteDroid->sMove.Status != MOVEWAITROUTE
|| (lastPartialFrame + 5) < frameGetFrameNumber()))
{
psPartialRouteDroid = NULL;
}
// Nothing now
}
@ -171,15 +307,8 @@ static inline int fpathDistToTile(int tileX, int tileY, int pointX, int pointY)
}
void fpathSetDirectRoute(DROID* psDroid, SDWORD targetX, SDWORD targetY)
static void fpathSetMove(MOVE_CONTROL *psMoveCntl, SDWORD targetX, SDWORD targetY)
{
MOVE_CONTROL *psMoveCntl;
ASSERT(psDroid != NULL, "fpathSetDirectRoute: invalid droid pointer");
ASSERT(psDroid->type == OBJ_DROID, "We got passed a DROID that isn't a DROID!");
psMoveCntl = &psDroid->sMove;
psMoveCntl->asPath = realloc(psMoveCntl->asPath, sizeof(*psMoveCntl->asPath));
psMoveCntl->DestinationX = targetX;
psMoveCntl->DestinationY = targetY;
@ -189,277 +318,348 @@ void fpathSetDirectRoute(DROID* psDroid, SDWORD targetX, SDWORD targetY)
}
// Variables for the callback
static SDWORD finalX,finalY, vectorX,vectorY;
static SDWORD clearX,clearY;
static BOOL obstruction;
/** Callback to find the first clear tile before an obstructed target
*
* @ingroup pathfinding
*/
static bool fpathEndPointCallback(Vector3i pos, int dist, void* data)
void fpathSetDirectRoute(DROID *psDroid, SDWORD targetX, SDWORD targetY)
{
// See if this point is past the final point (dot product)
int vx = pos.x - finalX;
int vy = pos.y - finalY;
if (vx*vectorX + vy*vectorY <= 0)
{
return false;
}
// note the last clear tile
if (!fpathBlockingTile(map_coord(pos.x), map_coord(pos.y), *(PROPULSION_TYPE*)data))
{
clearX = (pos.x & ~TILE_MASK) + TILE_UNITS/2;
clearY = (pos.y & ~TILE_MASK) + TILE_UNITS/2;
}
else
{
obstruction = true;
}
return true;
fpathSetMove(&psDroid->sMove, targetX, targetY);
}
/** Create a final route from a gateway route
*
* @ingroup pathfinding
*/
static FPATH_RETVAL fpathGatewayRoute(DROID* psDroid, SDWORD routeMode, SDWORD sx, SDWORD sy,
SDWORD fx, SDWORD fy, MOVE_CONTROL *psMoveCntl, PROPULSION_TYPE propulsion)
void fpathRemoveDroidData(int id)
{
int asret;
PATHJOB *psJob;
PATHJOB *psPrevJob = NULL;
PATHRESULT *psResult;
PATHRESULT *psPrevResult = NULL;
if (routeMode == ASR_NEWROUTE)
SDL_SemWait(fpathSemaphore);
psJob = firstJob;
psResult = firstResult;
while (psJob)
{
// initialise the move control structures
psMoveCntl->numPoints = 0;
}
objTrace(psDroid->id, "fpathGatewayRoute: astar route : (%d,%d) -> (%d,%d)",
map_coord(sx), map_coord(sy), map_coord(fx), map_coord(fy));
asret = fpathAStarRoute(routeMode, &psDroid->sMove, sx, sy, fx,fy, propulsion);
if (asret == ASR_PARTIAL)
{
// routing hasn't finished yet
objTrace(psDroid->id, "fpathGatewayRoute: Reschedule");
return FPR_WAIT;
}
else if (asret == ASR_NEAREST)
{
// all routing was in one zone - this is as good as it's going to be
objTrace(psDroid->id, "fpathGatewayRoute: Nearest route in same zone");
return FPR_OK;
}
else if (asret == ASR_FAILED)
{
// all routing was in one zone - can't retry
objTrace(psDroid->id, "fpathGatewayRoute: Failed route in same zone");
return FPR_FAILED;
}
return FPR_OK;
}
// Find a route for an DROID to a location
FPATH_RETVAL fpathRoute(DROID* psDroid, SDWORD tX, SDWORD tY)
{
FPATH_RETVAL retVal = FPR_OK;
MOVE_CONTROL *psMoveCntl = &psDroid->sMove;
Vector3i start = { psDroid->pos.x, psDroid->pos.y, 0 }, target = { tX, tY, 0 };
PROPULSION_STATS *psPropStats;
ASSERT(psDroid->type == OBJ_DROID, "We got passed a DROID that isn't a DROID!");
if (psPartialRouteDroid == NULL || psPartialRouteDroid != psDroid)
{
}
else if (psDroid->sMove.Status == MOVEWAITROUTE
&& psDroid->sMove.DestinationX != tX)
{
// we have a partial route, but changed destination, so need to recalculate
psPartialRouteDroid = NULL;
}
else
{
// continuing routing for the DROID
start = Vector3i_New(partialSX, partialSY, 0);
target = Vector3i_New(partialTX, partialTY, 0);
}
// don't have to do anything if already there
if (Vector3i_Compare(start, target))
{
// return failed to stop them moving anywhere
return FPR_FAILED;
}
// set the correct blocking tile function
psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION].nStat;
ASSERT(psPropStats != NULL, "invalid propulsion stats pointer");
if (psPartialRouteDroid == NULL || psPartialRouteDroid != psDroid)
{
Vector3i final = { (target.x & ~TILE_MASK) + TILE_UNITS/2, (target.y & ~TILE_MASK) + TILE_UNITS/2, 0 };
// check whether the start point of the route
// is a blocking tile and find an alternative if it is
if (fpathBlockingTile(map_coord(start.x), map_coord(start.y), psPropStats->propulsionType))
if (psJob->droidID == id)
{
// find the nearest non blocking tile to the DROID
int minDist = SDWORD_MAX;
int nearestDir = NUM_DIR;
int dir;
for (dir = 0; dir < NUM_DIR; dir++)
if (psPrevJob)
{
Vector3i test = { map_coord(start.x) + aDirOffset[dir].x, map_coord(start.y) + aDirOffset[dir].y, 0};
if (!fpathBlockingTile(test.x, test.y, psPropStats->propulsionType))
{
int tileDist = fpathDistToTile(test.x, test.y, start.x, start.y);
if (tileDist < minDist)
{
minDist = tileDist;
nearestDir = dir;
}
}
psPrevJob->next = psJob->next;
free(psJob);
psJob = psPrevJob->next;
}
if (nearestDir == NUM_DIR)
else
{
// surrounded by blocking tiles, give up
objTrace(psDroid->id, "droid %u: route failed (surrouned by blocking)", (unsigned int)psDroid->id);
return FPR_FAILED;
firstJob = psJob->next;
free(psJob);
psJob = firstJob;
}
start.x = world_coord(map_coord(start.x) + aDirOffset[nearestDir].x)
+ TILE_SHIFT / 2;
start.y = world_coord(map_coord(start.y) + aDirOffset[nearestDir].y)
+ TILE_SHIFT / 2;
}
// initialise the raycast - if there is los to the target, no routing necessary
finalX = final.x;
finalY = final.y;
clearX = finalX; clearY = finalY;
{
Vector3i dir = Vector3i_Sub(final, start);
vectorX = -dir.x;
vectorY = -dir.y;
obstruction = false;
// cast the ray to find the last clear tile before the obstruction
rayCast(start, dir, RAY_MAXLEN, fpathEndPointCallback, &psPropStats->propulsionType);
}
if (!obstruction)
{
// no obstructions - trivial route
fpathSetDirectRoute(psDroid, target.x, target.y);
objTrace(psDroid->id, "droid %u: trivial route", (unsigned int)psDroid->id);
if (psPartialRouteDroid != NULL)
{
objTrace(psDroid->id, "Unit %u: trivial route during multi-frame route", (unsigned int)psDroid->id);
}
return FPR_OK;
}
// check whether the end point of the route
// is a blocking tile and find an alternative if it is
if (fpathBlockingTile(map_coord(target.x), map_coord(target.y), psPropStats->propulsionType))
{
// route to the last clear tile found by the raycast
// Does this code work? - Per
target.x = clearX;
target.y = clearY;
objTrace(psDroid->id, "Unit %u: end point is blocked, going to (%d, %d) instead",
(unsigned int)psDroid->id, (int)clearX, (int)clearY);
}
}
ASSERT(start.x >= 0 && start.x < mapWidth * TILE_UNITS && start.y >= 0 && start.y < mapHeight * TILE_UNITS,
"start coords (%d, %d) off map (%u, %u)", start.x, start.y, mapWidth * TILE_UNITS, mapHeight * TILE_UNITS);
ASSERT(target.x >= 0 && target.x < mapWidth * TILE_UNITS && target.y >= 0 && target.y < mapHeight * TILE_UNITS,
"target coords (%d, %d) off map (%u, %u)", target.x, target.y, mapWidth * TILE_UNITS, mapHeight * TILE_UNITS);
ASSERT(astarInner >= 0, "astarInner overflowed!");
if (start.x < 0 || start.y < 0 || target.x < 0 || target.y < 0
|| start.x >= mapWidth * TILE_UNITS || start.y >= mapHeight * TILE_UNITS
|| target.x >= mapWidth * TILE_UNITS || target.y >= mapHeight * TILE_UNITS)
{
return FPR_FAILED; // fallback if we play with asserts off
}
if (astarInner > FPATH_LOOP_LIMIT)
{
// Time out
if (psPartialRouteDroid == psDroid)
{
return FPR_WAIT;
}
else
{
objTrace(psDroid->id, "droid %u: reschedule", (unsigned int)psDroid->id);
return FPR_RESCHEDULE;
psPrevJob = psJob;
psJob = psJob->next;
}
}
else if ((psPartialRouteDroid != NULL
&& psPartialRouteDroid != psDroid)
|| (psPartialRouteDroid != psDroid
&& psNextRouteDroid != NULL
&& psNextRouteDroid != psDroid))
while (psResult)
{
// Not our turn
return FPR_RESCHEDULE;
if (psResult->droidID == id)
{
if (psPrevResult)
{
psPrevResult->next = psResult->next;
free(psResult->sMove.asPath);
free(psResult);
psResult = psPrevResult->next;
}
else
{
firstResult = psResult->next;
free(psResult->sMove.asPath);
free(psResult);
psResult = firstResult;
}
}
else
{
psPrevResult = psResult;
psResult = psResult->next;
}
}
SDL_SemPost(fpathSemaphore);
}
static FPATH_RETVAL fpathRoute(MOVE_CONTROL *psMove, int id, int startX, int startY, int tX, int tY, PROPULSION_TYPE propulsionType, DROID_TYPE droidType)
{
PATHJOB *psJob = NULL;
objTrace(id, "called(,%d,%d,%d,%d,%d,,)", id, startX, startY, tX, tY);
// 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");
return FPR_FAILED;
}
// Now actually create a route
if (psPartialRouteDroid == NULL)
// Check if waiting for a result
if (psMove->Status == MOVEWAITROUTE)
{
retVal = fpathGatewayRoute(psDroid, ASR_NEWROUTE, start.x, start.y, target.x, target.y, psMoveCntl, psPropStats->propulsionType);
PATHRESULT *psPrev = NULL, *psNext = firstResult;
objTrace(id, "Checking if we have a path yet");
SDL_SemWait(fpathSemaphore);
while (psNext)
{
if (psNext->droidID == id && psNext->done)
{
FPATH_RETVAL retval;
ASSERT(psNext->retval != FPR_OK || psNext->sMove.asPath, "Ok result but no path in list");
// Remove it from the result list
if (psPrev)
{
psPrev->next = psNext->next;
}
else
{
firstResult = psNext->next;
}
// Copy over select fields - preserve others
psMove->DestinationX = psNext->sMove.DestinationX;
psMove->DestinationY = psNext->sMove.DestinationY;
psMove->numPoints = psNext->sMove.numPoints;
psMove->Position = 0;
psMove->Status = MOVEROUTE;
if (psMove->asPath)
{
free(psMove->asPath);
}
psMove->asPath = psNext->sMove.asPath;
retval = psNext->retval;
ASSERT(retval != FPR_OK || psMove->asPath, "Ok result but no path after copy");
free(psNext);
SDL_SemPost(fpathSemaphore);
objTrace(id, "Got a path to (%d, %d)! Length=%d Retval=%d", (int)psMove->DestinationX,
(int)psMove->DestinationY, (int)psMove->numPoints, (int)retval);
return retval;
}
psPrev = psNext;
psNext = psNext->next;
}
SDL_SemPost(fpathSemaphore);
objTrace(id, "No path yet. Waiting.");
return FPR_WAIT; // keep waiting
}
// We were not waiting for a result, and found no trivial path, so create new job and start waiting
psJob = malloc(sizeof(*psJob));
ASSERT(psJob, "Out of memory");
if (!psJob)
{
return FPR_FAILED;
}
psJob->origX = startX;
psJob->origY = startY;
psJob->droidID = id;
psJob->destX = tX;
psJob->destY = tY;
psJob->next = NULL;
psJob->droidType = droidType;
psJob->propulsion = propulsionType;
// 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);
SDL_SemWait(fpathSemaphore);
// Add to end of list
if (!firstJob)
{
firstJob = psJob;
}
else
{
objTrace(psDroid->id, "Partial Route");
psPartialRouteDroid = NULL;
retVal = fpathGatewayRoute(psDroid, ASR_CONTINUE, start.x, start.y, target.x, target.y, psMoveCntl, psPropStats->propulsionType);
}
if (retVal == FPR_WAIT)
{
psPartialRouteDroid = psDroid;
lastPartialFrame = frameGetFrameNumber();
partialSX = start.x;
partialSY = start.y;
partialTX = target.x;
partialTY = target.y;
}
else if (retVal == FPR_FAILED && isVtolDroid(psDroid))
{
fpathSetDirectRoute(psDroid, target.x, target.y);
retVal = FPR_OK;
}
PATHJOB *psNext = firstJob;
#ifdef DEBUG_MAP
{
MAPTILE *psTile;
psTile = psMapTiles;
for (x = 0; x < (SDWORD)(mapWidth*mapHeight); x++)
while (psNext->next != NULL)
{
if (psTile->tileInfoBits & BITS_FPATHBLOCK)
psNext = psNext->next;
}
psNext->next = psJob;
}
SDL_SemPost(fpathSemaphore);
objTrace(id, "Queued up a path-finding request to (%d, %d)", tX, tY);
return FPR_WAIT; // wait while polling result queue
}
// Find a route for an DROID to a location in world coordinates
FPATH_RETVAL fpathDroidRoute(DROID* psDroid, SDWORD tX, SDWORD tY)
{
PROPULSION_STATS *psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION].nStat;
ASSERT(psPropStats != NULL, "invalid propulsion stats pointer");
ASSERT(psDroid->type == OBJ_DROID, "We got passed an object that isn't a DROID!");
if (psDroid->type != OBJ_DROID || !psPropStats)
{
return FPR_FAILED;
}
// check whether the end point of the route
// is a blocking tile and find an alternative if it is
if (psDroid->sMove.Status != MOVEWAITROUTE && fpathBlockingTile(map_coord(tX), map_coord(tY), psPropStats->propulsionType))
{
// find the nearest non blocking tile to the DROID
int minDist = SDWORD_MAX;
int nearestDir = NUM_DIR;
int dir;
for (dir = 0; dir < NUM_DIR; dir++)
{
int x = map_coord(tX) + aDirOffset[dir].x;
int y = map_coord(tY) + aDirOffset[dir].y;
if (!fpathBlockingTile(x, y, psPropStats->propulsionType))
{
ASSERT( false,"blocking flags still in the map" );
// pick the adjacent tile closest to our starting point
int tileDist = fpathDistToTile(x, y, psDroid->pos.x, psDroid->pos.y);
if (tileDist < minDist)
{
minDist = tileDist;
nearestDir = dir;
}
}
psTile++;
}
if (nearestDir == NUM_DIR)
{
// surrounded by blocking tiles, give up
objTrace(psDroid->id, "route failed (target by blocking)");
return FPR_FAILED;
}
else
{
tX = world_coord(map_coord(tX) + aDirOffset[nearestDir].x) + TILE_SHIFT / 2;
tY = world_coord(map_coord(tY) + aDirOffset[nearestDir].y) + TILE_SHIFT / 2;
}
}
#endif
return retVal;
return fpathRoute(&psDroid->sMove, psDroid->id, psDroid->pos.x, psDroid->pos.y, tX, tY, psPropStats->propulsionType, psDroid->droidType);
}
// Run only from path thread
static void fpathExecute(PATHJOB *psJob, PATHRESULT *psResult)
{
FPATH_RETVAL retval = fpathAStarRoute(&psResult->sMove, psJob->origX, psJob->origY, psJob->destX, psJob->destY, psJob->propulsion);
ASSERT(retval != ASR_OK || psResult->sMove.asPath, "Ok result but no path in result");
switch (retval)
{
case ASR_NEAREST:
objTrace(psJob->droidID, "** Nearest route **");
psResult->retval = FPR_OK;
break;
case ASR_FAILED:
objTrace(psJob->droidID, "** Failed route **");
// Is this really a good idea? Was in original code.
if (psJob->propulsion == PROPULSION_TYPE_LIFT && psJob->droidType != DROID_TRANSPORTER)
{
objTrace(psJob->droidID, "Doing fallback for non-transport VTOL");
fpathSetMove(&psResult->sMove, psJob->destX, psJob->destY);
psResult->retval = FPR_OK;
}
else
{
psResult->retval = FPR_FAILED;
}
break;
default:
objTrace(psJob->droidID, "Got route of length %d", psResult->sMove.numPoints);
psResult->retval = FPR_OK;
break;
}
}
void fpathTest(int x, int y, int x2, int y2)
{
MOVE_CONTROL sMove;
FPATH_RETVAL r;
int i;
/* Check initial state */
assert(fpathThread != NULL);
assert(fpathSemaphore != NULL);
assert(firstJob == NULL);
assert(firstResult == NULL);
assert(fpathJobQueueLength() == 0);
assert(fpathResultQueueLength() == 0);
fpathRemoveDroidData(0); // should not crash
/* This should not leak memory */
sMove.asPath = NULL;
for (i = 0; i < 100; i++) fpathSetMove(&sMove, 1, 1);
free(sMove.asPath);
sMove.asPath = NULL;
/* Test one path */
sMove.Status = MOVEINACTIVE;
r = fpathRoute(&sMove, 1, x, y, x2, y2, PROPULSION_TYPE_WHEELED, DROID_WEAPON);
assert(r == FPR_WAIT);
sMove.Status = MOVEWAITROUTE;
assert(fpathJobQueueLength() == 1 || fpathResultQueueLength() == 1);
fpathRemoveDroidData(2); // should not crash, nor remove our path
assert(fpathJobQueueLength() == 1 || fpathResultQueueLength() == 1);
while (fpathResultQueueLength() == 0) usleep(1);
assert(fpathJobQueueLength() == 0);
assert(fpathResultQueueLength() == 1);
r = fpathRoute(&sMove, 1, x, y, x2, y2, PROPULSION_TYPE_WHEELED, DROID_WEAPON);
assert(r == FPR_OK);
assert(sMove.numPoints > 0 && sMove.asPath);
assert(sMove.asPath[sMove.numPoints - 1].x == map_coord(x2));
assert(sMove.asPath[sMove.numPoints - 1].y == map_coord(y2));
assert(fpathResultQueueLength() == 0);
/* Let one hundred paths flower! */
sMove.Status = MOVEINACTIVE;
for (i = 1; i <= 100; i++)
{
r = fpathRoute(&sMove, i, x, y, x2, y2, PROPULSION_TYPE_WHEELED, DROID_WEAPON);
assert(r == FPR_WAIT);
}
while (fpathResultQueueLength() != 100) usleep(1);
assert(fpathJobQueueLength() == 0);
for (i = 1; i <= 100; i++)
{
sMove.Status = MOVEWAITROUTE;
r = fpathRoute(&sMove, i, x, y, x2, y2, PROPULSION_TYPE_WHEELED, DROID_WEAPON);
assert(r == FPR_OK);
assert(sMove.numPoints > 0 && sMove.asPath);
assert(sMove.asPath[sMove.numPoints - 1].x == map_coord(x2));
assert(sMove.asPath[sMove.numPoints - 1].y == map_coord(y2));
}
assert(fpathResultQueueLength() == 0);
/* Kill a hundred flowers */
sMove.Status = MOVEINACTIVE;
for (i = 1; i <= 100; i++)
{
r = fpathRoute(&sMove, i, x, y, x2, y2, PROPULSION_TYPE_WHEELED, DROID_WEAPON);
assert(r == FPR_WAIT);
}
for (i = 1; i <= 100; i++)
{
fpathRemoveDroidData(i);
}
assert(fpathJobQueueLength() == 0);
assert(fpathResultQueueLength() == 0);
assert(firstJob == NULL);
assert(firstResult == NULL);
}

View File

@ -34,8 +34,7 @@ typedef enum _fpath_retval
{
FPR_OK, ///< found a route
FPR_FAILED, ///< failed to find a route
FPR_WAIT, ///< route was too long to calculate this frame, routing will continue on succeeding frames
FPR_RESCHEDULE, ///< didn't try to route because too much time has been spent on routing this frame
FPR_WAIT, ///< route is being calculated by the path-finding thread
} FPATH_RETVAL;
/** Initialise the path-finding module.
@ -56,7 +55,7 @@ extern void fpathUpdate(void);
*
* @ingroup pathfinding
*/
extern FPATH_RETVAL fpathRoute(DROID* psDroid, SDWORD targetX, SDWORD targetY);
extern FPATH_RETVAL fpathDroidRoute(DROID* psDroid, SDWORD targetX, SDWORD targetY);
/** Function pointer to the currently in-use blocking tile check function.
*
@ -75,10 +74,16 @@ extern BOOL fpathBlockingTile(SDWORD x, SDWORD y, PROPULSION_TYPE propulsion);
* Plan a path from @c psDroid's current position to given position without
* taking obstructions into consideration.
*
* Used for instance by VTOLs.
* Used for instance by VTOLs. Function is thread-safe.
*
* @ingroup pathfinding
*/
extern void fpathSetDirectRoute(DROID* psDroid, SDWORD targetX, SDWORD targetY);
/** Clean up path jobs and results for a droid. Function is thread-safe. */
extern void fpathRemoveDroidData(int id);
// Unit testing
void fpathTest(int x, int y, int x2, int y2);
#endif // __INCLUDED_SRC_FPATH_H__

View File

@ -42,6 +42,7 @@
#include "game.h"
#include "fpath.h"
#include "map.h"
#include "droid.h"
#include "action.h"
@ -5746,6 +5747,14 @@ static void LoadDroidMoveControl(DROID * const psDroid, SAVE_DROID const * const
}
}
}
// Recreate path-finding jobs
if (psDroid->sMove.Status == MOVEWAITROUTE)
{
psDroid->sMove.Status = MOVEINACTIVE;
fpathDroidRoute(psDroid, psDroid->sMove.DestinationX, psDroid->sMove.DestinationY);
psDroid->sMove.Status = MOVEWAITROUTE;
}
}
// -----------------------------------------------------------------------------------------

View File

@ -1509,33 +1509,27 @@ static void astarTest(const char *name, int x1, int y1, int x2, int y2)
int endy = world_coord(y2);
clock_t stop;
clock_t start = clock();
int iterations;
bool retval;
retval = levLoadData(name, NULL, 0);
ASSERT(retval, "Could not load %s", name);
fpathInitialise();
route.asPath = NULL;
for (i = 0; i < 100; i++)
{
iterations = 1;
route.numPoints = 0;
astarResetCounters();
ASSERT(astarInner == 0, "astarInner not reset");
asret = fpathAStarRoute(ASR_NEWROUTE, &route, x, y, endx, endy, PROPULSION_TYPE_WHEELED);
while (asret == ASR_PARTIAL)
{
astarResetCounters();
ASSERT(astarInner == 0, "astarInner not reset");
asret = fpathAStarRoute(ASR_CONTINUE, &route, x, y, endx, endy, PROPULSION_TYPE_WHEELED);
iterations++;
}
asret = fpathAStarRoute(&route, x, y, endx, endy, PROPULSION_TYPE_WHEELED);
free(route.asPath);
route.asPath = NULL;
}
stop = clock();
fprintf(stdout, "\t\tPath-finding timing %s: %.02f (%d nodes, %d iterations)\n", name,
(double)(stop - start) / (double)CLOCKS_PER_SEC, route.numPoints, iterations);
fprintf(stdout, "\t\tA* timing %s: %.02f (%d nodes)\n", name,
(double)(stop - start) / (double)CLOCKS_PER_SEC, route.numPoints);
start = clock();
fpathTest(x, y, endx, endy);
stop = clock();
fprintf(stdout, "\t\tfPath timing %s: %.02f (%d nodes)\n", name,
(double)(stop - start) / (double)CLOCKS_PER_SEC, route.numPoints);
retval = levReleaseAll();
assert(retval);
}

View File

@ -205,10 +205,6 @@ static UDWORD baseTimes[BASE_FRAMES];
/* The current base turn rate */
static float baseTurn;
// The next DROID that should get the router when a lot of units are
// in a MOVEROUTE state
DROID *psNextRouteDroid;
/* Function prototypes */
static void moveUpdatePersonModel(DROID *psDroid, SDWORD speed, SDWORD direction);
// Calculate the boundary vector
@ -268,8 +264,6 @@ BOOL moveInitialise(void)
baseTimes[i] = GAME_TICKS_PER_SEC / BASE_DEF_RATE;
}
psNextRouteDroid = NULL;
return true;
}
@ -301,19 +295,6 @@ void moveUpdateBaseSpeed(void)
// reset the astar counters
astarResetCounters();
// check the waiting droid pointer
if (psNextRouteDroid != NULL)
{
if ((psNextRouteDroid->died) ||
((psNextRouteDroid->sMove.Status != MOVEROUTE) &&
(psNextRouteDroid->sMove.Status != MOVEROUTESHUFFLE)))
{
objTrace(psNextRouteDroid->id, "Waiting droid %d (player %d) reset",
(int)psNextRouteDroid->id, (int)psNextRouteDroid->player);
psNextRouteDroid = NULL;
}
}
}
/** Set a target location in world coordinates for a droid to move to
@ -351,7 +332,7 @@ static BOOL moveDroidToBase(DROID *psDroid, UDWORD x, UDWORD y, BOOL bFormation)
}
else
{
retVal = fpathRoute(psDroid, x, y);
retVal = fpathDroidRoute(psDroid, x, y);
}
/* check formations */
@ -373,14 +354,6 @@ static BOOL moveDroidToBase(DROID *psDroid, UDWORD x, UDWORD y, BOOL bFormation)
psDroid->sMove.fy = psDroid->pos.y;
psDroid->sMove.fz = psDroid->pos.z;
// reset the next route droid
if (psDroid == psNextRouteDroid)
{
objTrace(psDroid->id, "Waiting droid %d (player %d) got route",
(int)psDroid->id, (int)psDroid->player);
psNextRouteDroid = NULL;
}
// leave any old formation
if (psDroid->sMove.psFormation)
{
@ -428,42 +401,9 @@ static BOOL moveDroidToBase(DROID *psDroid, UDWORD x, UDWORD y, BOOL bFormation)
}
}
}
else if (retVal == FPR_RESCHEDULE)
{
objTrace(psDroid->id, "moveDroidToBase(%d): out of time, not our turn; rescheduled", (int)psDroid->id);
// maxed out routing time this frame - do it next time
psDroid->sMove.DestinationX = x;
psDroid->sMove.DestinationY = y;
if ((psDroid->sMove.Status != MOVEROUTE) &&
(psDroid->sMove.Status != MOVEROUTESHUFFLE))
{
objTrace(psDroid->id, "moveDroidToBase(%d): started waiting at %d",
(int)psDroid->id, (int)gameTime);
psDroid->sMove.Status = MOVEROUTE;
// note when the unit first tried to route
psDroid->sMove.bumpTime = gameTime;
}
}
else if (retVal == FPR_WAIT)
{
// reset the next route droid
if (psDroid == psNextRouteDroid)
{
objTrace(psDroid->id, "moveDroidToBase(%d): out of time, waiting for next frame (we are next)",
(int)psDroid->id);
psNextRouteDroid = NULL;
}
else
{
objTrace(psDroid->id, "moveDroidToBase(%d): out of time, waiting for next frame (we are not next)",
(int)psDroid->id);
}
// the route will be calculated over a number of frames
// the route will be calculated by the path-finding thread
psDroid->sMove.Status = MOVEWAITROUTE;
psDroid->sMove.DestinationX = x;
psDroid->sMove.DestinationY = y;
@ -3070,45 +3010,6 @@ void moveUpdateDroid(DROID *psDroid)
// here because droids waiting for a route need to shuffle out of the way (MOVEROUTESHUFFLE)
// of those that have already got a route
if ((psDroid->sMove.Status == MOVEROUTE) ||
(psDroid->sMove.Status == MOVEROUTESHUFFLE))
{
// see if this droid started waiting for a route before the previous one
// and note it to be the next droid to route.
// selectedPlayer always gets precidence in single player
if (psNextRouteDroid == NULL)
{
objTrace(psDroid->id, "Waiting droid set to %d (player %d) started at %d now %d (none waiting)",
(int)psDroid->id, (int)psDroid->player, (int)psDroid->sMove.bumpTime, (int)gameTime);
psNextRouteDroid = psDroid;
}
else if (bMultiPlayer &&
(psNextRouteDroid->sMove.bumpTime > psDroid->sMove.bumpTime))
{
objTrace(psDroid->id, "Waiting droid set to %d (player %d) started at %d now %d (mulitplayer)",
(int)psDroid->id, (int)psDroid->player, (int)psDroid->sMove.bumpTime, (int)gameTime);
psNextRouteDroid = psDroid;
}
else if ( (psDroid->player == selectedPlayer) &&
( (psNextRouteDroid->player != selectedPlayer) ||
(psNextRouteDroid->sMove.bumpTime > psDroid->sMove.bumpTime) ) )
{
objTrace(psDroid->id, "Waiting droid set to %d (player %d) started at %d now %d (selectedPlayer)",
(int)psDroid->id, (int)psDroid->player, (int)psDroid->sMove.bumpTime, (int)gameTime);
psNextRouteDroid = psDroid;
}
else if ( (psDroid->player != selectedPlayer) &&
(psNextRouteDroid->player != selectedPlayer) &&
(psNextRouteDroid->sMove.bumpTime > psDroid->sMove.bumpTime) )
{
objTrace(psDroid->id, "Waiting droid set to %d (player %d) started at %d now %d (non selectedPlayer)",
(int)psDroid->id, (int)psDroid->player, (int)psDroid->sMove.bumpTime, (int)gameTime);
psNextRouteDroid = psDroid;
}
}
if ((psDroid->sMove.Status == MOVEROUTE) ||
(psDroid->sMove.Status == MOVEROUTESHUFFLE))
{