Browse Source

dunno what to write, this is a non-clean state...

lookup-tables
Stephan Richter 5 years ago
parent
commit
030b29499f
  1. 15
      src/main/java/de/srsoftware/web4rail/Route.java
  2. 3
      src/main/java/de/srsoftware/web4rail/actions/PreserveRoute.java
  3. 34
      src/main/java/de/srsoftware/web4rail/moving/Train.java
  4. 229
      src/main/java/de/srsoftware/web4rail/threads/RouteManager.java

15
src/main/java/de/srsoftware/web4rail/Route.java

@ -91,6 +91,8 @@ public class Route extends BaseClass {
private Block startBlock = null; private Block startBlock = null;
public Direction startDirection; public Direction startDirection;
private HashSet<Contact> triggeredContacts = new HashSet<>(); private HashSet<Contact> triggeredContacts = new HashSet<>();
private Route nextRoute;
public Route() { public Route() {
conditions = new ConditionList(); conditions = new ConditionList();
@ -404,6 +406,10 @@ public class Route extends BaseClass {
return sb.toString().trim(); return sb.toString().trim();
} }
public Route getNextRoute() {
return nextRoute;
}
public Id id() { public Id id() {
if (isNull(id)) id = new Id(""+(generateName().hashCode())); if (isNull(id)) id = new Id(""+(generateName().hashCode()));
return id; return id;
@ -775,6 +781,11 @@ public class Route extends BaseClass {
if (lastTile instanceof Turnout) addTurnout((Turnout) lastTile,state); if (lastTile instanceof Turnout) addTurnout((Turnout) lastTile,state);
} }
public void setNextRoute(Route nextRoute) {
this.nextRoute = nextRoute;
}
public boolean setSignals(String state) { public boolean setSignals(String state) {
LOG.debug("{}.setSignals({})",this,state); LOG.debug("{}.setSignals({})",this,state);
for (Signal signal : signals) { for (Signal signal : signals) {
@ -862,4 +873,8 @@ public class Route extends BaseClass {
super.update(params); super.update(params);
return properties(); return properties();
} }
public Integer waitTime() {
return isNull(context) ? null : context.waitTime();
}
} }

3
src/main/java/de/srsoftware/web4rail/actions/PreserveRoute.java

@ -30,7 +30,6 @@ public class PreserveRoute extends Action {
return false; // train is expected to wait in next block. return false; // train is expected to wait in next block.
} }
train.reserveRouteAfter(route); return route.prepareNext(train);
return true;
} }
} }

34
src/main/java/de/srsoftware/web4rail/moving/Train.java

@ -37,6 +37,7 @@ import de.srsoftware.web4rail.tags.Select;
import de.srsoftware.web4rail.tags.Table; import de.srsoftware.web4rail.tags.Table;
import de.srsoftware.web4rail.tags.Window; import de.srsoftware.web4rail.tags.Window;
import de.srsoftware.web4rail.threads.RouteManager; import de.srsoftware.web4rail.threads.RouteManager;
import de.srsoftware.web4rail.threads.RouteManager.Callback;
import de.srsoftware.web4rail.tiles.Block; import de.srsoftware.web4rail.tiles.Block;
import de.srsoftware.web4rail.tiles.Contact; import de.srsoftware.web4rail.tiles.Contact;
import de.srsoftware.web4rail.tiles.Tile; import de.srsoftware.web4rail.tiles.Tile;
@ -61,7 +62,7 @@ public class Train extends BaseClass implements Comparable<Train> {
private Route route; private Route route;
private Direction direction; private Direction direction;
private boolean autopilot;
private static final String PUSH_PULL = "pushPull"; private static final String PUSH_PULL = "pushPull";
public boolean pushPull = false; public boolean pushPull = false;
@ -93,6 +94,8 @@ public class Train extends BaseClass implements Comparable<Train> {
private HashSet<Tile> stuckTrace = null; private HashSet<Tile> stuckTrace = null;
private Route nextRoute;
public static Object action(HashMap<String, String> params, Plan plan) throws IOException { public static Object action(HashMap<String, String> params, Plan plan) throws IOException {
String action = params.get(ACTION); String action = params.get(ACTION);
if (isNull(action)) return t("No action passed to Train.action!"); if (isNull(action)) return t("No action passed to Train.action!");
@ -403,12 +406,18 @@ public class Train extends BaseClass implements Comparable<Train> {
public void endRoute(Block endBlock, Direction endDirection) { public void endRoute(Block endBlock, Direction endDirection) {
setSpeed(0); setSpeed(0);
nextRoute = route.getNextRoute();
Integer waitTime = route.waitTime();
route = null; route = null;
direction = endDirection; direction = endDirection;
endBlock.add(this, direction); endBlock.add(this, direction);
currentBlock = endBlock; currentBlock = endBlock;
trace.add(endBlock); trace.add(endBlock);
stuckTrace = null; stuckTrace = null;
if (autopilot) {
if (isSet(waitTime) && waitTime > 0) sleep(waitTime);
start(false);
}
} }
private Tag faster(int steps) { private Tag faster(int steps) {
@ -715,10 +724,6 @@ public class Train extends BaseClass implements Comparable<Train> {
return tags().iterator(); return tags().iterator();
} }
public void reserveRouteAfter(Route r) {
LOG.debug("reserveRouteAfter({})",r);
}
/** /**
* This turns the train as if it went through a loop. Example: * This turns the train as if it went through a loop. Example:
* before: CabCar MiddleCar Loco * before: CabCar MiddleCar Loco
@ -864,9 +869,20 @@ public class Train extends BaseClass implements Comparable<Train> {
public String start(boolean auto) { public String start(boolean auto) {
if (isNull(routeManager)) routeManager = new RouteManager(this); autopilot |= auto;
routeManager.setAuto(auto).start(); if (isSet(nextRoute) && nextRoute.start()) {
plan.stream(t("Started {}",this)); nextRoute = null;
return null;
}
if (isNull(routeManager)) routeManager = new RouteManager();
routeManager.setContext(new Context(this).block(currentBlock).direction(direction));
routeManager.start(new Callback() {
@Override
public void routePrepared(Route route) {
route.start();
plan.stream(t("Started {}",Train.this));
}
});
return null; return null;
} }
@ -999,6 +1015,6 @@ public class Train extends BaseClass implements Comparable<Train> {
} }
public boolean usesAutopilot() { public boolean usesAutopilot() {
return isSet(routeManager) && routeManager.autoEnabled(); return autopilot;
} }
} }

229
src/main/java/de/srsoftware/web4rail/threads/RouteManager.java

@ -16,202 +16,193 @@ import de.srsoftware.web4rail.moving.Train;
import de.srsoftware.web4rail.tiles.Block; import de.srsoftware.web4rail.tiles.Block;
import de.srsoftware.web4rail.tiles.Tile; import de.srsoftware.web4rail.tiles.Tile;
public class RouteManager extends BaseClass implements Runnable { public class RouteManager extends BaseClass {
enum State { public static abstract class Callback {
ENDED,IDLE,STARTED; public abstract void routePrepared(Route route);
} }
private static final Logger LOG = LoggerFactory.getLogger(RouteManager.class); private static final Logger LOG = LoggerFactory.getLogger(RouteManager.class);
private static final int DEFAULT_PAUSE_TIME = 250; // ms private Context ctx;
private State state = State.IDLE; private Callback callback;
private boolean autopilot; private boolean active;
private Context context;
private int time = 0; public RouteManager() {
active = false;
public RouteManager(Train train) {
context = new Context(train);
state = State.STARTED;
} }
private static TreeMap<Integer,List<Route>> availableRoutes(Context context,HashSet<Route> visitedRoutes){ private static TreeMap<Integer, List<Route>> availableRoutes(Context context, HashSet<Route> visitedRoutes) {
String inset = ""; String inset = "";
for (int i=0; i<visitedRoutes.size(); i++) inset+=" "; for (int i = 0; i < visitedRoutes.size(); i++) inset += " ";
LOG.debug("{}{}.availableRoutes({})",inset,RouteManager.class.getSimpleName(),context); LOG.debug("{}{}.availableRoutes({})", inset, RouteManager.class.getSimpleName(), context);
Block block = context.block(); Block block = context.block();
Train train = context.train(); Train train = context.train();
Direction startDirection = context.direction(); Direction startDirection = context.direction();
Route currentRoute = context.route(); 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; boolean error = false;
if (isNull(block) && (error = true)) LOG.warn("{} → {}.availableRoutes called without context.block!",inset,Train.class.getSimpleName()); if (isNull(block) && (error = true))
if (isNull(train) && (error = true)) LOG.warn("{}→ {}.availableRoutes called without context.train!", inset,Train.class.getSimpleName()); 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; if (error) return availableRoutes;
Block destination = train.destination(); Block destination = train.destination();
if (isSet(startDirection)) { if (isSet(startDirection)) {
LOG.debug("{}- Looking for {}-bound routes from {}",inset,startDirection,block); LOG.debug("{}- Looking for {}-bound routes from {}", inset, startDirection, block);
} else { } 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()) { for (Route routeCandidate : block.leavingRoutes()) {
if (context.invalidated()) return availableRoutes; if (context.invalidated()) return availableRoutes;
if (visitedRoutes.contains(routeCandidate)) { 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; 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)) { 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; continue;
} }
if (!routeCandidate.allowed(context)) { if (!routeCandidate.allowed(context)) {
if (routeCandidate.endBlock() != destination) { // allowance may be overridden by destination 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 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; 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 (!train.pushPull) continue; // Zug kann nicht wenden
if (!block.turnAllowed) continue; // Wenden im Block nicht gestattet if (!block.turnAllowed) continue; // Wenden im Block nicht gestattet
priority -= 5; 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 (isSet(destination)) {
if (routeCandidate.endBlock() == destination) { // route goes directly to 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; priority = 1_000_000;
} else { } else {
LOG.debug("{}- Candidate: {}",inset,routeCandidate.shortName()); LOG.debug("{}- Candidate: {}", inset, routeCandidate.shortName());
Context forwardContext = new Context(train).block(routeCandidate.endBlock()).route(null).direction(routeCandidate.endDirection); Context forwardContext = new Context(train).block(routeCandidate.endBlock()).route(null)
.direction(routeCandidate.endDirection);
visitedRoutes.add(routeCandidate); visitedRoutes.add(routeCandidate);
TreeMap<Integer, List<Route>> forwardRoutes = availableRoutes(forwardContext,visitedRoutes); TreeMap<Integer, List<Route>> forwardRoutes = availableRoutes(forwardContext, visitedRoutes);
visitedRoutes.remove(routeCandidate); 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(); Entry<Integer, List<Route>> entry = forwardRoutes.lastEntry();
LOG.debug("{}→ The following routes have connections to {}:",inset,destination); LOG.debug("{}→ The following routes have connections to {}:", inset, destination);
for (Route rt: entry.getValue()) LOG.debug("{} - {}",inset,rt.shortName()); for (Route rt : entry.getValue()) LOG.debug("{} - {}", inset, rt.shortName());
priority += entry.getKey()-10; priority += entry.getKey() - 10;
} }
} }
List<Route> routeSet = availableRoutes.get(priority); List<Route> routeSet = availableRoutes.get(priority);
if (isNull(routeSet)) { if (isNull(routeSet)) {
routeSet = new Vector<Route>(); routeSet = new Vector<Route>();
availableRoutes.put(priority, routeSet); availableRoutes.put(priority, routeSet);
} }
routeSet.add(routeCandidate); 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()) { for (Entry<Integer, List<Route>> entry : availableRoutes.entrySet()) {
LOG.debug("{} - Priority {}:",inset,entry.getKey()); LOG.debug("{} - Priority {}:", inset, entry.getKey());
for (Route r : entry.getValue()) LOG.debug("{} - {}",inset,r.shortName()); for (Route r : entry.getValue()) LOG.debug("{} - {}", inset, r.shortName());
} }
return availableRoutes; return availableRoutes;
} }
public boolean autoEnabled() {
return autopilot && isActive();
}
public static Route chooseRoute(Context context) { public static Route chooseRoute(Context context) {
LOG.debug("{}.chooseRoute({})",RouteManager.class.getSimpleName(),context); LOG.debug("{}.chooseRoute({})", RouteManager.class.getSimpleName(), context);
TreeMap<Integer, List<Route>> availableRoutes = availableRoutes(context,new HashSet<Route>()); TreeMap<Integer, List<Route>> availableRoutes = availableRoutes(context, new HashSet<Route>());
while (!availableRoutes.isEmpty()) { while (!availableRoutes.isEmpty()) {
if (context.invalidated()) break; if (context.invalidated()) break;
LOG.debug("availableRoutes: {}",availableRoutes); LOG.debug("availableRoutes: {}", availableRoutes);
Entry<Integer, List<Route>> entry = availableRoutes.lastEntry(); Entry<Integer, List<Route>> entry = availableRoutes.lastEntry();
List<Route> preferredRoutes = entry.getValue(); List<Route> preferredRoutes = entry.getValue();
LOG.debug("preferredRoutes: {}",preferredRoutes); LOG.debug("preferredRoutes: {}", preferredRoutes);
Route selectedRoute = preferredRoutes.get(random.nextInt(preferredRoutes.size())); Route selectedRoute = preferredRoutes.get(random.nextInt(preferredRoutes.size()));
if (selectedRoute.isFreeFor(context)) { if (selectedRoute.isFreeFor(context)) {
LOG.debug("Chose \"{}\" with priority {}.",selectedRoute,entry.getKey()); LOG.debug("Chose \"{}\" with priority {}.", selectedRoute, entry.getKey());
return selectedRoute; 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); preferredRoutes.remove(selectedRoute);
if (preferredRoutes.isEmpty()) availableRoutes.remove(availableRoutes.lastKey()); if (preferredRoutes.isEmpty()) availableRoutes.remove(availableRoutes.lastKey());
} }
return null; 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() { public void quit() {
LOG.debug("{}.quit",this); LOG.debug("{}.quit", this);
autopilot = false; callback = null;
context.invalidate(); if (isSet(ctx)) ctx.invalidate();
} }
@Override public Route prepareRoute(Context context) {
public void run() {
Train train = context.train();
try { try {
do { if (isNull(context) || context.invalidated()) return null;
pause(); Route route = chooseRoute(context);
if (isSet(train.route())) continue; if (isNull(route)) return null;
if (context.invalidated()) return; context.route(route);
context.block(train.currentBlock()).direction(train.direction()); if (!route.reserveFor(context)) {
Route route = chooseRoute(context); route.reset();
if (isNull(route)) continue; return null;
context.route(route); }
if (!route.reserveFor(context)) { if (!route.prepareAndLock()) {
route.reset(); route.reset();
continue; return null;
} }
if (!route.prepareAndLock()) {
route.reset(); return route;
continue;
}
// Route reserved, prepared and locked:
if (!route.start()) {
route.reset();
continue;
}
} while (autopilot);
} finally { } finally {
// do not invalidate context here: may be used in delayed actions called from (successful) start active = false;
state = State.ENDED; }
train.removeChild(this); }
public void setContext(Context context) {
ctx = context;
}
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 RouteManager setAuto(boolean auto) { public boolean isActive() {
LOG.debug("{}abled autopilot of {}",auto?"En":"Dis"); return active;
autopilot = auto;
return this;
} }
public void start() { public void start(Callback callback) {
Thread thread = new Thread(this); Thread thread = new Thread() {
thread.setName(context.train().name()); public void run() {
setCallback(callback);
};
};
thread.setName(getClass().getSimpleName() + "(" + ctx.train() + ")");
thread.start(); thread.start();
} }
} }

Loading…
Cancel
Save