From 243c9b83b952147d0abc2781664ebddcf8b1a2a3 Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Fri, 3 Sep 2021 03:14:39 +0800 Subject: [PATCH] feat: download concurrency settings. Closes #990. --- .../org/jackhuang/hmcl/setting/Config.java | 30 ++++++++ .../hmcl/setting/DownloadProviders.java | 14 +++- .../main/java/org/jackhuang/hmcl/ui/SVG.java | 6 ++ .../jackhuang/hmcl/ui/construct/HintPane.java | 69 +++++++++++++++++++ .../hmcl/ui/main/DownloadSettingsPage.java | 69 ++++++++++++++++++- .../hmcl/ui/main/LauncherSettingsPage.java | 7 +- HMCL/src/main/resources/assets/css/root.css | 9 +++ .../resources/assets/lang/I18N.properties | 5 ++ .../resources/assets/lang/I18N_zh.properties | 1 + .../assets/lang/I18N_zh_CN.properties | 6 ++ .../org/jackhuang/hmcl/task/FetchTask.java | 9 +-- 11 files changed, 214 insertions(+), 11 deletions(-) create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/HintPane.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java index 46b7e5f2c..9fafb4874 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java @@ -122,6 +122,12 @@ public final class Config implements Cloneable, Observable { @SerializedName("localization") private ObjectProperty localization = new SimpleObjectProperty<>(Locales.DEFAULT); + @SerializedName("autoDownloadThreads") + private BooleanProperty autoDownloadThreads = new SimpleBooleanProperty(false); + + @SerializedName("downloadThreads") + private IntegerProperty downloadThreads = new SimpleIntegerProperty(64); + @SerializedName("downloadType") private StringProperty downloadType = new SimpleStringProperty("mcbbs"); @@ -389,6 +395,30 @@ public final class Config implements Cloneable, Observable { return localization; } + public boolean getAutoDownloadThreads() { + return autoDownloadThreads.get(); + } + + public BooleanProperty autoDownloadThreadsProperty() { + return autoDownloadThreads; + } + + public void setAutoDownloadThreads(boolean autoDownloadThreads) { + this.autoDownloadThreads.set(autoDownloadThreads); + } + + public int getDownloadThreads() { + return downloadThreads.get(); + } + + public IntegerProperty downloadThreadsProperty() { + return downloadThreads; + } + + public void setDownloadThreads(int downloadThreads) { + this.downloadThreads.set(downloadThreads); + } + public String getDownloadType() { return downloadType.get(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java index c0e101ad2..1be743de7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java @@ -17,14 +17,19 @@ */ package org.jackhuang.hmcl.setting; +import javafx.beans.InvalidationListener; import org.jackhuang.hmcl.download.*; +import org.jackhuang.hmcl.task.FetchTask; import org.jackhuang.hmcl.ui.FXUtils; -import java.util.*; +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; import static org.jackhuang.hmcl.setting.ConfigHolder.config; +import static org.jackhuang.hmcl.task.FetchTask.DEFAULT_CONCURRENCY; import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Pair.pair; @@ -44,6 +49,8 @@ public final class DownloadProviders { public static final String DEFAULT_PROVIDER_ID = "balanced"; public static final String DEFAULT_RAW_PROVIDER_ID = "mcbbs"; + private static final InvalidationListener observer; + static { String bmclapiRoot = "https://bmclapi2.bangbang93.com"; String bmclapiRootOverride = System.getProperty("hmcl.bmclapi.override"); @@ -66,6 +73,11 @@ public final class DownloadProviders { pair("official", new AutoDownloadProvider(MOJANG, fileProvider)), pair("balanced", new AutoDownloadProvider(balanced, fileProvider)), pair("mirror", new AutoDownloadProvider(MCBBS, fileProvider))); + + observer = FXUtils.observeWeak(() -> { + FetchTask.setDownloadExecutorConcurrency( + config().getAutoDownloadThreads() ? DEFAULT_CONCURRENCY : config().getDownloadThreads()); + }, config().autoDownloadThreadsProperty(), config().downloadThreadsProperty()); } static void init() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java index c5b1b1b25..5c98633a2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java @@ -415,6 +415,12 @@ public final class SVG { fill, width, height); } + public static Node bell(ObjectBinding fill, double width, double height) { + return createSVGPath( + "M21,19V20H3V19L5,17V11C5,7.9 7.03,5.17 10,4.29C10,4.19 10,4.1 10,4A2,2 0 0,1 12,2A2,2 0 0,1 14,4C14,4.1 14,4.19 14,4.29C16.97,5.17 19,7.9 19,11V17L21,19M14,21A2,2 0 0,1 12,23A2,2 0 0,1 10,21", + fill, width, height); + } + public static Node contentSaveMoveOutline(ObjectBinding fill, double width, double height) { return createSVGPath( "M13 17H17V14L22 18.5L17 23V20H13V17M14 12.8C13.5 12.31 12.78 12 12 12C10.34 12 9 13.34 9 15C9 16.31 9.84 17.41 11 17.82C11.07 15.67 12.27 13.8 14 12.8M11.09 19H5V5H16.17L19 7.83V12.35C19.75 12.61 20.42 13 21 13.54V7L17 3H5C3.89 3 3 3.9 3 5V19C3 20.1 3.89 21 5 21H11.81C11.46 20.39 11.21 19.72 11.09 19M6 10H15V6H6V10Z", diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/HintPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/HintPane.java new file mode 100644 index 000000000..8c74aa890 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/HintPane.java @@ -0,0 +1,69 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui.construct; + +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.scene.text.Text; +import javafx.scene.text.TextFlow; +import org.jackhuang.hmcl.setting.Theme; +import org.jackhuang.hmcl.ui.SVG; +import org.jackhuang.hmcl.util.javafx.BindingMapping; + +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +public class HintPane extends VBox { + private final Text label = new Text(); + private final StringProperty text = new SimpleStringProperty(this, "text"); + + public HintPane(MessageDialogPane.MessageType type) { + setFillWidth(true); + getStyleClass().add("hint"); + HBox hbox = new HBox(); + hbox.setAlignment(Pos.CENTER_LEFT); + hbox.getChildren().setAll( + SVG.informationOutline(Theme.blackFillBinding(), 16, 16), + new Label(i18n("message.info")) + ); + TextFlow flow = new TextFlow(); + flow.getChildren().setAll(label); + getChildren().setAll(hbox, flow); + label.textProperty().bind(text); + VBox.setMargin(flow, new Insets(2, 2, 0, 2)); + + label.fillProperty().bind(BindingMapping.of(disabledProperty()).map(disabled -> disabled ? new Color(0, 0, 0, 0.5) : Color.BLACK)); + } + + public String getText() { + return text.get(); + } + + public StringProperty textProperty() { + return text; + } + + public void setText(String text) { + this.text.set(text); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java index 7680d4853..cc20353cf 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java @@ -22,17 +22,18 @@ import javafx.geometry.HPos; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Label; + import javafx.scene.control.ScrollPane; import javafx.scene.control.ToggleGroup; import javafx.scene.layout.*; import org.jackhuang.hmcl.setting.DownloadProviders; +import org.jackhuang.hmcl.task.FetchTask; import org.jackhuang.hmcl.ui.FXUtils; -import org.jackhuang.hmcl.ui.construct.ComponentList; -import org.jackhuang.hmcl.ui.construct.NumberValidator; -import org.jackhuang.hmcl.ui.construct.Validator; +import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.util.javafx.SafeStringConverter; import java.net.Proxy; +import java.util.concurrent.atomic.AtomicBoolean; import static org.jackhuang.hmcl.setting.ConfigHolder.config; import static org.jackhuang.hmcl.ui.FXUtils.stringConverter; @@ -103,6 +104,68 @@ public class DownloadSettingsPage extends StackPane { content.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.launcher.version_list_source")), downloadSource); } + { + ComponentList downloadThreads = new ComponentList(); + + { + VBox pane = new VBox(16); + pane.setPadding(new Insets(8, 0, 8, 0)); + + { + JFXCheckBox chkAutoDownloadThreads = new JFXCheckBox(i18n("settings.launcher.download.threads.auto")); + chkAutoDownloadThreads.selectedProperty().bindBidirectional(config().autoDownloadThreadsProperty()); + pane.getChildren().add(chkAutoDownloadThreads); + + chkAutoDownloadThreads.selectedProperty().addListener((a, b, newValue) -> { + if (newValue) { + config().downloadThreadsProperty().set(FetchTask.DEFAULT_CONCURRENCY); + } + }); + } + + { + HBox hbox = new HBox(8); + hbox.setAlignment(Pos.CENTER); + hbox.setPadding(new Insets(0, 0, 0, 30)); + hbox.disableProperty().bind(config().autoDownloadThreadsProperty()); + Label label = new Label(i18n("settings.launcher.download.threads")); + + JFXSlider slider = new JFXSlider(1, 256, 64); + HBox.setHgrow(slider, Priority.ALWAYS); + + JFXTextField threadsField = new JFXTextField(); + FXUtils.setLimitWidth(threadsField, 60); + threadsField.textProperty().bindBidirectional(config().downloadThreadsProperty(), SafeStringConverter.fromInteger()); + + AtomicBoolean changedByTextField = new AtomicBoolean(false); + FXUtils.onChangeAndOperate(config().downloadThreadsProperty(), value -> { + changedByTextField.set(true); + slider.setValue(value.intValue()); + changedByTextField.set(false); + }); + slider.valueProperty().addListener((value, oldVal, newVal) -> { + if (changedByTextField.get()) return; + config().downloadThreadsProperty().set(value.getValue().intValue()); + }); + + hbox.getChildren().setAll(label, slider, threadsField); + pane.getChildren().add(hbox); + } + + { + HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFORMATION); + VBox.setMargin(hintPane, new Insets(0, 0, 0, 30)); + hintPane.disableProperty().bind(config().autoDownloadThreadsProperty()); + hintPane.setText(i18n("settings.launcher.download.threads.hint")); + pane.getChildren().add(hintPane); + } + + downloadThreads.getContent().add(pane); + } + + content.getChildren().addAll(ComponentList.createComponentListTitle(i18n("download")), downloadThreads); + } + { ComponentList proxyList = new ComponentList(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java index f4ae91479..747c2924e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java @@ -67,14 +67,14 @@ public class LauncherSettingsPage extends BorderPane implements DecoratorPage { { AdvancedListBox sideBar = new AdvancedListBox() .addNavigationDrawerItem(settingsItem -> { - settingsItem.setTitle(i18n("settings.type.global.manage")); + settingsItem.setTitle(i18n("settings.game.current")); settingsItem.setLeftGraphic(wrap(SVG.gamepad(null, 20, 20))); settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(gameTab)); settingsItem.setOnAction(e -> tab.getSelectionModel().select(gameTab)); }) - .startCategory(i18n("settings.launcher")) + .startCategory(i18n("launcher")) .addNavigationDrawerItem(settingsItem -> { - settingsItem.setTitle(i18n("settings.launcher")); + settingsItem.setTitle(i18n("settings.launcher.general")); settingsItem.setLeftGraphic(wrap(SVG.applicationOutline(null, 20, 20))); settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(settingsTab)); settingsItem.setOnAction(e -> tab.getSelectionModel().select(settingsTab)); @@ -91,6 +91,7 @@ public class LauncherSettingsPage extends BorderPane implements DecoratorPage { downloadItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(downloadTab)); downloadItem.setOnAction(e -> tab.getSelectionModel().select(downloadTab)); }) + .startCategory(i18n("help")) .addNavigationDrawerItem(helpItem -> { helpItem.setTitle(i18n("help")); helpItem.setLeftGraphic(wrap(SVG.helpCircleOutline(null, 20, 20))); diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index a0ae474af..9bd29fc75 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -42,6 +42,15 @@ -fx-text-fill: rgba(0, 0, 0, 0.5); } +.hint { + -fx-border-color: #b8daff; + -fx-background-color: #cce5ff; + -fx-background-radius: 5; + -fx-border-width: 1; + -fx-border-radius: 5; + -fx-padding: 6; +} + .memory-label { } diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 1cd828c6d..cf9036869 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -530,6 +530,7 @@ settings.advanced.wrapper_launcher=Wrapper Launcher (i.e. optirun...) settings.custom=Custom settings.game=Games +settings.game.current=Game settings.game.dimension=Game Window Dimension settings.game.exploration=Explore settings.game.fullscreen=Fullscreen @@ -546,10 +547,14 @@ settings.launcher=Settings settings.launcher.appearance=Appearance settings.launcher.common_path.tooltip=This app will cache all downloads here. settings.launcher.debug=Debug +settings.launcher.download.threads=Threads +settings.launcher.download.threads.auto=Auto Determined +settings.launcher.download.threads.hint=Too large concurrency may cause system to freeze. Download speed may be affected by ICP and destination server. settings.launcher.download_source=Download Source settings.launcher.download_source.auto=Auto choose download source settings.launcher.enable_game_list=Show version list in main page settings.launcher.font=Font +settings.launcher.general=General settings.launcher.language=Language settings.launcher.launcher_log.export=Export launcher logs settings.launcher.launcher_log.export.failed=Failed to export logs diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index ea956a674..023948986 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -380,6 +380,7 @@ settings.advanced.wrapper_launcher=前置指令(不必填寫,如 optirun) settings.custom=自訂 settings.game=遊戲設定 +settings.game.current=遊戲 settings.game.dimension=遊戲介面解析度大小 settings.game.exploration=瀏覽 settings.game.fullscreen=全螢幕 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 404e073f9..b7916dfe0 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -538,6 +538,7 @@ settings.advanced.wrapper_launcher=前置指令(不必填写,如 optirun) settings.custom=自定义 settings.game=游戏设置 +settings.game.current=游戏 settings.game.dimension=游戏窗口分辨率 settings.game.exploration=浏览 settings.game.fullscreen=全屏 @@ -554,10 +555,15 @@ settings.launcher=启动器设置 settings.launcher.appearance=外观 settings.launcher.common_path.tooltip=启动器将所有游戏资源及依赖库文件放于此集中管理,如果游戏文件夹内有现成的将不会使用公共库文件 settings.launcher.debug=调试 +settings.launcher.download=下载 +settings.launcher.download.threads=并发数 +settings.launcher.download.threads.auto=自动选择并发数 +settings.launcher.download.threads.hint=并发数过大可能导致系统卡顿。你的下载速度会受到宽带运营商、服务器等方面的影响,调大下载并发数不一定能大幅提升总下载速度。 settings.launcher.download_source=下载源 settings.launcher.download_source.auto=自动选择下载源 settings.launcher.enable_game_list=在主页内显示版本列表 settings.launcher.font=字体 +settings.launcher.general=通用 settings.launcher.language=语言 settings.launcher.launcher_log.export=导出启动器日志 settings.launcher.launcher_log.export.failed=无法导出日志 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FetchTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FetchTask.java index 7f875c09e..eec7c982d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FetchTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FetchTask.java @@ -268,8 +268,9 @@ public abstract class FetchTask extends Task { } - private static int downloadExecutorConcurrency = Math.min(Runtime.getRuntime().availableProcessors() * 4, 64); - private static volatile ExecutorService DOWNLOAD_EXECUTOR; + public static int DEFAULT_CONCURRENCY = Math.min(Runtime.getRuntime().availableProcessors() * 4, 64); + private static int downloadExecutorConcurrency = DEFAULT_CONCURRENCY; + private static volatile ThreadPoolExecutor DOWNLOAD_EXECUTOR; /** * Get singleton instance of the thread pool for file downloading. @@ -292,8 +293,8 @@ public abstract class FetchTask extends Task { synchronized (Schedulers.class) { downloadExecutorConcurrency = concurrency; if (DOWNLOAD_EXECUTOR != null) { - DOWNLOAD_EXECUTOR.shutdownNow(); - DOWNLOAD_EXECUTOR = null; + DOWNLOAD_EXECUTOR.setCorePoolSize(concurrency); + DOWNLOAD_EXECUTOR.setMaximumPoolSize(concurrency); } } }