started re-implementing route reservation
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -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>
|
||||
|
||||
@@ -77,14 +77,20 @@ svg.Relay rect{
|
||||
fill: white;
|
||||
}
|
||||
|
||||
svg.locked polygon,
|
||||
svg.locked rect:not(.sig_a):not(.sig_b){
|
||||
fill:lime;
|
||||
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;
|
||||
}
|
||||
|
||||
.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{
|
||||
fill: #ddd !important;
|
||||
}
|
||||
|
||||
.occupied .block{
|
||||
fill: yellow;
|
||||
}
|
||||
|
||||
.active circle{
|
||||
fill: #f57900;
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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;
|
||||
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;
|
||||
*/
|
||||
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 {
|
||||
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 {
|
||||
|
||||
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 {
|
||||
conditions = new ConditionList();
|
||||
conditions.parent(this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* process commands from the client
|
||||
* @param params
|
||||
@@ -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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
LOG.debug("{}.reset()",this);
|
||||
|
||||
// TODO
|
||||
state = State.FREE;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
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 {
|
||||
newTrace.add(tile);
|
||||
remainingLength -= tile.length();
|
||||
} else if (Route.freeBehindTrain) {
|
||||
try {
|
||||
tile.unset(this);
|
||||
} catch (IllegalArgumentException e) {}
|
||||
|
||||
// TODO
|
||||
} else break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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> {
|
||||
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> {
|
||||
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> {
|
||||
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> {
|
||||
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> {
|
||||
}
|
||||
|
||||
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> {
|
||||
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> {
|
||||
|
||||
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> {
|
||||
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> {
|
||||
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> {
|
||||
|
||||
|
||||
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) {
|
||||
public void aborted() {
|
||||
LOG.debug("Aborted");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void found(Route newRoute) {
|
||||
// TODO Auto-generated method stub
|
||||
LOG.debug("Route {} prepared for {}",r,Train.this);
|
||||
LOG.debug("Found route {} for {}",newRoute,Train.this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void locked(Route r) {
|
||||
public void locked(Route newRoute) {
|
||||
// TODO Auto-generated method stub
|
||||
LOG.debug("Route {} locked for {}",r,Train.this);
|
||||
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> {
|
||||
}
|
||||
|
||||
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> {
|
||||
*/
|
||||
public Train turn() {
|
||||
LOG.debug("{}.turn()",this);
|
||||
setSpeed(0);
|
||||
for (Car car : cars) car.turn();
|
||||
Collections.reverse(cars);
|
||||
return reverse();
|
||||
|
||||
@@ -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+")"));
|
||||
|
||||
@@ -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{
|
||||
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{
|
||||
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{
|
||||
|
||||
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{
|
||||
}
|
||||
|
||||
@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{
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
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{
|
||||
super.removeChild(child);
|
||||
internalContacts.remove(child);
|
||||
if (parkedTrains.remove(child)) plan.place(this);
|
||||
if (train == child) setTrain(null);
|
||||
}
|
||||
|
||||
public void removeContact(BlockContact blockContact) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
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 {
|
||||
}
|
||||
|
||||
@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 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
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+")");
|
||||
|
||||
|
||||
@@ -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>{
|
||||
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>{
|
||||
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>{
|
||||
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>{
|
||||
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;
|
||||
|
||||
public boolean is(Status...states) {
|
||||
for (Status s: states) {
|
||||
if (status == s) return true;
|
||||
}
|
||||
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 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>{
|
||||
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>{
|
||||
@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>{
|
||||
}
|
||||
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>{
|
||||
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>{
|
||||
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>{
|
||||
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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user