optimised brake code

This commit is contained in:
Stephan Richter
2021-02-11 19:40:35 +01:00
parent c515e922dc
commit 2973bd1432
6 changed files with 74 additions and 94 deletions

View File

@@ -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.3.40</version> <version>1.3.41</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>

View File

@@ -347,7 +347,7 @@ public class Plan extends BaseClass{
new Input(ACTION,ACTION_UPDATE).hideIn(form); new Input(ACTION,ACTION_UPDATE).hideIn(form);
new Input(LENGTH_UNIT, lengthUnit).addTo(new Label(t("Length unit")+COL)).addTo(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(SPEED_UNIT, speedUnit).addTo(new Label(t("Speed unit")+COL)).addTo(form);
new Input(FINAL_SPEED, Route.endSpeed).addTo(new Label(t("Lower speed limit")+COL)).attr("title", t("Final speed after breaking, before halting")).addTo(form); new Input(FINAL_SPEED, Route.defaultEndSpeed).addTo(new Label(t("Lower speed limit")+COL)).attr("title", t("Final speed after breaking, before halting")).addTo(form);
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 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); new Button(t("Save"), form).addTo(form);
form.addTo(fieldset); form.addTo(fieldset);
@@ -481,7 +481,7 @@ public class Plan extends BaseClass{
.forEach(jTiles::put); .forEach(jTiles::put);
return new JSONObject() return new JSONObject()
.put(FINAL_SPEED, Route.endSpeed) .put(FINAL_SPEED, Route.defaultEndSpeed)
.put(FREE_BEHIND_TRAIN, Route.freeBehindTrain) .put(FREE_BEHIND_TRAIN, Route.freeBehindTrain)
.put(LENGTH_UNIT, lengthUnit) .put(LENGTH_UNIT, lengthUnit)
.put(SPEED_UNIT, speedUnit) .put(SPEED_UNIT, speedUnit)
@@ -515,7 +515,7 @@ public class Plan extends BaseClass{
if (json.has(TILE)) json.getJSONArray(TILE).forEach(object -> Tile.load(object, plan)); 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(LENGTH_UNIT)) lengthUnit = json.getString(LENGTH_UNIT);
if (json.has(SPEED_UNIT)) speedUnit = json.getString(SPEED_UNIT); if (json.has(SPEED_UNIT)) speedUnit = json.getString(SPEED_UNIT);
if (json.has(FINAL_SPEED)) Route.endSpeed = json.getInt(FINAL_SPEED); if (json.has(FINAL_SPEED)) Route.defaultEndSpeed = json.getInt(FINAL_SPEED);
if (json.has(FREE_BEHIND_TRAIN)) Route.freeBehindTrain = json.getBoolean(FREE_BEHIND_TRAIN); if (json.has(FREE_BEHIND_TRAIN)) Route.freeBehindTrain = json.getBoolean(FREE_BEHIND_TRAIN);
try { try {
@@ -973,7 +973,7 @@ public class Plan extends BaseClass{
if (params.containsKey(LENGTH_UNIT)) lengthUnit = params.get(LENGTH_UNIT); if (params.containsKey(LENGTH_UNIT)) lengthUnit = params.get(LENGTH_UNIT);
if (params.containsKey(SPEED_UNIT)) speedUnit = params.get(SPEED_UNIT); if (params.containsKey(SPEED_UNIT)) speedUnit = params.get(SPEED_UNIT);
if (params.containsKey(FINAL_SPEED)) Route.endSpeed = Integer.parseInt(params.get(FINAL_SPEED)); if (params.containsKey(FINAL_SPEED)) Route.defaultEndSpeed = Integer.parseInt(params.get(FINAL_SPEED));
Route.freeBehindTrain = "on".equalsIgnoreCase(params.get(FREE_BEHIND_TRAIN)); Route.freeBehindTrain = "on".equalsIgnoreCase(params.get(FREE_BEHIND_TRAIN));
return t("Plan updated."); return t("Plan updated.");

View File

@@ -71,50 +71,51 @@ public class Route extends BaseClass {
static final String SIGNALS = "signals"; static final String SIGNALS = "signals";
static final String TURNOUTS = "turnouts"; static final String TURNOUTS = "turnouts";
private State state = State.FREE; private State state = State.FREE;
public static int endSpeed = 10; public static int defaultEndSpeed = 10;
public static boolean freeBehindTrain = true; public static boolean freeBehindTrain = true;
private static final String ROUTE_START = "route_start"; private static final String ROUTE_START = "route_start";
private static final String ROUTE_SETUP = "route_setup"; private static final String ROUTE_SETUP = "route_setup";
private int startSpeed;
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 class BrakeProcessor extends Thread { private class BrakeProcessor extends Thread {
private long timestamp; private long latestTick;
private static final int SPEED_STEP = 5; private static final int SPEED_STEP = 5;
private Integer timeStep; private Integer timeStep;
private Route route; private Route route;
private Train train; private Train train;
private boolean aborted = false;
private String brakeId; private String brakeId;
private long estimatedDistance; // Unit: s*km/h "km/h-Sekunden" private long estimatedDistance; // Unit: s*km/h "km/h-Sekunden"
private int startSpeed,endSpeed;
private boolean aborted,modified;
public BrakeProcessor(Route route, Train train) { public BrakeProcessor(Route route, Train train) {
this.train = train; this.train = train;
this.route = route; this.route = route;
aborted = false;
modified = false;
estimatedDistance = 0; estimatedDistance = 0;
brakeId = train.brakeId(); brakeId = train.brakeId();
startSpeed = train.speed; startSpeed = train.speed;
endSpeed = defaultEndSpeed;
timeStep = brakeTimes.get(brakeId); timeStep = brakeTimes.get(brakeId);
// if no brake time is available for this train, use the fastest brake time already known for this route:
if (isNull(timeStep)) timeStep = 100; if (isNull(timeStep)) timeStep = 100; // if no brake time is available for this train
Application.threadPool.execute(this); Application.threadPool.execute(this);
} }
public void abort() {
aborted = true;
train.setSpeed(startSpeed);
}
protected void abort() {
aborted = true;
}
private long calcDistance(Integer ts) { private long calcDistance(Integer ts) {
long dist = 0; long dist = 0;
int s = startSpeed; int s = startSpeed;
while (s > endSpeed) { while (s > defaultEndSpeed) {
s -= SPEED_STEP; s -= SPEED_STEP;
dist += s*ts; dist += s*ts;
} }
@@ -126,59 +127,60 @@ public class Route extends BaseClass {
* This is called from route.finish when train came to stop * This is called from route.finish when train came to stop
*/ */
public void finish() { public void finish() {
LOG.debug("BrakeProcessor.finish(){}",aborted?" got aborted":""); LOG.debug("BrakeProcessor.finish()");
if (aborted) return; if (aborted || modified) return;
long runtime = 2+BaseClass.timestamp() - timestamp; increaseDistance();
estimatedDistance += train.speed * runtime;
train.setSpeed(0); train.setSpeed(0);
LOG.debug("Estimated distance: {}",estimatedDistance); LOG.debug("Estimated distance: {}",estimatedDistance);
if (startSpeed <= endSpeed) return; if (startSpeed <= endSpeed) return;
Integer newTimeStep = timeStep; Integer newTimeStep = timeStep;
while (calcDistance(newTimeStep) < estimatedDistance) { // zu schnell gebremst while (calcDistance(newTimeStep) < estimatedDistance) newTimeStep += (1+newTimeStep/8); // zu schnell gebremst: pasue verlängern
newTimeStep += (1+newTimeStep/8); while (calcDistance(newTimeStep) > estimatedDistance) newTimeStep -= 1+(newTimeStep/16); // zu langsam gebremst: pasue verkürzen
}
while (calcDistance(newTimeStep) > estimatedDistance) { // zu langsam gebremst
newTimeStep -= 1+(newTimeStep/16);
}
if (newTimeStep != timeStep) { if (newTimeStep != timeStep) {
route.brakeTimes.put(brakeId,newTimeStep); route.brakeTimes.put(brakeId,newTimeStep);
LOG.debug("Corrected brake timestep for {} @ {} from {} to {} ms.",train,route,timeStep,newTimeStep); LOG.debug("Corrected brake timestep for {} @ {} from {} to {} ms.",train,route,timeStep,newTimeStep);
} }
} }
private void increaseDistance(){
long tick = timestamp();
estimatedDistance += train.speed * (tick-latestTick);
latestTick = tick;
}
@Override @Override
public void run() { public void run() {
timestamp = timestamp(); LOG.debug("started BrakeProcessor ({} → {}) for {} with timestep = {} ms.",train.speed,endSpeed,train,timeStep);
Integer nextRouteSpeed = null; latestTick = timestamp();
if (train.speed == 0) aborted = true;
while (train.speed > endSpeed) { while (train.speed > endSpeed) {
if (train.nextRoutePrepared()) {
if (isNull(nextRouteSpeed)) nextRouteSpeed = train.nextRoute().startSpeed(); // get the starting speed of the next route
if (isNull(nextRouteSpeed)) {
LOG.warn("Train has prepared next route, but that route seems to have no start speed!?");
nextRouteSpeed = 0; // assume starting speed of zero
}
if (train.speed < nextRouteSpeed) { // train already is slower: stop braking, set train speed to next route's start speed
train.setSpeed(nextRouteSpeed);
abort();
}
// if train is still faster than starting speed for next route: continue braking
}
if (aborted) break;
LOG.debug("BrakeProcessor({}) setting Speed of {}.",route,train); LOG.debug("BrakeProcessor({}) setting Speed of {}.",route,train);
long runtime = BaseClass.timestamp() - timestamp; increaseDistance();
timestamp = timestamp+runtime; train.setSpeed(Math.max(train.speed - SPEED_STEP,endSpeed));
estimatedDistance += train.speed * runtime;
train.setSpeed(Math.max(train.speed - SPEED_STEP,endSpeed));
try { try {
sleep(timeStep); sleep(timeStep);
} catch (InterruptedException e) { } catch (InterruptedException e) {
LOG.warn("BrakeProcessor interrupted!", e); LOG.warn("BrakeProcessor interrupted!", e);
} }
Route nextRoute = train.nextRoute();
if (!modified && isSet(nextRoute) && nextRoute.state == Route.State.PREPARED) { // auf Startgeschwindigkeit der Nachfolgeroute bremsen
Integer nrsp = nextRoute.startSpeed();
if (isSet(nrsp)) {
LOG.debug("updating target velocity from {} to {}!",endSpeed,nrsp);
endSpeed = nrsp;
modified = true;
}
}
} }
if (endSpeed > train.speed) train.setSpeed(endSpeed);
}
public void setEndSpeed(Integer newEndSpeed) {
if (isNull(newEndSpeed)) return;
endSpeed = newEndSpeed;
modified = true;
} }
} }
@@ -360,11 +362,6 @@ public class Route extends BaseClass {
return this; return this;
} }
public void brakeCancel() {
if (isSet(brakeProcessor)) brakeProcessor.abort();
brakeProcessor = null;
}
public void brakeStart() { public void brakeStart() {
if (isNull(train)) return; if (isNull(train)) return;
brakeProcessor = new BrakeProcessor(this,train); brakeProcessor = new BrakeProcessor(this,train);
@@ -480,10 +477,8 @@ public class Route extends BaseClass {
LOG.debug("{}.finish()",this); LOG.debug("{}.finish()",this);
if (isSet(train)) { if (isSet(train)) {
if (train.nextRoutePrepared()) { Route nextRoute = train.nextRoute();
LOG.debug("{} has prepared next route: {}",train,train.nextRoute()); if (!isSet(nextRoute)) {
if (isSet(brakeProcessor)) brakeProcessor.abort();
} else {
LOG.debug("{} has no next route.",train); LOG.debug("{} has no next route.",train);
if (isSet(brakeProcessor)) { if (isSet(brakeProcessor)) {
brakeProcessor.finish(); brakeProcessor.finish();
@@ -508,7 +503,7 @@ public class Route extends BaseClass {
* frees all tiles occupied by this route * frees all tiles occupied by this route
*/ */
private void free() { private void free() {
LOG.debug("{}.reset()",this); LOG.debug("{}.free()",this);
context.clear(); // prevent delayed actions from firing after route has finished context.clear(); // prevent delayed actions from firing after route has finished
setSignals(Signal.RED); setSignals(Signal.RED);
@@ -760,11 +755,11 @@ public class Route extends BaseClass {
} }
public boolean lock() { public boolean lock() {
LOG.debug("{}.lock()",this);
return lockIgnoring(null); return lockIgnoring(null);
} }
public boolean lockIgnoring(Route ignoredRoute) { public boolean lockIgnoring(Route ignoredRoute) {
if (state == State.LOCKED || state == State.PREPARED || state == State.STARTED) return true;
LOG.debug("{}.lockIgnoring({})",this,ignoredRoute); LOG.debug("{}.lockIgnoring({})",this,ignoredRoute);
HashSet<Tile> ignoredPath = new HashSet<Tile>(); HashSet<Tile> ignoredPath = new HashSet<Tile>();
if (isSet(ignoredRoute)) ignoredPath.addAll(ignoredRoute.path); if (isSet(ignoredRoute)) ignoredPath.addAll(ignoredRoute.path);
@@ -856,6 +851,7 @@ public class Route extends BaseClass {
} }
public boolean prepare() { public boolean prepare() {
if (state == State.PREPARED || state == State.STARTED) return true;
LOG.debug("{}.prepare()",this); LOG.debug("{}.prepare()",this);
ActionList setupActions = triggeredActions.get(ROUTE_SETUP); ActionList setupActions = triggeredActions.get(ROUTE_SETUP);
if (isSet(setupActions) && !setupActions.fire(context)) return false; if (isSet(setupActions) && !setupActions.fire(context)) return false;
@@ -871,6 +867,11 @@ public class Route extends BaseClass {
return script; return script;
} }
public Route prolong(Route nextRoute) {
if (isSet(brakeProcessor)) brakeProcessor.setEndSpeed(nextRoute.startSpeed());
return nextRoute;
}
@Override @Override
protected Window properties(List<Fieldset> preForm, FormInput formInputs, List<Fieldset> postForm) { protected Window properties(List<Fieldset> preForm, FormInput formInputs, List<Fieldset> postForm) {
@@ -933,6 +934,7 @@ public class Route extends BaseClass {
LOG.debug("{}.reset()",this); LOG.debug("{}.reset()",this);
free(); free();
if (isSet(brakeProcessor)) brakeProcessor.abort();
if (isSet(train)) { if (isSet(train)) {
train.set(startBlock); train.set(startBlock);
train.heading(startDirection); train.heading(startDirection);
@@ -994,6 +996,7 @@ public class Route extends BaseClass {
} }
public boolean start(Train newTrain) { public boolean start(Train newTrain) {
if (state == State.STARTED) return true;
LOG.debug("{}.start({})",this,newTrain); LOG.debug("{}.start({})",this,newTrain);
if (isNull(newTrain)) return false; // can't set route's train to null if (isNull(newTrain)) return false; // can't set route's train to null
if (isSet(train)) { if (isSet(train)) {

View File

@@ -44,7 +44,6 @@ public abstract class Action extends BaseClass {
AddDestination.class, AddDestination.class,
AddRemoveTag.class, AddRemoveTag.class,
AlterDirection.class, AlterDirection.class,
BrakeCancel.class,
BrakeStart.class, BrakeStart.class,
ConditionalAction.class, ConditionalAction.class,
CoupleTrain.class, CoupleTrain.class,

View File

@@ -1,17 +0,0 @@
package de.srsoftware.web4rail.actions;
import de.srsoftware.web4rail.BaseClass;
public class BrakeCancel extends Action {
public BrakeCancel(BaseClass parent) {
super(parent);
}
@Override
public boolean fire(Context context) {
if (isNull(context.route())) return false;
context.route().brakeCancel();
return true;
}
}

View File

@@ -648,11 +648,6 @@ public class Train extends BaseClass implements Comparable<Train> {
return nextRoute; return nextRoute;
} }
public boolean nextRoutePrepared() {
return isSet(nextRoute) && nextRoute.state() == Route.State.PREPARED;
}
public boolean onTrace(Tile t) { public boolean onTrace(Tile t) {
return trace.contains(t); return trace.contains(t);
} }
@@ -760,21 +755,22 @@ public class Train extends BaseClass implements Comparable<Train> {
public void reserveNext() { public void reserveNext() {
LOG.debug("{}.reserveNext()",this); LOG.debug("{}.reserveNext()",this);
Context context = new Context(this).route(route).block(route.endBlock()).direction(route.endDirection); Context context = new Context(this).route(route).block(route.endBlock()).direction(route.endDirection);
Route nextRoute = PathFinder.chooseRoute(context); Route newRoute = PathFinder.chooseRoute(context);
if (isNull(nextRoute)) { if (isNull(newRoute)) {
LOG.debug("{}.reserveNext() found no available route!",this); LOG.debug("{}.reserveNext() found no available route!",this);
return; return;
} }
nextRoute.set(context); newRoute.set(context);
boolean error = !nextRoute.lockIgnoring(route); boolean error = !newRoute.lockIgnoring(route);
error = error || !nextRoute.prepare(); error = error || !newRoute.prepare();
if (error) { if (error) {
nextRoute.reset(); // may unlock tiles belonging to the current route. newRoute.reset(); // may unlock tiles belonging to the current route.
LOG.debug("failed to prepare new route {}",newRoute);
route.lock(); // corrects unlocked tiles of nextRoute route.lock(); // corrects unlocked tiles of nextRoute
} else { } else {
this.nextRoute = nextRoute; nextRoute = newRoute;
this.route.brakeCancel(); LOG.debug("prepared next route: {}",nextRoute);
} }
} }
@@ -1032,7 +1028,6 @@ public class Train extends BaseClass implements Comparable<Train> {
public Object stopNow() { public Object stopNow() {
quitAutopilot(); quitAutopilot();
if (isSet(route)) { if (isSet(route)) {
route.brakeCancel();
route.reset(); route.reset();
route = null; route = null;
} }