diff --git a/client/src/main/java/ru/trader/controllers/AnalyzerProgress.java b/client/src/main/java/ru/trader/controllers/AnalyzerProgress.java new file mode 100644 index 0000000..542d042 --- /dev/null +++ b/client/src/main/java/ru/trader/controllers/AnalyzerProgress.java @@ -0,0 +1,250 @@ +package ru.trader.controllers; + +import javafx.application.Platform; +import javafx.beans.property.LongProperty; +import javafx.beans.property.SimpleLongProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressBar; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import ru.trader.core.MarketAnalyzer; +import ru.trader.core.MarketAnalyzerCallBack; +import ru.trader.core.Place; +import ru.trader.core.Vendor; +import ru.trader.graph.Connectable; +import ru.trader.graph.GraphCallBack; +import ru.trader.graph.RouteSearcherCallBack; +import ru.trader.graph.Vertex; + +import java.util.concurrent.atomic.AtomicLong; + +public class AnalyzerProgress { + private VBox tasks; + private TaskVisual task; + + public void show(Parent main, String text, MarketAnalyzer analyzer){ + tasks = new VBox(5); + task = new TaskVisual(text); + Button button = new Button("Cancel"); + + Scene scene = new Scene(tasks, 200, 200); + Stage stage = new Stage(); + stage.setScene(scene); + stage.show(); + + MarketAnalyzerCallBack callBack = new AnalyzerCallBack(task); + analyzer.setCallback(callBack); + button.setOnAction(e -> callBack.cancel()); + createProgress(task); + tasks.getChildren().add(button); + + task.getSubTasks().addListener((ListChangeListener)l -> { + while (l.next()) { + if (l.wasRemoved()) { + l.getRemoved().forEach(this::removeProgress); + } + if (l.wasAdded()) { + l.getAddedSubList().forEach(this::createProgress); + } + } + }); + } + + private void createProgress(TaskVisual task){ + HBox hBox = new HBox(10); + hBox.setUserData(task); + Label txt = new Label("Процесс"); + ProgressBar bar = new ProgressBar(); + txt.textProperty().bind(task.messageProperty()); + bar.progressProperty().bind(task.countProperty().divide(task.maxProperty())); + hBox.getChildren().addAll(txt, bar); + tasks.getChildren().addAll(hBox); + } + + private void removeProgress(TaskVisual task){ + tasks.getChildren().removeIf(n -> task.equals(n.getUserData())); + } + + private class TaskVisual { + private final StringProperty message; + private final LongProperty count; + private final LongProperty max; + private final ObservableList subTasks; + + private TaskVisual(String text) { + message = new SimpleStringProperty(text); + count = new SimpleLongProperty(0); + max = new SimpleLongProperty(1); + subTasks = FXCollections.observableArrayList(); + } + + public String getMessage() { + return message.get(); + } + + public StringProperty messageProperty() { + return message; + } + + public void setMessage(String message) { + Platform.runLater(() -> this.message.set(message)); + } + + public long getCount() { + return count.get(); + } + + public LongProperty countProperty() { + return count; + } + + public void setCount(long count) { + Platform.runLater(() -> this.count.set(count)); + } + + public long getMax() { + return max.get(); + } + + public LongProperty maxProperty() { + return max; + } + + public void setMax(long max) { + Platform.runLater(() -> this.max.set(max)); + } + + public ObservableList getSubTasks() { + return subTasks; + } + + public void addSubTask(TaskVisual task){ + Platform.runLater(() -> { + synchronized (subTasks) { + subTasks.add(task); + } + }); + } + + public void removeSubTask(TaskVisual task){ + Platform.runLater(() -> { + synchronized (subTasks) { + subTasks.remove(task); + } + }); + } + } + + private class AnalyzerCallBack extends MarketAnalyzerCallBack { + private final TaskVisual task; + private final AtomicLong count = new AtomicLong(); + + private AnalyzerCallBack(TaskVisual task) { + this.task = task; + count.set(0); + } + + @Override + protected RouteSearcherCallBack getRouteSearcherCallBackInstance() { + return new RSCallBack(task); + } + + + @Override + protected GraphCallBack getGraphCallBackInstance() { + TaskVisual subtask = new TaskVisual("Build graph of system"); + task.addSubTask(subtask); + return new GCallBack(subtask, task); + } + + @Override + protected void onEnd() { + task.setMessage("Finish"); + } + + @Override + public void setCount(long count) { + task.setMax(count); + } + + @Override + public void inc() { + task.setCount(count.incrementAndGet()); + } + } + + private class RSCallBack extends RouteSearcherCallBack { + private final TaskVisual task; + + private RSCallBack(TaskVisual task) { + this.task = task; + } + + @Override + protected GraphCallBack getGraphCallBackInstance() { + TaskVisual subtask = new TaskVisual("Build graph of stations"); + task.addSubTask(subtask); + return new GCallBack<>(subtask, task); + } + } + + private class GCallBack> extends GraphCallBack { + private final TaskVisual task; + private final TaskVisual owner; + private final AtomicLong count = new AtomicLong(); + + private GCallBack(TaskVisual task, TaskVisual owner) { + this.task = task; + this.owner = owner; + count.set(0); + } + + @Override + public void onStartBuild(T from) { + task.setMessage(String.format("Build graph from %s", from)); + } + + @Override + public void onEndBuild() { + task.setMessage(""); + } + + @Override + public void onStartFind(Vertex from, Vertex to) { + if (to != null) { + task.setMessage(String.format("Find path from %s graph to %s", from.getEntry(), to.getEntry())); + } else { + task.setMessage(String.format("Find path from %s graph", from.getEntry())); + } + } + + @Override + public void onFound() { + task.setMessage(""); + } + + @Override + public void onEndFind() { + owner.removeSubTask(task); + } + + @Override + public void setCount(long count) { + task.setMax(count); + } + + @Override + public void inc() { + task.setCount(count.incrementAndGet()); + } + } +} diff --git a/client/src/main/java/ru/trader/controllers/PathsController.java b/client/src/main/java/ru/trader/controllers/PathsController.java index 76c9228..8340eea 100644 --- a/client/src/main/java/ru/trader/controllers/PathsController.java +++ b/client/src/main/java/ru/trader/controllers/PathsController.java @@ -1,6 +1,8 @@ package ru.trader.controllers; import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.Parent; import javafx.scene.control.TableView; @@ -29,7 +31,7 @@ public class PathsController { } - public PathRouteModel showDialog(Parent parent, Parent content, Collection paths) { + public PathRouteModel showDialog(Parent parent, Parent content, ObservableList paths) { init(paths); @@ -46,10 +48,17 @@ public class PathsController { return tblPaths.getSelectionModel().getSelectedItem(); } - private void init(Collection paths) { + private void init(ObservableList paths) { tblPaths.getSelectionModel().clearSelection(); this.paths.clear(); this.paths.addAll(paths); + paths.addListener((ListChangeListener) l -> { + while (l.next()) { + if (l.wasAdded()) { + this.paths.addAll(l.getAddedSubList()); + } + } + }); } } diff --git a/client/src/main/java/ru/trader/controllers/RouterController.java b/client/src/main/java/ru/trader/controllers/RouterController.java index cf0fae4..1cd06c4 100644 --- a/client/src/main/java/ru/trader/controllers/RouterController.java +++ b/client/src/main/java/ru/trader/controllers/RouterController.java @@ -1,6 +1,7 @@ package ru.trader.controllers; +import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; @@ -227,11 +228,12 @@ public class RouterController { SystemModel t = target.getValue(); StationModel sS = sStation.getValue(); StationModel tS = tStation.getValue(); + Platform.runLater(() -> { PathRouteModel path = Screeners.showRouters(market.getRoutes(s, sS, t, tS, totalBalance.getValue().doubleValue())); if (path!=null){ orders.addAll(path.getOrders()); addRouteToPath(path); - } + }}); } public void showTopRoutes(){ diff --git a/client/src/main/java/ru/trader/model/MarketModel.java b/client/src/main/java/ru/trader/model/MarketModel.java index 9b63fb0..c93ce23 100644 --- a/client/src/main/java/ru/trader/model/MarketModel.java +++ b/client/src/main/java/ru/trader/model/MarketModel.java @@ -9,12 +9,17 @@ import javafx.collections.ObservableList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.trader.World; +import ru.trader.controllers.AnalyzerProgress; +import ru.trader.controllers.Screeners; import ru.trader.core.*; import ru.trader.graph.PathRoute; import ru.trader.model.support.BindingsHelper; import ru.trader.model.support.Notificator; import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.*; +import java.util.stream.Collectors; public class MarketModel { @@ -167,29 +172,37 @@ public class MarketModel { } public ObservableList getRoutes(SystemModel from, StationModel stationFrom, SystemModel to, StationModel stationTo, double balance) { - Collection routes; - if (stationFrom != null && stationFrom != ModelFabric.NONE_STATION){ - if (stationTo != null && stationTo != ModelFabric.NONE_STATION){ - routes = analyzer.getPaths(stationFrom.getStation(), stationTo.getStation(), balance); - } else { - if (to != null && to != ModelFabric.NONE_SYSTEM){ - routes = analyzer.getPaths(stationFrom.getStation(), to.getSystem(), balance); + AnalyzerProgress progress = new AnalyzerProgress(); + progress.show(Screeners.getMainScreen(), "Get routes", analyzer); + ExecutorService executor = Executors.newSingleThreadExecutor(); + ObservableList res = FXCollections.observableArrayList(); + executor.execute(() -> { + Collection routes; + + if (stationFrom != null && stationFrom != ModelFabric.NONE_STATION) { + if (stationTo != null && stationTo != ModelFabric.NONE_STATION) { + routes = analyzer.getPaths(stationFrom.getStation(), stationTo.getStation(), balance); + } else { + if (to != null && to != ModelFabric.NONE_SYSTEM) { + routes = analyzer.getPaths(stationFrom.getStation(), to.getSystem(), balance); + } else { + routes = analyzer.getPaths(stationFrom.getStation(), balance); + } + } } else { - routes = analyzer.getPaths(stationFrom.getStation(), balance); + if (stationTo != null && stationTo != ModelFabric.NONE_STATION) { + routes = analyzer.getPaths(from.getSystem(), stationTo.getStation(), balance); + } else { + if (to != null && to != ModelFabric.NONE_SYSTEM) { + routes = analyzer.getPaths(from.getSystem(), to.getSystem(), balance); + } else { + routes = analyzer.getPaths(from.getSystem(), balance); + } + } } - } - } else { - if (stationTo != null && stationTo != ModelFabric.NONE_STATION){ - routes = analyzer.getPaths(from.getSystem(), stationTo.getStation(), balance); - } else { - if (to != null && to != ModelFabric.NONE_SYSTEM){ - routes = analyzer.getPaths(from.getSystem(), to.getSystem(), balance); - } else { - routes = analyzer.getPaths(from.getSystem(), balance); - } - } - } - return BindingsHelper.observableList(routes, modeler::get); + routes.stream().map(modeler::get).forEach(res::add); + }); + return res; } diff --git a/core/src/main/java/ru/trader/core/MarketAnalyzer.java b/core/src/main/java/ru/trader/core/MarketAnalyzer.java index f4d039a..dcb11e4 100644 --- a/core/src/main/java/ru/trader/core/MarketAnalyzer.java +++ b/core/src/main/java/ru/trader/core/MarketAnalyzer.java @@ -10,6 +10,7 @@ public class MarketAnalyzer { private final static Logger LOG = LoggerFactory.getLogger(MarketAnalyzer.class); private final Market market; + private MarketAnalyzerCallBack callback; private MarketFilter filter; private double tank; private double maxDistance; @@ -21,11 +22,20 @@ public class MarketAnalyzer { private final static Comparator orderComparator = (o1, o2) -> o2.compareTo(o1); public MarketAnalyzer(Market market) { + this(market, new MarketAnalyzerCallBack()); + } + + public MarketAnalyzer(Market market, MarketAnalyzerCallBack callback) { this.market = market; + this.callback = callback; this.limit = 100; this.segmentSize = 0; } + public void setCallback(MarketAnalyzerCallBack callback) { + this.callback = callback; + } + public void setFilter(MarketFilter filter) { this.filter = filter; } @@ -56,19 +66,23 @@ public class MarketAnalyzer { public Collection getTop(double balance){ LOG.debug("Get top {}", limit); - Iterable places = getPlaces(); + Collection places = getPlaces(); List top = new ArrayList<>(limit); + callback.setCount(places.size()); for (Place place : places) { - LOG.trace("Check place {}", place); - Collection orders = getOrders(place, balance, top.isEmpty() ? 0 : top.get(top.size()-1).getProfit()); - TopList.addAllToTop(top, orders, limit, orderComparator); + if (callback.isCancel()) break; + LOG.trace("Check place {}", place); + Collection orders = getOrders(place, balance, top.isEmpty() ? 0 : top.get(top.size()-1).getProfit()); + TopList.addAllToTop(top, orders, limit, orderComparator); + callback.inc(); } + callback.onEnd(); return top; } public Collection getOrders(Vendor vendor, double balance) { Collection places = getPlaces(); - Graph graph = new Graph(vendor.getPlace(), places, tank, maxDistance, true, jumps, Path::new); + Graph graph = new Graph(vendor.getPlace(), places, tank, maxDistance, true, jumps, Path::new, callback.onStartGraph()); return getOrders(graph, Collections.singleton(vendor), balance, 0); } @@ -78,18 +92,22 @@ public class MarketAnalyzer { private Collection getOrders(Place place, double balance, double lowProfit) { Collection places = getPlaces(); - Graph graph = new Graph<>(place, places, tank, maxDistance, true, jumps, Path::new); + Graph graph = new Graph<>(place, places, tank, maxDistance, true, jumps, Path::new, callback.onStartGraph()); return getOrders(graph, place.get(), balance, lowProfit); } private Collection getOrders(Graph graph, Collection sellers, double balance, double lowProfit) { List res = new ArrayList<>(20); + callback.setCount(sellers.size()); for (Vendor vendor : sellers) { + if (callback.isCancel()) break; if (isFiltered(vendor)){ LOG.trace("Is filtered, skip"); + callback.inc(); continue; } for (Offer sell : vendor.getAllSellOffers()) { + if (callback.isCancel()) break; LOG.trace("Sell offer {}", sell); if (sell.getCount() == 0) continue; long count = Order.getMaxCount(sell, balance, cargo); @@ -97,6 +115,7 @@ public class MarketAnalyzer { if (count == 0) continue; Iterator buyers = market.getStatBuy(sell.getItem()).getOffers().descendingIterator(); while (buyers.hasNext()){ + if (callback.isCancel()) break; Offer buy = buyers.next(); if (isFiltered(buy.getVendor())){ LOG.trace("Is filtered, skip"); @@ -116,6 +135,7 @@ public class MarketAnalyzer { res.add(order); } } + callback.inc(); } res.sort(orderComparator); return res; @@ -123,17 +143,22 @@ public class MarketAnalyzer { private Collection getOrders(Collection sellers, Collection buyers, double balance, double lowProfit) { List res = new ArrayList<>(); + callback.setCount(sellers.size()); for (Vendor seller : sellers) { + if (callback.isCancel()) break; if (isFiltered(seller)){ LOG.trace("Is filtered, skip"); + callback.inc(); continue; } for (Offer sell : seller.getAllSellOffers()) { + if (callback.isCancel()) break; if (sell.getCount() == 0) continue; long count = Order.getMaxCount(sell, balance, cargo); LOG.trace("Sell offer {}, count = {}", sell, count); if (count == 0) continue; for (Vendor buyer : buyers) { + if (callback.isCancel()) break; if (isFiltered(buyer)){ LOG.trace("Is filtered, skip"); continue; @@ -150,6 +175,7 @@ public class MarketAnalyzer { } } } + callback.inc(); } res.sort(orderComparator); return res; @@ -209,98 +235,131 @@ public class MarketAnalyzer { } public Collection getPaths(Vendor from, double balance){ - RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize); + callback.setCount(1); + RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize, callback.onStartSearch()); Collection vendors = getVendors(); - return searcher.getPaths(from, vendors, jumps, balance, cargo, limit); + Collection res = searcher.getPaths(from, vendors, jumps, balance, cargo, limit); + callback.inc(); + callback.onEndSearch(); + return res; } public Collection getPaths(Place from, double balance){ - RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize); + List top = new ArrayList<>(limit); Collection vendors = getVendors(); + callback.setCount(vendors.size()); + RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize, callback.onStartSearch()); for (Vendor vendor : from.get()) { + if (callback.isCancel()) break; if (isFiltered(vendor)){ LOG.trace("Is filtered, skip"); + callback.inc(); continue; } Collection paths = searcher.getPaths(vendor, vendors, jumps, balance, cargo, limit); - if (paths.size()>0){ - return paths; - } + TopList.addAllToTop(top, paths, limit, RouteGraph.byProfitComparator); + callback.inc(); } - return Collections.emptyList(); + callback.onEndSearch(); + return top; } public Collection getPaths(Vendor from, Vendor to, double balance){ - RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize); - return searcher.getPaths(from, to, getVendors(), jumps, balance, cargo, limit); + callback.setCount(1); + RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize, callback.onStartSearch()); + Collection res = searcher.getPaths(from, to, getVendors(), jumps, balance, cargo, limit); + callback.inc(); + callback.onEndSearch(); + return res; } public Collection getPaths(Place from, Place to, double balance){ List top = new ArrayList<>(limit); - RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize); Collection vendors = getVendors(); Collection fVendors = from.get(); Collection toVendors = to.get(); int count = (int) Math.ceil(limit / fVendors.size()); + callback.setCount(fVendors.size() * toVendors.size()); + RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize, callback.onStartSearch()); for (Vendor fromVendor : fVendors) { + if (callback.isCancel()) break; if (isFiltered(fromVendor)){ LOG.trace("Is filtered, skip"); + callback.inc(); continue; } for (Vendor toVendor : toVendors) { + if (callback.isCancel()) break; if (isFiltered(toVendor)){ LOG.trace("Is filtered, skip"); + callback.inc(); continue; } Collection paths = searcher.getPaths(fromVendor, toVendor, vendors, jumps, balance, cargo, count); TopList.addAllToTop(top, paths, limit, RouteGraph.byProfitComparator); + callback.inc(); } } + callback.onEndSearch(); return top; } public Collection getPaths(Vendor from, Place to, double balance){ List top = new ArrayList<>(limit); - RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize); Collection vendors = getVendors(); Collection toVendors = to.get(); int count = (int) Math.ceil(limit / toVendors.size()); + callback.setCount(toVendors.size()); + RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize, callback.onStartSearch()); for (Vendor toVendor : toVendors) { + if (callback.isCancel()) break; if (isFiltered(toVendor)){ LOG.trace("Is filtered, skip"); + callback.inc(); continue; } Collection paths = searcher.getPaths(from, toVendor, vendors, jumps, balance, cargo, count); TopList.addAllToTop(top, paths, limit, RouteGraph.byProfitComparator); + callback.inc(); } + callback.onEndSearch(); return top; } public Collection getPaths(Place from, Vendor to, double balance){ List top = new ArrayList<>(limit); - RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize); Collection vendors = getVendors(); Collection fVendors = from.get(); int count = (int) Math.ceil(limit / fVendors.size()); + callback.setCount(fVendors.size()); + RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize, callback.onStartSearch()); for (Vendor fromVendor : fVendors) { + if (callback.isCancel()) break; if (isFiltered(fromVendor)){ LOG.trace("Is filtered, skip"); + callback.inc(); continue; } Collection paths = searcher.getPaths(fromVendor, to, vendors, jumps, balance, cargo, count); TopList.addAllToTop(top, paths, limit, RouteGraph.byProfitComparator); + callback.inc(); } + callback.onEndSearch(); return top; } public Collection getTopPaths(double balance){ List top = new ArrayList<>(limit); - RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize); Collection vendors = getVendors(); + callback.setCount(vendors.size()); + RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize, callback.onStartSearch()); for (Vendor vendor : vendors) { + if (callback.isCancel()) break; Collection paths = searcher.getPaths(vendor, vendor, vendors, jumps, balance, cargo, 3); TopList.addAllToTop(top, paths, limit, RouteGraph.byProfitComparator); + callback.inc(); } + callback.onEndSearch(); return top; } diff --git a/core/src/main/java/ru/trader/core/MarketAnalyzerCallBack.java b/core/src/main/java/ru/trader/core/MarketAnalyzerCallBack.java new file mode 100644 index 0000000..ee07c00 --- /dev/null +++ b/core/src/main/java/ru/trader/core/MarketAnalyzerCallBack.java @@ -0,0 +1,62 @@ +package ru.trader.core; + +import ru.trader.graph.GraphCallBack; +import ru.trader.graph.RouteSearcherCallBack; + +public class MarketAnalyzerCallBack { + private volatile boolean cancel = false; + private RouteSearcherCallBack callbackRoute; + private GraphCallBack callbackGraph; + + + protected RouteSearcherCallBack getRouteSearcherCallBackInstance(){ + return new RouteSearcherCallBack(); + } + + protected GraphCallBack getGraphCallBackInstance(){ + return new GraphCallBack<>(); + } + + public final GraphCallBack onStartGraph(){ + callbackGraph = getGraphCallBackInstance(); + return callbackGraph; + } + + public final void onEndGraph(){ + callbackGraph = null; + } + + public final RouteSearcherCallBack onStartSearch(){ + callbackRoute = getRouteSearcherCallBackInstance(); + return callbackRoute; + } + + public final void onEndSearch(){ + callbackRoute = null; + onEnd(); + } + + protected void onEnd(){} + + public void setCount(long count){} + public void inc(){} + + + public final boolean isCancel() { + return cancel; + } + + public final void cancel(){ + if (cancel) return; + this.cancel = true; + if (callbackRoute != null){ + callbackRoute.cancel(); + callbackRoute = null; + } + if (callbackGraph != null){ + callbackGraph.cancel(); + callbackGraph = null; + } + } + +} diff --git a/core/src/main/java/ru/trader/graph/Graph.java b/core/src/main/java/ru/trader/graph/Graph.java index 60786cb..2a0bea0 100644 --- a/core/src/main/java/ru/trader/graph/Graph.java +++ b/core/src/main/java/ru/trader/graph/Graph.java @@ -23,6 +23,7 @@ public class Graph> { protected final Vertex root; protected final Map> vertexes; + private final GraphCallBack callback; protected final double stock; protected final double maxDistance; @@ -48,15 +49,22 @@ public class Graph> { } public Graph(T start, Collection set, double stock, double maxDistance, boolean withRefill, int maxDeep, PathConstructor pathFabric) { + this(start, set, stock, maxDistance, withRefill, maxDeep, pathFabric, new GraphCallBack<>()); + } + + public Graph(T start, Collection set, double stock, double maxDistance, boolean withRefill, int maxDeep, PathConstructor pathFabric, GraphCallBack callback) { this.maxDistance = maxDistance; this.stock = stock; this.withRefill = withRefill; this.pathFabric = pathFabric; + this.callback = callback; root = new Vertex<>(start); root.setLevel(maxDeep); vertexes = new ConcurrentHashMap<>(50, 0.9f, THRESHOLD); vertexes.put(root.getEntry(), root); + callback.onStartBuild(start); build(root, set, maxDeep, stock); + callback.onEndBuild(); } private void build(Vertex root, Collection set, int maxDeep, double stock) { @@ -81,7 +89,9 @@ public class Graph> { } private void findPathsTo(Vertex target, TopList> res, int deep){ + callback.onStartFind(root, target); POOL.invoke(new PathFinder(res, pathFabric.build(root), target, deep-1, stock)); + callback.onEndFind(); } public List> getPathsTo(T entry){ @@ -95,7 +105,9 @@ public class Graph> { public TopList> getPathsTo(T entry, int max, int deep){ Vertex target = getVertex(entry); TopList> paths = newTopList(max); + callback.setCount(1); findPathsTo(target, paths, deep); + callback.inc(); paths.finish(); return paths; } @@ -106,12 +118,15 @@ public class Graph> { public TopList> getPaths(int count, int deep){ TopList> paths = newTopList(count); + callback.setCount(vertexes.size()); for (Vertex target : vertexes.values()) { + if (callback.isCancel()) break; TopList> p = newTopList(minJumps); findPathsTo(target, p, deep); for (Path path : p.getList()) { paths.add(path); } + callback.inc(); } paths.finish(); return paths; @@ -204,6 +219,7 @@ public class Graph> { ArrayList subTasks = new ArrayList<>(set.size()); Iterator iterator = set.iterator(); while (iterator.hasNext()) { + if (callback.isCancel()) break; T entry = iterator.next(); if (entry == vertex.getEntry()) continue; double distance = vertex.getEntry().getDistance(entry); @@ -236,14 +252,22 @@ public class Graph> { } if (subTasks.size() == THRESHOLD || !iterator.hasNext()){ for (GraphBuilder subTask : subTasks) { - subTask.join(); + if (callback.isCancel()){ + subTask.cancel(true); + } else { + subTask.join(); + } } subTasks.clear(); } } if (!subTasks.isEmpty()){ for (GraphBuilder subTask : subTasks) { - subTask.join(); + if (callback.isCancel()){ + subTask.cancel(true); + } else { + subTask.join(); + } } subTasks.clear(); } @@ -282,6 +306,7 @@ public class Graph> { synchronized (paths){ if (!paths.add(path)) complete(null); } + callback.onFound(); } } if (deep > 0 ){ @@ -291,7 +316,7 @@ public class Graph> { Iterator> iterator = source.getEdges().iterator(); while (iterator.hasNext()) { Edge next = iterator.next(); - if (isDone()) break; + if (isDone() || callback.isCancel()) break; // target already added if source consist edge if (next.isConnect(target)) continue; if (!distanceFilter.test(next.getLength())) continue; @@ -305,8 +330,8 @@ public class Graph> { subTasks.add(task); if (subTasks.size() == THRESHOLD || !iterator.hasNext()){ for (PathFinder subTask : subTasks) { - if (isDone()) { - subTask.cancel(false); + if (isDone() || callback.isCancel()) { + subTask.cancel(callback.isCancel()); } else { subTask.join(); } @@ -316,8 +341,8 @@ public class Graph> { } if (!subTasks.isEmpty()){ for (PathFinder subTask : subTasks) { - if (isDone()) { - subTask.cancel(false); + if (isDone() || callback.isCancel()) { + subTask.cancel(callback.isCancel()); } else { subTask.join(); } diff --git a/core/src/main/java/ru/trader/graph/GraphCallBack.java b/core/src/main/java/ru/trader/graph/GraphCallBack.java new file mode 100644 index 0000000..947d0aa --- /dev/null +++ b/core/src/main/java/ru/trader/graph/GraphCallBack.java @@ -0,0 +1,27 @@ +package ru.trader.graph; + +public class GraphCallBack> { + + private volatile boolean cancel = false; + + public void onStartBuild(T from){} + public void onEndBuild(){} + + + public void onStartFind(Vertex from, Vertex to){} + public void onFound(){} + public void onEndFind(){} + + + public void setCount(long count){} + public void inc(){} + + public final boolean isCancel() { + return cancel; + } + + public final void cancel(){ + this.cancel = true; + } + +} diff --git a/core/src/main/java/ru/trader/graph/RouteGraph.java b/core/src/main/java/ru/trader/graph/RouteGraph.java index 26e9cb2..cfabdf3 100644 --- a/core/src/main/java/ru/trader/graph/RouteGraph.java +++ b/core/src/main/java/ru/trader/graph/RouteGraph.java @@ -31,9 +31,12 @@ public class RouteGraph extends Graph { public RouteGraph(Vendor start, Collection set, double stock, double maxDistance, boolean withRefill, int maxDeep) { this(start, set, stock, maxDistance, withRefill, maxDeep, false); } - public RouteGraph(Vendor start, Collection set, double stock, double maxDistance, boolean withRefill, int maxDeep, boolean groupRes) { - super(start, set, stock, maxDistance, withRefill, maxDeep, groupRes ? PathRoute::buildAvg : PathRoute::new); + this(start, set, stock, maxDistance, withRefill, maxDeep, groupRes, new GraphCallBack<>()); + } + + public RouteGraph(Vendor start, Collection set, double stock, double maxDistance, boolean withRefill, int maxDeep, boolean groupRes, GraphCallBack callback) { + super(start, set, stock, maxDistance, withRefill, maxDeep, groupRes ? PathRoute::buildAvg : PathRoute::new, callback); if (groupRes){ this.groupRes = maxDeep > minJumps; } diff --git a/core/src/main/java/ru/trader/graph/RouteSearcher.java b/core/src/main/java/ru/trader/graph/RouteSearcher.java index fee6e15..1aeef5a 100644 --- a/core/src/main/java/ru/trader/graph/RouteSearcher.java +++ b/core/src/main/java/ru/trader/graph/RouteSearcher.java @@ -14,17 +14,19 @@ public class RouteSearcher { private final static ForkJoinPool POOL = new ForkJoinPool(); private final static int THRESHOLD = (int) Math.ceil(Runtime.getRuntime().availableProcessors()/2.0); + private final RouteSearcherCallBack callback; private final double maxDistance; private final double stock; private final int segmentSize; public RouteSearcher(double maxDistance, double stock) { - this(maxDistance, stock, 0); + this(maxDistance, stock, 0, new RouteSearcherCallBack()); } - public RouteSearcher(double maxDistance, double stock, int segmentSize) { + public RouteSearcher(double maxDistance, double stock, int segmentSize, RouteSearcherCallBack callback) { this.maxDistance = maxDistance; this.stock = stock; this.segmentSize = segmentSize; + this.callback = callback; } public List getPaths(Vendor from, Vendor to, Collection vendors, int jumps, double balance, int cargo, int limit){ @@ -56,8 +58,10 @@ public class RouteSearcher { @Override protected List compute() { + if (callback.isCancel()) return Collections.emptyList(); LOG.trace("Start search route to {} from {}, jumps {}", source, target, jumps); - RouteGraph sGraph = new RouteGraph(source, vendors, stock, maxDistance, true, jumps, true); + GraphCallBack gCallBack = callback.onStart(); + RouteGraph sGraph = new RouteGraph(source, vendors, stock, maxDistance, true, jumps, true, gCallBack); int jumpsToAll = sGraph.getMinJumps(); LOG.trace("Segment jumps {}", jumpsToAll); sGraph.setCargo(cargo); @@ -87,8 +91,10 @@ public class RouteSearcher { res.add(path); } } + if (callback.isCancel()) break; subTasks.clear(); for (int taskIndex = 0; taskIndex < THRESHOLD && i+taskIndex < paths.size(); taskIndex++) { + if (callback.isCancel()) break; PathRoute path = (PathRoute) paths.get(i+taskIndex); double newBalance = balance + path.getRoot().getProfit(); SegmentSearcher task = new SegmentSearcher(path.get(), target, vendors, jumps - path.getLength(), newBalance, cargo, 1); @@ -103,6 +109,7 @@ public class RouteSearcher { } } res.finish(); + callback.onEnd(gCallBack); return res.getList(); } @@ -116,6 +123,10 @@ public class RouteSearcher { private void add(SegmentSearcher task, PathRoute path, TopList res){ + if (callback.isCancel()){ + task.cancel(true); + return; + } List tail = task.join(); if (tail.isEmpty()){ LOG.trace("Not found route from {} to {}, jumps {}", task.source, task.target, task.jumps); diff --git a/core/src/main/java/ru/trader/graph/RouteSearcherCallBack.java b/core/src/main/java/ru/trader/graph/RouteSearcherCallBack.java new file mode 100644 index 0000000..8caf29b --- /dev/null +++ b/core/src/main/java/ru/trader/graph/RouteSearcherCallBack.java @@ -0,0 +1,44 @@ +package ru.trader.graph; + +import ru.trader.core.Vendor; + +import java.util.LinkedList; +import java.util.List; + +public class RouteSearcherCallBack { + + private volatile boolean cancel = false; + private final List> callbacks = new LinkedList<>(); + + public final GraphCallBack onStart(){ + GraphCallBack callback = getGraphCallBackInstance(); + if (cancel) return callback; + synchronized (callbacks) { + callbacks.add(callback); + } + return callback; + } + + public final void onEnd( GraphCallBack callback){ + synchronized (callbacks) { + callbacks.remove(callback); + } + } + + protected GraphCallBack getGraphCallBackInstance(){ + return new GraphCallBack<>(); + } + + public final boolean isCancel() { + return cancel; + } + + public final void cancel(){ + if (cancel) return; + this.cancel = true; + synchronized (callbacks){ + callbacks.forEach(GraphCallBack::cancel); + callbacks.clear(); + } + } +}