Do sorting on non UI thread as its slow and only attach

it to table in UI thread afterwards.

Chain updateSelectedTradeStatistics and updateChartData calls.
This commit is contained in:
chimp1984 2021-11-02 00:57:30 +01:00
parent ce8a91fdb3
commit a659a78db6
No known key found for this signature in database
GPG Key ID: 9801B4EC591F90E3
3 changed files with 150 additions and 116 deletions

View File

@ -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<Long, Pair<Date, Set<TradeStatistics3>>> 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<Date, Set<TradeStatistics3>> 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<Date, Set<TradeStatistics3>> 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<Long, Pair<Date, Set<TradeStatistics3>>> itemsPerInterval) {
if (tick > TradesChartsViewModel.MAX_TICKS + 1 ||
if (tick > MAX_TICKS + 1 ||
itemsPerInterval.get(tick) == null) {
return 0;
}

View File

@ -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<VBox, TradesCharts
private SingleSelectionModel<Tab> tabPaneSelectionModel;
private TableColumn<TradeStatistics3ListItem, TradeStatistics3ListItem> priceColumn, volumeColumn, marketColumn;
private final ObservableList<TradeStatistics3ListItem> listItems = FXCollections.observableArrayList();
private final SortedList<TradeStatistics3ListItem> sortedList = new SortedList<>(listItems);
private SortedList<TradeStatistics3ListItem> sortedList = new SortedList<>(FXCollections.observableArrayList());
private ChangeListener<Toggle> timeUnitChangeListener;
private ChangeListener<Number> priceAxisYWidthListener;
@ -287,8 +286,6 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
@Override
protected void activate() {
long ts = System.currentTimeMillis();
// root.getParent() is null at initialize
tabPaneSelectionModel = GUIUtil.getParentOfType(root, JFXTabPane.class).getSelectionModel();
selectedTabIndexListener = (observable, oldValue, newValue) -> model.setSelectedTabIndex((int) newValue);
model.setSelectedTabIndex(tabPaneSelectionModel.getSelectedIndex());
@ -328,8 +325,6 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
currencySelectionSubscriber = currencySelectionBinding.subscribe((observable, oldValue, newValue) -> {
});
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<VBox, TradesCharts
nrOfTradeStatisticsLabel.setText(Res.get("market.trades.nrOfTrades", model.tradeStatisticsByCurrency.size()));
exportLink.setOnAction(e -> 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<VBox, TradesCharts
user.requestPersistence();
});
tableView.setItems(sortedList);
fillList();
layout();
log.error("activate took {} ms", System.currentTimeMillis() - ts);
}
@Override
@ -407,15 +397,20 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
.map(tradeStatistics -> 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<VBox, TradesCharts
private void createCharts() {
priceSeries = new XYChart.Series<>();
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<VBox, TradesCharts
priceChartPane.getChildren().add(priceChart);
volumeAxisX = new NumberAxis(0, TradesChartsViewModel.MAX_TICKS + 1, 1);
volumeAxisX = new NumberAxis(0, MAX_TICKS + 1, 1);
volumeAxisY = new NumberAxis();
volumeChart = getVolumeChart(volumeAxisX, volumeAxisY, volumeSeries, "BTC");
volumeInUsdAxisX = new NumberAxis(0, TradesChartsViewModel.MAX_TICKS + 1, 1);
volumeInUsdAxisX = new NumberAxis(0, MAX_TICKS + 1, 1);
NumberAxis volumeInUsdAxisY = new NumberAxis();
volumeInUsdChart = getVolumeChart(volumeInUsdAxisX, volumeInUsdAxisY, volumeInUsdSeries, "USD");
volumeInUsdChart.setVisible(false);
@ -650,7 +645,7 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
long index = MathUtils.doubleToLong((double) object);
// The last tick is on the chart edge, it is not well spaced with
// the previous tick and interferes with its label.
if (TradesChartsViewModel.MAX_TICKS + 1 == index) return "";
if (MAX_TICKS + 1 == index) return "";
long time = model.getTimeFromTickIndex(index);
String fmt = "";

View File

@ -118,8 +118,17 @@ class TradesChartsViewModel extends ActivatableViewModel {
this.navigation = navigation;
setChangeListener = change -> {
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<CompletableFuture<Boolean>> allFutures = new ArrayList<>();
CompletableFuture<Boolean> 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<Boolean> 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<Boolean> applyAsyncTradeStatisticsForCurrency(String currencyCode) {
return applyAsyncTradeStatisticsForCurrency(currencyCode, null);
}
private CompletableFuture<Boolean> applyAsyncTradeStatisticsForCurrency(String currencyCode,
@Nullable CompletableFuture<Boolean> completeFuture) {
CompletableFuture<Boolean> 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<TradeCurrency> 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);
}
}