Browse Source

implemented pre-reservation of routes when autopilot is active

lookup-tables
Stephan Richter 4 years ago
parent
commit
dec68728ad
  1. 2
      pom.xml
  2. 1
      resources/translations/Application.de.translation
  3. 19
      src/main/java/de/srsoftware/web4rail/BaseClass.java
  4. 2
      src/main/java/de/srsoftware/web4rail/PathFinder.java
  5. 18
      src/main/java/de/srsoftware/web4rail/Plan.java
  6. 27
      src/main/java/de/srsoftware/web4rail/Route.java
  7. 3
      src/main/java/de/srsoftware/web4rail/actions/Action.java
  8. 28
      src/main/java/de/srsoftware/web4rail/actions/PreserveRoute.java
  9. 43
      src/main/java/de/srsoftware/web4rail/moving/Locomotive.java
  10. 72
      src/main/java/de/srsoftware/web4rail/moving/Train.java
  11. 6
      src/main/java/de/srsoftware/web4rail/tiles/Block.java
  12. 2
      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.0.18</version>
<version>1.1.1</version>
<name>Web4Rail</name>
<packaging>jar</packaging>
<description>Java Model Railway Control</description>

1
resources/translations/Application.de.translation

@ -108,6 +108,7 @@ other train properties : andere Zug-Eigenschaften @@ -108,6 +108,7 @@ other train properties : andere Zug-Eigenschaften
Origin and destination : Start und Ziel
Origin\: {} to {} : Start: {} nach {}
Plan saved as "{}". : Plan als „{}“ gespeichert.
PreserveRoute : Anschlußroute vorwählen
Properties : Eigenschaften
Properties of {} : Eigenschaften von {}
Properties of {} @ ({},{}) : Eigenschaften von {} @ ({},{})

19
src/main/java/de/srsoftware/web4rail/BaseClass.java

@ -1,5 +1,7 @@ @@ -1,5 +1,7 @@
package de.srsoftware.web4rail;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
@ -13,7 +15,7 @@ public abstract class BaseClass implements Constants{ @@ -13,7 +15,7 @@ public abstract class BaseClass implements Constants{
protected static Plan plan; // the track layout in use
public static final Random random = new Random();
public static String speedUnit = DEFAULT_SPEED_UNIT;
private static final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
public static Button contextButton(String context,String text) {
String[] parts = context.split(":");
String realm = parts[0];
@ -34,6 +36,21 @@ public abstract class BaseClass implements Constants{ @@ -34,6 +36,21 @@ public abstract class BaseClass implements Constants{
return o != null;
}
public static String md5sum(Object o) {
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(o.toString().getBytes(UTF8));
StringBuffer sb = new StringBuffer();
for (byte b : digest.digest()) {
sb.append(HEX_CHARS[(b & 0xF0) >> 4]);
sb.append(HEX_CHARS[b & 0x0F]);
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
return null;
}
}
public static HashMap<String, String> merged(Map<String, String> base, Map<String, String> overlay) {
HashMap<String,String> merged = new HashMap<>(base);
overlay.entrySet().stream().forEach(entry -> merged.put(entry.getKey(), entry.getValue()));

2
src/main/java/de/srsoftware/web4rail/PathFinder.java

@ -71,7 +71,7 @@ public class PathFinder extends BaseClass{ @@ -71,7 +71,7 @@ public class PathFinder extends BaseClass{
LOG.debug("{}- Candidate: {}",inset,routeCandidate.shortName());
Context forwardContext = new Context(train);
forwardContext.block = routeCandidate.endBlock();
forwardContext.direction = routeCandidate.endDirection.inverse();
forwardContext.direction = routeCandidate.endDirection;
forwardContext.route = null;
visitedRoutes.add(routeCandidate);
TreeMap<Integer, List<Route>> forwardRoutes = availableRoutes(forwardContext,visitedRoutes);

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

@ -244,7 +244,9 @@ public class Plan extends BaseClass{ @@ -244,7 +244,9 @@ public class Plan extends BaseClass{
private String analyze() {
Vector<Route> routes = new Vector<Route>();
for (Block block : blocks) {
for (Connector con : block.startPoints()) routes.addAll(follow(new Route().begin(block,con.from.inverse()),con));
for (Connector con : block.startPoints()) {
routes.addAll(follow(new Route().begin(block,con.from.inverse()),con));
}
}
for (Tile tile : tiles.values()) tile.routes().clear();
for (Route route : routes) registerRoute(route.complete());
@ -287,7 +289,7 @@ public class Plan extends BaseClass{ @@ -287,7 +289,7 @@ public class Plan extends BaseClass{
Tile tile = get(Tile.id(connector.x,connector.y),false);
Vector<Route> results = new Vector<>();
if (tile == null) return results;
Tile addedTile = route.add(tile,connector.from);
Tile addedTile = route.add(tile,connector.from.inverse());
if (addedTile instanceof Block) {
Map<Connector, State> cons = addedTile.connections(connector.from);
LOG.debug("Found {}, coming from {}.",addedTile,connector.from);
@ -296,7 +298,7 @@ public class Plan extends BaseClass{ @@ -296,7 +298,7 @@ public class Plan extends BaseClass{
Tile nextTile = get(Tile.id(con.x,con.y),false);
if (nextTile instanceof Contact) {
LOG.debug("{} is followed by {}",addedTile,nextTile);
route.add(nextTile, con.from);
route.add(nextTile, con.from.inverse());
}
break;
}
@ -622,13 +624,11 @@ public class Plan extends BaseClass{ @@ -622,13 +624,11 @@ public class Plan extends BaseClass{
* @return
*/
Route registerRoute(Route newRoute) {
for (Tile tile: newRoute.path()) {
if (isSet(tile)) tile.add(newRoute);
}
int routeId = newRoute.id();
Route existingRoute = routes.get(routeId);
newRoute.path().stream().filter(Tile::isSet).forEach(tile -> tile.add(newRoute));
int newRouteId = newRoute.id();
Route existingRoute = routes.get(newRouteId);
if (isSet(existingRoute)) newRoute.addPropertiesFrom(existingRoute);
routes.put(routeId, newRoute);
routes.put(newRouteId, newRoute);
return newRoute;
}

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

@ -25,6 +25,7 @@ import de.srsoftware.web4rail.actions.Action; @@ -25,6 +25,7 @@ import de.srsoftware.web4rail.actions.Action;
import de.srsoftware.web4rail.actions.Action.Context;
import de.srsoftware.web4rail.actions.ActionList;
import de.srsoftware.web4rail.actions.FinishRoute;
import de.srsoftware.web4rail.actions.PreserveRoute;
import de.srsoftware.web4rail.actions.SetSignal;
import de.srsoftware.web4rail.actions.SetSpeed;
import de.srsoftware.web4rail.conditions.Condition;
@ -157,7 +158,7 @@ public class Route extends BaseClass implements Comparable<Route>{ @@ -157,7 +158,7 @@ public class Route extends BaseClass implements Comparable<Route>{
new Tag("h4").content(t("Origin and destination")).addTo(win);
Tag list = new Tag("ul");
Plan.addLink(startBlock, t("Origin: {} to {}",startBlock.name,startDirection), list);
Plan.addLink(endBlock, t("Destination: {} from {}",endBlock.name,endDirection), list);
Plan.addLink(endBlock, t("Destination: {} from {}",endBlock.name,endDirection.inverse()), list);
list.addTo(win);
@ -316,7 +317,8 @@ public class Route extends BaseClass implements Comparable<Route>{ @@ -316,7 +317,8 @@ public class Route extends BaseClass implements Comparable<Route>{
if (contacts.size()>1) { // mindestens 2 Kontakte: erster Kontakt aktiviert Block, vorletzter Kontakt leitet Bremsung ein
Contact nextToLastContact = contacts.get(contacts.size()-2);
String trigger = nextToLastContact.trigger();
add(trigger,new SetSpeed().to(30));
add(trigger,new SetSpeed().to(50));
add(trigger,new PreserveRoute());
for (Signal signal : signals) add(trigger,new SetSignal().set(signal).to(Signal.STOP));
}
if (!contacts.isEmpty()) {
@ -385,12 +387,12 @@ public class Route extends BaseClass implements Comparable<Route>{ @@ -385,12 +387,12 @@ public class Route extends BaseClass implements Comparable<Route>{
}
if (isSet(train)) {
train.set(endBlock);
train.heading(endDirection.inverse());
train.heading(endDirection);
if (endBlock == train.destination()) {
train.destination(null).quitAutopilot();
plan.stream(t("{} reached it`s destination!",train));
} else {
train.setWaitTime(endBlock.getWaitTime(train));
train.setWaitTime(endBlock.getWaitTime(train,train.direction()));
}
if (train.route == this) train.route = null;
}
@ -408,13 +410,13 @@ public class Route extends BaseClass implements Comparable<Route>{ @@ -408,13 +410,13 @@ public class Route extends BaseClass implements Comparable<Route>{
Tile tile = path.get(i);
if (i>0) sb.append("-");
if (tile instanceof Block) {
sb.append(((Block)tile).name);
sb.append(" ").append(((Block)tile).name).append(" ");
if (i>0) break; // Kontakt nach dem Ziel-Block nicht mitnehmen
} else {
sb.append(tile.id());
sb.append("("+tile.x+","+tile.y+")");
}
}
return sb.toString();
return sb.toString().trim();
}
public int id() {
@ -551,9 +553,16 @@ public class Route extends BaseClass implements Comparable<Route>{ @@ -551,9 +553,16 @@ public class Route extends BaseClass implements Comparable<Route>{
}
public boolean lock() {
return lockIgnoring(null);
}
public boolean lockIgnoring(Route ignoredRoute) {
Vector<Tile> alreadyLocked = new Vector<Tile>();
HashSet<Tile> ignoredPath = new HashSet<Tile>();
if (isSet(ignoredRoute)) ignoredPath.addAll(ignoredRoute.path);
boolean success = true;
for (Tile tile : path) {
if (ignoredPath.contains(tile)) continue;
try {
tile.setRoute(this);
} catch (IllegalStateException e) {
@ -583,7 +592,9 @@ public class Route extends BaseClass implements Comparable<Route>{ @@ -583,7 +592,9 @@ public class Route extends BaseClass implements Comparable<Route>{
}
public void name(String name) {
names.put(id(),name);
if (name.isEmpty()) {
names.remove(id());
} else names.put(id(),name);
}
public Vector<Tile> path() {

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

@ -40,7 +40,7 @@ public abstract class Action extends BaseClass { @@ -40,7 +40,7 @@ public abstract class Action extends BaseClass {
public Block block = null;
public Direction direction = null;
private Context(Contact c, Route r, Train t, Block b, Direction d) {
public Context(Contact c, Route r, Train t, Block b, Direction d) {
contact = c;
route = r;
train = t;
@ -138,6 +138,7 @@ public abstract class Action extends BaseClass { @@ -138,6 +138,7 @@ public abstract class Action extends BaseClass {
DelayedAction.class,
DetermineTrainInBlock.class,
FinishRoute.class,
PreserveRoute.class,
SendCommand.class,
SetDisplayText.class,
SetPower.class,

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

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
package de.srsoftware.web4rail.actions;
import de.srsoftware.web4rail.Range;
import de.srsoftware.web4rail.Route;
import de.srsoftware.web4rail.moving.Train;
public class PreserveRoute extends Action {
@Override
public boolean fire(Context context) {
Train train = context.train;
Route route = context.route;
// These are errors:
if (isNull(train)) return false;
if (isNull(route)) return false;
Range waitTime = context.route.endBlock().getWaitTime(context.train,context.route.endDirection);
// These are NOT errors:
if (!context.train.usesAutopilot()) return true;
if (waitTime.max > 0) return true; // train is expected to wait in next block.
if (train.destination() == route.endBlock()) return true;
context.train.reserveNext();
return true;
}
}

43
src/main/java/de/srsoftware/web4rail/moving/Locomotive.java

@ -13,15 +13,16 @@ import de.srsoftware.web4rail.Command; @@ -13,15 +13,16 @@ import de.srsoftware.web4rail.Command;
import de.srsoftware.web4rail.Constants;
import de.srsoftware.web4rail.Device;
import de.srsoftware.web4rail.Plan;
import de.srsoftware.web4rail.Protocol;
import de.srsoftware.web4rail.Window;
import de.srsoftware.web4rail.tags.Button;
import de.srsoftware.web4rail.tags.Fieldset;
import de.srsoftware.web4rail.Protocol;
import de.srsoftware.web4rail.tags.Form;
import de.srsoftware.web4rail.tags.Input;
import de.srsoftware.web4rail.tags.Label;
import de.srsoftware.web4rail.tags.Radio;
import de.srsoftware.web4rail.tags.Table;
import de.srsoftware.web4rail.tiles.Block;
public class Locomotive extends Car implements Constants,Device{
@ -76,16 +77,18 @@ public class Locomotive extends Car implements Constants,Device{ @@ -76,16 +77,18 @@ public class Locomotive extends Car implements Constants,Device{
}
public static Tag cockpit(Object locoOrTrain) {
String realm = null;
int id = 0;
int speed = 0;
String realm = null;
Train train = null;
Locomotive loco = null;
if (locoOrTrain instanceof Locomotive) {
Locomotive loco = (Locomotive) locoOrTrain;
loco = (Locomotive) locoOrTrain;
realm = REALM_LOCO;
id = loco.id();
speed = loco.speed;
} else if (locoOrTrain instanceof Train) {
Train train = (Train)locoOrTrain;
train = (Train)locoOrTrain;
realm = REALM_TRAIN;
id = train.id;
speed = train.speed;
@ -95,7 +98,7 @@ public class Locomotive extends Car implements Constants,Device{ @@ -95,7 +98,7 @@ public class Locomotive extends Car implements Constants,Device{
Fieldset fieldset = new Fieldset(t("Control"));
new Tag("span").content(t("Current velocity: {} {}",speed)).addTo(fieldset);
new Tag("span").content(t("Current velocity: {} {}",speed,speedUnit)).addTo(fieldset);
Tag par = new Tag("p");
Map.of("Slower (10 steps)",ACTION_SLOWER10,"Faster (10 steps)",ACTION_FASTER10).entrySet().forEach(e -> {
@ -105,10 +108,30 @@ public class Locomotive extends Car implements Constants,Device{ @@ -105,10 +108,30 @@ public class Locomotive extends Car implements Constants,Device{
par.addTo(fieldset);
Tag direction = new Tag("p");
Map.of("Turn",ACTION_TURN,"Stop",ACTION_STOP).entrySet().forEach(e -> {
params.put(ACTION, e.getValue());
new Button(t(e.getKey()),params).clazz(e.getValue()).addTo(direction);
});
if ((isSet(train) && train.speed > 0) || (isSet(loco) && loco.speed > 0)) {
params.put(ACTION, ACTION_STOP);
new Button(t("Stop"),params).clazz(ACTION_STOP).addTo(direction);
}
params.put(ACTION, ACTION_TURN);
new Button(t("Turn"),params).clazz(ACTION_TURN).addTo(direction);
if (isSet(train)) {
Block currentBlock = train.currentBlock();
if (isSet(currentBlock)) {
if (isNull(train.route)) {
params.put(ACTION, ACTION_START);
new Button(t("start"),params).addTo(direction);
}
if (train.usesAutopilot()) {
params.put(ACTION, ACTION_QUIT);
new Button(t("quit autopilot"),params).addTo(direction);
} else {
params.put(ACTION, ACTION_AUTO);
new Button(t("auto"),params).addTo(direction);
}
}
}
direction.addTo(fieldset);
Tag functions = new Tag("p");
@ -117,6 +140,8 @@ public class Locomotive extends Car implements Constants,Device{ @@ -117,6 +140,8 @@ public class Locomotive extends Car implements Constants,Device{
new Button(t(e.getKey()),params).addTo(functions);
});
functions.addTo(fieldset);
return fieldset.clazz("cockpit");
}

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

@ -81,6 +81,8 @@ public class Train extends BaseClass implements Comparable<Train> { @@ -81,6 +81,8 @@ public class Train extends BaseClass implements Comparable<Train> {
private Block currentBlock,destination = null;
LinkedList<Tile> trace = new LinkedList<Tile>();
private String brakeId = null;
private class Autopilot extends Thread{
boolean stop = false;
int waitTime = 100;
@ -109,6 +111,8 @@ public class Train extends BaseClass implements Comparable<Train> { @@ -109,6 +111,8 @@ public class Train extends BaseClass implements Comparable<Train> {
private Plan plan;
private Route nextRoute;
public Train(Locomotive loco) {
this(loco,null);
}
@ -147,7 +151,7 @@ public class Train extends BaseClass implements Comparable<Train> { @@ -147,7 +151,7 @@ public class Train extends BaseClass implements Comparable<Train> {
case ACTION_MOVE:
return train.setDestination(params);
case ACTION_PROPS:
return train.props();
return train.properties();
case ACTION_QUIT:
return train.quitAutopilot();
case ACTION_SLOWER10:
@ -182,11 +186,8 @@ public class Train extends BaseClass implements Comparable<Train> { @@ -182,11 +186,8 @@ public class Train extends BaseClass implements Comparable<Train> {
if (!params.containsKey(CAR_ID)) return t("No car id passed to Train.addCar!");
Car car = Car.get(params.get(CAR_ID));
if (isNull(car)) return t("No car with id \"{}\" known!",params.get(CAR_ID));
if (car instanceof Locomotive) {
locos.add((Locomotive) car);
} else cars.add(car);
car.train(this);
return props();
add(car);
return properties();
}
public void add(Car car) {
@ -194,6 +195,7 @@ public class Train extends BaseClass implements Comparable<Train> { @@ -194,6 +195,7 @@ public class Train extends BaseClass implements Comparable<Train> {
if (car instanceof Locomotive) {
locos.add((Locomotive) car);
} else cars.add(car);
brakeId = null;
car.train(this);
}
@ -205,6 +207,17 @@ public class Train extends BaseClass implements Comparable<Train> { @@ -205,6 +207,17 @@ public class Train extends BaseClass implements Comparable<Train> {
return t("{} now in auto-mode",this);
}
public String brakeId() {
if (isNull(brakeId)) {
TreeSet<Integer> carIds = new TreeSet<Integer>();
locos.stream().map(loco -> loco.id()).forEach(carIds::add);
cars.stream().map(car -> car.id()).forEach(carIds::add);
brakeId = md5sum(carIds);
LOG.debug("generated new brake id for {}: {}",brakeId,this);
}
return brakeId;
}
private Tag carList() {
Tag locoProp = new Tag("li").content(t("Cars:"));
Tag locoList = new Tag("ul").clazz("carlist").content("");
@ -294,7 +307,8 @@ public class Train extends BaseClass implements Comparable<Train> { @@ -294,7 +307,8 @@ public class Train extends BaseClass implements Comparable<Train> {
locos.remove(loco);
loco.train(null);
}
return props();
brakeId = null;
return properties();
}
public void dropTrace() {
@ -303,7 +317,7 @@ public class Train extends BaseClass implements Comparable<Train> { @@ -303,7 +317,7 @@ public class Train extends BaseClass implements Comparable<Train> {
private Tag faster(int steps) {
setSpeed(speed+steps);
return props();
return properties();
}
public static Train get(int id) {
@ -502,7 +516,7 @@ public class Train extends BaseClass implements Comparable<Train> { @@ -502,7 +516,7 @@ public class Train extends BaseClass implements Comparable<Train> {
return this;
}
public Tag props() {
public Tag properties() {
Window window = new Window("train-properties",t("Properties of {}",this));
Locomotive.cockpit(this).addTo(window);
@ -595,6 +609,22 @@ public class Train extends BaseClass implements Comparable<Train> { @@ -595,6 +609,22 @@ public class Train extends BaseClass implements Comparable<Train> {
trace.remove(tile);
}
public void reserveNext() {
Context context = new Context(null, route, this, route.endBlock(), route.endDirection);
Route nextRoute = PathFinder.chooseRoute(context);
boolean error = !nextRoute.lockIgnoring(route);
error = error || !nextRoute.setTurnouts();
error = error || !route.fireSetupActions(context);
if (error) {
nextRoute.reset(); // may unlock tiles belonging to the current route.
route.lock(); // corrects unlocked tiles of nextRoute
} else {
this.nextRoute = nextRoute;
}
}
private void reverseTrace() {
LinkedList<Tile> reversed = new LinkedList<Tile>();
LOG.debug("Trace: {}",trace);
@ -680,7 +710,7 @@ public class Train extends BaseClass implements Comparable<Train> { @@ -680,7 +710,7 @@ public class Train extends BaseClass implements Comparable<Train> {
private Tag slower(int steps) {
setSpeed(speed-steps);
return props();
return properties();
}
public String start() throws IOException {
@ -688,15 +718,20 @@ public class Train extends BaseClass implements Comparable<Train> { @@ -688,15 +718,20 @@ public class Train extends BaseClass implements Comparable<Train> {
if (isSet(route)) route.reset(); // reset route previously chosen
Context context = new Context(this);
String error = null;
if (isSet(nextRoute)) {
route = nextRoute;
if (!route.lock()) return t("Was not able to lock {}",route);
nextRoute = null;
} else {
route = PathFinder.chooseRoute(context);
if (isNull(route)) return t("No free routes from {}",currentBlock);
if (!route.lock()) return t("Was not able to lock {}",route);
if (direction != route.startDirection) turn();
String error = null;
if (!route.setTurnouts()) error = t("Was not able to set all turnouts!");
if (isNull(error) && !route.fireSetupActions(context)) error = t("Was not able to fire all setup actions of route!");
}
if (direction != route.startDirection) turn();
if (isNull(error) && !route.train(this)) error = t("Was not able to assign {} to {}!",this,route);
if (isSet(error)) {
route.reset();
@ -746,11 +781,16 @@ public class Train extends BaseClass implements Comparable<Train> { @@ -746,11 +781,16 @@ public class Train extends BaseClass implements Comparable<Train> {
public Object stopNow() {
quitAutopilot();
setSpeed(0);
if (isSet(nextRoute)) {
nextRoute.reset();
nextRoute = null;
}
if (isSet(route)) {
route.reset();
route = null;
}
return props();
return properties();
}
private static String t(String message, Object...fills) {
@ -778,7 +818,7 @@ public class Train extends BaseClass implements Comparable<Train> { @@ -778,7 +818,7 @@ public class Train extends BaseClass implements Comparable<Train> {
reverseTrace();
if (isSet(currentBlock)) plan.place(currentBlock);
}
return props();
return properties();
}
public Train update(HashMap<String, String> params) {

6
src/main/java/de/srsoftware/web4rail/tiles/Block.java

@ -122,7 +122,7 @@ public abstract class Block extends StretchableTile implements Comparable<Block> @@ -122,7 +122,7 @@ public abstract class Block extends StretchableTile implements Comparable<Block>
@Override
public Object click() throws IOException {
if (isSet(train)) return train.props();
if (isSet(train)) return train.properties();
return super.click();
}
@ -165,13 +165,13 @@ public abstract class Block extends StretchableTile implements Comparable<Block> @@ -165,13 +165,13 @@ public abstract class Block extends StretchableTile implements Comparable<Block>
return null;
}
public Range getWaitTime(Train train) {
public Range getWaitTime(Train train,Direction dir) {
for (WaitTime wt : waitTimes) {
if (train.tags().contains(wt.tag)) {
return wt.get(train.direction());
}
}
return getWaitTime(NO_TAG).get(train.direction());
return getWaitTime(NO_TAG).get(dir);
}
@Override

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

@ -118,7 +118,7 @@ public abstract class Tile extends BaseClass{ @@ -118,7 +118,7 @@ public abstract class Tile extends BaseClass{
public boolean isFreeFor(Train newTrain) {
if (disabled) return false;
if (isSet(route)) return false;
if (isSet(route) && route.train != newTrain) return false;
if (isSet(train) && train != newTrain) return false;
return true;
}

Loading…
Cancel
Save