diff --git a/pom.xml b/pom.xml index b337397..019ecca 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 de.srsoftware web4rail - 1.3.52 + 1.3.53 Web4Rail jar Java Model Railway Control diff --git a/resources/css/style.css b/resources/css/style.css index 62fc459..60088db 100644 --- a/resources/css/style.css +++ b/resources/css/style.css @@ -77,14 +77,20 @@ svg.Relay rect{ fill: white; } +svg.allocated polygon, +svg.allocated rect:not(.sig_a):not(.sig_b){ + fill: yellow; +} + svg.locked polygon, svg.locked rect:not(.sig_a):not(.sig_b){ - fill:lime; + fill: lime; } +.occupied .block, svg.occupied polygon, svg.occupied rect:not(.sig_a):not(.sig_b){ - fill:yellow; + fill: orange; } svg text{ @@ -232,10 +238,6 @@ svg.straight .right{ fill: #ddd !important; } -.occupied .block{ - fill: yellow; -} - .active circle{ fill: #f57900; } diff --git a/resources/logback.xml b/resources/logback.xml index 3013d46..bf19776 100644 --- a/resources/logback.xml +++ b/resources/logback.xml @@ -29,7 +29,7 @@ - + diff --git a/src/main/java/de/srsoftware/web4rail/Route.java b/src/main/java/de/srsoftware/web4rail/Route.java index 293a355..18a62f1 100644 --- a/src/main/java/de/srsoftware/web4rail/Route.java +++ b/src/main/java/de/srsoftware/web4rail/Route.java @@ -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 names = new HashMap(); // maps id to name. needed to keep names during plan.analyze() -// private BrakeProcessor brakeProcessor = null; private HashMap brakeTimes = new HashMap(); private ConditionList conditions; private Vector 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()); 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 ignoredPath = new HashSet(); - 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 multiply(int size) { Vector routes = new Vector(); for (int i=0; i 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; } } diff --git a/src/main/java/de/srsoftware/web4rail/actions/ActionList.java b/src/main/java/de/srsoftware/web4rail/actions/ActionList.java index dc7b17e..27ecfeb 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/ActionList.java +++ b/src/main/java/de/srsoftware/web4rail/actions/ActionList.java @@ -88,7 +88,10 @@ public class ActionList extends Action implements Iterable{ 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; } diff --git a/src/main/java/de/srsoftware/web4rail/conditions/BlockFree.java b/src/main/java/de/srsoftware/web4rail/conditions/BlockFree.java index f37b042..d46794b 100644 --- a/src/main/java/de/srsoftware/web4rail/conditions/BlockFree.java +++ b/src/main/java/de/srsoftware/web4rail/conditions/BlockFree.java @@ -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 diff --git a/src/main/java/de/srsoftware/web4rail/moving/Train.java b/src/main/java/de/srsoftware/web4rail/moving/Train.java index d957fd8..ece7fde 100644 --- a/src/main/java/de/srsoftware/web4rail/moving/Train.java +++ b/src/main/java/de/srsoftware/web4rail/moving/Train.java @@ -38,12 +38,23 @@ import de.srsoftware.web4rail.tags.Table; import de.srsoftware.web4rail.tags.Window; import de.srsoftware.web4rail.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 { + + 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 { private static final String SHUNTING = "shunting"; private boolean shunting = false; + private HashSet listeners = new HashSet(); + public static Object action(HashMap 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 { 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 params) { LOG.debug("addCar({})",params); String carId = params.get(CAR_ID); @@ -161,12 +182,8 @@ public class Train extends BaseClass implements Comparable { 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 { 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 { } 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 { 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 { 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 { 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 { 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 { public void start() { - Context context = new Context(this).block(currentBlock).direction(direction); - new PathFinder(context) { + new PathFinder(this,currentBlock,direction) { @Override - public void found(Route r) { - // TODO Auto-generated method stub - LOG.debug("Route {} prepared for {}",r,Train.this); + public void aborted() { + LOG.debug("Aborted"); } @Override - public void locked(Route r) { + public void found(Route newRoute) { // TODO Auto-generated method stub - LOG.debug("Route {} locked for {}",r,Train.this); + LOG.debug("Found route {} for {}",newRoute,Train.this); + } + + @Override + public void locked(Route newRoute) { + // TODO Auto-generated method stub + LOG.debug("Locked route {} for {}",newRoute,Train.this); } @Override - public void prepared(Route r) { - LOG.debug("Route {} prepared for {}",r,Train.this); - setRoute(r).start(Train.this); + public void prepared(Route newRoute) { + LOG.debug("Prepared route {} for {}",newRoute,Train.this); + newRoute.start(Train.this); } - }; + + + }.start(); } public static void startAll() { @@ -869,7 +903,8 @@ public class Train extends BaseClass implements Comparable { } 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 { */ public Train turn() { LOG.debug("{}.turn()",this); + setSpeed(0); for (Car car : cars) car.turn(); Collections.reverse(cars); return reverse(); diff --git a/src/main/java/de/srsoftware/web4rail/threads/ControlUnit.java b/src/main/java/de/srsoftware/web4rail/threads/ControlUnit.java index c93cb19..97e5054 100644 --- a/src/main/java/de/srsoftware/web4rail/threads/ControlUnit.java +++ b/src/main/java/de/srsoftware/web4rail/threads/ControlUnit.java @@ -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+")")); diff --git a/src/main/java/de/srsoftware/web4rail/threads/PathFinder.java b/src/main/java/de/srsoftware/web4rail/threads/PathFinder.java index 5c27806..aef3ded 100644 --- a/src/main/java/de/srsoftware/web4rail/threads/PathFinder.java +++ b/src/main/java/de/srsoftware/web4rail/threads/PathFinder.java @@ -18,71 +18,76 @@ import de.srsoftware.web4rail.tiles.Block; /** * @author Stephan Richter, SRSoftware 2020-2021 */ -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> availableRoutes(Context context,HashSet visitedRoutes){ + private static TreeMap> availableRoutes(Train train, Block start, Direction startDir, HashSet visitedRoutes){ String inset = ""; for (int i=0; i> availableRoutes = new TreeMap>(); + 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>(); - 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> availableRoutes = new TreeMap>(); + + 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> forwardRoutes = availableRoutes(forwardContext,visitedRoutes); + TreeMap> 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> 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> 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> availableRoutes = availableRoutes(context,new HashSet()); + HashSet visitedRoutes = new HashSet(); + TreeMap> availableRoutes = availableRoutes(train, startBlock, direction,visitedRoutes); while (!availableRoutes.isEmpty()) { LOG.debug("availableRoutes: {}",availableRoutes); Entry> entry = availableRoutes.lastEntry(); List 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(); + } } diff --git a/src/main/java/de/srsoftware/web4rail/tiles/Block.java b/src/main/java/de/srsoftware/web4rail/tiles/Block.java index 5bf4d4e..166c03e 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/Block.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/Block.java @@ -146,10 +146,17 @@ public abstract class Block extends StretchableTile{ return t("Trigger contact to learn new contact"); } + @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 classes() { HashSet 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) { diff --git a/src/main/java/de/srsoftware/web4rail/tiles/BlockContact.java b/src/main/java/de/srsoftware/web4rail/tiles/BlockContact.java index fcd8031..8963560 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/BlockContact.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/BlockContact.java @@ -27,11 +27,6 @@ public class BlockContact extends Contact { return new Id(block.name+":"+block.indexOf(this)); } - @Override - public Route route() { - return ((Block)parent()).route(); - } - @Override public Tag tag(Map replacements) throws IOException { return ((Block)parent()).tag(replacements); diff --git a/src/main/java/de/srsoftware/web4rail/tiles/Bridge.java b/src/main/java/de/srsoftware/web4rail/tiles/Bridge.java index b5e3c8b..260f92f 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/Bridge.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/Bridge.java @@ -9,7 +9,6 @@ import org.json.JSONObject; import de.srsoftware.tools.Tag; import de.srsoftware.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; - } } diff --git a/src/main/java/de/srsoftware/web4rail/tiles/Contact.java b/src/main/java/de/srsoftware/web4rail/tiles/Contact.java index 889302e..2e60e05 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/Contact.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/Contact.java @@ -16,7 +16,6 @@ import org.slf4j.LoggerFactory; import de.srsoftware.tools.Tag; import de.srsoftware.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+")"); diff --git a/src/main/java/de/srsoftware/web4rail/tiles/Tile.java b/src/main/java/de/srsoftware/web4rail/tiles/Tile.java index def0924..9df37f4 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/Tile.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/Tile.java @@ -40,12 +40,27 @@ import de.srsoftware.web4rail.threads.PathFinder; * */ public abstract class Tile extends BaseClass implements Comparable{ + 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{ private boolean isTrack = true; private int length = DEFAUT_LENGTH; protected Direction oneWay = null; - protected Route route = null; private TreeSet 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{ 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 classes(){ HashSet classes = new HashSet(); 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{ 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{ if (tile instanceof TileWithShadow) ((TileWithShadow)tile).placeShadows(); plan.place(tile); } - - public boolean isFreeFor(Context context) { - PathFinder.LOG.debug("{}.isFreeFor({})",this,context); - if (disabled) { - PathFinder.LOG.debug("{} is disabled!",this); - return false; - } - if (isNull(context)) { - if (isSet(train)) { - PathFinder.LOG.debug("{} is occupied by {}",this,train); - return false; - } - if (isSet(route)) { - PathFinder.LOG.debug("{} is occupied by {}",this,route); - return false; - } - } - if (isSet(train)) { - Train contextTrain = context.train(); - boolean free = train == contextTrain; // during train.reserveNext, we may encounter, parts, that are already reserved by the respective train, but having another route. do not compare routes in that case! - if (free) { - PathFinder.LOG.debug("already reserved by {} → true",train); - } else { - if (isSet(contextTrain) && contextTrain.isShunting()) { - PathFinder.LOG.debug("occupied by {}. Allowed for shunting {}",train,contextTrain); - free = true; - } else PathFinder.LOG.debug("occupied by {} → false",train); - } - return free; - } - - // if we get here, the tile is not occupied by a train, but reserved by a route, yet. thus, the tile is not available for another route - if (isSet(route) && route != context.route()) { - PathFinder.LOG.debug("reserved by other route: {}",route); - if (isSet(route.train())) { - if (route.train() == context.train()) { - PathFinder.LOG.debug("that route is used by {}, which is also requesting this tile → true",route.train()); - return true; - } - } - PathFinder.LOG.debug("{}.route.train = {} → false",this,route.train()); - return false; + + public boolean is(Status...states) { + for (Status s: states) { + if (status == s) return true; } - PathFinder.LOG.debug("free"); - return true; + return false; } - + public JSONObject json() { JSONObject json = super.json(); json.put(TYPE, getClass().getSimpleName()); if (isSet(x) && isSet(y)) json.put(POS, new JSONObject(Map.of(X,x,Y,y))); - if (isSet(route)) json.put(ROUTE, route.id()); if (isSet(oneWay)) json.put(ONEW_WAY, oneWay); if (disabled) json.put(DISABLED, true); if (isSet(train)) json.put(REALM_TRAIN, train.id()); @@ -242,7 +247,7 @@ public abstract class Tile extends BaseClass implements Comparable{ 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{ @Override protected Window properties(List
preForm, FormInput formInputs, List
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{ } return line; } - - public Route route() { - return route; - } public TreeSet routes() { return routes; @@ -370,24 +364,16 @@ public abstract class Tile extends BaseClass implements Comparable{ 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 replacements) throws IOException { int width = 100*width(); int height = 100*height(); @@ -477,7 +463,6 @@ public abstract class Tile extends BaseClass implements Comparable{ 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{ 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 params) { LOG.debug("{}.update({})",getClass().getSimpleName(),params); String oneWayDir = params.get("oneway"); diff --git a/src/main/java/de/srsoftware/web4rail/tiles/Turnout.java b/src/main/java/de/srsoftware/web4rail/tiles/Turnout.java index ee4945c..0b841e6 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/Turnout.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/Turnout.java @@ -159,7 +159,7 @@ public abstract class Turnout extends Tile implements Device{ } public Reply state(State newState) { - 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); diff --git a/src/main/java/de/srsoftware/web4rail/tiles/TurnoutL.java b/src/main/java/de/srsoftware/web4rail/tiles/TurnoutL.java index 19287dd..e82fa4b 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/TurnoutL.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/TurnoutL.java @@ -16,8 +16,8 @@ public abstract class TurnoutL extends Turnout { public Object click(boolean shift) throws IOException { 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; diff --git a/src/main/java/de/srsoftware/web4rail/tiles/TurnoutR.java b/src/main/java/de/srsoftware/web4rail/tiles/TurnoutR.java index 782c4b3..1c5dfde 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/TurnoutR.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/TurnoutR.java @@ -16,8 +16,8 @@ public abstract class TurnoutR extends Turnout { public Object click(boolean shift) throws IOException { 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;