You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

220 lines
8.2 KiB

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<EventListener> failListeners = new LinkedList<>();
private List<EventListener> foundListeners = new LinkedList<>();
private List<EventListener> lockedListeners = new LinkedList<>();
private List<EventListener> preparedListeners= new LinkedList<>();
public RoutePrepper(Context c) {
List<String> 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<Integer, List<Route>> availableRoutes(Context context, HashSet<Route> 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<Integer, List<Route>> availableRoutes = new TreeMap<Integer, List<Route>>();
boolean error = false;
if (isNull(block) && (error = true))
LOG.warn("{} → {}.availableRoutes called without context.block!", inset, Train.class.getSimpleName());
if (isNull(train) && (error = true))
LOG.warn("{}→ {}.availableRoutes called without context.train!", inset, Train.class.getSimpleName());
if (error) return availableRoutes;
if (isSet(startDirection)) {
LOG.debug("{}- Looking for {}-bound routes from {}", inset, startDirection, block);
} else {
LOG.debug("{}- Looking for all routes from {}", inset, block);
}
Block destination = train.destination();
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<Tile> stuckTrace = 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) && visitedRoutes.isEmpty() && !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<Integer, List<Route>> forwardRoutes = availableRoutes(forwardContext, visitedRoutes);
visitedRoutes.remove(routeCandidate);
if (forwardRoutes.isEmpty()) continue; // the candidate does not lead to a block, from which routes
// to the destination exist
Entry<Integer, List<Route>> entry = forwardRoutes.lastEntry();
LOG.debug("{}→ The following routes have connections to {}:", inset, destination);
for (Route rt : entry.getValue()) LOG.debug("{} - {}", inset, rt.shortName());
priority += entry.getKey() - 10;
}
}
List<Route> routeSet = availableRoutes.get(priority);
if (isNull(routeSet)) {
routeSet = new Vector<Route>();
availableRoutes.put(priority, routeSet);
}
routeSet.add(routeCandidate);
if (routeCandidate.endBlock() == destination) break; // direct connection to destination discovered, quit
// search
}
if (!availableRoutes.isEmpty())
LOG.debug("{}→ Routes from {}: {}", inset, block, availableRoutes.isEmpty() ? "none" : "");
for (Entry<Integer, List<Route>> entry : availableRoutes.entrySet()) {
LOG.debug("{} - Priority {}:", inset, entry.getKey());
for (Route r : entry.getValue()) LOG.debug("{} - {}", inset, r.shortName());
}
return availableRoutes;
}
private static Route chooseRoute(Context context) {
LOG.debug("{}.chooseRoute({})", RoutePrepper.class.getSimpleName(), context);
TreeMap<Integer, List<Route>> availableRoutes = availableRoutes(context, new HashSet<Route>());
while (!availableRoutes.isEmpty()) {
if (context.invalidated()) break;
LOG.debug("availableRoutes: {}", availableRoutes);
Entry<Integer, List<Route>> entry = availableRoutes.lastEntry();
List<Route> preferredRoutes = entry.getValue();
LOG.debug("preferredRoutes: {}", preferredRoutes);
Route selectedRoute = preferredRoutes.get(random.nextInt(preferredRoutes.size()));
if (selectedRoute.isFreeFor(context)) {
LOG.debug("Chose \"{}\" with priority {}.", selectedRoute, entry.getKey());
return selectedRoute;
}
LOG.debug("Selected route \"{}\" is not free for {}", selectedRoute, context);
preferredRoutes.remove(selectedRoute);
if (preferredRoutes.isEmpty()) availableRoutes.remove(availableRoutes.lastKey());
}
return null;
}
private void notify(List<EventListener> 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();
}
}