Browse Source

resetting route planner

lookup-tables
Stephan Richter 4 years ago
parent
commit
9939d8d630
  1. 2
      pom.xml
  2. 11
      src/main/java/de/srsoftware/web4rail/Plan.java
  3. 12
      src/main/java/de/srsoftware/web4rail/Route.java
  4. 86
      src/main/java/de/srsoftware/web4rail/moving/Train.java
  5. 98
      src/main/java/de/srsoftware/web4rail/threads/BrakeProcessor.java
  6. 185
      src/main/java/de/srsoftware/web4rail/threads/PathFinder.java
  7. 15
      src/main/java/de/srsoftware/web4rail/tiles/Tile.java

2
pom.xml

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

11
src/main/java/de/srsoftware/web4rail/Plan.java

@ -36,7 +36,6 @@ import de.srsoftware.web4rail.tags.Input; @@ -36,7 +36,6 @@ import de.srsoftware.web4rail.tags.Input;
import de.srsoftware.web4rail.tags.Label;
import de.srsoftware.web4rail.tags.Table;
import de.srsoftware.web4rail.tags.Window;
import de.srsoftware.web4rail.threads.BrakeProcessor;
import de.srsoftware.web4rail.threads.ControlUnit;
import de.srsoftware.web4rail.tiles.Block;
import de.srsoftware.web4rail.tiles.BlockContact;
@ -357,7 +356,7 @@ public class Plan extends BaseClass{ @@ -357,7 +356,7 @@ public class Plan extends BaseClass{
new Input(ACTION,ACTION_UPDATE).hideIn(form);
new Input(LENGTH_UNIT, lengthUnit).addTo(new Label(t("Length unit")+COL)).addTo(form);
new Input(SPEED_UNIT, speedUnit).addTo(new Label(t("Speed unit")+COL)).addTo(form);
new Input(FINAL_SPEED, BrakeProcessor.defaultEndSpeed).addTo(new Label(t("Lower speed limit")+COL)).attr("title", t("Final speed after breaking, before halting")).addTo(form); // TODO
new Input(FINAL_SPEED, Train.defaultEndSpeed).addTo(new Label(t("Lower speed limit")+COL)).attr("title", t("Final speed after breaking, before halting")).addTo(form); // TODO
new Checkbox(FREE_BEHIND_TRAIN, t("Free tiles behind train"), Route.freeBehindTrain).attr("title", t("If checked, tiles behind the train are freed according to the length of the train and the tiles. If it is unchecked, tiles will not get free before route is finished.")).addTo(form);
new Button(t("Save"), form).addTo(form);
form.addTo(fieldset);
@ -491,7 +490,7 @@ public class Plan extends BaseClass{ @@ -491,7 +490,7 @@ public class Plan extends BaseClass{
.forEach(jTiles::put);
return new JSONObject()
.put(FINAL_SPEED, BrakeProcessor.defaultEndSpeed)
.put(FINAL_SPEED, Train.defaultEndSpeed)
.put(FREE_BEHIND_TRAIN, Route.freeBehindTrain)
.put(LENGTH_UNIT, lengthUnit)
.put(SPEED_UNIT, speedUnit)
@ -525,7 +524,7 @@ public class Plan extends BaseClass{ @@ -525,7 +524,7 @@ public class Plan extends BaseClass{
if (json.has(TILE)) json.getJSONArray(TILE).forEach(object -> Tile.load(object, plan));
if (json.has(LENGTH_UNIT)) lengthUnit = json.getString(LENGTH_UNIT);
if (json.has(SPEED_UNIT)) speedUnit = json.getString(SPEED_UNIT);
if (json.has(FINAL_SPEED)) BrakeProcessor.defaultEndSpeed = json.getInt(FINAL_SPEED);
if (json.has(FINAL_SPEED)) Train.defaultEndSpeed = json.getInt(FINAL_SPEED);
if (json.has(FREE_BEHIND_TRAIN)) Route.freeBehindTrain = json.getBoolean(FREE_BEHIND_TRAIN);
try {
@ -691,7 +690,7 @@ public class Plan extends BaseClass{ @@ -691,7 +690,7 @@ public class Plan extends BaseClass{
formInputs.add(null, new Input(ACTION,ACTION_UPDATE));
formInputs.add(t("Length unit"),new Input(LENGTH_UNIT, lengthUnit));
formInputs.add(t("Speed unit"),new Input(SPEED_UNIT, speedUnit));
formInputs.add(t("Lower speed limit"),new Input(FINAL_SPEED, BrakeProcessor.defaultEndSpeed).attr("title", t("Final speed after breaking, before halting")));
formInputs.add(t("Lower speed limit"),new Input(FINAL_SPEED, Train.defaultEndSpeed).attr("title", t("Final speed after breaking, before halting")));
formInputs.add(t("Free tiles behind train"),new Checkbox(FREE_BEHIND_TRAIN, t("If checked, tiles behind the train are freed according to the length of the train and the tiles. If it is unchecked, tiles will not get free before route is finished."), Route.freeBehindTrain));
postForm.add(relayProperties());
@ -997,7 +996,7 @@ public class Plan extends BaseClass{ @@ -997,7 +996,7 @@ public class Plan extends BaseClass{
if (params.containsKey(LENGTH_UNIT)) lengthUnit = params.get(LENGTH_UNIT);
if (params.containsKey(SPEED_UNIT)) speedUnit = params.get(SPEED_UNIT);
if (params.containsKey(FINAL_SPEED)) BrakeProcessor.defaultEndSpeed = Integer.parseInt(params.get(FINAL_SPEED));
if (params.containsKey(FINAL_SPEED)) Train.defaultEndSpeed = Integer.parseInt(params.get(FINAL_SPEED));
Route.freeBehindTrain = "on".equalsIgnoreCase(params.get(FREE_BEHIND_TRAIN));
return t("Plan updated.");

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

@ -37,7 +37,6 @@ import de.srsoftware.web4rail.tags.Fieldset; @@ -37,7 +37,6 @@ import de.srsoftware.web4rail.tags.Fieldset;
import de.srsoftware.web4rail.tags.Input;
import de.srsoftware.web4rail.tags.Table;
import de.srsoftware.web4rail.tags.Window;
import de.srsoftware.web4rail.threads.PathFinder;
import de.srsoftware.web4rail.tiles.Block;
import de.srsoftware.web4rail.tiles.BlockContact;
import de.srsoftware.web4rail.tiles.Contact;
@ -88,7 +87,6 @@ public class Route extends BaseClass { @@ -88,7 +87,6 @@ public class Route extends BaseClass {
public Direction endDirection;
private Vector<Tile> path;
private Vector<Signal> signals;
//private Train train;
private HashMap<String,ActionList> triggeredActions = new HashMap<String, ActionList>();
private HashMap<Turnout,Turnout.State> turnouts;
private Block startBlock = null;
@ -417,14 +415,8 @@ public class Route extends BaseClass { @@ -417,14 +415,8 @@ public class Route extends BaseClass {
}
public boolean isFreeFor(Train newTrain) {
PathFinder.LOG.debug("{}.isFreeFor({})",this,newTrain);
for (int i=1; i<path.size(); i++) {
if (!path.get(i).canNeEnteredBy(newTrain)) {
PathFinder.LOG.debug("{}.isFreeFor(...) → false",this);
return false;
}
}
return true;
LOG.debug("{}.isFreeFor({})",this,newTrain);
return false;
}
/**

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

@ -36,8 +36,6 @@ import de.srsoftware.web4rail.tags.Label; @@ -36,8 +36,6 @@ import de.srsoftware.web4rail.tags.Label;
import de.srsoftware.web4rail.tags.Select;
import de.srsoftware.web4rail.tags.Table;
import de.srsoftware.web4rail.tags.Window;
import de.srsoftware.web4rail.threads.BrakeProcessor;
import de.srsoftware.web4rail.threads.PathFinder;
import de.srsoftware.web4rail.tiles.Block;
import de.srsoftware.web4rail.tiles.BlockContact;
import de.srsoftware.web4rail.tiles.Contact;
@ -55,6 +53,8 @@ public class Train extends BaseClass implements Comparable<Train> { @@ -55,6 +53,8 @@ public class Train extends BaseClass implements Comparable<Train> {
private static final String TRACE = "trace";
private static final String NAME = "name";
public static int defaultEndSpeed;
private String name = null;
@ -91,11 +91,6 @@ public class Train extends BaseClass implements Comparable<Train> { @@ -91,11 +91,6 @@ public class Train extends BaseClass implements Comparable<Train> {
public int speed = 0;
private static final String SHUNTING = "shunting";
private boolean shunting = false;
private BrakeProcessor brakeProcessor;
private PathFinder pathFinder;
private boolean autopilot = false;
public static Object action(HashMap<String, String> params, Plan plan) throws IOException {
@ -402,18 +397,7 @@ public class Train extends BaseClass implements Comparable<Train> { @@ -402,18 +397,7 @@ public class Train extends BaseClass implements Comparable<Train> {
}
public void endRoute(Block newBlock, Direction newDirection) {
if (isSet(brakeProcessor)) {
brakeProcessor.end();
} else setSpeed(0);
set(newBlock);
if (newBlock == destination) {
destination = null;
quitAutopilot();
}
heading(newDirection);
route = null;
brakeProcessor = null;
if (autopilot) start(false);
}
private Tag faster(int steps) {
@ -772,16 +756,6 @@ public class Train extends BaseClass implements Comparable<Train> { @@ -772,16 +756,6 @@ public class Train extends BaseClass implements Comparable<Train> {
}
}
private void set(BrakeProcessor bp) {
LOG.debug("{}.set({})",this,bp);
brakeProcessor = bp;
}
private void set(PathFinder pf) {
LOG.debug("{}.set({})",this,pf);
pathFinder = pf;
}
private Object setDestination(HashMap<String, String> params) {
String dest = params.get(DESTINATION);
if (isNull(dest)) return properties(t("No destination supplied!"));
@ -884,37 +858,7 @@ public class Train extends BaseClass implements Comparable<Train> { @@ -884,37 +858,7 @@ public class Train extends BaseClass implements Comparable<Train> {
public String start(boolean autopilot) {
this.autopilot |= autopilot;
if (isSet(route)) return t("{} already on {}!",this,route);
if (isSet(pathFinder)) return t("Pathfinder already active for {}!",this);
PathFinder pathFinder = new PathFinder(this,currentBlock,direction) {
@Override
public void aborted() {
plan.stream(t("Aborting route allocation..."));
LOG.debug("{} aborted",this);
}
@Override
public void found(Route newRoute) {
LOG.debug("Found route {} for {}",newRoute,Train.this);
}
@Override
public void locked(Route newRoute) {
LOG.debug("Locked route {} for {}",newRoute,Train.this);
route = newRoute;
}
@Override
public void prepared(Route newRoute) {
LOG.debug("Prepared route {} for {}",newRoute,Train.this);
set((PathFinder)null);
route.start(Train.this);
}
};
set(pathFinder);
return null;
return t("{}.start() not implemented",this);
}
public static void startAll() {
@ -922,28 +866,10 @@ public class Train extends BaseClass implements Comparable<Train> { @@ -922,28 +866,10 @@ public class Train extends BaseClass implements Comparable<Train> {
for (Train train : BaseClass.listElements(Train.class)) LOG.info(train.start(true));
}
public void startBrake() {
if (isNull(route)) {
LOG.warn("{}.startBrake() called, but train ist not on a route!",this);
return;
}
if (isSet(brakeProcessor)) {
LOG.debug("{} already is braking.");
return;
}
set(new BrakeProcessor(this));
}
public void startBrake() {}
public Window stopNow() {
setSpeed(0);
if (isSet(pathFinder)) {
pathFinder.abort();
set((PathFinder)null);
}
if (isSet(route)) {
route.reset();
route = null;
}
quitAutopilot();
return properties();
}
@ -1041,8 +967,6 @@ public class Train extends BaseClass implements Comparable<Train> { @@ -1041,8 +967,6 @@ public class Train extends BaseClass implements Comparable<Train> {
public boolean isStoppable() {
if (speed > 0) return true;
if (isSet(pathFinder)) return true;
if (isSet(route)) return true;
return false;
}
}

98
src/main/java/de/srsoftware/web4rail/threads/BrakeProcessor.java

@ -1,98 +0,0 @@ @@ -1,98 +0,0 @@
package de.srsoftware.web4rail.threads;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.srsoftware.web4rail.BaseClass;
import de.srsoftware.web4rail.Route;
import de.srsoftware.web4rail.moving.Train;
/**
* @author Stephan Richter, SRSoftware
*
*/
public class BrakeProcessor extends BaseClass implements Runnable {
private enum State {
IDLE, BRAKING, ABORTED, ENDED;
}
private static final Logger LOG = LoggerFactory.getLogger(BrakeProcessor.class);
public static int defaultEndSpeed;
private Train train;
private State state = State.IDLE;
private long measuredDistance;
private long lastTime;
private Integer brakeTime;
private int startSpeed;
public BrakeProcessor(Train train) {
this.train = train;
Thread thread = new Thread(this);
thread.setName(getClass().getSimpleName());
thread.start();
}
public void end() {
state = State.ENDED;
measuredDistance += train.speed * (BaseClass.timestamp() - lastTime);
train.setSpeed(0);
Route route = train.route();
if (isNull(route)) return;
LOG.debug("old brake time: {}, measured distance: {}", brakeTime, measuredDistance);
int step = brakeTime;
for (int i = 0; i < 15; i++) {
long calculatedDistance = calculate(brakeTime, startSpeed);
if (measuredDistance > calculatedDistance) {
brakeTime += brakeTime;
step = brakeTime/3;
}
if (measuredDistance < calculatedDistance) {
step /= 2;
if (step < 1) step = 1;
brakeTime -= step;
}
LOG.debug("new brake time: {}, calculated distance: {}", brakeTime, calculatedDistance);
}
route.brakeTime(train.brakeId(), brakeTime);
}
private static long calculate(int brakeTime, int speed) {
long dist = 0;
while (speed > defaultEndSpeed) {
dist += speed * brakeTime;
speed -= 10;
}
return dist;
}
@Override
public void run() {
LOG.debug("run()");
Route route = train.route();
if (isNull(route)) return;
brakeTime = route.brakeTime(train.brakeId());
if (isNull(brakeTime)) brakeTime = 250;
state = State.BRAKING;
measuredDistance = 0;
lastTime = BaseClass.timestamp();
startSpeed = train.speed;
int targetSpeed = defaultEndSpeed;
while (state == State.BRAKING) {
sleep(brakeTime);
long newTime = BaseClass.timestamp();
if (isNull(train.route())) state = State.ABORTED;
if (state != State.BRAKING) break;
measuredDistance += train.speed * (newTime - lastTime);
int newSpeed = train.speed - 10;
if (newSpeed < targetSpeed) {
train.setSpeed(targetSpeed);
break;
}
train.setSpeed(newSpeed);
lastTime = newTime;
}
LOG.debug("{} reached final speed.", train);
}
}

185
src/main/java/de/srsoftware/web4rail/threads/PathFinder.java

@ -1,185 +0,0 @@ @@ -1,185 +0,0 @@
package de.srsoftware.web4rail.threads;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.Vector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.srsoftware.web4rail.BaseClass;
import de.srsoftware.web4rail.Plan.Direction;
import de.srsoftware.web4rail.Route;
import de.srsoftware.web4rail.moving.Train;
import de.srsoftware.web4rail.tiles.Block;
/**
* @author Stephan Richter, SRSoftware 2020-2021
*/
public abstract class PathFinder extends BaseClass implements Runnable{
public static final Logger LOG = LoggerFactory.getLogger(PathFinder.class);
// private Context context;
private boolean aborted = false;
private Direction direction;
private Block startBlock;
private Train train;
public PathFinder(Train train, Block start, Direction direction) {
this.train = train;
this.startBlock = start;
this.direction = direction;
Thread thread = new Thread(this);
thread.setName("Pathfinder("+train+")");
thread.start();
}
public void abort() {
aborted = true;
aborted();
LOG.debug("aborted {}",this);
}
private static TreeMap<Integer,List<Route>> availableRoutes(Train train, Block start, Direction startDir, HashSet<Route> visitedRoutes){
String inset = "";
for (int i=0; i<visitedRoutes.size(); i++) inset+=" ";
LOG.debug(inset+"PathFinder.availableRoutes({})",visitedRoutes);
boolean error = false;
if (isNull(start)) {
LOG.warn("{} → {}.availableRoutes called without start block!",inset,Train.class.getSimpleName());
error = true;
}
if (isNull(train)) {
LOG.warn("{}→ {}.availableRoutes called without train!",inset,Train.class.getSimpleName());
error = true;
}
if (error) return new TreeMap<Integer, List<Route>>();
if (isSet(startDir)) {
LOG.debug("{}Looking for {}-bound routes from {}",inset,startDir,start);
} else {
LOG.debug("{}Looking for all routes from {}",inset,start);
}//*/
Block destination = train.destination();
if (isSet(destination) && visitedRoutes.isEmpty()) LOG.debug("{}- Destination: {}",inset,destination);
//Route currentRoute = context.route();
TreeMap<Integer,List<Route>> availableRoutes = new TreeMap<Integer, List<Route>>();
for (Route routeCandidate : start.routes()) {
if (routeCandidate.path().firstElement() != start) continue; // Routen, die nicht vom aktuellen Block starten sind bubu
if (visitedRoutes.contains(routeCandidate)) {
LOG.debug("{}→ Candidate {} would create loop, skipping",inset,routeCandidate.shortName());
continue;
}
Context c = new Context(train).block(start).direction(startDir);
if (!routeCandidate.allowed(c)) {
if (routeCandidate.endBlock() != destination) { // allowance may be overridden by destination
LOG.debug("{} not allowed for {}",routeCandidate,c);
continue; // Zug darf auf Grund einer nicht erfüllten Bedingung nicht auf die Route
}
LOG.debug("{} not allowed for {} – overridden by selected destination",routeCandidate,c);
}
int priority = 0;
if (isSet(startDir) && routeCandidate.startDirection != startDir) { // Route startet entgegen der aktuellen Fahrtrichtung des Zuges
if (!train.pushPull) continue; // Zug kann nicht wenden
if (!start.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 (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());
visitedRoutes.add(routeCandidate);
TreeMap<Integer, List<Route>> forwardRoutes = availableRoutes(train, routeCandidate.endBlock(), routeCandidate.endDirection, 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 (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 (!availableRoutes.isEmpty()) LOG.debug("{}→ Routes from {}: {}",inset,start,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;
}
public Route chooseRoute() {
LOG.debug("PathFinder.chooseRoute()");
HashSet<Route> visitedRoutes = new HashSet<Route>();
TreeMap<Integer, List<Route>> availableRoutes = availableRoutes(train, startBlock, direction,visitedRoutes);
while (!availableRoutes.isEmpty()) {
LOG.debug("availableRoutes: {}",availableRoutes);
Entry<Integer, List<Route>> entry = availableRoutes.lastEntry();
List<Route> preferredRoutes = entry.getValue();
LOG.debug("preferredRoutes: {}",preferredRoutes);
Route selectedRoute = preferredRoutes.get(random.nextInt(preferredRoutes.size()));
if (selectedRoute.isFreeFor(train)) {
LOG.debug("Chose \"{}\" with priority {}.",selectedRoute,entry.getKey());
return selectedRoute;
}
LOG.debug("Selected route \"{}\" is not free for {}",selectedRoute,train);
preferredRoutes.remove(selectedRoute);
if (preferredRoutes.isEmpty()) availableRoutes.remove(availableRoutes.lastKey());
}
return null;
}
@Override
public void run() {
while (true) {
if (aborted) return;
Route route = chooseRoute();
if (isSet(route)) {
if (aborted) return;
found(route);
if (route.allocateFor(train)) {
if (aborted) {
route.reset();
return;
}
locked(route);
if (route.prepareFor(train)) {
if (aborted) {
route.reset();
return;
}
prepared(route);
return;
}
}
}
sleep(1000);
}
}
public abstract void aborted();
public abstract void locked(Route r);
public abstract void found(Route r);
public abstract void prepared(Route r);
}

15
src/main/java/de/srsoftware/web4rail/tiles/Tile.java

@ -24,15 +24,14 @@ import de.srsoftware.web4rail.BaseClass; @@ -24,15 +24,14 @@ import de.srsoftware.web4rail.BaseClass;
import de.srsoftware.web4rail.Connector;
import de.srsoftware.web4rail.Plan;
import de.srsoftware.web4rail.Plan.Direction;
import de.srsoftware.web4rail.actions.AlterDirection;
import de.srsoftware.web4rail.Route;
import de.srsoftware.web4rail.actions.AlterDirection;
import de.srsoftware.web4rail.moving.Train;
import de.srsoftware.web4rail.tags.Checkbox;
import de.srsoftware.web4rail.tags.Fieldset;
import de.srsoftware.web4rail.tags.Input;
import de.srsoftware.web4rail.tags.Radio;
import de.srsoftware.web4rail.tags.Window;
import de.srsoftware.web4rail.threads.PathFinder;
/**
* Base class for all tiles
@ -82,28 +81,28 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{ @@ -82,28 +81,28 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
}
public boolean canNeEnteredBy(Train newTrain) {
PathFinder.LOG.debug("{}.canNeEnteredBy({})",this,newTrain);
LOG.debug("{}.canNeEnteredBy({})",this,newTrain);
if (disabled) {
PathFinder.LOG.debug("{} is disabled!",this);
LOG.debug("{} is disabled!",this);
return false;
}
if (isNull(train)) {
PathFinder.LOG.debug("→ free");
LOG.debug("→ free");
return true;
}
if (newTrain == train) { // during train.reserveNext, we may encounter, parts, that are already reserved by the respective train, but having another route. do not compare routes in that case!
PathFinder.LOG.debug("already reserved by {} → true",train);
LOG.debug("already reserved by {} → true",train);
return true;
}
if (isSet(newTrain) && newTrain.isShunting()) {
PathFinder.LOG.debug("occupied by {}. Allowed for shunting {}",train,newTrain);
LOG.debug("occupied by {}. Allowed for shunting {}",train,newTrain);
return true;
}
PathFinder.LOG.debug("occupied by {} → false",train);
LOG.debug("occupied by {} → false",train);
return false;
}

Loading…
Cancel
Save