Browse Source

started re-implementing route reservation

lookup-tables
Stephan Richter 4 years ago
parent
commit
830c1863ad
  1. 2
      pom.xml
  2. 14
      resources/css/style.css
  3. 2
      resources/logback.xml
  4. 113
      src/main/java/de/srsoftware/web4rail/Route.java
  5. 5
      src/main/java/de/srsoftware/web4rail/actions/ActionList.java
  6. 2
      src/main/java/de/srsoftware/web4rail/conditions/BlockFree.java
  7. 92
      src/main/java/de/srsoftware/web4rail/moving/Train.java
  8. 4
      src/main/java/de/srsoftware/web4rail/threads/ControlUnit.java
  9. 98
      src/main/java/de/srsoftware/web4rail/threads/PathFinder.java
  10. 18
      src/main/java/de/srsoftware/web4rail/tiles/Block.java
  11. 5
      src/main/java/de/srsoftware/web4rail/tiles/BlockContact.java
  12. 28
      src/main/java/de/srsoftware/web4rail/tiles/Bridge.java
  13. 11
      src/main/java/de/srsoftware/web4rail/tiles/Contact.java
  14. 167
      src/main/java/de/srsoftware/web4rail/tiles/Tile.java
  15. 2
      src/main/java/de/srsoftware/web4rail/tiles/Turnout.java
  16. 4
      src/main/java/de/srsoftware/web4rail/tiles/TurnoutL.java
  17. 4
      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.3.52</version> <version>1.3.53</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>

14
resources/css/style.css

@ -77,14 +77,20 @@ svg.Relay rect{
fill: white; fill: white;
} }
svg.allocated polygon,
svg.allocated rect:not(.sig_a):not(.sig_b){
fill: yellow;
}
svg.locked polygon, svg.locked polygon,
svg.locked rect:not(.sig_a):not(.sig_b){ svg.locked rect:not(.sig_a):not(.sig_b){
fill:lime; fill: lime;
} }
.occupied .block,
svg.occupied polygon, svg.occupied polygon,
svg.occupied rect:not(.sig_a):not(.sig_b){ svg.occupied rect:not(.sig_a):not(.sig_b){
fill:yellow; fill: orange;
} }
svg text{ svg text{
@ -232,10 +238,6 @@ svg.straight .right{
fill: #ddd !important; fill: #ddd !important;
} }
.occupied .block{
fill: yellow;
}
.active circle{ .active circle{
fill: #f57900; fill: #f57900;
} }

2
resources/logback.xml

@ -29,7 +29,7 @@
<appender-ref ref="STDOUT" /> <appender-ref ref="STDOUT" />
</root> </root>
<logger name="de.srsoftware.web4rail" level="INFO" /> <logger name="de.srsoftware.web4rail" level="DEBUG" />
<logger name="de.srsoftware.web4rail.Application" level="DEBUG" /> <logger name="de.srsoftware.web4rail.Application" level="DEBUG" />
<logger name="de.srsoftware.web4rail.Route" level="DEBUG" /> <logger name="de.srsoftware.web4rail.Route" level="DEBUG" />
<logger name="de.srsoftware.web4rail.actions.Action" level="DEBUG" /> <logger name="de.srsoftware.web4rail.actions.Action" level="DEBUG" />

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

@ -10,6 +10,7 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Stack;
import java.util.Vector; import java.util.Vector;
import org.json.JSONArray; import org.json.JSONArray;
@ -44,6 +45,7 @@ import de.srsoftware.web4rail.tiles.Contact;
import de.srsoftware.web4rail.tiles.Shadow; import de.srsoftware.web4rail.tiles.Shadow;
import de.srsoftware.web4rail.tiles.Signal; import de.srsoftware.web4rail.tiles.Signal;
import de.srsoftware.web4rail.tiles.Tile; import de.srsoftware.web4rail.tiles.Tile;
import de.srsoftware.web4rail.tiles.Tile.Status;
import de.srsoftware.web4rail.tiles.Turnout; import de.srsoftware.web4rail.tiles.Turnout;
/** /**
* A route is a vector of tiles that leads from one block to another. * A route is a vector of tiles that leads from one block to another.
@ -53,9 +55,9 @@ import de.srsoftware.web4rail.tiles.Turnout;
*/ */
public class Route extends BaseClass { public class Route extends BaseClass {
public enum State { /* public enum State {
FREE, LOCKED, PREPARED, STARTED; FREE, LOCKED, PREPARED, STARTED;
} }*/
public static final Logger LOG = LoggerFactory.getLogger(Route.class); public static final Logger LOG = LoggerFactory.getLogger(Route.class);
private static final String ACTIONS = "actions"; private static final String ACTIONS = "actions";
@ -70,7 +72,7 @@ public class Route extends BaseClass {
static final String PATH = "path"; static final String PATH = "path";
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 boolean freeBehindTrain = true; public static boolean freeBehindTrain = true;
private static final String ROUTE_START = "route_start"; private static final String ROUTE_START = "route_start";
@ -79,11 +81,9 @@ public class Route extends BaseClass {
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 BrakeProcessor brakeProcessor = null;
private HashMap<String,Integer> brakeTimes = new HashMap<String, Integer>(); private HashMap<String,Integer> brakeTimes = new HashMap<String, Integer>();
private ConditionList conditions; private ConditionList conditions;
private Vector<Contact> contacts; private Vector<Contact> contacts;
private Context context; // this context is passed to actions
private boolean disabled = false; private boolean disabled = false;
private Block endBlock = null; private Block endBlock = null;
public Direction endDirection; public Direction endDirection;
@ -100,7 +100,7 @@ public class Route extends BaseClass {
conditions = new ConditionList(); conditions = new ConditionList();
conditions.parent(this); conditions.parent(this);
} }
/** /**
* process commands from the client * process commands from the client
* @param params * @param params
@ -121,12 +121,6 @@ public class Route extends BaseClass {
plan.stream(t("Removed {}.",route)); plan.stream(t("Removed {}.",route));
return plan.properties(new HashMap<String,String>()); return plan.properties(new HashMap<String,String>());
case ACTION_PROPS: case ACTION_PROPS:
return route.properties();
case ACTION_START:
route.set(new Context(route));
route.prepare();
route.context.clear();
return route.properties(); return route.properties();
case ACTION_UPDATE: case ACTION_UPDATE:
return route.update(params,plan); return route.update(params,plan);
@ -204,6 +198,10 @@ public class Route extends BaseClass {
turnouts.put(t, s); turnouts.put(t, s);
} }
public boolean allocateFor(Train newTrain) {
return pathState(newTrain,Tile.Status.ALLOCATED);
}
/** /**
* checks, whether the route may be used in a given context * checks, whether the route may be used in a given context
* @param context * @param context
@ -333,6 +331,7 @@ public class Route extends BaseClass {
ActionList actions = triggeredActions.get(contact.trigger()); ActionList actions = triggeredActions.get(contact.trigger());
LOG.debug("Contact has id {} / trigger {} and is assigned with {}",contact.id(),contact.trigger(),isNull(actions)?t("nothing"):actions); LOG.debug("Contact has id {} / trigger {} and is assigned with {}",contact.id(),contact.trigger(),isNull(actions)?t("nothing"):actions);
if (isNull(actions)) return; if (isNull(actions)) return;
Context context = new Context(this).train(train);
actions.fire(context,"Route.Contact("+contact.addr()+")"); actions.fire(context,"Route.Contact("+contact.addr()+")");
} }
@ -373,10 +372,6 @@ public class Route extends BaseClass {
return win; return win;
} }
public Context context() {
return context.clone();
}
public void dropBraketimes(String...brakeIds) { public void dropBraketimes(String...brakeIds) {
for (String brakeId : brakeIds) brakeTimes.remove(brakeId); for (String brakeId : brakeIds) brakeTimes.remove(brakeId);
} }
@ -416,10 +411,10 @@ public class Route extends BaseClass {
return disabled; return disabled;
} }
public boolean isFreeFor(Context context) { public boolean isFreeFor(Train newTrain) {
PathFinder.LOG.debug("{}.isFreeFor({})",this,context); PathFinder.LOG.debug("{}.isFreeFor({})",this,newTrain);
for (int i=1; i<path.size(); i++) { for (int i=1; i<path.size(); i++) {
if (!path.get(i).isFreeFor(context)) { if (!path.get(i).canNeEnteredBy(newTrain)) {
PathFinder.LOG.debug("{}.isFreeFor(...) → false",this); PathFinder.LOG.debug("{}.isFreeFor(...) → false",this);
return false; return false;
} }
@ -627,33 +622,7 @@ public class Route extends BaseClass {
} }
fis.close(); fis.close();
} }
public boolean lock() {
return lockIgnoring(null);
}
public boolean lockIgnoring(Route ignoredRoute) {
if (state == State.LOCKED || state == State.PREPARED || state == State.STARTED) return true;
LOG.debug("{}.lockIgnoring({})",this,ignoredRoute);
HashSet<Tile> ignoredPath = new HashSet<Tile>();
if (isSet(ignoredRoute)) ignoredPath.addAll(ignoredRoute.path);
for (Tile tile : path) {
if (ignoredPath.contains(tile)) continue;
try {
tile.setRoute(this);
} catch (IllegalStateException e) {
LOG.debug("{}.lockIgnoring(...) failed at {}, rolling back",this,tile);
for (Tile lockedTile : path) { // unlock the same tiles that have been locked before, until we encounter the unlockable tile
if (lockedTile == tile) return false;
lockedTile.unset(this);
}
return false;
}
}
state = State.LOCKED;
return true;
}
public List<Route> multiply(int size) { public List<Route> multiply(int size) {
Vector<Route> routes = new Vector<Route>(); Vector<Route> routes = new Vector<Route>();
for (int i=0; i<size; i++) routes.add(i==0 ? this : this.clone()); for (int i=0; i<size; i++) routes.add(i==0 ? this : this.clone());
@ -681,12 +650,26 @@ public class Route extends BaseClass {
return result; return result;
} }
public boolean prepare() { private boolean pathState(Train newTrain, Status newState) {
if (state == State.PREPARED || state == State.STARTED) return true; Stack<Tile> visited = new Stack<>();
for (Tile t : path) {
if (t.setState(newState,newTrain)) {
visited.push(t);
} else {
while (!visited.isEmpty()) visited.pop().free();
return false;
}
}
return true;
}
public boolean prepareFor(Train newTrain) {
// 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,this+".prepare()")) return false; if (isSet(setupActions) && !setupActions.fire(new Context(newTrain).route(this),this+".prepare()")) return false;
state = State.PREPARED; // state = State.PREPARED;
pathState(newTrain,Tile.Status.LOCKED);
return true; return true;
} }
@ -760,7 +743,6 @@ public class Route extends BaseClass {
LOG.debug("{}.reset()",this); LOG.debug("{}.reset()",this);
// TODO // TODO
state = State.FREE;
return true; return true;
} }
@ -778,13 +760,6 @@ public class Route extends BaseClass {
file.close(); file.close();
} }
public Context set(Context newContext) {
LOG.debug("{}.set({})",this,newContext);
context = newContext;
context.route(this);
return context;
}
public void setLast(Turnout.State state) { public void setLast(Turnout.State state) {
if (isNull(state) || state == Turnout.State.UNDEF) return; if (isNull(state) || state == Turnout.State.UNDEF) return;
Tile lastTile = path.lastElement(); Tile lastTile = path.lastElement();
@ -810,20 +785,21 @@ public class Route extends BaseClass {
return this; return this;
} }
public Route.State state(){
return state;
}
public boolean start(Train newTrain) { public boolean start(Train newTrain) {
if (state == State.STARTED) return true; // if (state == State.STARTED) return true;
LOG.debug("{}.start()",this); LOG.debug("{}.start()",this);
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)) {
if (newTrain != train) return false; // can't alter route's train if (newTrain != train) return false; // can't alter route's train
} else train = newTrain; // set new train } else train = newTrain.setRoute(this); // set new train
ActionList startActions = triggeredActions.get(ROUTE_START); ActionList startActions = triggeredActions.get(ROUTE_START);
if (isSet(startActions) && !startActions.fire(context,this+".start("+train.name()+")")) return false; // start actions failed
state = State.STARTED; if (isSet(startActions)) {
Context context = new Context(train).route(this);
if (!startActions.fire(context,this+".start("+train.name()+")")) return false; // start actions failed
}
// state = State.STARTED;
triggeredContacts.clear(); triggeredContacts.clear();
return true; return true;
} }
@ -865,9 +841,8 @@ public class Route extends BaseClass {
newTrace.add(tile); newTrace.add(tile);
remainingLength -= tile.length(); remainingLength -= tile.length();
} else if (Route.freeBehindTrain) { } else if (Route.freeBehindTrain) {
try {
tile.unset(this); // TODO
} catch (IllegalArgumentException e) {}
} else break; } else break;
} }
} }

5
src/main/java/de/srsoftware/web4rail/actions/ActionList.java

@ -88,7 +88,10 @@ public class ActionList extends Action implements Iterable<Action>{
for (Action action : actions) { for (Action action : actions) {
LOG.debug("firing \"{}\"",action); LOG.debug("firing \"{}\"",action);
if (!action.fire(context,cause)) return false; if (!action.fire(context,cause)) {
LOG.warn("{} failed",action);
return false;
}
} }
return true; return true;
} }

2
src/main/java/de/srsoftware/web4rail/conditions/BlockFree.java

@ -25,7 +25,7 @@ public class BlockFree extends Condition {
@Override @Override
public boolean fulfilledBy(Context context) { public boolean fulfilledBy(Context context) {
return block.isFreeFor(null) != inverted; return block.canNeEnteredBy(null) != inverted;
} }
@Override @Override

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

@ -38,12 +38,23 @@ import de.srsoftware.web4rail.tags.Table;
import de.srsoftware.web4rail.tags.Window; import de.srsoftware.web4rail.tags.Window;
import de.srsoftware.web4rail.threads.PathFinder; import de.srsoftware.web4rail.threads.PathFinder;
import de.srsoftware.web4rail.tiles.Block; import de.srsoftware.web4rail.tiles.Block;
import de.srsoftware.web4rail.tiles.Contact;
import de.srsoftware.web4rail.tiles.Tile; import de.srsoftware.web4rail.tiles.Tile;
import de.srsoftware.web4rail.tiles.Tile.Status;
/** /**
* @author Stephan Richter, SRSoftware 2020-2021 * * @author Stephan Richter, SRSoftware 2020-2021 *
*/ */
public class Train extends BaseClass implements Comparable<Train> { public class Train extends BaseClass implements Comparable<Train> {
public interface Listener {
enum Signal {
STOP
}
public void on(Signal signal);
}
private static final Logger LOG = LoggerFactory.getLogger(Train.class); private static final Logger LOG = LoggerFactory.getLogger(Train.class);
private static final String CAR_ID = "carId"; private static final String CAR_ID = "carId";
@ -88,6 +99,8 @@ public class Train extends BaseClass implements Comparable<Train> {
private static final String SHUNTING = "shunting"; private static final String SHUNTING = "shunting";
private boolean shunting = false; private boolean shunting = false;
private HashSet<Listener> listeners = new HashSet<Train.Listener>();
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!");
@ -147,10 +160,18 @@ public class Train extends BaseClass implements Comparable<Train> {
return t("Unknown action: {}",params.get(ACTION)); return t("Unknown action: {}",params.get(ACTION));
} }
public Train add(Car car) {
if (isSet(car)) {
cars.add(car);
car.train(this);
}
return this;
}
public void addTag(String tag) { public void addTag(String tag) {
tags.add(tag); tags.add(tag);
} }
private Object addCar(HashMap<String, String> params) { private Object addCar(HashMap<String, String> params) {
LOG.debug("addCar({})",params); LOG.debug("addCar({})",params);
String carId = params.get(CAR_ID); String carId = params.get(CAR_ID);
@ -161,12 +182,8 @@ public class Train extends BaseClass implements Comparable<Train> {
return properties(); return properties();
} }
public Train add(Car car) { public void addListener(Listener listener) {
if (isSet(car)) { listeners.add(listener);
cars.add(car);
car.train(this);
}
return this;
} }
public boolean automatic() { public boolean automatic() {
@ -299,6 +316,11 @@ public class Train extends BaseClass implements Comparable<Train> {
return properties(); return properties();
} }
public void contact(Contact contact) {
if (isSet(route)) route.contact(contact);
}
public void coupleWith(Train parkingTrain,boolean swap) { public void coupleWith(Train parkingTrain,boolean swap) {
if (isSet(direction) && isSet(parkingTrain.direction) && parkingTrain.direction != direction) parkingTrain.turn(); if (isSet(direction) && isSet(parkingTrain.direction) && parkingTrain.direction != direction) parkingTrain.turn();
if (swap) { if (swap) {
@ -384,7 +406,7 @@ public class Train extends BaseClass implements Comparable<Train> {
} }
public void dropTrace() { public void dropTrace() {
while (!trace.isEmpty()) trace.removeFirst().setTrain(null); while (!trace.isEmpty()) trace.removeFirst().free();
} }
private Tag faster(int steps) { private Tag faster(int steps) {
@ -485,8 +507,14 @@ public class Train extends BaseClass implements Comparable<Train> {
if (json.has(DIRECTION)) direction = Direction.valueOf(json.getString(DIRECTION)); if (json.has(DIRECTION)) direction = Direction.valueOf(json.getString(DIRECTION));
if (json.has(NAME)) name = json.getString(NAME); if (json.has(NAME)) name = json.getString(NAME);
if (json.has(TAGS)) json.getJSONArray(TAGS ).forEach(elem -> { tags.add(elem.toString()); }); if (json.has(TAGS)) json.getJSONArray(TAGS ).forEach(elem -> { tags.add(elem.toString()); });
if (json.has(TRACE)) json.getJSONArray(TRACE).forEach(elem -> { trace.add(plan.get(new Id(elem.toString()), false).setTrain(this)); }); if (json.has(TRACE)) json.getJSONArray(TRACE).forEach(elem -> {
if (json.has(BLOCK)) currentBlock = (Block) plan.get(new Id(json.getString(BLOCK)), false).setTrain(this); // do not move this up! during set, other fields will be referenced! Tile tile = plan.get(new Id(elem.toString()), false);
if (tile.setState(Status.OCCUPIED,this)) trace.add(tile);
});
if (json.has(BLOCK)) {// do not move this up! during set, other fields will be referenced!
currentBlock = (Block) plan.get(new Id(json.getString(BLOCK)), false);
currentBlock.setState(Status.OCCUPIED,this);
}
if (json.has(LOCOS)) { // for downward compatibility if (json.has(LOCOS)) { // for downward compatibility
for (Object id : json.getJSONArray(LOCOS)) add(BaseClass.get(new Id(""+id))); for (Object id : json.getJSONArray(LOCOS)) add(BaseClass.get(new Id(""+id)));
} }
@ -724,10 +752,10 @@ public class Train extends BaseClass implements Comparable<Train> {
public void set(Block newBlock) { public void set(Block newBlock) {
LOG.debug("{}.set({})",this,newBlock); LOG.debug("{}.set({})",this,newBlock);
if (isSet(currentBlock)) currentBlock.setTrain(null); if (isSet(currentBlock)) currentBlock.free();
currentBlock = newBlock; currentBlock = newBlock;
if (isSet(currentBlock)) { if (isSet(currentBlock)) {
currentBlock.setTrain(this); currentBlock.setState(Status.OCCUPIED,this);
lastBlocks.add(newBlock); lastBlocks.add(newBlock);
if (lastBlocks.size()>32) lastBlocks.remove(0); if (lastBlocks.size()>32) lastBlocks.remove(0);
} }
@ -776,9 +804,9 @@ public class Train extends BaseClass implements Comparable<Train> {
return properties(); return properties();
} }
protected Route setRoute(Route newRoute) { public Train setRoute(Route newRoute) {
route = newRoute; route = newRoute;
return route; return this;
} }
public void setSpeed(int newSpeed) { public void setSpeed(int newSpeed) {
@ -794,9 +822,9 @@ public class Train extends BaseClass implements Comparable<Train> {
LOG.debug("old trace: {}",trace); LOG.debug("old trace: {}",trace);
trace.removeAll(newTrace); trace.removeAll(newTrace);
for (Tile tile : trace) tile.setTrain(null); for (Tile tile : trace) tile.free();
trace = newTrace; trace = newTrace;
for (Tile tile : trace) tile.setTrain(this); for (Tile tile : trace) tile.setState(Status.OCCUPIED,this);
LOG.debug("new trace of {}: {}",this,trace); LOG.debug("new trace of {}: {}",this,trace);
} }
@ -835,27 +863,33 @@ public class Train extends BaseClass implements Comparable<Train> {
public void start() { public void start() {
Context context = new Context(this).block(currentBlock).direction(direction); new PathFinder(this,currentBlock,direction) {
new PathFinder(context) {
@Override @Override
public void found(Route r) { public void aborted() {
// TODO Auto-generated method stub LOG.debug("Aborted");
LOG.debug("Route {} prepared for {}",r,Train.this);
} }
@Override @Override
public void locked(Route r) { public void found(Route newRoute) {
// TODO Auto-generated method stub // TODO Auto-generated method stub
LOG.debug("Route {} locked for {}",r,Train.this); LOG.debug("Found route {} for {}",newRoute,Train.this);
}
@Override
public void locked(Route newRoute) {
// TODO Auto-generated method stub
LOG.debug("Locked route {} for {}",newRoute,Train.this);
} }
@Override @Override
public void prepared(Route r) { public void prepared(Route newRoute) {
LOG.debug("Route {} prepared for {}",r,Train.this); LOG.debug("Prepared route {} for {}",newRoute,Train.this);
setRoute(r).start(Train.this); newRoute.start(Train.this);
} }
};
}.start();
} }
public static void startAll() { public static void startAll() {
@ -869,7 +903,8 @@ public class Train extends BaseClass implements Comparable<Train> {
} }
public Object stopNow() { public Object stopNow() {
setSpeed(0);
listeners.forEach(listener -> listener.on(Listener.Signal.STOP));
return properties(); return properties();
} }
@ -911,6 +946,7 @@ public class Train extends BaseClass implements Comparable<Train> {
*/ */
public Train turn() { public Train turn() {
LOG.debug("{}.turn()",this); LOG.debug("{}.turn()",this);
setSpeed(0);
for (Car car : cars) car.turn(); for (Car car : cars) car.turn();
Collections.reverse(cars); Collections.reverse(cars);
return reverse(); return reverse();

4
src/main/java/de/srsoftware/web4rail/threads/ControlUnit.java

@ -321,7 +321,9 @@ public class ControlUnit extends Thread implements Constants{
Thread thread = new Thread() { Thread thread = new Thread() {
@Override @Override
public void run() { public void run() {
ControlUnit.this.plan.sensor(addr,active); set(false);
plan.sensor(addr,active);
set(true);
} }
}; };
thread.setName(Application.threadName("CU.FeedBack("+addr+")")); thread.setName(Application.threadName("CU.FeedBack("+addr+")"));

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

@ -18,71 +18,76 @@ import de.srsoftware.web4rail.tiles.Block;
/** /**
* @author Stephan Richter, SRSoftware 2020-2021 * @author Stephan Richter, SRSoftware 2020-2021
*/ */
public abstract class PathFinder extends BaseClass implements Runnable{ public abstract class PathFinder extends BaseClass implements Runnable, Train.Listener{
public static final Logger LOG = LoggerFactory.getLogger(PathFinder.class); public static final Logger LOG = LoggerFactory.getLogger(PathFinder.class);
private Context context; // private Context context;
private boolean aborted = false; private boolean aborted = false;
private Direction direction;
private Block startBlock;
private Train train;
public PathFinder(Context context) { public PathFinder(Train train, Block start, Direction direction) {
this.context = context; this.train = train;
this.startBlock = start;
this.direction = direction;
} }
public void abort() { public void abort() {
aborted = true; aborted = true;
aborted();
LOG.debug("aborted {}",this);
} }
private static TreeMap<Integer,List<Route>> availableRoutes(Context context,HashSet<Route> visitedRoutes){ private static TreeMap<Integer,List<Route>> availableRoutes(Train train, Block start, Direction startDir, 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(inset+"PathFinder.availableRoutes({})",context); LOG.debug(inset+"PathFinder.availableRoutes({})",visitedRoutes);
TreeMap<Integer,List<Route>> availableRoutes = new TreeMap<Integer, List<Route>>();
boolean error = false; boolean error = false;
Block block = context.block(); if (isNull(start)) {
if (isNull(block)) { LOG.warn("{} → {}.availableRoutes called without start block!",inset,Train.class.getSimpleName());
LOG.warn("{} → {}.availableRoutes called without context.block!",inset,Train.class.getSimpleName());
error = true; error = true;
} }
Train train = context.train();
if (isNull(train)) { if (isNull(train)) {
LOG.warn("{}→ {}.availableRoutes called without context.train!",inset,Train.class.getSimpleName()); LOG.warn("{}→ {}.availableRoutes called without train!",inset,Train.class.getSimpleName());
error = true; error = true;
} }
if (error) return availableRoutes; if (error) return new TreeMap<Integer, List<Route>>();
Block destination = train.destination(); if (isSet(startDir)) {
Direction direction = context.direction(); LOG.debug("{}Looking for {}-bound routes from {}",inset,startDir,start);
if (isSet(direction)) {
LOG.debug("{}Looking for {}-bound routes from {}",inset,direction,block);
} else { } else {
LOG.debug("{}Looking for all routes from {}",inset,block); LOG.debug("{}Looking for all routes from {}",inset,start);
}//*/ }//*/
Block destination = train.destination();
if (isSet(destination) && visitedRoutes.isEmpty()) LOG.debug("{}- Destination: {}",inset,destination); if (isSet(destination) && visitedRoutes.isEmpty()) LOG.debug("{}- Destination: {}",inset,destination);
Route currentRoute = context.route(); //Route currentRoute = context.route();
TreeMap<Integer,List<Route>> availableRoutes = new TreeMap<Integer, List<Route>>();
for (Route routeCandidate : block.routes()) {
if (routeCandidate.path().firstElement() != block) continue; // Routen, die nicht vom aktuellen Block starten sind bubu for (Route routeCandidate : start.routes()) {
if (routeCandidate.path().firstElement() != start) continue; // Routen, die nicht vom aktuellen Block starten sind bubu
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;
} }
if (!routeCandidate.allowed(context)) { Context c = new Context(train).block(start).direction(startDir);
if (!routeCandidate.allowed(c)) {
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,c);
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,c);
} }
int priority = 0; int priority = 0;
if (isSet(direction) && routeCandidate.startDirection != direction) { // Route startet entgegen der aktuellen Fahrtrichtung des Zuges if (isSet(startDir) && routeCandidate.startDirection != startDir) { // 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 (!start.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
@ -90,9 +95,8 @@ public abstract class PathFinder extends BaseClass implements Runnable{
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);
visitedRoutes.add(routeCandidate); visitedRoutes.add(routeCandidate);
TreeMap<Integer, List<Route>> forwardRoutes = availableRoutes(forwardContext,visitedRoutes); TreeMap<Integer, List<Route>> forwardRoutes = availableRoutes(train, routeCandidate.endBlock(), routeCandidate.endDirection, 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();
@ -110,7 +114,7 @@ public abstract class PathFinder extends BaseClass implements Runnable{
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,start,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()) { for (Route r : entry.getValue()) {
@ -122,19 +126,20 @@ public abstract class PathFinder extends BaseClass implements Runnable{
public Route chooseRoute() { public Route chooseRoute() {
LOG.debug("PathFinder.chooseRoute()"); LOG.debug("PathFinder.chooseRoute()");
TreeMap<Integer, List<Route>> availableRoutes = availableRoutes(context,new HashSet<Route>()); HashSet<Route> visitedRoutes = new HashSet<Route>();
TreeMap<Integer, List<Route>> availableRoutes = availableRoutes(train, startBlock, direction,visitedRoutes);
while (!availableRoutes.isEmpty()) { while (!availableRoutes.isEmpty()) {
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.route(selectedRoute))) { if (selectedRoute.isFreeFor(train)) {
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,train);
preferredRoutes.remove(selectedRoute); preferredRoutes.remove(selectedRoute);
if (preferredRoutes.isEmpty()) availableRoutes.remove(availableRoutes.lastKey()); if (preferredRoutes.isEmpty()) availableRoutes.remove(availableRoutes.lastKey());
} }
@ -142,17 +147,17 @@ public abstract class PathFinder extends BaseClass implements Runnable{
} }
@Override @Override
public void run() { public void run() {
while (true) { while (true) {
Route route = chooseRoute();
if (aborted) return; if (aborted) return;
Route route = chooseRoute();
if (isSet(route)) { if (isSet(route)) {
found(route); found(route);
if (aborted) return; if (aborted) return;
if (route.lock()) { if (route.allocateFor(train)) {
locked(route); locked(route);
if (aborted) return; if (aborted) return;
if (route.prepare()) { if (route.prepareFor(train)) {
prepared(route); prepared(route);
return; return;
} }
@ -162,7 +167,24 @@ public abstract class PathFinder extends BaseClass implements Runnable{
} }
} }
public abstract void aborted();
public abstract void locked(Route r); public abstract void locked(Route r);
public abstract void found(Route r); public abstract void found(Route r);
public abstract void prepared(Route r); public abstract void prepared(Route r);
@Override
public void on(Signal signal) {
switch (signal) {
case STOP:
abort();
break;
}
}
public void start() {
train.addListener(this);
Thread thread = new Thread(this);
thread.setName("Pathfinder("+train+")");
thread.start();
}
} }

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

@ -146,10 +146,17 @@ public abstract class Block extends StretchableTile{
return t("Trigger contact to learn new contact"); return t("Trigger contact to learn new contact");
} }
@Override
public boolean canNeEnteredBy(Train newTrain) {
if (!super.canNeEnteredBy(newTrain)) return false;
if (parkedTrains.isEmpty()) return true;
return isNull(newTrain) ? false : newTrain.isShunting(); // block contains train(s), thus it is only free for shunting train
}
@Override @Override
protected HashSet<String> classes() { protected HashSet<String> classes() {
HashSet<String> classes = super.classes(); HashSet<String> classes = super.classes();
if (!parkedTrains.isEmpty()) classes.add(OCCUPIED); if (!parkedTrains.isEmpty()) classes.add(Status.OCCUPIED.toString());
return classes; return classes;
} }
@ -229,14 +236,6 @@ public abstract class Block extends StretchableTile{
return 1+internalContacts.indexOf(contact); return 1+internalContacts.indexOf(contact);
} }
@Override
public boolean isFreeFor(Context context) {
if (!super.isFreeFor(context)) return false;
if (parkedTrains.isEmpty()) return true;
Train t = isSet(context) ? context.train() : null;
return isSet(t) ? t.isShunting() : false; // block contains train(s), thus it is olny free for shunting train
}
@Override @Override
public JSONObject json() { public JSONObject json() {
JSONObject json = super.json(); JSONObject json = super.json();
@ -358,7 +357,6 @@ public abstract class Block extends StretchableTile{
super.removeChild(child); super.removeChild(child);
internalContacts.remove(child); internalContacts.remove(child);
if (parkedTrains.remove(child)) plan.place(this); if (parkedTrains.remove(child)) plan.place(this);
if (train == child) setTrain(null);
} }
public void removeContact(BlockContact blockContact) { public void removeContact(BlockContact blockContact) {

5
src/main/java/de/srsoftware/web4rail/tiles/BlockContact.java

@ -27,11 +27,6 @@ public class BlockContact extends Contact {
return new Id(block.name+":"+block.indexOf(this)); return new Id(block.name+":"+block.indexOf(this));
} }
@Override
public Route route() {
return ((Block)parent()).route();
}
@Override @Override
public Tag tag(Map<String, Object> replacements) throws IOException { public Tag tag(Map<String, Object> replacements) throws IOException {
return ((Block)parent()).tag(replacements); return ((Block)parent()).tag(replacements);

28
src/main/java/de/srsoftware/web4rail/tiles/Bridge.java

@ -9,7 +9,6 @@ import org.json.JSONObject;
import de.srsoftware.tools.Tag; import de.srsoftware.tools.Tag;
import de.srsoftware.web4rail.BaseClass; import de.srsoftware.web4rail.BaseClass;
import de.srsoftware.web4rail.Connector; import de.srsoftware.web4rail.Connector;
import de.srsoftware.web4rail.Route;
import de.srsoftware.web4rail.moving.Train; import de.srsoftware.web4rail.moving.Train;
import de.srsoftware.web4rail.tags.Fieldset; import de.srsoftware.web4rail.tags.Fieldset;
import de.srsoftware.web4rail.tags.Window; import de.srsoftware.web4rail.tags.Window;
@ -42,6 +41,12 @@ public abstract class Bridge extends Tile {
protected abstract Connector connector(); protected abstract Connector connector();
@Override
public void free() {
if (isSet(counterpart) && counterpart.train != null) counterpart.free();
super.free();
}
@Override @Override
public JSONObject json() { public JSONObject json() {
JSONObject json = super.json(); JSONObject json = super.json();
@ -63,16 +68,10 @@ public abstract class Bridge extends Tile {
} }
@Override @Override
public Tile setRoute(Route route) { public boolean setState(Status newState, Train newTrain) {
super.setRoute(route); if (train == newTrain && is(newState)) return true;
if (isSet(counterpart) && counterpart.route != route) counterpart.setRoute(route); if (!super.setState(newState,newTrain)) return false;
return this; return isNull(counterpart) ? true : counterpart.setState(newState,newTrain);
}
public Tile setTrain(Train train) {
super.setTrain(train);
if (isSet(counterpart) && counterpart.train != train) counterpart.setTrain(train);
return this;
} }
@Override @Override
@ -109,11 +108,4 @@ public abstract class Bridge extends Tile {
if (isNull(counterpart)) tag.clazz(tag.get("class")+" disconnected"); if (isNull(counterpart)) tag.clazz(tag.get("class")+" disconnected");
return tag; return tag;
} }
@Override
public Tile unset(Route oldRoute) {
super.unset(oldRoute);
if (isSet(counterpart) && isSet(counterpart.route)) counterpart.unset(oldRoute);
return this;
}
} }

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

@ -16,7 +16,6 @@ import org.slf4j.LoggerFactory;
import de.srsoftware.tools.Tag; import de.srsoftware.tools.Tag;
import de.srsoftware.web4rail.Application; import de.srsoftware.web4rail.Application;
import de.srsoftware.web4rail.BaseClass; import de.srsoftware.web4rail.BaseClass;
import de.srsoftware.web4rail.Route;
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.tags.Fieldset; import de.srsoftware.web4rail.tags.Fieldset;
@ -85,12 +84,12 @@ public class Contact extends Tile{
LOG.debug("{} activated.",this); LOG.debug("{} activated.",this);
state = true; state = true;
if (isSet(timer)) timer.abort(); if (isSet(timer)) timer.abort();
Route route = route(); Context context = new Context(this);
Context context = isSet(route) ? route.context().contact(this) : new Context(this); if (isSet(train)) {
train.contact(this);
if (isSet(route)) route.traceTrainFrom(this); context.train(train);
}
actions.fire(context,"Contact("+addr+")"); actions.fire(context,"Contact("+addr+")");
if (isSet(route)) route.contact(this);
for (Listener listener : listeners) listener.fired("Contact("+addr+")"); for (Listener listener : listeners) listener.fired("Contact("+addr+")");

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

@ -40,12 +40,27 @@ import de.srsoftware.web4rail.threads.PathFinder;
* *
*/ */
public abstract class Tile extends BaseClass implements Comparable<Tile>{ public abstract class Tile extends BaseClass implements Comparable<Tile>{
public enum Status{
FREE("free"),
ALLOCATED("allocated"),
LOCKED("locked"),
OCCUPIED("occupied");
private String tx;
Status(String s) {
tx = s;
}
@Override
public String toString() {
return tx;
}
}
protected static Logger LOG = LoggerFactory.getLogger(Tile.class); protected static Logger LOG = LoggerFactory.getLogger(Tile.class);
private static int DEFAUT_LENGTH = 100; // 10cm private static int DEFAUT_LENGTH = 100; // 10cm
private static final String LENGTH = "length"; private static final String LENGTH = "length";
private static final String LOCKED = "locked";
protected static final String OCCUPIED = "occupied";
private static final String ONEW_WAY = "one_way"; private static final String ONEW_WAY = "one_way";
private static final String POS = "pos"; private static final String POS = "pos";
private static final String TYPE = "type"; private static final String TYPE = "type";
@ -56,8 +71,8 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
private boolean isTrack = true; private boolean isTrack = true;
private int length = DEFAUT_LENGTH; private int length = DEFAUT_LENGTH;
protected Direction oneWay = null; protected Direction oneWay = null;
protected Route route = null;
private TreeSet<Route> routes = new TreeSet<>((r1,r2)->r1.toString().compareTo(r2.toString())); private TreeSet<Route> routes = new TreeSet<>((r1,r2)->r1.toString().compareTo(r2.toString()));
private Status status = Status.FREE;
protected Train train = null; protected Train train = null;
public Integer x = null; public Integer x = null;
public Integer y = null; public Integer y = null;
@ -65,13 +80,38 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
public void add(Route route) { public void add(Route route) {
this.routes.add(route); this.routes.add(route);
} }
public boolean canNeEnteredBy(Train newTrain) {
PathFinder.LOG.debug("{}.canNeEnteredBy({})",this,newTrain);
if (disabled) {
PathFinder.LOG.debug("{} is disabled!",this);
return false;
}
if (isNull(train)) {
PathFinder.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);
return true;
}
if (isSet(newTrain) && newTrain.isShunting()) {
PathFinder.LOG.debug("occupied by {}. Allowed for shunting {}",train,newTrain);
return true;
}
PathFinder.LOG.debug("occupied by {} → false",train);
return false;
}
protected HashSet<String> classes(){ protected HashSet<String> classes(){
HashSet<String> classes = new HashSet<String>(); HashSet<String> classes = new HashSet<String>();
classes.add("tile"); classes.add("tile");
classes.add(getClass().getSimpleName()); classes.add(getClass().getSimpleName());
if (isSet(route)) classes.add(LOCKED); if (!is(Status.FREE)) classes.add(status.toString());
if (isSet(train)) classes.add(OCCUPIED);
if (disabled) classes.add(DISABLED); if (disabled) classes.add(DISABLED);
return classes; return classes;
} }
@ -96,6 +136,11 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
return new HashMap<>(); return new HashMap<>();
} }
public void free() {
train = null;
status = Status.FREE;
}
public int height() { public int height() {
return 1; return 1;
} }
@ -115,58 +160,18 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
if (tile instanceof TileWithShadow) ((TileWithShadow)tile).placeShadows(); if (tile instanceof TileWithShadow) ((TileWithShadow)tile).placeShadows();
plan.place(tile); plan.place(tile);
} }
public boolean isFreeFor(Context context) { public boolean is(Status...states) {
PathFinder.LOG.debug("{}.isFreeFor({})",this,context); for (Status s: states) {
if (disabled) { if (status == s) return true;
PathFinder.LOG.debug("{} is disabled!",this);
return false;
}
if (isNull(context)) {
if (isSet(train)) {
PathFinder.LOG.debug("{} is occupied by {}",this,train);
return false;
}
if (isSet(route)) {
PathFinder.LOG.debug("{} is occupied by {}",this,route);
return false;
}
}
if (isSet(train)) {
Train contextTrain = context.train();
boolean free = train == contextTrain; // 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!
if (free) {
PathFinder.LOG.debug("already reserved by {} → true",train);
} else {
if (isSet(contextTrain) && contextTrain.isShunting()) {
PathFinder.LOG.debug("occupied by {}. Allowed for shunting {}",train,contextTrain);
free = true;
} else PathFinder.LOG.debug("occupied by {} → false",train);
}
return free;
}
// if we get here, the tile is not occupied by a train, but reserved by a route, yet. thus, the tile is not available for another route
if (isSet(route) && route != context.route()) {
PathFinder.LOG.debug("reserved by other route: {}",route);
if (isSet(route.train())) {
if (route.train() == context.train()) {
PathFinder.LOG.debug("that route is used by {}, which is also requesting this tile → true",route.train());
return true;
}
}
PathFinder.LOG.debug("{}.route.train = {} → false",this,route.train());
return false;
} }
PathFinder.LOG.debug("free"); return false;
return true;
} }
public JSONObject json() { public JSONObject json() {
JSONObject json = super.json(); JSONObject json = super.json();
json.put(TYPE, getClass().getSimpleName()); json.put(TYPE, getClass().getSimpleName());
if (isSet(x) && isSet(y)) json.put(POS, new JSONObject(Map.of(X,x,Y,y))); if (isSet(x) && isSet(y)) json.put(POS, new JSONObject(Map.of(X,x,Y,y)));
if (isSet(route)) json.put(ROUTE, route.id());
if (isSet(oneWay)) json.put(ONEW_WAY, oneWay); if (isSet(oneWay)) json.put(ONEW_WAY, oneWay);
if (disabled) json.put(DISABLED, true); if (disabled) json.put(DISABLED, true);
if (isSet(train)) json.put(REALM_TRAIN, train.id()); if (isSet(train)) json.put(REALM_TRAIN, train.id());
@ -242,7 +247,7 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
protected void noTrack() { protected void noTrack() {
isTrack = false; isTrack = false;
} }
public Tile position(int x, int y) { public Tile position(int x, int y) {
this.x = x; this.x = x;
this.y = y; this.y = y;
@ -256,16 +261,9 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
@Override @Override
protected Window properties(List<Fieldset> preForm, FormInput formInputs, List<Fieldset> postForm) { protected Window properties(List<Fieldset> preForm, FormInput formInputs, List<Fieldset> postForm) {
Fieldset fieldset = null; Fieldset fieldset = null;
if (isSet(route)) {
fieldset = new Fieldset(t("Route"));
route.link("p",t("Locked by {}",route)).addTo(fieldset);
}
if (isSet(train)) { if (isSet(train)) {
if (isSet(fieldset)) { fieldset = new Fieldset(t("Train"));
fieldset.children().firstElement().content(" / "+t("Train"));
} else fieldset = new Fieldset(t("Train"));
train.link("span", t("Train")+":"+NBSP+train+NBSP).addTo(fieldset); train.link("span", t("Train")+":"+NBSP+train+NBSP).addTo(fieldset);
if (isSet(train.route())) { if (isSet(train.route())) {
train.button(t("stop"), Map.of(ACTION,ACTION_STOP)).addTo(fieldset); train.button(t("stop"), Map.of(ACTION,ACTION_STOP)).addTo(fieldset);
@ -352,10 +350,6 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
} }
return line; return line;
} }
public Route route() {
return route;
}
public TreeSet<Route> routes() { public TreeSet<Route> routes() {
return routes; return routes;
@ -370,24 +364,16 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
file.close(); file.close();
} }
public Tile setTrain(Train newTrain) { public boolean setState(Status newState,Train newTrain) {
LOG.debug("{}.setTrain({})",this,newTrain); if (isNull(newTrain)) return false;
if (newTrain == train) return this; // nothing to update if (isSet(train) && newTrain != train) return false; // already locked by other train
this.train = newTrain; if (is(Status.OCCUPIED,newState)) return true; // do not downgrade occupied tiles, accept current state
return plan.place(this); train = newTrain;
} status = newState;
plan.place(this);
public Tile setRoute(Route lockingRoute) { return true;
LOG.debug("{}.setRoute({})",this,lockingRoute);
if (isNull(lockingRoute)) throw new NullPointerException();
if (isSet(route)) {
if (route == lockingRoute) return this; // nothing changed
throw new IllegalStateException(this.toString()); // tile already locked by other route
}
route = lockingRoute;
return plan.place(this);
} }
public Tag tag(Map<String,Object> replacements) throws IOException { public Tag tag(Map<String,Object> replacements) throws IOException {
int width = 100*width(); int width = 100*width();
int height = 100*height(); int height = 100*height();
@ -477,7 +463,6 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
if (child instanceof Route) routes.remove(child); if (child instanceof Route) routes.remove(child);
if (child == train) train = null; if (child == train) train = null;
if (child == route) route = null;
super.removeChild(child); super.removeChild(child);
plan.place(this); plan.place(this);
} }
@ -487,22 +472,6 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
plan.place(this); plan.place(this);
} }
public void unlock() {
route = null;
train = null;
plan.place(this);
}
public Tile unset(Route oldRoute) {
LOG.debug("{}.unset({})",this,oldRoute);
if (route == null) return this;
if (route == oldRoute) {
route = null;
return plan.place(this);
}
throw new IllegalArgumentException(t("{} not occupied by {}!",this,oldRoute));
}
public Tile update(HashMap<String, String> params) { public Tile update(HashMap<String, String> params) {
LOG.debug("{}.update({})",getClass().getSimpleName(),params); LOG.debug("{}.update({})",getClass().getSimpleName(),params);
String oneWayDir = params.get("oneway"); String oneWayDir = params.get("oneway");

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

@ -159,7 +159,7 @@ public abstract class Turnout extends Tile implements Device{
} }
public Reply state(State newState) { public Reply state(State newState) {
if (train != null && newState != state) return new Reply(415, t("{} locked by {}!",this,train)); if (is(Status.LOCKED,Status.OCCUPIED) && newState != state) return new Reply(415, t("{} locked by {}!",this,train));
if (address == 0) { if (address == 0) {
state = newState; state = newState;
plan.place(this); plan.place(this);

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

@ -16,8 +16,8 @@ public abstract class TurnoutL extends Turnout {
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) { if (!shift) {
if (route != null) { if (isSet(train)) {
plan.stream(t("{} is locked by {}!",this,route)); plan.stream(t("{} is locked by {}!",this,train));
} else state(state == State.STRAIGHT ? State.LEFT : State.STRAIGHT); } else state(state == State.STRAIGHT ? State.LEFT : State.STRAIGHT);
} }
return o; return o;

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

@ -16,8 +16,8 @@ public abstract class TurnoutR extends Turnout {
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) { if (!shift) {
if (route != null) { if (isSet(train)) {
plan.stream(t("{} is locked by {}!",this,route)); plan.stream(t("{} is locked by {}!",this,train));
} else state(state == State.STRAIGHT ? State.RIGHT : State.STRAIGHT); } else state(state == State.STRAIGHT ? State.RIGHT : State.STRAIGHT);
} }
return o; return o;

Loading…
Cancel
Save