Browse Source

overhauled route search algorithm:

now using breadth-first search instead of depth-first search
lookup-tables
Stephan Richter 4 years ago
parent
commit
db9fed1642
  1. 2
      pom.xml
  2. 1
      resources/translations/Application.de.translation
  3. 12
      src/main/java/de/srsoftware/web4rail/Range.java
  4. 66
      src/main/java/de/srsoftware/web4rail/Route.java
  5. 2
      src/main/java/de/srsoftware/web4rail/actions/SetTurnout.java
  6. 2
      src/main/java/de/srsoftware/web4rail/moving/Train.java
  7. 278
      src/main/java/de/srsoftware/web4rail/threads/RoutePrepper.java
  8. 1
      src/main/java/de/srsoftware/web4rail/tiles/Contact.java
  9. 10
      src/main/java/de/srsoftware/web4rail/tiles/Turnout.java
  10. 2
      src/main/java/de/srsoftware/web4rail/tiles/TurnoutL.java
  11. 3
      src/main/java/de/srsoftware/web4rail/tiles/TurnoutR.java

2
pom.xml

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>de.srsoftware</groupId> <groupId>de.srsoftware</groupId>
<artifactId>web4rail</artifactId> <artifactId>web4rail</artifactId>
<version>1.4.6</version> <version>1.4.7</version>
<name>Web4Rail</name> <name>Web4Rail</name>
<packaging>jar</packaging> <packaging>jar</packaging>
<description>Java Model Railway Control</description> <description>Java Model Railway Control</description>

1
resources/translations/Application.de.translation

@ -326,6 +326,7 @@ Start actions : Start-Aktionen
Stock ID : Inventarnummer Stock ID : Inventarnummer
Stop settings : Halte-Einstellungen Stop settings : Halte-Einstellungen
Start autopilot : Autopilot starten Start autopilot : Autopilot starten
Start delay : Start-Verzögerung
Started {} : {} gestartet Started {} : {} gestartet
starting delay : Anfahrverzögerung starting delay : Anfahrverzögerung
State : Status State : Status

12
src/main/java/de/srsoftware/web4rail/Range.java

@ -13,8 +13,18 @@ public class Range extends BaseClass{
private static final String MAX = "max"; private static final String MAX = "max";
private static final String MIN = "min"; private static final String MIN = "min";
public int min=0,max=10000; public int min,max;
public Range(int min, int max) {
this.min = min;
this.max = max;
validate();
}
public Range() {
this(0,10_000);
}
public JSONObject json() { public JSONObject json() {
return new JSONObject(Map.of(MIN,min,MAX,max)); return new JSONObject(Map.of(MIN,min,MAX,max));
} }

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

@ -22,7 +22,6 @@ import de.srsoftware.web4rail.Plan.Direction;
import de.srsoftware.web4rail.actions.Action; import de.srsoftware.web4rail.actions.Action;
import de.srsoftware.web4rail.actions.ActionList; import de.srsoftware.web4rail.actions.ActionList;
import de.srsoftware.web4rail.actions.BrakeStart; import de.srsoftware.web4rail.actions.BrakeStart;
import de.srsoftware.web4rail.actions.DelayedAction;
import de.srsoftware.web4rail.actions.FinishRoute; import de.srsoftware.web4rail.actions.FinishRoute;
import de.srsoftware.web4rail.actions.PreserveRoute; import de.srsoftware.web4rail.actions.PreserveRoute;
import de.srsoftware.web4rail.actions.SetSignal; import de.srsoftware.web4rail.actions.SetSignal;
@ -76,6 +75,12 @@ public class Route extends BaseClass {
private static final String ROUTE_SETUP = "route_setup"; private static final String ROUTE_SETUP = "route_setup";
private static final String MIN_START_DELAY = "min_start_delay";
private static final String MAX_START_DELAY = "max_start_delay";
private static final String STARt_DELAY = "start_delay";
private static HashMap<Id, String> names = new HashMap<Id, String>(); // maps id to name. needed to keep names during plan.analyze() private static HashMap<Id, String> names = new HashMap<Id, String>(); // maps id to name. needed to keep names during plan.analyze()
private HashMap<String,Integer> brakeTimes = new HashMap<String, Integer>(); private HashMap<String,Integer> brakeTimes = new HashMap<String, Integer>();
@ -85,17 +90,17 @@ public class Route extends BaseClass {
private boolean disabled = false; private boolean disabled = false;
private Block endBlock = null; private Block endBlock = null;
public Direction endDirection; public Direction endDirection;
private Route nextPreparedRoute = null;
private RoutePrepper nextRoutePrepper = null;
private Vector<Tile> path; private Vector<Tile> path;
private Vector<Signal> signals; private Vector<Signal> signals;
private HashMap<String,ActionList> triggeredActions = new HashMap<String, ActionList>(); private HashMap<String,ActionList> triggeredActions = new HashMap<String, ActionList>();
private HashMap<Turnout,Turnout.State> turnouts; private HashMap<Turnout,Turnout.State> turnouts;
private Block startBlock = null; private Block startBlock = null;
private Range startDelay = null;
public Direction startDirection; public Direction startDirection;
private HashSet<Contact> triggeredContacts = new HashSet<>(); private HashSet<Contact> triggeredContacts = new HashSet<>();
private Route nextPreparedRoute;
private RoutePrepper nextRoutePrepper;
public Route() { public Route() {
conditions = new ConditionList(); conditions = new ConditionList();
@ -303,12 +308,8 @@ public class Route extends BaseClass {
add(ROUTE_SETUP,new SetTurnout(this).setTurnout(turnout).setState(state)); add(ROUTE_SETUP,new SetTurnout(this).setTurnout(turnout).setState(state));
} }
for (Signal signal : signals) add(ROUTE_SETUP,new SetSignal(this).set(signal).to(Signal.GREEN)); for (Signal signal : signals) add(ROUTE_SETUP,new SetSignal(this).set(signal).to(Signal.GREEN));
if (signals.isEmpty()) { startDelay = signals.isEmpty() ? new Range(0,0) : new Range(1000,7500);
add(ROUTE_START,new SetSpeed(this).to(999)); add(ROUTE_START,new SetSpeed(this).to(999));
} else {
DelayedAction da = new DelayedAction(this).setMinDelay(1000).setMaxDelay(7500);
add(ROUTE_START,da.add(new SetSpeed(this).to(999)));
}
return this; return this;
} }
@ -485,6 +486,7 @@ public class Route extends BaseClass {
if (isSet(name)) json.put(NAME, name); if (isSet(name)) json.put(NAME, name);
if (disabled) json.put(DISABLED, true); if (disabled) json.put(DISABLED, true);
if (isSet(startDelay)) json.put(STARt_DELAY, startDelay.json());
return json; return json;
} }
@ -618,6 +620,7 @@ public class Route extends BaseClass {
JSONObject dummy = json.getJSONObject(BRAKE_TIMES); JSONObject dummy = json.getJSONObject(BRAKE_TIMES);
dummy.keySet().forEach(key -> brakeTimes.put(key, dummy.getInt(key))); dummy.keySet().forEach(key -> brakeTimes.put(key, dummy.getInt(key)));
} }
if (json.has(STARt_DELAY)) startDelay = new Range().load(json.getJSONObject(STARt_DELAY));
return plan.registerRoute(this); return plan.registerRoute(this);
} }
@ -725,7 +728,12 @@ public class Route extends BaseClass {
formInputs.add(t("Name"),nameSpan); formInputs.add(t("Name"),nameSpan);
Checkbox checkbox = new Checkbox(DISABLED, t("disabled"), disabled); Checkbox checkbox = new Checkbox(DISABLED, t("disabled"), disabled);
if (disabled) checkbox.clazz("disabled"); if (disabled) checkbox.clazz("disabled");
formInputs.add(t("State"),checkbox); formInputs.add(t("State"),checkbox);
Tag span = new Tag("span");
new Input(MIN_START_DELAY,isSet(startDelay) ? startDelay.min : 0).numeric().addTo(span).content(" ... ");
new Input(MAX_START_DELAY,isSet(startDelay) ? startDelay.max : 0).numeric().addTo(span).content(" ms");
formInputs.add(t("Start delay"),span);
postForm.add(basicProperties()); postForm.add(basicProperties());
if (!turnouts.isEmpty()) postForm.add(turnouts()); if (!turnouts.isEmpty()) postForm.add(turnouts());
@ -846,9 +854,10 @@ public class Route extends BaseClass {
if (parts.length>1) name(parts[0].trim()+" - "+parts[parts.length-1].trim()); if (parts.length>1) name(parts[0].trim()+" - "+parts[parts.length-1].trim());
return this; return this;
} }
public boolean start() { public boolean startNow() {
LOG.debug("{}.start()",this); LOG.debug("{}.startNow()",this);
if (isNull(context) || context.invalidated()) { if (isNull(context) || context.invalidated()) {
LOG.debug("Invalid context: {}",context); LOG.debug("Invalid context: {}",context);
return false; return false;
@ -878,6 +887,11 @@ public class Route extends BaseClass {
return true; return true;
} }
public boolean start() {
if (isSet(startDelay)) sleep(startDelay.random());
return startNow();
}
public Block startBlock() { public Block startBlock() {
return startBlock; return startBlock;
} }
@ -916,11 +930,35 @@ public class Route extends BaseClass {
disabled = "on".equals(params.get(DISABLED)); disabled = "on".equals(params.get(DISABLED));
String delay = params.get(MIN_START_DELAY);
if (isSet(delay)) try {
int min = Integer.parseInt(delay);
if (isNull(startDelay)) {
startDelay = new Range(min, min);
} else {
startDelay.min = min;
startDelay.validate();
}
} catch (NumberFormatException e) {}
delay = params.get(MAX_START_DELAY);
if (isSet(delay)) try {
int max = Integer.parseInt(delay);
if (isNull(startDelay)) {
startDelay = new Range(max, max);
} else {
startDelay.max = max;
startDelay.validate();
}
} catch (NumberFormatException e) {}
Condition condition = Condition.create(params.get(REALM_CONDITION)); Condition condition = Condition.create(params.get(REALM_CONDITION));
if (isSet(condition)) { if (isSet(condition)) {
condition.parent(this); condition.parent(this);
conditions.add(condition); conditions.add(condition);
} }
super.update(params); super.update(params);
return properties(); return properties();
} }

2
src/main/java/de/srsoftware/web4rail/actions/SetTurnout.java

@ -28,7 +28,7 @@ public class SetTurnout extends Action {
public boolean fire(Context context,Object cause) { public boolean fire(Context context,Object cause) {
if (context.invalidated()) return false; if (context.invalidated()) return false;
if (isNull(turnout)) return false; if (isNull(turnout)) return false;
if (!turnout.state(state).succeeded()) return false; if (!turnout.state(state,false).succeeded()) return false;
if (turnout.address() == 0) return true; if (turnout.address() == 0) return true;
sleep(1000); sleep(1000);
return true; return true;

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

@ -979,7 +979,7 @@ public class Train extends BaseClass implements Comparable<Train> {
autopilot |= auto; autopilot |= auto;
if (isSet(nextPreparedRoute)) { if (isSet(nextPreparedRoute)) {
LOG.debug("starting nextPreparedRoute: {}",nextPreparedRoute); LOG.debug("starting nextPreparedRoute: {}",nextPreparedRoute);
if (nextPreparedRoute.start()) { if (nextPreparedRoute.startNow()) {
LOG.debug("dropped nextPreparedRoute (was {})",nextPreparedRoute); LOG.debug("dropped nextPreparedRoute (was {})",nextPreparedRoute);
nextPreparedRoute = null; nextPreparedRoute = null;
return null; return null;

278
src/main/java/de/srsoftware/web4rail/threads/RoutePrepper.java

@ -1,11 +1,11 @@
package de.srsoftware.web4rail.threads; package de.srsoftware.web4rail.threads;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.Vector;
import de.srsoftware.web4rail.Application; import de.srsoftware.web4rail.Application;
import de.srsoftware.web4rail.BaseClass; import de.srsoftware.web4rail.BaseClass;
@ -17,6 +17,23 @@ import de.srsoftware.web4rail.tiles.Block;
import de.srsoftware.web4rail.tiles.Tile; import de.srsoftware.web4rail.tiles.Tile;
public class RoutePrepper extends BaseClass implements Runnable{ public class RoutePrepper extends BaseClass implements Runnable{
private static class Candidate{
private int score;
private Route route;
public Candidate(Route r, int s) {
route = r;
score = s;
}
@Override
public String toString() {
return route+"(score: "+score+")";
}
}
private Context context; private Context context;
private Route route; private Route route;
private List<EventListener> failListeners = new LinkedList<>(); private List<EventListener> failListeners = new LinkedList<>();
@ -33,118 +50,113 @@ public class RoutePrepper extends BaseClass implements Runnable{
if (!errors.isEmpty()) throw new NullPointerException(String.join(", ", errors)); if (!errors.isEmpty()) throw new NullPointerException(String.join(", ", errors));
context = c; context = c;
} }
private static TreeMap<Integer, List<Route>> availableRoutes(Context context, HashSet<Route> visitedRoutes) { private static TreeMap<Integer,LinkedList<Route>> availableRoutes(Context c){
String inset = "";
for (int i = 0; i < visitedRoutes.size(); i++) inset += " ";
LOG.debug("{}{}.availableRoutes({})", inset, RoutePrepper.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>>();
boolean error = false; boolean error = false;
if (isNull(block) && (error = true))
LOG.warn("{} → {}.availableRoutes called without context.block!", inset, Train.class.getSimpleName()); Block startBlock = c.block();
if (isNull(train) && (error = true)) if (isNull(startBlock) && (error=true)) LOG.warn("RoutePrepper.findRoute(…) called without a startBlock!");
LOG.warn("{}→ {}.availableRoutes called without context.train!", inset, Train.class.getSimpleName());
if (error) return availableRoutes; Train train = c.train();
if (isNull(train) && (error=true)) LOG.warn("RoutePrepper.findRoute(…) called without a startBlock!");
if (isSet(startDirection)) {
LOG.debug("{}- Looking for {}-bound routes from {}", inset, startDirection, block); if (error) return new TreeMap<>();
} else {
LOG.debug("{}- Looking for all routes from {}", inset, block);
}
Block destination = train.destination(); Block destination = train.destination();
if (isSet(destination) && visitedRoutes.isEmpty()) LOG.debug("{}- Destination: {}", inset, destination);
Direction startDirection = c.direction();
for (Route routeCandidate : block.leavingRoutes()) { LOG.debug("RoutePrepper.findRoute({},{},{}), dest = {}",startBlock,startDirection,train,destination);
if (context.invalidated()) return availableRoutes;
if (visitedRoutes.contains(routeCandidate)) { TreeMap<Integer, LinkedList<Route>> candidates = routesFrom(c);
LOG.debug("{}→ Candidate {} would create loop, skipping", inset, routeCandidate.shortName());
continue; if (isNull(destination)) {
} LOG.debug("{} has no destination, returning {}",train,candidates);
return candidates;
HashSet<Tile> stuckTrace = train.stuckTrace(); // if train has been stopped in between two blocks lastly: }
// only allow starting routes that do not conflict with current train
// position LOG.debug("{} is heading for {}, starting breadth-first search…",train,destination);
if (isSet(stuckTrace) && visitedRoutes.isEmpty() && !routeCandidate.path().containsAll(stuckTrace)) {
LOG.debug("Stuck train occupies tiles ({}) outside of {} – not allowed.", stuckTrace, routeCandidate); HashMap<Route,Candidate> predecessors = new HashMap<>();
continue; TreeMap<Integer,LinkedList<Route>> routesToDest = new TreeMap<>();
}
int level = 0;
if (!routeCandidate.allowed(context)) {
if (routeCandidate.endBlock() != destination) { // allowance may be overridden by destination while (!candidates.isEmpty()) {
LOG.debug("{} not allowed for {}", routeCandidate, context); TreeMap<Integer, LinkedList<Route>> queue = new TreeMap<>();
continue; // Zug darf auf Grund einer nicht erfüllten Bedingung nicht auf die Route
while (!candidates.isEmpty()) {
Candidate candidate = pop(candidates);
LOG.debug(" - examining {}…",candidate);
Block endBlock = candidate.route.endBlock();
Direction endDir = candidate.route.endDirection;
if (endBlock == destination) {
LOG.debug(" - {} reaches destination!",candidate);
int score = candidate.score;
// The route we found leads to the destination block.
// However it might be the last route in a long path.
// Thus, we need to get the first route in this path:
while (predecessors.containsKey(candidate.route)) {
candidate = predecessors.get(candidate.route);
LOG.debug(" - predecessed by {}",candidate);
score += candidate.score;
}
LOG.debug(" → path starts with {} and has total score of {}",candidate.route,score);
LinkedList<Route> routesForScore = routesToDest.get(score);
if (isNull(routesForScore)) routesToDest.put(score, routesForScore = new LinkedList<Route>());
routesForScore.add(candidate.route);
continue;
} }
LOG.debug("{} not allowed for {} – overridden by selected destination", routeCandidate, context);
} LOG.debug(" - {} not reaching {}, adding ongoing routes to queue:",candidate,destination);
TreeMap<Integer, LinkedList<Route>> successors = routesFrom(c.clone().block(endBlock).direction(endDir));
int priority = 0; while (!successors.isEmpty()) {
if (isSet(startDirection) && routeCandidate.startDirection != startDirection) { // Route startet entgegen int score = successors.firstKey();
// der aktuellen LinkedList<Route> best = successors.remove(score);
// Fahrtrichtung des Zuges score -= 25; // Nachfolgeroute
if (!train.pushPull) continue; // Zug kann nicht wenden for (Route route : best) {
if (!block.turnAllowed) continue; // Wenden im Block nicht gestattet LOG.debug(" - queueing {} with score {}",route,score);
priority -= 5; if (predecessors.containsKey(route)) continue; // Route wurde bereits besucht
} predecessors.put(route, candidate);
if (routeCandidate == currentRoute) priority -= 10; // möglichst andere Route als zuvor wählen // TODO: den
// Routen einen "last-used" Zeitstempel hinzufügen, und LinkedList<Route> list = queue.get(score);
// diesen mit in die Priorisierung einbeziehen if (isNull(list)) queue.put(score, list = new LinkedList<>());
list.add(route);
if (isSet(destination)) { }
if (routeCandidate.endBlock() == destination) { // route goes directly to 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);
visitedRoutes.add(routeCandidate);
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
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;
} }
} }
List<Route> routeSet = availableRoutes.get(priority); if (!routesToDest.isEmpty()) return routesToDest;
if (isNull(routeSet)) { LOG.debug("No routes to {} found with distance {}!",destination,level);
routeSet = new Vector<Route>(); level ++;
availableRoutes.put(priority, routeSet); candidates = queue;
}
routeSet.add(routeCandidate);
if (routeCandidate.endBlock() == destination) break; // direct connection to destination discovered, quit
// search
}
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());
} }
return availableRoutes; LOG.debug("No more candidates for routes towards {}!",destination);
return new TreeMap<>();
} }
private static Route chooseRoute(Context context) { private static Route chooseRoute(Context context) {
LOG.debug("{}.chooseRoute({})", RoutePrepper.class.getSimpleName(), context); LOG.debug("{}.chooseRoute({})", RoutePrepper.class.getSimpleName(), context);
TreeMap<Integer, List<Route>> availableRoutes = availableRoutes(context, new HashSet<Route>()); TreeMap<Integer, LinkedList<Route>> availableRoutes = availableRoutes(context);
LOG.debug("available routes: {}",availableRoutes);
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, LinkedList<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)) {
HashSet<Tile> stuckTrace = context.train().stuckTrace(); // if train has been stopped in between two blocks lastly:
// only allow starting routes that do not conflict with current train position
if (isSet(stuckTrace) && !selectedRoute.path().containsAll(stuckTrace)) {
LOG.debug("Stuck train occupies tiles ({}) outside of {} – not allowed.", stuckTrace, selectedRoute);
} else if (selectedRoute.isFreeFor(context)) {
LOG.debug("Chose \"{}\" with priority {}.", selectedRoute, entry.getKey()); LOG.debug("Chose \"{}\" with priority {}.", selectedRoute, entry.getKey());
return selectedRoute; return selectedRoute;
} }
@ -156,6 +168,11 @@ public class RoutePrepper extends BaseClass implements Runnable{
return null; return null;
} }
private boolean fail() {
notify(failListeners);
route = null;
return false;
}
private void notify(List<EventListener> listeners) { private void notify(List<EventListener> listeners) {
for (EventListener listener: listeners) { for (EventListener listener: listeners) {
@ -163,13 +180,6 @@ public class RoutePrepper extends BaseClass implements Runnable{
} }
} }
private boolean fail() {
notify(failListeners);
// if (isSet(route) route.reset();
route = null;
return false;
}
public void onFail(EventListener l) { public void onFail(EventListener l) {
failListeners.add(l); failListeners.add(l);
} }
@ -186,7 +196,20 @@ public class RoutePrepper extends BaseClass implements Runnable{
preparedListener = l; preparedListener = l;
} }
private static Candidate pop(TreeMap<Integer, LinkedList<Route>> candidates) {
while (!candidates.isEmpty()) {
int score = candidates.firstKey();
LinkedList<Route> list = candidates.get(score);
if (isNull(list) || list.isEmpty()) {
candidates.remove(score);
} else {
Candidate candidate = new Candidate(list.removeFirst(),score);
if (list.isEmpty()) candidates.remove(score);
return candidate;
}
}
return null;
}
public boolean prepareRoute() { public boolean prepareRoute() {
if (isNull(context) || context.invalidated()) return fail(); if (isNull(context) || context.invalidated()) return fail();
@ -206,6 +229,53 @@ public class RoutePrepper extends BaseClass implements Runnable{
return route; return route;
} }
private static TreeMap<Integer,LinkedList<Route>> routesFrom(Context c){
boolean error = false;
Block startBlock = c.block();
if (isNull(startBlock) && (error=true)) LOG.warn("RoutePrepper.routesFrom(…) called without a startBlock!");
Train train = c.train();
if (isNull(train) && (error=true)) LOG.warn("RoutePrepper.routesFrom(…) called without a startBlock!");
if (error) return null;
Block destination = train.destination();
Direction startDirection = c.direction();
LOG.debug("RoutePrepper.routesFrom({},{},{}), dest = {}",startBlock,startDirection,train,destination);
TreeMap<Integer, LinkedList<Route>> routes = new TreeMap<>();
for (Route route : startBlock.leavingRoutes()) {
LOG.debug(" - evaluating {}",route);
int score = 0;
if (!route.allowed(new Context(train).block(startBlock).direction(startDirection))) {
LOG.debug(" - {} not allowed for {}", route, train);
if (route.endBlock() != destination) continue;
LOG.debug(" …overridden by destination of train!", route, train);
}
if (route.endBlock() == destination) score = 100_000;
if (isSet(startDirection) && route.startDirection != startDirection) { // Route startet entgegen der aktuellen Fahrtrichtung des Zuges
if (!train.pushPull) continue; // Zug kann nicht wenden
if (!startBlock.turnAllowed) continue; // Wenden im Block nicht gestattet
score -= 5;
}
LinkedList<Route> routesForScore = routes.get(score);
if (isNull(routesForScore)) routes.put(score, routesForScore = new LinkedList<Route>());
LOG.debug(" → candidate!");
routesForScore.add(route);
}
return routes;
}
@Override @Override
public void run() { public void run() {
LOG.debug("{}.run()",this); LOG.debug("{}.run()",this);

1
src/main/java/de/srsoftware/web4rail/tiles/Contact.java

@ -27,7 +27,6 @@ import de.srsoftware.web4rail.tags.Window;
import de.srsoftware.web4rail.threads.DelayedExecution; import de.srsoftware.web4rail.threads.DelayedExecution;
public class Contact extends Tile{ public class Contact extends Tile{
private static Logger LOG = LoggerFactory.getLogger(Contact.class);
private static final String ADDRESS = "address"; private static final String ADDRESS = "address";
private static final HashMap<Integer, Contact> contactsByAddr = new HashMap<Integer, Contact>(); private static final HashMap<Integer, Contact> contactsByAddr = new HashMap<Integer, Contact>();
private boolean state = false; private boolean state = false;

10
src/main/java/de/srsoftware/web4rail/tiles/Turnout.java

@ -52,7 +52,7 @@ public abstract class Turnout extends Tile implements Device{
@Override @Override
public Object click(boolean shift) throws IOException { public Object click(boolean shift) throws IOException {
LOG.debug(getClass().getSimpleName()+".click()"); LOG.debug(getClass().getSimpleName()+".click()");
if (!shift) init(); init();
return super.click(shift); return super.click(shift);
} }
@ -159,9 +159,13 @@ public abstract class Turnout extends Tile implements Device{
return state; return state;
} }
public Reply state(State newState) { public Reply state(State newState,boolean shift) {
Train lockingTrain = lockingTrain(); Train lockingTrain = lockingTrain();
if (isSet(lockingTrain) && newState != state) return new Reply(415, t("{} locked by {}!",this,lockingTrain));
if (isSet(lockingTrain)) {
if (newState != state && !shift) return new Reply(415, t("{} locked by {}!",this,lockingTrain));
// shift allows to switch locked turnouts...
} else if (shift) return new Reply(200,"OK"); // shift on a non-locked turnout skips the switch process
if (address == 0) { if (address == 0) {
sleep(300); sleep(300);
state = newState; state = newState;

2
src/main/java/de/srsoftware/web4rail/tiles/TurnoutL.java

@ -15,7 +15,7 @@ public abstract class TurnoutL extends Turnout {
@Override @Override
public Object click(boolean shift) throws IOException { public Object click(boolean shift) throws IOException {
Object o = super.click(shift); Object o = super.click(shift);
if (!shift) state(state == State.STRAIGHT ? State.LEFT : State.STRAIGHT); state(state == State.STRAIGHT ? State.LEFT : State.STRAIGHT,shift);
return o; return o;
} }

3
src/main/java/de/srsoftware/web4rail/tiles/TurnoutR.java

@ -15,13 +15,12 @@ public abstract class TurnoutR extends Turnout {
@Override @Override
public Object click(boolean shift) throws IOException { public Object click(boolean shift) throws IOException {
Object o = super.click(shift); Object o = super.click(shift);
if (!shift) state(state == State.STRAIGHT ? State.RIGHT : State.STRAIGHT); state(state == State.STRAIGHT ? State.RIGHT : State.STRAIGHT,shift);
return o; return o;
} }
@Override @Override
public String commandFor(State newState) { public String commandFor(State newState) {
switch (newState) { switch (newState) {
case RIGHT: case RIGHT:
return "SET {} GA "+address+" "+portB+" 1 "+delay; return "SET {} GA "+address+" "+portB+" 1 "+delay;

Loading…
Cancel
Save