Started clean re-implementation of
- reserving route
- preparing route
- locking route
New implementation now properly handles stop event from user.
Next things to implement:
- tracing train upon contact activation, taking care of:
- causing contact
- train's last position
- route
- plan.freeBehindTrain
- finish event, keeping brake processor in mind
This commit is contained in:
@@ -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.62</version>
|
<version>1.3.65</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>
|
||||||
|
|||||||
@@ -77,8 +77,8 @@ svg.Relay rect{
|
|||||||
fill: white;
|
fill: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
svg.allocated polygon,
|
svg.reserved polygon,
|
||||||
svg.allocated rect:not(.sig_a):not(.sig_b){
|
svg.reserved rect:not(.sig_a):not(.sig_b){
|
||||||
fill: yellow;
|
fill: yellow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<appender name="STDOUT"
|
<appender name="STDOUT"
|
||||||
class="ch.qos.logback.core.ConsoleAppender">
|
class="ch.qos.logback.core.ConsoleAppender">
|
||||||
<encoder>
|
<encoder>
|
||||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5}: %msg%n
|
||||||
</pattern>
|
</pattern>
|
||||||
</encoder>
|
</encoder>
|
||||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||||
@@ -35,8 +35,8 @@
|
|||||||
<logger name="de.srsoftware.web4rail.actions.Action" level="DEBUG" />
|
<logger name="de.srsoftware.web4rail.actions.Action" level="DEBUG" />
|
||||||
<logger name="de.srsoftware.web4rail.actions.ActionList" level="DEBUG" />
|
<logger name="de.srsoftware.web4rail.actions.ActionList" level="DEBUG" />
|
||||||
<logger name="de.srsoftware.web4rail.moving.Train" level="DEBUG" />
|
<logger name="de.srsoftware.web4rail.moving.Train" level="DEBUG" />
|
||||||
<logger name="de.srsoftware.web4rail.tiles.Contact" level="DEBUG" />
|
<logger name="de.srsoftware.web4rail.tiles" level="DEBUG" />
|
||||||
<logger name="de.srsoftware.web4rail.tiles.Block" level="DEBUG" />
|
<logger name="de.srsoftware.web4rail.tiles" level="DEBUG" />
|
||||||
<logger name="de.srsoftware.web4rail.threads.BrakeProcessor" level="DEBUG" />
|
<logger name="de.srsoftware.web4rail.threads.RouteManager" level="DEBUG" />
|
||||||
|
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
@@ -141,6 +141,10 @@ public abstract class BaseClass implements Constants{
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void invalidate() {
|
||||||
|
setMain(null);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean invalidated() {
|
public boolean invalidated() {
|
||||||
return isNull(main);
|
return isNull(main);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ public interface Constants {
|
|||||||
public static final String DEFAULT_SPEED_UNIT = "km/h";
|
public static final String DEFAULT_SPEED_UNIT = "km/h";
|
||||||
public static final String DEFAULT_LENGTH_UNIT = "mm";
|
public static final String DEFAULT_LENGTH_UNIT = "mm";
|
||||||
public static final String DISABLED = "disabled";
|
public static final String DISABLED = "disabled";
|
||||||
|
public static final String DIRECTION = "direction";
|
||||||
public static final String GITHUB_URL = "https://github.com/srsoftware-de/Web4Rail";
|
public static final String GITHUB_URL = "https://github.com/srsoftware-de/Web4Rail";
|
||||||
public static final String ID = "id";
|
public static final String ID = "id";
|
||||||
public static final String NAME = "name";
|
public static final String NAME = "name";
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import java.util.HashSet;
|
|||||||
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;
|
||||||
@@ -43,7 +42,6 @@ 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.
|
||||||
@@ -82,6 +80,7 @@ public class Route extends BaseClass {
|
|||||||
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;
|
||||||
private boolean disabled = false;
|
private boolean disabled = false;
|
||||||
private Block endBlock = null;
|
private Block endBlock = null;
|
||||||
public Direction endDirection;
|
public Direction endDirection;
|
||||||
@@ -195,10 +194,6 @@ 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
|
||||||
@@ -316,17 +311,18 @@ public class Route extends BaseClass {
|
|||||||
* Kontakt der Route aktivieren
|
* Kontakt der Route aktivieren
|
||||||
* @param contact
|
* @param contact
|
||||||
* @param trainHead
|
* @param trainHead
|
||||||
|
* @return
|
||||||
*/
|
*/
|
||||||
public void contact(Context context) {
|
public Context contact(Contact contact) {
|
||||||
Contact contact = context.contact();
|
context.contact(contact);
|
||||||
if (triggeredContacts.contains(contact)) return; // don't trigger contact a second time
|
if (triggeredContacts.contains(contact)) return context; // don't trigger contact a second time
|
||||||
triggeredContacts.add(contact);
|
triggeredContacts.add(contact);
|
||||||
LOG.debug("{} on {} activated {}.",context.train(),this,contact);
|
LOG.debug("{} on {} activated {}.",context.train(),this,contact);
|
||||||
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 context = new Context(this).train(train).contact(contact);
|
actions.fire(context,"Route.Contact("+contact.addr()+")");
|
||||||
actions.fire(context.route(this),"Route.Contact("+contact.addr()+")");
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector<Contact> contacts() {
|
public Vector<Contact> contacts() {
|
||||||
@@ -377,18 +373,21 @@ public class Route extends BaseClass {
|
|||||||
public void finish(Train train) {
|
public void finish(Train train) {
|
||||||
LOG.debug("{}.finish()",this);
|
LOG.debug("{}.finish()",this);
|
||||||
train.endRoute(endBlock,endDirection);
|
train.endRoute(endBlock,endDirection);
|
||||||
train = null;
|
|
||||||
free();
|
free();
|
||||||
|
train = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void free() {
|
private void free() {
|
||||||
for (Tile tile : path) {
|
context.invalidate();
|
||||||
Train train = tile.train();
|
Train train = context.train();
|
||||||
if (isSet(train) && train.onTrace(tile)) {
|
for (int i=path.size(); i>0; i--) {
|
||||||
tile.setState(Status.OCCUPIED, train);
|
Tile tile = path.get(i-1);
|
||||||
} else tile.free();
|
if (isSet(train) && !train.onTrace(tile)) {
|
||||||
|
tile.free(train);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
context = null;
|
||||||
|
}
|
||||||
|
|
||||||
private String generateName() {
|
private String generateName() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
@@ -414,9 +413,13 @@ public class Route extends BaseClass {
|
|||||||
return disabled;
|
return disabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFreeFor(Train newTrain) {
|
public boolean isFreeFor(Context train) {
|
||||||
LOG.debug("{}.isFreeFor({})",this,newTrain);
|
LOG.debug("{}.isFreeFor({})",this,train);
|
||||||
return false;
|
if (isNull(train.train())) return false;
|
||||||
|
for (Tile tile : path) {
|
||||||
|
if (!tile.isFreeFor(train)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -647,29 +650,24 @@ public class Route extends BaseClass {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean pathState(Train newTrain, Status newState) {
|
public boolean prepareAndLock() {
|
||||||
Stack<Tile> visited = new Stack<>();
|
LOG.debug("{}.prepareAndLock()",this);
|
||||||
for (Tile t : path) {
|
Train train = context.train();
|
||||||
if (t.setState(newState,newTrain)) {
|
ActionList setupActions = triggeredActions.get(ROUTE_SETUP);
|
||||||
visited.push(t);
|
if (isSet(setupActions) && !setupActions.fire(context.route(this),this+".prepare()")) {
|
||||||
} else {
|
LOG.debug("Was not able to prepare route for {}.",train);
|
||||||
while (!visited.isEmpty()) visited.pop().free();
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Tile tile : path) {
|
||||||
|
if (context.invalidated() || !tile.lockFor(context)) {
|
||||||
|
LOG.debug("Was not able to allocate route for {}.",context);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
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(new Context(newTrain).route(this),this+".prepare()")) return false;
|
|
||||||
// state = State.PREPARED;
|
|
||||||
pathState(newTrain,Tile.Status.LOCKED);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Tag previewScript() {
|
private Tag previewScript() {
|
||||||
Tag script = new Tag("script").attr("type", "text/javascript");
|
Tag script = new Tag("script").attr("type", "text/javascript");
|
||||||
for (Tile tile : path) {
|
for (Tile tile : path) {
|
||||||
@@ -736,11 +734,25 @@ public class Route extends BaseClass {
|
|||||||
super.removeChild(child);
|
super.removeChild(child);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean reserveFor(Context newContext) {
|
||||||
|
LOG.debug("{}.reserverFor({})",this,newContext);
|
||||||
|
if (isSet(context)) return false; // route already has context!
|
||||||
|
context = newContext;
|
||||||
|
for (Tile tile : path) {
|
||||||
|
if (newContext.invalidated() || !tile.reserveFor(newContext)) {
|
||||||
|
LOG.debug("Was not able to allocate route for {}.",newContext);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean reset() {
|
public boolean reset() {
|
||||||
LOG.debug("{}.reset()",this);
|
LOG.debug("{}.reset()",this);
|
||||||
setSignals(Signal.RED);
|
setSignals(Signal.RED);
|
||||||
|
Train train = context.train();
|
||||||
free();
|
free();
|
||||||
// train = null;
|
train.drop(this);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -783,21 +795,22 @@ public class Route extends BaseClass {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean start(Train newTrain) {
|
public boolean start() {
|
||||||
// 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(context) || context.invalidated()) return false;
|
||||||
/* if (isSet(train)) {
|
|
||||||
if (newTrain != train) return false; // can't alter route's train
|
Train train = context.train();
|
||||||
} else train = */newTrain.setRoute(this); // set new train
|
if (isNull(train)) return false; // can't set route's train to null
|
||||||
|
train.setRoute(this); // set new train
|
||||||
|
|
||||||
ActionList startActions = triggeredActions.get(ROUTE_START);
|
ActionList startActions = triggeredActions.get(ROUTE_START);
|
||||||
|
|
||||||
if (isSet(startActions)) {
|
if (isSet(startActions)) {
|
||||||
Context context = new Context(newTrain).route(this);
|
context.route(this);
|
||||||
if (!startActions.fire(context,this+".start("+newTrain.name()+")")) return false; // start actions failed
|
String cause = this+".start("+train.name()+")";
|
||||||
|
if (!startActions.fire(context,cause)) return false; // start actions failed
|
||||||
}
|
}
|
||||||
// state = State.STARTED;
|
|
||||||
triggeredContacts.clear();
|
triggeredContacts.clear();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,12 +81,11 @@ public class ActionList extends Action implements Iterable<Action>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean fire(Context context,Object cause) {
|
public boolean fire(Context context,Object cause) {
|
||||||
|
for (Action action : actions) {
|
||||||
if (context.invalidated()) {
|
if (context.invalidated()) {
|
||||||
LOG.debug("Context has been invalidated, aborting {}",this);
|
LOG.debug("Context has been invalidated, aborting {}",this);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Action action : actions) {
|
|
||||||
LOG.debug("firing \"{}\"",action);
|
LOG.debug("firing \"{}\"",action);
|
||||||
if (!action.fire(context,cause)) {
|
if (!action.fire(context,cause)) {
|
||||||
LOG.warn("{} failed",action);
|
LOG.warn("{} failed",action);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public class CoupleTrain extends Action {
|
|||||||
if (isNull(train)) return false;
|
if (isNull(train)) return false;
|
||||||
Block block = train.currentBlock();
|
Block block = train.currentBlock();
|
||||||
if (isNull(block)) return false;
|
if (isNull(block)) return false;
|
||||||
Train parkingTrain = block.parkedTrain(last);
|
Train parkingTrain = block.lastTrain();
|
||||||
if (isNull(parkingTrain)) return false;
|
if (isNull(parkingTrain)) return false;
|
||||||
train.coupleWith(parkingTrain,swap);
|
train.coupleWith(parkingTrain,swap);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public class BlockFree extends Condition {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean fulfilledBy(Context context) {
|
public boolean fulfilledBy(Context context) {
|
||||||
return block.canNeEnteredBy(null) != inverted;
|
return block.isFreeFor(null) != inverted;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ public class Locomotive extends Car implements Constants,Device{
|
|||||||
if (isSet(train)) {
|
if (isSet(train)) {
|
||||||
Block currentBlock = train.currentBlock();
|
Block currentBlock = train.currentBlock();
|
||||||
if (isSet(currentBlock)) {
|
if (isSet(currentBlock)) {
|
||||||
if (isNull(train.route())) {
|
if (!train.isStoppable()) {
|
||||||
params.put(ACTION, ACTION_START);
|
params.put(ACTION, ACTION_START);
|
||||||
new Button(t("depart"),params).addTo(direction);
|
new Button(t("depart"),params).addTo(direction);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,11 +36,11 @@ import de.srsoftware.web4rail.tags.Label;
|
|||||||
import de.srsoftware.web4rail.tags.Select;
|
import de.srsoftware.web4rail.tags.Select;
|
||||||
import de.srsoftware.web4rail.tags.Table;
|
import de.srsoftware.web4rail.tags.Table;
|
||||||
import de.srsoftware.web4rail.tags.Window;
|
import de.srsoftware.web4rail.tags.Window;
|
||||||
|
import de.srsoftware.web4rail.threads.RouteManager;
|
||||||
import de.srsoftware.web4rail.tiles.Block;
|
import de.srsoftware.web4rail.tiles.Block;
|
||||||
import de.srsoftware.web4rail.tiles.BlockContact;
|
import de.srsoftware.web4rail.tiles.BlockContact;
|
||||||
import de.srsoftware.web4rail.tiles.Contact;
|
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 *
|
||||||
@@ -61,7 +61,6 @@ public class Train extends BaseClass implements Comparable<Train> {
|
|||||||
private static final String ROUTE = "route";
|
private static final String ROUTE = "route";
|
||||||
private Route route;
|
private Route route;
|
||||||
|
|
||||||
private static final String DIRECTION = "direction";
|
|
||||||
private Direction direction;
|
private Direction direction;
|
||||||
|
|
||||||
private static final String PUSH_PULL = "pushPull";
|
private static final String PUSH_PULL = "pushPull";
|
||||||
@@ -91,7 +90,7 @@ public class Train extends BaseClass implements Comparable<Train> {
|
|||||||
public int speed = 0;
|
public int speed = 0;
|
||||||
private static final String SHUNTING = "shunting";
|
private static final String SHUNTING = "shunting";
|
||||||
private boolean shunting = false;
|
private boolean shunting = false;
|
||||||
private boolean autopilot = false;
|
private RouteManager routeManager = null;
|
||||||
|
|
||||||
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);
|
||||||
@@ -270,7 +269,7 @@ public class Train extends BaseClass implements Comparable<Train> {
|
|||||||
if (isSet(currentBlock)) {
|
if (isSet(currentBlock)) {
|
||||||
Tag ul = new Tag("ul");
|
Tag ul = new Tag("ul");
|
||||||
if (isSet(currentBlock.train()) && currentBlock.train() != this) currentBlock.train().link().addTo(new Tag("li")).addTo(ul);
|
if (isSet(currentBlock.train()) && currentBlock.train() != this) currentBlock.train().link().addTo(new Tag("li")).addTo(ul);
|
||||||
for (Train tr : currentBlock.parkedTrains()) {
|
for (Train tr : currentBlock.trains()) {
|
||||||
if (tr == this) continue;
|
if (tr == this) continue;
|
||||||
Tag li = new Tag("li").addTo(ul);
|
Tag li = new Tag("li").addTo(ul);
|
||||||
tr.link().addTo(li);
|
tr.link().addTo(li);
|
||||||
@@ -299,12 +298,11 @@ public class Train extends BaseClass implements Comparable<Train> {
|
|||||||
return properties();
|
return properties();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void contact(Contact contact) {
|
public Context contact(Contact contact) {
|
||||||
if (isSet(route)) {
|
if (isNull(route)) return new Context(contact).train(this);
|
||||||
Route lastRoute = route; // route field might be set to null during route.contact(...)!
|
Context context = route.contact(contact);
|
||||||
route.contact(new Context(contact).train(this));
|
traceFrom(context);
|
||||||
traceFrom(contact,lastRoute);
|
return context;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -357,12 +355,11 @@ public class Train extends BaseClass implements Comparable<Train> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String directedName(Direction dir) {
|
||||||
public String directedName() {
|
|
||||||
String result = name();
|
String result = name();
|
||||||
String mark = ""; //isSet(autopilot) ? "ⓐ" : "";
|
String mark = ""; //isSet(autopilot) ? "ⓐ" : "";
|
||||||
if (isNull(direction)) return result;
|
if (isNull(dir)) return result;
|
||||||
switch (direction) {
|
switch (dir) {
|
||||||
case NORTH:
|
case NORTH:
|
||||||
case WEST:
|
case WEST:
|
||||||
return '←'+mark+result;
|
return '←'+mark+result;
|
||||||
@@ -370,7 +367,7 @@ public class Train extends BaseClass implements Comparable<Train> {
|
|||||||
case EAST:
|
case EAST:
|
||||||
return result+mark+'→';
|
return result+mark+'→';
|
||||||
}
|
}
|
||||||
return result;
|
return mark+result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Direction direction() {
|
public Direction direction() {
|
||||||
@@ -392,8 +389,16 @@ public class Train extends BaseClass implements Comparable<Train> {
|
|||||||
return properties();
|
return properties();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean drop(Route oldRoute) {
|
||||||
|
if (isNull(route)) return true;
|
||||||
|
if (route != oldRoute) return false;
|
||||||
|
route = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void dropTrace() {
|
public void dropTrace() {
|
||||||
while (!trace.isEmpty()) trace.removeFirst().free();
|
while (!trace.isEmpty()) trace.removeFirst().free(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void endRoute(Block newBlock, Direction newDirection) {
|
public void endRoute(Block newBlock, Direction newDirection) {
|
||||||
@@ -438,6 +443,12 @@ public class Train extends BaseClass implements Comparable<Train> {
|
|||||||
return shunting;
|
return shunting;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isStoppable() {
|
||||||
|
if (speed > 0) return true;
|
||||||
|
if (isSet(routeManager) && routeManager.isActive()) return true;
|
||||||
|
if (isSet(route)) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public JSONObject json() {
|
public JSONObject json() {
|
||||||
JSONObject json = super.json();
|
JSONObject json = super.json();
|
||||||
@@ -500,11 +511,11 @@ public class Train extends BaseClass implements Comparable<Train> {
|
|||||||
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 -> {
|
if (json.has(TRACE)) json.getJSONArray(TRACE).forEach(elem -> {
|
||||||
Tile tile = plan.get(new Id(elem.toString()), false);
|
Tile tile = plan.get(new Id(elem.toString()), false);
|
||||||
if (tile.setState(Status.OCCUPIED,this)) trace.add(tile);
|
if (tile.setTrain(this)) trace.add(tile);
|
||||||
});
|
});
|
||||||
if (json.has(BLOCK)) {// do not move this up! during set, other fields will be referenced!
|
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 = (Block) plan.get(new Id(json.getString(BLOCK)), false);
|
||||||
currentBlock.setState(Status.OCCUPIED,this);
|
currentBlock.add(this, direction);
|
||||||
}
|
}
|
||||||
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)));
|
||||||
@@ -654,10 +665,7 @@ public class Train extends BaseClass implements Comparable<Train> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String quitAutopilot() {
|
public String quitAutopilot() {
|
||||||
if (autopilot) {
|
if (isSet(routeManager)) routeManager.quit();
|
||||||
autopilot = false;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return t("Autopilot already was disabled!");
|
return t("Autopilot already was disabled!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -682,6 +690,7 @@ public class Train extends BaseClass implements Comparable<Train> {
|
|||||||
//if (child == nextRoute) nextRoute = null; // TODO
|
//if (child == nextRoute) nextRoute = null; // TODO
|
||||||
if (child == currentBlock) currentBlock = null;
|
if (child == currentBlock) currentBlock = null;
|
||||||
if (child == destination) destination = null;
|
if (child == destination) destination = null;
|
||||||
|
if (child == routeManager) routeManager = null;
|
||||||
cars.remove(child);
|
cars.remove(child);
|
||||||
trace.remove(child);
|
trace.remove(child);
|
||||||
super.removeChild(child);
|
super.removeChild(child);
|
||||||
@@ -747,10 +756,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.free();
|
if (isSet(currentBlock)) currentBlock.free(this);
|
||||||
currentBlock = newBlock;
|
currentBlock = newBlock;
|
||||||
if (isSet(currentBlock)) {
|
if (isSet(currentBlock)) {
|
||||||
currentBlock.setState(Status.OCCUPIED,this);
|
currentBlock.setTrain(this);
|
||||||
lastBlocks.add(newBlock);
|
lastBlocks.add(newBlock);
|
||||||
if (lastBlocks.size()>32) lastBlocks.remove(0);
|
if (lastBlocks.size()>32) lastBlocks.remove(0);
|
||||||
}
|
}
|
||||||
@@ -812,18 +821,6 @@ public class Train extends BaseClass implements Comparable<Train> {
|
|||||||
plan.stream(t("Set {} to {} {}",this,speed,speedUnit));
|
plan.stream(t("Set {} to {} {}",this,speed,speedUnit));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTrace(LinkedList<Tile> newTrace) {
|
|
||||||
LOG.debug("{}.setTrace({})",this,newTrace);
|
|
||||||
LOG.debug("old trace: {}",trace);
|
|
||||||
|
|
||||||
trace.removeAll(newTrace);
|
|
||||||
for (Tile tile : trace) tile.free();
|
|
||||||
trace = newTrace;
|
|
||||||
for (Tile tile : trace) tile.setState(Status.OCCUPIED,this);
|
|
||||||
|
|
||||||
LOG.debug("new trace of {}: {}",this,trace);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Tag slower(int steps) {
|
private Tag slower(int steps) {
|
||||||
setSpeed(speed-steps);
|
setSpeed(speed-steps);
|
||||||
return properties();
|
return properties();
|
||||||
@@ -850,15 +847,18 @@ public class Train extends BaseClass implements Comparable<Train> {
|
|||||||
if (remaining.cars.isEmpty()) return false;
|
if (remaining.cars.isEmpty()) return false;
|
||||||
remaining.direction = this.direction;
|
remaining.direction = this.direction;
|
||||||
this.name = null;
|
this.name = null;
|
||||||
currentBlock.add(remaining);
|
currentBlock.add(remaining,direction);
|
||||||
remaining.currentBlock = currentBlock;
|
remaining.currentBlock = currentBlock;
|
||||||
plan.place(currentBlock);
|
plan.place(currentBlock);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public String start(boolean autopilot) {
|
public String start(boolean auto) {
|
||||||
return t("{}.start() not implemented",this);
|
if (isNull(routeManager)) routeManager = new RouteManager(this);
|
||||||
|
routeManager.setAuto(auto);
|
||||||
|
plan.stream(t("Started {}",this));
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void startAll() {
|
public static void startAll() {
|
||||||
@@ -871,6 +871,9 @@ public class Train extends BaseClass implements Comparable<Train> {
|
|||||||
public Window stopNow() {
|
public Window stopNow() {
|
||||||
setSpeed(0);
|
setSpeed(0);
|
||||||
quitAutopilot();
|
quitAutopilot();
|
||||||
|
if (isSet(route)) {
|
||||||
|
route.reset();
|
||||||
|
}
|
||||||
return properties();
|
return properties();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -900,31 +903,9 @@ public class Train extends BaseClass implements Comparable<Train> {
|
|||||||
return name();
|
return name();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void traceFrom(Tile newHead,Route route) {
|
public void traceFrom(Context context) {
|
||||||
LOG.debug("{}.traceTrainFrom({})",this,newHead);
|
// TOSO: neu implementieren!
|
||||||
if (isNull(route)) return;
|
// Beachten: Route aus Context, plan.freeBehindTrain
|
||||||
if (newHead instanceof BlockContact) newHead = (Tile) ((BlockContact)newHead).parent();
|
|
||||||
|
|
||||||
Tile traceHead = traceHead();
|
|
||||||
Integer remainingLength = null;
|
|
||||||
LinkedList<Tile> newTrace = new LinkedList<Tile>();
|
|
||||||
Vector<Tile> path = route.path();
|
|
||||||
for (int i=path.size(); i>0; i--) { // pfad rückwärts ablaufen
|
|
||||||
Tile tile = path.elementAt(i-1);
|
|
||||||
if (isNull(remainingLength)) {
|
|
||||||
if (tile == newHead) traceHead = newHead; // wenn wir zuerst newHead finden: newHead als neuen traceHead übernehmen
|
|
||||||
if (tile == traceHead) remainingLength = length(); // sobald wir auf den traceHead stoßen: Suche beenden
|
|
||||||
}
|
|
||||||
if (isSet(remainingLength)) {
|
|
||||||
if (remainingLength>=0) {
|
|
||||||
newTrace.add(tile);
|
|
||||||
remainingLength -= tile.length();
|
|
||||||
} else if (Route.freeBehindTrain) {
|
|
||||||
tile.free();
|
|
||||||
} else break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setTrace(newTrace);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Tile traceHead() {
|
public Tile traceHead() {
|
||||||
@@ -962,11 +943,6 @@ public class Train extends BaseClass implements Comparable<Train> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean usesAutopilot() {
|
public boolean usesAutopilot() {
|
||||||
return autopilot ;
|
return isSet(routeManager) && routeManager.autoEnabled();
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isStoppable() {
|
|
||||||
if (speed > 0) return true;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,204 @@
|
|||||||
|
package de.srsoftware.web4rail.threads;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import de.srsoftware.web4rail.BaseClass;
|
||||||
|
import de.srsoftware.web4rail.Plan.Direction;
|
||||||
|
import de.srsoftware.web4rail.Route;
|
||||||
|
import de.srsoftware.web4rail.moving.Train;
|
||||||
|
import de.srsoftware.web4rail.tiles.Block;
|
||||||
|
|
||||||
|
public class RouteManager extends BaseClass implements Runnable {
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
ENDED,IDLE,STARTED;
|
||||||
|
}
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(RouteManager.class);
|
||||||
|
private static final int DEFAULT_PAUSE_TIME = 250; // ms
|
||||||
|
private State state = State.IDLE;
|
||||||
|
private boolean autopilot;
|
||||||
|
private Context context;
|
||||||
|
private int time = 0;
|
||||||
|
|
||||||
|
public RouteManager(Train train) {
|
||||||
|
context = new Context(train);
|
||||||
|
state = State.STARTED;
|
||||||
|
Thread thread = new Thread(this);
|
||||||
|
thread.setName(train.name());
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TreeMap<Integer,List<Route>> availableRoutes(Context context,HashSet<Route> visitedRoutes){
|
||||||
|
String inset = "";
|
||||||
|
for (int i=0; i<visitedRoutes.size(); i++) inset+=" ";
|
||||||
|
LOG.debug("{}{}.availableRoutes({})",inset,RouteManager.class.getSimpleName(),context);
|
||||||
|
|
||||||
|
Block block = context.block();
|
||||||
|
Train train = context.train();
|
||||||
|
Direction startDirection = context.direction();
|
||||||
|
Route currentRoute = context.route();
|
||||||
|
TreeMap<Integer,List<Route>> availableRoutes = new TreeMap<Integer, List<Route>>();
|
||||||
|
|
||||||
|
boolean error = false;
|
||||||
|
if (isNull(block) && (error = true)) LOG.warn("{} → {}.availableRoutes called without context.block!",inset,Train.class.getSimpleName());
|
||||||
|
if (isNull(train) && (error = true)) LOG.warn("{}→ {}.availableRoutes called without context.train!", inset,Train.class.getSimpleName());
|
||||||
|
if (error) return availableRoutes;
|
||||||
|
|
||||||
|
Block destination = train.destination();
|
||||||
|
if (isSet(startDirection)) {
|
||||||
|
LOG.debug("{}- Looking for {}-bound routes from {}",inset,startDirection,block);
|
||||||
|
} else {
|
||||||
|
LOG.debug("{}- Looking for all routes from {}",inset,block);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSet(destination) && visitedRoutes.isEmpty()) LOG.debug("{}- Destination: {}",inset,destination);
|
||||||
|
|
||||||
|
for (Route routeCandidate : block.routes(startDirection)) {
|
||||||
|
if (context.invalidated()) return availableRoutes;
|
||||||
|
if (visitedRoutes.contains(routeCandidate)) {
|
||||||
|
LOG.debug("{}→ Candidate {} would create loop, skipping",inset,routeCandidate.shortName());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!routeCandidate.allowed(context)) {
|
||||||
|
if (routeCandidate.endBlock() != destination) { // allowance may be overridden by destination
|
||||||
|
LOG.debug("{} not allowed for {}",routeCandidate,context);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
int priority = 0;
|
||||||
|
if (isSet(startDirection) && routeCandidate.startDirection != startDirection) { // 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
|
||||||
|
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 (isSet(destination)) {
|
||||||
|
if (routeCandidate.endBlock() == destination) { // route goes directly to destination
|
||||||
|
LOG.debug("{}→ Candidate {} directly leads to {}",inset,routeCandidate.shortName(),destination);
|
||||||
|
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);
|
||||||
|
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();
|
||||||
|
LOG.debug("{}→ The following routes have connections to {}:",inset,destination);
|
||||||
|
for (Route rt: entry.getValue()) LOG.debug("{} - {}",inset,rt.shortName());
|
||||||
|
priority += entry.getKey()-10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Route> routeSet = availableRoutes.get(priority);
|
||||||
|
if (isNull(routeSet)) {
|
||||||
|
routeSet = new Vector<Route>();
|
||||||
|
availableRoutes.put(priority, routeSet);
|
||||||
|
}
|
||||||
|
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":"");
|
||||||
|
for (Entry<Integer, List<Route>> entry : availableRoutes.entrySet()) {
|
||||||
|
LOG.debug("{} - Priority {}:",inset,entry.getKey());
|
||||||
|
for (Route r : entry.getValue()) LOG.debug("{} - {}",inset,r.shortName());
|
||||||
|
}
|
||||||
|
return availableRoutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean autoEnabled() {
|
||||||
|
return autopilot && isActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Route chooseRoute(Context context) {
|
||||||
|
LOG.debug("{}.chooseRoute({})",RouteManager.class.getSimpleName(),context);
|
||||||
|
TreeMap<Integer, List<Route>> availableRoutes = availableRoutes(context,new HashSet<Route>());
|
||||||
|
while (!availableRoutes.isEmpty()) {
|
||||||
|
if (context.invalidated()) break;
|
||||||
|
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)) {
|
||||||
|
LOG.debug("Chose \"{}\" with priority {}.",selectedRoute,entry.getKey());
|
||||||
|
return selectedRoute;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("Selected route \"{}\" is not free for {}",selectedRoute,context);
|
||||||
|
preferredRoutes.remove(selectedRoute);
|
||||||
|
if (preferredRoutes.isEmpty()) availableRoutes.remove(availableRoutes.lastKey());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isActive() {
|
||||||
|
switch (state) {
|
||||||
|
case ENDED:
|
||||||
|
return false;
|
||||||
|
case IDLE:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pause(){
|
||||||
|
if (time == 0) {
|
||||||
|
time = DEFAULT_PAUSE_TIME;
|
||||||
|
} else sleep(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void quit() {
|
||||||
|
LOG.debug("{}.quit",this);
|
||||||
|
autopilot = false;
|
||||||
|
context.invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Train train = context.train();
|
||||||
|
try {
|
||||||
|
do {
|
||||||
|
pause();
|
||||||
|
if (context.invalidated()) return;
|
||||||
|
context.block(train.currentBlock()).direction(train.direction());
|
||||||
|
Route route = chooseRoute(context);
|
||||||
|
if (isNull(route)) continue;
|
||||||
|
context.route(route);
|
||||||
|
if (!route.reserveFor(context)) {
|
||||||
|
route.reset();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!route.prepareAndLock()) {
|
||||||
|
route.reset();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Route reserved, prepared and locked:
|
||||||
|
if (!route.start()) {
|
||||||
|
route.reset();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} while (autopilot);
|
||||||
|
} finally {
|
||||||
|
// do not invalidate context here: may be used in delayed actions called from (successful) start
|
||||||
|
state = State.ENDED;
|
||||||
|
train.removeChild(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuto(boolean auto) {
|
||||||
|
LOG.debug("{}abled autopilot of {}",auto?"En":"Dis");
|
||||||
|
autopilot = auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,11 +3,13 @@ package de.srsoftware.web4rail.tiles;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.Iterator;
|
||||||
|
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.Vector;
|
import java.util.Vector;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
@@ -20,6 +22,7 @@ import de.srsoftware.web4rail.BaseClass;
|
|||||||
import de.srsoftware.web4rail.Connector;
|
import de.srsoftware.web4rail.Connector;
|
||||||
import de.srsoftware.web4rail.Plan.Direction;
|
import de.srsoftware.web4rail.Plan.Direction;
|
||||||
import de.srsoftware.web4rail.Range;
|
import de.srsoftware.web4rail.Range;
|
||||||
|
import de.srsoftware.web4rail.Route;
|
||||||
import de.srsoftware.web4rail.moving.Train;
|
import de.srsoftware.web4rail.moving.Train;
|
||||||
import de.srsoftware.web4rail.tags.Button;
|
import de.srsoftware.web4rail.tags.Button;
|
||||||
import de.srsoftware.web4rail.tags.Checkbox;
|
import de.srsoftware.web4rail.tags.Checkbox;
|
||||||
@@ -35,6 +38,53 @@ import de.srsoftware.web4rail.tags.Window;
|
|||||||
*/
|
*/
|
||||||
public abstract class Block extends StretchableTile{
|
public abstract class Block extends StretchableTile{
|
||||||
protected static Logger LOG = LoggerFactory.getLogger(Block.class);
|
protected static Logger LOG = LoggerFactory.getLogger(Block.class);
|
||||||
|
|
||||||
|
private class TrainList implements Iterable<Train>{
|
||||||
|
|
||||||
|
private LinkedList<Train> trains = new LinkedList<Train>();
|
||||||
|
private HashMap<Train, Direction> dirs = new HashMap<Train, Direction>();
|
||||||
|
|
||||||
|
public void add(Train train, Direction direction) {
|
||||||
|
trains.remove(train);
|
||||||
|
trains.addFirst(train);
|
||||||
|
if (isSet(direction)) dirs.put(train, direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Direction directionOf(Train train) {
|
||||||
|
return dirs.get(train);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Train first() {
|
||||||
|
return trains.isEmpty() ? null : trains.getFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return trains.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<Train> iterator() {
|
||||||
|
return trains.iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Train last() {
|
||||||
|
return trains.isEmpty() ? null : trains.getLast();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Train> list() {
|
||||||
|
return new Vector<>(trains);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean remove(BaseClass b) {
|
||||||
|
dirs.remove(b);
|
||||||
|
return trains.remove(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return trains.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
private static final String ALLOW_TURN = "allowTurn";
|
private static final String ALLOW_TURN = "allowTurn";
|
||||||
private static final String NAME = "name";
|
private static final String NAME = "name";
|
||||||
private static final String NO_TAG = "[default]";
|
private static final String NO_TAG = "[default]";
|
||||||
@@ -44,11 +94,12 @@ public abstract class Block extends StretchableTile{
|
|||||||
private static final String RAISE = "raise";
|
private static final String RAISE = "raise";
|
||||||
public static final String ACTION_ADD_CONTACT = "add_contact";
|
public static final String ACTION_ADD_CONTACT = "add_contact";
|
||||||
private static final String PARKED_TRAINS = "parked_trains";
|
private static final String PARKED_TRAINS = "parked_trains";
|
||||||
|
private static final String TRAINS = "parked_trains";
|
||||||
|
|
||||||
public String name = "Block";
|
public String name = "Block";
|
||||||
public boolean turnAllowed = false;
|
public boolean turnAllowed = false;
|
||||||
private Vector<BlockContact> internalContacts = new Vector<BlockContact>();
|
private Vector<BlockContact> internalContacts = new Vector<BlockContact>();
|
||||||
private Vector<Train> parkedTrains = new Vector<Train>();
|
private TrainList trains = new TrainList();
|
||||||
|
|
||||||
public Block() {
|
public Block() {
|
||||||
super();
|
super();
|
||||||
@@ -134,9 +185,9 @@ public abstract class Block extends StretchableTile{
|
|||||||
|
|
||||||
private Vector<WaitTime> waitTimes = new Vector<WaitTime>();
|
private Vector<WaitTime> waitTimes = new Vector<WaitTime>();
|
||||||
|
|
||||||
public void add(Train parkedTrain) {
|
public void add(Train train,Direction direction) {
|
||||||
parkedTrain.register();
|
train.register();
|
||||||
parkedTrains.add(parkedTrain);
|
trains.add(train,direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -147,22 +198,17 @@ public abstract class Block extends StretchableTile{
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canNeEnteredBy(Train newTrain) {
|
public boolean isFreeFor(Context context) {
|
||||||
if (!super.canNeEnteredBy(newTrain)) return false;
|
Train train = context.train();
|
||||||
if (parkedTrains.isEmpty()) return true;
|
if (is(Status.DISABLED)) return false;
|
||||||
return isNull(newTrain) ? false : newTrain.isShunting(); // block contains train(s), thus it is only free for shunting train
|
if (trains.isEmpty()) return true;
|
||||||
}
|
if (trains.first() == train) return true;
|
||||||
|
return train.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(Status.OCCUPIED.toString());
|
|
||||||
return classes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object click(boolean shift) throws IOException {
|
public Object click(boolean shift) throws IOException {
|
||||||
if (isSet(train) && !shift) return train.properties();
|
if (!trains.isEmpty() && !shift) return trains.first().properties();
|
||||||
return super.click(false);
|
return super.click(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,6 +257,27 @@ public abstract class Block extends StretchableTile{
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void dropFirstTrain() {
|
||||||
|
Train train = trains.first();
|
||||||
|
if (isSet(train)) {
|
||||||
|
train.dropTrace();
|
||||||
|
train.set(null);
|
||||||
|
trains.remove(train);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean free(Train train) {
|
||||||
|
LOG.debug("{}.free()");
|
||||||
|
Train firstTrain = trains.first();
|
||||||
|
if (isNull(firstTrain)) return true;
|
||||||
|
if (firstTrain != train) return false;
|
||||||
|
trains.remove(train);
|
||||||
|
status = trains.isEmpty() ? Status.FREE : Status.OCCUPIED;
|
||||||
|
plan.place(this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private WaitTime getWaitTime(String tag) {
|
private WaitTime getWaitTime(String tag) {
|
||||||
if (tag == null) return null;
|
if (tag == null) return null;
|
||||||
for (WaitTime wt : waitTimes) {
|
for (WaitTime wt : waitTimes) {
|
||||||
@@ -252,9 +319,9 @@ public abstract class Block extends StretchableTile{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isSet(jContacts)) json.put(CONTACT, jContacts);
|
if (isSet(jContacts)) json.put(CONTACT, jContacts);
|
||||||
if (!parkedTrains.isEmpty()) {
|
if (!trains.isEmpty()) {
|
||||||
JSONArray ptids = new JSONArray();
|
JSONArray ptids = new JSONArray();
|
||||||
for (Train parked : parkedTrains) {
|
for (Train parked : trains) {
|
||||||
if (isSet(parked)) ptids.put(parked.id().toString());
|
if (isSet(parked)) ptids.put(parked.id().toString());
|
||||||
}
|
}
|
||||||
json.put(PARKED_TRAINS, ptids);
|
json.put(PARKED_TRAINS, ptids);
|
||||||
@@ -262,6 +329,11 @@ public abstract class Block extends StretchableTile{
|
|||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Train lastTrain() {
|
||||||
|
return trains.last();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If arguments are given, the first is taken as content, the second as tag type.
|
* If arguments are given, the first is taken as content, the second as tag type.
|
||||||
* If no content is supplied, name is set as content.
|
* If no content is supplied, name is set as content.
|
||||||
@@ -294,44 +366,61 @@ public abstract class Block extends StretchableTile{
|
|||||||
} catch (JSONException e) {}
|
} catch (JSONException e) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (json.has(PARKED_TRAINS)) {
|
if (json.has(TRAINS)) {
|
||||||
|
JSONArray jTrains = json.getJSONArray(TRAINS);
|
||||||
|
for (Object o : jTrains) {
|
||||||
|
if (o instanceof JSONObject) {
|
||||||
|
JSONObject to = (JSONObject) o;
|
||||||
|
Train train = BaseClass.get(new Id(to.getString(ID)));
|
||||||
|
Direction direction = to.has(DIRECTION) ? Direction.valueOf(to.getString(DIRECTION)) : null;
|
||||||
|
if (isSet(train)) trains.add(train, direction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (json.has(PARKED_TRAINS)) { // legacy
|
||||||
JSONArray ptids = json.getJSONArray(PARKED_TRAINS);
|
JSONArray ptids = json.getJSONArray(PARKED_TRAINS);
|
||||||
for (Object id : ptids) {
|
for (Object id : ptids) {
|
||||||
Train train = BaseClass.get(new Id(id.toString()));
|
Train train = BaseClass.get(new Id(id.toString()));
|
||||||
if (isSet(train)) parkedTrains.add(train);
|
if (isSet(train)) trains.add(train,null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return super.load(json);
|
return super.load(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Fieldset parkedTrainList() {
|
@Override
|
||||||
Fieldset fieldset = new Fieldset(t("parked trains"));
|
public boolean lockFor(Context context) {
|
||||||
Tag list = new Tag("ul");
|
Train newTrain = context.train();
|
||||||
for (Train t : parkedTrains) {
|
Route route = context.route();
|
||||||
if (isSet(t)) t.link("li", t).addTo(list);
|
LOG.debug("{}.lock({})",this,newTrain);
|
||||||
|
if (isNull(newTrain)) return false;
|
||||||
|
Train train = trains.first();
|
||||||
|
if (isSet(train) && train != newTrain) return false;
|
||||||
|
switch (status) {
|
||||||
|
case DISABLED:
|
||||||
|
return false;
|
||||||
|
case FREE:
|
||||||
|
case RESERVED:
|
||||||
|
status = Status.LOCKED;
|
||||||
|
Direction dir = trains.directionOf(train);
|
||||||
|
if (isSet(route) && this == route.endBlock()) dir = route.endDirection;
|
||||||
|
add(newTrain,dir);
|
||||||
|
plan.place(this);
|
||||||
|
break;
|
||||||
|
case OCCUPIED:
|
||||||
|
case LOCKED:
|
||||||
|
break; // do not downgrade
|
||||||
}
|
}
|
||||||
list.addTo(fieldset);
|
return true;
|
||||||
return fieldset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Train> parkedTrains(){
|
|
||||||
return parkedTrains;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Train parkedTrain(boolean last) {
|
|
||||||
if (parkedTrains.isEmpty()) return null;
|
|
||||||
return last ? parkedTrains.lastElement() : parkedTrains.firstElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Window properties(List<Fieldset> preForm, FormInput formInputs, List<Fieldset> postForm,String...errors) {
|
protected Window properties(List<Fieldset> preForm, FormInput formInputs, List<Fieldset> postForm,String...errors) {
|
||||||
formInputs.add(t("Name"),new Input(NAME, name));
|
formInputs.add(t("Name"),new Input(NAME, name));
|
||||||
formInputs.add("",new Checkbox(ALLOW_TURN,t("Turn allowed"),turnAllowed));
|
formInputs.add("",new Checkbox(ALLOW_TURN,t("Turn allowed"),turnAllowed));
|
||||||
formInputs.add(t("Train"),Train.selector(train, null));
|
formInputs.add(t("Train"),Train.selector(trains.first(), null));
|
||||||
postForm.add(contactForm());
|
postForm.add(contactForm());
|
||||||
postForm.add(waitTimeForm());
|
postForm.add(waitTimeForm());
|
||||||
if (!parkedTrains.isEmpty()) postForm.add(parkedTrainList());
|
if (!trains.isEmpty()) postForm.add(trainList());
|
||||||
return super.properties(preForm, formInputs, postForm,errors);
|
return super.properties(preForm, formInputs, postForm,errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,6 +436,32 @@ public abstract class Block extends StretchableTile{
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean reserveFor(Context context) {
|
||||||
|
Train newTrain = context.train();
|
||||||
|
Route route = context.route();
|
||||||
|
LOG.debug("{}.reserverFor({})",this,newTrain);
|
||||||
|
if (isNull(newTrain)) return false;
|
||||||
|
Train train = trains.first();
|
||||||
|
if (isSet(train) && train != newTrain) return false;
|
||||||
|
switch (status) {
|
||||||
|
case DISABLED:
|
||||||
|
return false;
|
||||||
|
case FREE:
|
||||||
|
status = Status.RESERVED;
|
||||||
|
Direction dir = trains.directionOf(train);
|
||||||
|
if (isSet(route) && this == route.endBlock()) dir = route.endDirection;
|
||||||
|
add(newTrain,dir);
|
||||||
|
plan.place(this);
|
||||||
|
break;
|
||||||
|
case OCCUPIED:
|
||||||
|
case LOCKED:
|
||||||
|
case RESERVED:
|
||||||
|
break; // do not downgrade
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public BlockContact register(BlockContact contact) {
|
public BlockContact register(BlockContact contact) {
|
||||||
internalContacts.add(contact);
|
internalContacts.add(contact);
|
||||||
return contact;
|
return contact;
|
||||||
@@ -356,13 +471,17 @@ public abstract class Block extends StretchableTile{
|
|||||||
public void removeChild(BaseClass child) {
|
public void removeChild(BaseClass child) {
|
||||||
super.removeChild(child);
|
super.removeChild(child);
|
||||||
internalContacts.remove(child);
|
internalContacts.remove(child);
|
||||||
if (parkedTrains.remove(child)) plan.place(this);
|
if (trains.remove(child)) plan.place(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeContact(BlockContact blockContact) {
|
public void removeContact(BlockContact blockContact) {
|
||||||
internalContacts.remove(blockContact);
|
internalContacts.remove(blockContact);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Route> routes(Direction direction) {
|
||||||
|
return routes().stream().filter(route -> route.startBlock() == Block.this).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
public abstract List<Connector> startPoints();
|
public abstract List<Connector> startPoints();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -370,10 +489,7 @@ public abstract class Block extends StretchableTile{
|
|||||||
if (isNull(replacements)) replacements = new HashMap<String, Object>();
|
if (isNull(replacements)) replacements = new HashMap<String, Object>();
|
||||||
replacements.put("%text%",name);
|
replacements.put("%text%",name);
|
||||||
Vector<String> trainNames = new Vector<String>();
|
Vector<String> trainNames = new Vector<String>();
|
||||||
if (isSet(train)) trainNames.add(train.directedName());
|
for (Train train: trains) trainNames.add(train.directedName(trains.directionOf(train)));
|
||||||
for (Train t:parkedTrains) {
|
|
||||||
if (isSet(t)) trainNames.add(t.directedName());
|
|
||||||
}
|
|
||||||
if (!trainNames.isEmpty())replacements.put("%text%",String.join(" | ", trainNames));
|
if (!trainNames.isEmpty())replacements.put("%text%",String.join(" | ", trainNames));
|
||||||
Tag tag = super.tag(replacements);
|
Tag tag = super.tag(replacements);
|
||||||
tag.clazz(tag.get("class")+" Block");
|
tag.clazz(tag.get("class")+" Block");
|
||||||
@@ -390,23 +506,45 @@ public abstract class Block extends StretchableTile{
|
|||||||
return name + " @ ("+x+","+y+")";
|
return name + " @ ("+x+","+y+")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Train train() {
|
||||||
|
return train(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Train train(boolean last) {
|
||||||
|
return last ? trains.last() : trains.first();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Fieldset trainList() {
|
||||||
|
Fieldset fieldset = new Fieldset(t("Trains"));
|
||||||
|
Tag list = new Tag("ul");
|
||||||
|
for (Train t : trains) {
|
||||||
|
if (isSet(t)) t.link("li", t).addTo(list);
|
||||||
|
}
|
||||||
|
list.addTo(fieldset);
|
||||||
|
return fieldset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Train> trains(){
|
||||||
|
return trains.list();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Tile update(HashMap<String, String> params) {
|
public Tile update(HashMap<String, String> params) {
|
||||||
if (params.containsKey(NAME)) name=params.get(NAME);
|
if (params.containsKey(NAME)) name=params.get(NAME);
|
||||||
if (params.containsKey(Train.class.getSimpleName())) {
|
if (params.containsKey(Train.class.getSimpleName())) {
|
||||||
Id trainId = Id.from(params,Train.class.getSimpleName());
|
Id trainId = Id.from(params,Train.class.getSimpleName());
|
||||||
if (trainId.equals(0)) {
|
if (trainId.equals(0)) { // remove first train
|
||||||
if (isSet(train)) {
|
dropFirstTrain();
|
||||||
train.dropTrace();
|
|
||||||
train.set(null);
|
|
||||||
}
|
|
||||||
train = null;
|
|
||||||
} else {
|
} else {
|
||||||
Train newTrain = Train.get(trainId);
|
Train train = Train.get(trainId);
|
||||||
if (isSet(newTrain) && newTrain != train) {
|
if (isSet(train) && train != trains.first()) {
|
||||||
newTrain.dropTrace();
|
dropFirstTrain();
|
||||||
if (connections(newTrain.direction()).isEmpty()) newTrain.heading(null);
|
train.dropTrace();
|
||||||
newTrain.set(this);
|
if (connections(train.direction()).isEmpty()) train.heading(null);
|
||||||
|
train.set(this);
|
||||||
|
trains.add(train,train.direction());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,7 @@ public class BlockContact extends Contact {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Train train() {
|
public Train train() {
|
||||||
train = ((Block)parent()).train();
|
return ((Block)parent()).train();
|
||||||
return train;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -42,9 +42,9 @@ public abstract class Bridge extends Tile {
|
|||||||
protected abstract Connector connector();
|
protected abstract Connector connector();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void free() {
|
public boolean free(Train train) {
|
||||||
if (isSet(counterpart) && counterpart.train != null) counterpart.free();
|
if (!super.free(train)) return false;
|
||||||
super.free();
|
return isSet(counterpart) ? counterpart.free(train) : true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -68,10 +68,10 @@ public abstract class Bridge extends Tile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean setState(Status newState, Train newTrain) {
|
public boolean setTrain(Train newTrain) {
|
||||||
if (train == newTrain && is(newState)) return true;
|
if (train() == newTrain) return true;
|
||||||
if (!super.setState(newState,newTrain)) return false;
|
if (!super.setTrain(newTrain)) return false;
|
||||||
return isNull(counterpart) ? true : counterpart.setState(newState,newTrain);
|
return isNull(counterpart) ? true : counterpart.setTrain(newTrain);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import de.srsoftware.web4rail.Application;
|
|||||||
import de.srsoftware.web4rail.BaseClass;
|
import de.srsoftware.web4rail.BaseClass;
|
||||||
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.moving.Train;
|
||||||
import de.srsoftware.web4rail.tags.Fieldset;
|
import de.srsoftware.web4rail.tags.Fieldset;
|
||||||
import de.srsoftware.web4rail.tags.Input;
|
import de.srsoftware.web4rail.tags.Input;
|
||||||
import de.srsoftware.web4rail.tags.Select;
|
import de.srsoftware.web4rail.tags.Select;
|
||||||
@@ -84,11 +85,8 @@ 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();
|
||||||
Context context = new Context(this);
|
Train train = train();
|
||||||
if (isSet(train())) {
|
Context context = isSet(train) ? train.contact(this) : new Context(this);
|
||||||
train.contact(this);
|
|
||||||
context.train(train);
|
|
||||||
}
|
|
||||||
actions.fire(context,"Contact("+addr+")");
|
actions.fire(context,"Contact("+addr+")");
|
||||||
|
|
||||||
for (Listener listener : listeners) listener.fired("Contact("+addr+")");
|
for (Listener listener : listeners) listener.fired("Contact("+addr+")");
|
||||||
|
|||||||
@@ -40,10 +40,7 @@ import de.srsoftware.web4rail.tags.Window;
|
|||||||
*/
|
*/
|
||||||
public abstract class Tile extends BaseClass implements Comparable<Tile> {
|
public abstract class Tile extends BaseClass implements Comparable<Tile> {
|
||||||
public enum Status {
|
public enum Status {
|
||||||
FREE("free"),
|
DISABLED("disabled"), FREE("free"), RESERVED("reserved"), LOCKED("locked"), OCCUPIED("occupied");
|
||||||
ALLOCATED("allocated"),
|
|
||||||
LOCKED("locked"),
|
|
||||||
OCCUPIED("occupied");
|
|
||||||
|
|
||||||
private String tx;
|
private String tx;
|
||||||
|
|
||||||
@@ -56,6 +53,7 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
|
|||||||
return tx;
|
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
|
||||||
|
|
||||||
@@ -66,13 +64,12 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
|
|||||||
private static final String X = "x";
|
private static final String X = "x";
|
||||||
private static final String Y = "y";
|
private static final String Y = "y";
|
||||||
|
|
||||||
private boolean disabled = false;
|
|
||||||
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;
|
||||||
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;
|
private Train train = null;
|
||||||
protected Train train = null;
|
protected Status status = Status.FREE;
|
||||||
public Integer x = null;
|
public Integer x = null;
|
||||||
public Integer y = null;
|
public Integer y = null;
|
||||||
|
|
||||||
@@ -80,38 +77,11 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
|
|||||||
this.routes.add(route);
|
this.routes.add(route);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean canNeEnteredBy(Train newTrain) {
|
|
||||||
LOG.debug("{}.canNeEnteredBy({})",this,newTrain);
|
|
||||||
if (disabled) {
|
|
||||||
LOG.debug("{} is disabled!",this);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNull(train)) {
|
|
||||||
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!
|
|
||||||
LOG.debug("already reserved by {} → true",train);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSet(newTrain) && newTrain.isShunting()) {
|
|
||||||
LOG.debug("occupied by {}. Allowed for shunting {}",train,newTrain);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (!is(Status.FREE)) classes.add(status.toString());
|
if (!is(Status.FREE)) classes.add(status.toString());
|
||||||
if (disabled) classes.add(DISABLED);
|
|
||||||
return classes;
|
return classes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,10 +105,12 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
|
|||||||
return new HashMap<>();
|
return new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void free() {
|
public boolean free(Train t) {
|
||||||
|
if (t != train) return false;
|
||||||
train = null;
|
train = null;
|
||||||
status = Status.FREE;
|
status = Status.FREE;
|
||||||
plan.place(this);
|
plan.place(this);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int height() {
|
public int height() {
|
||||||
@@ -153,7 +125,8 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
|
|||||||
return new Id(x + "-" + y);
|
return new Id(x + "-" + y);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void inflate(String clazz, JSONObject json, Plan plan) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, ClassNotFoundException, IOException {
|
private static void inflate(String clazz, JSONObject json,
|
||||||
|
Plan plan) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, ClassNotFoundException, IOException {
|
||||||
clazz = Tile.class.getName().replace(".Tile", "." + clazz);
|
clazz = Tile.class.getName().replace(".Tile", "." + clazz);
|
||||||
Tile tile = (Tile) Tile.class.getClassLoader().loadClass(clazz).getDeclaredConstructor().newInstance();
|
Tile tile = (Tile) Tile.class.getClassLoader().loadClass(clazz).getDeclaredConstructor().newInstance();
|
||||||
tile.load(json).register();
|
tile.load(json).register();
|
||||||
@@ -168,12 +141,40 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isFreeFor(Context newTrain) {
|
||||||
|
LOG.debug("{}.isFreeFor({})", this, newTrain);
|
||||||
|
if (is(Status.DISABLED)) {
|
||||||
|
LOG.debug("{} is disabled!", this);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNull(train)) {
|
||||||
|
LOG.debug("→ free");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newTrain.train() == 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!
|
||||||
|
LOG.debug("already reserved by {} → true", train);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSet(newTrain.train()) && newTrain.train().isShunting()) {
|
||||||
|
LOG.debug("occupied by {}. Allowed for shunting {}", train, newTrain.train());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("occupied by {} → false", train);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
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(oneWay)) json.put(ONEW_WAY, oneWay);
|
if (isSet(oneWay)) json.put(ONEW_WAY, oneWay);
|
||||||
if (disabled) json.put(DISABLED, true);
|
if (is(Status.DISABLED)) json.put(DISABLED, true);
|
||||||
if (isSet(train)) json.put(REALM_TRAIN, train.id());
|
if (isSet(train)) json.put(REALM_TRAIN, train.id());
|
||||||
json.put(LENGTH, length);
|
json.put(LENGTH, length);
|
||||||
return json;
|
return json;
|
||||||
@@ -189,9 +190,10 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If arguments are given, the first is taken as content, the second as tag type.
|
* If arguments are given, the first is taken as content, the second as tag
|
||||||
* If no content is supplied, id() is set as content.
|
* type. If no content is supplied, id() is set as content. If no type is
|
||||||
* If no type is supplied, "span" is preset.
|
* supplied, "span" is preset.
|
||||||
|
*
|
||||||
* @param args
|
* @param args
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@@ -201,7 +203,6 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
|
|||||||
return super.link(type, (Object) tx, Map.of(ACTION, ACTION_CLICK));
|
return super.link(type, (Object) tx, Map.of(ACTION, ACTION_CLICK));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void load(Object object, Plan plan) {
|
public static void load(Object object, Plan plan) {
|
||||||
if (object instanceof JSONObject) {
|
if (object instanceof JSONObject) {
|
||||||
JSONObject json = (JSONObject) object;
|
JSONObject json = (JSONObject) object;
|
||||||
@@ -211,7 +212,9 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Tile.inflate(clazz, json, plan);
|
Tile.inflate(clazz, json, plan);
|
||||||
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException | ClassNotFoundException | IOException e) {
|
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
|
||||||
|
| InvocationTargetException | NoSuchMethodException | SecurityException | ClassNotFoundException
|
||||||
|
| IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,7 +228,7 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
|
|||||||
x = pos.getInt(X);
|
x = pos.getInt(X);
|
||||||
y = pos.getInt(Y);
|
y = pos.getInt(Y);
|
||||||
}
|
}
|
||||||
if (json.has(DISABLED)) disabled = json.getBoolean(DISABLED);
|
if (json.has(DISABLED) && json.getBoolean(DISABLED)) status = Status.DISABLED;
|
||||||
if (json.has(LENGTH)) length = json.getInt(LENGTH);
|
if (json.has(LENGTH)) length = json.getInt(LENGTH);
|
||||||
if (json.has(ONEW_WAY)) oneWay = Direction.valueOf(json.getString(ONEW_WAY));
|
if (json.has(ONEW_WAY)) oneWay = Direction.valueOf(json.getString(ONEW_WAY));
|
||||||
return this;
|
return this;
|
||||||
@@ -259,7 +262,8 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Window properties(List<Fieldset> preForm, FormInput formInputs, List<Fieldset> postForm,String...errors) {
|
protected Window properties(List<Fieldset> preForm, FormInput formInputs, List<Fieldset> postForm,
|
||||||
|
String... errors) {
|
||||||
Fieldset fieldset = null;
|
Fieldset fieldset = null;
|
||||||
|
|
||||||
if (isSet(train)) {
|
if (isSet(train)) {
|
||||||
@@ -280,9 +284,10 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
|
|||||||
if (isSet(fieldset)) preForm.add(fieldset);
|
if (isSet(fieldset)) preForm.add(fieldset);
|
||||||
|
|
||||||
if (isTrack) {
|
if (isTrack) {
|
||||||
formInputs.add(t("Length"),new Input(LENGTH,length).numeric().addTo(new Tag("span")).content(NBSP+lengthUnit));
|
formInputs.add(t("Length"),
|
||||||
Checkbox checkbox = new Checkbox(DISABLED, t("disabled"),disabled);
|
new Input(LENGTH, length).numeric().addTo(new Tag("span")).content(NBSP + lengthUnit));
|
||||||
if (disabled) checkbox.clazz("disabled");
|
Checkbox checkbox = new Checkbox(DISABLED, t("disabled"), is(Status.DISABLED));
|
||||||
|
if (is(Status.DISABLED)) checkbox.clazz("disabled");
|
||||||
formInputs.add(t("State"), checkbox);
|
formInputs.add(t("State"), checkbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,14 +301,15 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
|
|||||||
formInputs.add(t("One way"), div);
|
formInputs.add(t("One way"), div);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (!routes.isEmpty()) {
|
if (!routes.isEmpty()) {
|
||||||
fieldset = new Fieldset(t("Routes")).id("props-routes");
|
fieldset = new Fieldset(t("Routes")).id("props-routes");
|
||||||
Tag routeList = new Tag("ol");
|
Tag routeList = new Tag("ol");
|
||||||
boolean empty = true;
|
boolean empty = true;
|
||||||
for (Route route : routes) {
|
for (Route route : routes) {
|
||||||
if (route.isDisabled()) continue;
|
if (route.isDisabled()) continue;
|
||||||
Tag li = route.link("span", route.name()+(route.isDisabled()?" ["+t("disabled")+"]" : "")+NBSP).addTo(new Tag("li").clazz("link"));
|
Tag li = route
|
||||||
|
.link("span", route.name() + (route.isDisabled() ? " [" + t("disabled") + "]" : "") + NBSP)
|
||||||
|
.addTo(new Tag("li").clazz("link"));
|
||||||
route.button(t("delete route"), Map.of(ACTION, ACTION_DROP)).addTo(li);
|
route.button(t("delete route"), Map.of(ACTION, ACTION_DROP)).addTo(li);
|
||||||
button(t("simplify name"), Map.of(ACTION, ACTION_AUTO, ROUTE, route.id().toString())).addTo(li);
|
button(t("simplify name"), Map.of(ACTION, ACTION_AUTO, ROUTE, route.id().toString())).addTo(li);
|
||||||
li.addTo(routeList);
|
li.addTo(routeList);
|
||||||
@@ -319,7 +325,9 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
|
|||||||
empty = true;
|
empty = true;
|
||||||
for (Route route : routes) {
|
for (Route route : routes) {
|
||||||
if (!route.isDisabled()) continue;
|
if (!route.isDisabled()) continue;
|
||||||
Tag li = route.link("span", route.name()+(route.isDisabled()?" ["+t("disabled")+"]" : "")+NBSP).addTo(new Tag("li").clazz("link"));
|
Tag li = route
|
||||||
|
.link("span", route.name() + (route.isDisabled() ? " [" + t("disabled") + "]" : "") + NBSP)
|
||||||
|
.addTo(new Tag("li").clazz("link"));
|
||||||
route.button(t("delete route"), Map.of(ACTION, ACTION_DROP)).addTo(li);
|
route.button(t("delete route"), Map.of(ACTION, ACTION_DROP)).addTo(li);
|
||||||
button(t("simplify name"), Map.of(ACTION, ACTION_AUTO, ROUTE, route.id().toString())).addTo(li);
|
button(t("simplify name"), Map.of(ACTION, ACTION_AUTO, ROUTE, route.id().toString())).addTo(li);
|
||||||
li.addTo(routeList);
|
li.addTo(routeList);
|
||||||
@@ -364,13 +372,22 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
|
|||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean setState(Status newState,Train newTrain) {
|
public boolean setTrain(Train newTrain) {
|
||||||
if (isNull(newTrain)) return false;
|
if (isNull(newTrain)) return false;
|
||||||
if (isSet(train) && newTrain != train) return false; // already locked by other train
|
if (isSet(train) && newTrain != train) return false;
|
||||||
if (is(Status.OCCUPIED,newState)) return true; // do not downgrade occupied tiles, accept current state
|
switch (status) { // bisheriger Status
|
||||||
|
case DISABLED:
|
||||||
|
return false;
|
||||||
|
case FREE:
|
||||||
|
case RESERVED:
|
||||||
|
case LOCKED:
|
||||||
train = newTrain;
|
train = newTrain;
|
||||||
status = newState;
|
status = Status.OCCUPIED;
|
||||||
plan.place(this);
|
plan.place(this);
|
||||||
|
break;
|
||||||
|
case OCCUPIED:
|
||||||
|
break;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,11 +398,8 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
|
|||||||
replacements.put("%width%", width);
|
replacements.put("%width%", width);
|
||||||
replacements.put("%height%", height);
|
replacements.put("%height%", height);
|
||||||
String style = "";
|
String style = "";
|
||||||
Tag svg = new Tag("svg")
|
Tag svg = new Tag("svg").id(isSet(x) && isSet(y) ? id().toString() : getClass().getSimpleName())
|
||||||
.id(isSet(x) && isSet(y) ? id().toString() : getClass().getSimpleName())
|
.clazz(classes()).size(100, 100).attr("name", getClass().getSimpleName())
|
||||||
.clazz(classes())
|
|
||||||
.size(100,100)
|
|
||||||
.attr("name", getClass().getSimpleName())
|
|
||||||
.attr("viewbox", "0 0 " + width + " " + height);
|
.attr("viewbox", "0 0 " + width + " " + height);
|
||||||
if (isSet(x)) style = "left: " + (30 * x) + "px; top: " + (30 * y) + "px;";
|
if (isSet(x)) style = "left: " + (30 * x) + "px; top: " + (30 * y) + "px;";
|
||||||
if (width() > 1) style += " width: " + (30 * width()) + "px;";
|
if (width() > 1) style += " width: " + (30 * width()) + "px;";
|
||||||
@@ -427,10 +441,7 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
|
|||||||
if (isSet(title)) new Tag("title").content(title()).addTo(svg);
|
if (isSet(title)) new Tag("title").content(title()).addTo(svg);
|
||||||
} else {
|
} else {
|
||||||
new Tag("title").content(t("No display defined for this tile ({})", getClass().getSimpleName())).addTo(svg);
|
new Tag("title").content(t("No display defined for this tile ({})", getClass().getSimpleName())).addTo(svg);
|
||||||
new Tag("text")
|
new Tag("text").pos(35, 70).content("?").addTo(svg);
|
||||||
.pos(35,70)
|
|
||||||
.content("?")
|
|
||||||
.addTo(svg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return svg;
|
return svg;
|
||||||
@@ -467,8 +478,54 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
|
|||||||
plan.place(this);
|
plan.place(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setEnabled(boolean newState) {
|
public boolean lockFor(Context context) {
|
||||||
disabled = !newState;
|
Train newTrain = context.train();
|
||||||
|
LOG.debug("{}.lockFor({})",this,newTrain);
|
||||||
|
if (isNull(newTrain)) return false;
|
||||||
|
if (isSet(train) && train != newTrain) return false;
|
||||||
|
switch (status) {
|
||||||
|
case DISABLED:
|
||||||
|
return false;
|
||||||
|
case FREE:
|
||||||
|
case RESERVED:
|
||||||
|
status = Status.LOCKED;
|
||||||
|
plan.place(this);
|
||||||
|
break;
|
||||||
|
case OCCUPIED:
|
||||||
|
case LOCKED:
|
||||||
|
break; // do not downgrade
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean reserveFor(Context context) {
|
||||||
|
Train newTrain = context.train();
|
||||||
|
LOG.debug("{}.reserverFor({})",this,newTrain);
|
||||||
|
if (isNull(newTrain)) return false;
|
||||||
|
if (isSet(train) && train != newTrain) return false;
|
||||||
|
switch (status) {
|
||||||
|
case DISABLED:
|
||||||
|
return false;
|
||||||
|
case FREE:
|
||||||
|
status = Status.RESERVED;
|
||||||
|
train = newTrain;
|
||||||
|
plan.place(this);
|
||||||
|
break;
|
||||||
|
case OCCUPIED:
|
||||||
|
case LOCKED:
|
||||||
|
case RESERVED:
|
||||||
|
break; // do not downgrade
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
if (!enabled) {
|
||||||
|
status = Status.DISABLED;
|
||||||
|
} else if (is(Status.DISABLED)) { // Status nur ändern, wenn er bisher DISABLED war
|
||||||
|
status = isNull(train) ? Status.FREE : Status.OCCUPIED;
|
||||||
|
}
|
||||||
plan.place(this);
|
plan.place(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,7 +539,7 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
|
|||||||
oneWay = null;
|
oneWay = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
disabled = "on".equals(params.get(DISABLED));
|
if ("on".equals(params.get(DISABLED))) status = Status.DISABLED;
|
||||||
String len = params.get(LENGTH);
|
String len = params.get(LENGTH);
|
||||||
if (isSet(len)) length(Integer.parseInt(len));
|
if (isSet(len)) length(Integer.parseInt(len));
|
||||||
super.update(params);
|
super.update(params);
|
||||||
@@ -493,6 +550,4 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
|
|||||||
public int width() {
|
public int width() {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ public abstract class Turnout extends Tile implements Device{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Reply state(State newState) {
|
public Reply state(State newState) {
|
||||||
if (is(Status.LOCKED,Status.OCCUPIED) && 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,6 +4,7 @@ import java.io.IOException;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.srsoftware.web4rail.moving.Train;
|
||||||
import de.srsoftware.web4rail.tags.Fieldset;
|
import de.srsoftware.web4rail.tags.Fieldset;
|
||||||
import de.srsoftware.web4rail.tags.Input;
|
import de.srsoftware.web4rail.tags.Input;
|
||||||
import de.srsoftware.web4rail.tags.Window;
|
import de.srsoftware.web4rail.tags.Window;
|
||||||
@@ -16,6 +17,7 @@ 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) {
|
||||||
|
Train train = train();
|
||||||
if (isSet(train)) {
|
if (isSet(train)) {
|
||||||
plan.stream(t("{} is locked by {}!",this,train));
|
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);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.io.IOException;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.srsoftware.web4rail.moving.Train;
|
||||||
import de.srsoftware.web4rail.tags.Fieldset;
|
import de.srsoftware.web4rail.tags.Fieldset;
|
||||||
import de.srsoftware.web4rail.tags.Input;
|
import de.srsoftware.web4rail.tags.Input;
|
||||||
import de.srsoftware.web4rail.tags.Window;
|
import de.srsoftware.web4rail.tags.Window;
|
||||||
@@ -16,6 +17,7 @@ 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) {
|
||||||
|
Train train = train();
|
||||||
if (isSet(train)) {
|
if (isSet(train)) {
|
||||||
plan.stream(t("{} is locked by {}!",this,train));
|
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);
|
||||||
|
|||||||
Reference in New Issue
Block a user