From 7170952cb521c2a0194b6eb6f31ae0708296ac45 Mon Sep 17 00:00:00 2001 From: Stephan Richter <github@keawe.de> Date: Sat, 26 Jun 2021 19:23:56 +0200 Subject: [PATCH] re-implemented route-search --- pom.xml | 2 +- .../translations/Application.de.translation | 2 +- .../de/srsoftware/web4rail/moving/Train.java | 5 +- .../web4rail/threads/RoutePrepper.java | 241 ++++++++---------- 4 files changed, 108 insertions(+), 142 deletions(-) diff --git a/pom.xml b/pom.xml index 0d444c4..642d572 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>de.srsoftware</groupId> <artifactId>web4rail</artifactId> - <version>1.5.8</version> + <version>1.5.9</version> <name>Web4Rail</name> <packaging>jar</packaging> <description>Java Model Railway Control</description> diff --git a/resources/translations/Application.de.translation b/resources/translations/Application.de.translation index 4da6c47..30033a0 100644 --- a/resources/translations/Application.de.translation +++ b/resources/translations/Application.de.translation @@ -460,7 +460,7 @@ TurnoutRS : WeicheRS TurnoutRW : WeicheRW Turnouts : Weichen turn within train : innerhalb des Zugs drehen -Turns the train, as if it went through a loop. : Dreht den ZUg, als wenn er eine Wendeschleife passiert hätte. +Turns the train, as if it went through a loop. : Dreht den Zug, als wenn er eine Wendeschleife passiert hätte. Type : Typ Unknown action\: {} : Unbekannte Aktion: {} Unknown decoder type : Unbekannter Decoder-Typ diff --git a/src/main/java/de/srsoftware/web4rail/moving/Train.java b/src/main/java/de/srsoftware/web4rail/moving/Train.java index 4aa0757..abe392f 100644 --- a/src/main/java/de/srsoftware/web4rail/moving/Train.java +++ b/src/main/java/de/srsoftware/web4rail/moving/Train.java @@ -360,7 +360,7 @@ public class Train extends BaseClass implements Comparable<Train> { } public Block destination(){ - LOG.debug("{}.destination()",this); + //LOG.debug("{}.destination()",this); if (isNull(destination)) { String destTag = destinationTag(); LOG.debug("→ processing \"{}\"...",destTag); @@ -985,7 +985,7 @@ public class Train extends BaseClass implements Comparable<Train> { if (isSet(currentBlock)) plan.place(currentBlock); } -x if (isSet(route)) { + if (isSet(route)) { LOG.debug("Train already on route {}",route); return null; } @@ -1141,6 +1141,7 @@ x if (isSet(route)) { public Context updateTrace(Context context) { LOG.debug("updateTrace({})",context); + if (isNull(context)) return null; Tile from = context.tile(); if (isNull(from)) from = context.contact(); if (isNull(from)) { diff --git a/src/main/java/de/srsoftware/web4rail/threads/RoutePrepper.java b/src/main/java/de/srsoftware/web4rail/threads/RoutePrepper.java index d862ad2..04f69bd 100644 --- a/src/main/java/de/srsoftware/web4rail/threads/RoutePrepper.java +++ b/src/main/java/de/srsoftware/web4rail/threads/RoutePrepper.java @@ -1,12 +1,11 @@ package de.srsoftware.web4rail.threads; -import java.util.HashMap; +import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.List; -import java.util.Map.Entry; -import java.util.TreeMap; -import java.util.stream.Collectors; +import java.util.PriorityQueue; +import java.util.Vector; import de.srsoftware.web4rail.Application; import de.srsoftware.web4rail.BaseClass; @@ -19,21 +18,65 @@ import de.srsoftware.web4rail.tiles.Tile; public class RoutePrepper extends BaseClass implements Runnable{ - private static class Candidate{ + + + public static class Trail extends Vector<Route> implements Comparable<Trail> { - private int score; - private Route route; + private static final long serialVersionUID = -620911121007236751L; + private int score = 0; - public Candidate(Route r, int s) { - route = r; - score = s; + @Override + public int compareTo(Trail other) { + return other.score() - this.score(); } + public Trail add(Route route, int score) { + add(route); + this.score += score; + return this; + } + + public Collection<Trail> derive(Collection<Trail> ongoing) { + Vector<Trail> derived = new Vector<>(); + for (Trail trail : ongoing) { + derived.add(trail.prepend(this)); + } + return derived; + } + + public boolean endsAt(Block block) { + return lastElement().endBlock() == block; + } + + private Trail prepend(Trail trail) { + addAll(0, trail); + score += trail.score; + return this; + } + + public int score() { + return score; + } + @Override - public String toString() { - return route.toString().replace(")", ", score: "+score+")"); + public synchronized String toString() { + StringBuilder sb = new StringBuilder("\n"+getClass().getSimpleName()); + sb.append("(Score "); + sb.append(score); + sb.append(": "); + for (Route r : this) { + sb.append(r.startBlock().name); + sb.append(" → "); + } + if (!isEmpty())sb.append(lastElement().endBlock().name); + return sb+")"; } + + + + } + private Context context; private Route route; @@ -52,7 +95,7 @@ public class RoutePrepper extends BaseClass implements Runnable{ context = c; } - private static TreeMap<Integer,LinkedList<Route>> availableRoutes(Context c){ + private static PriorityQueue<Trail> availableRoutes(Context c){ boolean error = false; Block startBlock = c.block(); @@ -61,14 +104,14 @@ public class RoutePrepper extends BaseClass implements Runnable{ Train train = c.train(); if (isNull(train) && (error=true)) LOG.warn("RoutePrepper.availableRoutes(…) called without a startBlock!"); - if (error) return new TreeMap<>(); + if (error) return new PriorityQueue<>(); Block destination = train.destination(); Direction startDirection = c.direction(); LOG.debug("RoutePrepper.availableRoutes({},{},{}), dest = {}",startBlock,startDirection,train,destination); - TreeMap<Integer, LinkedList<Route>> candidates = routesFrom(c); // map: score → [route] + PriorityQueue<Trail> candidates = routesFrom(c); // map: route → score if (isNull(destination)) { LOG.debug("{} has no destination, returning {}",train,candidates); @@ -77,114 +120,57 @@ public class RoutePrepper extends BaseClass implements Runnable{ LOG.debug("{} is heading for {}, starting breadth-first search…",train,destination); - HashMap<Route,Candidate> predecessors = new HashMap<>() { - private static final long serialVersionUID = -42682947866294566L; - - public String toString() { - return entrySet().stream() - .sorted((e1,e2) -> e1.getValue().toString().compareTo(e2.getValue().toString())) - .map(entry -> entry.getValue()+" → "+entry.getKey()) - .collect(Collectors.joining("\n")); - }; - }; - candidates.entrySet().stream().flatMap(entry -> entry.getValue().stream()).forEach(route -> predecessors.put(route, null)); - TreeMap<Integer,LinkedList<Route>> routesToDest = new TreeMap<>(); - - int level = 0; - int level2 = 0; - - while (!candidates.isEmpty()) { - LOG.debug("Candidates for level {}:",level); - candidates.entrySet().stream().flatMap(entry -> entry.getValue().stream()).forEach(route -> LOG.debug(" - {}",route)); - - TreeMap<Integer, LinkedList<Route>> queue = new TreeMap<>(); + for (int depth=1; depth<99; depth++) { + if (candidates.stream().filter(trail -> trail.endsAt(destination)).count() > 0) return candidates; // return connectingTrails + other routes, in case all connecting trails are occupied - while (!candidates.isEmpty()) { - Candidate candidate = pop(candidates); - LOG.debug(" - examining {}…",candidate); - - Block endBlock = candidate.route.endBlock(); - Direction endDir = candidate.route.endDirection; - - if (endBlock == destination) { - LOG.debug(" - {} reaches destination!",candidate); - int score = candidate.score; - - // The route we found leads to the destination block. - // However it might be the last route in a long path. - // Thus, we need to get the first route in this path: - while (predecessors.containsKey(candidate.route)) { - Candidate predecessor = predecessors.get(candidate.route); - if (isNull(predecessor)) break; - LOG.debug(" - {} is predecessed by {}",candidate,predecessor); - candidate = predecessor; - score += candidate.score; - } - - LOG.debug(" → path starts with {} and has total score of {}",candidate.route,score); - LinkedList<Route> routesForScore = routesToDest.get(score); - if (isNull(routesForScore)) routesToDest.put(score, routesForScore = new LinkedList<Route>()); - routesForScore.add(candidate.route); - continue; - } - - LOG.debug(" - {} not reaching {}, adding ongoing routes to queue:",candidate,destination); - TreeMap<Integer, LinkedList<Route>> successors = routesFrom(c.clone().block(endBlock).direction(endDir)); - while (!successors.isEmpty()) { - int score = successors.firstKey(); - LinkedList<Route> best = successors.remove(score); - score -= 25; // Nachfolgeroute - for (Route route : best) { - LOG.debug(" - queueing {} with score {}",route,score); - if (predecessors.containsKey(route)) { - LOG.debug("this route already has a predecessor: {}",predecessors.get(route)); - continue; // Route wurde bereits besucht - } - predecessors.put(route, candidate); - - LinkedList<Route> list = queue.get(score); - if (isNull(list)) queue.put(score, list = new LinkedList<>()); - list.add(route); - } - } + PriorityQueue<Trail> nextLevelCandidates = new PriorityQueue<Trail>(); + for (Trail trail : candidates) { + Route lastRoute = trail.lastElement(); + c.block(lastRoute.endBlock()).direction(lastRoute.endDirection); + PriorityQueue<Trail> ongoing = routesFrom(c); + if (!ongoing.isEmpty()) nextLevelCandidates.addAll(trail.derive(ongoing)); } - - if (!routesToDest.isEmpty()) { - if (level2++ > 5) return routesToDest; - } else LOG.debug("No routes to {} found with distance {}!",destination,level); - level ++; - candidates = queue; - } - LOG.debug("No more candidates for routes towards {}!",destination); + candidates = nextLevelCandidates; - return routesToDest; + } + return new PriorityQueue<>(); } private static Route chooseRoute(Context context) { LOG.debug("{}.chooseRoute({})", RoutePrepper.class.getSimpleName(), context); - TreeMap<Integer, LinkedList<Route>> availableRoutes = availableRoutes(context); + PriorityQueue<Trail> availableRoutes = availableRoutes(context); LOG.debug("available routes: {}",availableRoutes); while (!availableRoutes.isEmpty()) { if (context.invalidated()) break; LOG.debug("availableRoutes: {}", availableRoutes); - Entry<Integer, LinkedList<Route>> entry = availableRoutes.lastEntry(); - List<Route> preferredRoutes = entry.getValue(); - LOG.debug("preferredRoutes: {}", preferredRoutes); - Route selectedRoute = preferredRoutes.get(random.nextInt(preferredRoutes.size())); - HashSet<Tile> stuckTrace = context.train().stuckTrace(); // if train has been stopped in between two blocks lastly: - // only allow starting routes that do not conflict with current train position - if (isSet(stuckTrace) && !selectedRoute.path().containsAll(stuckTrace)) { - LOG.debug("Stuck train occupies tiles ({}) outside of {} – not allowed.", stuckTrace, selectedRoute); - } else if (selectedRoute.isFreeFor(context)) { - LOG.debug("Chose \"{}\" with priority {}.", selectedRoute, entry.getKey()); - return selectedRoute; + Vector<Route> preferredRoutes = new Vector<>(); + Integer score = null; + while (!availableRoutes.isEmpty()) { + Trail trail = availableRoutes.peek(); + if (score == null) score = trail.score(); + if (score != trail.score()) break; + availableRoutes.remove(trail); + preferredRoutes.add(trail.firstElement()); } - LOG.debug("Selected route \"{}\" is not free for {}", selectedRoute, context); - preferredRoutes.remove(selectedRoute); - if (preferredRoutes.isEmpty()) availableRoutes.remove(availableRoutes.lastKey()); + LOG.debug("preferredRoutes: {}", preferredRoutes); + + while (!preferredRoutes.isEmpty()) { + Route selectedRoute = preferredRoutes.remove(random.nextInt(preferredRoutes.size())); + HashSet<Tile> stuckTrace = context.train().stuckTrace(); // if train has been stopped in between two blocks lastly: + // only allow starting routes that do not conflict with current train position + if (isSet(stuckTrace) && !selectedRoute.path().containsAll(stuckTrace)) { + LOG.debug("Stuck train occupies tiles ({}) outside of {} – not allowed.", stuckTrace, selectedRoute); + continue; + } else if (!selectedRoute.isFreeFor(context)) { + LOG.debug("Selected route \"{}\" is not free for {}", selectedRoute, context); + continue; + } + LOG.debug("Chose \"{}\" with priority {}.", selectedRoute, score); + return selectedRoute; + } } return null; } @@ -217,21 +203,6 @@ public class RoutePrepper extends BaseClass implements Runnable{ preparedListener = l; } - private static Candidate pop(TreeMap<Integer, LinkedList<Route>> candidates) { - while (!candidates.isEmpty()) { - int score = candidates.firstKey(); - LinkedList<Route> list = candidates.get(score); - if (isNull(list) || list.isEmpty()) { - candidates.remove(score); - } else { - Candidate candidate = new Candidate(list.removeFirst(),score); - if (list.isEmpty()) candidates.remove(score); - return candidate; - } - } - return null; - } - public boolean prepareRoute() { if (isNull(context) || context.invalidated()) return fail(); route = chooseRoute(context); @@ -250,28 +221,28 @@ public class RoutePrepper extends BaseClass implements Runnable{ return route; } - private static TreeMap<Integer,LinkedList<Route>> routesFrom(Context c){ + private static PriorityQueue<Trail> routesFrom(Context context){ boolean error = false; - Block startBlock = c.block(); + Block startBlock = context.block(); if (isNull(startBlock) && (error=true)) LOG.warn("RoutePrepper.routesFrom(…) called without a startBlock!"); - Train train = c.train(); + Train train = context.train(); if (isNull(train) && (error=true)) LOG.warn("RoutePrepper.routesFrom(…) called without a startBlock!"); if (error) return null; Block destination = train.destination(); - Direction startDirection = c.direction(); + Direction startDirection = context.direction(); LOG.debug(" RoutePrepper.routesFrom({},{},{}), dest = {}:",startBlock,startDirection,train,destination); - TreeMap<Integer, LinkedList<Route>> routes = new TreeMap<>(); + PriorityQueue<Trail> trails = new PriorityQueue<>(); for (Route route : startBlock.leavingRoutes()) { - int score = 0; - + int score = (route.endBlock() == destination) ? 100_000 : 0; + if (isSet(startDirection) && route.startDirection != startDirection) { // Route startet entgegen der aktuellen Fahrtrichtung des Zuges if (!train.pushPull) continue; // Zug kann nicht wenden if (!startBlock.turnAllowed) continue; // Wenden im Block nicht gestattet @@ -286,16 +257,10 @@ public class RoutePrepper extends BaseClass implements Runnable{ LOG.debug(" …overridden by destination of train!", route, train); } - if (route.endBlock() == destination) score = 100_000; - - - LinkedList<Route> routesForScore = routes.get(score); - if (isNull(routesForScore)) routes.put(score, routesForScore = new LinkedList<Route>()); - LOG.debug(" → candidate!"); - routesForScore.add(route); + trails.add(new Trail().add(route,score)); } - return routes; + return trails; } @Override