You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
610 lines
13 KiB
610 lines
13 KiB
/*
|
|
|
|
Copyright (C) 1998 Stefan Westerfeld <stefan@space.twc.de>,
|
|
2002 Hans Meine <hans_meine@gmx.net>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
#include "autorouter.h"
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <arts/debug.h>
|
|
|
|
#include <kdebug.h>
|
|
|
|
#ifndef HAVE_LIBPTHREAD
|
|
#define pthread_create(a, b, c, d) ;
|
|
#define pthread_join(a, b) ;
|
|
#define pthread_mutex_lock(a) ;
|
|
#define pthread_mutex_unlock(a) ;
|
|
#define pthread_mutex_init(a, b) ;
|
|
#define pthread_attr_init(a) ;
|
|
#endif
|
|
|
|
using namespace std;
|
|
|
|
const int COST_INIT = 200000;
|
|
const int PENALTY_OTHERCONNECTION = 100;
|
|
const int PENALTY_CORNER = 100;
|
|
const int IDLE_SLEEP_MILLISECONDS = 40;
|
|
|
|
void *startroute(void *where)
|
|
{
|
|
((AutoRouter *)where)->thread_command_loop();
|
|
// just to prevent the compiler warning
|
|
return 0;
|
|
}
|
|
|
|
bool ARCommand::isDestructive()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
AutoRouter::AutoRouter(int width, int height)
|
|
: width(width), height(height)
|
|
{
|
|
#ifdef HAVE_LIBPTHREAD
|
|
pthread_attr_t attrs;
|
|
|
|
pthread_attr_init(&attrs);
|
|
pthread_mutex_init(&mutex_sync, 0);
|
|
pthread_mutex_init(&mutex_queue, 0);
|
|
#endif
|
|
|
|
// allocate memory for fields..
|
|
field = new Field*[width];
|
|
completeField = new Field*[width];
|
|
|
|
for(int i = 0; i < width; i++)
|
|
{
|
|
field[i] = new Field[height];
|
|
completeField[i] = new Field[height];
|
|
}
|
|
|
|
// ..and clear the freshly allocated memory
|
|
thread_clear();
|
|
thread_sync();
|
|
|
|
newOwner = 1;
|
|
|
|
// init those two arrays - they should be constant somehow..
|
|
directionMask[DIR_DOWN] = ~(left | right);
|
|
directionMask[DIR_UP] = ~(left | right);
|
|
directionMask[DIR_LEFT] = ~(up | down);
|
|
directionMask[DIR_RIGHT] = ~(up | down);
|
|
|
|
ownerIndex[DIR_DOWN] = OWNER_UD;
|
|
ownerIndex[DIR_UP] = OWNER_UD;
|
|
ownerIndex[DIR_LEFT] = OWNER_LR;
|
|
ownerIndex[DIR_RIGHT] = OWNER_LR;
|
|
|
|
initPseudoRandom();
|
|
|
|
thread_terminate_now = false; // not yet
|
|
#ifdef HAVE_LIBPTHREAD
|
|
// setup thread and run thread_command_loop() in it
|
|
pthread_create(&route_thread, &attrs, startroute, this);
|
|
#endif
|
|
|
|
arts_debug("AR UP...");
|
|
}
|
|
|
|
void AutoRouter::initPseudoRandom()
|
|
{
|
|
// init pseudoRandomDir array with random directions
|
|
|
|
int remainingDirs[4];
|
|
for(int i = 0; i < PRSIZE; i++)
|
|
{
|
|
if((i & 3) == 0)
|
|
{
|
|
int j = 0;
|
|
for(int dir = DIR_UP; dir <= DIR_RIGHT; dir++)
|
|
remainingDirs[j++] = dir;
|
|
}
|
|
|
|
int rnd;
|
|
do rnd = rand()&3;
|
|
while(remainingDirs[rnd] == DIR_NONE);
|
|
|
|
pseudoRandomDir[i] = remainingDirs[rnd];
|
|
|
|
remainingDirs[rnd] = DIR_NONE;
|
|
}
|
|
nextPR = 0;
|
|
}
|
|
|
|
AutoRouter::~AutoRouter()
|
|
{
|
|
// tell the thread to shut down
|
|
pthread_mutex_lock(&mutex_queue);
|
|
thread_terminate_now = true;
|
|
pthread_mutex_unlock(&mutex_queue);
|
|
|
|
// terminate thread
|
|
void *rc;
|
|
pthread_join(route_thread, &rc);
|
|
|
|
// clean up
|
|
for(int i = 0; i < width; i++)
|
|
{
|
|
delete[] field[i];
|
|
delete[] completeField[i];
|
|
}
|
|
delete[] completeField;
|
|
delete[] field;
|
|
|
|
arts_debug("AR DOWN...");
|
|
}
|
|
|
|
void AutoRouter::enqueue(ARCommand *command)
|
|
{
|
|
#ifdef HAVE_LIBPTHREAD
|
|
// threaded execution, locking the queue
|
|
pthread_mutex_lock(&mutex_queue);
|
|
|
|
if(command->isDestructive())
|
|
{
|
|
// ok, then we can kill the whole list, since this will clear
|
|
// the whole results anyway
|
|
|
|
command_queue.setAutoDelete(true);
|
|
command_queue.clear();
|
|
command_queue.setAutoDelete(false);
|
|
}
|
|
command_queue.append(command);
|
|
|
|
pthread_mutex_unlock(&mutex_queue);
|
|
#else
|
|
// immediate execution
|
|
command->execute(this);
|
|
delete command;
|
|
#endif
|
|
}
|
|
|
|
void AutoRouter::thread_command_loop()
|
|
{
|
|
while(1)
|
|
{
|
|
ARCommand *command = 0;
|
|
|
|
// pop one command from queue if possible..
|
|
pthread_mutex_lock(&mutex_queue);
|
|
if(!command_queue.isEmpty())
|
|
{
|
|
command = command_queue.first();
|
|
command_queue.remove(unsigned(0));
|
|
}
|
|
else
|
|
{
|
|
if (thread_terminate_now) {
|
|
pthread_mutex_unlock(&mutex_queue);
|
|
return;
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&mutex_queue);
|
|
|
|
// ..and execute command if we got one
|
|
if(command)
|
|
{
|
|
command->execute(this);
|
|
delete command;
|
|
}
|
|
else // no more commands->wait for some milliseconds
|
|
usleep(1000 * IDLE_SLEEP_MILLISECONDS);
|
|
// TODO: use pthread_cond_wait here instead of half-busy-waiting?
|
|
}
|
|
}
|
|
|
|
// ------------------------- getXXX() handling -------------------------
|
|
|
|
long AutoRouter::get(int x, int y)
|
|
{
|
|
assert(x >= 0 && x < width);
|
|
assert(y >= 0 && y < height);
|
|
|
|
pthread_mutex_lock(&mutex_sync);
|
|
long result = completeField[x][y].data;
|
|
pthread_mutex_unlock(&mutex_sync);
|
|
|
|
return(result);
|
|
}
|
|
|
|
void AutoRouter::getowners(int x, int y, long& ud_owner, long& lr_owner)
|
|
{
|
|
assert(x >= 0 && x < width);
|
|
assert(y >= 0 && y < height);
|
|
|
|
pthread_mutex_lock(&mutex_sync);
|
|
ud_owner = completeField[x][y].owner[OWNER_UD];
|
|
lr_owner = completeField[x][y].owner[OWNER_LR];
|
|
pthread_mutex_unlock(&mutex_sync);
|
|
}
|
|
|
|
// ------------------------- sync() handling -------------------------
|
|
|
|
void AutoRouter::sync()
|
|
{
|
|
enqueue(new ARSyncCommand());
|
|
}
|
|
|
|
void ARSyncCommand::execute(AutoRouter *router)
|
|
{
|
|
router->thread_sync();
|
|
}
|
|
|
|
void AutoRouter::thread_sync()
|
|
{
|
|
int i;
|
|
pthread_mutex_lock(&mutex_sync);
|
|
|
|
for(i = 0; i < width; i++)
|
|
memcpy(completeField[i], field[i], sizeof(Field)*height);
|
|
_needRedraw = true;
|
|
|
|
pthread_mutex_unlock(&mutex_sync);
|
|
}
|
|
|
|
bool AutoRouter::needRedraw()
|
|
{
|
|
bool result;
|
|
|
|
pthread_mutex_lock(&mutex_sync);
|
|
result = _needRedraw;
|
|
_needRedraw = false;
|
|
#ifdef AR_DEBUG
|
|
if(result) arts_debug("NEED REDRAW NOW!");
|
|
#endif
|
|
pthread_mutex_unlock(&mutex_sync);
|
|
|
|
return result;
|
|
}
|
|
|
|
// ------------------------- clear() handling -------------------------
|
|
|
|
void AutoRouter::clear()
|
|
{
|
|
enqueue(new ARClearCommand());
|
|
}
|
|
|
|
bool ARClearCommand::isDestructive()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void ARClearCommand::execute(AutoRouter *router)
|
|
{
|
|
router->thread_clear();
|
|
}
|
|
|
|
void AutoRouter::thread_clear()
|
|
{
|
|
arts_debug("clear()ing now...");
|
|
int x, y;
|
|
|
|
for(x = 0; x < width; x++)
|
|
for(y = 0; y < height; y++)
|
|
{
|
|
field[x][y].data = none;
|
|
field[x][y].penalty = 0;
|
|
field[x][y].owner[0] = -1;
|
|
field[x][y].owner[1] = -1;
|
|
}
|
|
}
|
|
|
|
// ------------------------- set() command handling -------------------------
|
|
|
|
void AutoRouter::set(int x1, int y1, int x2, int y2, long lt)
|
|
{
|
|
enqueue(new ARSetCommand(x1, y1, x2, y2, lt));
|
|
}
|
|
|
|
ARSetCommand::ARSetCommand(int x1, int y1, int x2, int y2, long lt)
|
|
: _x1(x1), _y1(y1), _x2(x2), _y2(y2), _lt(lt)
|
|
{
|
|
}
|
|
|
|
void ARSetCommand::execute(AutoRouter *router)
|
|
{
|
|
router->thread_set(_x1, _y1, _x2, _y2, _lt);
|
|
}
|
|
|
|
void AutoRouter::thread_set(int x1, int y1, int x2, int y2, long lt)
|
|
{
|
|
for(int x = x1; x <= x2; x++)
|
|
{
|
|
for(int y = y1; y <= y2; y++)
|
|
{
|
|
assert(x >= 0 && x < width);
|
|
assert(y >= 0 && y < height);
|
|
|
|
if(lt & solid)
|
|
{
|
|
if((y - 1) >= 0)
|
|
field[x][y - 1].penalty += 5;
|
|
|
|
if((y - 2) >= 0)
|
|
field[x][y - 2].penalty += 2;
|
|
|
|
if((y + 1) < height)
|
|
field[x][y + 1].penalty += 5;
|
|
|
|
if((y + 2) < height)
|
|
field[x][y + 2].penalty += 2;
|
|
}
|
|
|
|
field[x][y].data = lt;
|
|
field[x][y].owner[0] = 0;
|
|
field[x][y].owner[1] = 0; // don't pass
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
long AutoRouter::connect(int x1, int y1, int x2, int y2, long owner)
|
|
{
|
|
if(owner == 0)
|
|
owner = newOwner++;
|
|
|
|
enqueue(new ARConnectCommand(x1, y1, x2, y2, owner));
|
|
|
|
return owner;
|
|
}
|
|
|
|
ARConnectCommand::ARConnectCommand(int x1, int y1, int x2, int y2, long owner)
|
|
{
|
|
_x1 = x1; _y1 = y1; _x2 = x2; _y2 = y2; _owner = owner;
|
|
}
|
|
|
|
void ARConnectCommand::execute(AutoRouter *router)
|
|
{
|
|
router->thread_connect(_x1, _y1, _x2, _y2, _owner);
|
|
}
|
|
|
|
void AutoRouter::thread_connect(int x1, int y1, int x2, int y2, long owner)
|
|
{
|
|
currentOwner = owner;
|
|
|
|
#ifdef AR_DEBUG
|
|
arts_debug("-field[x1][y1].owner[0..1] = %ld, %ld",field[x1][y1].owner[0],
|
|
field[x1][y1].owner[1]);
|
|
arts_debug("-field[x2][y2].owner[0..1] = %ld, %ld", field[x2][y2].owner[0],
|
|
field[x2][y2].owner[1]);
|
|
#endif
|
|
|
|
// clear data(source) & data(dest) first and restore later, since
|
|
// they might be solid
|
|
long sourceFieldData = field[x1][y1].data; field[x1][y1].data = none;
|
|
long destFieldData = field[x2][y2].data; field[x2][y2].data = none;
|
|
|
|
for(int x = 0; x < width; x++)
|
|
for(int y = 0; y < height; y++)
|
|
field[x][y].minCost = COST_INIT;
|
|
|
|
#ifdef AR_DEBUG
|
|
arts_debug("autorouter: trying to connect %d, %d with %d, %d (owner %ld)",
|
|
x1, y1, x2, y2, owner);
|
|
#endif
|
|
nextPR = 0;
|
|
|
|
bestGoalPath.cost = COST_INIT;
|
|
|
|
int activelist = 0;
|
|
numQueuedPaths = 0;
|
|
|
|
PathInfo path;
|
|
path.x1 = x1;
|
|
path.x2 = x2;
|
|
path.y1 = y1;
|
|
path.y2 = y2;
|
|
path.cost = 0;
|
|
path.depth = 0;
|
|
queuePath(path);
|
|
|
|
while(numQueuedPaths)
|
|
{
|
|
while(!pathlist[activelist].size())
|
|
activelist++;
|
|
PathQueue& activePathList =
|
|
pathlist[activelist];
|
|
|
|
assert(activePathList.size());
|
|
examinePath(activePathList.first());
|
|
activePathList.pop_front();
|
|
numQueuedPaths--;
|
|
}
|
|
|
|
field[x1][y1].data = sourceFieldData;
|
|
field[x2][y2].data = destFieldData;
|
|
|
|
if(bestGoalPath.cost != COST_INIT)
|
|
{
|
|
//arts_debug("bestGoalPath.history for this connection is %s", bestGoalPath.history.data());
|
|
//arts_debug("minCost for that was %d", gms);
|
|
|
|
const char *walk = bestGoalPath.history.ascii();
|
|
|
|
int x = x1;
|
|
int y = y1;
|
|
|
|
while(*walk)
|
|
{
|
|
field[x][y].owner[ownerIndex[*walk]] = currentOwner;
|
|
switch(*walk)
|
|
{
|
|
case DIR_DOWN:
|
|
field[x][y++].data |= down;
|
|
field[x][y].data |= up;
|
|
break;
|
|
case DIR_UP:
|
|
field[x][y--].data |= up;
|
|
field[x][y].data |= down;
|
|
break;
|
|
case DIR_LEFT:
|
|
field[x--][y].data |= left;
|
|
field[x][y].data |= right;
|
|
break;
|
|
case DIR_RIGHT:
|
|
field[x++][y].data |= right;
|
|
field[x][y].data |= left;
|
|
break;
|
|
}
|
|
field[x][y].owner[ownerIndex[*walk]] = currentOwner;
|
|
walk++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#ifdef AR_DEBUG
|
|
arts_debug("!! sorry, this connection is impossible !!");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void AutoRouter::queuePath(const PathInfo &path)
|
|
{
|
|
PathInfo newPath = path;
|
|
|
|
int targetlist = newPath.cost/5;
|
|
if(targetlist > 1023)
|
|
targetlist = 1023;
|
|
|
|
pathlist[targetlist].append(newPath);
|
|
qHeapSort(pathlist[targetlist]);
|
|
|
|
numQueuedPaths++;
|
|
}
|
|
|
|
void AutoRouter::examinePath(const PathInfo &path)
|
|
{
|
|
const char *walk = path.history.ascii();
|
|
|
|
// check if we can go here:
|
|
|
|
if(path.x1 < 0 || path.x1 >= width)
|
|
return;
|
|
if(path.y1 < 0 || path.y1 >= width)
|
|
return;
|
|
|
|
int currentFieldCost = path.cost;
|
|
|
|
if(path.depth > 0)
|
|
{
|
|
// going over a field where already connections are is bad
|
|
if(field[path.x1][path.y1].data != 0)
|
|
currentFieldCost += PENALTY_OTHERCONNECTION;
|
|
|
|
if(directionMask[walk[path.depth - 1]] & field[path.x1][path.y1].data)
|
|
{
|
|
// someone already uses that field... we can continue
|
|
// only if the connection has the same sourceport
|
|
|
|
long fieldowner = field[path.x1][path.y1].owner[ownerIndex[walk[path.depth - 1]]];
|
|
|
|
if(fieldowner != -1) // used?
|
|
{
|
|
if(fieldowner != currentOwner)
|
|
return;
|
|
|
|
// oops, the connections are from the same owner -> no
|
|
// penalty needed!
|
|
currentFieldCost -= PENALTY_OTHERCONNECTION;
|
|
}
|
|
}
|
|
}
|
|
|
|
// add cityblock distance to costs
|
|
currentFieldCost += abs(path.x1 - path.x2) + abs(path.y1 - path.y2);
|
|
|
|
// add field penalty to costs
|
|
currentFieldCost += field[path.x1][path.y1].penalty;
|
|
|
|
// add corner penalty to costs if path had corner here
|
|
if(path.depth > 2)
|
|
if(walk[path.depth - 2] != walk[path.depth - 1])
|
|
currentFieldCost += PENALTY_CORNER;
|
|
|
|
if(currentFieldCost > bestGoalPath.cost)
|
|
return;
|
|
|
|
// better path found?
|
|
if(currentFieldCost < field[path.x1][path.y1].minCost)
|
|
{
|
|
field[path.x1][path.y1].minCost = currentFieldCost;
|
|
|
|
// check if we are where we wanted to be:
|
|
if(path.x1 == path.x2 && path.y1 == path.y2) {
|
|
// goal! success! :-)
|
|
|
|
if(path.cost < bestGoalPath.cost)
|
|
{
|
|
bestGoalPath = path; // best solution until now
|
|
bestGoalPath.cost = currentFieldCost;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// not at the goal yet, search next place to go; take some
|
|
// pseudo random direction order (this method improves search
|
|
// speed)
|
|
|
|
PathInfo newPath = path;
|
|
newPath.depth++;
|
|
newPath.cost = currentFieldCost;
|
|
|
|
for(int i = 0; i < 4; i++)
|
|
{
|
|
if(nextPR >= PRSIZE)
|
|
nextPR = 0;
|
|
|
|
switch(pseudoRandomDir[nextPR++])
|
|
{
|
|
case DIR_LEFT:
|
|
newPath.x1--;
|
|
newPath.history = path.history + (char)DIR_LEFT;
|
|
queuePath(newPath);
|
|
newPath.x1++;
|
|
break;
|
|
|
|
case DIR_RIGHT:
|
|
newPath.x1++;
|
|
newPath.history = path.history + (char)DIR_RIGHT;
|
|
queuePath(newPath);
|
|
newPath.x1--;
|
|
break;
|
|
|
|
case DIR_UP:
|
|
newPath.y1--;
|
|
newPath.history = path.history + (char)DIR_UP;
|
|
queuePath(newPath);
|
|
newPath.y1++;
|
|
break;
|
|
|
|
case DIR_DOWN:
|
|
newPath.y1++;
|
|
newPath.history = path.history + (char)DIR_DOWN;
|
|
queuePath(newPath);
|
|
newPath.y1--;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|