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 @@ @@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>de.srsoftware</groupId>
<artifactId>web4rail</artifactId>
<version>1.3.52</version>
<version>1.3.53</version>
<name>Web4Rail</name>
<packaging>jar</packaging>
<description>Java Model Railway Control</description>

14
resources/css/style.css

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

2
resources/logback.xml

@ -29,7 +29,7 @@ @@ -29,7 +29,7 @@
<appender-ref ref="STDOUT" />
</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.Route" 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; @@ -10,6 +10,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Stack;
import java.util.Vector;
import org.json.JSONArray;
@ -44,6 +45,7 @@ import de.srsoftware.web4rail.tiles.Contact; @@ -44,6 +45,7 @@ import de.srsoftware.web4rail.tiles.Contact;
import de.srsoftware.web4rail.tiles.Shadow;
import de.srsoftware.web4rail.tiles.Signal;
import de.srsoftware.web4rail.tiles.Tile;
import de.srsoftware.web4rail.tiles.Tile.Status;
import de.srsoftware.web4rail.tiles.Turnout;
/**
* A route is a vector of tiles that leads from one block to another.
@ -53,9 +55,9 @@ import de.srsoftware.web4rail.tiles.Turnout; @@ -53,9 +55,9 @@ import de.srsoftware.web4rail.tiles.Turnout;
*/
public class Route extends BaseClass {
public enum State {
/* public enum State {
FREE, LOCKED, PREPARED, STARTED;
}
}*/
public static final Logger LOG = LoggerFactory.getLogger(Route.class);
private static final String ACTIONS = "actions";
@ -70,7 +72,7 @@ public class Route extends BaseClass { @@ -70,7 +72,7 @@ public class Route extends BaseClass {
static final String PATH = "path";
static final String SIGNALS = "signals";
static final String TURNOUTS = "turnouts";
private State state = State.FREE;
//private State state = State.FREE;
public static boolean freeBehindTrain = true;
private static final String ROUTE_START = "route_start";
@ -79,11 +81,9 @@ public class Route extends BaseClass { @@ -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 BrakeProcessor brakeProcessor = null;
private HashMap<String,Integer> brakeTimes = new HashMap<String, Integer>();
private ConditionList conditions;
private Vector<Contact> contacts;
private Context context; // this context is passed to actions
private boolean disabled = false;
private Block endBlock = null;
public Direction endDirection;
@ -100,7 +100,7 @@ public class Route extends BaseClass { @@ -100,7 +100,7 @@ public class Route extends BaseClass {
conditions = new ConditionList();
conditions.parent(this);
}
/**
* process commands from the client
* @param params
@ -121,12 +121,6 @@ public class Route extends BaseClass { @@ -121,12 +121,6 @@ public class Route extends BaseClass {
plan.stream(t("Removed {}.",route));
return plan.properties(new HashMap<String,String>());
case ACTION_PROPS:
return route.properties();
case ACTION_START:
route.set(new Context(route));
route.prepare();
route.context.clear();
return route.properties();
case ACTION_UPDATE:
return route.update(params,plan);
@ -204,6 +198,10 @@ public class Route extends BaseClass { @@ -204,6 +198,10 @@ public class Route extends BaseClass {
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
* @param context
@ -333,6 +331,7 @@ public class Route extends BaseClass { @@ -333,6 +331,7 @@ public class Route extends BaseClass {
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);
if (isNull(actions)) return;
Context context = new Context(this).train(train);
actions.fire(context,"Route.Contact("+contact.addr()+")");
}
@ -373,10 +372,6 @@ public class Route extends BaseClass { @@ -373,10 +372,6 @@ public class Route extends BaseClass {
return win;
}
public Context context() {
return context.clone();
}
public void dropBraketimes(String...brakeIds) {
for (String brakeId : brakeIds) brakeTimes.remove(brakeId);
}
@ -416,10 +411,10 @@ public class Route extends BaseClass { @@ -416,10 +411,10 @@ public class Route extends BaseClass {
return disabled;
}
public boolean isFreeFor(Context context) {
PathFinder.LOG.debug("{}.isFreeFor({})",this,context);
public boolean isFreeFor(Train newTrain) {
PathFinder.LOG.debug("{}.isFreeFor({})",this,newTrain);
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);
return false;
}
@ -627,33 +622,7 @@ public class Route extends BaseClass { @@ -627,33 +622,7 @@ public class Route extends BaseClass {
}
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) {
Vector<Route> routes = new Vector<Route>();
for (int i=0; i<size; i++) routes.add(i==0 ? this : this.clone());
@ -681,12 +650,26 @@ public class Route extends BaseClass { @@ -681,12 +650,26 @@ public class Route extends BaseClass {
return result;
}
public boolean prepare() {
if (state == State.PREPARED || state == State.STARTED) return true;
private boolean pathState(Train newTrain, Status newState) {
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);
ActionList setupActions = triggeredActions.get(ROUTE_SETUP);
if (isSet(setupActions) && !setupActions.fire(context,this+".prepare()")) return false;
state = State.PREPARED;
if (isSet(setupActions) && !setupActions.fire(new Context(newTrain).route(this),this+".prepare()")) return false;
// state = State.PREPARED;
pathState(newTrain,Tile.Status.LOCKED);
return true;
}
@ -760,7 +743,6 @@ public class Route extends BaseClass { @@ -760,7 +743,6 @@ public class Route extends BaseClass {
LOG.debug("{}.reset()",this);
// TODO
state = State.FREE;
return true;
}
@ -778,13 +760,6 @@ public class Route extends BaseClass { @@ -778,13 +760,6 @@ public class Route extends BaseClass {
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) {
if (isNull(state) || state == Turnout.State.UNDEF) return;
Tile lastTile = path.lastElement();
@ -810,20 +785,21 @@ public class Route extends BaseClass { @@ -810,20 +785,21 @@ public class Route extends BaseClass {
return this;
}
public Route.State state(){
return state;
}
public boolean start(Train newTrain) {
if (state == State.STARTED) return true;
// if (state == State.STARTED) return true;
LOG.debug("{}.start()",this);
if (isNull(newTrain)) return false; // can't set route's train to null
if (isSet(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);
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();
return true;
}
@ -865,9 +841,8 @@ public class Route extends BaseClass { @@ -865,9 +841,8 @@ public class Route extends BaseClass {
newTrace.add(tile);
remainingLength -= tile.length();
} else if (Route.freeBehindTrain) {
try {
tile.unset(this);
} catch (IllegalArgumentException e) {}
// TODO
} else break;
}
}

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

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

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

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

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

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

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

@ -321,7 +321,9 @@ public class ControlUnit extends Thread implements Constants{ @@ -321,7 +321,9 @@ public class ControlUnit extends Thread implements Constants{
Thread thread = new Thread() {
@Override
public void run() {
ControlUnit.this.plan.sensor(addr,active);
set(false);
plan.sensor(addr,active);
set(true);
}
};
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; @@ -18,71 +18,76 @@ import de.srsoftware.web4rail.tiles.Block;
/**
* @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);
private Context context;
// private Context context;
private boolean aborted = false;
private Direction direction;
private Block startBlock;
private Train train;
public PathFinder(Context context) {
this.context = context;
public PathFinder(Train train, Block start, Direction direction) {
this.train = train;
this.startBlock = start;
this.direction = direction;
}
public void abort() {
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 = "";
for (int i=0; i<visitedRoutes.size(); i++) inset+=" ";
LOG.debug(inset+"PathFinder.availableRoutes({})",context);
TreeMap<Integer,List<Route>> availableRoutes = new TreeMap<Integer, List<Route>>();
LOG.debug(inset+"PathFinder.availableRoutes({})",visitedRoutes);
boolean error = false;
Block block = context.block();
if (isNull(block)) {
LOG.warn("{} → {}.availableRoutes called without context.block!",inset,Train.class.getSimpleName());
if (isNull(start)) {
LOG.warn("{} → {}.availableRoutes called without start block!",inset,Train.class.getSimpleName());
error = true;
}
Train train = context.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;
}
if (error) return availableRoutes;
if (error) return new TreeMap<Integer, List<Route>>();
Block destination = train.destination();
Direction direction = context.direction();
if (isSet(direction)) {
LOG.debug("{}Looking for {}-bound routes from {}",inset,direction,block);
if (isSet(startDir)) {
LOG.debug("{}Looking for {}-bound routes from {}",inset,startDir,start);
} 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);
Route currentRoute = context.route();
for (Route routeCandidate : block.routes()) {
if (routeCandidate.path().firstElement() != block) continue; // Routen, die nicht vom aktuellen Block starten sind bubu
//Route currentRoute = context.route();
TreeMap<Integer,List<Route>> availableRoutes = new TreeMap<Integer, List<Route>>();
for (Route routeCandidate : start.routes()) {
if (routeCandidate.path().firstElement() != start) continue; // Routen, die nicht vom aktuellen Block starten sind bubu
if (visitedRoutes.contains(routeCandidate)) {
LOG.debug("{}→ Candidate {} would create loop, skipping",inset,routeCandidate.shortName());
continue;
}
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
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
}
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;
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 (!block.turnAllowed) continue; // Wenden im Block nicht gestattet
if (!start.turnAllowed) continue; // Wenden im Block nicht gestattet
priority -= 5;
}
if (routeCandidate == currentRoute) priority-=10; // möglichst andere Route als zuvor wählen // TODO: den Routen einen "last-used" Zeitstempel hinzufügen, und diesen mit in die Priorisierung einbeziehen
//if (routeCandidate == currentRoute) priority-=10; // möglichst andere Route als zuvor wählen // TODO: den Routen einen "last-used" Zeitstempel hinzufügen, und diesen mit in die Priorisierung einbeziehen
if (isSet(destination)) {
if (routeCandidate.endBlock() == destination) { // route goes directly to destination
@ -90,9 +95,8 @@ public abstract class PathFinder extends BaseClass implements Runnable{ @@ -90,9 +95,8 @@ public abstract class PathFinder extends BaseClass implements Runnable{
priority = 1_000_000;
} else {
LOG.debug("{}- Candidate: {}",inset,routeCandidate.shortName());
Context forwardContext = new Context(train).block(routeCandidate.endBlock()).route(null).direction(routeCandidate.endDirection);
visitedRoutes.add(routeCandidate);
TreeMap<Integer, List<Route>> forwardRoutes = availableRoutes(forwardContext,visitedRoutes);
TreeMap<Integer, List<Route>> forwardRoutes = availableRoutes(train, routeCandidate.endBlock(), routeCandidate.endDirection, visitedRoutes);
visitedRoutes.remove(routeCandidate);
if (forwardRoutes.isEmpty()) continue; // the candidate does not lead to a block, from which routes to the destination exist
Entry<Integer, List<Route>> entry = forwardRoutes.lastEntry();
@ -110,7 +114,7 @@ public abstract class PathFinder extends BaseClass implements Runnable{ @@ -110,7 +114,7 @@ public abstract class PathFinder extends BaseClass implements Runnable{
routeSet.add(routeCandidate);
if (routeCandidate.endBlock() == destination) break; // direct connection to destination discovered, quit search
}
if (!availableRoutes.isEmpty()) LOG.debug("{}→ Routes from {}: {}",inset,block,availableRoutes.isEmpty()?"none":"");
if (!availableRoutes.isEmpty()) LOG.debug("{}→ Routes from {}: {}",inset,start,availableRoutes.isEmpty()?"none":"");
for (Entry<Integer, List<Route>> entry : availableRoutes.entrySet()) {
LOG.debug("{} - Priority {}:",inset,entry.getKey());
for (Route r : entry.getValue()) {
@ -122,19 +126,20 @@ public abstract class PathFinder extends BaseClass implements Runnable{ @@ -122,19 +126,20 @@ public abstract class PathFinder extends BaseClass implements Runnable{
public Route 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()) {
LOG.debug("availableRoutes: {}",availableRoutes);
Entry<Integer, List<Route>> entry = availableRoutes.lastEntry();
List<Route> preferredRoutes = entry.getValue();
LOG.debug("preferredRoutes: {}",preferredRoutes);
Route selectedRoute = preferredRoutes.get(random.nextInt(preferredRoutes.size()));
if (selectedRoute.isFreeFor(context.route(selectedRoute))) {
if (selectedRoute.isFreeFor(train)) {
LOG.debug("Chose \"{}\" with priority {}.",selectedRoute,entry.getKey());
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);
if (preferredRoutes.isEmpty()) availableRoutes.remove(availableRoutes.lastKey());
}
@ -142,17 +147,17 @@ public abstract class PathFinder extends BaseClass implements Runnable{ @@ -142,17 +147,17 @@ public abstract class PathFinder extends BaseClass implements Runnable{
}
@Override
public void run() {
public void run() {
while (true) {
Route route = chooseRoute();
if (aborted) return;
Route route = chooseRoute();
if (isSet(route)) {
found(route);
if (aborted) return;
if (route.lock()) {
if (route.allocateFor(train)) {
locked(route);
if (aborted) return;
if (route.prepare()) {
if (route.prepareFor(train)) {
prepared(route);
return;
}
@ -162,7 +167,24 @@ public abstract class PathFinder extends BaseClass implements Runnable{ @@ -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 found(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{ @@ -146,10 +146,17 @@ public abstract class Block extends StretchableTile{
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
protected HashSet<String> classes() {
HashSet<String> classes = super.classes();
if (!parkedTrains.isEmpty()) classes.add(OCCUPIED);
if (!parkedTrains.isEmpty()) classes.add(Status.OCCUPIED.toString());
return classes;
}
@ -229,14 +236,6 @@ public abstract class Block extends StretchableTile{ @@ -229,14 +236,6 @@ public abstract class Block extends StretchableTile{
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
public JSONObject json() {
JSONObject json = super.json();
@ -358,7 +357,6 @@ public abstract class Block extends StretchableTile{ @@ -358,7 +357,6 @@ public abstract class Block extends StretchableTile{
super.removeChild(child);
internalContacts.remove(child);
if (parkedTrains.remove(child)) plan.place(this);
if (train == child) setTrain(null);
}
public void removeContact(BlockContact blockContact) {

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

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

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

@ -9,7 +9,6 @@ import org.json.JSONObject; @@ -9,7 +9,6 @@ import org.json.JSONObject;
import de.srsoftware.tools.Tag;
import de.srsoftware.web4rail.BaseClass;
import de.srsoftware.web4rail.Connector;
import de.srsoftware.web4rail.Route;
import de.srsoftware.web4rail.moving.Train;
import de.srsoftware.web4rail.tags.Fieldset;
import de.srsoftware.web4rail.tags.Window;
@ -42,6 +41,12 @@ public abstract class Bridge extends Tile { @@ -42,6 +41,12 @@ public abstract class Bridge extends Tile {
protected abstract Connector connector();
@Override
public void free() {
if (isSet(counterpart) && counterpart.train != null) counterpart.free();
super.free();
}
@Override
public JSONObject json() {
JSONObject json = super.json();
@ -63,16 +68,10 @@ public abstract class Bridge extends Tile { @@ -63,16 +68,10 @@ public abstract class Bridge extends Tile {
}
@Override
public Tile setRoute(Route route) {
super.setRoute(route);
if (isSet(counterpart) && counterpart.route != route) counterpart.setRoute(route);
return this;
}
public Tile setTrain(Train train) {
super.setTrain(train);
if (isSet(counterpart) && counterpart.train != train) counterpart.setTrain(train);
return this;
public boolean setState(Status newState, Train newTrain) {
if (train == newTrain && is(newState)) return true;
if (!super.setState(newState,newTrain)) return false;
return isNull(counterpart) ? true : counterpart.setState(newState,newTrain);
}
@Override
@ -109,11 +108,4 @@ public abstract class Bridge extends Tile { @@ -109,11 +108,4 @@ public abstract class Bridge extends Tile {
if (isNull(counterpart)) tag.clazz(tag.get("class")+" disconnected");
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; @@ -16,7 +16,6 @@ import org.slf4j.LoggerFactory;
import de.srsoftware.tools.Tag;
import de.srsoftware.web4rail.Application;
import de.srsoftware.web4rail.BaseClass;
import de.srsoftware.web4rail.Route;
import de.srsoftware.web4rail.actions.Action;
import de.srsoftware.web4rail.actions.ActionList;
import de.srsoftware.web4rail.tags.Fieldset;
@ -85,12 +84,12 @@ public class Contact extends Tile{ @@ -85,12 +84,12 @@ public class Contact extends Tile{
LOG.debug("{} activated.",this);
state = true;
if (isSet(timer)) timer.abort();
Route route = route();
Context context = isSet(route) ? route.context().contact(this) : new Context(this);
if (isSet(route)) route.traceTrainFrom(this);
Context context = new Context(this);
if (isSet(train)) {
train.contact(this);
context.train(train);
}
actions.fire(context,"Contact("+addr+")");
if (isSet(route)) route.contact(this);
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; @@ -40,12 +40,27 @@ import de.srsoftware.web4rail.threads.PathFinder;
*
*/
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);
private static int DEFAUT_LENGTH = 100; // 10cm
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 POS = "pos";
private static final String TYPE = "type";
@ -56,8 +71,8 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{ @@ -56,8 +71,8 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
private boolean isTrack = true;
private int length = DEFAUT_LENGTH;
protected Direction oneWay = null;
protected Route route = null;
private TreeSet<Route> routes = new TreeSet<>((r1,r2)->r1.toString().compareTo(r2.toString()));
private Status status = Status.FREE;
protected Train train = null;
public Integer x = null;
public Integer y = null;
@ -65,13 +80,38 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{ @@ -65,13 +80,38 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
public void add(Route 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(){
HashSet<String> classes = new HashSet<String>();
classes.add("tile");
classes.add(getClass().getSimpleName());
if (isSet(route)) classes.add(LOCKED);
if (isSet(train)) classes.add(OCCUPIED);
classes.add(getClass().getSimpleName());
if (!is(Status.FREE)) classes.add(status.toString());
if (disabled) classes.add(DISABLED);
return classes;
}
@ -96,6 +136,11 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{ @@ -96,6 +136,11 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
return new HashMap<>();
}
public void free() {
train = null;
status = Status.FREE;
}
public int height() {
return 1;
}
@ -115,58 +160,18 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{ @@ -115,58 +160,18 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
if (tile instanceof TileWithShadow) ((TileWithShadow)tile).placeShadows();
plan.place(tile);
}
public boolean isFreeFor(Context context) {
PathFinder.LOG.debug("{}.isFreeFor({})",this,context);
if (disabled) {
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;
public boolean is(Status...states) {
for (Status s: states) {
if (status == s) return true;
}
PathFinder.LOG.debug("free");
return true;
return false;
}
public JSONObject json() {
JSONObject json = super.json();
json.put(TYPE, getClass().getSimpleName());
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 (disabled) json.put(DISABLED, true);
if (isSet(train)) json.put(REALM_TRAIN, train.id());
@ -242,7 +247,7 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{ @@ -242,7 +247,7 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
protected void noTrack() {
isTrack = false;
}
public Tile position(int x, int y) {
this.x = x;
this.y = y;
@ -256,16 +261,9 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{ @@ -256,16 +261,9 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
@Override
protected Window properties(List<Fieldset> preForm, FormInput formInputs, List<Fieldset> postForm) {
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(fieldset)) {
fieldset.children().firstElement().content(" / "+t("Train"));
} else fieldset = new Fieldset(t("Train"));
fieldset = new Fieldset(t("Train"));
train.link("span", t("Train")+":"+NBSP+train+NBSP).addTo(fieldset);
if (isSet(train.route())) {
train.button(t("stop"), Map.of(ACTION,ACTION_STOP)).addTo(fieldset);
@ -352,10 +350,6 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{ @@ -352,10 +350,6 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
}
return line;
}
public Route route() {
return route;
}
public TreeSet<Route> routes() {
return routes;
@ -370,24 +364,16 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{ @@ -370,24 +364,16 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
file.close();
}
public Tile setTrain(Train newTrain) {
LOG.debug("{}.setTrain({})",this,newTrain);
if (newTrain == train) return this; // nothing to update
this.train = newTrain;
return plan.place(this);
}
public Tile setRoute(Route lockingRoute) {
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 boolean setState(Status newState,Train newTrain) {
if (isNull(newTrain)) return false;
if (isSet(train) && newTrain != train) return false; // already locked by other train
if (is(Status.OCCUPIED,newState)) return true; // do not downgrade occupied tiles, accept current state
train = newTrain;
status = newState;
plan.place(this);
return true;
}
public Tag tag(Map<String,Object> replacements) throws IOException {
int width = 100*width();
int height = 100*height();
@ -477,7 +463,6 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{ @@ -477,7 +463,6 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
if (child instanceof Route) routes.remove(child);
if (child == train) train = null;
if (child == route) route = null;
super.removeChild(child);
plan.place(this);
}
@ -487,22 +472,6 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{ @@ -487,22 +472,6 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
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) {
LOG.debug("{}.update({})",getClass().getSimpleName(),params);
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{ @@ -159,7 +159,7 @@ public abstract class Turnout extends Tile implements Device{
}
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) {
state = newState;
plan.place(this);

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

@ -16,8 +16,8 @@ public abstract class TurnoutL extends Turnout { @@ -16,8 +16,8 @@ public abstract class TurnoutL extends Turnout {
public Object click(boolean shift) throws IOException {
Object o = super.click(shift);
if (!shift) {
if (route != null) {
plan.stream(t("{} is locked by {}!",this,route));
if (isSet(train)) {
plan.stream(t("{} is locked by {}!",this,train));
} else state(state == State.STRAIGHT ? State.LEFT : State.STRAIGHT);
}
return o;

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

@ -16,8 +16,8 @@ public abstract class TurnoutR extends Turnout { @@ -16,8 +16,8 @@ public abstract class TurnoutR extends Turnout {
public Object click(boolean shift) throws IOException {
Object o = super.click(shift);
if (!shift) {
if (route != null) {
plan.stream(t("{} is locked by {}!",this,route));
if (isSet(train)) {
plan.stream(t("{} is locked by {}!",this,train));
} else state(state == State.STRAIGHT ? State.RIGHT : State.STRAIGHT);
}
return o;

Loading…
Cancel
Save