@@ -16,202 +16,193 @@ import de.srsoftware.web4rail.moving.Train;
import de.srsoftware.web4rail.tiles.Block ;
import de.srsoftware.web4rail.tiles.Tile ;
public class RouteManager extends BaseClass implements Runnable {
enum State {
ENDED , IDLE , STARTED ;
}
private static final Logger LOG = LoggerFactory . getLogger ( RouteManager . class ) ;
private static final int DEFAULT_PAUSE_TIME = 250 ; // ms
private State state = State . IDLE ;
private boolean autopilot ;
private Context context ;
private int time = 0 ;
public class RouteManager extends BaseClass {
public RouteManager ( Train train ) {
context = new Context ( train ) ;
state = State . STARTED ;
public static abstract class Callback {
public abstract void routePrepared ( Route route ) ;
}
private static TreeMap < Inte ger, List < Route > > availableRoutes ( Context context , HashSet < Route > visitedRoute s ) {
private static final Log ger LOG = LoggerFactory . getLogger ( RouteManager . clas s ) ;
private Context ctx ;
private Callback callback ;
private boolean active ;
public RouteManager ( ) {
active = false ;
}
private static TreeMap < Integer , List < Route > > availableRoutes ( Context context , HashSet < Route > visitedRoutes ) {
String inset = " " ;
for ( int i = 0 ; i < visitedRoutes . size ( ) ; i + + ) inset + = " " ;
LOG . debug ( " {}{}.availableRoutes({}) " , inset , RouteManager . class . getSimpleName ( ) , context ) ;
for ( int i = 0 ; i < visitedRoutes . size ( ) ; i + + ) inset + = " " ;
LOG . debug ( " {}{}.availableRoutes({}) " , inset , RouteManager . class . getSimpleName ( ) , context ) ;
Block block = context . block ( ) ;
Train train = context . train ( ) ;
Direction startDirection = context . direction ( ) ;
Route currentRoute = context . route ( ) ;
TreeMap < Integer , List < Route > > availableRoutes = new TreeMap < Integer , List < Route > > ( ) ;
TreeMap < Integer , List < Route > > availableRoutes = new TreeMap < Integer , List < Route > > ( ) ;
boolean error = false ;
if ( isNull ( block ) & & ( error = true ) ) LOG . warn ( " {} → {}.availableRoutes called without context.block! " , inset , Train . class . getSimpleName ( ) ) ;
if ( isNull ( train ) & & ( error = true ) ) LOG . warn ( " {}→ {}.availableRoutes called without context.train ! " , inset , Train . class . getSimpleName ( ) ) ;
if ( isNull ( block ) & & ( error = true ) )
LOG . warn ( " {} → {}.availableRoutes called without context.block ! " , inset , Train . class . getSimpleName ( ) ) ;
if ( isNull ( train ) & & ( error = true ) )
LOG . warn ( " {}→ {}.availableRoutes called without context.train! " , inset , Train . class . getSimpleName ( ) ) ;
if ( error ) return availableRoutes ;
Block destination = train . destination ( ) ;
if ( isSet ( startDirection ) ) {
LOG . debug ( " {}- Looking for {}-bound routes from {} " , inset , startDirection , block ) ;
LOG . debug ( " {}- Looking for {}-bound routes from {} " , inset , startDirection , block ) ;
} else {
LOG . debug ( " {}- Looking for all routes from {} " , inset , block ) ;
LOG . debug ( " {}- Looking for all routes from {} " , inset , block ) ;
}
if ( isSet ( destination ) & & visitedRoutes . isEmpty ( ) ) LOG . debug ( " {}- Destination: {} " , inset , destination ) ;
if ( isSet ( destination ) & & visitedRoutes . isEmpty ( ) ) LOG . debug ( " {}- Destination: {} " , inset , destination ) ;
for ( Route routeCandidate : block . leavingRoutes ( ) ) {
if ( context . invalidated ( ) ) return availableRoutes ;
if ( visitedRoutes . contains ( routeCandidate ) ) {
LOG . debug ( " {}→ Candidate {} would create loop, skipping " , inset , routeCandidate . shortName ( ) ) ;
LOG . debug ( " {}→ Candidate {} would create loop, skipping " , inset , routeCandidate . shortName ( ) ) ;
continue ;
}
HashSet < Tile > stuckTrace = train . stuckTrace ( ) ; // if train has been stopped in between two blocks lastly: only allow routes that do not conflict with current train position
HashSet < Tile > stuckTrace = train . stuckTrace ( ) ; // if train has been stopped in between two blocks lastly:
// only allow routes that do not conflict with current train
// position
if ( isSet ( stuckTrace ) & & ! routeCandidate . path ( ) . containsAll ( stuckTrace ) ) {
LOG . debug ( " Stuck train occupies tiles ({}) outside of {} – not allowed. " , stuckTrace , routeCandidate ) ;
LOG . debug ( " Stuck train occupies tiles ({}) outside of {} – not allowed. " , stuckTrace , routeCandidate ) ;
continue ;
}
if ( ! routeCandidate . allowed ( context ) ) {
if ( ! routeCandidate . allowed ( context ) ) {
if ( routeCandidate . endBlock ( ) ! = destination ) { // allowance may be overridden by destination
LOG . debug ( " {} not allowed for {} " , routeCandidate , context ) ;
LOG . debug ( " {} not allowed for {} " , routeCandidate , context ) ;
continue ; // Zug darf auf Grund einer nicht erfüllten Bedingung nicht auf die Route
}
LOG . debug ( " {} not allowed for {} – overridden by selected destination " , routeCandidate , context ) ;
LOG . debug ( " {} not allowed for {} – overridden by selected destination " , routeCandidate , context ) ;
}
int priority = 0 ;
if ( isSet ( startDirection ) & & routeCandidate . startDirection ! = startDirection ) { // Route startet entgegen der aktuellen Fahrtrichtung des Zuges
if ( isSet ( startDirection ) & & routeCandidate . startDirection ! = startDirection ) { // Route startet entgegen
// der aktuellen
// Fahrtrichtung des Zuges
if ( ! train . pushPull ) continue ; // Zug kann nicht wenden
if ( ! block . turnAllowed ) continue ; // Wenden im Block nicht gestattet
priority - = 5 ;
}
if ( routeCandidate = = currentRoute ) priority - = 10 ; // möglichst andere Route als zuvor wählen // TODO: den Routen einen "last-used" Zeitstempel hinzufügen, und diesen mit in die Priorisierung einbeziehen
if ( routeCandidate = = currentRoute ) priority - = 10 ; // möglichst andere Route als zuvor wählen // TODO: den
// Routen einen "last-used" Zeitstempel hinzufügen, und
// diesen mit in die Priorisierung einbeziehen
if ( isSet ( destination ) ) {
if ( routeCandidate . endBlock ( ) = = destination ) { // route goes directly to destination
LOG . debug ( " {}→ Candidate {} directly leads to {} " , inset , routeCandidate . shortName ( ) , destination ) ;
LOG . debug ( " {}→ Candidate {} directly leads to {} " , inset , routeCandidate . shortName ( ) , destination ) ;
priority = 1_000_000 ;
} else {
LOG . debug ( " {}- Candidate: {} " , inset , routeCandidate . shortName ( ) ) ;
Context forwardContext = new Context ( train ) . block ( routeCandidate . endBlock ( ) ) . route ( null ) . direction ( routeCandidate . endDirection ) ;
LOG . debug ( " {}- Candidate: {} " , inset , routeCandidate . shortName ( ) ) ;
Context forwardContext = new Context ( train ) . block ( routeCandidate . endBlock ( ) ) . route ( null )
. direction ( routeCandidate . endDirection ) ;
visitedRoutes . add ( routeCandidate ) ;
TreeMap < Integer , List < Route > > forwardRoutes = availableRoutes ( forwardContext , visitedRoutes ) ;
TreeMap < Integer , List < Route > > forwardRoutes = availableRoutes ( forwardContext , visitedRoutes ) ;
visitedRoutes . remove ( routeCandidate ) ;
if ( forwardRoutes . isEmpty ( ) ) continue ; // the candidate does not lead to a block, from which routes to the destination exist
if ( forwardRoutes . isEmpty ( ) ) continue ; // the candidate does not lead to a block, from which routes
// to the destination exist
Entry < Integer , List < Route > > entry = forwardRoutes . lastEntry ( ) ;
LOG . debug ( " {}→ The following routes have connections to {}: " , inset , destination ) ;
for ( Route rt : entry . getValue ( ) ) LOG . debug ( " {} - {} " , inset , rt . shortName ( ) ) ;
priority + = entry . getKey ( ) - 10 ;
LOG . debug ( " {}→ The following routes have connections to {}: " , inset , destination ) ;
for ( Route rt : entry . getValue ( ) ) LOG . debug ( " {} - {} " , inset , rt . shortName ( ) ) ;
priority + = entry . getKey ( ) - 10 ;
}
}
List < Route > routeSet = availableRoutes . get ( priority ) ;
if ( isNull ( routeSet ) ) {
routeSet = new Vector < Route > ( ) ;
availableRoutes . put ( priority , routeSet ) ;
}
routeSet . add ( routeCandidate ) ;
if ( routeCandidate . endBlock ( ) = = destination ) break ; // direct connection to destination discovered, quit search
if ( routeCandidate . endBlock ( ) = = destination ) break ; // direct connection to destination discovered, quit
// search
}
if ( ! availableRoutes . isEmpty ( ) ) LOG . debug ( " {}→ Routes from {}: {} " , inset , block , availableRoutes . isEmpty ( ) ? " none " : " " ) ;
if ( ! availableRoutes . isEmpty ( ) )
LOG . debug ( " {}→ Routes from {}: {} " , inset , block , availableRoutes . isEmpty ( ) ? " none " : " " ) ;
for ( Entry < Integer , List < Route > > entry : availableRoutes . entrySet ( ) ) {
LOG . debug ( " {} - Priority {}: " , inset , entry . getKey ( ) ) ;
for ( Route r : entry . getValue ( ) ) LOG . debug ( " {} - {} " , inset , r . shortName ( ) ) ;
LOG . debug ( " {} - Priority {}: " , inset , entry . getKey ( ) ) ;
for ( Route r : entry . getValue ( ) ) LOG . debug ( " {} - {} " , inset , r . shortName ( ) ) ;
}
return availableRoutes ;
}
public boolean autoEnabled ( ) {
return autopilot & & isActive ( ) ;
}
public static Route chooseRoute ( Context context ) {
LOG . debug ( " {}.chooseRoute({}) " , RouteManager . class . getSimpleName ( ) , context ) ;
TreeMap < Integer , List < Route > > availableRoutes = availableRoutes ( context , new HashSet < Route > ( ) ) ;
LOG . debug ( " {}.chooseRoute({}) " , RouteManager . class . getSimpleName ( ) , context ) ;
TreeMap < Integer , List < Route > > availableRoutes = availableRoutes ( context , new HashSet < Route > ( ) ) ;
while ( ! availableRoutes . isEmpty ( ) ) {
if ( context . invalidated ( ) ) break ;
LOG . debug ( " availableRoutes: {} " , availableRoutes ) ;
LOG . debug ( " availableRoutes: {} " , availableRoutes ) ;
Entry < Integer , List < Route > > entry = availableRoutes . lastEntry ( ) ;
List < Route > preferredRoutes = entry . getValue ( ) ;
LOG . debug ( " preferredRoutes: {} " , preferredRoutes ) ;
LOG . debug ( " preferredRoutes: {} " , preferredRoutes ) ;
Route selectedRoute = preferredRoutes . get ( random . nextInt ( preferredRoutes . size ( ) ) ) ;
if ( selectedRoute . isFreeFor ( context ) ) {
LOG . debug ( " Chose \" {} \" with priority {}. " , selectedRoute , entry . getKey ( ) ) ;
LOG . debug ( " Chose \" {} \" with priority {}. " , selectedRoute , entry . getKey ( ) ) ;
return selectedRoute ;
}
LOG . debug ( " Selected route \" {} \" is not free for {} " , selectedRoute , context ) ;
LOG . debug ( " Selected route \" {} \" is not free for {} " , selectedRoute , context ) ;
preferredRoutes . remove ( selectedRoute ) ;
if ( preferredRoutes . isEmpty ( ) ) availableRoutes . remove ( availableRoutes . lastKey ( ) ) ;
}
return null ;
}
public boolean isActive ( ) {
switch ( state ) {
case ENDED :
return false ;
case IDLE :
return false ;
default :
return true ;
}
}
private void pause ( ) {
if ( time = = 0 ) {
time = DEFAULT_PAUSE_TIME ;
} else sleep ( time ) ;
}
public void quit ( ) {
LOG . debug ( " {}.quit " , this ) ;
autopilot = false ;
context . invalidate ( ) ;
LOG . debug ( " {}.quit " , this ) ;
callback = null ;
if ( isSet ( ctx ) ) ctx . invalidate ( ) ;
}
@Override
public void run ( ) {
Train train = context . train ( ) ;
public Route prepareRoute ( Context context ) {
try {
do {
pause ( ) ;
if ( isSet ( train . route( )) ) continue ;
if ( context . invalida ted ( )) return ;
context . block ( train . currentBlock ( ) ) . direction ( train . direction ( ) ) ;
R oute route = chooseRoute ( context ) ;
if ( isNull ( route ) ) continue ;
context . route ( route ) ;
if ( ! route . reserveFor ( context ) ) {
route . reset ( ) ;
continue ;
}
if ( ! route . prepareAndLock ( ) ) {
route . reset ( ) ;
continue ;
}
// Route reserved, prepared and locked:
if ( ! route . start ( ) ) {
route . reset ( ) ;
continue ;
}
} while ( autopilot ) ;
if ( isNull ( context ) | | context . invalidated ( ) ) return null ;
Route route = chooseRoute ( context ) ;
if ( isNull ( route ) ) return null ;
context . rou te( route ) ;
if ( ! route . reserveFor ( context ) ) {
r oute. reset ( ) ;
return null ;
}
if ( ! route . p repareAndLock ( ) ) {
route . reset ( ) ;
return null ;
}
return route ;
} finally {
// do not invalidate context here: may be used in delayed actions called from (successful) start
state = State . ENDED ;
train . removeChild ( this ) ;
active = false ;
}
}
public RouteManager setAuto ( boolean auto ) {
LOG . debug ( " {}abled autopilot of {} " , auto ? " En " : " Dis " ) ;
autopilot = auto ;
return this ;
public void setContext ( Context context ) {
ctx = context ;
}
public void start ( ) {
Thread thread = new Thread ( this ) ;
thread . setName ( context . train ( ) . name ( ) ) ;
public void setCallback ( Callback callback ) {
if ( ctx . invalidated ( ) ) return ;
if ( isNull ( this . callback ) ) {
Route route = prepareRoute ( ctx ) ;
if ( isSet ( route ) & & isSet ( callback ) ) callback . routePrepared ( route ) ;
}
}
public boolean isActive ( ) {
return active ;
}
public void start ( Callback callback ) {
Thread thread = new Thread ( ) {
public void run ( ) {
setCallback ( callback ) ;
} ;
} ;
thread . setName ( getClass ( ) . getSimpleName ( ) + " ( " + ctx . train ( ) + " ) " ) ;
thread . start ( ) ;
}
}