diff --git a/pom.xml b/pom.xml index c6655c1..94c5a7b 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 de.srsoftware web4rail - 1.3.51 + 1.4.1 Web4Rail jar Java Model Railway Control diff --git a/resources/css/style.css b/resources/css/style.css index 62fc459..2d980de 100644 --- a/resources/css/style.css +++ b/resources/css/style.css @@ -77,14 +77,31 @@ svg.Relay rect{ fill: white; } +svg.reserved polygon, +svg.reserved rect{ + fill: yellow; +} + svg.locked polygon, -svg.locked rect:not(.sig_a):not(.sig_b){ - fill:lime; +svg.locked rect{ + fill: lime; } svg.occupied polygon, -svg.occupied rect:not(.sig_a):not(.sig_b){ - fill:yellow; +svg.occupied rect{ + fill: orange; +} + +svg.preview circle, +svg.preview line, +svg.preview polygon, +svg.preview rect{ + fill:cyan; +} + +svg rect.sig_a, +svg rect.sig_b{ + fill: inherit; } svg text{ @@ -232,10 +249,6 @@ svg.straight .right{ fill: #ddd !important; } -.occupied .block{ - fill: yellow; -} - .active circle{ fill: #f57900; } @@ -254,7 +267,14 @@ fieldset{ border-radius: 5px; } .error{ - background: red; + background: orange; + margin: 30px 10px 5px; + padding: 0; + border-radius: 10px; +} + +.error p{ + padding: 5px; } h4,ul{ @@ -356,18 +376,6 @@ textarea.json { margin-left: 5%; } -svg.preview circle, -svg.preview line, -svg.preview polygon, -svg.preview rect{ - fill:cyan; -} - -svg.preview rect.sig_a, -svg.preview rect.sig_b{ - fill: inherit; -} - svg.Block text{ fill: black; } @@ -422,4 +430,4 @@ svg.Block text{ } .Switch.on rect.enabled{ fill: forestgreen; -} \ No newline at end of file +} diff --git a/resources/js/plan.js b/resources/js/plan.js index dfb4ef6..b4a2737 100644 --- a/resources/js/plan.js +++ b/resources/js/plan.js @@ -37,7 +37,7 @@ function arrangeTabs(){ var tabs = $('
',{'class':'tabs'}); var winId = $('.window').attr('id')+"-"; - tabs.insertAfter($('.swapbtn')); + tabs.insertAfter($('.error')); var target = null; var index = null; $('.window > fieldset > legend').each(function(){ @@ -48,7 +48,6 @@ function arrangeTabs(){ index = i; target = this; } - //if (fs.id == lastTab) target = this; $(this).appendTo(tabs).click(fs.id,clickLegend); if (id > 0) { $(fs).hide(); @@ -71,6 +70,7 @@ function clickLegend(ev){ $(ev.target).addClass('front'); $('.window > fieldset').hide(); $('#'+lastTab).show(); + $('#'+lastTab+" input:not([type=hidden])").first().focus().select(); if (!('no-update' in ev)) remember(lastTab); } diff --git a/resources/logback.xml b/resources/logback.xml index 3013d46..54dfeb3 100644 --- a/resources/logback.xml +++ b/resources/logback.xml @@ -4,7 +4,7 @@ - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{5}: %msg%n @@ -35,7 +35,9 @@ - - + + + + diff --git a/resources/translations/Application.de.translation b/resources/translations/Application.de.translation index b140948..15e63fc 100644 --- a/resources/translations/Application.de.translation +++ b/resources/translations/Application.de.translation @@ -1,4 +1,5 @@ abort : abbrechen +Aborting route allocation... : Routen-Reservierung wird abgebrochen... Accessory : Zubehör Action : Aktion Actions : Aktionen diff --git a/src/main/java/de/srsoftware/web4rail/BaseClass.java b/src/main/java/de/srsoftware/web4rail/BaseClass.java index 56ab24b..887e4aa 100644 --- a/src/main/java/de/srsoftware/web4rail/BaseClass.java +++ b/src/main/java/de/srsoftware/web4rail/BaseClass.java @@ -4,9 +4,11 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.AbstractMap; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Random; @@ -39,7 +41,7 @@ import de.srsoftware.web4rail.tiles.Tile; * @author Stephan Richter, SRSoftware 2020…2021 */ public abstract class BaseClass implements Constants{ - protected static Plan plan; // the track layout in use + public static Plan plan; // the track layout in use public static final Random random = new Random(); public static String speedUnit = DEFAULT_SPEED_UNIT; public static String lengthUnit = DEFAULT_LENGTH_UNIT; @@ -67,6 +69,8 @@ public abstract class BaseClass implements Constants{ private Car car; private Contact contact; private Direction direction; + private Integer waitTime; + List invalidationListeners = new LinkedList<>(); public Context(BaseClass object) { setMain(object); @@ -101,6 +105,7 @@ public abstract class BaseClass implements Constants{ route = null; tile = null; train = null; + waitTime = null; } public Context clone() { @@ -114,6 +119,7 @@ public abstract class BaseClass implements Constants{ clone.route = route; clone.tile = tile; clone.train = train; + clone.waitTime = waitTime; return clone; } @@ -141,10 +147,20 @@ public abstract class BaseClass implements Constants{ return this; } + public void invalidate() { + setMain(null); + invalidationListeners.forEach(EventListener::fire); + } + public boolean invalidated() { return isNull(main); } + public void onInvalidate(EventListener listener) { + invalidationListeners.add(listener); + } + + public Context setMain(BaseClass object) { main = object; LOG.debug("{}.setMain({})",this,object); @@ -182,6 +198,7 @@ public abstract class BaseClass implements Constants{ if (isSet(block)) sb.append(", "+t("Block: {}",block)); if (isSet(route)) sb.append(", "+t("Route: {}",route)); if (isSet(contact)) sb.append(", "+t("Contact: {}",contact)); + if (isSet(waitTime)) sb.append(", "+t("Wait time: {} ms",waitTime)); sb.append(")"); return sb.toString(); } @@ -195,6 +212,14 @@ public abstract class BaseClass implements Constants{ train = newTrain; return this; } + + public Integer waitTime() { + return waitTime; + } + + public void waitTime(int ms) { + waitTime = ms; + } } public class FormInput extends ArrayList>{ @@ -423,13 +448,21 @@ public abstract class BaseClass implements Constants{ return (T) this; } - public Window properties() { - return properties(new ArrayList<>(), new FormInput(), new ArrayList<>()); + public Window properties(String...error) { + return properties(new ArrayList<>(), new FormInput(), new ArrayList<>(),error); } - protected Window properties(List
preForm,FormInput formInputs,List
postForm) { + protected Window properties(List
preForm,FormInput formInputs,List
postForm, String...errorMessages) { Window win = new Window(getClass().getSimpleName()+"-properties", t("Properties of {}",this.title())); + + Tag errorDiv = new Tag("div").clazz("error").content(""); + if (errorMessages != null && errorMessages.length > 0) { + for (String errorMessage : errorMessages) { + if (isSet(errorMessage)) new Tag("p").content(errorMessage).addTo(errorDiv); + } + } + errorDiv.addTo(win); preForm.forEach(fieldset -> fieldset.addTo(win)); @@ -509,9 +542,21 @@ public abstract class BaseClass implements Constants{ registry = new HashMap(); customFieldNames = new HashMap, Set>(); } + + public static > L reverse(L list){ + Collections.reverse(list); + return list; + } + public void sleep(long ms) { + try { + Thread.sleep(ms); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } - protected static String t(String txt, Object...fills) { + public static String t(String txt, Object...fills) { if (isSet(fills)) for (int i=0; i timeout) timeout(); - Thread.sleep(10); - } catch (InterruptedException e) { - LOG.warn("wait() interrupted!",e); + sleep(10); } return reply; } diff --git a/src/main/java/de/srsoftware/web4rail/Constants.java b/src/main/java/de/srsoftware/web4rail/Constants.java index 23d8501..4df1d7f 100644 --- a/src/main/java/de/srsoftware/web4rail/Constants.java +++ b/src/main/java/de/srsoftware/web4rail/Constants.java @@ -58,6 +58,7 @@ public interface Constants { public static final String DEFAULT_SPEED_UNIT = "km/h"; public static final String DEFAULT_LENGTH_UNIT = "mm"; 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 ID = "id"; public static final String NAME = "name"; diff --git a/src/main/java/de/srsoftware/web4rail/EventListener.java b/src/main/java/de/srsoftware/web4rail/EventListener.java new file mode 100644 index 0000000..4c275fa --- /dev/null +++ b/src/main/java/de/srsoftware/web4rail/EventListener.java @@ -0,0 +1,5 @@ +package de.srsoftware.web4rail; + +public interface EventListener { + public void fire(); +} diff --git a/src/main/java/de/srsoftware/web4rail/LoadCallback.java b/src/main/java/de/srsoftware/web4rail/LoadCallback.java new file mode 100644 index 0000000..945262a --- /dev/null +++ b/src/main/java/de/srsoftware/web4rail/LoadCallback.java @@ -0,0 +1,18 @@ +package de.srsoftware.web4rail; + +import java.util.LinkedList; + +public abstract class LoadCallback { + + private static LinkedList callbacks = new LinkedList(); + + public LoadCallback() { + callbacks.add(this); + } + + public abstract void afterLoad(); + + public static void fire() { + for (LoadCallback callback : callbacks) callback.afterLoad(); + } +} diff --git a/src/main/java/de/srsoftware/web4rail/PathFinder.java b/src/main/java/de/srsoftware/web4rail/PathFinder.java deleted file mode 100644 index 731ac90..0000000 --- a/src/main/java/de/srsoftware/web4rail/PathFinder.java +++ /dev/null @@ -1,131 +0,0 @@ -package de.srsoftware.web4rail; - -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.Plan.Direction; -import de.srsoftware.web4rail.moving.Train; -import de.srsoftware.web4rail.tiles.Block; - -/** - * @author Stephan Richter, SRSoftware 2020-2021 - */ -public class PathFinder extends BaseClass{ - public static final Logger LOG = LoggerFactory.getLogger(PathFinder.class); - - private static TreeMap> availableRoutes(Context context,HashSet visitedRoutes){ - String inset = ""; - for (int i=0; i> availableRoutes = new TreeMap>(); - - boolean error = false; - Block block = context.block(); - if (isNull(block)) { - LOG.warn("{} → {}.availableRoutes called without context.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()); - error = true; - } - if (error) return availableRoutes; - - Block destination = train.destination(); - Direction direction = context.direction(); - if (isSet(direction)) { - LOG.debug("{}Looking for {}-bound routes from {}",inset,direction,block); - } else { - LOG.debug("{}Looking for all routes from {}",inset,block); - }//*/ - 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 - 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(direction) && routeCandidate.startDirection != direction) { // 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> 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> 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 routeSet = availableRoutes.get(priority); - if (isNull(routeSet)) { - routeSet = new Vector(); - 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> entry : availableRoutes.entrySet()) { - LOG.debug("{} - Priority {}:",inset,entry.getKey()); - for (Route r : entry.getValue()) { - LOG.debug("{} - {}",inset,r.shortName()); - } - } - return availableRoutes; - } - - public static Route chooseRoute(Context context) { - LOG.debug("PathFinder.chooseRoute({})",context); - TreeMap> availableRoutes = PathFinder.availableRoutes(context,new HashSet()); - 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))) { - 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; - } -} diff --git a/src/main/java/de/srsoftware/web4rail/Plan.java b/src/main/java/de/srsoftware/web4rail/Plan.java index 04c4743..5bfe737 100644 --- a/src/main/java/de/srsoftware/web4rail/Plan.java +++ b/src/main/java/de/srsoftware/web4rail/Plan.java @@ -36,6 +36,7 @@ import de.srsoftware.web4rail.tags.Input; import de.srsoftware.web4rail.tags.Label; import de.srsoftware.web4rail.tags.Table; import de.srsoftware.web4rail.tags.Window; +import de.srsoftware.web4rail.threads.ControlUnit; import de.srsoftware.web4rail.tiles.Block; import de.srsoftware.web4rail.tiles.BlockContact; import de.srsoftware.web4rail.tiles.BlockH; @@ -151,7 +152,7 @@ public class Plan extends BaseClass{ private static final String SPEED_UNIT = "speed_unit"; private static final String LENGTH_UNIT = "length_unit"; private static final String CONFIRM = "confirm"; - private static final String FINAL_SPEED = "final_speed"; + private static final String FINAL_SPEED = "final_speed"; private static final String FREE_BEHIND_TRAIN = "free_behind_train"; private static final String RENAME = "rename"; private String name = DEFAULT_NAME; @@ -299,7 +300,7 @@ public class Plan extends BaseClass{ return win; } - Thread analyzer = new Thread() { + new Thread(Application.threadName("Plan.Analyzer")) { public void run() { Vector newRoutes = new Vector(); for (Block block : BaseClass.listElements(Block.class)) { @@ -316,9 +317,7 @@ public class Plan extends BaseClass{ stream(t("Found {} routes.",newRoutes.size())); } - }; - analyzer.setName(Application.threadName("Plan.Analyzer")); - analyzer.start(); + }.start(); return t("Analyzing plan..."); } @@ -355,7 +354,7 @@ public class Plan extends BaseClass{ new Input(ACTION,ACTION_UPDATE).hideIn(form); new Input(LENGTH_UNIT, lengthUnit).addTo(new Label(t("Length unit")+COL)).addTo(form); new Input(SPEED_UNIT, speedUnit).addTo(new Label(t("Speed unit")+COL)).addTo(form); - new Input(FINAL_SPEED, Route.defaultEndSpeed).addTo(new Label(t("Lower speed limit")+COL)).attr("title", t("Final speed after breaking, before halting")).addTo(form); + new Input(FINAL_SPEED, Train.defaultEndSpeed).addTo(new Label(t("Lower speed limit")+COL)).attr("title", t("Final speed after breaking, before halting")).addTo(form); new Checkbox(FREE_BEHIND_TRAIN, t("Free tiles behind train"), Route.freeBehindTrain).attr("title", t("If checked, tiles behind the train are freed according to the length of the train and the tiles. If it is unchecked, tiles will not get free before route is finished.")).addTo(form); new Button(t("Save"), form).addTo(form); form.addTo(fieldset); @@ -489,7 +488,7 @@ public class Plan extends BaseClass{ .forEach(jTiles::put); return new JSONObject() - .put(FINAL_SPEED, Route.defaultEndSpeed) + .put(FINAL_SPEED, Train.defaultEndSpeed) .put(FREE_BEHIND_TRAIN, Route.freeBehindTrain) .put(LENGTH_UNIT, lengthUnit) .put(SPEED_UNIT, speedUnit) @@ -512,25 +511,29 @@ public class Plan extends BaseClass{ public static void load(String name) throws IOException { plan = new Plan(); plan.name = name; + + String content = new String(Files.readAllBytes(new File(name+".plan").toPath()),UTF8); + JSONObject json = new JSONObject(content); + + if (json.has(LENGTH_UNIT)) lengthUnit = json.getString(LENGTH_UNIT); + if (json.has(SPEED_UNIT)) speedUnit = json.getString(SPEED_UNIT); + if (json.has(FINAL_SPEED)) Train.defaultEndSpeed = json.getInt(FINAL_SPEED); + if (json.has(FREE_BEHIND_TRAIN)) Route.freeBehindTrain = json.getBoolean(FREE_BEHIND_TRAIN); + try { Car.loadAll(name+".cars",plan); } catch (Exception e) { LOG.warn("Was not able to load cars!",e); } - String content = new String(Files.readAllBytes(new File(name+".plan").toPath()),UTF8); - JSONObject json = new JSONObject(content); - if (json.has(TILE)) json.getJSONArray(TILE).forEach(object -> Tile.load(object, plan)); - if (json.has(LENGTH_UNIT)) lengthUnit = json.getString(LENGTH_UNIT); - if (json.has(SPEED_UNIT)) speedUnit = json.getString(SPEED_UNIT); - if (json.has(FINAL_SPEED)) Route.defaultEndSpeed = json.getInt(FINAL_SPEED); - if (json.has(FREE_BEHIND_TRAIN)) Route.freeBehindTrain = json.getBoolean(FREE_BEHIND_TRAIN); - try { Train.loadAll(name+".trains",plan); } catch (Exception e) { LOG.warn("Was not able to load trains!",e); } + + if (json.has(TILE)) json.getJSONArray(TILE).forEach(object -> Tile.load(object, plan)); + try { Route.loadAll(name+".routes",plan); } catch (Exception e) { @@ -546,6 +549,7 @@ public class Plan extends BaseClass{ } catch (Exception e) { LOG.warn("Was not able to establish connection to control unit!"); } + LoadCallback.fire(); } /** @@ -646,7 +650,7 @@ public class Plan extends BaseClass{ */ public Tile place(Tile tile) { try { - tile.parent(this); +// tile.parent(this); tile.register(); stream("place "+tile.tag(null)); } catch (IOException e) { @@ -674,19 +678,27 @@ public class Plan extends BaseClass{ } public Window properties(HashMap params) { + if (params.containsKey(ID)) { Tile tile = get(Id.from(params), true); if (isSet(tile)) return tile.properties(); - } + } - Window win = new Window("plan-properties", t("Properties of {}",t("Plan"))); + return properties(); + } + + @Override + protected Window properties(List
preForm, FormInput formInputs, List
postForm, String... errorMessages) { + formInputs.add(null, new Input(REALM,REALM_PLAN)); + formInputs.add(null, new Input(ACTION,ACTION_UPDATE)); + formInputs.add(t("Length unit"),new Input(LENGTH_UNIT, lengthUnit)); + formInputs.add(t("Speed unit"),new Input(SPEED_UNIT, speedUnit)); + formInputs.add(t("Lower speed limit"),new Input(FINAL_SPEED, Train.defaultEndSpeed).attr("title", t("Final speed after breaking, before halting"))); + formInputs.add(t("Free tiles behind train"),new Checkbox(FREE_BEHIND_TRAIN, t("If checked, tiles behind the train are freed according to the length of the train and the tiles. If it is unchecked, tiles will not get free before route is finished."), Route.freeBehindTrain)); - editableProperties().addTo(win); - relayProperties().addTo(win); - routeProperties().addTo(win); - - - return win; + postForm.add(relayProperties()); + postForm.add(routeProperties()); + return super.properties(preForm, formInputs, postForm, errorMessages); } /** @@ -788,7 +800,7 @@ public class Plan extends BaseClass{ } - private Tag routeProperties() { + private Fieldset routeProperties() { Fieldset fieldset = new Fieldset(t("Routes")); Table table = new Table(); table.addHead(t("Name"),t("Start"),t("End"),t("Actions")); @@ -967,6 +979,11 @@ public class Plan extends BaseClass{ new Div(ACTION_PROPS).clazz(REALM_CAR).content(t("Manage cars")).addTo(tiles); return tiles.addTo(tileMenu); } + + @Override + public String toString() { + return name; + } /** * updates a tile @@ -982,7 +999,7 @@ public class Plan extends BaseClass{ if (params.containsKey(LENGTH_UNIT)) lengthUnit = params.get(LENGTH_UNIT); if (params.containsKey(SPEED_UNIT)) speedUnit = params.get(SPEED_UNIT); - if (params.containsKey(FINAL_SPEED)) Route.defaultEndSpeed = Integer.parseInt(params.get(FINAL_SPEED)); + if (params.containsKey(FINAL_SPEED)) Train.defaultEndSpeed = Integer.parseInt(params.get(FINAL_SPEED)); Route.freeBehindTrain = "on".equalsIgnoreCase(params.get(FREE_BEHIND_TRAIN)); return t("Plan updated."); diff --git a/src/main/java/de/srsoftware/web4rail/Route.java b/src/main/java/de/srsoftware/web4rail/Route.java index 3ddb42e..d6df5fc 100644 --- a/src/main/java/de/srsoftware/web4rail/Route.java +++ b/src/main/java/de/srsoftware/web4rail/Route.java @@ -4,9 +4,9 @@ import java.io.BufferedWriter; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -18,7 +18,6 @@ import org.json.JSONTokener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import de.keawe.tools.translations.Translation; import de.srsoftware.tools.Tag; import de.srsoftware.web4rail.Plan.Direction; import de.srsoftware.web4rail.actions.Action; @@ -38,6 +37,8 @@ import de.srsoftware.web4rail.tags.Fieldset; import de.srsoftware.web4rail.tags.Input; import de.srsoftware.web4rail.tags.Table; import de.srsoftware.web4rail.tags.Window; +import de.srsoftware.web4rail.threads.BrakeProcess; +import de.srsoftware.web4rail.threads.RoutePrepper; import de.srsoftware.web4rail.tiles.Block; import de.srsoftware.web4rail.tiles.BlockContact; import de.srsoftware.web4rail.tiles.Contact; @@ -53,9 +54,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,8 +71,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; - public static int defaultEndSpeed = 10; + //private State state = State.FREE; public static boolean freeBehindTrain = true; private static final String ROUTE_START = "route_start"; @@ -79,161 +79,31 @@ public class Route extends BaseClass { private static final String ROUTE_SETUP = "route_setup"; private static HashMap names = new HashMap(); // maps id to name. needed to keep names during plan.analyze() - - private class BrakeProcessor extends Thread { - private long latestTick; - private static final int SPEED_STEP = 5; - private Integer timeStep; - private Route route; - private Train train; - private String brakeId; - private long estimatedDistance; // Unit: s*km/h "km/h-Sekunden" - private int startSpeed,endSpeed; - private boolean aborted,modified,finished; - - public BrakeProcessor(Route route, Train train) { - this.train = train; - this.route = route; - - aborted = false; - modified = false; - finished = false; - brakeId = train.brakeId(); - startSpeed = train.speed; - endSpeed = defaultEndSpeed; - - timeStep = brakeTimes.get(brakeId); - if (isNull(timeStep) || timeStep>1000000) timeStep = 256; // if no brake time is available for this train - setName(Application.threadName("BrakeProcessor("+train+")")); - start(); - } - - protected void abort() { - aborted = true; - } - - private long calcDistance(Integer ts) { - long dist = 0; - int s = startSpeed; - while (s > defaultEndSpeed) { - s -= SPEED_STEP; - dist += s*ts; - } - LOG.debug("Estimated distamce with {} ms timestep: {}",ts,dist); - return dist; - } - - private void checkNextRoute() { - Route nextRoute = train.nextRoute(); - if (isSet(nextRoute) && nextRoute.state == Route.State.PREPARED) { // auf Startgeschwindigkeit der Nachfolgeroute bremsen - Integer nextRouteStartSpeed = nextRoute.startSpeed(); - if (isSet(nextRouteStartSpeed)) { - LOG.debug("updating target velocity from {} to {}!",endSpeed,nextRouteStartSpeed); - endSpeed = nextRouteStartSpeed; - modified = true; - if (endSpeed > train.speed) train.setSpeed(endSpeed); - } - } - } - - /** - * This is called from route.finish when train came to stop - */ - public void finish() { - LOG.debug("BrakeProcessor.finish()"); - finished = true; - if (aborted || modified) return; - increaseDistance(); - train.setSpeed(0); - LOG.debug("Estimated distance: {}",estimatedDistance); - - if (startSpeed <= endSpeed) return; - if (timeStep<0) timeStep = 100; - Integer newTimeStep = timeStep; - long calculated; - int step = 32*newTimeStep; - for (int i=0; i<20; i++) { - step = step/2; - if (step<1) step = 1; - calculated = calcDistance(newTimeStep); - LOG.debug("Calculated distance for step = {} ms: {}",newTimeStep,calculated); - LOG.debug("Update step: {}",step); - newTimeStep = newTimeStep + (calculated > estimatedDistance ? -step : step); - } - - if (!newTimeStep.equals(timeStep)) { - route.brakeTimes.put(brakeId,newTimeStep); - calculated = calcDistance(newTimeStep); - LOG.debug("Corrected brake timestep for {} @ {} from {} to {} ms.",train,route,timeStep,newTimeStep); - LOG.debug("Differemce from estimated distance: {} ({}%)",estimatedDistance-calculated,100*(estimatedDistance-calculated)/(float)estimatedDistance); - } - } - - private void increaseDistance(){ - long tick = timestamp(); - estimatedDistance += train.speed * (3+tick-latestTick); - latestTick = tick; - } - - @Override - public void run() { - setName(Application.threadName("BreakeProcessor("+train+")")); - LOG.debug("started BrakeProcessor ({} → {}) for {} with timestep = {} ms.",train.speed,endSpeed,train,timeStep); - estimatedDistance = 0; - latestTick = timestamp(); - while (train.speed > endSpeed) { - if (finished || aborted) return; - increaseDistance(); - LOG.debug("BrakeProcessor({}) setting Speed of {}.",route,train); - train.setSpeed(Math.max(train.speed - SPEED_STEP,endSpeed)); - if (!modified) checkNextRoute(); - try { - sleep(timeStep); - } catch (InterruptedException e) { - LOG.warn("BrakeProcessor interrupted!", e); - } - } - - while (!finished && !aborted && !modified) { - try { - sleep(1000); - } catch (InterruptedException e) { - LOG.warn("BrakeProcessor interrupted!", e); - } - checkNextRoute(); - } - } - - public void setEndSpeed(Integer newEndSpeed) { - if (isNull(newEndSpeed)) return; - endSpeed = newEndSpeed; - modified = true; - } - } - - 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 Context context; private boolean disabled = false; private Block endBlock = null; public Direction endDirection; private Vector path; private Vector signals; - private Train train; private HashMap triggeredActions = new HashMap(); private HashMap turnouts; private Block startBlock = null; public Direction startDirection; private HashSet triggeredContacts = new HashSet<>(); + + private Route nextPreparedRoute; + + private RoutePrepper nextRoutePrepper; public Route() { conditions = new ConditionList(); conditions.parent(this); } - + /** * process commands from the client * @param params @@ -254,12 +124,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); @@ -350,7 +214,7 @@ public class Route extends BaseClass { private Fieldset basicProperties() { Fieldset fieldset = new Fieldset(t("Route properties")); - if (isSet(train)) train.link("span",t("Train")+": "+train).addTo(fieldset); +// if (isSet(train)) train.link("span",t("Train")+": "+train).addTo(fieldset); Tag list = new Tag("ul"); startBlock.link("li",t("Origin: {} to {}",startBlock.name,startDirection)).addTo(list); endBlock.link("li",t("Destination: {} from {}",endBlock.name,endDirection.inverse())).addTo(list); @@ -367,6 +231,17 @@ public class Route extends BaseClass { return fieldset; } + public Integer brakeTime(String brakeId) { + Integer result = brakeTimes.get(brakeId); + Collection values = brakeTimes.values(); + return values.isEmpty() ? BrakeProcess.defaultTimeStep : values.stream().mapToInt(Integer::intValue).sum()/values.size(); + } + + public void brakeTime(String brakeId, Integer newTimeStep) { + LOG.debug("new brake time for route {}: {}",this,newTimeStep); + brakeTimes.put(brakeId,newTimeStep); + } + private Fieldset brakeTimes() { Fieldset fieldset = new Fieldset(t("Brake time table")); Table table = new Table(); @@ -393,11 +268,6 @@ public class Route extends BaseClass { return this; } - public void brakeStart() { - if (isNull(train)) return; - brakeProcessor = new BrakeProcessor(this,train); - } - protected Route clone() { Route clone = new Route(); clone.startBlock = startBlock; @@ -417,18 +287,18 @@ public class Route extends BaseClass { Contact nextToLastContact = contacts.get(contacts.size()-2); String trigger = nextToLastContact.trigger(); add(trigger,new BrakeStart(this)); - add(trigger,new PreserveRoute(this)); int count = 0; for (int i=0;i entry : turnouts.entrySet()) { @@ -450,15 +320,20 @@ public class Route extends BaseClass { * Kontakt der Route aktivieren * @param contact * @param trainHead + * @return */ - public void contact(Contact contact) { - if (triggeredContacts.contains(contact)) return; // don't trigger contact a second time + public Context contact(Contact contact) { + context.contact(contact); + if (triggeredContacts.contains(contact)) return context; // don't trigger contact a second time triggeredContacts.add(contact); - LOG.debug("{} on {} activated {}.",train,this,contact); + LOG.debug("{} on {} activated {}.",context.train(),this,contact); 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; + if (isNull(actions)) return context; actions.fire(context,"Route.Contact("+contact.addr()+")"); + Context previousContext = context; + if (context.invalidated()) context = null; // route has been freed in between. + return previousContext; } public Vector contacts() { @@ -498,64 +373,36 @@ 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); } + public Route dropNextPreparedRoute() { + try { + return nextPreparedRoute; + } finally { + nextPreparedRoute = null; + } + } + public Block endBlock() { return endBlock; } - public void finish() { + public void finish(Train train) { LOG.debug("{}.finish()",this); - - if (isSet(train)) { - Route nextRoute = train.nextRoute(); - if (isSet(nextRoute)) { - LOG.debug("{} has next route: {}",train,nextRoute); - if (isSet(brakeProcessor)) brakeProcessor.abort(); - } else { - LOG.debug("{} has no next route.",train); - if (isSet(brakeProcessor)) { - brakeProcessor.finish(); - } else train.setSpeed(0); - } - } - brakeProcessor = null; - - free(); - - if (isSet(train)) { - moveTrainToEndBlock(); - if (train.route() == this) train.route(null); - train = null; - } - - state = State.FREE; + train.endRoute(endBlock,endDirection); + freeIgnoring(null); + train = null; } - /** - * sets all signals of this route to RED, - * frees all tiles occupied by this route - */ - private void free() { - LOG.debug("{}.free()",this); - context.clear(); // prevent delayed actions from firing after route has finished - - setSignals(Signal.RED); - for (Tile tile : path) try { // remove route from tiles on path - tile.unset(this); - } catch (IllegalArgumentException e) {} - -/* Tile lastTile = path.lastElement(); - if (lastTile instanceof Contact) { - lastTile.setTrain(null); - if (isSet(train)) train.removeChild(lastTile); - }*/ + private void freeIgnoring(Vector skipTiles) { + Train train = context.train(); + Vector reversedPath = reverse(path()); + if (isSet(skipTiles)) reversedPath.removeAll(skipTiles); + for (Tile tile : reversedPath) { + if (isSet(train) && !train.onTrace(tile)) tile.free(train); + } } private String generateName() { @@ -573,6 +420,10 @@ public class Route extends BaseClass { return sb.toString().trim(); } + public Route getNextPreparedRoute() { + return nextPreparedRoute; + } + public Id id() { if (isNull(id)) id = new Id(""+(generateName().hashCode())); return id; @@ -582,13 +433,11 @@ public class Route extends BaseClass { return disabled; } - public boolean isFreeFor(Context context) { - PathFinder.LOG.debug("{}.isFreeFor({})",this,context); - for (int i=1; i ignoredPath = new HashSet(); - if (isSet(ignoredRoute)) ignoredPath.addAll(ignoredRoute.path); - boolean success = true; - 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); - success = false; - break; - } - } - if (success) state = State.LOCKED; - return success; - } - - private void moveTrainToEndBlock() { - if (isNull(train)) return; - LOG.debug("{}.moveTrainToEndBlock()",this); - - train.set(endBlock); - traceTrainFrom(endBlock); - train.heading(endDirection); - - if (endBlock == train.destination()) { - train.destination(null); // unset old destination - String destTag = train.destinationTag(); - if (isSet(destTag)) { - LOG.debug("destination list: {}",destTag); - String[] parts = destTag.split(Train.DESTINATION_PREFIX); - for (int i=0; i0; i--) { - switch (destId.charAt(i)) { - case Train.FLAG_SEPARATOR: - destId = destId.substring(0,i); - i=0; - break; - case Train.TURN_FLAG: - turn = true; - LOG.debug("Turn flag is set!"); - break; - } - } - if (destId.equals(endBlock.id().toString())) { - if (turn) train.turn(); - - // update destination tag: remove and add altered tag: - train.removeTag(destTag); - destTag = destTag.substring(parts[1].length()+1); - if (destTag.isEmpty()) { // no further destinations - destTag = null; - } else train.addTag(destTag); - } - } - if (isNull(destTag)) { - train.quitAutopilot(); - plan.stream(t("{} reached it`s destination!",train)); - } - } else train.setWaitTime(endBlock.getWaitTime(train,train.direction())); - if (startBlock.train() == train && !train.onTrace(startBlock)) startBlock.setTrain(null); // withdraw train from start block only if trace does not go back there - } - public List multiply(int size) { Vector routes = new Vector(); for (int i=0; i path() { - Vector result = new Vector(); - if (isSet(path)) result.addAll(path); - return result; + return isSet(path) ? new Vector<>(path) : new Vector<>(); } - public boolean prepare() { - if (state == State.PREPARED || state == State.STARTED) return true; - LOG.debug("{}.prepare()",this); + public boolean prepareAndLock() { + LOG.debug("{}.prepareAndLock()",this); + Train train = context.train(); ActionList setupActions = triggeredActions.get(ROUTE_SETUP); - if (isSet(setupActions) && !setupActions.fire(context,this+".prepare()")) return false; - state = State.PREPARED; + if (isSet(setupActions) && !setupActions.fire(context.route(this),this+".prepare()")) { + LOG.debug("Was not able to prepare route for {}.",train); + return false; + } + + for (Tile tile : path) { + if (context.invalidated() || !tile.lockFor(context,false)) { + LOG.debug("Was not able to allocate route for {}.",context); + return false; + } + } return true; } + public boolean prepareNext(Context context) { + if (isSet(nextRoutePrepper)) return false; + nextRoutePrepper = new RoutePrepper(context); + nextRoutePrepper.onRoutePrepared(() -> { + nextPreparedRoute = nextRoutePrepper.route(); + nextRoutePrepper = null; + }); + nextRoutePrepper.onFail(() -> { + Route rt = nextRoutePrepper.route(); // Nachfolgeroute kann ja schon reserviert sein, oder gar schon teilweise vorbereitet! + if (isSet(rt)) rt.resetIgnoring(this); // angefangene Route freigeben ohne Teile der aktuellen Route freizugeben + nextRoutePrepper = null; + }); + return nextRoutePrepper.prepareRoute(); + } + private Tag previewScript() { Tag script = new Tag("script").attr("type", "text/javascript"); for (Tile tile : path) { @@ -913,13 +709,8 @@ public class Route extends BaseClass { return script; } - public Route prolong(Route nextRoute) { - if (isSet(brakeProcessor)) brakeProcessor.setEndSpeed(nextRoute.startSpeed()); - return nextRoute; - } - @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { preForm.add(conditions.list(t("Route will only be available, if all conditions are fulfilled."))); preForm.add(contactsAndActions()); @@ -935,7 +726,7 @@ public class Route extends BaseClass { postForm.add(basicProperties()); if (!turnouts.isEmpty()) postForm.add(turnouts()); postForm.add(brakeTimes()); - Window win = super.properties(preForm, formInputs, postForm); + Window win = super.properties(preForm, formInputs, postForm,errors); previewScript().addTo(win); return win; } @@ -947,7 +738,7 @@ public class Route extends BaseClass { @Override public BaseClass remove() { LOG.debug("Removing route ({}) {}",id(),this); - if (isSet(train)) train.removeChild(this); +// if (isSet(train)) train.removeChild(this); for (Tile tile : path) { tile.removeChild(this); } @@ -966,7 +757,7 @@ public class Route extends BaseClass { if (child == endBlock) endBlock = null; path.remove(child); signals.remove(child); - if (child == train) train = null; +// if (child == train) train = null; for (ActionList list : triggeredActions.values()) { list.removeChild(child); } @@ -976,21 +767,38 @@ public class Route extends BaseClass { super.removeChild(child); } - public boolean reset() { - LOG.debug("{}.reset()",this); - free(); - - if (isSet(brakeProcessor)) brakeProcessor.abort(); - if (isSet(train)) { - train.set(startBlock); - train.heading(startDirection); - if (train.route() == this) train.route(null); - train = null; + public boolean reserveFor(Context newContext) { + LOG.debug("{}.reserverFor({})",this,newContext); + + context = newContext; + for (Tile tile : path) { + if (newContext.invalidated() || !tile.reserveFor(newContext)) { + LOG.debug("Was not able to allocate route for {}.",newContext); + return false; + } } - state = State.FREE; return true; } + public void reset() { + resetIgnoring(null); + } + + public void resetIgnoring(Route otherRoute) { + LOG.debug("{}.resetIgnoring({})",this,otherRoute); + resetNext(); + setSignals(Signal.RED); + Train train = context.train(); + freeIgnoring(isSet(otherRoute) ? otherRoute.path : null); + train.drop(this); + context = null; + } + + public void resetNext() { + if (isSet(nextRoutePrepper)) nextRoutePrepper.stop(); + if (isSet(nextPreparedRoute)) nextPreparedRoute.reset(); + } + public static void saveAll(String filename) throws IOException { BufferedWriter file = new BufferedWriter(new FileWriter(filename)); file.write("{\""+ROUTES+"\":[\n"); @@ -1005,19 +813,17 @@ 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(); if (lastTile instanceof Turnout) addTurnout((Turnout) lastTile,state); } + public void setNextPreparedRoute(Route route) { + nextPreparedRoute = route; + } + + public boolean setSignals(String state) { LOG.debug("{}.setSignals({})",this,state); for (Signal signal : signals) { @@ -1033,25 +839,29 @@ public class Route extends BaseClass { public Route simplyfyName() { String[] parts = name().split("-"); - if (parts.length>1) name(parts[0]+" - "+parts[parts.length-1]); + if (parts.length>1) name(parts[0].trim()+" - "+parts[parts.length-1].trim()); return this; } - public Route.State state(){ - return state; - } - - public boolean start(Train newTrain) { - if (state == State.STARTED) return true; + public boolean start() { 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 - 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 (isNull(context) || context.invalidated()) return false; + + Train train = context.train(); + if (isNull(train)) return false; // can't set route's train to null + train.setRoute(this); // set new train + triggeredContacts.clear(); + + ActionList startActions = triggeredActions.get(ROUTE_START); + + if (isSet(startActions)) { + context.route(this); + String cause = this+".start("+train.name()+")"; + if (!startActions.fire(context,cause)) return false; // start actions failed + } + + context.waitTime(endBlock.getWaitTime(train, endDirection).random()); return true; } @@ -1066,49 +876,14 @@ public class Route extends BaseClass { return isSet(startActions) ? startActions.getSpeed(context) : null; } - - protected static String t(String txt, Object...fills) { - return Translation.get(Application.class, txt, fills); - } - @Override public String toString() { - return getClass().getSimpleName()+"("+(isSet(train)?train+":":"")+name()+")"; + return getClass().getSimpleName()+"("+name()+")"; } - - public void traceTrainFrom(Tile newHead) { - LOG.debug("{}.traceTrainFrom({})",this,newHead); - if (isNull(train)) return; - if (newHead instanceof BlockContact) newHead = (Tile) ((BlockContact)newHead).parent(); - Tile traceHead = train.traceHead(); - Integer remainingLength = null; - LinkedList newTrace = new LinkedList(); - 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 = train.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) { - try { - tile.unset(this); - } catch (IllegalArgumentException e) {} - } else break; - } - } - train.setTrace(newTrace); - } - - public Train train() { +/* public Train train() { return train; - } + }*/ private Fieldset turnouts() { Fieldset win = new Fieldset(t("Turnouts")); @@ -1137,7 +912,7 @@ public class Route extends BaseClass { return properties(); } - public Integer brakeTime(String brakeId) { - return brakeTimes.get(brakeId); + public Integer waitTime() { + return isNull(context) ? null : context.waitTime(); } } diff --git a/src/main/java/de/srsoftware/web4rail/actions/Action.java b/src/main/java/de/srsoftware/web4rail/actions/Action.java index 84ba186..605e6eb 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/Action.java +++ b/src/main/java/de/srsoftware/web4rail/actions/Action.java @@ -10,9 +10,7 @@ import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import de.keawe.tools.translations.Translation; import de.srsoftware.tools.Tag; -import de.srsoftware.web4rail.Application; import de.srsoftware.web4rail.BaseClass; import de.srsoftware.web4rail.tags.Button; import de.srsoftware.web4rail.tags.Fieldset; @@ -140,10 +138,10 @@ public abstract class Action extends BaseClass { } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Edit json"),button(t("export"), Map.of(ACTION, ACTION_SAVE))); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } public static Tag selector() { @@ -159,10 +157,6 @@ public abstract class Action extends BaseClass { return select.addTo(new Label(t("Action type")+COL)); } - protected static String t(String tex,Object...fills) { - return Translation.get(Application.class, tex, fills); - } - @Override public String toString() { return t(getClass().getSimpleName()); diff --git a/src/main/java/de/srsoftware/web4rail/actions/ActionList.java b/src/main/java/de/srsoftware/web4rail/actions/ActionList.java index dc7b17e..d662cda 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/ActionList.java +++ b/src/main/java/de/srsoftware/web4rail/actions/ActionList.java @@ -81,14 +81,12 @@ public class ActionList extends Action implements Iterable{ } public boolean fire(Context context,Object cause) { - if (context.invalidated()) { - LOG.debug("Context has been invalidated, aborting {}",this); - return false; - } - 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; } @@ -249,11 +247,11 @@ public class ActionList extends Action implements Iterable{ } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { Fieldset fieldset = new Fieldset(t("Actions")); list().addTo(fieldset); postForm.add(fieldset); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/actions/AddRemoveDestination.java b/src/main/java/de/srsoftware/web4rail/actions/AddRemoveDestination.java index e01bb85..1fead2e 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/AddRemoveDestination.java +++ b/src/main/java/de/srsoftware/web4rail/actions/AddRemoveDestination.java @@ -9,7 +9,7 @@ import org.json.JSONObject; import de.srsoftware.tools.Tag; import de.srsoftware.web4rail.BaseClass; -import de.srsoftware.web4rail.DelayedExecution; +import de.srsoftware.web4rail.LoadCallback; import de.srsoftware.web4rail.moving.Train; import de.srsoftware.web4rail.tags.Checkbox; import de.srsoftware.web4rail.tags.Fieldset; @@ -69,31 +69,25 @@ public class AddRemoveDestination extends Action { public Action load(JSONObject json) { if (json.has(TURN)) turnAtDestination = json.getBoolean(TURN); if (json.has(SHUNTING)) shunting = json.getBoolean(SHUNTING); - if (json.has(Train.DESTINATION)) { - Id blockId = new Id(json.getString(Train.DESTINATION)); - destination = BaseClass.get(blockId); - if (isNull(destination)) { - new DelayedExecution(this) { - - @Override - public void execute() { - destination = BaseClass.get(blockId); - } - }; + if (json.has(Train.DESTINATION)) new LoadCallback() { + @Override + public void afterLoad() { + destination = BaseClass.get(Id.from(json, Train.DESTINATION)); } - } + }; + return super.load(json); } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { Tag span = new Tag("span"); button(t("Select from plan"),Map.of(ACTION,ACTION_UPDATE,ASSIGN,Train.DESTINATION)).addTo(span); button(t("Clear destinations"),Map.of(ACTION,ACTION_UPDATE,Train.DESTINATION,"0")).addTo(span); formInputs.add(t("Destination")+": "+(isNull(destination) ? t("Clear destinations") : destination),span); formInputs.add(t("Turn at destination"),new Checkbox(TURN, t("Turn"), turnAtDestination)); formInputs.add(t("Shunting"),new Checkbox(SHUNTING, t("Shunting"), shunting)); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/actions/AddRemoveTag.java b/src/main/java/de/srsoftware/web4rail/actions/AddRemoveTag.java index 52be625..307610c 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/AddRemoveTag.java +++ b/src/main/java/de/srsoftware/web4rail/actions/AddRemoveTag.java @@ -51,13 +51,13 @@ public class AddRemoveTag extends Action{ } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Tag"),new Input(TAG, tag)); Tag div = new Tag("div"); new Radio(TYPE, ACTION_ADD, t("add"), !remove).addTo(div); new Radio(TYPE, ACTION_DROP, t("delete"), remove).addTo(div); formInputs.add(t("Action"),div); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/actions/AlterDirection.java b/src/main/java/de/srsoftware/web4rail/actions/AlterDirection.java index 7c6d98d..ea342d2 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/AlterDirection.java +++ b/src/main/java/de/srsoftware/web4rail/actions/AlterDirection.java @@ -77,14 +77,14 @@ public class AlterDirection extends Action{ } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { Tag radios = new Tag("div"); for (NEWDIR d : NEWDIR.values()) { new Radio(NEW_DIRECTION, d, t("{}",d), newDir == d).addTo(radios); } formInputs.add(t("new direction"),radios); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @SuppressWarnings("incomplete-switch") diff --git a/src/main/java/de/srsoftware/web4rail/actions/BrakeStart.java b/src/main/java/de/srsoftware/web4rail/actions/BrakeStart.java index 554f886..0c2b33a 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/BrakeStart.java +++ b/src/main/java/de/srsoftware/web4rail/actions/BrakeStart.java @@ -10,8 +10,8 @@ public class BrakeStart extends Action { @Override public boolean fire(Context context,Object cause) { - if (isNull(context.route())) return false; - context.route().brakeStart(); + if (isNull(context.train())) return false; + context.train().startBrake(); LOG.debug("Started brake process..."); return true; } diff --git a/src/main/java/de/srsoftware/web4rail/actions/ConditionalAction.java b/src/main/java/de/srsoftware/web4rail/actions/ConditionalAction.java index 1f3ad8f..ae86c67 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/ConditionalAction.java +++ b/src/main/java/de/srsoftware/web4rail/actions/ConditionalAction.java @@ -70,9 +70,9 @@ public class ConditionalAction extends ActionList { } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { preForm.add(conditions.list()); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } diff --git a/src/main/java/de/srsoftware/web4rail/actions/CoupleTrain.java b/src/main/java/de/srsoftware/web4rail/actions/CoupleTrain.java index a4557fd..a1e4b1a 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/CoupleTrain.java +++ b/src/main/java/de/srsoftware/web4rail/actions/CoupleTrain.java @@ -29,7 +29,7 @@ public class CoupleTrain extends Action { if (isNull(train)) return false; Block block = train.currentBlock(); if (isNull(block)) return false; - Train parkingTrain = block.parkedTrain(last); + Train parkingTrain = block.lastTrain(); if (isNull(parkingTrain)) return false; train.coupleWith(parkingTrain,swap); return true; @@ -56,10 +56,10 @@ public class CoupleTrain extends Action { } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Couple"),new Checkbox(LAST, t("last parked train"), last)); formInputs.add(t("Swap order"),new Checkbox(SWAP, t("Swap order of trains"), swap)); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/actions/DelayedAction.java b/src/main/java/de/srsoftware/web4rail/actions/DelayedAction.java index 9630284..5cca381 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/DelayedAction.java +++ b/src/main/java/de/srsoftware/web4rail/actions/DelayedAction.java @@ -7,10 +7,10 @@ import org.json.JSONObject; import de.srsoftware.tools.Tag; import de.srsoftware.web4rail.BaseClass; -import de.srsoftware.web4rail.DelayedExecution; import de.srsoftware.web4rail.tags.Fieldset; import de.srsoftware.web4rail.tags.Input; import de.srsoftware.web4rail.tags.Window; +import de.srsoftware.web4rail.threads.DelayedExecution; public class DelayedAction extends ActionList { @@ -61,10 +61,10 @@ public class DelayedAction extends ActionList { } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Minimum delay"),new Input(MIN_DELAY,min_delay).numeric().addTo(new Tag("span")).content(NBSP+"ms")); formInputs.add(t("Maximum delay"),new Input(MAX_DELAY,max_delay).numeric().addTo(new Tag("span")).content(NBSP+"ms")); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } public DelayedAction setMaxDelay(int max_delay) { diff --git a/src/main/java/de/srsoftware/web4rail/actions/DetermineTrainInBlock.java b/src/main/java/de/srsoftware/web4rail/actions/DetermineTrainInBlock.java index 2671dad..ed71f16 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/DetermineTrainInBlock.java +++ b/src/main/java/de/srsoftware/web4rail/actions/DetermineTrainInBlock.java @@ -7,7 +7,7 @@ import java.util.Map; import org.json.JSONObject; import de.srsoftware.web4rail.BaseClass; -import de.srsoftware.web4rail.DelayedExecution; +import de.srsoftware.web4rail.LoadCallback; import de.srsoftware.web4rail.tags.Fieldset; import de.srsoftware.web4rail.tags.Window; import de.srsoftware.web4rail.tiles.Block; @@ -39,25 +39,20 @@ public class DetermineTrainInBlock extends Action { public Action load(JSONObject json) { super.load(json); Id blockId = Id.from(json,BLOCK); - if (isSet(blockId)) { - block = Block.get(blockId); - if (isNull(block)) { - new DelayedExecution(this) { - - @Override - public void execute() { - block = Block.get(blockId); - } - }; + if (isSet(blockId)) new LoadCallback() { + + @Override + public void afterLoad() { + block = Block.get(blockId); } - } + }; return this; } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Block")+": "+(isNull(block) ? t("unset") : block),button(t("Select from plan"),Map.of(ACTION,ACTION_UPDATE,ASSIGN,BLOCK))); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/actions/DisableEnableBlock.java b/src/main/java/de/srsoftware/web4rail/actions/DisableEnableBlock.java index e68723e..e022403 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/DisableEnableBlock.java +++ b/src/main/java/de/srsoftware/web4rail/actions/DisableEnableBlock.java @@ -8,7 +8,7 @@ import org.json.JSONObject; import de.srsoftware.tools.Tag; import de.srsoftware.web4rail.BaseClass; -import de.srsoftware.web4rail.DelayedExecution; +import de.srsoftware.web4rail.LoadCallback; import de.srsoftware.web4rail.tags.Fieldset; import de.srsoftware.web4rail.tags.Radio; import de.srsoftware.web4rail.tags.Window; @@ -43,34 +43,25 @@ public class DisableEnableBlock extends Action { @Override public Action load(JSONObject json) { - super.load(json); - Id blockId = Id.from(json,BLOCK); - if (isSet(blockId)) { - block = Block.get(blockId); - if (isNull(block)) { - new DelayedExecution(this) { - - @Override - public void execute() { - block = Block.get(blockId); - } - }; + if (json.has(STATE)) disable = !json.getBoolean(STATE); + if (json.has(BLOCK)) new LoadCallback() { + @Override + public void afterLoad() { + block = Block.get(Id.from(json,BLOCK)); } - } - if (json.has(STATE)) { - disable = !json.getBoolean(STATE); - } - return this; + }; + return super.load(json); + } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Block")+": "+(isNull(block) ? t("block from context") : block),button(t("Select from plan"),Map.of(ACTION,ACTION_UPDATE,ASSIGN,Block.class.getSimpleName()))); Tag radios = new Tag("p"); new Radio(STATE, "enable", t("enable"), !disable).addTo(radios); new Radio(STATE, "disable", t("disable"), disable).addTo(radios); formInputs.add(t("Action"),radios); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/actions/EngageDecoupler.java b/src/main/java/de/srsoftware/web4rail/actions/EngageDecoupler.java index 5c2f487..240cc44 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/EngageDecoupler.java +++ b/src/main/java/de/srsoftware/web4rail/actions/EngageDecoupler.java @@ -7,14 +7,14 @@ import java.util.Map; import org.json.JSONObject; import de.srsoftware.web4rail.BaseClass; -import de.srsoftware.web4rail.DelayedExecution; +import de.srsoftware.web4rail.LoadCallback; import de.srsoftware.web4rail.tags.Fieldset; import de.srsoftware.web4rail.tags.Window; import de.srsoftware.web4rail.tiles.Decoupler; import de.srsoftware.web4rail.tiles.Tile; public class EngageDecoupler extends Action { - + private static final String DECOUPLER = Decoupler.class.getSimpleName(); public EngageDecoupler(BaseClass parent) { @@ -24,12 +24,12 @@ public class EngageDecoupler extends Action { private Decoupler decoupler = null; @Override - public boolean fire(Context context,Object cause) { + public boolean fire(Context context, Object cause) { if (isNull(decoupler)) return false; decoupler.engage(); return true; } - + @Override public JSONObject json() { JSONObject json = super.json(); @@ -38,50 +38,44 @@ public class EngageDecoupler extends Action { } return json; } - + @Override public Action load(JSONObject json) { - super.load(json); - if (json.has(DECOUPLER)) { - String decouplerId = json.getString(DECOUPLER); - decoupler = BaseClass.get(new Id(decouplerId)); - if (isNull(decoupler)) new DelayedExecution(this) { - - @Override - public void execute() { - decoupler = BaseClass.get(new Id(decouplerId)); - } - }; - } - return this; + if (json.has(DECOUPLER)) new LoadCallback() { + @Override + public void afterLoad() { + decoupler = BaseClass.get(Id.from(json, DECOUPLER)); + } + }; + return super.load(json); } - + @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { - formInputs.add(t("Decoupler")+": "+(isNull(decoupler) ? t("unset") : decoupler),button(t("Select from plan"),Map.of(ACTION,ACTION_UPDATE,ASSIGN,DECOUPLER))); - - return super.properties(preForm, formInputs, postForm); + protected Window properties(List
preForm, FormInput formInputs, List
postForm, String... errors) { + formInputs.add(t("Decoupler") + ": " + (isNull(decoupler) ? t("unset") : decoupler), button(t("Select from plan"), Map.of(ACTION, ACTION_UPDATE, ASSIGN, DECOUPLER))); + + return super.properties(preForm, formInputs, postForm, errors); } - + @Override protected void removeChild(BaseClass child) { if (child == decoupler) decoupler = null; super.removeChild(child); } - + public String toString() { - if (isNull(decoupler)) return "["+t("Click here to setup decoupler")+"]"; - return t("Engage {}",decoupler); + if (isNull(decoupler)) return "[" + t("Click here to setup decoupler") + "]"; + return t("Engage {}", decoupler); }; - + @Override protected Object update(HashMap params) { - LOG.debug("update: {}",params); + LOG.debug("update: {}", params); if (params.containsKey(DECOUPLER)) { Tile tile = BaseClass.get(new Id(params.get(DECOUPLER))); if (tile instanceof Decoupler) { - decoupler = (Decoupler) tile; - } else return t("Clicked tile is not a {}!",t("decoupler")); + decoupler = (Decoupler) tile; + } else return t("Clicked tile is not a {}!", t("decoupler")); } return context().properties(); } diff --git a/src/main/java/de/srsoftware/web4rail/actions/FinishRoute.java b/src/main/java/de/srsoftware/web4rail/actions/FinishRoute.java index 0a8a94f..a263f94 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/FinishRoute.java +++ b/src/main/java/de/srsoftware/web4rail/actions/FinishRoute.java @@ -2,6 +2,7 @@ package de.srsoftware.web4rail.actions; import de.srsoftware.web4rail.BaseClass; import de.srsoftware.web4rail.Route; +import de.srsoftware.web4rail.moving.Train; public class FinishRoute extends Action { @@ -12,7 +13,9 @@ public class FinishRoute extends Action { @Override public boolean fire(Context context,Object cause) { Route route = context.route(); - if (isSet(route)) route.finish(); + Train train = context.train(); + if (isNull(train)) return false; + if (isSet(route)) route.finish(train); return true; } } diff --git a/src/main/java/de/srsoftware/web4rail/actions/Loop.java b/src/main/java/de/srsoftware/web4rail/actions/Loop.java index 818fe76..a0ac414 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/Loop.java +++ b/src/main/java/de/srsoftware/web4rail/actions/Loop.java @@ -73,9 +73,9 @@ public class Loop extends ActionList { } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Select object"),typeSelector()); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } public String toString() { diff --git a/src/main/java/de/srsoftware/web4rail/actions/PreserveRoute.java b/src/main/java/de/srsoftware/web4rail/actions/PreserveRoute.java index a4f4670..3bebb91 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/PreserveRoute.java +++ b/src/main/java/de/srsoftware/web4rail/actions/PreserveRoute.java @@ -1,7 +1,6 @@ package de.srsoftware.web4rail.actions; import de.srsoftware.web4rail.BaseClass; -import de.srsoftware.web4rail.Range; import de.srsoftware.web4rail.Route; import de.srsoftware.web4rail.moving.Train; import de.srsoftware.web4rail.tiles.Block; @@ -14,6 +13,7 @@ public class PreserveRoute extends Action { @Override public boolean fire(Context context,Object cause) { + if (context.invalidated()) return false; Train train = context.train(); Route route = context.route(); // These are errors: @@ -25,13 +25,14 @@ public class PreserveRoute extends Action { Block endBlock = route.endBlock(); if (train.destination() == endBlock) return true; // do not reserve routes, when destination has been reached - Range waitTime = endBlock.getWaitTime(train,route.endDirection); - if (waitTime.max > 0) { + Integer waitTime = context.waitTime(); + if (isSet(waitTime) && waitTime > 0) { LOG.debug("Not preserving route, as train needs to stop for {} ms at {}!",waitTime,endBlock); - return true; // train is expected to wait in next block. + return false; // train is expected to wait in next block. } - - train.reserveNext(); - return true; + if (isSet(route.getNextPreparedRoute())) return true; + Context nextContext = new Context(train).block(route.endBlock()).direction(route.endDirection); + context.onInvalidate(nextContext::invalidate); + return route.prepareNext(nextContext); } } diff --git a/src/main/java/de/srsoftware/web4rail/actions/SendCommand.java b/src/main/java/de/srsoftware/web4rail/actions/SendCommand.java index 2081ad1..6f186e8 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/SendCommand.java +++ b/src/main/java/de/srsoftware/web4rail/actions/SendCommand.java @@ -71,13 +71,13 @@ public class SendCommand extends Action{ } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Command to send"),new Input(COMMAND, command)); Tag div = new Tag("div"); new Radio(TARGET, Target.SYSTEM, t("Operating System"), target == Target.SYSTEM).addTo(div); new Radio(TARGET, Target.SRCP, t("SRCP daemon"), target == Target.SRCP).addTo(div); formInputs.add(t("Send command to"),div); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/actions/SetContextTrain.java b/src/main/java/de/srsoftware/web4rail/actions/SetContextTrain.java index 36182d1..d28407d 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/SetContextTrain.java +++ b/src/main/java/de/srsoftware/web4rail/actions/SetContextTrain.java @@ -6,7 +6,7 @@ import java.util.List; import org.json.JSONObject; import de.srsoftware.web4rail.BaseClass; -import de.srsoftware.web4rail.DelayedExecution; +import de.srsoftware.web4rail.LoadCallback; import de.srsoftware.web4rail.moving.Train; import de.srsoftware.web4rail.tags.Fieldset; import de.srsoftware.web4rail.tags.Window; @@ -34,27 +34,19 @@ public class SetContextTrain extends Action { @Override public Action load(JSONObject json) { - super.load(json); - if (json.has(REALM_TRAIN)) { - Id trainId = Id.from(json,REALM_TRAIN); - if (isSet(trainId)) { - train = Train.get(trainId); - if (isNull(train)) new DelayedExecution(this) { - - @Override - public void execute() { - train = Train.get(trainId); - } - }; + if (json.has(REALM_TRAIN)) new LoadCallback() { + @Override + public void afterLoad() { + train = Train.get(Id.from(json,REALM_TRAIN)); } - } - return this; + }; + return super.load(json); } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Select train"),Train.selector(train, null)); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/actions/SetDisplayText.java b/src/main/java/de/srsoftware/web4rail/actions/SetDisplayText.java index 14dbc37..8353483 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/SetDisplayText.java +++ b/src/main/java/de/srsoftware/web4rail/actions/SetDisplayText.java @@ -7,7 +7,7 @@ import java.util.Map; import org.json.JSONObject; import de.srsoftware.web4rail.BaseClass; -import de.srsoftware.web4rail.DelayedExecution; +import de.srsoftware.web4rail.LoadCallback; import de.srsoftware.web4rail.tags.Fieldset; import de.srsoftware.web4rail.tags.Label; import de.srsoftware.web4rail.tags.Window; @@ -43,15 +43,12 @@ public class SetDisplayText extends TextAction{ @Override public Action load(JSONObject json) { - if (json.has(DISPLAY)) { - new DelayedExecution(this) { - - @Override - public void execute() { - display = (TextDisplay) plan.get(Id.from(json,DISPLAY), false); - }; - }; - } + if (json.has(DISPLAY)) new LoadCallback() { + @Override + public void afterLoad() { + display = (TextDisplay) plan.get(Id.from(json,DISPLAY), false); + } + }; return super.load(json); } @@ -62,9 +59,9 @@ public class SetDisplayText extends TextAction{ } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Display")+": "+(isNull(display) ? t("unset") : display),button(t("Select from plan"),Map.of(ACTION,ACTION_UPDATE,ASSIGN,DISPLAY))); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/actions/SetPower.java b/src/main/java/de/srsoftware/web4rail/actions/SetPower.java index e51ab3f..3de5b74 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/SetPower.java +++ b/src/main/java/de/srsoftware/web4rail/actions/SetPower.java @@ -7,10 +7,10 @@ import org.json.JSONObject; import de.srsoftware.tools.Tag; import de.srsoftware.web4rail.BaseClass; -import de.srsoftware.web4rail.ControlUnit; import de.srsoftware.web4rail.tags.Fieldset; import de.srsoftware.web4rail.tags.Radio; import de.srsoftware.web4rail.tags.Window; +import de.srsoftware.web4rail.threads.ControlUnit; public class SetPower extends Action{ @@ -58,14 +58,14 @@ public class SetPower extends Action{ } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { Tag div = new Tag("div"); new Radio(STATE, POWERCHANGE.ON, t("On"), pc == POWERCHANGE.ON).addTo(div); new Radio(STATE, POWERCHANGE.OFF, t("Off"), pc == POWERCHANGE.OFF).addTo(div); new Radio(STATE, POWERCHANGE.TOGGLE, t("Toggle"), pc == POWERCHANGE.TOGGLE).addTo(div); formInputs.add(t("Set state to"),div); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/actions/SetRelayOrSwitch.java b/src/main/java/de/srsoftware/web4rail/actions/SetRelayOrSwitch.java index c8aba80..8dd21ba 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/SetRelayOrSwitch.java +++ b/src/main/java/de/srsoftware/web4rail/actions/SetRelayOrSwitch.java @@ -8,7 +8,7 @@ import org.json.JSONObject; import de.srsoftware.tools.Tag; import de.srsoftware.web4rail.BaseClass; -import de.srsoftware.web4rail.DelayedExecution; +import de.srsoftware.web4rail.LoadCallback; import de.srsoftware.web4rail.tags.Fieldset; import de.srsoftware.web4rail.tags.Select; import de.srsoftware.web4rail.tags.Window; @@ -51,35 +51,27 @@ public class SetRelayOrSwitch extends Action { @Override public Action load(JSONObject json) { - super.load(json); - if (json.has(RELAY)) { - String relayId = json.getString(RELAY); - relayOrSwitch = BaseClass.get(new Id(relayId)); - if (isNull(relayOrSwitch)) new DelayedExecution(this) { - - @Override - public void execute() { - relayOrSwitch = BaseClass.get(new Id(relayId)); - }; - }; - } - if (json.has(SWITCH)) { - String relayId = json.getString(SWITCH); - relayOrSwitch = BaseClass.get(new Id(relayId)); - if (isNull(relayOrSwitch)) new DelayedExecution(this) { - - @Override - public void execute() { - relayOrSwitch = BaseClass.get(new Id(relayId)); - } - }; - } if (json.has(STATE)) state = json.getBoolean(STATE); - return this; + + if (json.has(RELAY)) new LoadCallback() { + @Override + public void afterLoad() { + relayOrSwitch = BaseClass.get(Id.from(json, RELAY)); + }; + }; + + if (json.has(SWITCH)) new LoadCallback() { + @Override + public void afterLoad() { + relayOrSwitch = BaseClass.get(Id.from(json, SWITCH)); + }; + }; + + return super.load(json); } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { Tag span = new Tag("span"); if (isSet(relayOrSwitch)) span.content(relayOrSwitch+NBSP); button(t("Select from plan"),Map.of(ACTION,ACTION_UPDATE,ASSIGN,Relay.class.getSimpleName())).addTo(span); @@ -96,7 +88,7 @@ public class SetRelayOrSwitch extends Action { } formInputs.add(t("Select state"),state); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/actions/SetSignal.java b/src/main/java/de/srsoftware/web4rail/actions/SetSignal.java index 02304f5..e776bed 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/SetSignal.java +++ b/src/main/java/de/srsoftware/web4rail/actions/SetSignal.java @@ -35,6 +35,7 @@ public class SetSignal extends Action { @Override public boolean fire(Context context,Object cause) { + if (context.invalidated()) return false; if (isNull(signal)) return false; return signal.state(state); } @@ -59,7 +60,7 @@ public class SetSignal extends Action { } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Signal")+": "+(isNull(signal) ? t("unset") : signal),button(t("Select from plan"),Map.of(ACTION,ACTION_UPDATE,ASSIGN,SIGNAL))); Select state = new Select(Signal.STATE); for (String st:Signal.knownStates) { @@ -68,7 +69,7 @@ public class SetSignal extends Action { } formInputs.add(t("Select state"),state); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/actions/SetSpeed.java b/src/main/java/de/srsoftware/web4rail/actions/SetSpeed.java index d0621a8..0d52c21 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/SetSpeed.java +++ b/src/main/java/de/srsoftware/web4rail/actions/SetSpeed.java @@ -27,6 +27,7 @@ public class SetSpeed extends Action{ @Override public boolean fire(Context context,Object cause) { + if (context.invalidated()) return false; if (isNull(context.train())) return false; context.train().setSpeed(speed); return true; @@ -47,9 +48,9 @@ public class SetSpeed extends Action{ } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Set speed to"),new Input(MAX_SPEED, speed).numeric().addTo(new Tag("span")).content(NBSP+speedUnit)); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } public int getSpeed() { diff --git a/src/main/java/de/srsoftware/web4rail/actions/SetTurnout.java b/src/main/java/de/srsoftware/web4rail/actions/SetTurnout.java index fe5f5fb..dcdf36f 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/SetTurnout.java +++ b/src/main/java/de/srsoftware/web4rail/actions/SetTurnout.java @@ -8,7 +8,7 @@ import org.json.JSONObject; import de.srsoftware.tools.Tag; import de.srsoftware.web4rail.BaseClass; -import de.srsoftware.web4rail.DelayedExecution; +import de.srsoftware.web4rail.LoadCallback; import de.srsoftware.web4rail.tags.Fieldset; import de.srsoftware.web4rail.tags.Select; import de.srsoftware.web4rail.tags.Window; @@ -26,14 +26,11 @@ public class SetTurnout extends Action { @Override public boolean fire(Context context,Object cause) { + if (context.invalidated()) return false; if (isNull(turnout)) return false; if (!turnout.state(state).succeeded()) return false; if (turnout.address() == 0) return true; - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } + sleep(1000); return true; } @@ -49,24 +46,19 @@ public class SetTurnout extends Action { @Override public Action load(JSONObject json) { - super.load(json); - Id turnoutId = json.has(TURNOUT) ? new Id(json.getString(TURNOUT)) : null; - if (isSet(turnoutId)) { - turnout = BaseClass.get(turnoutId); - if (isNull(turnout)) new DelayedExecution(this) { - - @Override - public void execute() { - turnout = BaseClass.get(turnoutId); - } - }; - } if (json.has(Turnout.STATE)) state = Turnout.State.valueOf(json.getString(Turnout.STATE)); - return this; + if (json.has(TURNOUT)) new LoadCallback() { + + @Override + public void afterLoad() { + turnout = BaseClass.get(Id.from(json, TURNOUT)); + } + }; + return super.load(json); } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Turnout")+": "+(isNull(turnout) ? t("unset") : turnout),button(t("Select from plan"),Map.of(ACTION,ACTION_UPDATE,ASSIGN,TURNOUT))); if (isSet(turnout)) { @@ -79,7 +71,7 @@ public class SetTurnout extends Action { formInputs.add(t("Select state"),select); } - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/actions/SplitTrain.java b/src/main/java/de/srsoftware/web4rail/actions/SplitTrain.java index 9ab466f..c3e383d 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/SplitTrain.java +++ b/src/main/java/de/srsoftware/web4rail/actions/SplitTrain.java @@ -47,9 +47,9 @@ public class SplitTrain extends Action { } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Split behind"),new Input(POSITION, position).numeric().addTo(new Tag("span")).content(t(" cars"))); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/actions/StartStopAuto.java b/src/main/java/de/srsoftware/web4rail/actions/StartStopAuto.java index 53be6a3..b1a8381 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/StartStopAuto.java +++ b/src/main/java/de/srsoftware/web4rail/actions/StartStopAuto.java @@ -24,7 +24,7 @@ public class StartStopAuto extends Action { public boolean fire(Context context,Object cause) { if (isNull(context.train())) return false; if (inverted) { - context.train().automatic(); + context.train().start(true); } else context.train().quitAutopilot(); return true; } @@ -41,13 +41,13 @@ public class StartStopAuto extends Action { } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { Tag radios = new Tag("div"); new Radio(INVERTED, "on", t("Start autopilot"), inverted).addTo(radios); new Radio(INVERTED, "off", t("Stop autopilot"), !inverted).addTo(radios); formInputs.add(t("Action"), radios); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/actions/SwitchFunction.java b/src/main/java/de/srsoftware/web4rail/actions/SwitchFunction.java index 62768c4..e00d773 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/SwitchFunction.java +++ b/src/main/java/de/srsoftware/web4rail/actions/SwitchFunction.java @@ -61,7 +61,7 @@ public class SwitchFunction extends Action { } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { Select selector = new Select(FUNCTION); for (int i=1; i<5;i++) { @@ -76,7 +76,7 @@ public class SwitchFunction extends Action { new Radio(EFFECT, OFF, t("Off"), effect == OFF).addTo(radioGroup); formInputs.add(t("Effect"),radioGroup); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/actions/TextAction.java b/src/main/java/de/srsoftware/web4rail/actions/TextAction.java index 5cce90d..1b43b1e 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/TextAction.java +++ b/src/main/java/de/srsoftware/web4rail/actions/TextAction.java @@ -46,9 +46,9 @@ public abstract class TextAction extends Action { } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Text"),new Input(TEXT, text)); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/actions/TriggerContact.java b/src/main/java/de/srsoftware/web4rail/actions/TriggerContact.java index 4c60474..d825322 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/TriggerContact.java +++ b/src/main/java/de/srsoftware/web4rail/actions/TriggerContact.java @@ -41,9 +41,9 @@ public class TriggerContact extends Action { } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Select contact")+": "+(isNull(contact) ? t("unset") : contact),button(t("Select from plan"),Map.of(ACTION,ACTION_UPDATE,ASSIGN,CONTACT))); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/actions/WaitForContact.java b/src/main/java/de/srsoftware/web4rail/actions/WaitForContact.java index 83df759..f5f9c83 100644 --- a/src/main/java/de/srsoftware/web4rail/actions/WaitForContact.java +++ b/src/main/java/de/srsoftware/web4rail/actions/WaitForContact.java @@ -8,10 +8,11 @@ import org.json.JSONObject; import de.srsoftware.tools.Tag; import de.srsoftware.web4rail.BaseClass; -import de.srsoftware.web4rail.DelayedExecution; +import de.srsoftware.web4rail.LoadCallback; import de.srsoftware.web4rail.tags.Fieldset; import de.srsoftware.web4rail.tags.Input; import de.srsoftware.web4rail.tags.Window; +import de.srsoftware.web4rail.threads.DelayedExecution; import de.srsoftware.web4rail.tiles.Contact; import de.srsoftware.web4rail.tiles.Contact.Listener; import de.srsoftware.web4rail.tiles.Tile; @@ -82,24 +83,21 @@ public class WaitForContact extends ActionList { @Override public Action load(JSONObject json) { - if (json.has(CONTACT)) { - String cid = json.getString(CONTACT); - contact = BaseClass.get(new Id(cid)); - if (isNull(contact)) new DelayedExecution(this) { - - @Override - public void execute() { - contact = BaseClass.get(new Id(cid)); - } - }; - } if (json.has(TIMEOUT)) timeout = json.getInt(TIMEOUT); if (json.has(TIMEOUT_ACTIONS)) timeoutActions.load(json.getJSONObject(TIMEOUT_ACTIONS)); + + if (json.has(CONTACT)) new LoadCallback() { + + @Override + public void afterLoad() { + contact = BaseClass.get(Id.from(json, CONTACT)); + } + }; return super.load(json); } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Contact")+": "+(isNull(contact) ? t("unset") : contact),button(t("Select from plan"),Map.of(ACTION,ACTION_UPDATE,ASSIGN,CONTACT))); formInputs.add(t("Timeout"),new Input(TIMEOUT,timeout).numeric().addTo(new Tag("span")).content(NBSP+"ms")); @@ -108,7 +106,7 @@ public class WaitForContact extends ActionList { timeoutActions.list().addTo(fieldset); postForm.add(fieldset); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/conditions/BlockFree.java b/src/main/java/de/srsoftware/web4rail/conditions/BlockFree.java index 364d60b..4784e60 100644 --- a/src/main/java/de/srsoftware/web4rail/conditions/BlockFree.java +++ b/src/main/java/de/srsoftware/web4rail/conditions/BlockFree.java @@ -7,7 +7,7 @@ import java.util.Map; import org.json.JSONObject; import de.srsoftware.web4rail.BaseClass; -import de.srsoftware.web4rail.DelayedExecution; +import de.srsoftware.web4rail.LoadCallback; import de.srsoftware.web4rail.tags.Fieldset; import de.srsoftware.web4rail.tags.Window; import de.srsoftware.web4rail.tiles.Block; @@ -25,7 +25,7 @@ public class BlockFree extends Condition { @Override public boolean fulfilledBy(Context context) { - return block.isFreeFor(null) != inverted; + return block.isFreeFor(context) != inverted; } @Override @@ -34,26 +34,19 @@ public class BlockFree extends Condition { } public Condition load(JSONObject json) { - super.load(json); - if (json.has(BLOCK)) { - Id bid = new Id(json.getString(BLOCK)); - block(BaseClass.get(bid)); - if (isNull(block)) { - new DelayedExecution(this) { - @Override - public void execute() { - block(BaseClass.get(bid)); - } - }; + if (json.has(BLOCK)) new LoadCallback() { + @Override + public void afterLoad() { + block(BaseClass.get(Id.from(json, BLOCK))); } - } - return this; + }; + return super.load(json); } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Block")+": "+(isNull(block) ? t("unset") : block),button(t("Select from plan"),Map.of(ACTION,ACTION_UPDATE,ASSIGN,BLOCK))); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/conditions/CarInTrain.java b/src/main/java/de/srsoftware/web4rail/conditions/CarInTrain.java index 532cbd5..a037d14 100644 --- a/src/main/java/de/srsoftware/web4rail/conditions/CarInTrain.java +++ b/src/main/java/de/srsoftware/web4rail/conditions/CarInTrain.java @@ -38,10 +38,10 @@ public class CarInTrain extends Condition { } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Select car"),Car.selector(car, null)); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/conditions/CarOrientation.java b/src/main/java/de/srsoftware/web4rail/conditions/CarOrientation.java index 9463942..8ef19fb 100644 --- a/src/main/java/de/srsoftware/web4rail/conditions/CarOrientation.java +++ b/src/main/java/de/srsoftware/web4rail/conditions/CarOrientation.java @@ -59,7 +59,7 @@ public class CarOrientation extends Condition { } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Select car"),Car.selector(isSet(car) ? car : t("Car of train"), null)); formInputs.add(t("If car of train: inspect car number"),new Input(POSITION, position).numeric().addTo(new Tag("span")).content(NBSP+"("+t("Use negative number to count from end.")+")")); @@ -67,7 +67,7 @@ public class CarOrientation extends Condition { new Radio(ORIENTATION, "f", t("forward"), orientation).addTo(radioGroup); new Radio(ORIENTATION, "r", t("revers"), !orientation).addTo(radioGroup); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/conditions/Condition.java b/src/main/java/de/srsoftware/web4rail/conditions/Condition.java index 6dab371..d10d46e 100644 --- a/src/main/java/de/srsoftware/web4rail/conditions/Condition.java +++ b/src/main/java/de/srsoftware/web4rail/conditions/Condition.java @@ -142,9 +142,9 @@ public abstract class Condition extends BaseClass { } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { inversionOption(formInputs); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } public static Select selector() { diff --git a/src/main/java/de/srsoftware/web4rail/conditions/ConditionList.java b/src/main/java/de/srsoftware/web4rail/conditions/ConditionList.java index 8f4467b..435ef4c 100644 --- a/src/main/java/de/srsoftware/web4rail/conditions/ConditionList.java +++ b/src/main/java/de/srsoftware/web4rail/conditions/ConditionList.java @@ -114,10 +114,10 @@ public class ConditionList extends Condition implements Iterable{ } @Override - public Window properties() { + public Window properties(String...errors) { BaseClass parent = parent(); - if (isSet(parent)) return parent.properties(); - return super.properties(); + if (isSet(parent)) return parent.properties(errors); + return super.properties(errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/conditions/RouteEndBlock.java b/src/main/java/de/srsoftware/web4rail/conditions/RouteEndBlock.java index 2f6676c..6451f8e 100644 --- a/src/main/java/de/srsoftware/web4rail/conditions/RouteEndBlock.java +++ b/src/main/java/de/srsoftware/web4rail/conditions/RouteEndBlock.java @@ -7,7 +7,7 @@ import java.util.Map; import org.json.JSONObject; import de.srsoftware.web4rail.BaseClass; -import de.srsoftware.web4rail.DelayedExecution; +import de.srsoftware.web4rail.LoadCallback; import de.srsoftware.web4rail.Route; import de.srsoftware.web4rail.tags.Fieldset; import de.srsoftware.web4rail.tags.Window; @@ -38,28 +38,21 @@ public class RouteEndBlock extends Condition{ } public Condition load(JSONObject json) { - super.load(json); - Id bid = new Id(json.getString(BLOCK)); - Block block = BaseClass.get(bid); - if (isSet(block)) { - block(block); - } else { - new DelayedExecution(this) { - - @Override - public void execute() { - block(BaseClass.get(bid)); - } - }; - } + new LoadCallback() { + + @Override + public void afterLoad() { + block(BaseClass.get(Id.from(json, BLOCK))); + } + }; - return this; + return super.load(json); } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Block")+": "+(isNull(block) ? t("unset") : block),button(t("Select from plan"),Map.of(ACTION,ACTION_UPDATE,ASSIGN,BLOCK))); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/conditions/SwitchIsOn.java b/src/main/java/de/srsoftware/web4rail/conditions/SwitchIsOn.java index e43879e..54eba23 100644 --- a/src/main/java/de/srsoftware/web4rail/conditions/SwitchIsOn.java +++ b/src/main/java/de/srsoftware/web4rail/conditions/SwitchIsOn.java @@ -8,7 +8,7 @@ import org.json.JSONObject; import de.srsoftware.tools.Tag; import de.srsoftware.web4rail.BaseClass; -import de.srsoftware.web4rail.DelayedExecution; +import de.srsoftware.web4rail.LoadCallback; import de.srsoftware.web4rail.tags.Fieldset; import de.srsoftware.web4rail.tags.Radio; import de.srsoftware.web4rail.tags.Window; @@ -44,27 +44,20 @@ public class SwitchIsOn extends Condition { } public Condition load(JSONObject json) { - super.load(json); - if (json.has(SWITCH)) { - swtch = BaseClass.get(new Id(json.getString(SWITCH))); - if (isNull(swtch)) { - new DelayedExecution(this) { - - @Override - public void execute() { - swtch = BaseClass.get(new Id(json.getString(SWITCH))); - } - }; + if (json.has(SWITCH)) new LoadCallback() { + @Override + public void afterLoad() { + swtch = BaseClass.get(Id.from(json,SWITCH)); } - } - return this; + }; + return super.load(json); } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Select switch")+": "+(isNull(swtch) ? t("unset") : swtch),button(t("Select from plan"),Map.of(ACTION,ACTION_UPDATE,ASSIGN,SWITCH))); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/conditions/TrainHasTag.java b/src/main/java/de/srsoftware/web4rail/conditions/TrainHasTag.java index 43206f6..c9f6dcc 100644 --- a/src/main/java/de/srsoftware/web4rail/conditions/TrainHasTag.java +++ b/src/main/java/de/srsoftware/web4rail/conditions/TrainHasTag.java @@ -46,9 +46,9 @@ public class TrainHasTag extends Condition { } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Tag"),new Input(TAG, tag == null ? "" : tag)); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/conditions/TrainLength.java b/src/main/java/de/srsoftware/web4rail/conditions/TrainLength.java index 1b3f084..284cb84 100644 --- a/src/main/java/de/srsoftware/web4rail/conditions/TrainLength.java +++ b/src/main/java/de/srsoftware/web4rail/conditions/TrainLength.java @@ -34,9 +34,9 @@ public class TrainLength extends Condition { } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Maximum train length"),new Input(LENGTH, treshold).numeric().addTo(new Tag("span")).content(lengthUnit)); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/conditions/TrainSelect.java b/src/main/java/de/srsoftware/web4rail/conditions/TrainSelect.java index 5ca364a..100d329 100644 --- a/src/main/java/de/srsoftware/web4rail/conditions/TrainSelect.java +++ b/src/main/java/de/srsoftware/web4rail/conditions/TrainSelect.java @@ -34,9 +34,9 @@ public class TrainSelect extends Condition { } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Select train")+":",Train.selector(train, null)); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/conditions/TrainSpeed.java b/src/main/java/de/srsoftware/web4rail/conditions/TrainSpeed.java index 885141f..d0586ed 100644 --- a/src/main/java/de/srsoftware/web4rail/conditions/TrainSpeed.java +++ b/src/main/java/de/srsoftware/web4rail/conditions/TrainSpeed.java @@ -33,9 +33,9 @@ public class TrainSpeed extends Condition { } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Train speed"),new Input(SPEED, treshold).numeric().addTo(new Tag("span")).content(speedUnit)); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/conditions/TrainWasInBlock.java b/src/main/java/de/srsoftware/web4rail/conditions/TrainWasInBlock.java index ed67d39..61d9ce3 100644 --- a/src/main/java/de/srsoftware/web4rail/conditions/TrainWasInBlock.java +++ b/src/main/java/de/srsoftware/web4rail/conditions/TrainWasInBlock.java @@ -48,10 +48,10 @@ public class TrainWasInBlock extends Condition { } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Block")+": "+(isNull(block) ? t("block from context") : block),button(t("Select from plan"),Map.of(ACTION,ACTION_UPDATE,ASSIGN,BLOCK))); formInputs.add(t("Seek in last"), new Input(COUNT, count).numeric().addTo(new Tag("span")).content(NBSP+t("blocks of train"))); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/moving/Car.java b/src/main/java/de/srsoftware/web4rail/moving/Car.java index b02951e..731f0f4 100644 --- a/src/main/java/de/srsoftware/web4rail/moving/Car.java +++ b/src/main/java/de/srsoftware/web4rail/moving/Car.java @@ -18,9 +18,7 @@ import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import de.keawe.tools.translations.Translation; import de.srsoftware.tools.Tag; -import de.srsoftware.web4rail.Application; import de.srsoftware.web4rail.BaseClass; import de.srsoftware.web4rail.Plan; import de.srsoftware.web4rail.tags.Button; @@ -245,7 +243,7 @@ public class Car extends BaseClass implements Comparable{ } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Name"),new Input(NAME,name)); formInputs.add(t("Stock ID"),new Input(STOCK_ID,stockId)); formInputs.add(t("Length"),new Input(LENGTH,length).attr("type", "number").addTo(new Tag("span")).content(NBSP+lengthUnit)); @@ -257,7 +255,7 @@ public class Car extends BaseClass implements Comparable{ if (train != null) formInputs.add(t("Train"), train.link()); formInputs.add(t("Current orientation"),new Tag("span").content(orientation ? t("forward") : t("reverse"))); - return super.properties(preForm,formInputs,postForm); + return super.properties(preForm,formInputs,postForm,errors); } @Override @@ -273,10 +271,6 @@ public class Car extends BaseClass implements Comparable{ file.close(); } - protected static String t(String txt, Object...fills) { - return Translation.get(Application.class, txt, fills); - } - public TreeSet tags() { return new TreeSet(tags); } diff --git a/src/main/java/de/srsoftware/web4rail/moving/Locomotive.java b/src/main/java/de/srsoftware/web4rail/moving/Locomotive.java index b474cc5..f96e9fd 100644 --- a/src/main/java/de/srsoftware/web4rail/moving/Locomotive.java +++ b/src/main/java/de/srsoftware/web4rail/moving/Locomotive.java @@ -142,7 +142,7 @@ public class Locomotive extends Car implements Constants,Device{ par.addTo(fieldset); Tag direction = new Tag("p"); - if ((isSet(train) && (train.speed > 0 || isSet(train.route()))) || (isSet(loco) && loco.speed > 0)) { + if ((isSet(train) && train.isStoppable()) || (isSet(loco) && loco.speed > 0)) { params.put(ACTION, ACTION_STOP); new Button(t("Stop"),params).clazz(ACTION_STOP).addTo(direction); } @@ -153,7 +153,7 @@ public class Locomotive extends Car implements Constants,Device{ if (isSet(train)) { Block currentBlock = train.currentBlock(); if (isSet(currentBlock)) { - if (isNull(train.route())) { + if (!train.isStoppable()) { params.put(ACTION, ACTION_START); new Button(t("depart"),params).addTo(direction); } @@ -338,7 +338,7 @@ public class Locomotive extends Car implements Constants,Device{ } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { preForm.add(cockpit(this)); Tag div = new Tag("div"); for (Protocol proto : Protocol.values()) { @@ -347,7 +347,7 @@ public class Locomotive extends Car implements Constants,Device{ formInputs.add(t("Protocol"),div); formInputs.add(t("Address"),new Input(ADDRESS, address).numeric()); postForm.add(programming()); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } private void queue() { diff --git a/src/main/java/de/srsoftware/web4rail/moving/Train.java b/src/main/java/de/srsoftware/web4rail/moving/Train.java index 891b55a..f1a920d 100644 --- a/src/main/java/de/srsoftware/web4rail/moving/Train.java +++ b/src/main/java/de/srsoftware/web4rail/moving/Train.java @@ -10,7 +10,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.SortedSet; @@ -23,12 +22,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import de.srsoftware.tools.Tag; -import de.srsoftware.web4rail.Application; import de.srsoftware.web4rail.BaseClass; -import de.srsoftware.web4rail.PathFinder; +import de.srsoftware.web4rail.LoadCallback; import de.srsoftware.web4rail.Plan; import de.srsoftware.web4rail.Plan.Direction; -import de.srsoftware.web4rail.Range; import de.srsoftware.web4rail.Route; import de.srsoftware.web4rail.tags.Button; import de.srsoftware.web4rail.tags.Checkbox; @@ -39,6 +36,9 @@ import de.srsoftware.web4rail.tags.Label; import de.srsoftware.web4rail.tags.Select; import de.srsoftware.web4rail.tags.Table; import de.srsoftware.web4rail.tags.Window; +import de.srsoftware.web4rail.threads.BrakeProcess; +import de.srsoftware.web4rail.threads.DelayedExecution; +import de.srsoftware.web4rail.threads.RoutePrepper; import de.srsoftware.web4rail.tiles.Block; import de.srsoftware.web4rail.tiles.Contact; import de.srsoftware.web4rail.tiles.Tile; @@ -54,15 +54,16 @@ public class Train extends BaseClass implements Comparable { private static final String TRACE = "trace"; private static final String NAME = "name"; + + public static int defaultEndSpeed; private String name = null; private static final String ROUTE = "route"; private Route route; - private static final String DIRECTION = "direction"; private Direction direction; - + private boolean autopilot; private static final String PUSH_PULL = "pushPull"; public boolean pushPull = false; @@ -84,59 +85,20 @@ public class Train extends BaseClass implements Comparable { private boolean f1,f2,f3,f4; private Block currentBlock,destination = null; - LinkedList trace = new LinkedList(); + HashSet trace = new HashSet(); private Vector lastBlocks = new Vector(); public int speed = 0; - private Autopilot autopilot = null; - private Route nextRoute; private static final String SHUNTING = "shunting"; private boolean shunting = false; + private RoutePrepper routePrepper = null; + + private HashSet stuckTrace = null; + + private Route nextPreparedRoute; + + private BrakeProcess brake; - private boolean reserving; // used to prevent recursive calls of reserveNext - - private class Autopilot extends Thread{ - boolean stop = false; - int waitTime = 100; - - public Autopilot() { - setName(Application.threadName("Autopilot("+Train.this+")")); - start(); - } - - @Override - public void run() { - try { - stop = false; - while (true) { - if (isNull(route)) { - Thread.sleep(waitTime); - if (waitTime > 100) waitTime /=2; - if (stop) break; - if (isNull(route)) { // may have been set by start action in between - String message = Train.this.start(); - if (isSet(route)) { - LOG.debug("{}.start called, route now is {}",Train.this,route); - plan.stream(message); - //if (isSet(destination)) Thread.sleep(1000); // limit load on PathFinder - } else { - LOG.debug(message); - waitTime = 1000; // limit load on PathFinder - } - } - } else { - if (stop) break; - Thread.sleep(250); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - autopilot = null; - if (isSet(currentBlock)) plan.place(currentBlock); - } - } - 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!"); @@ -156,7 +118,7 @@ public class Train extends BaseClass implements Comparable { case ACTION_ADD: return train.addCar(params); case ACTION_AUTO: - return train.automatic(); + return train.start(true); case ACTION_CONNECT: return train.connect(params); case ACTION_DROP: @@ -176,14 +138,13 @@ public class Train extends BaseClass implements Comparable { case ACTION_PROPS: return train.properties(); case ACTION_QUIT: - return train.quitAutopilot(); + return train.properties(train.quitAutopilot()); case ACTION_REVERSE: return train.reverse().properties(); case ACTION_SLOWER10: return train.slower(10); case ACTION_START: - train.start(); - return train.properties(); + return train.properties(train.start(false)); case ACTION_STOP: return train.stopNow(); case ACTION_TIMES: @@ -196,10 +157,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); @@ -210,22 +179,6 @@ 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 String automatic() { - if (isNull(autopilot)) { - autopilot = new Autopilot(); - if (isSet(currentBlock)) plan.place(currentBlock); - } - return t("{} now in auto-mode",this); - } - private Fieldset blockHistory() { Fieldset fieldset = new Fieldset(t("Last blocks")).id("props-history"); Tag list = new Tag("ol"); @@ -323,7 +276,7 @@ public class Train extends BaseClass implements Comparable { if (isSet(currentBlock)) { Tag ul = new Tag("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; Tag li = new Tag("li").addTo(ul); tr.link().addTo(li); @@ -352,6 +305,12 @@ public class Train extends BaseClass implements Comparable { return properties(); } + public Context contact(Contact contact) { + if (isNull(route)) return new Context(contact).train(this); + return updateTrace(route.contact(contact)); + } + + public void coupleWith(Train parkingTrain,boolean swap) { if (isSet(direction) && isSet(parkingTrain.direction) && parkingTrain.direction != direction) parkingTrain.turn(); if (swap) { @@ -390,40 +349,10 @@ public class Train extends BaseClass implements Comparable { return properties(); } - public Block destination() { - //LOG.debug("{}.destination()",this); - if (isNull(destination)) { - String destTag = destinationTag(); - //LOG.debug("→ processing \"{}\"...",destTag); - if (isSet(destTag)) { - destTag = destTag.split(DESTINATION_PREFIX)[1]; - //LOG.debug("....processing \"{}\"…",destTag); - for (int i=destTag.length()-1; i>0; i--) { - switch (destTag.charAt(i)) { - case FLAG_SEPARATOR: - destTag = destTag.substring(0,i); - i=0; - break; - case SHUNTING_FLAG: - //LOG.debug("....enabled shunting option"); - shunting = true; - break; - } - } - - Block block = BaseClass.get(new Id(destTag)); - if (isSet(block)) destination(block); - } - }// else LOG.debug("→ heading towards {}",destination); + public Block destination(){ return destination; } - public Train destination(Block dest) { - LOG.debug("destination({})",dest); - destination = dest; - return this; - } - public String destinationTag() { for (String tag : tags()) { // check, if endBlock is in train's destinations if (tag.startsWith(DESTINATION_PREFIX)) return tag; @@ -431,12 +360,11 @@ public class Train extends BaseClass implements Comparable { return null; } - - public String directedName() { + public String directedName(Direction dir) { String result = name(); - String mark = isSet(autopilot) ? "ⓐ" : ""; - if (isNull(direction)) return result; - switch (direction) { + String mark = ""; //isSet(autopilot) ? "ⓐ" : ""; + if (isNull(dir)) return result; + switch (dir) { case NORTH: case WEST: return '←'+mark+result; @@ -444,7 +372,7 @@ public class Train extends BaseClass implements Comparable { case EAST: return result+mark+'→'; } - return result; + return mark+result; } public Direction direction() { @@ -466,10 +394,56 @@ public class Train extends BaseClass implements Comparable { return properties(); } + public boolean drop(Route oldRoute) { + if (isNull(route)) return true; + if (route != oldRoute) return false; + route = null; + return true; + } + + public void dropTrace() { - while (!trace.isEmpty()) trace.removeFirst().setTrain(null); + trace.forEach(tile -> tile.free(this)); + trace.clear(); } + private BrakeProcess endBrake() { + if (isNull(brake)) return null; + try { + return brake.end(); + } finally { + brake = null; + } + } + + public void endRoute(Block endBlock, Direction endDirection) { + BrakeProcess brake = endBrake(); + if (isSet(brake)) brake.updateTime(); + Integer waitTime = route.waitTime(); + nextPreparedRoute = route.dropNextPreparedRoute(); + if ((!autopilot)|| isNull(nextPreparedRoute) || (isSet(waitTime) && waitTime > 0)) setSpeed(0); + route = null; + direction = endDirection; + endBlock.add(this, direction); + currentBlock = endBlock; + trace.add(endBlock); + stuckTrace = null; + if (autopilot) { + if (isNull(waitTime) || waitTime == 0) { + start(false); + } else if (isSet(waitTime) && waitTime > 0) { + new DelayedExecution(waitTime,this) { + + @Override + public void execute() { + if (autopilot) Train.this.start(false); + } + }; + plan.stream(t("{} waiting {} secs",this,(int)(waitTime/1000))); + } + } + } + private Tag faster(int steps) { setSpeed(speed+steps); return properties(); @@ -496,6 +470,11 @@ public class Train extends BaseClass implements Comparable { } return false; } + + public boolean hasNextPreparedRoute() { + return isSet(nextPreparedRoute) || (isSet(route) && isSet(route.getNextPreparedRoute())); + } + public Train heading(Direction dir) { LOG.debug("{}.heading({})",this,dir); @@ -508,7 +487,13 @@ public class Train extends BaseClass implements Comparable { return shunting; } - + public boolean isStoppable() { + if (speed > 0) return true; + if (isSet(routePrepper)) return true; + if (isSet(route)) return true; + return false; + } + public JSONObject json() { JSONObject json = super.json(); json.put(PUSH_PULL, pushPull); @@ -568,12 +553,29 @@ 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(LOCOS)) { // for downward compatibility for (Object id : json.getJSONArray(LOCOS)) add(BaseClass.get(new Id(""+id))); } for (Object id : json.getJSONArray(CARS)) add(BaseClass.get(new Id(""+id))); + new LoadCallback() { + @Override + public void afterLoad() { + if (json.has(TRACE)) json.getJSONArray(TRACE).forEach(elem -> { + Tile tile = plan.get(new Id(elem.toString()), false); + if (tile instanceof Block) { + ((Block)tile).add(Train.this, direction); + } else if (tile.setTrain(Train.this)); + trace.add(tile); + }); + if (json.has(BLOCK)) {// do not move this up! during set, other fields will be referenced! + currentBlock = (Block) plan.get(Id.from(json, BLOCK), false); + if (isSet(currentBlock)) { + currentBlock.setTrain(Train.this); + currentBlock.add(Train.this, direction); + } + } + } + }; super.load(json); return this; } @@ -594,8 +596,8 @@ public class Train extends BaseClass implements Comparable { String.join(", ", train.tags()), train.route, isSet(train.currentBlock) ? train.currentBlock.link() : null, - train.destination(), - t(isSet(train.autopilot)?"On":"Off") + null, // TODO: show destination here! + null // TODO: show state of autopilot here ); }); table.addTo(win); @@ -658,17 +660,13 @@ public class Train extends BaseClass implements Comparable { return this; } - public Route nextRoute() { - return nextRoute; - } - public boolean onTrace(Tile t) { return trace.contains(t); } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { Tag propList = new Tag("ul").clazz("proplist"); if (isSet(currentBlock)) currentBlock.button(currentBlock.toString()).addTo(new Tag("li").content(t("Current location")+COL)).addTo(propList); @@ -718,20 +716,16 @@ public class Train extends BaseClass implements Comparable { postForm.add(blockHistory()); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } - public Object quitAutopilot() { - if (isSet(nextRoute)) { - nextRoute.reset(); - nextRoute = null; - } - if (isSet(autopilot)) { - autopilot.stop = true; - autopilot = null; - if (isSet(currentBlock)) plan.place(currentBlock); - return t("{} stopping at next block.",this); - } else return t("autopilot not active."); + public String quitAutopilot() { + if (isSet(routePrepper)) routePrepper.stop(); + try { + return autopilot ? t("Autopilot disabled") : t("Autopilot already was disabled!"); + } finally { + autopilot = false; + } } @Override @@ -752,9 +746,10 @@ public class Train extends BaseClass implements Comparable { public void removeChild(BaseClass child) { LOG.debug("{}.removeChild({})",this,child); if (child == route) route = null; - if (child == nextRoute) nextRoute = null; + //if (child == nextRoute) nextRoute = null; // TODO if (child == currentBlock) currentBlock = null; if (child == destination) destination = null; + if (child == routePrepper) routePrepper.stop(); cars.remove(child); trace.remove(child); super.removeChild(child); @@ -765,36 +760,6 @@ public class Train extends BaseClass implements Comparable { return tags().iterator(); } - public void reserveNext() { - if (reserving) return; - LOG.debug("{}.reserveNext()",this); - if (isSet(nextRoute)) { - LOG.debug("Train already has next route: {}",nextRoute); - return; - } - Context context = new Context(this).route(route).block(route.endBlock()).direction(route.endDirection); - Route newRoute = PathFinder.chooseRoute(context); - if (isNull(newRoute)) { - LOG.debug("{}.reserveNext() found no available route!",this); - return; - } - reserving = true; - LOG.debug("next route: {}",newRoute); - newRoute.set(context); - boolean error = !newRoute.lockIgnoring(route); - error = error || !newRoute.prepare(); - - if (error) { - newRoute.reset(); // may unlock tiles belonging to the current route. - LOG.debug("failed to prepare new route {}",newRoute); - route.lock(); // corrects unlocked tiles of nextRoute - } else { - nextRoute = newRoute; - LOG.debug("prepared next route: {}",nextRoute); - } - reserving = false; - } - /** * This turns the train as if it went through a loop. Example: * before: CabCar→ MiddleCar→ Loco→ @@ -803,32 +768,18 @@ public class Train extends BaseClass implements Comparable { public Train reverse() { LOG.debug("train.reverse();"); - if (isSet(direction)) { - direction = direction.inverse(); - reverseTrace(); + if (isSet(direction)) direction = direction.inverse(); + if (isSet(currentBlock)) { + currentBlock.set(this,direction); + plan.place(currentBlock); } - if (isSet(currentBlock)) plan.place(currentBlock); return this; } - private void reverseTrace() { - LinkedList reversed = new LinkedList(); - LOG.debug("Trace: {}",trace); - while (!trace.isEmpty()) reversed.addFirst(trace.removeFirst()); - trace = reversed; - LOG.debug("reversed: {}",trace); - } - public Route route() { return route; } - public Train route(Route newRoute) { - route = newRoute; - if (isNull(route)) shunting = false; // quit shunting on finish route - return this; - } - public static void saveAll(String filename) throws IOException { BufferedWriter file = new BufferedWriter(new FileWriter(filename)); for (Train train:BaseClass.listElements(Train.class)) file.write(train.json()+"\n"); @@ -852,7 +803,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)) { + if (newBlock == currentBlock) return; + currentBlock.free(this); + } currentBlock = newBlock; if (isSet(currentBlock)) { currentBlock.setTrain(this); @@ -863,19 +817,19 @@ public class Train extends BaseClass implements Comparable { private Object setDestination(HashMap params) { String dest = params.get(DESTINATION); - if (isNull(dest)) return t("No destination supplied!"); + if (isNull(dest)) return properties(t("No destination supplied!")); if (dest.isEmpty()) { destination = null; return properties(); } Tile tile = plan.get(new Id(dest), true); - if (isNull(tile)) return t("Tile {} not known!",dest); + if (isNull(tile)) return properties(t("Tile {} not known!",dest)); if (tile instanceof Block) { destination = (Block) tile; - automatic(); + start(false); return t("{} now heading for {}",this,destination); } - return t("{} is not a block!",tile); + return properties(t("{} is not a block!",tile)); } public Object setFunction(int num, boolean active) { @@ -904,6 +858,11 @@ public class Train extends BaseClass implements Comparable { return properties(); } + public Train setRoute(Route newRoute) { + route = newRoute; + return this; + } + public void setSpeed(int newSpeed) { LOG.debug("{}.setSpeed({})",this,newSpeed); speed = Math.min(newSpeed,maxSpeed()); @@ -911,27 +870,7 @@ public class Train extends BaseClass implements Comparable { cars.stream().filter(c -> c instanceof Locomotive).forEach(car -> ((Locomotive)car).setSpeed(speed)); plan.stream(t("Set {} to {} {}",this,speed,speedUnit)); } - - public void setTrace(LinkedList newTrace) { - LOG.debug("{}.setTrace({})",this,newTrace); - LOG.debug("old trace: {}",trace); - trace.removeAll(newTrace); - for (Tile tile : trace) tile.setTrain(null); - trace = newTrace; - for (Tile tile : trace) tile.setTrain(this); - - LOG.debug("new trace of {}: {}",this,trace); - } - - public void setWaitTime(Range waitTime) { - if (isNull(autopilot)) return; - autopilot.waitTime = waitTime.random(); - String msg = t("{} waiting {} secs...",this,autopilot.waitTime/1000d); - LOG.debug(msg); - plan.stream(msg); - } - private Tag slower(int steps) { setSpeed(speed-steps); return properties(); @@ -951,111 +890,84 @@ public class Train extends BaseClass implements Comparable { } else if (remaining.name.length()+car.name().length()<30){ remaining.name += ", "+car.name(); } - } else { - LOG.debug("Skipping {}",cars.get(i)); - } + } else LOG.debug("Skipping {}",cars.get(i)); } if (remaining.cars.isEmpty()) return false; remaining.direction = this.direction; this.name = null; - currentBlock.add(remaining); + currentBlock.add(remaining,direction); remaining.currentBlock = currentBlock; plan.place(currentBlock); return true; } - public String start() throws IOException { - LOG.debug("{}.start()",this); - if (isNull(currentBlock)) return t("{} not in a block",this); - if (maxSpeed() == 0) return t("Train has maximum speed of 0 {}, cannot go!",speedUnit); - if (isSet(route)) route.reset(); // reset route previously chosen - - String error = null; - if (isSet(nextRoute)) { - LOG.debug("{}.nextRoute = {}",this,nextRoute); - route = nextRoute; - if (!route.lock()) return t("Was not able to lock {}",route); - nextRoute = null; - route.set(new Context(this).block(currentBlock).direction(direction)); - } else { - if (reserving) return t("Route chooser already active"); - Context context = new Context(this).block(currentBlock).direction(direction); - route = PathFinder.chooseRoute(context); - if (isNull(route)) return t("No free routes from {}",currentBlock); - LOG.debug("Chosen route: {}",route); - if (!route.lock()) error = t("Was not able to lock {}",route); - route.set(context); - if (isNull(error) && !route.prepare()) error = t("Was not able to fire all setup actions of route!"); + public String start(boolean auto) { + autopilot |= auto; + if (isSet(nextPreparedRoute) && nextPreparedRoute.start()) { + nextPreparedRoute = null; + return null; } - if (isNull(route)) return t("Route cancelled"); // route may have been canceled in between - if (isNull(error) && direction != route.startDirection) turn(); + if (isSet(routePrepper)) return t("Already searching route for {}",this); + routePrepper = new RoutePrepper(new Context(this).block(currentBlock).direction(direction)); - if (isNull(error) && !route.start(this)) error = t("Was not able to assign {} to {}!",this,route); - if (isSet(error)) { - LOG.debug("{}.start:error = {}",this,error); - route.reset(); - route = null; - return error; - } - startSimulation(); - - String res = t("Started {}",this); - plan.stream(res); - return res; + routePrepper.onRoutePrepared(() -> { + routePrepper.route().start(); + routePrepper = null; + plan.stream(t("Started {}",Train.this)); + }); + + routePrepper.onFail(() -> { + Route failedRoute = routePrepper.route(); + routePrepper = null; + if (isSet(failedRoute)) failedRoute.reset(); + LOG.debug("Starting {} failed due to unavailable route!",this); + if (autopilot) new DelayedExecution(250,this) { + + @Override + public void execute() { + if (autopilot) { + Train.this.start(false); + } + } + }; + }); + + routePrepper.start(); + + return null; } - + public static void startAll() { LOG.debug("Train.startAll()"); - for (Train train : BaseClass.listElements(Train.class)) LOG.info(train.automatic()); + for (Train train : BaseClass.listElements(Train.class)) LOG.info(train.start(true)); } - private void startSimulation() { - LOG.debug("{}.startSimulation()",this); - for (Contact contact : route.contacts()) { - if (contact.addr() != 0) { - LOG.debug("{}.startSimulation aborted!",this); - return; // simulate train only when all contacts are non-physical - } - } - try { - Thread.sleep(1000); - plan.stream(t("Simulating movement of {}...",this)); - Thread simulation = new Thread() { - public void run() { - for (Tile tile : route.path()) { - if (isNull(route)) break; - try { - if (tile instanceof Contact) { - Contact contact = (Contact) tile; - contact.activate(true); - sleep(200); - contact.activate(false); - } - sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - }; - }; - simulation.setName(Application.threadName("Simulation("+Train.this+")")); - simulation.start(); - } catch (InterruptedException e) { - e.printStackTrace(); - } + public void startBrake() { + LOG.debug("{}.startBrake()",this); + if (autopilot && isSet(nextPreparedRoute)) return; + brake = new BrakeProcess(this); } - public Object stopNow() { + public Window stopNow() { + endBrake(); + setSpeed(0); quitAutopilot(); if (isSet(route)) { + stuckTrace = new HashSet(); + for (Tile tile : route.path()) { // collect occupied tiles of route. stuckTrace is considered during next route search + if (trace.contains(tile)) stuckTrace.add(tile); + } route.reset(); route = null; } - setSpeed(0); - return properties(); } + + public HashSet stuckTrace() { + return stuckTrace; + } + public SortedSet tags() { TreeSet list = new TreeSet(tags); @@ -1083,10 +995,6 @@ public class Train extends BaseClass implements Comparable { return name(); } - public Tile traceHead() { - return trace == null || trace.isEmpty() ? null : trace.getFirst(); - } - /** * this inverts the direction the train is heading to. Example: * before: CabCar→ MiddleCar→ Loco→ @@ -1095,8 +1003,9 @@ 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); + reverse(cars); return reverse(); } @@ -1116,7 +1025,51 @@ public class Train extends BaseClass implements Comparable { return properties(); } + public Context updateTrace(Context context) { + LOG.debug("updateTrace({})",context); + Tile from = context.tile(); + if (isNull(from)) from = context.contact(); + if (isNull(from)) { + LOG.debug("no starting point for trace given in {}",context); + return context; + } + trace.add(from); + Route route = context.route(); + LOG.debug("Route: {}",route); + if (isNull(route)) return context; + Vector reversedPath = reverse(route.path()); + HashSet newTrace = new HashSet(); + Integer remainingLength = null; + + for (Tile tile : reversedPath) { + if (isNull(remainingLength) && onTrace(tile)) remainingLength = length(); + if (remainingLength == null) { // ahead of train + LOG.debug("{} is ahead of train and will not be touched.",tile); + trace.remove(tile); // old trace will be cleared afterwards. but this tile shall not be cleared, so remove it from old trace + } else if (remainingLength > 0) { // within train + LOG.debug("{} is occupied by train and will be marked as \"occupied\"",tile); + remainingLength -= tile.length(); + newTrace.add(tile); + trace.remove(tile); // old trace will be cleared afterwards. but this tile shall not be cleared, so remove it from old trace + tile.setTrain(this); + LOG.debug("remaining length: {}",remainingLength); + } else { // behind train + if (Route.freeBehindTrain) { + LOG.debug("{} is behind train and will be freed in the next step",tile); + trace.add(tile); // old trace will be cleared afterwards + } else { + LOG.debug("{} is behind train and will be reset to \"locked\" state",tile); + tile.lockFor(context,true); + trace.remove(tile); // old trace will be cleared afterwards. but this tile shall not be cleared, so remove it from old trace + } + } + } + for (Tile tile : trace) tile.free(this); + trace = newTrace; + return context; + } + public boolean usesAutopilot() { - return isSet(autopilot); + return autopilot; } } diff --git a/src/main/java/de/srsoftware/web4rail/threads/BrakeProcess.java b/src/main/java/de/srsoftware/web4rail/threads/BrakeProcess.java new file mode 100644 index 0000000..16fd781 --- /dev/null +++ b/src/main/java/de/srsoftware/web4rail/threads/BrakeProcess.java @@ -0,0 +1,67 @@ +package de.srsoftware.web4rail.threads; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.srsoftware.web4rail.Application; +import de.srsoftware.web4rail.BaseClass; +import de.srsoftware.web4rail.moving.Train; + +public class BrakeProcess extends BaseClass implements Runnable{ + + private static final Logger LOG = LoggerFactory.getLogger(BrakeProcess.class); + + public static int defaultTimeStep = 500; + private Train train; + private int targetSpeed = Train.defaultEndSpeed; + boolean ended = false; + long distance = 0; + private int startSpeed; + private int lastSpeed; + private long lastTime; + + public BrakeProcess(Train train) { + this.train = train; + new Thread(this, Application.threadName(this)).start(); + } + + public BrakeProcess end() { + LOG.debug("{}.end()",this); + ended = true; + return this; + } + + @Override + public void run() { + Integer delay = train.route().brakeTime(train.brakeId()); + startSpeed = train.speed; + lastTime = timestamp(); + while (!train.hasNextPreparedRoute()) { + sleep(delay); + lastSpeed = train.speed; + updateDistance(); + if (lastSpeed > targetSpeed) lastSpeed -= 10; + if (ended) break; + if (lastSpeed <= targetSpeed && (ended = true)) lastSpeed = targetSpeed; + train.setSpeed(lastSpeed); + } + } + + private void updateDistance() { + long newTime = timestamp(); + distance += (newTime-lastTime)*lastSpeed; + lastTime = newTime; + LOG.debug("measured distance: {} units",distance); + } + + @Override + public String toString() { + return getClass().getSimpleName()+"("+train+")"; + } + + public void updateTime() { + updateDistance(); + LOG.debug("updateTime(): start speed was {} {}.",startSpeed,BaseClass.speedUnit); + // TODO + } +} diff --git a/src/main/java/de/srsoftware/web4rail/ControlUnit.java b/src/main/java/de/srsoftware/web4rail/threads/ControlUnit.java similarity index 96% rename from src/main/java/de/srsoftware/web4rail/ControlUnit.java rename to src/main/java/de/srsoftware/web4rail/threads/ControlUnit.java index b5f6878..2aae71c 100644 --- a/src/main/java/de/srsoftware/web4rail/ControlUnit.java +++ b/src/main/java/de/srsoftware/web4rail/threads/ControlUnit.java @@ -1,4 +1,4 @@ -package de.srsoftware.web4rail; +package de.srsoftware.web4rail.threads; import java.io.BufferedReader; import java.io.BufferedWriter; @@ -17,6 +17,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import de.keawe.tools.translations.Translation; +import de.srsoftware.web4rail.Application; +import de.srsoftware.web4rail.Command; +import de.srsoftware.web4rail.Constants; +import de.srsoftware.web4rail.Plan; import de.srsoftware.web4rail.tags.Button; import de.srsoftware.web4rail.tags.Fieldset; import de.srsoftware.web4rail.tags.Form; @@ -296,7 +300,7 @@ public class ControlUnit extends Thread implements Constants{ private void startInfoThread() { infoSocket = commandSocket; // handshake läuft immer über commandSocket und commandScanner infoScanner = commandScanner; - Thread infoThread = new Thread() { + new Thread(Application.threadName("CU.InfoThread")) { @Override public void run() { @@ -314,14 +318,12 @@ public class ControlUnit extends Thread implements Constants{ case FEEDBACK: int addr = Integer.parseInt(parts[5]); boolean active = !parts[6].equals("0"); - Thread thread = new Thread() { + new Thread(Application.threadName("CU.FeedBack("+addr+")")) { @Override public void run() { - ControlUnit.this.plan.sensor(addr,active); + plan.sensor(addr,active); } - }; - thread.setName(Application.threadName("CU.FeedBack("+addr+")")); - thread.start(); + }.start(); case ACESSORY: break; default: @@ -347,9 +349,7 @@ public class ControlUnit extends Thread implements Constants{ public String toString() { return "CU.InfoThread"; } - }; - infoThread.setName(Application.threadName("CU.InfoThread")); - infoThread.start(); + }.start(); } /** diff --git a/src/main/java/de/srsoftware/web4rail/DelayedExecution.java b/src/main/java/de/srsoftware/web4rail/threads/DelayedExecution.java similarity index 75% rename from src/main/java/de/srsoftware/web4rail/DelayedExecution.java rename to src/main/java/de/srsoftware/web4rail/threads/DelayedExecution.java index 2610e52..b05390f 100644 --- a/src/main/java/de/srsoftware/web4rail/DelayedExecution.java +++ b/src/main/java/de/srsoftware/web4rail/threads/DelayedExecution.java @@ -1,4 +1,6 @@ -package de.srsoftware.web4rail; +package de.srsoftware.web4rail.threads; + +import de.srsoftware.web4rail.Application; public abstract class DelayedExecution extends Thread { private int delay; @@ -8,9 +10,8 @@ public abstract class DelayedExecution extends Thread { } public DelayedExecution(int delay,Object cause) { + super(Application.threadName(cause)); this.delay = delay; - - setName(Application.threadName(cause)); start(); } diff --git a/src/main/java/de/srsoftware/web4rail/threads/RoutePrepper.java b/src/main/java/de/srsoftware/web4rail/threads/RoutePrepper.java new file mode 100644 index 0000000..b6b1139 --- /dev/null +++ b/src/main/java/de/srsoftware/web4rail/threads/RoutePrepper.java @@ -0,0 +1,220 @@ +package de.srsoftware.web4rail.threads; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map.Entry; +import java.util.TreeMap; +import java.util.Vector; + +import de.srsoftware.web4rail.Application; +import de.srsoftware.web4rail.BaseClass; +import de.srsoftware.web4rail.EventListener; +import de.srsoftware.web4rail.Plan.Direction; +import de.srsoftware.web4rail.Route; +import de.srsoftware.web4rail.moving.Train; +import de.srsoftware.web4rail.tiles.Block; +import de.srsoftware.web4rail.tiles.Tile; + +public class RoutePrepper extends BaseClass implements Runnable{ + private Context context; + private Route route; + private List failListeners = new LinkedList<>(); + private List foundListeners = new LinkedList<>(); + private List lockedListeners = new LinkedList<>(); + private List preparedListeners= new LinkedList<>(); + + public RoutePrepper(Context c) { + List errors = new LinkedList<>(); + if (isNull(c.train())) errors.add(t("No train in context for {}",getClass().getSimpleName())); + if (isNull(c.block())) errors.add(t("No block in context for {}",getClass().getSimpleName())); + if (isNull(c.direction())) errors.add(t("No direction in context for {}",getClass().getSimpleName())); + if (!errors.isEmpty()) throw new NullPointerException(String.join(", ", errors)); + context = c; + } + + private static TreeMap> availableRoutes(Context context, HashSet visitedRoutes) { + String inset = ""; + for (int i = 0; i < visitedRoutes.size(); i++) inset += " "; + LOG.debug("{}{}.availableRoutes({})", inset, RoutePrepper.class.getSimpleName(), context); + + Block block = context.block(); + Train train = context.train(); + Direction startDirection = context.direction(); + Route currentRoute = context.route(); + TreeMap> availableRoutes = new TreeMap>(); + + 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.leavingRoutes()) { + if (context.invalidated()) return availableRoutes; + if (visitedRoutes.contains(routeCandidate)) { + LOG.debug("{}→ Candidate {} would create loop, skipping", inset, routeCandidate.shortName()); + continue; + } + + HashSet stuckTrace = train.stuckTrace(); // if train has been stopped in between two blocks lastly: + // only allow routes that do not conflict with current train + // position + if (isSet(stuckTrace) && !routeCandidate.path().containsAll(stuckTrace)) { + LOG.debug("Stuck train occupies tiles ({}) outside of {} – not allowed.", stuckTrace, routeCandidate); + 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> 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> 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 routeSet = availableRoutes.get(priority); + if (isNull(routeSet)) { + routeSet = new Vector(); + 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> entry : availableRoutes.entrySet()) { + LOG.debug("{} - Priority {}:", inset, entry.getKey()); + for (Route r : entry.getValue()) LOG.debug("{} - {}", inset, r.shortName()); + } + return availableRoutes; + } + + private static Route chooseRoute(Context context) { + LOG.debug("{}.chooseRoute({})", RoutePrepper.class.getSimpleName(), context); + TreeMap> availableRoutes = availableRoutes(context, new HashSet()); + while (!availableRoutes.isEmpty()) { + if (context.invalidated()) break; + 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)) { + 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; + } + + + private void notify(List listeners) { + for (EventListener listener: listeners) { + listener.fire(); + } + } + + private boolean fail() { + notify(failListeners); + // if (isSet(route) route.reset(); + route = null; + return false; + } + + public void onFail(EventListener l) { + failListeners.add(l); + } + + public void onRouteFound(EventListener l) { + foundListeners.add(l); + } + + public void onRouteLocked(EventListener l) { + lockedListeners.add(l); + } + + public void onRoutePrepared(EventListener l) { + preparedListeners.add(l); + } + + + + public boolean prepareRoute() { + if (isNull(context) || context.invalidated()) return fail(); + route = chooseRoute(context); + if (isNull(route)) return fail(); + context.route(route); + notify(foundListeners); + if (!route.reserveFor(context)) return fail(); + notify(lockedListeners); + if (!route.prepareAndLock()) return fail(); + notify(preparedListeners); + return true; + } + + + public Route route() { + return route; + } + + @Override + public void run() { + prepareRoute(); + } + + public void start() { + new Thread(this,Application.threadName(this)).start(); + } + + public void stop() { + context.invalidate(); + } +} diff --git a/src/main/java/de/srsoftware/web4rail/tiles/Block.java b/src/main/java/de/srsoftware/web4rail/tiles/Block.java index 6c449ea..5dcaea8 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/Block.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/Block.java @@ -3,11 +3,13 @@ package de.srsoftware.web4rail.tiles; import java.io.IOException; import java.util.Collection; import java.util.HashMap; -import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Vector; +import java.util.stream.Collectors; import org.json.JSONArray; import org.json.JSONException; @@ -18,8 +20,10 @@ import org.slf4j.LoggerFactory; import de.srsoftware.tools.Tag; import de.srsoftware.web4rail.BaseClass; import de.srsoftware.web4rail.Connector; +import de.srsoftware.web4rail.LoadCallback; import de.srsoftware.web4rail.Plan.Direction; import de.srsoftware.web4rail.Range; +import de.srsoftware.web4rail.Route; import de.srsoftware.web4rail.moving.Train; import de.srsoftware.web4rail.tags.Button; import de.srsoftware.web4rail.tags.Checkbox; @@ -35,6 +39,58 @@ import de.srsoftware.web4rail.tags.Window; */ public abstract class Block extends StretchableTile{ protected static Logger LOG = LoggerFactory.getLogger(Block.class); + + private class TrainList implements Iterable{ + + private LinkedList trains = new LinkedList(); + private HashMap dirs = new HashMap(); + + 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 iterator() { + return trains.iterator(); + } + + public Train last() { + return trains.isEmpty() ? null : trains.getLast(); + } + + public List list() { + return new Vector<>(trains); + } + + public boolean remove(BaseClass b) { + dirs.remove(b); + return trains.remove(b); + } + + + public void set(Train train, Direction newDirection) { + dirs.put(train, newDirection); + } + + @Override + public String toString() { + return trains.stream().map(t -> t+"→"+directionOf(t)).reduce((s1,s2) -> s1+", "+s2).orElse(t("empty")); + } + } private static final String ALLOW_TURN = "allowTurn"; private static final String NAME = "name"; private static final String NO_TAG = "[default]"; @@ -44,11 +100,12 @@ public abstract class Block extends StretchableTile{ private static final String RAISE = "raise"; public static final String ACTION_ADD_CONTACT = "add_contact"; private static final String PARKED_TRAINS = "parked_trains"; + private static final String TRAINS = "trains"; public String name = "Block"; public boolean turnAllowed = false; private Vector internalContacts = new Vector(); - private Vector parkedTrains = new Vector(); + private TrainList trains = new TrainList(); public Block() { super(); @@ -134,9 +191,10 @@ public abstract class Block extends StretchableTile{ private Vector waitTimes = new Vector(); - public void add(Train parkedTrain) { - parkedTrain.register(); - parkedTrains.add(parkedTrain); + public void add(Train train,Direction direction) { + if (isNull(train)) return; + train.register(); + trains.add(train,direction); } @@ -146,16 +204,9 @@ public abstract class Block extends StretchableTile{ return t("Trigger contact to learn new contact"); } - @Override - protected HashSet classes() { - HashSet classes = super.classes(); - if (!parkedTrains.isEmpty()) classes.add(OCCUPIED); - return classes; - } - @Override 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); } @@ -204,6 +255,28 @@ public abstract class Block extends StretchableTile{ 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({})",this,train); + Train firstTrain = trains.first(); + if (isNull(firstTrain)) return true; + if (firstTrain != train) return false; + trains.remove(train); + if (isSet(firstTrain)) { + super.free(train); + } else super.setTrain(firstTrain); + return true; + } + private WaitTime getWaitTime(String tag) { if (tag == null) return null; for (WaitTime wt : waitTimes) { @@ -231,12 +304,13 @@ public abstract class Block extends StretchableTile{ @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 + Train train = context.train(); + if (is(Status.DISABLED)) return false; + 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 public JSONObject json() { JSONObject json = super.json(); @@ -253,16 +327,30 @@ public abstract class Block extends StretchableTile{ } } if (isSet(jContacts)) json.put(CONTACT, jContacts); - if (!parkedTrains.isEmpty()) { - JSONArray ptids = new JSONArray(); - for (Train parked : parkedTrains) { - if (isSet(parked)) ptids.put(parked.id().toString()); + json.remove(REALM_TRAIN); // is set by TRAINS field for blocks + if (!trains.isEmpty()) { + JSONArray jTrains = new JSONArray(); + for (Train train : trains) { + JSONObject to = new JSONObject(); + to.put(ID, train.id()); + Direction dir = trains.directionOf(train); + if (isSet(dir)) to.put(DIRECTION, dir.toString()); + jTrains.put(to); } - json.put(PARKED_TRAINS, ptids); + json.put(TRAINS, jTrains); } return json; } + public Train lastTrain() { + return trains.last(); + } + + public List leavingRoutes() { + return routes().stream().filter(route -> route.startBlock() == Block.this).collect(Collectors.toList()); + } + + /** * 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. @@ -295,45 +383,73 @@ public abstract class Block extends StretchableTile{ } catch (JSONException e) {} } } - if (json.has(PARKED_TRAINS)) { - JSONArray ptids = json.getJSONArray(PARKED_TRAINS); - for (Object id : ptids) { - Train train = BaseClass.get(new Id(id.toString())); - if (isSet(train)) parkedTrains.add(train); + + new LoadCallback() { + @Override + public void afterLoad() { + if (json.has(TRAINS)) { + JSONArray jTrains = json.getJSONArray(TRAINS); + for (Object o : jTrains) { + if (o instanceof JSONObject) { + JSONObject to = (JSONObject) o; + Id tID = new Id(to.getString(ID)); + Train train = BaseClass.get(tID); + Direction direction = to.has(DIRECTION) ? Direction.valueOf(to.getString(DIRECTION)) : null; + if (isSet(train)) { + train.set(Block.this); + trains.add(train, direction); + status = Status.OCCUPIED; + } + } + } + } else if (json.has(PARKED_TRAINS)) { // legacy + for (Object id : json.getJSONArray(PARKED_TRAINS)) { + Train train = BaseClass.get(new Id(id.toString())); + if (isSet(train)) trains.add(train,null); + } + } } - } + }; + return super.load(json); } - private Fieldset parkedTrainList() { - Fieldset fieldset = new Fieldset(t("parked trains")); - Tag list = new Tag("ul"); - for (Train t : parkedTrains) { - if (isSet(t)) t.link("li", t).addTo(list); + @Override + public boolean lockFor(Context context, boolean downgrade) { + Train newTrain = context.train(); + Route route = context.route(); + 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 OCCUPIED: + if (!downgrade) break; + 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 LOCKED: + break; // do not downgrade } - list.addTo(fieldset); - return fieldset; + return true; } - public List parkedTrains(){ - return parkedTrains; - } - - public Train parkedTrain(boolean last) { - if (parkedTrains.isEmpty()) return null; - return last ? parkedTrains.lastElement() : parkedTrains.firstElement(); - } - - @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Name"),new Input(NAME, name)); 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(waitTimeForm()); - if (!parkedTrains.isEmpty()) postForm.add(parkedTrainList()); - return super.properties(preForm, formInputs, postForm); + if (!trains.isEmpty()) postForm.add(trainList()); + return super.properties(preForm, formInputs, postForm,errors); } public Tile raise(String tag) { @@ -347,6 +463,32 @@ public abstract class Block extends StretchableTile{ } 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) { internalContacts.add(contact); @@ -357,14 +499,18 @@ public abstract class Block extends StretchableTile{ public void removeChild(BaseClass child) { super.removeChild(child); internalContacts.remove(child); - if (parkedTrains.remove(child)) plan.place(this); - if (train == child) setTrain(null); + if (trains.remove(child)) plan.place(this); } public void removeContact(BlockContact blockContact) { internalContacts.remove(blockContact); } + public void set(Train train, Direction direction) { + trains.set(train,direction); + } + + public abstract List startPoints(); @Override @@ -372,10 +518,7 @@ public abstract class Block extends StretchableTile{ if (isNull(replacements)) replacements = new HashMap(); replacements.put("%text%",name); Vector trainNames = new Vector(); - if (isSet(train)) trainNames.add(train.directedName()); - for (Train t:parkedTrains) { - if (isSet(t)) trainNames.add(t.directedName()); - } + for (Train train: trains) trainNames.add(train.directedName(trains.directionOf(train))); if (!trainNames.isEmpty())replacements.put("%text%",String.join(" | ", trainNames)); Tag tag = super.tag(replacements); tag.clazz(tag.get("class")+" Block"); @@ -392,23 +535,45 @@ public abstract class Block extends StretchableTile{ 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 trains(){ + return trains.list(); + } + @Override public Tile update(HashMap params) { if (params.containsKey(NAME)) name=params.get(NAME); if (params.containsKey(Train.class.getSimpleName())) { Id trainId = Id.from(params,Train.class.getSimpleName()); - if (trainId.equals(0)) { - if (isSet(train)) { - train.dropTrace(); - train.set(null); - } - train = null; + if (trainId.equals(0)) { // remove first train + dropFirstTrain(); } else { - Train newTrain = Train.get(trainId); - if (isSet(newTrain) && newTrain != train) { - newTrain.dropTrace(); - if (connections(newTrain.direction()).isEmpty()) newTrain.heading(null); - newTrain.set(this); + Train train = Train.get(trainId); + if (isSet(train) && train != trains.first()) { + dropFirstTrain(); + train.dropTrace(); + if (connections(train.direction()).isEmpty()) train.heading(null); + train.set(this); + trains.add(train,train.direction()); } } } diff --git a/src/main/java/de/srsoftware/web4rail/tiles/BlockContact.java b/src/main/java/de/srsoftware/web4rail/tiles/BlockContact.java index f9c2e19..d053835 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/BlockContact.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/BlockContact.java @@ -28,11 +28,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); @@ -40,8 +35,7 @@ public class BlockContact extends Contact { @Override public Train train() { - train = ((Block)parent()).train(); - return train; + return ((Block)parent()).train(); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/tiles/Bridge.java b/src/main/java/de/srsoftware/web4rail/tiles/Bridge.java index f5c05e4..4dcbff2 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/Bridge.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/Bridge.java @@ -9,8 +9,7 @@ import org.json.JSONObject; import de.srsoftware.tools.Tag; import de.srsoftware.web4rail.BaseClass; import de.srsoftware.web4rail.Connector; -import de.srsoftware.web4rail.DelayedExecution; -import de.srsoftware.web4rail.Route; +import de.srsoftware.web4rail.LoadCallback; 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 boolean free(Train train) { + if (!super.free(train)) return false; + return isSet(counterpart) ? counterpart.free(train) : true; + } + @Override public JSONObject json() { JSONObject json = super.json(); @@ -51,37 +56,29 @@ public abstract class Bridge extends Tile { @Override public Tile load(JSONObject json) { - if (json.has(COUNTERPART)) { - new DelayedExecution(this) { - @Override - public void execute() { - counterpart = (Bridge) plan.get(Id.from(json, COUNTERPART), false); - } - }; - } + if (json.has(COUNTERPART)) new LoadCallback() { + @Override + public void afterLoad() { + counterpart = (Bridge) plan.get(Id.from(json, COUNTERPART), false); + } + }; return super.load(json); } @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 setTrain(Train newTrain) { + if (train() == newTrain) return true; + if (!super.setTrain(newTrain)) return false; + return isNull(counterpart) ? true : counterpart.setTrain(newTrain); } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { Fieldset fieldset = new Fieldset(t("Counterpart")); new Tag("p").content(isSet(counterpart) ? t("Connected to {}.",counterpart) : t("Not connected to other bridge part!")).addTo(fieldset); button(t("Select counterpart"),Map.of(ACTION,ACTION_CONNECT)).addTo(fieldset); preForm.add(fieldset); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } public Window propMenu() { @@ -109,11 +106,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 7c4b2cc..e36953f 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/Contact.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/Contact.java @@ -16,14 +16,14 @@ import org.slf4j.LoggerFactory; import de.srsoftware.tools.Tag; import de.srsoftware.web4rail.Application; import de.srsoftware.web4rail.BaseClass; -import de.srsoftware.web4rail.DelayedExecution; -import de.srsoftware.web4rail.Route; import de.srsoftware.web4rail.actions.Action; import de.srsoftware.web4rail.actions.ActionList; +import de.srsoftware.web4rail.moving.Train; import de.srsoftware.web4rail.tags.Fieldset; import de.srsoftware.web4rail.tags.Input; import de.srsoftware.web4rail.tags.Select; import de.srsoftware.web4rail.tags.Window; +import de.srsoftware.web4rail.threads.DelayedExecution; public class Contact extends Tile{ private static Logger LOG = LoggerFactory.getLogger(Contact.class); @@ -53,7 +53,7 @@ public class Contact extends Tile{ boolean aborted = false; public OffTimer() { - setName(Application.threadName("OffTimer("+Contact.this+")")); + super(Application.threadName("OffTimer("+Contact.this+")")); start(); } @@ -85,12 +85,9 @@ 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); + Train train = train(); + Context context = isSet(train) ? train.contact(this) : new Context(this); actions.fire(context,"Contact("+addr+")"); - if (isSet(route)) route.contact(this); for (Listener listener : listeners) listener.fired("Contact("+addr+")"); @@ -115,7 +112,12 @@ public class Contact extends Tile{ @Override public Object click(boolean shift) throws IOException { - if (!shift) trigger(200); + if (!shift) new Thread(Application.threadName(this)) { + @Override + public void run() { + trigger(200); + } + }.start(); return super.click(shift); } @@ -191,7 +193,7 @@ public class Contact extends Tile{ } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { Tag span = new Tag("span"); new Input(ADDRESS, addr).numeric().addTo(span).content(NBSP); button(t("learn"),Map.of(ACTION,ACTION_ANALYZE)).addTo(span); @@ -200,7 +202,7 @@ public class Contact extends Tile{ Fieldset fieldset = new Fieldset(t("Actions")).id("props-actions"); actions.list().addTo(fieldset); postForm.add(fieldset); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/tiles/Decoupler.java b/src/main/java/de/srsoftware/web4rail/tiles/Decoupler.java index 6435689..d3a7157 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/Decoupler.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/Decoupler.java @@ -111,7 +111,7 @@ public abstract class Decoupler extends Tile implements Device{ } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { Tag div = new Tag("div"); for (Protocol proto : Protocol.values()) { new Radio(PROTOCOL, proto.toString(), t(proto.toString()), proto == protocol).addTo(div); @@ -120,7 +120,7 @@ public abstract class Decoupler extends Tile implements Device{ formInputs.add(t("Address"),new Input(ADDRESS, address).numeric()); formInputs.add(t("Port"),new Input(PORT, port).numeric()); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } private char proto() { diff --git a/src/main/java/de/srsoftware/web4rail/tiles/Relay.java b/src/main/java/de/srsoftware/web4rail/tiles/Relay.java index b2a8bb4..d6a15af 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/Relay.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/Relay.java @@ -124,7 +124,7 @@ public class Relay extends Tile implements Device{ } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Name"),new Input(NAME,name)); Tag div = new Tag("div"); for (Protocol proto : Protocol.values()) { @@ -136,7 +136,7 @@ public class Relay extends Tile implements Device{ formInputs.add(t("Label for state {}","B"),new Input(LABEL_B, stateLabelB)); formInputs.add(t("Port for state {}",stateLabelA),new Input(PORT_A, portA).numeric()); formInputs.add(t("Port for state {}",stateLabelB),new Input(PORT_B, portB).numeric()); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } private char proto() { diff --git a/src/main/java/de/srsoftware/web4rail/tiles/Signal.java b/src/main/java/de/srsoftware/web4rail/tiles/Signal.java index 3f5c537..803ba5d 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/Signal.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/Signal.java @@ -127,7 +127,7 @@ public abstract class Signal extends Tile { } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { Fieldset aspectEditor = new Fieldset(t("Aspects")).id("props-aspects"); Form form = new Form("aspect-form"); new Input(REALM,REALM_PLAN).hideIn(form); @@ -158,7 +158,7 @@ public abstract class Signal extends Tile { form.addTo(aspectEditor); postForm.add(aspectEditor); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } public boolean state(String aspect) { diff --git a/src/main/java/de/srsoftware/web4rail/tiles/StretchableTile.java b/src/main/java/de/srsoftware/web4rail/tiles/StretchableTile.java index eceb967..25285a3 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/StretchableTile.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/StretchableTile.java @@ -56,9 +56,9 @@ public abstract class StretchableTile extends TileWithShadow { } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(stretchType(),new Input(STRETCH_LENGTH, stretch).numeric().addTo(new Tag("span")).content(NBSP+t("Tile(s)"))); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/tiles/Switch.java b/src/main/java/de/srsoftware/web4rail/tiles/Switch.java index 974bedf..7a00a3d 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/Switch.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/Switch.java @@ -126,7 +126,7 @@ public class Switch extends Tile{ } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { Fieldset fieldset = new Fieldset(t("Actions (On)")); fieldset.id("actionsOn"); actionsOn.list().addTo(fieldset); @@ -135,7 +135,7 @@ public class Switch extends Tile{ fieldset.id("actionsOff"); actionsOff.list().addTo(fieldset); postForm.add(fieldset); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override @@ -164,7 +164,7 @@ public class Switch extends Tile{ public void state(boolean newState) { state = newState; - Thread thread = new Thread() { + new Thread(Application.threadName(this)) { @Override public void run() { @@ -173,9 +173,7 @@ public class Switch extends Tile{ actionsOn.fire(context,Switch.this); } else actionsOff.fire(context,Switch.this); } - }; - thread.setName(Application.threadName(this)); - thread.start(); + }.start(); stream(); } diff --git a/src/main/java/de/srsoftware/web4rail/tiles/TextDisplay.java b/src/main/java/de/srsoftware/web4rail/tiles/TextDisplay.java index 02610f3..49e847a 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/TextDisplay.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/TextDisplay.java @@ -33,9 +33,9 @@ public class TextDisplay extends StretchableTile { } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Text"),new Input(TEXT, text)); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } public static Select selector(TextDisplay preselected,Collection exclude) { diff --git a/src/main/java/de/srsoftware/web4rail/tiles/Tile.java b/src/main/java/de/srsoftware/web4rail/tiles/Tile.java index d7d45dc..992243d 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/Tile.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/Tile.java @@ -22,11 +22,10 @@ import org.slf4j.LoggerFactory; import de.srsoftware.tools.Tag; import de.srsoftware.web4rail.BaseClass; import de.srsoftware.web4rail.Connector; -import de.srsoftware.web4rail.PathFinder; import de.srsoftware.web4rail.Plan; import de.srsoftware.web4rail.Plan.Direction; -import de.srsoftware.web4rail.actions.AlterDirection; import de.srsoftware.web4rail.Route; +import de.srsoftware.web4rail.actions.AlterDirection; import de.srsoftware.web4rail.moving.Train; import de.srsoftware.web4rail.tags.Checkbox; import de.srsoftware.web4rail.tags.Fieldset; @@ -39,63 +38,81 @@ import de.srsoftware.web4rail.tags.Window; * @author Stephan Richter, SRSoftware * */ -public abstract class Tile extends BaseClass implements Comparable{ - protected static Logger LOG = LoggerFactory.getLogger(Tile.class); +public abstract class Tile extends BaseClass implements Comparable { + public enum Status { + DISABLED("disabled"), FREE("free"), RESERVED("reserved"), 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"; - private static final String X = "x"; - private static final String Y = "y"; - - private boolean disabled = false; - 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())); - protected Train train = null; - public Integer x = null; - public Integer y = null; - + + private static final String LENGTH = "length"; + private static final String ONEW_WAY = "one_way"; + private static final String POS = "pos"; + private static final String TYPE = "type"; + private static final String X = "x"; + private static final String Y = "y"; + + private boolean isTrack = true; + private int length = DEFAUT_LENGTH; + protected Direction oneWay = null; + private TreeSet routes = new TreeSet<>((r1, r2) -> r1.toString().compareTo(r2.toString())); + private Train train = null; + protected Status status = Status.FREE; + public Integer x = null; + public Integer y = null; + public void add(Route route) { this.routes.add(route); } - protected HashSet classes(){ + 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); - if (disabled) classes.add(DISABLED); + if (!is(Status.FREE)) classes.add(status.toString()); return classes; } public Object click(boolean shift) throws IOException { - LOG.debug("{}.click()",getClass().getSimpleName()); + LOG.debug("{}.click()", getClass().getSimpleName()); if (isSet(train) && shift) return train.properties(); return properties(); } - + @Override public int compareTo(Tile other) { - if (x == other.x) return y-other.y; + if (x == other.x) return y - other.y; return x - other.x; } - + public JSONObject config() { return new JSONObject(); } - - public Map connections(Direction from){ + + public Map connections(Direction from) { return new HashMap<>(); } - + + public boolean free(Train t) { + if (isSet(train) && t != train) return false; + train = null; + status = Status.FREE; + plan.place(this); + return true; + } + public int height() { return 1; } @@ -103,100 +120,89 @@ public abstract class Tile extends BaseClass implements Comparable{ public Id id() { return Tile.id(x, y); } - + public static Id id(int x, int y) { - 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 { - clazz = Tile.class.getName().replace(".Tile", "."+clazz); + + 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); Tile tile = (Tile) Tile.class.getClassLoader().loadClass(clazz).getDeclaredConstructor().newInstance(); tile.load(json).register(); - if (tile instanceof TileWithShadow) ((TileWithShadow)tile).placeShadows(); + if (tile instanceof TileWithShadow) ((TileWithShadow) tile).placeShadows(); plan.place(tile); } - public boolean isFreeFor(Context context) { - PathFinder.LOG.debug("{}.isFreeFor({})",this,context); - if (disabled) { - PathFinder.LOG.debug("{} is disabled!",this); - return false; + public boolean is(Status... states) { + for (Status s : states) { + if (status == s) return true; } - if (isNull(context)) { - if (isSet(train)) { - PathFinder.LOG.debug("{} is occupied by {}",this,train); - return false; - } - if (isSet(route)) { - PathFinder.LOG.debug("{} is occupied by {}",this,route); - return false; - } - } - if (isSet(train)) { - Train contextTrain = context.train(); - boolean free = train == contextTrain; // during train.reserveNext, we may encounter, parts, that are already reserved by the respective train, but having another route. do not compare routes in that case! - if (free) { - PathFinder.LOG.debug("already reserved by {} → true",train); - } else { - if (isSet(contextTrain) && contextTrain.isShunting()) { - PathFinder.LOG.debug("occupied by {}. Allowed for shunting {}",train,contextTrain); - free = true; - } else PathFinder.LOG.debug("occupied by {} → false",train); - } - return free; - } - - // if we get here, the tile is not occupied by a train, but reserved by a route, yet. thus, the tile is not available for another route - if (isSet(route) && route != context.route()) { - PathFinder.LOG.debug("reserved by other route: {}",route); - if (isSet(route.train())) { - if (route.train() == context.train()) { - PathFinder.LOG.debug("that route is used by {}, which is also requesting this tile → true",route.train()); - return true; - } - } - PathFinder.LOG.debug("{}.route.train = {} → false",this,route.train()); - return false; - } - PathFinder.LOG.debug("free"); - return true; + return false; } - + + public 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() { 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()); + 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 (is(Status.DISABLED)) json.put(DISABLED, true); + if (isSet(train)) json.put(REALM_TRAIN, train.id()); json.put(LENGTH, length); return json; } - + public int length() { return length; } - + public Tile length(int newLength) { length = Math.max(0, newLength); return this; } - + /** - * If arguments are given, the first is taken as content, the second as tag type. - * If no content is supplied, id() is set as content. - * If no type is supplied, "span" is preset. + * If arguments are given, the first is taken as content, the second as tag + * type. If no content is supplied, id() is set as content. If no type is + * supplied, "span" is preset. + * * @param args * @return */ - public Tag link(String...args) { - String tx = args.length<1 ? id()+NBSP : args[0]; - String type = args.length<2 ? "span" : args[1]; - return super.link(type, (Object)tx, Map.of(ACTION,ACTION_CLICK)); + public Tag link(String... args) { + String tx = args.length < 1 ? id() + NBSP : args[0]; + String type = args.length < 2 ? "span" : args[1]; + return super.link(type, (Object) tx, Map.of(ACTION, ACTION_CLICK)); } - - + public static void load(Object object, Plan plan) { if (object instanceof JSONObject) { JSONObject json = (JSONObject) object; @@ -205,10 +211,12 @@ public abstract class Tile extends BaseClass implements Comparable{ clazz = AlterDirection.class.getSimpleName(); } try { - Tile.inflate(clazz,json,plan); - } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException | ClassNotFoundException | IOException e) { + Tile.inflate(clazz, json, plan); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException + | InvocationTargetException | NoSuchMethodException | SecurityException | ClassNotFoundException + | IOException e) { e.printStackTrace(); - } + } } } @@ -218,96 +226,92 @@ public abstract class Tile extends BaseClass implements Comparable{ if (json.has(POS)) { JSONObject pos = json.getJSONObject(POS); x = pos.getInt(X); - y = pos.getInt(Y); + y = pos.getInt(Y); } - if (json.has(DISABLED)) disabled = json.getBoolean(DISABLED); - if (json.has(LENGTH)) length = json.getInt(LENGTH); - if (json.has(ONEW_WAY)) oneWay = Direction.valueOf(json.getString(ONEW_WAY)); + if (json.has(DISABLED) && json.getBoolean(DISABLED)) status = Status.DISABLED; + if (json.has(LENGTH)) length = json.getInt(LENGTH); + if (json.has(ONEW_WAY)) oneWay = Direction.valueOf(json.getString(ONEW_WAY)); return this; } - + public boolean move(int dx, int dy) { - int destX = x+(dx > 0 ? width() : dx); - int destY = y+(dy > 0 ? height() : dy); + int destX = x + (dx > 0 ? width() : dx); + int destY = y + (dy > 0 ? height() : dy); if (destX < 0 || destY < 0) return false; - - Tile tileAtDestination = plan.get(id(destX, destY),true); + + Tile tileAtDestination = plan.get(id(destX, destY), true); if (isSet(tileAtDestination) && !tileAtDestination.move(dx, dy)) return false; - plan.drop(this); - position(x+dx, y+dy); + plan.drop(this); + position(x + dx, y + dy); plan.place(this); return true; } - + protected void noTrack() { - isTrack = false; + isTrack = false; } - + public Tile position(int x, int y) { this.x = x; this.y = y; return this; } - + public List possibleDirections() { return new Vector(); } - + @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm, + String... errors) { 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")); - train.link("span", t("Train")+":"+NBSP+train+NBSP).addTo(fieldset); + 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); + train.button(t("stop"), Map.of(ACTION, ACTION_STOP)).addTo(fieldset); } else { - train.button(t("depart"), Map.of(ACTION,ACTION_START)).addTo(fieldset); + train.button(t("depart"), Map.of(ACTION, ACTION_START)).addTo(fieldset); } if (train.usesAutopilot()) { - train.button(t("quit autopilot"), Map.of(ACTION,ACTION_QUIT)).addTo(fieldset); + train.button(t("quit autopilot"), Map.of(ACTION, ACTION_QUIT)).addTo(fieldset); } else { - train.button(t("auto"), Map.of(ACTION,ACTION_AUTO)).addTo(fieldset); + train.button(t("auto"), Map.of(ACTION, ACTION_AUTO)).addTo(fieldset); } } - + if (isSet(fieldset)) preForm.add(fieldset); - + if (isTrack) { - formInputs.add(t("Length"),new Input(LENGTH,length).numeric().addTo(new Tag("span")).content(NBSP+lengthUnit)); - Checkbox checkbox = new Checkbox(DISABLED, t("disabled"),disabled); - if (disabled) checkbox.clazz("disabled"); - formInputs.add(t("State"),checkbox); + formInputs.add(t("Length"), + new Input(LENGTH, length).numeric().addTo(new Tag("span")).content(NBSP + lengthUnit)); + Checkbox checkbox = new Checkbox(DISABLED, t("disabled"), is(Status.DISABLED)); + if (is(Status.DISABLED)) checkbox.clazz("disabled"); + formInputs.add(t("State"), checkbox); } - + List pd = possibleDirections(); if (!pd.isEmpty()) { Tag div = new Tag("div"); - new Radio("oneway","none",t("No"),isNull(oneWay)).addTo(div); - for (Direction d:pd) { - new Radio("oneway",d.toString(),t(d.toString()),d == oneWay).addTo(div); + new Radio("oneway", "none", t("No"), isNull(oneWay)).addTo(div); + for (Direction d : pd) { + new Radio("oneway", d.toString(), t(d.toString()), d == oneWay).addTo(div); } - formInputs.add(t("One way"),div); + formInputs.add(t("One way"), div); } - if (!routes.isEmpty()) { fieldset = new Fieldset(t("Routes")).id("props-routes"); Tag routeList = new Tag("ol"); boolean empty = true; for (Route route : routes) { if (route.isDisabled()) continue; - 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); - button(t("simplify name"), Map.of(ACTION,ACTION_AUTO,ROUTE,route.id().toString())).addTo(li); + 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); + button(t("simplify name"), Map.of(ACTION, ACTION_AUTO, ROUTE, route.id().toString())).addTo(li); li.addTo(routeList); empty = false; } @@ -316,14 +320,16 @@ public abstract class Tile extends BaseClass implements Comparable{ routeList.addTo(fieldset); postForm.add(fieldset); } - + routeList = new Tag("ol"); empty = true; for (Route route : routes) { if (!route.isDisabled()) continue; - 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); - button(t("simplify name"), Map.of(ACTION,ACTION_AUTO,ROUTE,route.id().toString())).addTo(li); + 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); + button(t("simplify name"), Map.of(ACTION, ACTION_AUTO, ROUTE, route.id().toString())).addTo(li); li.addTo(routeList); empty = false; } @@ -332,96 +338,90 @@ public abstract class Tile extends BaseClass implements Comparable{ routeList.addTo(fieldset); } } - - return super.properties(preForm, formInputs, postForm); + + return super.properties(preForm, formInputs, postForm, errors); } - + private static String replace(String line, Entry replacement) { - String key = replacement.getKey(); + String key = replacement.getKey(); Object val = replacement.getValue(); int start = line.indexOf(key); int len = key.length(); - while (start>0) { - int end = line.indexOf("\"",start); - int end2 = line.indexOf("<",start); - if (end2>0 && (end<0 || end2 0) { + int end = line.indexOf("\"", start); + int end2 = line.indexOf("<", start); + if (end2 > 0 && (end < 0 || end2 < end)) end = end2; String tag = line.substring(start, end); - if (tag.length()>len) val = Integer.parseInt(tag.substring(len)) + (int) val; - line = line.replace(tag, ""+val); + if (tag.length() > len) val = Integer.parseInt(tag.substring(len)) + (int) val; + line = line.replace(tag, "" + val); start = line.indexOf(key); } return line; } - public Route route() { - return route; - } - public TreeSet routes() { return routes; } - + public static void saveAll(String filename) throws IOException { BufferedWriter file = new BufferedWriter(new FileWriter(filename)); for (Tile tile : BaseClass.listElements(Tile.class)) { if (isNull(tile) || tile instanceof Shadow || tile instanceof BlockContact) continue; - file.append(tile.json()+"\n"); + file.append(tile.json() + "\n"); } 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 setTrain(Train newTrain) { + if (isNull(newTrain)) return false; + if (isSet(train) && newTrain != train) return false; + switch (status) { // bisheriger Status + case DISABLED: + return false; + case FREE: + case RESERVED: + case LOCKED: + train = newTrain; + status = Status.OCCUPIED; + plan.place(this); + break; + case OCCUPIED: + break; + } + return true; } - public Tag tag(Map replacements) throws IOException { - int width = 100*width(); - int height = 100*height(); + public Tag tag(Map replacements) throws IOException { + int width = 100 * width(); + int height = 100 * height(); if (isNull(replacements)) replacements = new HashMap(); - replacements.put("%width%",width); - replacements.put("%height%",height); + replacements.put("%width%", width); + replacements.put("%height%", height); String style = ""; - Tag svg = new Tag("svg") - .id(isSet(x) && isSet(y) ? id().toString() : getClass().getSimpleName()) - .clazz(classes()) - .size(100,100) - .attr("name", getClass().getSimpleName()) - .attr("viewbox", "0 0 "+width+" "+height); - if (isSet(x)) style="left: "+(30*x)+"px; top: "+(30*y)+"px;"; - if (width()>1) style+=" width: "+(30*width())+"px;"; - if (height()>1) style+=" height: "+(30*height())+"px;"; - + Tag svg = new Tag("svg").id(isSet(x) && isSet(y) ? id().toString() : getClass().getSimpleName()) + .clazz(classes()).size(100, 100).attr("name", getClass().getSimpleName()) + .attr("viewbox", "0 0 " + width + " " + height); + if (isSet(x)) style = "left: " + (30 * x) + "px; top: " + (30 * y) + "px;"; + if (width() > 1) style += " width: " + (30 * width()) + "px;"; + if (height() > 1) style += " height: " + (30 * height()) + "px;"; + if (!style.isEmpty()) svg.style(style); - File file = new File(System.getProperty("user.dir")+"/resources/svg/"+getClass().getSimpleName()+".svg"); + File file = new File(System.getProperty("user.dir") + "/resources/svg/" + getClass().getSimpleName() + ".svg"); if (file.exists()) { Scanner scanner = new Scanner(file, StandardCharsets.UTF_8); StringBuffer sb = new StringBuffer(); while (scanner.hasNextLine()) { String line = scanner.nextLine(); if (line.startsWith("")) continue; - for (Entry replacement : replacements.entrySet()) line = replace(line,replacement); - sb.append(line+"\n"); + for (Entry replacement : replacements.entrySet()) line = replace(line, replacement); + sb.append(line + "\n"); } scanner.close(); svg.content(sb.toString()); - + if (isSet(oneWay)) { - switch (oneWay) { + switch (oneWay) { case EAST: new Tag("polygon").clazz("oneway").attr("points", "100,50 75,35 75,65").addTo(svg); break; @@ -440,71 +440,98 @@ public abstract class Tile extends BaseClass implements Comparable{ String title = title(); if (isSet(title)) new Tag("title").content(title()).addTo(svg); } else { - new Tag("title").content(t("No display defined for this tile ({})",getClass().getSimpleName())).addTo(svg); - new Tag("text") - .pos(35,70) - .content("?") - .addTo(svg); + new Tag("title").content(t("No display defined for this tile ({})", getClass().getSimpleName())).addTo(svg); + new Tag("text").pos(35, 70).content("?").addTo(svg); } return svg; } - + public String title() { - return getClass().getSimpleName() + " @ ("+x+", "+y+")"; + return getClass().getSimpleName() + " @ (" + x + ", " + y + ")"; } - + @Override public String toString() { - return t("{}({},{})",getClass().getSimpleName(),x,y) ; + return t("{}({},{})", getClass().getSimpleName(), x, y); } - + public Train train() { return train; } - + @Override public BaseClass remove() { while (!routes.isEmpty()) routes.first().remove(); return super.remove(); } - + @Override public void removeChild(BaseClass child) { String childAsString = child.toString(); - if (childAsString.length()>20) childAsString = childAsString.substring(0, 20)+"…"; - LOG.debug("Removing {} from {}",childAsString,this); + if (childAsString.length() > 20) childAsString = childAsString.substring(0, 20) + "…"; + LOG.debug("Removing {} from {}", childAsString, this); if (child instanceof Route) routes.remove(child); - + if (child == train) train = null; - if (child == route) route = null; super.removeChild(child); plan.place(this); } - public void setEnabled(boolean newState) { - disabled = !newState; - 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); + public boolean lockFor(Context context,boolean downgrade) { + 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 OCCUPIED: + if (!downgrade) break; + case FREE: + case RESERVED: + status = Status.LOCKED; + plan.place(this); + break; + case LOCKED: + break; // already locked } - throw new IllegalArgumentException(t("{} not occupied by {}!",this,oldRoute)); + 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); } public Tile update(HashMap params) { - LOG.debug("{}.update({})",getClass().getSimpleName(),params); + LOG.debug("{}.update({})", getClass().getSimpleName(), params); String oneWayDir = params.get("oneway"); if (isSet(oneWayDir)) { try { @@ -513,17 +540,15 @@ public abstract class Tile extends BaseClass implements Comparable{ oneWay = null; } } - disabled = "on".equals(params.get(DISABLED)); + if ("on".equals(params.get(DISABLED))) status = Status.DISABLED; String len = params.get(LENGTH); if (isSet(len)) length(Integer.parseInt(len)); super.update(params); plan.place(this); return this; } - + public int width() { return 1; } - - } diff --git a/src/main/java/de/srsoftware/web4rail/tiles/Turnout.java b/src/main/java/de/srsoftware/web4rail/tiles/Turnout.java index ee4945c..b0be00b 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/Turnout.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/Turnout.java @@ -116,14 +116,14 @@ public abstract class Turnout extends Tile implements Device{ } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { Tag div = new Tag("div"); for (Protocol proto : Protocol.values()) { new Radio(PROTOCOL, proto.toString(), t(proto.toString()), proto == protocol).addTo(div); } formInputs.add(t("Protocol"),div); formInputs.add(t("Address"),new Input(ADDRESS, address).numeric()); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } private char proto() { @@ -159,9 +159,10 @@ 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; + sleep(300); + state = newState; plan.place(this); return new Reply(200,"OK"); } diff --git a/src/main/java/de/srsoftware/web4rail/tiles/TurnoutL.java b/src/main/java/de/srsoftware/web4rail/tiles/TurnoutL.java index 19287dd..811fa5c 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/TurnoutL.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/TurnoutL.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.util.HashMap; import java.util.List; +import de.srsoftware.web4rail.moving.Train; import de.srsoftware.web4rail.tags.Fieldset; import de.srsoftware.web4rail.tags.Input; import de.srsoftware.web4rail.tags.Window; @@ -16,8 +17,9 @@ 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)); + Train train = train(); + if (isSet(train)) { + plan.stream(t("{} is locked by {}!",this,train)); } else state(state == State.STRAIGHT ? State.LEFT : State.STRAIGHT); } return o; @@ -36,10 +38,10 @@ public abstract class TurnoutL extends Turnout { } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Straight port")+COL,new Input(STRAIGHT, portA).numeric()); formInputs.add(t("Left port")+COL,new Input(LEFT, portB).numeric()); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override diff --git a/src/main/java/de/srsoftware/web4rail/tiles/TurnoutR.java b/src/main/java/de/srsoftware/web4rail/tiles/TurnoutR.java index 782c4b3..6c22028 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/TurnoutR.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/TurnoutR.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.util.HashMap; import java.util.List; +import de.srsoftware.web4rail.moving.Train; import de.srsoftware.web4rail.tags.Fieldset; import de.srsoftware.web4rail.tags.Input; import de.srsoftware.web4rail.tags.Window; @@ -16,8 +17,9 @@ 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)); + Train train = train(); + if (isSet(train)) { + plan.stream(t("{} is locked by {}!",this,train)); } else state(state == State.STRAIGHT ? State.RIGHT : State.STRAIGHT); } return o; @@ -37,10 +39,10 @@ public abstract class TurnoutR extends Turnout { } @Override - protected Window properties(List
preForm, FormInput formInputs, List
postForm) { + protected Window properties(List
preForm, FormInput formInputs, List
postForm,String...errors) { formInputs.add(t("Straight port")+COL,new Input(STRAIGHT, portA).numeric()); formInputs.add(t("Right port")+COL,new Input(RIGHT, portB).numeric()); - return super.properties(preForm, formInputs, postForm); + return super.properties(preForm, formInputs, postForm,errors); } @Override