diff options
Diffstat (limited to 'arts/builder/autorouter.cpp')
-rw-r--r-- | arts/builder/autorouter.cpp | 609 |
1 files changed, 609 insertions, 0 deletions
diff --git a/arts/builder/autorouter.cpp b/arts/builder/autorouter.cpp new file mode 100644 index 00000000..bc69a7ee --- /dev/null +++ b/arts/builder/autorouter.cpp @@ -0,0 +1,609 @@ + /* + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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; + } + } + } +} |