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.
tdemultimedia/arts/builder/autorouter.cpp

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;
}
}
}
}