add: more brief launching steps
This commit is contained in:
@@ -20,11 +20,7 @@ package org.jackhuang.hmcl.game;
|
|||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import org.jackhuang.hmcl.Launcher;
|
import org.jackhuang.hmcl.Launcher;
|
||||||
import org.jackhuang.hmcl.auth.Account;
|
import org.jackhuang.hmcl.auth.*;
|
||||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
|
||||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
|
||||||
import org.jackhuang.hmcl.auth.CharacterDeletedException;
|
|
||||||
import org.jackhuang.hmcl.auth.CredentialExpiredException;
|
|
||||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloadException;
|
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloadException;
|
||||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||||
import org.jackhuang.hmcl.download.MaintainTask;
|
import org.jackhuang.hmcl.download.MaintainTask;
|
||||||
@@ -51,11 +47,7 @@ import org.jackhuang.hmcl.ui.LogWindow;
|
|||||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||||
import org.jackhuang.hmcl.util.Log4jLevel;
|
import org.jackhuang.hmcl.util.*;
|
||||||
import org.jackhuang.hmcl.util.Logging;
|
|
||||||
import org.jackhuang.hmcl.util.Pair;
|
|
||||||
import org.jackhuang.hmcl.util.StringUtils;
|
|
||||||
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
|
|
||||||
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
|
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
|
||||||
import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
||||||
import org.jackhuang.hmcl.util.platform.CommandBuilder;
|
import org.jackhuang.hmcl.util.platform.CommandBuilder;
|
||||||
@@ -68,16 +60,9 @@ import java.io.File;
|
|||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Collections;
|
import java.util.*;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||||
@@ -102,9 +87,11 @@ public final class LauncherHelper {
|
|||||||
this.setting = profile.getVersionSetting(selectedVersion);
|
this.setting = profile.getVersionSetting(selectedVersion);
|
||||||
this.launcherVisibility = setting.getLauncherVisibility();
|
this.launcherVisibility = setting.getLauncherVisibility();
|
||||||
this.showLogs = setting.isShowLogs();
|
this.showLogs = setting.isShowLogs();
|
||||||
|
this.launchingStepsPane.setTitle(i18n("version.launch"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private final TaskExecutorDialogPane launchingStepsPane = new TaskExecutorDialogPane(it -> {});
|
private final TaskExecutorDialogPane launchingStepsPane = new TaskExecutorDialogPane(it -> {});
|
||||||
|
private CountDownLatch launchingLatch;
|
||||||
|
|
||||||
public void setTestMode() {
|
public void setTestMode() {
|
||||||
launcherVisibility = LauncherVisibility.KEEP;
|
launcherVisibility = LauncherVisibility.KEEP;
|
||||||
@@ -140,15 +127,15 @@ public final class LauncherHelper {
|
|||||||
Version version = MaintainTask.maintain(repository, repository.getResolvedVersion(selectedVersion));
|
Version version = MaintainTask.maintain(repository, repository.getResolvedVersion(selectedVersion));
|
||||||
Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version));
|
Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version));
|
||||||
|
|
||||||
TaskExecutor executor = Task.runAsync(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES))
|
TaskExecutor executor = Task.runAsync(() -> {
|
||||||
.thenComposeAsync(() -> {
|
})
|
||||||
|
.thenComposeAsync(() -> Task.composeAsync(() -> {
|
||||||
if (setting.isNotCheckGame())
|
if (setting.isNotCheckGame())
|
||||||
return null;
|
return null;
|
||||||
else
|
else
|
||||||
return dependencyManager.checkGameCompletionAsync(version);
|
return dependencyManager.checkGameCompletionAsync(version);
|
||||||
})
|
}).withStage("launch.state.dependencies"))
|
||||||
.thenRunAsync(Schedulers.javafx(), () -> emitStatus(LoadingState.MODS))
|
.thenComposeAsync(() -> Task.composeAsync(null, () -> {
|
||||||
.thenComposeAsync(() -> {
|
|
||||||
try {
|
try {
|
||||||
ModpackConfiguration<?> configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion));
|
ModpackConfiguration<?> configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion));
|
||||||
if ("Curse".equals(configuration.getType()))
|
if ("Curse".equals(configuration.getType()))
|
||||||
@@ -160,9 +147,8 @@ public final class LauncherHelper {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
})
|
}).withStage("launch.state.modpack"))
|
||||||
.thenRunAsync(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN))
|
.thenComposeAsync(() -> Task.supplyAsync((String) null, () -> {
|
||||||
.thenSupplyAsync(i18n("launch.state.logging_in"), () -> {
|
|
||||||
try {
|
try {
|
||||||
return account.logIn();
|
return account.logIn();
|
||||||
} catch (CredentialExpiredException e) {
|
} catch (CredentialExpiredException e) {
|
||||||
@@ -172,31 +158,27 @@ public final class LauncherHelper {
|
|||||||
LOG.warning("Authentication failed, try playing offline: " + e);
|
LOG.warning("Authentication failed, try playing offline: " + e);
|
||||||
return account.playOffline().orElseThrow(() -> e);
|
return account.playOffline().orElseThrow(() -> e);
|
||||||
}
|
}
|
||||||
})
|
}).withStage("launch.state.logging_in"))
|
||||||
.thenApplyAsync(Schedulers.javafx(), authInfo -> {
|
.thenComposeAsync(authInfo -> Task.supplyAsync((String) null, () -> {
|
||||||
emitStatus(LoadingState.LAUNCHING);
|
return new HMCLGameLauncher(
|
||||||
return authInfo;
|
repository,
|
||||||
})
|
version.getPatches().isEmpty() ? repository.getResolvedVersion(selectedVersion) : version,
|
||||||
.thenApplyAsync(authInfo -> new HMCLGameLauncher(
|
authInfo,
|
||||||
repository,
|
setting.toLaunchOptions(profile.getGameDir(), !setting.isNotCheckJVM()),
|
||||||
version.getPatches().isEmpty() ? repository.getResolvedVersion(selectedVersion) : version,
|
launcherVisibility == LauncherVisibility.CLOSE
|
||||||
authInfo,
|
? null // Unnecessary to start listening to game process output when close launcher immediately after game launched.
|
||||||
setting.toLaunchOptions(profile.getGameDir(), !setting.isNotCheckJVM()),
|
: new HMCLProcessListener(authInfo, gameVersion.isPresent())
|
||||||
launcherVisibility == LauncherVisibility.CLOSE
|
);
|
||||||
? null // Unnecessary to start listening to game process output when close launcher immediately after game launched.
|
}).thenComposeAsync(launcher -> { // launcher is prev task's result
|
||||||
: new HMCLProcessListener(authInfo, gameVersion.isPresent())
|
|
||||||
))
|
|
||||||
.thenComposeAsync(launcher -> { // launcher is prev task's result
|
|
||||||
if (scriptFile == null) {
|
if (scriptFile == null) {
|
||||||
return new LaunchTask<>(launcher::launch).setName(i18n("version.launch"));
|
return Task.supplyAsync((String) null, launcher::launch);
|
||||||
} else {
|
} else {
|
||||||
return new LaunchTask<ManagedProcess>(() -> {
|
return Task.supplyAsync((String) null, () -> {
|
||||||
launcher.makeLaunchScript(scriptFile);
|
launcher.makeLaunchScript(scriptFile);
|
||||||
return null;
|
return null;
|
||||||
}).setName(i18n("version.launch_script"));
|
});
|
||||||
}
|
}
|
||||||
})
|
}).thenAcceptAsync(process -> { // process is LaunchTask's result
|
||||||
.thenAcceptAsync(process -> { // process is LaunchTask's result
|
|
||||||
if (scriptFile == null) {
|
if (scriptFile == null) {
|
||||||
PROCESSES.add(process);
|
PROCESSES.add(process);
|
||||||
if (launcherVisibility == LauncherVisibility.CLOSE)
|
if (launcherVisibility == LauncherVisibility.CLOSE)
|
||||||
@@ -212,19 +194,18 @@ public final class LauncherHelper {
|
|||||||
Controllers.dialog(i18n("version.launch_script.success", scriptFile.getAbsolutePath()));
|
Controllers.dialog(i18n("version.launch_script.success", scriptFile.getAbsolutePath()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
}).thenRunAsync(null, Schedulers.defaultScheduler(), () -> {
|
||||||
|
launchingLatch = new CountDownLatch(1);
|
||||||
|
launchingLatch.await();
|
||||||
|
}).withStage("launch.state.waiting_launching"))
|
||||||
.cancellableExecutor();
|
.cancellableExecutor();
|
||||||
|
|
||||||
launchingStepsPane.setExecutor(executor, false);
|
launchingStepsPane.setExecutor(executor, Lang.immutableListOf(
|
||||||
|
"launch.state.dependencies",
|
||||||
|
"launch.state.modpack",
|
||||||
|
"launch.state.logging_in",
|
||||||
|
"launch.state.waiting_launching"), false);
|
||||||
executor.addTaskListener(new TaskListener() {
|
executor.addTaskListener(new TaskListener() {
|
||||||
final AtomicInteger finished = new AtomicInteger(0);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFinished(Task<?> task) {
|
|
||||||
finished.incrementAndGet();
|
|
||||||
int runningTasks = executor.getRunningTasks();
|
|
||||||
Platform.runLater(() -> launchingStepsPane.setProgress(1.0 * finished.get() / runningTasks));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop(boolean success, TaskExecutor executor) {
|
public void onStop(boolean success, TaskExecutor executor) {
|
||||||
@@ -432,15 +413,6 @@ public final class LauncherHelper {
|
|||||||
onAccept.run();
|
onAccept.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void emitStatus(LoadingState state) {
|
|
||||||
if (state == LoadingState.DONE) {
|
|
||||||
launchingStepsPane.fireEvent(new DialogCloseEvent());
|
|
||||||
}
|
|
||||||
|
|
||||||
launchingStepsPane.setTitle(state.getLocalizedMessage());
|
|
||||||
launchingStepsPane.setSubtitle((state.ordinal() + 1) + " / " + LoadingState.values().length);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkExit() {
|
private void checkExit() {
|
||||||
switch (launcherVisibility) {
|
switch (launcherVisibility) {
|
||||||
case HIDE_AND_REOPEN:
|
case HIDE_AND_REOPEN:
|
||||||
@@ -465,19 +437,6 @@ public final class LauncherHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class LaunchTask<T> extends Task<T> {
|
|
||||||
private final ExceptionalSupplier<T, Exception> supplier;
|
|
||||||
|
|
||||||
public LaunchTask(ExceptionalSupplier<T, Exception> supplier) {
|
|
||||||
this.supplier = supplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void execute() throws Exception {
|
|
||||||
setResult(supplier.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The managed process listener.
|
* The managed process listener.
|
||||||
* Guarantee that one [JavaProcess], one [HMCLProcessListener].
|
* Guarantee that one [JavaProcess], one [HMCLProcessListener].
|
||||||
@@ -534,7 +493,7 @@ public final class LauncherHelper {
|
|||||||
// these codes will be executed.
|
// these codes will be executed.
|
||||||
if (Controllers.getStage() != null) {
|
if (Controllers.getStage() != null) {
|
||||||
Controllers.getStage().hide();
|
Controllers.getStage().hide();
|
||||||
emitStatus(LoadingState.DONE);
|
launchingLatch.countDown();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
@@ -543,7 +502,7 @@ public final class LauncherHelper {
|
|||||||
break;
|
break;
|
||||||
case KEEP:
|
case KEEP:
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
emitStatus(LoadingState.DONE);
|
launchingLatch.countDown();
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case HIDE:
|
case HIDE:
|
||||||
@@ -552,7 +511,7 @@ public final class LauncherHelper {
|
|||||||
// these codes will be executed.
|
// these codes will be executed.
|
||||||
if (Controllers.getStage() != null) {
|
if (Controllers.getStage() != null) {
|
||||||
Controllers.getStage().close();
|
Controllers.getStage().close();
|
||||||
emitStatus(LoadingState.DONE);
|
launchingLatch.countDown();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -248,18 +248,13 @@ public final class Controllers {
|
|||||||
return pane;
|
return pane;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Region taskDialog(TaskExecutor executor, String title) {
|
public static TaskExecutorDialogPane taskDialog(TaskExecutor executor, String title) {
|
||||||
return taskDialog(executor, title, "");
|
return taskDialog(executor, title, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Region taskDialog(TaskExecutor executor, String title, String subtitle) {
|
public static TaskExecutorDialogPane taskDialog(TaskExecutor executor, String title, Consumer<Region> onCancel) {
|
||||||
return taskDialog(executor, title, subtitle, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Region taskDialog(TaskExecutor executor, String title, String subtitle, Consumer<Region> onCancel) {
|
|
||||||
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(onCancel);
|
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(onCancel);
|
||||||
pane.setTitle(title);
|
pane.setTitle(title);
|
||||||
pane.setSubtitle(subtitle);
|
|
||||||
pane.setExecutor(executor);
|
pane.setExecutor(executor);
|
||||||
dialog(pane);
|
dialog(pane);
|
||||||
return pane;
|
return pane;
|
||||||
|
|||||||
@@ -71,6 +71,10 @@ public final class SVG {
|
|||||||
return createSVGPath("M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z", fill, width, height);
|
return createSVGPath("M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z", fill, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Node dotsHorizontal(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||||
|
return createSVGPath("M16,12A2,2 0 0,1 18,10A2,2 0 0,1 20,12A2,2 0 0,1 18,14A2,2 0 0,1 16,12M10,12A2,2 0 0,1 12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12M4,12A2,2 0 0,1 6,10A2,2 0 0,1 8,12A2,2 0 0,1 6,14A2,2 0 0,1 4,12Z", fill, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
public static Node delete(ObjectBinding<? extends Paint> fill, double width, double height) {
|
public static Node delete(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||||
return createSVGPath("M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z", fill, width, height);
|
return createSVGPath("M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z", fill, width, height);
|
||||||
}
|
}
|
||||||
@@ -170,4 +174,8 @@ public final class SVG {
|
|||||||
public static Node check(ObjectBinding<? extends Paint> fill, double width, double height) {
|
public static Node check(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||||
return createSVGPath("M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z", fill, width, height);
|
return createSVGPath("M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z", fill, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Node arrowRight(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||||
|
return createSVGPath("M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z", fill, width, height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,22 +53,37 @@ public class AdvancedListBox extends ScrollPane {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AdvancedListBox remove(Node child) {
|
public AdvancedListBox add(int index, Node child) {
|
||||||
if (child instanceof Pane)
|
if (child instanceof Pane || child instanceof AdvancedListItem)
|
||||||
container.getChildren().remove(child);
|
container.getChildren().add(index, child);
|
||||||
else {
|
else {
|
||||||
StackPane pane = null;
|
StackPane pane = new StackPane();
|
||||||
for (Node node : container.getChildren())
|
pane.getStyleClass().add("advanced-list-box-item");
|
||||||
|
pane.getChildren().setAll(child);
|
||||||
|
container.getChildren().add(index, pane);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdvancedListBox remove(Node child) {
|
||||||
|
container.getChildren().remove(indexOf(child));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int indexOf(Node child) {
|
||||||
|
if (child instanceof Pane) {
|
||||||
|
return container.getChildren().indexOf(child);
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < container.getChildren().size(); ++i) {
|
||||||
|
Node node = container.getChildren().get(i);
|
||||||
if (node instanceof StackPane) {
|
if (node instanceof StackPane) {
|
||||||
ObservableList<Node> list = ((StackPane) node).getChildren();
|
ObservableList<Node> list = ((StackPane) node).getChildren();
|
||||||
if (list.size() == 1 && list.get(0) == child)
|
if (list.size() == 1 && list.get(0) == child)
|
||||||
pane = (StackPane) node;
|
return i;
|
||||||
}
|
}
|
||||||
if (pane == null)
|
}
|
||||||
throw new Error();
|
return -1;
|
||||||
container.getChildren().remove(pane);
|
|
||||||
}
|
}
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AdvancedListBox startCategory(String category) {
|
public AdvancedListBox startCategory(String category) {
|
||||||
|
|||||||
@@ -18,18 +18,19 @@
|
|||||||
package org.jackhuang.hmcl.ui.construct;
|
package org.jackhuang.hmcl.ui.construct;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXButton;
|
import com.jfoenix.controls.JFXButton;
|
||||||
import com.jfoenix.controls.JFXProgressBar;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.Bindings;
|
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
|
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||||
import org.jackhuang.hmcl.task.TaskListener;
|
import org.jackhuang.hmcl.task.TaskListener;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@@ -38,14 +39,11 @@ import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
|||||||
public class TaskExecutorDialogPane extends StackPane {
|
public class TaskExecutorDialogPane extends StackPane {
|
||||||
private TaskExecutor executor;
|
private TaskExecutor executor;
|
||||||
private Consumer<Region> onCancel;
|
private Consumer<Region> onCancel;
|
||||||
|
private final Consumer<FileDownloadTask.SpeedEvent> speedEventHandler;
|
||||||
|
|
||||||
@FXML
|
|
||||||
private JFXProgressBar progressBar;
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label lblTitle;
|
private Label lblTitle;
|
||||||
@FXML
|
@FXML
|
||||||
private Label lblSubtitle;
|
|
||||||
@FXML
|
|
||||||
private Label lblProgress;
|
private Label lblProgress;
|
||||||
@FXML
|
@FXML
|
||||||
private JFXButton btnCancel;
|
private JFXButton btnCancel;
|
||||||
@@ -62,10 +60,24 @@ public class TaskExecutorDialogPane extends StackPane {
|
|||||||
onCancel.accept(this);
|
onCancel.accept(this);
|
||||||
});
|
});
|
||||||
|
|
||||||
lblProgress.textProperty().bind(Bindings.createStringBinding(
|
speedEventHandler = speedEvent -> {
|
||||||
() -> taskListPane.finishedTasksProperty().get() + "/" + taskListPane.totTasksProperty().get(),
|
String unit = "B/s";
|
||||||
taskListPane.finishedTasksProperty(), taskListPane.totTasksProperty()
|
double speed = speedEvent.getSpeed();
|
||||||
));
|
if (speed > 1024) {
|
||||||
|
speed /= 1024;
|
||||||
|
unit = "KB/s";
|
||||||
|
}
|
||||||
|
if (speed > 1024) {
|
||||||
|
speed /= 1024;
|
||||||
|
unit = "MB/s";
|
||||||
|
}
|
||||||
|
double finalSpeed = speed;
|
||||||
|
String finalUnit = unit;
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
lblProgress.setText(String.format("%.1f", finalSpeed) + " " + finalUnit);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
FileDownloadTask.speedEvent.channel(FileDownloadTask.SpeedEvent.class).registerWeak(speedEventHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setExecutor(TaskExecutor executor) {
|
public void setExecutor(TaskExecutor executor) {
|
||||||
@@ -73,10 +85,18 @@ public class TaskExecutorDialogPane extends StackPane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setExecutor(TaskExecutor executor, boolean autoClose) {
|
public void setExecutor(TaskExecutor executor, boolean autoClose) {
|
||||||
|
setExecutor(executor, Collections.emptyList(), autoClose);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExecutor(TaskExecutor executor, List<String> stages) {
|
||||||
|
setExecutor(executor, stages, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExecutor(TaskExecutor executor, List<String> stages, boolean autoClose) {
|
||||||
this.executor = executor;
|
this.executor = executor;
|
||||||
|
|
||||||
if (executor != null) {
|
if (executor != null) {
|
||||||
taskListPane.setExecutor(executor);
|
taskListPane.setExecutor(executor, stages);
|
||||||
|
|
||||||
if (autoClose)
|
if (autoClose)
|
||||||
executor.addTaskListener(new TaskListener() {
|
executor.addTaskListener(new TaskListener() {
|
||||||
@@ -100,25 +120,6 @@ public class TaskExecutorDialogPane extends StackPane {
|
|||||||
lblTitle.setText(currentState);
|
lblTitle.setText(currentState);
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringProperty subtitleProperty() {
|
|
||||||
return lblSubtitle.textProperty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSubtitle() {
|
|
||||||
return lblSubtitle.getText();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSubtitle(String subtitle) {
|
|
||||||
lblSubtitle.setText(subtitle);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setProgress(double progress) {
|
|
||||||
if (progress == Double.MAX_VALUE)
|
|
||||||
progressBar.setVisible(false);
|
|
||||||
else
|
|
||||||
progressBar.setProgress(progress);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCancel(Consumer<Region> onCancel) {
|
public void setCancel(Consumer<Region> onCancel) {
|
||||||
this.onCancel = onCancel;
|
this.onCancel = onCancel;
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import com.jfoenix.controls.JFXProgressBar;
|
|||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.ReadOnlyIntegerProperty;
|
import javafx.beans.property.ReadOnlyIntegerProperty;
|
||||||
import javafx.beans.property.ReadOnlyIntegerWrapper;
|
import javafx.beans.property.ReadOnlyIntegerWrapper;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
@@ -37,20 +39,22 @@ import org.jackhuang.hmcl.mod.ModpackUpdateTask;
|
|||||||
import org.jackhuang.hmcl.mod.curse.CurseCompletionTask;
|
import org.jackhuang.hmcl.mod.curse.CurseCompletionTask;
|
||||||
import org.jackhuang.hmcl.mod.curse.CurseInstallTask;
|
import org.jackhuang.hmcl.mod.curse.CurseInstallTask;
|
||||||
import org.jackhuang.hmcl.mod.multimc.MultiMCModpackInstallTask;
|
import org.jackhuang.hmcl.mod.multimc.MultiMCModpackInstallTask;
|
||||||
|
import org.jackhuang.hmcl.setting.Theme;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||||
import org.jackhuang.hmcl.task.TaskListener;
|
import org.jackhuang.hmcl.task.TaskListener;
|
||||||
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
|
import org.jackhuang.hmcl.ui.SVG;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.Map;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
|
|
||||||
public final class TaskListPane extends StackPane {
|
public final class TaskListPane extends StackPane {
|
||||||
private final AdvancedListBox listBox = new AdvancedListBox();
|
private final AdvancedListBox listBox = new AdvancedListBox();
|
||||||
private final Map<Task<?>, ProgressListNode> nodes = new HashMap<>();
|
private final Map<Task<?>, ProgressListNode> nodes = new HashMap<>();
|
||||||
private final ReadOnlyIntegerWrapper finishedTasks = new ReadOnlyIntegerWrapper();
|
private final List<StageNode> stageNodes = new ArrayList<>();
|
||||||
private final ReadOnlyIntegerWrapper totTasks = new ReadOnlyIntegerWrapper();
|
|
||||||
|
|
||||||
public TaskListPane() {
|
public TaskListPane() {
|
||||||
listBox.setSpacing(0);
|
listBox.setSpacing(0);
|
||||||
@@ -58,33 +62,35 @@ public final class TaskListPane extends StackPane {
|
|||||||
getChildren().setAll(listBox);
|
getChildren().setAll(listBox);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyIntegerProperty finishedTasksProperty() {
|
|
||||||
return finishedTasks.getReadOnlyProperty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyIntegerProperty totTasksProperty() {
|
|
||||||
return totTasks.getReadOnlyProperty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setExecutor(TaskExecutor executor) {
|
public void setExecutor(TaskExecutor executor) {
|
||||||
|
setExecutor(executor, Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExecutor(TaskExecutor executor, List<String> stages) {
|
||||||
|
|
||||||
executor.addTaskListener(new TaskListener() {
|
executor.addTaskListener(new TaskListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
|
stageNodes.clear();
|
||||||
listBox.clear();
|
listBox.clear();
|
||||||
finishedTasks.set(0);
|
stageNodes.addAll(stages.stream().map(StageNode::new).collect(Collectors.toList()));
|
||||||
totTasks.set(0);
|
stageNodes.forEach(listBox::add);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReady(Task<?> task) {
|
public void onReady(Task<?> task) {
|
||||||
Platform.runLater(() -> totTasks.set(totTasks.getValue() + 1));
|
if (task instanceof Task.StageTask) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
stageNodes.stream().filter(x -> x.stage.equals(task.getStage())).findAny().ifPresent(StageNode::begin);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRunning(Task<?> task) {
|
public void onRunning(Task<?> task) {
|
||||||
if (!task.getSignificance().shouldShow())
|
if (!task.getSignificance().shouldShow() || task.getName() == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (task instanceof GameAssetDownloadTask) {
|
if (task instanceof GameAssetDownloadTask) {
|
||||||
@@ -117,34 +123,77 @@ public final class TaskListPane extends StackPane {
|
|||||||
|
|
||||||
ProgressListNode node = new ProgressListNode(task);
|
ProgressListNode node = new ProgressListNode(task);
|
||||||
nodes.put(task, node);
|
nodes.put(task, node);
|
||||||
Platform.runLater(() -> listBox.add(node));
|
Platform.runLater(() -> {
|
||||||
|
StageNode stageNode = stageNodes.stream().filter(x -> x.stage.equals(task.getStage())).findAny().orElse(null);
|
||||||
|
listBox.add(listBox.indexOf(stageNode) + 1, node);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFinished(Task<?> task) {
|
public void onFinished(Task<?> task) {
|
||||||
|
if (task instanceof Task.StageTask) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
stageNodes.stream().filter(x -> x.stage.equals(task.getStage())).findAny().ifPresent(StageNode::succeed);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ProgressListNode node = nodes.remove(task);
|
ProgressListNode node = nodes.remove(task);
|
||||||
if (node == null)
|
if (node == null)
|
||||||
return;
|
return;
|
||||||
node.unbind();
|
node.unbind();
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
listBox.remove(node);
|
listBox.remove(node);
|
||||||
finishedTasks.set(finishedTasks.getValue() + 1);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailed(Task<?> task, Throwable throwable) {
|
public void onFailed(Task<?> task, Throwable throwable) {
|
||||||
|
if (task instanceof Task.StageTask) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
stageNodes.stream().filter(x -> x.stage.equals(task.getStage())).findAny().ifPresent(StageNode::fail);
|
||||||
|
});
|
||||||
|
}
|
||||||
ProgressListNode node = nodes.remove(task);
|
ProgressListNode node = nodes.remove(task);
|
||||||
if (node == null)
|
if (node == null)
|
||||||
return;
|
return;
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
node.setThrowable(throwable);
|
node.setThrowable(throwable);
|
||||||
finishedTasks.set(finishedTasks.getValue() + 1);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class StageNode extends BorderPane {
|
||||||
|
private final String stage;
|
||||||
|
private final Label title = new Label();
|
||||||
|
private boolean started = false;
|
||||||
|
|
||||||
|
public StageNode(String stage) {
|
||||||
|
this.stage = stage;
|
||||||
|
|
||||||
|
title.setText(i18n(stage));
|
||||||
|
BorderPane.setAlignment(title, Pos.CENTER_LEFT);
|
||||||
|
BorderPane.setMargin(title, new Insets(0, 0, 0, 8));
|
||||||
|
setPadding(new Insets(0, 0, 8, 4));
|
||||||
|
setCenter(title);
|
||||||
|
setLeft(SVG.dotsHorizontal(Theme.blackFillBinding(), 14, 14));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void begin() {
|
||||||
|
if (started) return;
|
||||||
|
started = true;
|
||||||
|
setLeft(FXUtils.limitingSize(SVG.arrowRight(Theme.blackFillBinding(), 14, 14), 14, 14));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fail() {
|
||||||
|
setLeft(FXUtils.limitingSize(SVG.close(Theme.blackFillBinding(), 14, 14), 14, 14));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void succeed() {
|
||||||
|
setLeft(FXUtils.limitingSize(SVG.check(Theme.blackFillBinding(), 14, 14), 14, 14));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class ProgressListNode extends BorderPane {
|
private static class ProgressListNode extends BorderPane {
|
||||||
private final JFXProgressBar bar = new JFXProgressBar();
|
private final JFXProgressBar bar = new JFXProgressBar();
|
||||||
private final Label title = new Label();
|
private final Label title = new Label();
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
|
|||||||
});
|
});
|
||||||
|
|
||||||
pane.setTitle(i18n("message.doing"));
|
pane.setTitle(i18n("message.doing"));
|
||||||
pane.setProgress(Double.MAX_VALUE);
|
|
||||||
if (settings.containsKey("title")) {
|
if (settings.containsKey("title")) {
|
||||||
Object title = settings.get("title");
|
Object title = settings.get("title");
|
||||||
if (title instanceof StringProperty)
|
if (title instanceof StringProperty)
|
||||||
@@ -51,14 +50,6 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
|
|||||||
pane.setTitle((String) title);
|
pane.setTitle((String) title);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.containsKey("subtitle")) {
|
|
||||||
Object subtitle = settings.get("subtitle");
|
|
||||||
if (subtitle instanceof StringProperty)
|
|
||||||
pane.subtitleProperty().bind((StringProperty) subtitle);
|
|
||||||
else if (subtitle instanceof String)
|
|
||||||
pane.setSubtitle((String) subtitle);
|
|
||||||
}
|
|
||||||
|
|
||||||
runInFX(() -> {
|
runInFX(() -> {
|
||||||
TaskExecutor executor = task.cancellableExecutor(new TaskListener() {
|
TaskExecutor executor = task.cancellableExecutor(new TaskListener() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import com.jfoenix.controls.JFXButton?>
|
<?import com.jfoenix.controls.JFXButton?>
|
||||||
<?import com.jfoenix.controls.JFXProgressBar?>
|
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.ScrollPane?>
|
<?import javafx.scene.control.ScrollPane?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.*?>
|
||||||
@@ -11,13 +10,9 @@
|
|||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
type="StackPane" FXUtils.limitWidth="500" FXUtils.limitHeight="300">
|
type="StackPane" FXUtils.limitWidth="500" FXUtils.limitHeight="300">
|
||||||
<BorderPane>
|
<BorderPane>
|
||||||
<top>
|
|
||||||
<JFXProgressBar fx:id="progressBar" StackPane.alignment="TOP_CENTER" />
|
|
||||||
</top>
|
|
||||||
<center>
|
<center>
|
||||||
<VBox alignment="TOP_CENTER" style="-fx-padding: 16px;">
|
<VBox style="-fx-padding: 16px;">
|
||||||
<Label fx:id="lblTitle" style="-fx-font-size: 20px;" />
|
<Label fx:id="lblTitle" style="-fx-font-size: 14px; -fx-font-weight: BOLD;" />
|
||||||
<Label fx:id="lblSubtitle" style="-fx-font-size: 14px;" />
|
|
||||||
|
|
||||||
<ScrollPane fitToHeight="true" fitToWidth="true" VBox.vgrow="ALWAYS">
|
<ScrollPane fitToHeight="true" fitToWidth="true" VBox.vgrow="ALWAYS">
|
||||||
<TaskListPane fx:id="taskListPane" />
|
<TaskListPane fx:id="taskListPane" />
|
||||||
|
|||||||
@@ -179,10 +179,10 @@ launch.failed.decompressing_natives=無法解壓縮遊戲資源庫。
|
|||||||
launch.failed.download_library=無法下載遊戲相依元件 %s。
|
launch.failed.download_library=無法下載遊戲相依元件 %s。
|
||||||
launch.failed.executable_permission=無法為啟動檔案新增執行權限。
|
launch.failed.executable_permission=無法為啟動檔案新增執行權限。
|
||||||
launch.failed.exited_abnormally=遊戲非正常退出,請查看記錄檔案,或聯絡他人尋求幫助。
|
launch.failed.exited_abnormally=遊戲非正常退出,請查看記錄檔案,或聯絡他人尋求幫助。
|
||||||
launch.state.dependencies=正在處理遊戲相依元件
|
launch.state.dependencies=處理遊戲相依元件
|
||||||
launch.state.done=啟動完成
|
launch.state.done=啟動完成
|
||||||
launch.state.logging_in=登入中
|
launch.state.logging_in=登入
|
||||||
launch.state.modpack=正在下載必要檔案
|
launch.state.modpack=下載必要檔案
|
||||||
launch.state.waiting_launching=等待遊戲啟動
|
launch.state.waiting_launching=等待遊戲啟動
|
||||||
launch.wrong_javadir=Java 路徑錯誤,將自動重設為預設 Java 路徑。
|
launch.wrong_javadir=Java 路徑錯誤,將自動重設為預設 Java 路徑。
|
||||||
|
|
||||||
|
|||||||
@@ -180,10 +180,10 @@ launch.failed.decompressing_natives=未能解压游戏本地库。
|
|||||||
launch.failed.download_library=未能下载游戏依赖 %s.
|
launch.failed.download_library=未能下载游戏依赖 %s.
|
||||||
launch.failed.executable_permission=未能为启动文件添加执行权限。
|
launch.failed.executable_permission=未能为启动文件添加执行权限。
|
||||||
launch.failed.exited_abnormally=游戏非正常退出,请查看日志文件,或联系他人寻求帮助。
|
launch.failed.exited_abnormally=游戏非正常退出,请查看日志文件,或联系他人寻求帮助。
|
||||||
launch.state.dependencies=正在处理游戏依赖
|
launch.state.dependencies=处理游戏依赖
|
||||||
launch.state.done=启动完成
|
launch.state.done=启动完成
|
||||||
launch.state.logging_in=登录中
|
launch.state.logging_in=登录
|
||||||
launch.state.modpack=正在下载必要文件
|
launch.state.modpack=下载必要文件
|
||||||
launch.state.waiting_launching=等待游戏启动
|
launch.state.waiting_launching=等待游戏启动
|
||||||
launch.wrong_javadir=错误的 Java 路径,将自动重置为默认 Java 路径。
|
launch.wrong_javadir=错误的 Java 路径,将自动重置为默认 Java 路径。
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,10 @@ import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
|
|||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.CancellationException;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,7 +45,7 @@ public final class AsyncTaskExecutor extends TaskExecutor {
|
|||||||
@Override
|
@Override
|
||||||
public TaskExecutor start() {
|
public TaskExecutor start() {
|
||||||
taskListeners.forEach(TaskListener::onStart);
|
taskListeners.forEach(TaskListener::onStart);
|
||||||
future = executeTasks(Collections.singleton(firstTask))
|
future = executeTasks(null, Collections.singleton(firstTask))
|
||||||
.thenApplyAsync(exception -> {
|
.thenApplyAsync(exception -> {
|
||||||
boolean success = exception == null;
|
boolean success = exception == null;
|
||||||
|
|
||||||
@@ -95,7 +98,7 @@ public final class AsyncTaskExecutor extends TaskExecutor {
|
|||||||
future.cancel(true);
|
future.cancel(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompletableFuture<Exception> executeTasks(Collection<Task<?>> tasks) {
|
private CompletableFuture<Exception> executeTasks(Task<?> parentTask, Collection<Task<?>> tasks) {
|
||||||
if (tasks == null || tasks.isEmpty())
|
if (tasks == null || tasks.isEmpty())
|
||||||
return CompletableFuture.completedFuture(null);
|
return CompletableFuture.completedFuture(null);
|
||||||
|
|
||||||
@@ -105,7 +108,7 @@ public final class AsyncTaskExecutor extends TaskExecutor {
|
|||||||
|
|
||||||
return CompletableFuture.allOf(tasks.stream()
|
return CompletableFuture.allOf(tasks.stream()
|
||||||
.map(task -> CompletableFuture.completedFuture(null)
|
.map(task -> CompletableFuture.completedFuture(null)
|
||||||
.thenComposeAsync(unused2 -> executeTask(task))
|
.thenComposeAsync(unused2 -> executeTask(parentTask, task))
|
||||||
).toArray(CompletableFuture<?>[]::new));
|
).toArray(CompletableFuture<?>[]::new));
|
||||||
})
|
})
|
||||||
.thenApplyAsync(unused -> (Exception) null)
|
.thenApplyAsync(unused -> (Exception) null)
|
||||||
@@ -120,11 +123,13 @@ public final class AsyncTaskExecutor extends TaskExecutor {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompletableFuture<?> executeTask(Task<?> task) {
|
private CompletableFuture<?> executeTask(Task<?> parentTask, Task<?> task) {
|
||||||
return CompletableFuture.completedFuture(null)
|
return CompletableFuture.completedFuture(null)
|
||||||
.thenComposeAsync(unused -> {
|
.thenComposeAsync(unused -> {
|
||||||
task.setCancelled(this::isCancelled);
|
task.setCancelled(this::isCancelled);
|
||||||
task.setState(Task.TaskState.READY);
|
task.setState(Task.TaskState.READY);
|
||||||
|
if (parentTask != null && task.getStage() == null)
|
||||||
|
task.setStage(parentTask.getStage());
|
||||||
|
|
||||||
if (task.getSignificance().shouldLog())
|
if (task.getSignificance().shouldLog())
|
||||||
Logging.LOG.log(Level.FINE, "Executing task: " + task.getName());
|
Logging.LOG.log(Level.FINE, "Executing task: " + task.getName());
|
||||||
@@ -137,7 +142,7 @@ public final class AsyncTaskExecutor extends TaskExecutor {
|
|||||||
return CompletableFuture.completedFuture(null);
|
return CompletableFuture.completedFuture(null);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.thenComposeAsync(unused -> executeTasks(task.getDependents()))
|
.thenComposeAsync(unused -> executeTasks(task, task.getDependents()))
|
||||||
.thenComposeAsync(dependentsException -> {
|
.thenComposeAsync(dependentsException -> {
|
||||||
boolean isDependentsSucceeded = dependentsException == null;
|
boolean isDependentsSucceeded = dependentsException == null;
|
||||||
|
|
||||||
@@ -158,7 +163,7 @@ public final class AsyncTaskExecutor extends TaskExecutor {
|
|||||||
rethrow(throwable);
|
rethrow(throwable);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.thenComposeAsync(unused -> executeTasks(task.getDependencies()))
|
.thenComposeAsync(unused -> executeTasks(task, task.getDependencies()))
|
||||||
.thenComposeAsync(dependenciesException -> {
|
.thenComposeAsync(dependenciesException -> {
|
||||||
boolean isDependenciesSucceeded = dependenciesException == null;
|
boolean isDependenciesSucceeded = dependenciesException == null;
|
||||||
|
|
||||||
|
|||||||
@@ -23,14 +23,7 @@ import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CancellationException;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.CompletionException;
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
@@ -47,7 +40,7 @@ public class CancellableTaskExecutor extends TaskExecutor {
|
|||||||
public TaskExecutor start() {
|
public TaskExecutor start() {
|
||||||
taskListeners.forEach(TaskListener::onStart);
|
taskListeners.forEach(TaskListener::onStart);
|
||||||
workerQueue.add(Schedulers.schedule(scheduler, wrap(() -> {
|
workerQueue.add(Schedulers.schedule(scheduler, wrap(() -> {
|
||||||
boolean flag = executeTasks(Collections.singleton(firstTask));
|
boolean flag = executeTasks(null, Collections.singleton(firstTask));
|
||||||
taskListeners.forEach(it -> it.onStop(flag, this));
|
taskListeners.forEach(it -> it.onStop(flag, this));
|
||||||
})));
|
})));
|
||||||
return this;
|
return this;
|
||||||
@@ -58,7 +51,7 @@ public class CancellableTaskExecutor extends TaskExecutor {
|
|||||||
taskListeners.forEach(TaskListener::onStart);
|
taskListeners.forEach(TaskListener::onStart);
|
||||||
AtomicBoolean flag = new AtomicBoolean(true);
|
AtomicBoolean flag = new AtomicBoolean(true);
|
||||||
Future<?> future = Schedulers.schedule(scheduler, wrap(() -> {
|
Future<?> future = Schedulers.schedule(scheduler, wrap(() -> {
|
||||||
flag.set(executeTasks(Collections.singleton(firstTask)));
|
flag.set(executeTasks(null, Collections.singleton(firstTask)));
|
||||||
taskListeners.forEach(it -> it.onStop(flag.get(), this));
|
taskListeners.forEach(it -> it.onStop(flag.get(), this));
|
||||||
}));
|
}));
|
||||||
workerQueue.add(future);
|
workerQueue.add(future);
|
||||||
@@ -82,7 +75,7 @@ public class CancellableTaskExecutor extends TaskExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean executeTasks(Collection<? extends Task<?>> tasks) throws InterruptedException {
|
private boolean executeTasks(Task<?> parentTask, Collection<? extends Task<?>> tasks) throws InterruptedException {
|
||||||
if (tasks.isEmpty())
|
if (tasks.isEmpty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@@ -92,7 +85,7 @@ public class CancellableTaskExecutor extends TaskExecutor {
|
|||||||
for (Task<?> task : tasks) {
|
for (Task<?> task : tasks) {
|
||||||
if (cancelled.get())
|
if (cancelled.get())
|
||||||
return false;
|
return false;
|
||||||
Invoker invoker = new Invoker(task, latch, success);
|
Invoker invoker = new Invoker(parentTask, task, latch, success);
|
||||||
try {
|
try {
|
||||||
Future<?> future = Schedulers.schedule(scheduler, invoker);
|
Future<?> future = Schedulers.schedule(scheduler, invoker);
|
||||||
workerQueue.add(future);
|
workerQueue.add(future);
|
||||||
@@ -112,7 +105,7 @@ public class CancellableTaskExecutor extends TaskExecutor {
|
|||||||
return success.get() && !cancelled.get();
|
return success.get() && !cancelled.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean executeTask(Task<?> task) {
|
private boolean executeTask(Task<?> parentTask, Task<?> task) {
|
||||||
task.setCancelled(this::isCancelled);
|
task.setCancelled(this::isCancelled);
|
||||||
|
|
||||||
if (cancelled.get()) {
|
if (cancelled.get()) {
|
||||||
@@ -122,6 +115,8 @@ public class CancellableTaskExecutor extends TaskExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
task.setState(Task.TaskState.READY);
|
task.setState(Task.TaskState.READY);
|
||||||
|
if (parentTask != null && task.getStage() == null)
|
||||||
|
task.setStage(parentTask.getStage());
|
||||||
|
|
||||||
if (task.getSignificance().shouldLog())
|
if (task.getSignificance().shouldLog())
|
||||||
Logging.LOG.log(Level.FINE, "Executing task: " + task.getName());
|
Logging.LOG.log(Level.FINE, "Executing task: " + task.getName());
|
||||||
@@ -140,8 +135,12 @@ public class CancellableTaskExecutor extends TaskExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Collection<? extends Task<?>> dependents = task.getDependents();
|
Collection<? extends Task<?>> dependents = task.getDependents();
|
||||||
boolean doDependentsSucceeded = executeTasks(dependents);
|
boolean doDependentsSucceeded = executeTasks(task, dependents);
|
||||||
Exception dependentsException = dependents.stream().map(Task::getException).filter(Objects::nonNull).findAny().orElse(null);
|
Exception dependentsException = dependents.stream().map(Task::getException)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.filter(x -> !(x instanceof CancellationException))
|
||||||
|
.filter(x -> !(x instanceof InterruptedException))
|
||||||
|
.findAny().orElse(null);
|
||||||
if (!doDependentsSucceeded && task.isRelyingOnDependents() || cancelled.get()) {
|
if (!doDependentsSucceeded && task.isRelyingOnDependents() || cancelled.get()) {
|
||||||
task.setException(dependentsException);
|
task.setException(dependentsException);
|
||||||
throw new CancellationException();
|
throw new CancellationException();
|
||||||
@@ -163,8 +162,12 @@ public class CancellableTaskExecutor extends TaskExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Collection<? extends Task<?>> dependencies = task.getDependencies();
|
Collection<? extends Task<?>> dependencies = task.getDependencies();
|
||||||
boolean doDependenciesSucceeded = executeTasks(dependencies);
|
boolean doDependenciesSucceeded = executeTasks(task, dependencies);
|
||||||
Exception dependenciesException = dependencies.stream().map(Task::getException).filter(Objects::nonNull).findAny().orElse(null);
|
Exception dependenciesException = dependencies.stream().map(Task::getException)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.filter(x -> !(x instanceof CancellationException))
|
||||||
|
.filter(x -> !(x instanceof InterruptedException))
|
||||||
|
.findAny().orElse(null);
|
||||||
|
|
||||||
if (doDependenciesSucceeded)
|
if (doDependenciesSucceeded)
|
||||||
task.setDependenciesSucceeded();
|
task.setDependenciesSucceeded();
|
||||||
@@ -250,11 +253,13 @@ public class CancellableTaskExecutor extends TaskExecutor {
|
|||||||
|
|
||||||
private class Invoker implements Runnable {
|
private class Invoker implements Runnable {
|
||||||
|
|
||||||
|
private final Task<?> parentTask;
|
||||||
private final Task<?> task;
|
private final Task<?> task;
|
||||||
private final CountDownLatch latch;
|
private final CountDownLatch latch;
|
||||||
private final AtomicBoolean success;
|
private final AtomicBoolean success;
|
||||||
|
|
||||||
public Invoker(Task<?> task, CountDownLatch latch, AtomicBoolean success) {
|
public Invoker(Task<?> parentTask, Task<?> task, CountDownLatch latch, AtomicBoolean success) {
|
||||||
|
this.parentTask = parentTask;
|
||||||
this.task = task;
|
this.task = task;
|
||||||
this.latch = latch;
|
this.latch = latch;
|
||||||
this.success = success;
|
this.success = success;
|
||||||
@@ -264,7 +269,7 @@ public class CancellableTaskExecutor extends TaskExecutor {
|
|||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
Thread.currentThread().setName(task.getName());
|
Thread.currentThread().setName(task.getName());
|
||||||
if (!executeTask(task))
|
if (!executeTask(parentTask, task))
|
||||||
success.set(false);
|
success.set(false);
|
||||||
} finally {
|
} finally {
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
|
|||||||
@@ -17,13 +17,11 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.task;
|
package org.jackhuang.hmcl.task;
|
||||||
|
|
||||||
|
import org.jackhuang.hmcl.event.Event;
|
||||||
|
import org.jackhuang.hmcl.event.EventBus;
|
||||||
import org.jackhuang.hmcl.util.CacheRepository;
|
import org.jackhuang.hmcl.util.CacheRepository;
|
||||||
import org.jackhuang.hmcl.util.Logging;
|
import org.jackhuang.hmcl.util.Logging;
|
||||||
import org.jackhuang.hmcl.util.io.ChecksumMismatchException;
|
import org.jackhuang.hmcl.util.io.*;
|
||||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
|
||||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
|
||||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
|
||||||
import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -35,10 +33,8 @@ import java.net.URL;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
@@ -279,7 +275,7 @@ public class FileDownloadTask extends Task<Void> {
|
|||||||
updateProgress(downloaded, contentLength);
|
updateProgress(downloaded, contentLength);
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
if (now - lastTime >= 1000) {
|
if (now - lastTime >= 1000) {
|
||||||
updateMessage((downloaded - lastDownloaded) / 1024 + "KB/s");
|
updateDownloadSpeed(downloaded - lastDownloaded);
|
||||||
lastDownloaded = downloaded;
|
lastDownloaded = downloaded;
|
||||||
lastTime = now;
|
lastTime = now;
|
||||||
}
|
}
|
||||||
@@ -337,4 +333,39 @@ public class FileDownloadTask extends Task<Void> {
|
|||||||
throw new DownloadException(urls.get(0), exception);
|
throw new DownloadException(urls.get(0), exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final Timer timer = new Timer("DownloadSpeedRecorder", true);
|
||||||
|
private static final AtomicInteger downloadSpeed = new AtomicInteger(0);
|
||||||
|
public static final EventBus speedEvent = new EventBus();
|
||||||
|
|
||||||
|
static {
|
||||||
|
timer.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
speedEvent.fireEvent(new SpeedEvent(speedEvent, downloadSpeed.getAndSet(0)));
|
||||||
|
}
|
||||||
|
}, 0, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void updateDownloadSpeed(int speed) {
|
||||||
|
downloadSpeed.addAndGet(speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SpeedEvent extends Event {
|
||||||
|
private final int speed;
|
||||||
|
|
||||||
|
public SpeedEvent(Object source, int speed) {
|
||||||
|
super(source);
|
||||||
|
|
||||||
|
this.speed = speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download speed in byte/sec.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int getSpeed() {
|
||||||
|
return speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,25 @@ public abstract class Task<T> {
|
|||||||
return cancelled != null ? cancelled.get() : false;
|
return cancelled != null ? cancelled.get() : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stage
|
||||||
|
private String stage = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stage of task implies the goal of this task, for grouping tasks.
|
||||||
|
* Stage will inherit from the parent task.
|
||||||
|
*/
|
||||||
|
public String getStage() {
|
||||||
|
return stage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* You must initialize stage in preExecute.
|
||||||
|
* @param stage the stage
|
||||||
|
*/
|
||||||
|
public void setStage(String stage) {
|
||||||
|
this.stage = stage;
|
||||||
|
}
|
||||||
|
|
||||||
// state
|
// state
|
||||||
private TaskState state = TaskState.READY;
|
private TaskState state = TaskState.READY;
|
||||||
|
|
||||||
@@ -344,18 +363,18 @@ public abstract class Task<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final TaskExecutor cancellableExecutor() {
|
public final TaskExecutor cancellableExecutor() {
|
||||||
return new CancellableTaskExecutor(this);
|
return new AsyncTaskExecutor(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final TaskExecutor cancellableExecutor(boolean start) {
|
public final TaskExecutor cancellableExecutor(boolean start) {
|
||||||
TaskExecutor executor = new CancellableTaskExecutor(this);
|
TaskExecutor executor = new AsyncTaskExecutor(this);
|
||||||
if (start)
|
if (start)
|
||||||
executor.start();
|
executor.start();
|
||||||
return executor;
|
return executor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final TaskExecutor cancellableExecutor(TaskListener taskListener) {
|
public final TaskExecutor cancellableExecutor(TaskListener taskListener) {
|
||||||
TaskExecutor executor = new CancellableTaskExecutor(this);
|
TaskExecutor executor = new AsyncTaskExecutor(this);
|
||||||
executor.addTaskListener(taskListener);
|
executor.addTaskListener(taskListener);
|
||||||
return executor;
|
return executor;
|
||||||
}
|
}
|
||||||
@@ -743,6 +762,12 @@ public abstract class Task<T> {
|
|||||||
return whenComplete(executor, () -> success.accept(getResult()), failure);
|
return whenComplete(executor, () -> success.accept(getResult()), failure);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<T> withStage(String stage) {
|
||||||
|
StageTask<T> task = new StageTask<>(this);
|
||||||
|
task.setStage(stage);
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
public static Task<Void> runAsync(ExceptionalRunnable<?> closure) {
|
public static Task<Void> runAsync(ExceptionalRunnable<?> closure) {
|
||||||
return runAsync(Schedulers.defaultScheduler(), closure);
|
return runAsync(Schedulers.defaultScheduler(), closure);
|
||||||
}
|
}
|
||||||
@@ -760,6 +785,10 @@ public abstract class Task<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static <T> Task<T> composeAsync(ExceptionalSupplier<Task<T>, ?> fn) {
|
public static <T> Task<T> composeAsync(ExceptionalSupplier<Task<T>, ?> fn) {
|
||||||
|
return composeAsync(getCaller(), fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Task<T> composeAsync(String name, ExceptionalSupplier<Task<T>, ?> fn) {
|
||||||
return new Task<T>() {
|
return new Task<T>() {
|
||||||
Task<T> then;
|
Task<T> then;
|
||||||
|
|
||||||
@@ -774,7 +803,7 @@ public abstract class Task<T> {
|
|||||||
public Collection<Task<?>> getDependencies() {
|
public Collection<Task<?>> getDependencies() {
|
||||||
return then == null ? Collections.emptySet() : Collections.singleton(then);
|
return then == null ? Collections.emptySet() : Collections.singleton(then);
|
||||||
}
|
}
|
||||||
};
|
}.setName(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <V> Task<V> supplyAsync(Callable<V> callable) {
|
public static <V> Task<V> supplyAsync(Callable<V> callable) {
|
||||||
@@ -960,4 +989,21 @@ public abstract class Task<T> {
|
|||||||
return relyingOnDependents;
|
return relyingOnDependents;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class StageTask<T> extends Task<T> {
|
||||||
|
private final Task<T> task;
|
||||||
|
StageTask(Task<T> task) {
|
||||||
|
this.task = task;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Task<?>> getDependents() {
|
||||||
|
return Collections.singleton(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute() throws Exception {
|
||||||
|
setResult(task.getResult());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user