diff --git a/desktop/src/main/java/bisq/desktop/main/market/trades/ChartCalculations.java b/desktop/src/main/java/bisq/desktop/main/market/trades/ChartCalculations.java index 9612fdad05..3c720ee48d 100644 --- a/desktop/src/main/java/bisq/desktop/main/market/trades/ChartCalculations.java +++ b/desktop/src/main/java/bisq/desktop/main/market/trades/ChartCalculations.java @@ -53,6 +53,8 @@ import java.util.stream.Collectors; import lombok.Getter; +import static bisq.desktop.main.market.trades.TradesChartsViewModel.MAX_TICKS; + public class ChartCalculations { static final ZoneId ZONE_ID = ZoneId.systemDefault(); @@ -169,7 +171,7 @@ public class ChartCalculations { // Generate date range and create sets for all ticks Map>> itemsPerInterval = new HashMap<>(); Date time = new Date(); - for (long i = TradesChartsViewModel.MAX_TICKS + 1; i >= 0; --i) { + for (long i = MAX_TICKS + 1; i >= 0; --i) { Pair> pair = new Pair<>((Date) time.clone(), new HashSet<>()); itemsPerInterval.put(i, pair); // We adjust the time for the next iteration @@ -179,7 +181,7 @@ public class ChartCalculations { // Get all entries for the defined time interval tradeStatisticsByCurrency.forEach(tradeStatistics -> { - for (long i = TradesChartsViewModel.MAX_TICKS; i > 0; --i) { + for (long i = MAX_TICKS; i > 0; --i) { Pair> pair = itemsPerInterval.get(i); if (tradeStatistics.getDate().after(pair.getKey())) { pair.getValue().add(tradeStatistics); @@ -292,7 +294,7 @@ public class ChartCalculations { } static long getTimeFromTickIndex(long tick, Map>> itemsPerInterval) { - if (tick > TradesChartsViewModel.MAX_TICKS + 1 || + if (tick > MAX_TICKS + 1 || itemsPerInterval.get(tick) == null) { return 0; } diff --git a/desktop/src/main/java/bisq/desktop/main/market/trades/TradesChartsView.java b/desktop/src/main/java/bisq/desktop/main/market/trades/TradesChartsView.java index 13ace457bd..46cb807607 100644 --- a/desktop/src/main/java/bisq/desktop/main/market/trades/TradesChartsView.java +++ b/desktop/src/main/java/bisq/desktop/main/market/trades/TradesChartsView.java @@ -104,11 +104,11 @@ import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import org.jetbrains.annotations.NotNull; +import static bisq.desktop.main.market.trades.TradesChartsViewModel.MAX_TICKS; import static bisq.desktop.util.FormBuilder.addTopLabelAutocompleteComboBox; import static bisq.desktop.util.FormBuilder.getTopLabelWithVBox; @@ -162,8 +162,7 @@ public class TradesChartsView extends ActivatableViewAndModel tabPaneSelectionModel; private TableColumn priceColumn, volumeColumn, marketColumn; - private final ObservableList listItems = FXCollections.observableArrayList(); - private final SortedList sortedList = new SortedList<>(listItems); + private SortedList sortedList = new SortedList<>(FXCollections.observableArrayList()); private ChangeListener timeUnitChangeListener; private ChangeListener priceAxisYWidthListener; @@ -287,8 +286,6 @@ public class TradesChartsView extends ActivatableViewAndModel model.setSelectedTabIndex((int) newValue); model.setSelectedTabIndex(tabPaneSelectionModel.getSelectedIndex()); @@ -328,8 +325,6 @@ public class TradesChartsView extends ActivatableViewAndModel { }); - sortedList.comparatorProperty().bind(tableView.comparatorProperty()); - boolean useAnimations = model.preferences.isUseAnimations(); priceChart.setAnimated(useAnimations); volumeChart.setAnimated(useAnimations); @@ -341,7 +336,6 @@ public class TradesChartsView extends ActivatableViewAndModel exportToCsv()); - UserThread.runAfter(this::updateChartData, 100, TimeUnit.MILLISECONDS); if (root.getParent() instanceof Pane) { rootParent = (Pane) root.getParent(); @@ -359,11 +353,7 @@ public class TradesChartsView extends ActivatableViewAndModel new TradeStatistics3ListItem(tradeStatistics, coinFormatter, model.showAllTradeCurrenciesProperty.get())) - .collect(Collectors.toList()); - }).whenComplete((items, throwable) -> { - log.error("Creating listItems took {} ms", System.currentTimeMillis() - ts); + .collect(Collectors.toCollection(FXCollections::observableArrayList)); + }).whenComplete((listItems, throwable) -> { + log.debug("Creating listItems took {} ms", System.currentTimeMillis() - ts); + long ts2 = System.currentTimeMillis(); - listItems.setAll(items); - // Is slow because of sorting of > 100k items. But at least it seems it works on the thread from the - // CompletableFuture callback, so we do not block the UI thread;. - log.error("Applying sorted list took {} ms", - System.currentTimeMillis() - ts2); + sortedList.comparatorProperty().unbind(); + // Sorting is slow as we have > 100k items. So we prefer to do it on the non UI thread. + sortedList = new SortedList<>(listItems); + sortedList.comparatorProperty().bind(tableView.comparatorProperty()); + log.debug("Created sorted list took {} ms", System.currentTimeMillis() - ts2); + UserThread.execute(() -> { + // When we attach the list to the table we need to be on the UI thread. + tableView.setItems(sortedList); + }); }); } @@ -473,7 +468,7 @@ public class TradesChartsView extends ActivatableViewAndModel(); - priceAxisX = new NumberAxis(0, TradesChartsViewModel.MAX_TICKS + 1, 1); + priceAxisX = new NumberAxis(0, MAX_TICKS + 1, 1); priceAxisX.setTickUnit(4); priceAxisX.setMinorTickCount(4); priceAxisX.setMinorTickVisible(true); @@ -539,11 +534,11 @@ public class TradesChartsView extends ActivatableViewAndModel { - updateSelectedTradeStatistics(getCurrencyCode()); //todo - updateChartData(); + applyAsyncTradeStatisticsForCurrency(getCurrencyCode()) + .whenComplete((result, throwable) -> { + if (deactivateCalled) { + return; + } + if (throwable != null) { + log.error("Error at setChangeListener/applyAsyncTradeStatisticsForCurrency. {}", throwable.toString()); + return; + } + applyAsyncChartData(); + }); fillTradeCurrencies(); }; @@ -137,9 +146,8 @@ class TradesChartsViewModel extends ActivatableViewModel { @Override protected void activate() { - deactivateCalled = false; long ts = System.currentTimeMillis(); - + deactivateCalled = false; tradeStatisticsManager.getObservableTradeStatisticsSet().addListener(setChangeListener); if (!fillTradeCurrenciesOnActivateCalled) { @@ -149,8 +157,6 @@ class TradesChartsViewModel extends ActivatableViewModel { syncPriceFeedCurrency(); setMarketPriceFeedCurrency(); - long ts1 = System.currentTimeMillis(); - List> allFutures = new ArrayList<>(); CompletableFuture task1Done = new CompletableFuture<>(); allFutures.add(task1Done); @@ -165,76 +171,17 @@ class TradesChartsViewModel extends ActivatableViewModel { log.error(throwable.toString()); return; } - //Once getUsdAveragePriceMapsPerTickUnit and getUsdAveragePriceMapsPerTickUnit are both completed we - // call updateChartData2 - UserThread.execute(this::updateChartData); + //Once applyAsyncUsdAveragePriceMapsPerTickUnit and applyAsyncTradeStatisticsForCurrency are + // both completed we call applyAsyncChartData + UserThread.execute(this::applyAsyncChartData); }); - // We start getUsdAveragePriceMapsPerTickUnit and getUsdAveragePriceMapsPerTickUnit in parallel threads for - // better performance - ChartCalculations.getUsdAveragePriceMapsPerTickUnit(tradeStatisticsManager.getObservableTradeStatisticsSet()) - .whenComplete((usdAveragePriceMapsPerTickUnit, throwable) -> { - if (deactivateCalled) { - return; - } - if (throwable != null) { - log.error(throwable.toString()); - task1Done.completeExceptionally(throwable); - return; - } - UserThread.execute(() -> { - this.usdAveragePriceMapsPerTickUnit.clear(); - this.usdAveragePriceMapsPerTickUnit.putAll(usdAveragePriceMapsPerTickUnit); - log.error("getUsdAveragePriceMapsPerTickUnit took {}", System.currentTimeMillis() - ts1); - task1Done.complete(true); - }); - }); + // We call applyAsyncUsdAveragePriceMapsPerTickUnit and applyAsyncTradeStatisticsForCurrency + // in parallel for better performance + applyAsyncUsdAveragePriceMapsPerTickUnit(task1Done); + applyAsyncTradeStatisticsForCurrency(getCurrencyCode(), task2Done); - long ts2 = System.currentTimeMillis(); - ChartCalculations.getTradeStatisticsForCurrency(tradeStatisticsManager.getObservableTradeStatisticsSet(), - getCurrencyCode(), - showAllTradeCurrenciesProperty.get()) - .whenComplete((list, throwable) -> { - if (deactivateCalled) { - return; - } - if (throwable != null) { - log.error(throwable.toString()); - task2Done.completeExceptionally(throwable); - return; - } - - UserThread.execute(() -> { - tradeStatisticsByCurrency.setAll(list); - log.error("getTradeStatisticsForCurrency took {}", System.currentTimeMillis() - ts2); - task2Done.complete(true); - }); - }); - - log.error("activate took {}", System.currentTimeMillis() - ts); - } - - private void updateChartData() { - long ts = System.currentTimeMillis(); - ChartCalculations.getUpdateChartResult(tradeStatisticsByCurrency, tickUnit, usdAveragePriceMapsPerTickUnit, getCurrencyCode()) - .whenComplete((updateChartResult, throwable) -> { - if (deactivateCalled) { - return; - } - if (throwable != null) { - log.error(throwable.toString()); - return; - } - UserThread.execute(() -> { - itemsPerInterval.clear(); - itemsPerInterval.putAll(updateChartResult.getItemsPerInterval()); - - priceItems.setAll(updateChartResult.getPriceItems()); - volumeItems.setAll(updateChartResult.getVolumeItems()); - volumeInUsdItems.setAll(updateChartResult.getVolumeInUsdItems()); - log.error("updateChartData took {}", System.currentTimeMillis() - ts); - }); - }); + log.debug("activate took {}", System.currentTimeMillis() - ts); } @Override @@ -254,6 +201,94 @@ class TradesChartsViewModel extends ActivatableViewModel { }); } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Async calls + /////////////////////////////////////////////////////////////////////////////////////////// + + private void applyAsyncUsdAveragePriceMapsPerTickUnit(CompletableFuture completeFuture) { + long ts = System.currentTimeMillis(); + ChartCalculations.getUsdAveragePriceMapsPerTickUnit(tradeStatisticsManager.getObservableTradeStatisticsSet()) + .whenComplete((usdAveragePriceMapsPerTickUnit, throwable) -> { + if (deactivateCalled) { + return; + } + if (throwable != null) { + log.error("Error at applyAsyncUsdAveragePriceMapsPerTickUnit. {}", throwable.toString()); + completeFuture.completeExceptionally(throwable); + return; + } + UserThread.execute(() -> { + this.usdAveragePriceMapsPerTickUnit.clear(); + this.usdAveragePriceMapsPerTickUnit.putAll(usdAveragePriceMapsPerTickUnit); + log.debug("applyAsyncUsdAveragePriceMapsPerTickUnit took {}", System.currentTimeMillis() - ts); + completeFuture.complete(true); + }); + }); + } + + private CompletableFuture applyAsyncTradeStatisticsForCurrency(String currencyCode) { + return applyAsyncTradeStatisticsForCurrency(currencyCode, null); + } + + private CompletableFuture applyAsyncTradeStatisticsForCurrency(String currencyCode, + @Nullable CompletableFuture completeFuture) { + CompletableFuture future = new CompletableFuture<>(); + long ts = System.currentTimeMillis(); + ChartCalculations.getTradeStatisticsForCurrency(tradeStatisticsManager.getObservableTradeStatisticsSet(), + currencyCode, + showAllTradeCurrenciesProperty.get()) + .whenComplete((list, throwable) -> { + if (deactivateCalled) { + return; + } + if (throwable != null) { + log.error("Error at applyAsyncTradeStatisticsForCurrency. {}", throwable.toString()); + if (completeFuture != null) { + completeFuture.completeExceptionally(throwable); + } + return; + } + + UserThread.execute(() -> { + tradeStatisticsByCurrency.setAll(list); + log.debug("applyAsyncTradeStatisticsForCurrency took {}", System.currentTimeMillis() - ts); + if (completeFuture != null) { + completeFuture.complete(true); + } + future.complete(true); + }); + }); + return future; + } + + private void applyAsyncChartData() { + long ts = System.currentTimeMillis(); + ChartCalculations.getUpdateChartResult(new ArrayList<>(tradeStatisticsByCurrency), + tickUnit, + usdAveragePriceMapsPerTickUnit, + getCurrencyCode()) + .whenComplete((updateChartResult, throwable) -> { + if (deactivateCalled) { + return; + } + if (throwable != null) { + log.error("Error at applyAsyncChartData. {}", throwable.toString()); + return; + } + UserThread.execute(() -> { + itemsPerInterval.clear(); + itemsPerInterval.putAll(updateChartResult.getItemsPerInterval()); + + priceItems.setAll(updateChartResult.getPriceItems()); + volumeItems.setAll(updateChartResult.getVolumeItems()); + volumeInUsdItems.setAll(updateChartResult.getVolumeInUsdItems()); + log.debug("applyAsyncChartData took {}", System.currentTimeMillis() - ts); + }); + }); + } + + /////////////////////////////////////////////////////////////////////////////////////////// // UI actions /////////////////////////////////////////////////////////////////////////////////////////// @@ -278,15 +313,24 @@ class TradesChartsViewModel extends ActivatableViewModel { } preferences.setTradeChartsScreenCurrencyCode(code); - updateSelectedTradeStatistics(getCurrencyCode());//todo - updateChartData(); + applyAsyncTradeStatisticsForCurrency(getCurrencyCode()) + .whenComplete((result, throwable) -> { + if (deactivateCalled) { + return; + } + if (throwable != null) { + log.error("Error at onSetTradeCurrency/applyAsyncTradeStatisticsForCurrency. {}", throwable.toString()); + return; + } + applyAsyncChartData(); + }); } } void setTickUnit(TickUnit tickUnit) { this.tickUnit = tickUnit; preferences.setTradeStatisticsTickUnitIndex(tickUnit.ordinal()); - updateChartData(); + applyAsyncChartData(); } void setSelectedTabIndex(int selectedTabIndex) { @@ -295,6 +339,7 @@ class TradesChartsViewModel extends ActivatableViewModel { setMarketPriceFeedCurrency(); } + /////////////////////////////////////////////////////////////////////////////////////////// // Getters /////////////////////////////////////////////////////////////////////////////////////////// @@ -311,6 +356,11 @@ class TradesChartsViewModel extends ActivatableViewModel { return currencyListItems.getObservableList().stream().filter(e -> e.tradeCurrency.equals(selectedTradeCurrencyProperty.get())).findAny(); } + long getTimeFromTickIndex(long tick) { + return ChartCalculations.getTimeFromTickIndex(tick, itemsPerInterval); + } + + /////////////////////////////////////////////////////////////////////////////////////////// // Private /////////////////////////////////////////////////////////////////////////////////////////// @@ -320,7 +370,6 @@ class TradesChartsViewModel extends ActivatableViewModel { List tradeCurrencyList = tradeStatisticsManager.getObservableTradeStatisticsSet().stream() .flatMap(e -> CurrencyUtil.getTradeCurrency(e.getCurrency()).stream()) .collect(Collectors.toList()); - currencyListItems.updateWithCurrencies(tradeCurrencyList, showAllCurrencyListItem); } @@ -338,17 +387,6 @@ class TradesChartsViewModel extends ActivatableViewModel { priceFeedService.setCurrencyCode(selectedTradeCurrencyProperty.get().getCode()); } - //todo - private void updateSelectedTradeStatistics(String currencyCode) { - tradeStatisticsByCurrency.setAll(tradeStatisticsManager.getObservableTradeStatisticsSet().stream() - .filter(e -> showAllTradeCurrenciesProperty.get() || e.getCurrency().equals(currencyCode)) - .collect(Collectors.toList())); - } - - long getTimeFromTickIndex(long tick) { - return ChartCalculations.getTimeFromTickIndex(tick, itemsPerInterval); - } - private boolean isShowAllEntry(@Nullable String id) { return id != null && id.equals(GUIUtil.SHOW_ALL_FLAG); } @@ -356,5 +394,4 @@ class TradesChartsViewModel extends ActivatableViewModel { private boolean isEditEntry(@Nullable String id) { return id != null && id.equals(GUIUtil.EDIT_FLAG); } - }