Merge branch 'task-refactor' into javafx

# Conflicts:
#	HMCL/src/main/java/org/jackhuang/hmcl/ui/LeftPaneController.java
#	HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java
#	HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java
#	HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPage.java
#	HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java
#	HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java
#	HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java
#	HMCLCore/src/main/java/org/jackhuang/hmcl/download/MaintainTask.java
#	HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java
#	HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java
This commit is contained in:
huanghongxun
2019-05-10 10:33:06 +08:00
89 changed files with 1074 additions and 1347 deletions

View File

@@ -30,7 +30,7 @@ import java.util.List;
/** /**
* Export the game to a mod pack file. * Export the game to a mod pack file.
*/ */
public class HMCLModpackExportTask extends Task { public class HMCLModpackExportTask extends Task<Void> {
private final DefaultGameRepository repository; private final DefaultGameRepository repository;
private final String version; private final String version;
private final List<String> whitelist; private final List<String> whitelist;

View File

@@ -36,13 +36,13 @@ import java.io.IOException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
public final class HMCLModpackInstallTask extends Task { public final class HMCLModpackInstallTask extends Task<Void> {
private final File zipFile; private final File zipFile;
private final String name; private final String name;
private final HMCLGameRepository repository; private final HMCLGameRepository repository;
private final Modpack modpack; private final Modpack modpack;
private final List<Task> dependencies = new LinkedList<>(); private final List<Task<?>> dependencies = new LinkedList<>();
private final List<Task> dependents = new LinkedList<>(); private final List<Task<?>> dependents = new LinkedList<>();
public HMCLModpackInstallTask(Profile profile, File zipFile, Modpack modpack, String name) { public HMCLModpackInstallTask(Profile profile, File zipFile, Modpack modpack, String name) {
DependencyManager dependency = profile.getDependency(); DependencyManager dependency = profile.getDependency();
@@ -77,12 +77,12 @@ public final class HMCLModpackInstallTask extends Task {
} }
@Override @Override
public List<Task> getDependencies() { public List<Task<?>> getDependencies() {
return dependencies; return dependencies;
} }
@Override @Override
public List<Task> getDependents() { public List<Task<?>> getDependents() {
return dependents; return dependents;
} }

View File

@@ -103,7 +103,7 @@ public final class LauncherHelper {
try { try {
checkGameState(profile, setting, version, () -> { checkGameState(profile, setting, version, () -> {
Controllers.dialog(launchingStepsPane); Controllers.dialog(launchingStepsPane);
Schedulers.newThread().schedule(this::launch0); Schedulers.newThread().execute(this::launch0);
}); });
} catch (InterruptedException ignore) { } catch (InterruptedException ignore) {
} }
@@ -122,15 +122,15 @@ public final class LauncherHelper {
Version version = MaintainTask.maintain(repository.getResolvedVersion(selectedVersion)); Version version = MaintainTask.maintain(repository.getResolvedVersion(selectedVersion));
Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version)); Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version));
TaskExecutor executor = Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES)) TaskExecutor executor = Task.runAsync(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES))
.then(() -> { .thenCompose(() -> {
if (setting.isNotCheckGame()) if (setting.isNotCheckGame())
return null; return null;
else else
return dependencyManager.checkGameCompletionAsync(version); return dependencyManager.checkGameCompletionAsync(version);
}) })
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.MODS))) .thenRun(Schedulers.javafx(), () -> emitStatus(LoadingState.MODS))
.then(() -> { .thenCompose(() -> {
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()))
@@ -141,8 +141,8 @@ public final class LauncherHelper {
return null; return null;
} }
}) })
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN))) .thenRun(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN))
.thenCompose(() -> Task.ofResult(i18n("account.methods"), () -> { .thenSupply(i18n("account.methods"), () -> {
try { try {
return account.logIn(); return account.logIn();
} catch (CredentialExpiredException e) { } catch (CredentialExpiredException e) {
@@ -152,7 +152,7 @@ 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);
} }
})) })
.thenApply(Schedulers.javafx(), authInfo -> { .thenApply(Schedulers.javafx(), authInfo -> {
emitStatus(LoadingState.LAUNCHING); emitStatus(LoadingState.LAUNCHING);
return authInfo; return authInfo;
@@ -164,7 +164,7 @@ public final class LauncherHelper {
setting.toLaunchOptions(profile.getGameDir()), setting.toLaunchOptions(profile.getGameDir()),
launcherVisibility == LauncherVisibility.CLOSE launcherVisibility == LauncherVisibility.CLOSE
? null // Unnecessary to start listening to game process output when close launcher immediately after game launched. ? null // Unnecessary to start listening to game process output when close launcher immediately after game launched.
: new HMCLProcessListener(authInfo, setting, gameVersion.isPresent()) : new HMCLProcessListener(authInfo, gameVersion.isPresent())
)) ))
.thenCompose(launcher -> { // launcher is prev task's result .thenCompose(launcher -> { // launcher is prev task's result
if (scriptFile == null) { if (scriptFile == null) {
@@ -200,7 +200,7 @@ public final class LauncherHelper {
final AtomicInteger finished = new AtomicInteger(0); final AtomicInteger finished = new AtomicInteger(0);
@Override @Override
public void onFinished(Task task) { public void onFinished(Task<?> task) {
finished.incrementAndGet(); finished.incrementAndGet();
int runningTasks = executor.getRunningTasks(); int runningTasks = executor.getRunningTasks();
Platform.runLater(() -> launchingStepsPane.setProgress(1.0 * finished.get() / runningTasks)); Platform.runLater(() -> launchingStepsPane.setProgress(1.0 * finished.get() / runningTasks));
@@ -214,7 +214,7 @@ public final class LauncherHelper {
// because onStop will be invoked if tasks fail when the executor service shut down. // because onStop will be invoked if tasks fail when the executor service shut down.
if (!Controllers.isStopped()) { if (!Controllers.isStopped()) {
launchingStepsPane.fireEvent(new DialogCloseEvent()); launchingStepsPane.fireEvent(new DialogCloseEvent());
Exception ex = executor.getLastException(); Exception ex = executor.getException();
if (ex != null) { if (ex != null) {
String message; String message;
if (ex instanceof CurseCompletionException) { if (ex instanceof CurseCompletionException) {
@@ -420,7 +420,7 @@ public final class LauncherHelper {
} }
} }
private static class LaunchTask<T> extends TaskResult<T> { private static class LaunchTask<T> extends Task<T> {
private final ExceptionalSupplier<T, Exception> supplier; private final ExceptionalSupplier<T, Exception> supplier;
public LaunchTask(ExceptionalSupplier<T, Exception> supplier) { public LaunchTask(ExceptionalSupplier<T, Exception> supplier) {
@@ -440,7 +440,6 @@ public final class LauncherHelper {
*/ */
class HMCLProcessListener implements ProcessListener { class HMCLProcessListener implements ProcessListener {
private final VersionSetting setting;
private final Map<String, String> forbiddenTokens; private final Map<String, String> forbiddenTokens;
private ManagedProcess process; private ManagedProcess process;
private boolean lwjgl; private boolean lwjgl;
@@ -449,8 +448,7 @@ public final class LauncherHelper {
private final LinkedList<Pair<String, Log4jLevel>> logs; private final LinkedList<Pair<String, Log4jLevel>> logs;
private final CountDownLatch latch = new CountDownLatch(1); private final CountDownLatch latch = new CountDownLatch(1);
public HMCLProcessListener(AuthInfo authInfo, VersionSetting setting, boolean detectWindow) { public HMCLProcessListener(AuthInfo authInfo, boolean detectWindow) {
this.setting = setting;
this.detectWindow = detectWindow; this.detectWindow = detectWindow;
if (authInfo == null) if (authInfo == null)

View File

@@ -86,7 +86,7 @@ public final class ModpackHelper {
throw new UnsupportedModpackException(); throw new UnsupportedModpackException();
} }
public static Task getInstallTask(Profile profile, File zipFile, String name, Modpack modpack) { public static Task<Void> getInstallTask(Profile profile, File zipFile, String name, Modpack modpack) {
profile.getRepository().markVersionAsModpack(name); profile.getRepository().markVersionAsModpack(name);
ExceptionalRunnable<?> success = () -> { ExceptionalRunnable<?> success = () -> {
@@ -117,11 +117,11 @@ public final class ModpackHelper {
else if (modpack.getManifest() instanceof MultiMCInstanceConfiguration) else if (modpack.getManifest() instanceof MultiMCInstanceConfiguration)
return new MultiMCModpackInstallTask(profile.getDependency(), zipFile, modpack, ((MultiMCInstanceConfiguration) modpack.getManifest()), name) return new MultiMCModpackInstallTask(profile.getDependency(), zipFile, modpack, ((MultiMCInstanceConfiguration) modpack.getManifest()), name)
.whenComplete(Schedulers.defaultScheduler(), success, failure) .whenComplete(Schedulers.defaultScheduler(), success, failure)
.then(new MultiMCInstallVersionSettingTask(profile, ((MultiMCInstanceConfiguration) modpack.getManifest()), name)); .thenCompose(new MultiMCInstallVersionSettingTask(profile, ((MultiMCInstanceConfiguration) modpack.getManifest()), name));
else throw new IllegalStateException("Unrecognized modpack: " + modpack); else throw new IllegalStateException("Unrecognized modpack: " + modpack);
} }
public static Task getUpdateTask(Profile profile, File zipFile, Charset charset, String name, ModpackConfiguration<?> configuration) throws UnsupportedModpackException, MismatchedModpackTypeException { public static Task<Void> getUpdateTask(Profile profile, File zipFile, Charset charset, String name, ModpackConfiguration<?> configuration) throws UnsupportedModpackException, MismatchedModpackTypeException {
Modpack modpack = ModpackHelper.readModpackManifest(zipFile.toPath(), charset); Modpack modpack = ModpackHelper.readModpackManifest(zipFile.toPath(), charset);
switch (configuration.getType()) { switch (configuration.getType()) {

View File

@@ -20,13 +20,12 @@ package org.jackhuang.hmcl.game;
import org.jackhuang.hmcl.mod.MultiMCInstanceConfiguration; import org.jackhuang.hmcl.mod.MultiMCInstanceConfiguration;
import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.VersionSetting; import org.jackhuang.hmcl.setting.VersionSetting;
import org.jackhuang.hmcl.task.Scheduler;
import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import java.util.Objects; import java.util.Objects;
public final class MultiMCInstallVersionSettingTask extends Task { public final class MultiMCInstallVersionSettingTask extends Task<Void> {
private final Profile profile; private final Profile profile;
private final MultiMCInstanceConfiguration manifest; private final MultiMCInstanceConfiguration manifest;
private final String version; private final String version;
@@ -35,11 +34,8 @@ public final class MultiMCInstallVersionSettingTask extends Task {
this.profile = profile; this.profile = profile;
this.manifest = manifest; this.manifest = manifest;
this.version = version; this.version = version;
}
@Override setExecutor(Schedulers.javafx());
public Scheduler getScheduler() {
return Schedulers.javafx();
} }
@Override @Override

View File

@@ -197,7 +197,7 @@ public final class Accounts {
Account selected = selectedAccount.get(); Account selected = selectedAccount.get();
if (selected != null) { if (selected != null) {
Schedulers.io().schedule(() -> { Schedulers.io().execute(() -> {
try { try {
selected.logIn(); selected.logIn();
} catch (AuthenticationException e) { } catch (AuthenticationException e) {
@@ -209,7 +209,7 @@ public final class Accounts {
for (AuthlibInjectorServer server : config().getAuthlibInjectorServers()) { for (AuthlibInjectorServer server : config().getAuthlibInjectorServers()) {
if (selected instanceof AuthlibInjectorAccount && ((AuthlibInjectorAccount) selected).getServer() == server) if (selected instanceof AuthlibInjectorAccount && ((AuthlibInjectorAccount) selected).getServer() == server)
continue; continue;
Schedulers.io().schedule(() -> { Schedulers.io().execute(() -> {
try { try {
server.fetchMetadataResponse(); server.fetchMetadataResponse();
} catch (IOException e) { } catch (IOException e) {

View File

@@ -208,7 +208,7 @@ public final class Controllers {
dialog(i18n("launcher.cache_directory.invalid")); dialog(i18n("launcher.cache_directory.invalid"));
} }
Task.of(JavaVersion::initialize).start(); Task.runAsync(JavaVersion::initialize).start();
scene = new Scene(decorator.getDecorator(), 800, 519); scene = new Scene(decorator.getDecorator(), 800, 519);
scene.getStylesheets().setAll(config().getTheme().getStylesheets()); scene.getStylesheets().setAll(config().getTheme().getStylesheets());

View File

@@ -397,7 +397,7 @@ public final class FXUtils {
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Deprecated @Deprecated
public static void bindEnum(JFXComboBox<?> comboBox, Property<? extends Enum> property) { public static void bindEnum(JFXComboBox<?> comboBox, Property<? extends Enum<?>> property) {
unbindEnum(comboBox); unbindEnum(comboBox);
ChangeListener<Number> listener = (a, b, newValue) -> ChangeListener<Number> listener = (a, b, newValue) ->
((Property) property).setValue(property.getValue().getClass().getEnumConstants()[newValue.intValue()]); ((Property) property).setValue(property.getValue().getClass().getEnumConstants()[newValue.intValue()]);

View File

@@ -118,10 +118,10 @@ public final class LeftPaneController extends AdvancedListBox {
if (repository.getVersionCount() == 0) { if (repository.getVersionCount() == 0) {
File modpackFile = new File("modpack.zip").getAbsoluteFile(); File modpackFile = new File("modpack.zip").getAbsoluteFile();
if (modpackFile.exists()) { if (modpackFile.exists()) {
Task.ofResult(() -> CompressingUtils.findSuitableEncoding(modpackFile.toPath())) Task.supplyAsync(() -> CompressingUtils.findSuitableEncoding(modpackFile.toPath()))
.thenApply(encoding -> ModpackHelper.readModpackManifest(modpackFile.toPath(), encoding)) .thenApply(encoding -> ModpackHelper.readModpackManifest(modpackFile.toPath(), encoding))
.thenApply(modpack -> ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack) .thenApply(modpack -> ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
.with(Task.of(Schedulers.javafx(), this::checkAccount)).executor()) .withRun(Schedulers.javafx(), this::checkAccount).executor())
.thenAccept(Schedulers.javafx(), executor -> { .thenAccept(Schedulers.javafx(), executor -> {
Controllers.taskDialog(executor, i18n("modpack.installing")); Controllers.taskDialog(executor, i18n("modpack.installing"));
executor.start(); executor.start();

View File

@@ -33,7 +33,6 @@ import javafx.scene.control.Label;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle; import javafx.scene.shape.Rectangle;
import javafx.util.Duration; import javafx.util.Duration;
import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profile;

View File

@@ -61,7 +61,7 @@ public class AccountLoginPane extends StackPane {
String password = txtPassword.getText(); String password = txtPassword.getText();
progressBar.setVisible(true); progressBar.setVisible(true);
lblCreationWarning.setText(""); lblCreationWarning.setText("");
Task.ofResult(() -> oldAccount.logInWithPassword(password)) Task.supplyAsync(() -> oldAccount.logInWithPassword(password))
.whenComplete(Schedulers.javafx(), authInfo -> { .whenComplete(Schedulers.javafx(), authInfo -> {
success.accept(authInfo); success.accept(authInfo);
fireEvent(new DialogCloseEvent()); fireEvent(new DialogCloseEvent());

View File

@@ -197,7 +197,7 @@ public class AddAccountPane extends StackPane {
AccountFactory<?> factory = cboType.getSelectionModel().getSelectedItem(); AccountFactory<?> factory = cboType.getSelectionModel().getSelectedItem();
Object additionalData = getAuthAdditionalData(); Object additionalData = getAuthAdditionalData();
Task.ofResult(() -> factory.create(new Selector(), username, password, additionalData)) Task.supplyAsync(() -> factory.create(new Selector(), username, password, additionalData))
.whenComplete(Schedulers.javafx(), account -> { .whenComplete(Schedulers.javafx(), account -> {
int oldIndex = Accounts.getAccounts().indexOf(account); int oldIndex = Accounts.getAccounts().indexOf(account);
if (oldIndex == -1) { if (oldIndex == -1) {

View File

@@ -104,13 +104,13 @@ public class AddAuthlibInjectorServerPane extends StackPane implements DialogAwa
nextPane.showSpinner(); nextPane.showSpinner();
addServerPane.setDisable(true); addServerPane.setDisable(true);
Task.of(() -> { Task.runAsync(() -> {
serverBeingAdded = AuthlibInjectorServer.locateServer(url); serverBeingAdded = AuthlibInjectorServer.locateServer(url);
}).whenComplete(Schedulers.javafx(), (isDependentSucceeded, exception) -> { }).whenComplete(Schedulers.javafx(), exception -> {
addServerPane.setDisable(false); addServerPane.setDisable(false);
nextPane.hideSpinner(); nextPane.hideSpinner();
if (isDependentSucceeded) { if (exception == null) {
lblServerName.setText(serverBeingAdded.getName()); lblServerName.setText(serverBeingAdded.getName());
lblServerUrl.setText(serverBeingAdded.getUrl()); lblServerUrl.setText(serverBeingAdded.getUrl());

View File

@@ -17,7 +17,6 @@
*/ */
package org.jackhuang.hmcl.ui.construct; package org.jackhuang.hmcl.ui.construct;
import javafx.geometry.Pos;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;

View File

@@ -42,7 +42,7 @@ 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 ReadOnlyIntegerWrapper finishedTasks = new ReadOnlyIntegerWrapper();
private final ReadOnlyIntegerWrapper totTasks = new ReadOnlyIntegerWrapper(); private final ReadOnlyIntegerWrapper totTasks = new ReadOnlyIntegerWrapper();
@@ -72,12 +72,12 @@ public final class TaskListPane extends StackPane {
} }
@Override @Override
public void onReady(Task task) { public void onReady(Task<?> task) {
Platform.runLater(() -> totTasks.set(totTasks.getValue() + 1)); Platform.runLater(() -> totTasks.set(totTasks.getValue() + 1));
} }
@Override @Override
public void onRunning(Task task) { public void onRunning(Task<?> task) {
if (!task.getSignificance().shouldShow()) if (!task.getSignificance().shouldShow())
return; return;
@@ -113,7 +113,7 @@ public final class TaskListPane extends StackPane {
} }
@Override @Override
public void onFinished(Task task) { public void onFinished(Task<?> task) {
ProgressListNode node = nodes.remove(task); ProgressListNode node = nodes.remove(task);
if (node == null) if (node == null)
return; return;
@@ -125,7 +125,7 @@ public final class TaskListPane extends StackPane {
} }
@Override @Override
public void onFailed(Task task, Throwable throwable) { public void onFailed(Task<?> task, Throwable throwable) {
ProgressListNode node = nodes.remove(task); ProgressListNode node = nodes.remove(task);
if (node == null) if (node == null)
return; return;
@@ -142,7 +142,7 @@ public final class TaskListPane extends StackPane {
private final Label title = new Label(); private final Label title = new Label();
private final Label state = new Label(); private final Label state = new Label();
public ProgressListNode(Task task) { public ProgressListNode(Task<?> task) {
bar.progressProperty().bind(task.progressProperty()); bar.progressProperty().bind(task.progressProperty());
title.setText(task.getName()); title.setText(task.getName());
state.textProperty().bind(task.messageProperty()); state.textProperty().bind(task.messageProperty());

View File

@@ -21,7 +21,6 @@ import com.jfoenix.controls.JFXButton;
import com.jfoenix.svg.SVGGlyph; import com.jfoenix.svg.SVGGlyph;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds; import javafx.geometry.Bounds;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Pos; import javafx.geometry.Pos;
@@ -56,7 +55,6 @@ public class DecoratorSkin extends SkinBase<Decorator> {
private double xOffset, yOffset, newX, newY, initX, initY; private double xOffset, yOffset, newX, newY, initX, initY;
private boolean allowMove, isDragging; private boolean allowMove, isDragging;
private BoundingBox originalBox, maximizedBox;
/** /**
* Constructor for all SkinBase instances. * Constructor for all SkinBase instances.

View File

@@ -29,7 +29,6 @@ import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.DownloadException; import org.jackhuang.hmcl.task.DownloadException;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType; import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
import org.jackhuang.hmcl.ui.wizard.WizardController; import org.jackhuang.hmcl.ui.wizard.WizardController;
@@ -96,7 +95,7 @@ public final class InstallerWizardProvider implements WizardProvider {
settings.put("success_message", i18n("install.success")); settings.put("success_message", i18n("install.success"));
settings.put("failure_callback", (FailureCallback) (settings1, exception, next) -> alertFailureMessage(exception, next)); settings.put("failure_callback", (FailureCallback) (settings1, exception, next) -> alertFailureMessage(exception, next));
TaskResult<Version> ret = Task.ofResult(() -> version); Task<Version> ret = Task.supplyAsync(() -> version);
if (settings.containsKey("forge")) if (settings.containsKey("forge"))
ret = ret.thenCompose(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("forge"))); ret = ret.thenCompose(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("forge")));
@@ -107,7 +106,7 @@ public final class InstallerWizardProvider implements WizardProvider {
if (settings.containsKey("optifine")) if (settings.containsKey("optifine"))
ret = ret.thenCompose(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("optifine"))); ret = ret.thenCompose(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("optifine")));
return ret.then(profile.getRepository().refreshVersionsAsync()); return ret.thenCompose(profile.getRepository().refreshVersionsAsync());
} }
@Override @Override

View File

@@ -71,7 +71,7 @@ public class ModpackInstallWizardProvider implements WizardProvider {
settings.put(PROFILE, profile); settings.put(PROFILE, profile);
} }
private Task finishModpackInstallingAsync(Map<String, Object> settings) { private Task<Void> finishModpackInstallingAsync(Map<String, Object> settings) {
if (!settings.containsKey(ModpackPage.MODPACK_FILE)) if (!settings.containsKey(ModpackPage.MODPACK_FILE))
return null; return null;
@@ -93,7 +93,7 @@ public class ModpackInstallWizardProvider implements WizardProvider {
return null; return null;
} else { } else {
return ModpackHelper.getInstallTask(profile, selected, name, modpack) return ModpackHelper.getInstallTask(profile, selected, name, modpack)
.then(Task.of(Schedulers.javafx(), () -> profile.setSelectedVersion(name))); .thenRun(Schedulers.javafx(), () -> profile.setSelectedVersion(name));
} }
} }

View File

@@ -109,7 +109,7 @@ public final class ModpackPage extends StackPane implements WizardPage {
} }
spinnerPane.showSpinner(); spinnerPane.showSpinner();
Task.ofResult(() -> CompressingUtils.findSuitableEncoding(selectedFile.toPath())) Task.supplyAsync(() -> CompressingUtils.findSuitableEncoding(selectedFile.toPath()))
.thenApply(encoding -> manifest = ModpackHelper.readModpackManifest(selectedFile.toPath(), encoding)) .thenApply(encoding -> manifest = ModpackHelper.readModpackManifest(selectedFile.toPath(), encoding))
.whenComplete(Schedulers.javafx(), manifest -> { .whenComplete(Schedulers.javafx(), manifest -> {
spinnerPane.hideSpinner(); spinnerPane.hideSpinner();

View File

@@ -43,7 +43,7 @@ public final class VanillaInstallWizardProvider implements WizardProvider {
settings.put(PROFILE, profile); settings.put(PROFILE, profile);
} }
private Task finishVersionDownloadingAsync(Map<String, Object> settings) { private Task<Void> finishVersionDownloadingAsync(Map<String, Object> settings) {
GameBuilder builder = profile.getDependency().gameBuilder(); GameBuilder builder = profile.getDependency().gameBuilder();
String name = (String) settings.get("name"); String name = (String) settings.get("name");
@@ -59,8 +59,8 @@ public final class VanillaInstallWizardProvider implements WizardProvider {
if (settings.containsKey("optifine")) if (settings.containsKey("optifine"))
builder.version((RemoteVersion) settings.get("optifine")); builder.version((RemoteVersion) settings.get("optifine"));
return builder.buildAsync().whenComplete((a, b) -> profile.getRepository().refreshVersions()) return builder.buildAsync().whenComplete(any -> profile.getRepository().refreshVersions())
.then(Task.of(Schedulers.javafx(), () -> profile.setSelectedVersion(name))); .thenRun(Schedulers.javafx(), () -> profile.setSelectedVersion(name));
} }
@Override @Override

View File

@@ -128,8 +128,8 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
@Override @Override
public void refresh() { public void refresh() {
transitionHandler.setContent(spinner, ContainerAnimations.FADE.getAnimationProducer()); transitionHandler.setContent(spinner, ContainerAnimations.FADE.getAnimationProducer());
executor = versionList.refreshAsync(gameVersion, downloadProvider).whenComplete((isDependentSucceeded, exception) -> { executor = versionList.refreshAsync(gameVersion, downloadProvider).whenComplete(exception -> {
if (isDependentSucceeded) { if (exception == null) {
List<VersionsPageItem> items = loadVersions(); List<VersionsPageItem> items = loadVersions();
Platform.runLater(() -> { Platform.runLater(() -> {

View File

@@ -60,8 +60,8 @@ public final class ExportWizardProvider implements WizardProvider {
List<File> launcherJar = Launcher.getCurrentJarFiles(); List<File> launcherJar = Launcher.getCurrentJarFiles();
boolean includeLauncher = (Boolean) settings.get(ModpackInfoPage.MODPACK_INCLUDE_LAUNCHER) && launcherJar != null; boolean includeLauncher = (Boolean) settings.get(ModpackInfoPage.MODPACK_INCLUDE_LAUNCHER) && launcherJar != null;
return new Task() { return new Task<Void>() {
Task dependency = null; Task<?> dependency = null;
@Override @Override
public void execute() throws Exception { public void execute() throws Exception {
@@ -80,7 +80,7 @@ public final class ExportWizardProvider implements WizardProvider {
), tempModpack); ), tempModpack);
if (includeLauncher) { if (includeLauncher) {
dependency = dependency.then(Task.of(() -> { dependency = dependency.thenRun(() -> {
try (Zipper zip = new Zipper(modpackFile.toPath())) { try (Zipper zip = new Zipper(modpackFile.toPath())) {
Config exported = new Config(); Config exported = new Config();
@@ -109,12 +109,12 @@ public final class ExportWizardProvider implements WizardProvider {
for (File jar : launcherJar) for (File jar : launcherJar)
zip.putFile(jar, jar.getName()); zip.putFile(jar, jar.getName());
} }
})); });
} }
} }
@Override @Override
public Collection<? extends Task> getDependencies() { public Collection<Task<?>> getDependencies() {
return Collections.singleton(dependency); return Collections.singleton(dependency);
} }
}; };

View File

@@ -26,11 +26,9 @@ import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Profiles; import org.jackhuang.hmcl.setting.Profiles;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.FileItem; import org.jackhuang.hmcl.ui.construct.FileItem;
import org.jackhuang.hmcl.ui.construct.PageCloseEvent; import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
@@ -38,7 +36,6 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.StringUtils;
import java.io.File; import java.io.File;
import java.nio.file.Paths;
import java.util.Optional; import java.util.Optional;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.i18n.I18n.i18n;

View File

@@ -80,9 +80,9 @@ public class InstallerListPage extends ListPageBase<InstallerItem> {
LinkedList<Library> newList = new LinkedList<>(version.getLibraries()); LinkedList<Library> newList = new LinkedList<>(version.getLibraries());
newList.remove(library); newList.remove(library);
new MaintainTask(version.setLibraries(newList)) new MaintainTask(version.setLibraries(newList))
.then(maintainedVersion -> new VersionJsonSaveTask(profile.getRepository(), maintainedVersion)) .thenCompose(maintainedVersion -> new VersionJsonSaveTask(profile.getRepository(), maintainedVersion))
.with(profile.getRepository().refreshVersionsAsync()) .withCompose(profile.getRepository().refreshVersionsAsync())
.with(Task.of(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId))) .withRun(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId))
.start(); .start();
}; };

View File

@@ -86,15 +86,15 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
private void loadMods(ModManager modManager) { private void loadMods(ModManager modManager) {
this.modManager = modManager; this.modManager = modManager;
Task.ofResult(() -> { Task.supplyAsync(() -> {
synchronized (ModListPage.this) { synchronized (ModListPage.this) {
runInFX(() -> loadingProperty().set(true)); runInFX(() -> loadingProperty().set(true));
modManager.refreshMods(); modManager.refreshMods();
return new LinkedList<>(modManager.getMods()); return new LinkedList<>(modManager.getMods());
} }
}).whenComplete(Schedulers.javafx(), (list, isDependentSucceeded, exception) -> { }).whenComplete(Schedulers.javafx(), (list, exception) -> {
loadingProperty().set(false); loadingProperty().set(false);
if (isDependentSucceeded) if (exception == null)
FXUtils.onWeakChangeAndOperate(parentTab.getSelectionModel().selectedItemProperty(), newValue -> { FXUtils.onWeakChangeAndOperate(parentTab.getSelectionModel().selectedItemProperty(), newValue -> {
if (newValue != null && newValue.getUserData() == ModListPage.this) if (newValue != null && newValue.getUserData() == ModListPage.this)
itemsProperty().setAll(list.stream().map(ModListPageSkin.ModInfoObject::new).collect(Collectors.toList())); itemsProperty().setAll(list.stream().map(ModListPageSkin.ModInfoObject::new).collect(Collectors.toList()));
@@ -112,7 +112,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
List<String> succeeded = new LinkedList<>(); List<String> succeeded = new LinkedList<>();
List<String> failed = new LinkedList<>(); List<String> failed = new LinkedList<>();
if (res == null) return; if (res == null) return;
Task.of(() -> { Task.runAsync(() -> {
for (File file : res) { for (File file : res) {
try { try {
modManager.addMod(file); modManager.addMod(file);
@@ -124,7 +124,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
// Actually addMod will not throw exceptions because FileChooser has already filtered files. // Actually addMod will not throw exceptions because FileChooser has already filtered files.
} }
} }
}).with(Task.of(Schedulers.javafx(), () -> { }).withRun(Schedulers.javafx(), () -> {
List<String> prompt = new LinkedList<>(); List<String> prompt = new LinkedList<>();
if (!succeeded.isEmpty()) if (!succeeded.isEmpty())
prompt.add(i18n("mods.add.success", String.join(", ", succeeded))); prompt.add(i18n("mods.add.success", String.join(", ", succeeded)));
@@ -132,7 +132,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
prompt.add(i18n("mods.add.failed", String.join(", ", failed))); prompt.add(i18n("mods.add.failed", String.join(", ", failed)));
Controllers.dialog(String.join("\n", prompt), i18n("mods.add")); Controllers.dialog(String.join("\n", prompt), i18n("mods.add"));
loadMods(modManager); loadMods(modManager);
})).start(); }).start();
} }
public void setParentTab(JFXTabPane parentTab) { public void setParentTab(JFXTabPane parentTab) {

View File

@@ -115,7 +115,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
FXUtils.smoothScrolling(scroll); FXUtils.smoothScrolling(scroll);
Task.ofResult(JavaVersion::getJavas).thenAccept(Schedulers.javafx(), list -> { Task.supplyAsync(JavaVersion::getJavas).thenAccept(Schedulers.javafx(), list -> {
javaItem.loadChildren(list.stream() javaItem.loadChildren(list.stream()
.map(javaVersion -> javaItem.createChildren(javaVersion.getVersion() + i18n("settings.game.java_directory.bit", .map(javaVersion -> javaItem.createChildren(javaVersion.getVersion() + i18n("settings.game.java_directory.bit",
javaVersion.getPlatform().getBit()), javaVersion.getBinary().toString(), javaVersion)) javaVersion.getPlatform().getBit()), javaVersion.getBinary().toString(), javaVersion))
@@ -270,7 +270,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
VersionSetting versionSetting = lastVersionSetting; VersionSetting versionSetting = lastVersionSetting;
if (versionSetting == null) if (versionSetting == null)
return; return;
Task.ofResult(versionSetting::getJavaVersion) Task.supplyAsync(versionSetting::getJavaVersion)
.thenAccept(Schedulers.javafx(), javaVersion -> javaItem.setSubtitle(Optional.ofNullable(javaVersion) .thenAccept(Schedulers.javafx(), javaVersion -> javaItem.setSubtitle(Optional.ofNullable(javaVersion)
.map(JavaVersion::getBinary).map(Path::toString).orElse("Invalid Java Path"))) .map(JavaVersion::getBinary).map(Path::toString).orElse("Invalid Java Path")))
.start(); .start();

View File

@@ -19,7 +19,6 @@ package org.jackhuang.hmcl.ui.versions;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask; import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
import org.jackhuang.hmcl.download.game.GameAssetIndexDownloadTask;
import org.jackhuang.hmcl.game.GameRepository; import org.jackhuang.hmcl.game.GameRepository;
import org.jackhuang.hmcl.game.LauncherHelper; import org.jackhuang.hmcl.game.LauncherHelper;
import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.game.Version;

View File

@@ -74,6 +74,6 @@ public class WorldExportPage extends WizardSinglePage {
@Override @Override
protected Object finish() { protected Object finish() {
return Task.of(i18n("world.export.wizard", worldName.get()), () -> world.export(Paths.get(path.get()), worldName.get())); return Task.runAsync(i18n("world.export.wizard", worldName.get()), () -> world.export(Paths.get(path.get()), worldName.get()));
} }
} }

View File

@@ -82,12 +82,12 @@ public class WorldListPage extends ListPageBase<WorldListItem> {
setLoading(true); setLoading(true);
Task Task
.of(() -> gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse(null)) .runAsync(() -> gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse(null))
.thenSupply(() -> World.getWorlds(savesDir).parallel().collect(Collectors.toList())) .thenSupply(() -> World.getWorlds(savesDir).parallel().collect(Collectors.toList()))
.whenComplete(Schedulers.javafx(), (result, isDependentSucceeded, exception) -> { .whenComplete(Schedulers.javafx(), (result, exception) -> {
worlds = result; worlds = result;
setLoading(false); setLoading(false);
if (isDependentSucceeded) if (exception == null)
itemsProperty().setAll(result.stream() itemsProperty().setAll(result.stream()
.filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion)) .filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
.map(WorldListItem::new).collect(Collectors.toList())); .map(WorldListItem::new).collect(Collectors.toList()));
@@ -107,10 +107,10 @@ public class WorldListPage extends ListPageBase<WorldListItem> {
private void installWorld(File zipFile) { private void installWorld(File zipFile) {
// Only accept one world file because user is required to confirm the new world name // Only accept one world file because user is required to confirm the new world name
// Or too many input dialogs are popped. // Or too many input dialogs are popped.
Task.ofResult(() -> new World(zipFile.toPath())) Task.supplyAsync(() -> new World(zipFile.toPath()))
.whenComplete(Schedulers.javafx(), world -> { .whenComplete(Schedulers.javafx(), world -> {
Controllers.inputDialog(i18n("world.name.enter"), (name, resolve, reject) -> { Controllers.inputDialog(i18n("world.name.enter"), (name, resolve, reject) -> {
Task.of(() -> world.install(savesDir, name)) Task.runAsync(() -> world.install(savesDir, name))
.whenComplete(Schedulers.javafx(), () -> { .whenComplete(Schedulers.javafx(), () -> {
itemsProperty().add(new WorldListItem(new World(savesDir.resolve(name)))); itemsProperty().add(new WorldListItem(new World(savesDir.resolve(name))));
resolve.run(); resolve.run();

View File

@@ -32,8 +32,8 @@ public interface AbstractWizardDisplayer extends WizardDisplayer {
Queue<Object> getCancelQueue(); Queue<Object> getCancelQueue();
@Override @Override
default void handleTask(Map<String, Object> settings, Task task) { default void handleTask(Map<String, Object> settings, Task<?> task) {
TaskExecutor executor = task.with(Task.of(Schedulers.javafx(), this::navigateToSuccess)).executor(); TaskExecutor executor = task.withRun(Schedulers.javafx(), this::navigateToSuccess).executor();
TaskListPane pane = new TaskListPane(); TaskListPane pane = new TaskListPane();
pane.setExecutor(executor); pane.setExecutor(executor);
navigateTo(pane, Navigation.NavigationDirection.FINISH); navigateTo(pane, Navigation.NavigationDirection.FINISH);

View File

@@ -23,7 +23,6 @@ import javafx.fxml.FXML;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.animation.TransitionHandler; import org.jackhuang.hmcl.ui.animation.TransitionHandler;
import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.StringUtils;

View File

@@ -35,7 +35,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplayer { public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplayer {
@Override @Override
default void handleTask(Map<String, Object> settings, Task task) { default void handleTask(Map<String, Object> settings, Task<?> task) {
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(it -> { TaskExecutorDialogPane pane = new TaskExecutorDialogPane(it -> {
it.fireEvent(new DialogCloseEvent()); it.fireEvent(new DialogCloseEvent());
onEnd(); onEnd();
@@ -70,11 +70,11 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
else if (!settings.containsKey("forbid_success_message")) else if (!settings.containsKey("forbid_success_message"))
Controllers.dialog(i18n("message.success"), null, MessageType.FINE, () -> onEnd()); Controllers.dialog(i18n("message.success"), null, MessageType.FINE, () -> onEnd());
} else { } else {
if (executor.getLastException() == null) if (executor.getException() == null)
return; return;
String appendix = StringUtils.getStackTrace(executor.getLastException()); String appendix = StringUtils.getStackTrace(executor.getException());
if (settings.get("failure_callback") instanceof WizardProvider.FailureCallback) if (settings.get("failure_callback") instanceof WizardProvider.FailureCallback)
((WizardProvider.FailureCallback)settings.get("failure_callback")).onFail(settings, executor.getLastException(), () -> onEnd()); ((WizardProvider.FailureCallback)settings.get("failure_callback")).onFail(settings, executor.getException(), () -> onEnd());
else if (settings.get("failure_message") instanceof String) else if (settings.get("failure_message") instanceof String)
Controllers.dialog(appendix, (String) settings.get("failure_message"), MessageType.ERROR, () -> onEnd()); Controllers.dialog(appendix, (String) settings.get("failure_message"), MessageType.ERROR, () -> onEnd());
else if (!settings.containsKey("forbid_failure_message")) else if (!settings.containsKey("forbid_failure_message"))

View File

@@ -111,7 +111,7 @@ public class WizardController implements Navigation {
public void onFinish() { public void onFinish() {
Object result = provider.finish(settings); Object result = provider.finish(settings);
if (result instanceof Summary) displayer.navigateTo(((Summary) result).getComponent(), NavigationDirection.NEXT); if (result instanceof Summary) displayer.navigateTo(((Summary) result).getComponent(), NavigationDirection.NEXT);
else if (result instanceof Task) displayer.handleTask(settings, ((Task) result)); else if (result instanceof Task<?>) displayer.handleTask(settings, ((Task<?>) result));
else if (result != null) throw new IllegalStateException("Unrecognized wizard result: " + result); else if (result != null) throw new IllegalStateException("Unrecognized wizard result: " + result);
} }

View File

@@ -27,5 +27,5 @@ public interface WizardDisplayer {
void onEnd(); void onEnd();
void onCancel(); void onCancel();
void navigateTo(Node page, Navigation.NavigationDirection nav); void navigateTo(Node page, Navigation.NavigationDirection nav);
void handleTask(Map<String, Object> settings, Task task); void handleTask(Map<String, Object> settings, Task<?> task);
} }

View File

@@ -20,7 +20,7 @@ package org.jackhuang.hmcl.upgrade;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.scene.layout.Region;
import org.jackhuang.hmcl.Main; import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
@@ -101,7 +101,7 @@ public final class UpdateHandler {
return; return;
} }
Task task = new HMCLDownloadTask(version, downloaded); Task<?> task = new HMCLDownloadTask(version, downloaded);
TaskExecutor executor = task.executor(); TaskExecutor executor = task.executor();
Controllers.taskDialog(executor, i18n("message.downloading")); Controllers.taskDialog(executor, i18n("message.downloading"));
@@ -122,7 +122,7 @@ public final class UpdateHandler {
} }
} else { } else {
Throwable e = executor.getLastException(); Exception e = executor.getException();
LOG.log(Level.WARNING, "Failed to update to " + version, e); LOG.log(Level.WARNING, "Failed to update to " + version, e);
Platform.runLater(() -> Controllers.dialog(e.toString(), i18n("update.failed"), MessageType.ERROR)); Platform.runLater(() -> Controllers.dialog(e.toString(), i18n("update.failed"), MessageType.ERROR));
} }

View File

@@ -17,8 +17,6 @@
*/ */
package org.jackhuang.hmcl.download; package org.jackhuang.hmcl.download;
import org.jackhuang.hmcl.util.CacheRepository;
/** /**
* *
* @author huangyuhui * @author huangyuhui

View File

@@ -26,9 +26,7 @@ import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion; import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion;
import org.jackhuang.hmcl.game.DefaultGameRepository; import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.ParallelTask;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.function.ExceptionalFunction; import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import java.io.IOException; import java.io.IOException;
@@ -72,9 +70,9 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
} }
@Override @Override
public Task checkGameCompletionAsync(Version version) { public Task<?> checkGameCompletionAsync(Version version) {
return new ParallelTask( return Task.allOf(
Task.ofThen(() -> { Task.composeAsync(() -> {
if (!repository.getVersionJar(version).exists()) if (!repository.getVersionJar(version).exists())
return new GameDownloadTask(this, null, version); return new GameDownloadTask(this, null, version);
else else
@@ -86,12 +84,12 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
} }
@Override @Override
public Task checkLibraryCompletionAsync(Version version) { public Task<?> checkLibraryCompletionAsync(Version version) {
return new GameLibrariesTask(this, version); return new GameLibrariesTask(this, version);
} }
@Override @Override
public TaskResult<Version> installLibraryAsync(String gameVersion, Version version, String libraryId, String libraryVersion) { public Task<Version> installLibraryAsync(String gameVersion, Version version, String libraryId, String libraryVersion) {
VersionList<?> versionList = getVersionList(libraryId); VersionList<?> versionList = getVersionList(libraryId);
return versionList.loadAsync(gameVersion, getDownloadProvider()) return versionList.loadAsync(gameVersion, getDownloadProvider())
.thenCompose(() -> installLibraryAsync(version, versionList.getVersion(gameVersion, libraryVersion) .thenCompose(() -> installLibraryAsync(version, versionList.getVersion(gameVersion, libraryVersion)
@@ -99,8 +97,8 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
} }
@Override @Override
public TaskResult<Version> installLibraryAsync(Version oldVersion, RemoteVersion libraryVersion) { public Task<Version> installLibraryAsync(Version oldVersion, RemoteVersion libraryVersion) {
TaskResult<Version> task; Task<Version> task;
if (libraryVersion instanceof ForgeRemoteVersion) if (libraryVersion instanceof ForgeRemoteVersion)
task = new ForgeInstallTask(this, oldVersion, (ForgeRemoteVersion) libraryVersion); task = new ForgeInstallTask(this, oldVersion, (ForgeRemoteVersion) libraryVersion);
else if (libraryVersion instanceof LiteLoaderRemoteVersion) else if (libraryVersion instanceof LiteLoaderRemoteVersion)
@@ -115,7 +113,8 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
.thenCompose(newVersion -> new VersionJsonSaveTask(repository, newVersion)); .thenCompose(newVersion -> new VersionJsonSaveTask(repository, newVersion));
} }
public ExceptionalFunction<Version, TaskResult<Version>, ?> installLibraryAsync(RemoteVersion libraryVersion) {
public ExceptionalFunction<Version, Task<Version>, ?> installLibraryAsync(RemoteVersion libraryVersion) {
return version -> installLibraryAsync(version, libraryVersion); return version -> installLibraryAsync(version, libraryVersion);
} }

View File

@@ -19,9 +19,7 @@ package org.jackhuang.hmcl.download;
import org.jackhuang.hmcl.download.game.*; import org.jackhuang.hmcl.download.game.*;
import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.ParallelTask;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.function.ExceptionalFunction; import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.gson.JsonUtils;
@@ -48,16 +46,16 @@ public class DefaultGameBuilder extends GameBuilder {
} }
@Override @Override
public Task buildAsync() { public Task<?> buildAsync() {
return new VersionJsonDownloadTask(gameVersion, dependencyManager).thenCompose(rawJson -> { return new VersionJsonDownloadTask(gameVersion, dependencyManager).thenCompose(rawJson -> {
Version original = JsonUtils.GSON.fromJson(rawJson, Version.class); Version original = JsonUtils.GSON.fromJson(rawJson, Version.class);
Version version = original.setId(name).setJar(null); Version version = original.setId(name).setJar(null);
Task vanillaTask = downloadGameAsync(gameVersion, version).then(new ParallelTask( Task<?> vanillaTask = downloadGameAsync(gameVersion, version).thenCompose(Task.allOf(
new GameAssetDownloadTask(dependencyManager, version, GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY), new GameAssetDownloadTask(dependencyManager, version, GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY),
new GameLibrariesTask(dependencyManager, version) // Game libraries will be downloaded for multiple times partly, this time is for vanilla libraries. new GameLibrariesTask(dependencyManager, version) // Game libraries will be downloaded for multiple times partly, this time is for vanilla libraries.
).with(new VersionJsonSaveTask(dependencyManager.getGameRepository(), version))); // using [with] because download failure here are tolerant. ).withCompose(new VersionJsonSaveTask(dependencyManager.getGameRepository(), version))); // using [with] because download failure here are tolerant.
TaskResult<Version> libraryTask = vanillaTask.thenSupply(() -> version); Task<Version> libraryTask = vanillaTask.thenSupply(() -> version);
if (toolVersions.containsKey("forge")) if (toolVersions.containsKey("forge"))
libraryTask = libraryTask.thenCompose(libraryTaskHelper(gameVersion, "forge")); libraryTask = libraryTask.thenCompose(libraryTaskHelper(gameVersion, "forge"));
@@ -70,17 +68,17 @@ public class DefaultGameBuilder extends GameBuilder {
libraryTask = libraryTask.thenCompose(dependencyManager.installLibraryAsync(remoteVersion)); libraryTask = libraryTask.thenCompose(dependencyManager.installLibraryAsync(remoteVersion));
return libraryTask; return libraryTask;
}).whenComplete((isDependentSucceeded, exception) -> { }).whenComplete(exception -> {
if (!isDependentSucceeded) if (exception != null)
dependencyManager.getGameRepository().removeVersionFromDisk(name); dependencyManager.getGameRepository().removeVersionFromDisk(name);
}); });
} }
private ExceptionalFunction<Version, TaskResult<Version>, ?> libraryTaskHelper(String gameVersion, String libraryId) { private ExceptionalFunction<Version, Task<Version>, ?> libraryTaskHelper(String gameVersion, String libraryId) {
return version -> dependencyManager.installLibraryAsync(gameVersion, version, libraryId, toolVersions.get(libraryId)); return version -> dependencyManager.installLibraryAsync(gameVersion, version, libraryId, toolVersions.get(libraryId));
} }
protected Task downloadGameAsync(String gameVersion, Version version) { protected Task<Void> downloadGameAsync(String gameVersion, Version version) {
return new GameDownloadTask(dependencyManager, gameVersion, version); return new GameDownloadTask(dependencyManager, gameVersion, version);
} }

View File

@@ -46,7 +46,7 @@ public interface DependencyManager {
* *
* @return the task to check game completion. * @return the task to check game completion.
*/ */
Task checkGameCompletionAsync(Version version); Task<?> checkGameCompletionAsync(Version version);
/** /**
* Check if the game is complete. * Check if the game is complete.
@@ -54,7 +54,7 @@ public interface DependencyManager {
* *
* @return the task to check game completion. * @return the task to check game completion.
*/ */
Task checkLibraryCompletionAsync(Version version); Task<?> checkLibraryCompletionAsync(Version version);
/** /**
* The builder to build a brand new game then libraries such as Forge, LiteLoader and OptiFine. * The builder to build a brand new game then libraries such as Forge, LiteLoader and OptiFine.
@@ -71,7 +71,7 @@ public interface DependencyManager {
* @param libraryVersion the version of being installed library. * @param libraryVersion the version of being installed library.
* @return the task to install the specific library. * @return the task to install the specific library.
*/ */
Task installLibraryAsync(String gameVersion, Version version, String libraryId, String libraryVersion); Task<?> installLibraryAsync(String gameVersion, Version version, String libraryId, String libraryVersion);
/** /**
* Install a library to a version. * Install a library to a version.
@@ -81,7 +81,7 @@ public interface DependencyManager {
* @param libraryVersion the remote version of being installed library. * @param libraryVersion the remote version of being installed library.
* @return the task to install the specific library. * @return the task to install the specific library.
*/ */
Task installLibraryAsync(Version version, RemoteVersion libraryVersion); Task<?> installLibraryAsync(Version version, RemoteVersion libraryVersion);
/** /**
* Get registered version list. * Get registered version list.

View File

@@ -72,5 +72,5 @@ public abstract class GameBuilder {
/** /**
* @return the task that can build thw whole Minecraft environment * @return the task that can build thw whole Minecraft environment
*/ */
public abstract Task buildAsync(); public abstract Task<?> buildAsync();
} }

View File

@@ -18,11 +18,10 @@
package org.jackhuang.hmcl.download; package org.jackhuang.hmcl.download;
import org.jackhuang.hmcl.game.*; import org.jackhuang.hmcl.game.*;
import org.jackhuang.hmcl.task.TaskResult; import org.jackhuang.hmcl.task.Task;
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*; import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;
public class MaintainTask extends TaskResult<Version> { public class MaintainTask extends Task<Version> {
private final Version version; private final Version version;

View File

@@ -18,7 +18,6 @@
package org.jackhuang.hmcl.download; package org.jackhuang.hmcl.download;
import org.jackhuang.hmcl.download.forge.ForgeBMCLVersionList; import org.jackhuang.hmcl.download.forge.ForgeBMCLVersionList;
import org.jackhuang.hmcl.download.forge.ForgeVersionList;
import org.jackhuang.hmcl.download.game.GameVersionList; import org.jackhuang.hmcl.download.game.GameVersionList;
import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList; import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList;
import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList; import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList;

View File

@@ -62,19 +62,19 @@ public abstract class VersionList<T extends RemoteVersion> {
* @param downloadProvider DownloadProvider * @param downloadProvider DownloadProvider
* @return the task to reload the remote version list. * @return the task to reload the remote version list.
*/ */
public abstract Task refreshAsync(DownloadProvider downloadProvider); public abstract Task<?> refreshAsync(DownloadProvider downloadProvider);
/** /**
* @param gameVersion the remote version depends on * @param gameVersion the remote version depends on
* @param downloadProvider DownloadProvider * @param downloadProvider DownloadProvider
* @return the task to reload the remote version list. * @return the task to reload the remote version list.
*/ */
public Task refreshAsync(String gameVersion, DownloadProvider downloadProvider) { public Task<?> refreshAsync(String gameVersion, DownloadProvider downloadProvider) {
return refreshAsync(downloadProvider); return refreshAsync(downloadProvider);
} }
public Task loadAsync(DownloadProvider downloadProvider) { public Task<?> loadAsync(DownloadProvider downloadProvider) {
return Task.ofThen(() -> { return Task.composeAsync(() -> {
lock.readLock().lock(); lock.readLock().lock();
boolean loaded; boolean loaded;
@@ -87,8 +87,8 @@ public abstract class VersionList<T extends RemoteVersion> {
}); });
} }
public Task loadAsync(String gameVersion, DownloadProvider downloadProvider) { public Task<?> loadAsync(String gameVersion, DownloadProvider downloadProvider) {
return Task.ofThen(() -> { return Task.composeAsync(() -> {
lock.readLock().lock(); lock.readLock().lock();
boolean loaded; boolean loaded;

View File

@@ -46,21 +46,21 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
} }
@Override @Override
public Task loadAsync(DownloadProvider downloadProvider) { public Task<?> loadAsync(DownloadProvider downloadProvider) {
throw new UnsupportedOperationException("ForgeBMCLVersionList does not support loading the entire Forge remote version list."); throw new UnsupportedOperationException("ForgeBMCLVersionList does not support loading the entire Forge remote version list.");
} }
@Override @Override
public Task refreshAsync(DownloadProvider downloadProvider) { public Task<?> refreshAsync(DownloadProvider downloadProvider) {
throw new UnsupportedOperationException("ForgeBMCLVersionList does not support loading the entire Forge remote version list."); throw new UnsupportedOperationException("ForgeBMCLVersionList does not support loading the entire Forge remote version list.");
} }
@Override @Override
public Task refreshAsync(String gameVersion, DownloadProvider downloadProvider) { public Task<?> refreshAsync(String gameVersion, DownloadProvider downloadProvider) {
final GetTask task = new GetTask(NetworkUtils.toURL("https://bmclapi2.bangbang93.com/forge/minecraft/" + gameVersion)); final GetTask task = new GetTask(NetworkUtils.toURL("https://bmclapi2.bangbang93.com/forge/minecraft/" + gameVersion));
return new Task() { return new Task<Void>() {
@Override @Override
public Collection<Task> getDependents() { public Collection<Task<?>> getDependents() {
return Collections.singleton(task); return Collections.singleton(task);
} }

View File

@@ -23,7 +23,6 @@ import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.FileUtils;
@@ -43,14 +42,14 @@ import java.util.Optional;
* *
* @author huangyuhui * @author huangyuhui
*/ */
public final class ForgeInstallTask extends TaskResult<Version> { public final class ForgeInstallTask extends Task<Version> {
private final DefaultDependencyManager dependencyManager; private final DefaultDependencyManager dependencyManager;
private final Version version; private final Version version;
private Path installer; private Path installer;
private final ForgeRemoteVersion remote; private final ForgeRemoteVersion remote;
private Task dependent; private Task<Void> dependent;
private TaskResult<Version> dependency; private Task<Version> dependency;
public ForgeInstallTask(DefaultDependencyManager dependencyManager, Version version, ForgeRemoteVersion remoteVersion) { public ForgeInstallTask(DefaultDependencyManager dependencyManager, Version version, ForgeRemoteVersion remoteVersion) {
this.dependencyManager = dependencyManager; this.dependencyManager = dependencyManager;
@@ -83,12 +82,12 @@ public final class ForgeInstallTask extends TaskResult<Version> {
} }
@Override @Override
public Collection<Task> getDependents() { public Collection<Task<?>> getDependents() {
return Collections.singleton(dependent); return Collections.singleton(dependent);
} }
@Override @Override
public Collection<Task> getDependencies() { public Collection<Task<?>> getDependencies() {
return Collections.singleton(dependency); return Collections.singleton(dependency);
} }
@@ -110,7 +109,7 @@ public final class ForgeInstallTask extends TaskResult<Version> {
* @throws IOException if unable to read compressed content of installer file, or installer file is corrupted, or the installer is not the one we want. * @throws IOException if unable to read compressed content of installer file, or installer file is corrupted, or the installer is not the one we want.
* @throws VersionMismatchException if required game version of installer does not match the actual one. * @throws VersionMismatchException if required game version of installer does not match the actual one.
*/ */
public static TaskResult<Version> install(DefaultDependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException { public static Task<Version> install(DefaultDependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException {
Optional<String> gameVersion = GameVersion.minecraftVersion(dependencyManager.getGameRepository().getVersionJar(version)); Optional<String> gameVersion = GameVersion.minecraftVersion(dependencyManager.getGameRepository().getVersionJar(version));
if (!gameVersion.isPresent()) throw new IOException(); if (!gameVersion.isPresent()) throw new IOException();
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {

View File

@@ -22,7 +22,6 @@ import org.jackhuang.hmcl.download.game.GameLibrariesTask;
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask; import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
import org.jackhuang.hmcl.game.*; import org.jackhuang.hmcl.game.*;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.function.ExceptionalFunction; import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.gson.JsonUtils;
@@ -50,19 +49,19 @@ import static org.jackhuang.hmcl.util.DigestUtils.digest;
import static org.jackhuang.hmcl.util.Hex.encodeHex; import static org.jackhuang.hmcl.util.Hex.encodeHex;
import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.Logging.LOG;
public class ForgeNewInstallTask extends TaskResult<Version> { public class ForgeNewInstallTask extends Task<Version> {
private final DefaultDependencyManager dependencyManager; private final DefaultDependencyManager dependencyManager;
private final DefaultGameRepository gameRepository; private final DefaultGameRepository gameRepository;
private final Version version; private final Version version;
private final Path installer; private final Path installer;
private final List<Task> dependents = new LinkedList<>(); private final List<Task<?>> dependents = new LinkedList<>();
private final List<Task> dependencies = new LinkedList<>(); private final List<Task<?>> dependencies = new LinkedList<>();
private ForgeNewInstallProfile profile; private ForgeNewInstallProfile profile;
private Version forgeVersion; private Version forgeVersion;
public ForgeNewInstallTask(DefaultDependencyManager dependencyManager, Version version, Path installer) { ForgeNewInstallTask(DefaultDependencyManager dependencyManager, Version version, Path installer) {
this.dependencyManager = dependencyManager; this.dependencyManager = dependencyManager;
this.gameRepository = dependencyManager.getGameRepository(); this.gameRepository = dependencyManager.getGameRepository();
this.version = version; this.version = version;
@@ -83,12 +82,12 @@ public class ForgeNewInstallTask extends TaskResult<Version> {
} }
@Override @Override
public Collection<Task> getDependents() { public Collection<Task<?>> getDependents() {
return dependents; return dependents;
} }
@Override @Override
public List<Task> getDependencies() { public Collection<Task<?>> getDependencies() {
return dependencies; return dependencies;
} }

View File

@@ -20,7 +20,6 @@ package org.jackhuang.hmcl.download.forge;
import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.game.*; import org.jackhuang.hmcl.game.*;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.IOUtils; import org.jackhuang.hmcl.util.io.IOUtils;
@@ -31,14 +30,14 @@ import java.util.*;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
public class ForgeOldInstallTask extends TaskResult<Version> { public class ForgeOldInstallTask extends Task<Version> {
private final DefaultDependencyManager dependencyManager; private final DefaultDependencyManager dependencyManager;
private final Version version; private final Version version;
private final Path installer; private final Path installer;
private final List<Task> dependencies = new LinkedList<>(); private final List<Task<?>> dependencies = new LinkedList<>();
public ForgeOldInstallTask(DefaultDependencyManager dependencyManager, Version version, Path installer) { ForgeOldInstallTask(DefaultDependencyManager dependencyManager, Version version, Path installer) {
this.dependencyManager = dependencyManager; this.dependencyManager = dependencyManager;
this.version = version; this.version = version;
this.installer = installer; this.installer = installer;
@@ -47,7 +46,7 @@ public class ForgeOldInstallTask extends TaskResult<Version> {
} }
@Override @Override
public List<Task> getDependencies() { public List<Task<?>> getDependencies() {
return dependencies; return dependencies;
} }

View File

@@ -45,12 +45,12 @@ public final class ForgeVersionList extends VersionList<ForgeRemoteVersion> {
} }
@Override @Override
public Task refreshAsync(DownloadProvider downloadProvider) { public Task<?> refreshAsync(DownloadProvider downloadProvider) {
final GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL(FORGE_LIST))); final GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL(FORGE_LIST)));
return new Task() { return new Task<Void>() {
@Override @Override
public Collection<Task> getDependents() { public Collection<Task<?>> getDependents() {
return Collections.singleton(task); return Collections.singleton(task);
} }

View File

@@ -38,14 +38,14 @@ import java.util.List;
* *
* @author huangyuhui * @author huangyuhui
*/ */
public final class GameAssetDownloadTask extends Task { public final class GameAssetDownloadTask extends Task<Void> {
private final AbstractDependencyManager dependencyManager; private final AbstractDependencyManager dependencyManager;
private final Version version; private final Version version;
private final AssetIndexInfo assetIndexInfo; private final AssetIndexInfo assetIndexInfo;
private final File assetIndexFile; private final File assetIndexFile;
private final List<Task> dependents = new LinkedList<>(); private final List<Task<?>> dependents = new LinkedList<>();
private final List<Task> dependencies = new LinkedList<>(); private final List<Task<?>> dependencies = new LinkedList<>();
/** /**
* Constructor. * Constructor.
@@ -64,12 +64,12 @@ public final class GameAssetDownloadTask extends Task {
} }
@Override @Override
public Collection<Task> getDependents() { public Collection<Task<?>> getDependents() {
return dependents; return dependents;
} }
@Override @Override
public Collection<Task> getDependencies() { public Collection<Task<?>> getDependencies() {
return dependencies; return dependencies;
} }

View File

@@ -22,7 +22,6 @@ import org.jackhuang.hmcl.game.AssetIndexInfo;
import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.CacheRepository;
import org.jackhuang.hmcl.util.io.NetworkUtils; import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.io.File; import java.io.File;
@@ -34,11 +33,11 @@ import java.util.List;
* *
* @author huangyuhui * @author huangyuhui
*/ */
public final class GameAssetIndexDownloadTask extends Task { public final class GameAssetIndexDownloadTask extends Task<Void> {
private final AbstractDependencyManager dependencyManager; private final AbstractDependencyManager dependencyManager;
private final Version version; private final Version version;
private final List<Task> dependencies = new LinkedList<>(); private final List<Task<?>> dependencies = new LinkedList<>();
/** /**
* Constructor. * Constructor.
@@ -53,7 +52,7 @@ public final class GameAssetIndexDownloadTask extends Task {
} }
@Override @Override
public List<Task> getDependencies() { public List<Task<?>> getDependencies() {
return dependencies; return dependencies;
} }

View File

@@ -26,6 +26,7 @@ import org.jackhuang.hmcl.util.CacheRepository;
import org.jackhuang.hmcl.util.io.NetworkUtils; import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.io.File; import java.io.File;
import java.util.Collection;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@@ -33,11 +34,11 @@ import java.util.List;
* *
* @author huangyuhui * @author huangyuhui
*/ */
public final class GameDownloadTask extends Task { public final class GameDownloadTask extends Task<Void> {
private final DefaultDependencyManager dependencyManager; private final DefaultDependencyManager dependencyManager;
private final String gameVersion; private final String gameVersion;
private final Version version; private final Version version;
private final List<Task> dependencies = new LinkedList<>(); private final List<Task<?>> dependencies = new LinkedList<>();
public GameDownloadTask(DefaultDependencyManager dependencyManager, String gameVersion, Version version) { public GameDownloadTask(DefaultDependencyManager dependencyManager, String gameVersion, Version version) {
this.dependencyManager = dependencyManager; this.dependencyManager = dependencyManager;
@@ -48,7 +49,7 @@ public final class GameDownloadTask extends Task {
} }
@Override @Override
public List<Task> getDependencies() { public Collection<Task<?>> getDependencies() {
return dependencies; return dependencies;
} }

View File

@@ -32,12 +32,12 @@ import java.util.List;
* *
* @author huangyuhui * @author huangyuhui
*/ */
public final class GameLibrariesTask extends Task { public final class GameLibrariesTask extends Task<Void> {
private final AbstractDependencyManager dependencyManager; private final AbstractDependencyManager dependencyManager;
private final Version version; private final Version version;
private final List<Library> libraries; private final List<Library> libraries;
private final List<Task> dependencies = new LinkedList<>(); private final List<Task<?>> dependencies = new LinkedList<>();
/** /**
* Constructor. * Constructor.
@@ -64,7 +64,7 @@ public final class GameLibrariesTask extends Task {
} }
@Override @Override
public List<Task> getDependencies() { public List<Task<?>> getDependencies() {
return dependencies; return dependencies;
} }

View File

@@ -55,11 +55,11 @@ public final class GameVersionList extends VersionList<GameRemoteVersion> {
} }
@Override @Override
public Task refreshAsync(DownloadProvider downloadProvider) { public Task<?> refreshAsync(DownloadProvider downloadProvider) {
GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.getVersionListURL())); GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.getVersionListURL()));
return new Task() { return new Task<Void>() {
@Override @Override
public Collection<Task> getDependents() { public Collection<Task<?>> getDependents() {
return Collections.singleton(task); return Collections.singleton(task);
} }

View File

@@ -20,7 +20,7 @@ package org.jackhuang.hmcl.download.game;
import org.jackhuang.hmcl.game.CompatibilityRule; import org.jackhuang.hmcl.game.CompatibilityRule;
import org.jackhuang.hmcl.game.Library; import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.TaskResult; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.SimpleMultimap; import org.jackhuang.hmcl.util.SimpleMultimap;
import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.versioning.VersionNumber; import org.jackhuang.hmcl.util.versioning.VersionNumber;
@@ -31,7 +31,7 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class LibrariesUniqueTask extends TaskResult<Version> { public class LibrariesUniqueTask extends Task<Version> {
private final Version version; private final Version version;
public LibrariesUniqueTask(Version version) { public LibrariesUniqueTask(Version version) {

View File

@@ -45,7 +45,7 @@ import java.util.logging.Level;
import static org.jackhuang.hmcl.util.DigestUtils.digest; import static org.jackhuang.hmcl.util.DigestUtils.digest;
import static org.jackhuang.hmcl.util.Hex.encodeHex; import static org.jackhuang.hmcl.util.Hex.encodeHex;
public class LibraryDownloadTask extends Task { public class LibraryDownloadTask extends Task<Void> {
private FileDownloadTask task; private FileDownloadTask task;
protected final File jar; protected final File jar;
protected final DefaultCacheRepository cacheRepository; protected final DefaultCacheRepository cacheRepository;
@@ -74,7 +74,7 @@ public class LibraryDownloadTask extends Task {
} }
@Override @Override
public Collection<? extends Task> getDependents() { public Collection<Task<?>> getDependents() {
if (cached) return Collections.emptyList(); if (cached) return Collections.emptyList();
else return Collections.singleton(task); else return Collections.singleton(task);
} }
@@ -91,7 +91,7 @@ public class LibraryDownloadTask extends Task {
if (!isDependentsSucceeded()) { if (!isDependentsSucceeded()) {
// Since FileDownloadTask wraps the actual exception with DownloadException. // Since FileDownloadTask wraps the actual exception with DownloadException.
// We should extract it letting the error message clearer. // We should extract it letting the error message clearer.
Throwable t = task.getLastException(); Exception t = task.getException();
if (t instanceof DownloadException) if (t instanceof DownloadException)
throw new LibraryDownloadException(library, t.getCause()); throw new LibraryDownloadException(library, t.getCause());
else else

View File

@@ -22,7 +22,6 @@ import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.download.VersionList; import org.jackhuang.hmcl.download.VersionList;
import org.jackhuang.hmcl.task.GetTask; import org.jackhuang.hmcl.task.GetTask;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.io.NetworkUtils; import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.util.Collection; import java.util.Collection;
@@ -33,11 +32,11 @@ import java.util.List;
* *
* @author huangyuhui * @author huangyuhui
*/ */
public final class VersionJsonDownloadTask extends TaskResult<String> { public final class VersionJsonDownloadTask extends Task<String> {
private final String gameVersion; private final String gameVersion;
private final DefaultDependencyManager dependencyManager; private final DefaultDependencyManager dependencyManager;
private final List<Task> dependents = new LinkedList<>(); private final List<Task<?>> dependents = new LinkedList<>();
private final List<Task> dependencies = new LinkedList<>(); private final List<Task<?>> dependencies = new LinkedList<>();
private final VersionList<?> gameVersionList; private final VersionList<?> gameVersionList;
public VersionJsonDownloadTask(String gameVersion, DefaultDependencyManager dependencyManager) { public VersionJsonDownloadTask(String gameVersion, DefaultDependencyManager dependencyManager) {
@@ -52,12 +51,12 @@ public final class VersionJsonDownloadTask extends TaskResult<String> {
} }
@Override @Override
public Collection<Task> getDependencies() { public Collection<Task<?>> getDependencies() {
return dependencies; return dependencies;
} }
@Override @Override
public Collection<Task> getDependents() { public Collection<Task<?>> getDependents() {
return dependents; return dependents;
} }

View File

@@ -19,7 +19,7 @@ package org.jackhuang.hmcl.download.game;
import org.jackhuang.hmcl.game.DefaultGameRepository; import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.TaskResult; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.FileUtils;
@@ -30,7 +30,7 @@ import java.io.File;
* *
* @author huangyuhui * @author huangyuhui
*/ */
public final class VersionJsonSaveTask extends TaskResult<Version> { public final class VersionJsonSaveTask extends Task<Version> {
private final DefaultGameRepository repository; private final DefaultGameRepository repository;
private final Version version; private final Version version;

View File

@@ -33,7 +33,6 @@ import javax.xml.parsers.DocumentBuilderFactory;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.Optional;
/** /**
* *
@@ -52,11 +51,11 @@ public final class LiteLoaderBMCLVersionList extends VersionList<LiteLoaderRemot
} }
@Override @Override
public Task refreshAsync(DownloadProvider downloadProvider) { public Task<?> refreshAsync(DownloadProvider downloadProvider) {
GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL(LITELOADER_LIST))); GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL(LITELOADER_LIST)));
return new Task() { return new Task<Void>() {
@Override @Override
public Collection<Task> getDependents() { public Collection<Task<?>> getDependents() {
return Collections.singleton(task); return Collections.singleton(task);
} }

View File

@@ -23,7 +23,6 @@ import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.LibraryDownloadInfo; import org.jackhuang.hmcl.game.LibraryDownloadInfo;
import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.Lang;
import java.util.Collection; import java.util.Collection;
@@ -36,13 +35,13 @@ import java.util.List;
* *
* @author huangyuhui * @author huangyuhui
*/ */
public final class LiteLoaderInstallTask extends TaskResult<Version> { public final class LiteLoaderInstallTask extends Task<Version> {
private final DefaultDependencyManager dependencyManager; private final DefaultDependencyManager dependencyManager;
private final Version version; private final Version version;
private final LiteLoaderRemoteVersion remote; private final LiteLoaderRemoteVersion remote;
private final List<Task> dependents = new LinkedList<>(); private final List<Task<?>> dependents = new LinkedList<>();
private final List<Task> dependencies = new LinkedList<>(); private final List<Task<?>> dependencies = new LinkedList<>();
public LiteLoaderInstallTask(DefaultDependencyManager dependencyManager, Version version, LiteLoaderRemoteVersion remoteVersion) { public LiteLoaderInstallTask(DefaultDependencyManager dependencyManager, Version version, LiteLoaderRemoteVersion remoteVersion) {
this.dependencyManager = dependencyManager; this.dependencyManager = dependencyManager;
@@ -51,12 +50,12 @@ public final class LiteLoaderInstallTask extends TaskResult<Version> {
} }
@Override @Override
public Collection<Task> getDependents() { public Collection<Task<?>> getDependents() {
return dependents; return dependents;
} }
@Override @Override
public Collection<Task> getDependencies() { public Collection<Task<?>> getDependencies() {
return dependencies; return dependencies;
} }

View File

@@ -33,7 +33,6 @@ import javax.xml.parsers.DocumentBuilderFactory;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.Optional;
/** /**
* *
@@ -52,11 +51,11 @@ public final class LiteLoaderVersionList extends VersionList<LiteLoaderRemoteVer
} }
@Override @Override
public Task refreshAsync(DownloadProvider downloadProvider) { public Task<?> refreshAsync(DownloadProvider downloadProvider) {
GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL(LITELOADER_LIST))); GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL(LITELOADER_LIST)));
return new Task() { return new Task<Void>() {
@Override @Override
public Collection<Task> getDependents() { public Collection<Task<?>> getDependents() {
return Collections.singleton(task); return Collections.singleton(task);
} }

View File

@@ -46,11 +46,11 @@ public final class OptiFineBMCLVersionList extends VersionList<OptiFineRemoteVer
} }
@Override @Override
public Task refreshAsync(DownloadProvider downloadProvider) { public Task<?> refreshAsync(DownloadProvider downloadProvider) {
GetTask task = new GetTask(NetworkUtils.toURL("http://bmclapi2.bangbang93.com/optifine/versionlist")); GetTask task = new GetTask(NetworkUtils.toURL("http://bmclapi2.bangbang93.com/optifine/versionlist"));
return new Task() { return new Task<Void>() {
@Override @Override
public Collection<Task> getDependents() { public Collection<Task<?>> getDependents() {
return Collections.singleton(task); return Collections.singleton(task);
} }

View File

@@ -21,7 +21,6 @@ import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.VersionMismatchException; import org.jackhuang.hmcl.download.VersionMismatchException;
import org.jackhuang.hmcl.game.*; import org.jackhuang.hmcl.game.*;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.FileUtils;
@@ -44,14 +43,14 @@ import static org.jackhuang.hmcl.util.Lang.getOrDefault;
* *
* @author huangyuhui * @author huangyuhui
*/ */
public final class OptiFineInstallTask extends TaskResult<Version> { public final class OptiFineInstallTask extends Task<Version> {
private final DefaultDependencyManager dependencyManager; private final DefaultDependencyManager dependencyManager;
private final Version version; private final Version version;
private final OptiFineRemoteVersion remote; private final OptiFineRemoteVersion remote;
private final Path installer; private final Path installer;
private final List<Task> dependents = new LinkedList<>(); private final List<Task<?>> dependents = new LinkedList<>();
private final List<Task> dependencies = new LinkedList<>(); private final List<Task<?>> dependencies = new LinkedList<>();
public OptiFineInstallTask(DefaultDependencyManager dependencyManager, Version version, OptiFineRemoteVersion remoteVersion) { public OptiFineInstallTask(DefaultDependencyManager dependencyManager, Version version, OptiFineRemoteVersion remoteVersion) {
this(dependencyManager, version, remoteVersion, null); this(dependencyManager, version, remoteVersion, null);
@@ -65,12 +64,12 @@ public final class OptiFineInstallTask extends TaskResult<Version> {
} }
@Override @Override
public Collection<Task> getDependents() { public Collection<Task<?>> getDependents() {
return dependents; return dependents;
} }
@Override @Override
public Collection<Task> getDependencies() { public Collection<Task<?>> getDependencies() {
return dependencies; return dependencies;
} }
@@ -127,7 +126,7 @@ public final class OptiFineInstallTask extends TaskResult<Version> {
* @throws IOException if unable to read compressed content of installer file, or installer file is corrupted, or the installer is not the one we want. * @throws IOException if unable to read compressed content of installer file, or installer file is corrupted, or the installer is not the one we want.
* @throws VersionMismatchException if required game version of installer does not match the actual one. * @throws VersionMismatchException if required game version of installer does not match the actual one.
*/ */
public static TaskResult<Version> install(DefaultDependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException { public static Task<Version> install(DefaultDependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException {
File jar = dependencyManager.getGameRepository().getVersionJar(version); File jar = dependencyManager.getGameRepository().getVersionJar(version);
Optional<String> gameVersion = GameVersion.minecraftVersion(jar); Optional<String> gameVersion = GameVersion.minecraftVersion(jar);
if (!gameVersion.isPresent()) throw new IOException(); if (!gameVersion.isPresent()) throw new IOException();

View File

@@ -77,8 +77,8 @@ public interface GameRepository extends VersionProvider {
*/ */
void refreshVersions(); void refreshVersions();
default Task refreshVersionsAsync() { default Task<Void> refreshVersionsAsync() {
return Task.of(this::refreshVersions); return Task.runAsync(this::refreshVersions);
} }
/** /**

View File

@@ -36,7 +36,6 @@ import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileSystem; import java.nio.file.FileSystem;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;

View File

@@ -43,14 +43,14 @@ import java.util.stream.Collectors;
* *
* @author huangyuhui * @author huangyuhui
*/ */
public final class CurseCompletionTask extends Task { public final class CurseCompletionTask extends Task<Void> {
private final DefaultGameRepository repository; private final DefaultGameRepository repository;
private final ModManager modManager; private final ModManager modManager;
private final String version; private final String version;
private CurseManifest manifest = null; private CurseManifest manifest = null;
private final List<Task> dependents = new LinkedList<>(); private final List<Task<?>> dependents = new LinkedList<>();
private final List<Task> dependencies = new LinkedList<>(); private final List<Task<?>> dependencies = new LinkedList<>();
/** /**
* Constructor. * Constructor.
@@ -86,12 +86,12 @@ public final class CurseCompletionTask extends Task {
} }
@Override @Override
public Collection<Task> getDependencies() { public Collection<Task<?>> getDependencies() {
return dependencies; return dependencies;
} }
@Override @Override
public Collection<Task> getDependents() { public Collection<Task<?>> getDependents() {
return dependents; return dependents;
} }
@@ -147,7 +147,7 @@ public final class CurseCompletionTask extends Task {
// Let this task fail if the curse manifest has not been completed. // Let this task fail if the curse manifest has not been completed.
// But continue other downloads. // But continue other downloads.
if (!flag.get() || notFound.get()) if (!flag.get() || notFound.get())
dependencies.add(Task.of(() -> { dependencies.add(Task.runAsync(() -> {
if (notFound.get()) if (notFound.get())
throw new CurseCompletionException(new FileNotFoundException()); throw new CurseCompletionException(new FileNotFoundException());
else else

View File

@@ -38,7 +38,7 @@ import java.util.List;
* *
* @author huangyuhui * @author huangyuhui
*/ */
public final class CurseInstallTask extends Task { public final class CurseInstallTask extends Task<Void> {
private final DefaultDependencyManager dependencyManager; private final DefaultDependencyManager dependencyManager;
private final DefaultGameRepository repository; private final DefaultGameRepository repository;
@@ -48,8 +48,8 @@ public final class CurseInstallTask extends Task {
private final String name; private final String name;
private final File run; private final File run;
private final ModpackConfiguration<CurseManifest> config; private final ModpackConfiguration<CurseManifest> config;
private final List<Task> dependents = new LinkedList<>(); private final List<Task<?>> dependents = new LinkedList<>();
private final List<Task> dependencies = new LinkedList<>(); private final List<Task<?>> dependencies = new LinkedList<>();
/** /**
* Constructor. * Constructor.
@@ -99,12 +99,12 @@ public final class CurseInstallTask extends Task {
} }
@Override @Override
public Collection<Task> getDependents() { public Collection<Task<?>> getDependents() {
return dependents; return dependents;
} }
@Override @Override
public Collection<Task> getDependencies() { public Collection<Task<?>> getDependencies() {
return dependencies; return dependencies;
} }

View File

@@ -33,7 +33,7 @@ import java.util.List;
import static org.jackhuang.hmcl.util.DigestUtils.digest; import static org.jackhuang.hmcl.util.DigestUtils.digest;
import static org.jackhuang.hmcl.util.Hex.encodeHex; import static org.jackhuang.hmcl.util.Hex.encodeHex;
public final class MinecraftInstanceTask<T> extends Task { public final class MinecraftInstanceTask<T> extends Task<Void> {
private final File zipFile; private final File zipFile;
private final Charset encoding; private final Charset encoding;

View File

@@ -31,7 +31,7 @@ import java.util.function.Predicate;
import static org.jackhuang.hmcl.util.DigestUtils.digest; import static org.jackhuang.hmcl.util.DigestUtils.digest;
import static org.jackhuang.hmcl.util.Hex.encodeHex; import static org.jackhuang.hmcl.util.Hex.encodeHex;
public class ModpackInstallTask<T> extends Task { public class ModpackInstallTask<T> extends Task<Void> {
private final File modpackFile; private final File modpackFile;
private final File dest; private final File dest;

View File

@@ -26,14 +26,14 @@ import java.nio.file.Path;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
public class ModpackUpdateTask extends Task { public class ModpackUpdateTask extends Task<Void> {
private final DefaultGameRepository repository; private final DefaultGameRepository repository;
private final String id; private final String id;
private final Task updateTask; private final Task<?> updateTask;
private final Path backupFolder; private final Path backupFolder;
public ModpackUpdateTask(DefaultGameRepository repository, String id, Task updateTask) { public ModpackUpdateTask(DefaultGameRepository repository, String id, Task<?> updateTask) {
this.repository = repository; this.repository = repository;
this.id = id; this.id = id;
this.updateTask = updateTask; this.updateTask = updateTask;
@@ -49,7 +49,7 @@ public class ModpackUpdateTask extends Task {
} }
@Override @Override
public Collection<? extends Task> getDependencies() { public Collection<Task<?>> getDependencies() {
return Collections.singleton(updateTask); return Collections.singleton(updateTask);
} }

View File

@@ -46,15 +46,15 @@ import java.util.Optional;
* *
* @author huangyuhui * @author huangyuhui
*/ */
public final class MultiMCModpackInstallTask extends Task { public final class MultiMCModpackInstallTask extends Task<Void> {
private final File zipFile; private final File zipFile;
private final Modpack modpack; private final Modpack modpack;
private final MultiMCInstanceConfiguration manifest; private final MultiMCInstanceConfiguration manifest;
private final String name; private final String name;
private final DefaultGameRepository repository; private final DefaultGameRepository repository;
private final List<Task> dependencies = new LinkedList<>(); private final List<Task<?>> dependencies = new LinkedList<>();
private final List<Task> dependents = new LinkedList<>(); private final List<Task<?>> dependents = new LinkedList<>();
public MultiMCModpackInstallTask(DefaultDependencyManager dependencyManager, File zipFile, Modpack modpack, MultiMCInstanceConfiguration manifest, String name) { public MultiMCModpackInstallTask(DefaultDependencyManager dependencyManager, File zipFile, Modpack modpack, MultiMCInstanceConfiguration manifest, String name) {
this.zipFile = zipFile; this.zipFile = zipFile;
@@ -91,7 +91,7 @@ public final class MultiMCModpackInstallTask extends Task {
} }
@Override @Override
public List<Task> getDependencies() { public List<Task<?>> getDependencies() {
return dependencies; return dependencies;
} }
@@ -126,7 +126,7 @@ public final class MultiMCModpackInstallTask extends Task {
} }
@Override @Override
public List<Task> getDependents() { public List<Task<?>> getDependents() {
return dependents; return dependents;
} }

View File

@@ -1,74 +0,0 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.task;
import org.jackhuang.hmcl.util.AutoTypingMap;
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
import java.util.Collection;
import java.util.Collections;
/**
* A task that combines two tasks and make sure [pred] runs before succ.
*
* @author huangyuhui
*/
final class CoupleTask extends Task {
private final boolean relyingOnDependents;
private final Task pred;
private Task succ;
private final ExceptionalSupplier<Task, ?> supplier;
/**
* A task that combines two tasks and make sure pred runs before succ.
*
* @param pred the task that runs before supplier.
* @param supplier a callback that returns the task runs after pred, succ will be executed asynchronously. You can do something that relies on the result of pred.
* @param relyingOnDependents true if this task chain will be broken when task pred fails.
*/
CoupleTask(Task pred, ExceptionalSupplier<Task, ?> supplier, boolean relyingOnDependents) {
this.pred = pred;
this.supplier = supplier;
this.relyingOnDependents = relyingOnDependents;
setSignificance(TaskSignificance.MODERATE);
setName(supplier.toString());
}
@Override
public void execute() throws Exception {
setName(supplier.toString());
succ = supplier.get();
}
@Override
public Collection<Task> getDependents() {
return pred == null ? Collections.emptySet() : Collections.singleton(pred);
}
@Override
public Collection<Task> getDependencies() {
return succ == null ? Collections.emptySet() : Collections.singleton(succ);
}
@Override
public boolean isRelyingOnDependents() {
return relyingOnDependents;
}
}

View File

@@ -34,6 +34,7 @@ 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.Optional; import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.logging.Level; import java.util.logging.Level;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
@@ -44,7 +45,7 @@ import static org.jackhuang.hmcl.util.DigestUtils.getDigest;
* *
* @author huangyuhui * @author huangyuhui
*/ */
public class FileDownloadTask extends Task { public class FileDownloadTask extends Task<Void> {
public static class IntegrityCheck { public static class IntegrityCheck {
private String algorithm; private String algorithm;
@@ -121,6 +122,7 @@ public class FileDownloadTask extends Task {
this.retry = retry; this.retry = retry;
setName(file.getName()); setName(file.getName());
setExecutor(Schedulers.io());
} }
private void closeFiles() { private void closeFiles() {
@@ -142,11 +144,6 @@ public class FileDownloadTask extends Task {
stream = null; stream = null;
} }
@Override
public Scheduler getScheduler() {
return Schedulers.io();
}
public EventManager<FailedEvent<URL>> getOnFailed() { public EventManager<FailedEvent<URL>> getOnFailed() {
return onFailed; return onFailed;
} }

View File

@@ -30,46 +30,37 @@ import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.concurrent.Executor;
import java.util.logging.Level; import java.util.logging.Level;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
/** /**
* *
* @author huang * @author huangyuhui
*/ */
public final class GetTask extends TaskResult<String> { public final class GetTask extends Task<String> {
private final URL url; private final URL url;
private final Charset charset; private final Charset charset;
private final int retry; private final int retry;
private final String id;
private CacheRepository repository = CacheRepository.getInstance(); private CacheRepository repository = CacheRepository.getInstance();
public GetTask(URL url) { public GetTask(URL url) {
this(url, ID); this(url, UTF_8);
} }
public GetTask(URL url, String id) { public GetTask(URL url, Charset charset) {
this(url, id, UTF_8); this(url, charset, 5);
} }
public GetTask(URL url, String id, Charset charset) { public GetTask(URL url, Charset charset, int retry) {
this(url, id, charset, 5);
}
public GetTask(URL url, String id, Charset charset, int retry) {
this.url = url; this.url = url;
this.charset = charset; this.charset = charset;
this.retry = retry; this.retry = retry;
this.id = id;
setName(url.toString()); setName(url.toString());
} setExecutor(Schedulers.io());
@Override
public Scheduler getScheduler() {
return Schedulers.io();
} }
public GetTask setCacheRepository(CacheRepository repository) { public GetTask setCacheRepository(CacheRepository repository) {
@@ -138,9 +129,4 @@ public final class GetTask extends TaskResult<String> {
throw new DownloadException(url, exception); throw new DownloadException(url, exception);
} }
/**
* The default task result ID.
*/
public static final String ID = "http_get";
} }

View File

@@ -1,57 +0,0 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.task;
import java.util.Arrays;
import java.util.Collection;
/**
* The tasks that provides a way to execute tasks parallelly.
* Fails when some of {@link #tasks} failed.
*
* @author huangyuhui
*/
public final class ParallelTask extends Task {
private final Collection<Task> tasks;
/**
* Constructor.
*
* @param tasks the tasks that can be executed parallelly.
*/
public ParallelTask(Task... tasks) {
this.tasks = Arrays.asList(tasks);
setSignificance(TaskSignificance.MINOR);
}
public ParallelTask(Collection<Task> tasks) {
this.tasks = tasks;
setSignificance(TaskSignificance.MINOR);
}
@Override
public void execute() {
}
@Override
public Collection<Task> getDependents() {
return tasks;
}
}

View File

@@ -1,38 +0,0 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.task;
import java.util.concurrent.Future;
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
/**
* Determines how a task is executed.
*
* @author huangyuhui
*/
public abstract class Scheduler {
/**
* Schedules the given task.
*
* @return the future
*/
public abstract Future<?> schedule(ExceptionalRunnable<?> block);
}

View File

@@ -1,45 +0,0 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.task;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
/**
*
* @author huangyuhui
*/
class SchedulerExecutorService extends Scheduler {
private final ExecutorService executorService;
public SchedulerExecutorService(ExecutorService executorService) {
this.executorService = executorService;
}
@Override
public Future<?> schedule(ExceptionalRunnable<?> block) {
if (executorService.isShutdown() || executorService.isTerminated())
return Schedulers.NONE.schedule(block);
else
return executorService.submit(block.toCallable());
}
}

View File

@@ -1,92 +0,0 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.task;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
/**
*
* @author huangyuhui
*/
class SchedulerImpl extends Scheduler {
private final Consumer<Runnable> executor;
public SchedulerImpl(Consumer<Runnable> executor) {
this.executor = executor;
}
@Override
public Future<?> schedule(ExceptionalRunnable<?> block) {
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<Exception> wrapper = new AtomicReference<>();
executor.accept(() -> {
try {
block.run();
} catch (Exception e) {
wrapper.set(e);
} finally {
latch.countDown();
}
Thread.interrupted(); // clear the `interrupted` flag to prevent from interrupting EventDispatch thread.
});
return new Future<Void>() {
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
@Override
public boolean isCancelled() {
return false;
}
@Override
public boolean isDone() {
return latch.getCount() == 0;
}
private Void getImpl() throws ExecutionException {
Exception e = wrapper.get();
if (e != null)
throw new ExecutionException(e);
return null;
}
@Override
public Void get() throws InterruptedException, ExecutionException {
latch.await();
return getImpl();
}
@Override
public Void get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
if (!latch.await(timeout, unit))
throw new TimeoutException();
return getImpl();
}
};
}
}

View File

@@ -17,8 +17,10 @@
*/ */
package org.jackhuang.hmcl.task; package org.jackhuang.hmcl.task;
import javafx.application.Platform;
import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.Logging;
import javax.swing.*;
import java.util.concurrent.*; import java.util.concurrent.*;
/** /**
@@ -32,7 +34,7 @@ public final class Schedulers {
private static volatile ExecutorService CACHED_EXECUTOR; private static volatile ExecutorService CACHED_EXECUTOR;
private static synchronized ExecutorService getCachedExecutorService() { public static synchronized Executor newThread() {
if (CACHED_EXECUTOR == null) if (CACHED_EXECUTOR == null)
CACHED_EXECUTOR = new ThreadPoolExecutor(0, Integer.MAX_VALUE, CACHED_EXECUTOR = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60, TimeUnit.SECONDS, new SynchronousQueue<>(), Executors.defaultThreadFactory()); 60, TimeUnit.SECONDS, new SynchronousQueue<>(), Executors.defaultThreadFactory());
@@ -42,7 +44,7 @@ public final class Schedulers {
private static volatile ExecutorService IO_EXECUTOR; private static volatile ExecutorService IO_EXECUTOR;
private static synchronized ExecutorService getIOExecutorService() { public static synchronized Executor io() {
if (IO_EXECUTOR == null) if (IO_EXECUTOR == null)
IO_EXECUTOR = Executors.newFixedThreadPool(6, runnable -> { IO_EXECUTOR = Executors.newFixedThreadPool(6, runnable -> {
Thread thread = Executors.defaultThreadFactory().newThread(runnable); Thread thread = Executors.defaultThreadFactory().newThread(runnable);
@@ -55,7 +57,7 @@ public final class Schedulers {
private static volatile ExecutorService SINGLE_EXECUTOR; private static volatile ExecutorService SINGLE_EXECUTOR;
private static synchronized ExecutorService getSingleExecutorService() { public static synchronized Executor computation() {
if (SINGLE_EXECUTOR == null) if (SINGLE_EXECUTOR == null)
SINGLE_EXECUTOR = Executors.newSingleThreadExecutor(runnable -> { SINGLE_EXECUTOR = Executors.newSingleThreadExecutor(runnable -> {
Thread thread = Executors.defaultThreadFactory().newThread(runnable); Thread thread = Executors.defaultThreadFactory().newThread(runnable);
@@ -66,54 +68,18 @@ public final class Schedulers {
return SINGLE_EXECUTOR; return SINGLE_EXECUTOR;
} }
private static final Scheduler IMMEDIATE = new SchedulerImpl(Runnable::run); public static Executor javafx() {
return Platform::runLater;
public static Scheduler immediate() {
return IMMEDIATE;
} }
private static Scheduler NEW_THREAD; public static Executor swing() {
return SwingUtilities::invokeLater;
public static synchronized Scheduler newThread() {
if (NEW_THREAD == null)
NEW_THREAD = new SchedulerExecutorService(getCachedExecutorService());
return NEW_THREAD;
} }
private static Scheduler IO; public static Executor defaultScheduler() {
public static synchronized Scheduler io() {
if (IO == null)
IO = new SchedulerExecutorService(getIOExecutorService());
return IO;
}
private static Scheduler COMPUTATION;
public static synchronized Scheduler computation() {
if (COMPUTATION == null)
COMPUTATION = new SchedulerExecutorService(getSingleExecutorService());
return COMPUTATION;
}
private static final Scheduler JAVAFX = new SchedulerImpl(javafx.application.Platform::runLater);
public static Scheduler javafx() {
return JAVAFX;
}
private static final Scheduler SWING = new SchedulerImpl(javax.swing.SwingUtilities::invokeLater);
public static Scheduler swing() {
return SWING;
}
public static synchronized Scheduler defaultScheduler() {
return newThread(); return newThread();
} }
static final Scheduler NONE = new SchedulerImpl(any -> {});
public static synchronized void shutdown() { public static synchronized void shutdown() {
Logging.LOG.info("Shutting down executor services."); Logging.LOG.info("Shutting down executor services.");

View File

@@ -1,52 +0,0 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.task;
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
/**
*
* @author huangyuhui
*/
class SimpleTask extends Task {
private final ExceptionalRunnable<?> closure;
private final Scheduler scheduler;
public SimpleTask(String name, ExceptionalRunnable<?> closure, Scheduler scheduler) {
this.closure = closure;
this.scheduler = scheduler;
if (name == null) {
setSignificance(TaskSignificance.MINOR);
setName(closure.toString());
} else {
setName(name);
}
}
@Override
public Scheduler getScheduler() {
return scheduler;
}
@Override
public void execute() throws Exception {
closure.run();
}
}

View File

@@ -1,46 +0,0 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.task;
import org.jackhuang.hmcl.util.AutoTypingMap;
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
import java.util.concurrent.Callable;
/**
*
* @author huangyuhui
*/
class SimpleTaskResult<V> extends TaskResult<V> {
private final Callable<V> callable;
public SimpleTaskResult(Callable<V> callable) {
this.callable = callable;
}
public SimpleTaskResult(ExceptionalSupplier<V, ?> supplier) {
this.callable = supplier.toCallable();
}
@Override
public void execute() throws Exception {
setResult(callable.call());
}
}

View File

@@ -27,13 +27,17 @@ import org.jackhuang.hmcl.util.InvocationDispatcher;
import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.ReflectionHelper; import org.jackhuang.hmcl.util.ReflectionHelper;
import org.jackhuang.hmcl.util.function.ExceptionalConsumer; import org.jackhuang.hmcl.util.function.ExceptionalConsumer;
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import org.jackhuang.hmcl.util.function.ExceptionalRunnable; import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
import org.jackhuang.hmcl.util.function.ExceptionalSupplier; import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.logging.Level; import java.util.logging.Level;
/** /**
@@ -41,50 +45,65 @@ import java.util.logging.Level;
* *
* @author huangyuhui * @author huangyuhui
*/ */
public abstract class Task { public abstract class Task<T> {
private final EventManager<TaskEvent> onDone = new EventManager<>(); private final EventManager<TaskEvent> onDone = new EventManager<>();
private TaskSignificance significance = TaskSignificance.MAJOR;
/** /**
* True if not logging when executing this task. * True if not logging when executing this task.
*/ */
private TaskSignificance significance = TaskSignificance.MAJOR;
public final TaskSignificance getSignificance() { public final TaskSignificance getSignificance() {
return significance; return significance;
} }
public void setSignificance(TaskSignificance significance) { public final void setSignificance(TaskSignificance significance) {
this.significance = significance; this.significance = significance;
} }
// state
private TaskState state = TaskState.READY; private TaskState state = TaskState.READY;
public TaskState getState() { public final TaskState getState() {
return state; return state;
} }
void setState(TaskState state) { final void setState(TaskState state) {
this.state = state; this.state = state;
} }
private Exception lastException; // last exception
private Exception exception;
public Exception getLastException() {
return lastException;
}
void setLastException(Exception e) {
lastException = e;
}
/** /**
* The scheduler that decides how this task runs. * When task has been cancelled, task.exception will be null.
*
* @return the exception thrown during execution, possibly from dependents or dependencies.
*/ */
public Scheduler getScheduler() { public final Exception getException() {
return Schedulers.defaultScheduler(); return exception;
} }
final void setException(Exception e) {
exception = e;
}
private Executor executor = Schedulers.defaultScheduler();
/**
* The executor that decides how this task runs.
*/
public final Executor getExecutor() {
return executor;
}
public final Task<T> setExecutor(Executor executor) {
this.executor = executor;
return this;
}
// dependents succeeded
private boolean dependentsSucceeded = false; private boolean dependentsSucceeded = false;
public boolean isDependentsSucceeded() { public boolean isDependentsSucceeded() {
@@ -95,6 +114,7 @@ public abstract class Task {
dependentsSucceeded = true; dependentsSucceeded = true;
} }
// dependencies succeeded
private boolean dependenciesSucceeded = false; private boolean dependenciesSucceeded = false;
public boolean isDependenciesSucceeded() { public boolean isDependenciesSucceeded() {
@@ -123,17 +143,57 @@ public abstract class Task {
return true; return true;
} }
// name
private String name = getClass().getName(); private String name = getClass().getName();
public String getName() { public String getName() {
return name; return name;
} }
public Task setName(String name) { public Task<T> setName(String name) {
this.name = name; this.name = name;
return this; return this;
} }
@Override
public String toString() {
if (getClass().getName().equals(getName()))
return getName();
else
return getClass().getName() + "[" + getName() + "]";
}
// result
private T result;
private Consumer<T> resultConsumer;
/**
* Returns the result of this task.
*
* The result will be generated only if the execution is completed.
*/
public T getResult() {
return result;
}
protected void setResult(T result) {
this.result = result;
if (resultConsumer != null)
resultConsumer.accept(result);
}
/**
* Sync the result of this task by given action.
*
* @param action the action to perform when result of this task changed
* @return this Task
*/
public Task<T> storeTo(Consumer<T> action) {
this.resultConsumer = action;
return this;
}
// execution
public boolean doPreExecute() { public boolean doPreExecute() {
return false; return false;
} }
@@ -171,7 +231,7 @@ public abstract class Task {
/** /**
* The collection of sub-tasks that should execute **before** this task running. * The collection of sub-tasks that should execute **before** this task running.
*/ */
public Collection<? extends Task> getDependents() { public Collection<Task<?>> getDependents() {
return Collections.emptySet(); return Collections.emptySet();
} }
@@ -179,7 +239,7 @@ public abstract class Task {
* The collection of sub-tasks that should execute **after** this task running. * The collection of sub-tasks that should execute **after** this task running.
* Will not be executed if execution fails. * Will not be executed if execution fails.
*/ */
public Collection<? extends Task> getDependencies() { public Collection<Task<?>> getDependencies() {
return Collections.emptySet(); return Collections.emptySet();
} }
@@ -232,15 +292,15 @@ public abstract class Task {
if (getSignificance().shouldLog()) if (getSignificance().shouldLog())
Logging.LOG.log(Level.FINE, "Executing task: " + getName()); Logging.LOG.log(Level.FINE, "Executing task: " + getName());
for (Task task : getDependents()) for (Task<?> task : getDependents())
doSubTask(task); doSubTask(task);
execute(); execute();
for (Task task : getDependencies()) for (Task<?> task : getDependencies())
doSubTask(task); doSubTask(task);
onDone.fireEvent(new TaskEvent(this, this, false)); onDone.fireEvent(new TaskEvent(this, this, false));
} }
private void doSubTask(Task task) throws Exception { private void doSubTask(Task<?> task) throws Exception {
message.bind(task.message); message.bind(task.message);
progress.bind(task.progress); progress.bind(task.progress);
task.run(); task.run();
@@ -273,61 +333,240 @@ public abstract class Task {
return executor().test(); return executor().test();
} }
public final Task then(Task b) {
return then(convert(b));
}
public final Task then(ExceptionalSupplier<Task, ?> b) {
return new CoupleTask(this, b, true);
}
/** /**
* Returns a new TaskResult that, when this task completes * Returns a new Task that, when this task completes
* normally, is executed using the default Scheduler. * normally, is executed using the default Executor, with this
* task's result as the argument to the supplied function.
* *
* @param fn the function to use to compute the value of the returned TaskResult * @param fn the function to use to compute the value of the returned Task
* @param <U> the function's return type * @param <U> the function's return type
* @return the new TaskResult * @return the new Task
*/ */
public final <U> TaskResult<U> thenSupply(Callable<U> fn) { public <U, E extends Exception> Task<U> thenApply(ExceptionalFunction<T, U, E> fn) {
return thenCompose(() -> Task.ofResult(fn)); return thenApply(Schedulers.defaultScheduler(), fn);
} }
/** /**
* Returns a new TaskResult that, when this task completes * Returns a new Task that, when this task completes
* normally, is executed using the supplied Executor, with this
* task's result as the argument to the supplied function.
*
* @param executor the executor to use for asynchronous execution
* @param fn the function to use to compute the value of the returned Task
* @param <U> the function's return type
* @return the new Task
*/
public <U, E extends Exception> Task<U> thenApply(Executor executor, ExceptionalFunction<T, U, E> fn) {
return thenApply(getCaller(), executor, fn);
}
/**
* Returns a new Task that, when this task completes
* normally, is executed using the supplied Executor, with this
* task's result as the argument to the supplied function.
*
* @param name the name of this new Task for displaying
* @param executor the executor to use for asynchronous execution
* @param fn the function to use to compute the value of the returned Task
* @param <U> the function's return type
* @return the new Task
*/
public <U, E extends Exception> Task<U> thenApply(String name, Executor executor, ExceptionalFunction<T, U, E> fn) {
return new UniApply<>(fn).setExecutor(executor).setName(name);
}
/**
* Returns a new Task that, when this task completes
* normally, is executed using the default Executor, with this
* task's result as the argument to the supplied action.
*
* @param action the action to perform before completing the
* returned Task
* @return the new Task
*/
public <E extends Exception> Task<Void> thenAccept(ExceptionalConsumer<T, E> action) {
return thenAccept(Schedulers.defaultScheduler(), action);
}
/**
* Returns a new Task that, when this task completes
* normally, is executed using the supplied Executor, with this
* task's result as the argument to the supplied action.
*
* @param action the action to perform before completing the returned Task
* @param executor the executor to use for asynchronous execution
* @return the new Task
*/
public <E extends Exception> Task<Void> thenAccept(Executor executor, ExceptionalConsumer<T, E> action) {
return thenAccept(getCaller(), executor, action);
}
/**
* Returns a new Task that, when this task completes
* normally, is executed using the supplied Executor, with this
* task's result as the argument to the supplied action.
*
* @param name the name of this new Task for displaying
* @param action the action to perform before completing the returned Task
* @param executor the executor to use for asynchronous execution
* @return the new Task
*/
public <E extends Exception> Task<Void> thenAccept(String name, Executor executor, ExceptionalConsumer<T, E> action) {
return thenApply(name, executor, result -> {
action.accept(result);
return null;
});
}
/**
* Returns a new Task that, when this task completes
* normally, executes the given action using the default Executor.
*
* @param action the action to perform before completing the
* returned Task
* @return the new Task
*/
public <E extends Exception> Task<Void> thenRun(ExceptionalRunnable<E> action) {
return thenRun(Schedulers.defaultScheduler(), action);
}
/**
* Returns a new Task that, when this task completes
* normally, executes the given action using the supplied Executor.
*
* @param action the action to perform before completing the
* returned Task
* @param executor the executor to use for asynchronous execution
* @return the new Task
*/
public <E extends Exception> Task<Void> thenRun(Executor executor, ExceptionalRunnable<E> action) {
return thenRun(getCaller(), executor, action);
}
/**
* Returns a new Task that, when this task completes
* normally, executes the given action using the supplied Executor.
*
* @param name the name of this new Task for displaying
* @param action the action to perform before completing the
* returned Task
* @param executor the executor to use for asynchronous execution
* @return the new Task
*/
public <E extends Exception> Task<Void> thenRun(String name, Executor executor, ExceptionalRunnable<E> action) {
return thenApply(name, executor, ignore -> {
action.run();
return null;
});
}
/**
* Returns a new Task that, when this task completes
* normally, is executed using the default Executor.
*
* @param fn the function to use to compute the value of the returned Task
* @param <U> the function's return type
* @return the new Task
*/
public final <U> Task<U> thenSupply(Callable<U> fn) {
return thenCompose(() -> Task.supplyAsync(fn));
}
/**
* Returns a new Task that, when this task completes
* normally, is executed using the default Executor.
*
* @param name the name of this new Task for displaying
* @param fn the function to use to compute the value of the returned Task
* @param <U> the function's return type
* @return the new Task
*/
public final <U> Task<U> thenSupply(String name, Callable<U> fn) {
return thenCompose(() -> Task.supplyAsync(name, fn));
}
/**
* Returns a new Task that, when this task completes
* normally, is executed. * normally, is executed.
* *
* @param fn the function returning a new TaskResult * @param other the another Task
* @param <U> the type of the returned TaskResult's result * @param <U> the type of the returned Task's result
* @return the TaskResult * @return the Task
*/ */
public final <U> TaskResult<U> thenCompose(ExceptionalSupplier<TaskResult<U>, ?> fn) { public final <U> Task<U> thenCompose(Task<U> other) {
return new TaskResult<U>() { return thenCompose(() -> other);
TaskResult<U> then; }
@Override /**
public Collection<? extends Task> getDependents() { * Returns a new Task that, when this task completes
return Collections.singleton(Task.this); * normally, is executed.
} *
* @param fn the function returning a new Task
@Override * @param <U> the type of the returned Task's result
public void execute() throws Exception { * @return the Task
then = fn.get().storeTo(this::setResult); */
} public final <U> Task<U> thenCompose(ExceptionalSupplier<Task<U>, ?> fn) {
return new UniCompose<>(fn, true);
@Override
public Collection<? extends Task> getDependencies() {
return then == null ? Collections.emptyList() : Collections.singleton(then);
}
};
} }
public final Task with(Task b) { /**
return with(convert(b)); * Returns a new Task that, when this task completes
* normally, is executed with result of this task as the argument
* to the supplied function.
*
* @param fn the function returning a new Task
* @param <U> the type of the returned Task's result
* @return the Task
*/
public <U, E extends Exception> Task<U> thenCompose(ExceptionalFunction<T, Task<U>, E> fn) {
return new UniCompose<>(fn, true);
} }
public final <E extends Exception> Task with(ExceptionalSupplier<Task, E> b) { public final <U> Task<U> withCompose(Task<U> other) {
return new CoupleTask(this, b, false); return withCompose(() -> other);
}
public final <U, E extends Exception> Task<U> withCompose(ExceptionalSupplier<Task<U>, E> fn) {
return new UniCompose<>(fn, false);
}
/**
* Returns a new Task that, when this task completes
* normally, executes the given action using the default Executor.
*
* @param action the action to perform before completing the
* returned Task
* @return the new Task
*/
public <E extends Exception> Task<Void> withRun(ExceptionalRunnable<E> action) {
return withRun(Schedulers.defaultScheduler(), action);
}
/**
* Returns a new Task that, when this task completes
* normally, executes the given action using the supplied Executor.
*
* @param action the action to perform before completing the
* returned Task
* @param executor the executor to use for asynchronous execution
* @return the new Task
*/
public <E extends Exception> Task<Void> withRun(Executor executor, ExceptionalRunnable<E> action) {
return withRun(getCaller(), executor, action);
}
/**
* Returns a new Task that, when this task completes
* normally, executes the given action using the supplied Executor.
*
* @param name the name of this new Task for displaying
* @param action the action to perform before completing the
* returned Task
* @param executor the executor to use for asynchronous execution
* @return the new Task
*/
public <E extends Exception> Task<Void> withRun(String name, Executor executor, ExceptionalRunnable<E> action) {
return new UniCompose<>(() -> Task.runAsync(name, executor, action), false);
} }
/** /**
@@ -344,7 +583,7 @@ public abstract class Task {
* @param action the action to perform * @param action the action to perform
* @return the new Task * @return the new Task
*/ */
public final Task whenComplete(FinalizedCallback action) { public final Task<Void> whenComplete(FinalizedCallback action) {
return whenComplete(Schedulers.defaultScheduler(), action); return whenComplete(Schedulers.defaultScheduler(), action);
} }
@@ -361,35 +600,33 @@ public abstract class Task {
* with this exception unless this task also completed exceptionally. * with this exception unless this task also completed exceptionally.
* *
* @param action the action to perform * @param action the action to perform
* @param scheduler the executor to use for asynchronous execution * @param executor the executor to use for asynchronous execution
* @return the new Task * @return the new Task
*/ */
public final Task whenComplete(Scheduler scheduler, FinalizedCallback action) { public final Task<Void> whenComplete(Executor executor, FinalizedCallback action) {
return new Task() { return new Task<Void>() {
{ {
setSignificance(TaskSignificance.MODERATE); setSignificance(TaskSignificance.MODERATE);
} }
@Override
public Scheduler getScheduler() {
return scheduler;
}
@Override @Override
public void execute() throws Exception { public void execute() throws Exception {
action.execute(isDependentsSucceeded(), Task.this.getLastException()); if (isDependentsSucceeded() != (Task.this.getException() == null))
throw new AssertionError("When dependents succeeded, Task.exception must be nonnull.");
action.execute(Task.this.getException());
if (!isDependentsSucceeded()) { if (!isDependentsSucceeded()) {
setSignificance(TaskSignificance.MINOR); setSignificance(TaskSignificance.MINOR);
if (Task.this.getLastException() == null) if (Task.this.getException() == null)
throw new CancellationException(); throw new CancellationException();
else else
throw Task.this.getLastException(); throw Task.this.getException();
} }
} }
@Override @Override
public Collection<Task> getDependents() { public Collection<Task<?>> getDependents() {
return Collections.singleton(Task.this); return Collections.singleton(Task.this);
} }
@@ -397,7 +634,26 @@ public abstract class Task {
public boolean isRelyingOnDependents() { public boolean isRelyingOnDependents() {
return false; return false;
} }
}.setName(getCaller()); }.setExecutor(executor).setName(getCaller());
}
/**
* Returns a new Task with the same exception as this task, that executes
* the given action when this task completes.
*
* <p>When this task is complete, the given action is invoked with the
* result (or {@code null} if none), a boolean value represents the
* execution status of this task, and the exception (or {@code null}
* if none) of this task as arguments. The returned task is completed
* when the action returns. If the supplied action itself encounters an
* exception, then the returned task exceptionally completes with this
* exception unless this task also completed exceptionally.
*
* @param action the action to perform
* @return the new Task
*/
public Task<Void> whenComplete(Executor executor, FinalizedCallbackWithResult<T> action) {
return whenComplete(executor, (exception -> action.execute(getResult(), exception)));
} }
/** /**
@@ -415,9 +671,9 @@ public abstract class Task {
* @param failure the action to perform when this task exceptionally returned * @param failure the action to perform when this task exceptionally returned
* @return the new Task * @return the new Task
*/ */
public final <E1 extends Exception, E2 extends Exception> Task whenComplete(Scheduler scheduler, ExceptionalRunnable<E1> success, ExceptionalConsumer<Exception, E2> failure) { public final <E1 extends Exception, E2 extends Exception> Task<Void> whenComplete(Executor executor, ExceptionalRunnable<E1> success, ExceptionalConsumer<Exception, E2> failure) {
return whenComplete(scheduler, (isDependentSucceeded, exception) -> { return whenComplete(executor, exception -> {
if (isDependentSucceeded) { if (exception == null) {
if (success != null) if (success != null)
try { try {
success.run(); success.run();
@@ -433,44 +689,114 @@ public abstract class Task {
}); });
} }
public static Task of(ExceptionalRunnable<?> closure) { /**
return of(Schedulers.defaultScheduler(), closure); * Returns a new Task with the same exception as this task, that executes
* the given actions when this task completes.
*
* <p>When this task is complete, the given success action is invoked with
* the result, the given failure action is invoked with the exception of
* this task. The returned task is completed when the action returns. If
* the supplied action itself encounters an exception, then the returned
* task exceptionally completes with this exception unless this task also
* completed exceptionally.
*
* @param success the action to perform when this task successfully completed
* @param failure the action to perform when this task exceptionally returned
* @return the new Task
*/
public <E1 extends Exception, E2 extends Exception> Task<Void> whenComplete(Executor executor, ExceptionalConsumer<T, E1> success, ExceptionalConsumer<Exception, E2> failure) {
return whenComplete(executor, () -> success.accept(getResult()), failure);
} }
public static Task of(String name, ExceptionalRunnable<?> closure) { public static Task<Void> runAsync(ExceptionalRunnable<?> closure) {
return of(name, Schedulers.defaultScheduler(), closure); return runAsync(Schedulers.defaultScheduler(), closure);
} }
public static Task of(Scheduler scheduler, ExceptionalRunnable<?> closure) { public static Task<Void> runAsync(String name, ExceptionalRunnable<?> closure) {
return of(getCaller(), scheduler, closure); return runAsync(name, Schedulers.defaultScheduler(), closure);
} }
public static Task of(String name, Scheduler scheduler, ExceptionalRunnable<?> closure) { public static Task<Void> runAsync(Executor executor, ExceptionalRunnable<?> closure) {
return new SimpleTask(name, closure, scheduler); return runAsync(getCaller(), executor, closure);
} }
public static Task ofThen(ExceptionalSupplier<Task, ?> b) { public static Task<Void> runAsync(String name, Executor executor, ExceptionalRunnable<?> closure) {
return new CoupleTask(null, b, true); return new SimpleTask<>(closure.toCallable()).setExecutor(executor).setName(name);
} }
public static <V> TaskResult<V> ofResult(Callable<V> callable) { public static <T> Task<T> composeAsync(ExceptionalSupplier<Task<T>, ?> fn) {
return ofResult(getCaller(), callable); return new Task<T>() {
} Task<T> then;
public static <V> TaskResult<V> ofResult(String name, Callable<V> callable) {
return new SimpleTaskResult<>(callable).setName(name);
}
private static ExceptionalSupplier<Task, ?> convert(Task t) {
return new ExceptionalSupplier<Task, Exception>() {
@Override @Override
public Task get() { public void execute() throws Exception {
return t; then = fn.get();
if (then != null)
then.storeTo(this::setResult);
} }
@Override @Override
public String toString() { public Collection<Task<?>> getDependencies() {
return t.getName(); return then == null ? Collections.emptySet() : Collections.singleton(then);
}
};
}
public static <V> Task<V> supplyAsync(Callable<V> callable) {
return supplyAsync(getCaller(), callable);
}
public static <V> Task<V> supplyAsync(Executor executor, Callable<V> callable) {
return supplyAsync(getCaller(), executor, callable);
}
public static <V> Task<V> supplyAsync(String name, Callable<V> callable) {
return supplyAsync(name, Schedulers.defaultScheduler(), callable);
}
public static <V> Task<V> supplyAsync(String name, Executor executor, Callable<V> callable) {
return new SimpleTask<>(callable).setExecutor(executor).setName(name);
}
/**
* Returns a new Task that is completed when all of the given Tasks
* complete. If any of the given Tasks complete exceptionally,
* then the returned Task also does so. Otherwise, the results, if
* any, of the given Tasks are not reflected in the returned Task,
* but may be obtained by inspecting them individually. If no Tasks
* are provided, returns a Task completed with the value {@code null}.
*
* @param tasks the Tasks
* @return a new Task that is completed when all of the given Tasks complete
*/
public static Task<Void> allOf(Task<?>... tasks) {
return allOf(Arrays.asList(tasks));
}
/**
* Returns a new Task that is completed when all of the given Tasks
* complete. If any of the given Tasks complete exceptionally,
* then the returned Task also does so. Otherwise, the results, if
* any, of the given Tasks are not reflected in the returned Task,
* but may be obtained by inspecting them individually. If no Tasks
* are provided, returns a Task completed with the value {@code null}.
*
* @param tasks the Tasks
* @return a new Task that is completed when all of the given Tasks complete
*/
public static Task<Void> allOf(Collection<Task<?>> tasks) {
return new Task<Void>() {
{
setSignificance(TaskSignificance.MINOR);
}
@Override
public void execute() {
}
@Override
public Collection<Task<?>> getDependents() {
return tasks;
} }
}; };
} }
@@ -498,10 +824,105 @@ public abstract class Task {
} }
public interface FinalizedCallback { public interface FinalizedCallback {
void execute(boolean isDependentSucceeded, Exception exception) throws Exception; void execute(Exception exception) throws Exception;
} }
static String getCaller() { public interface FinalizedCallbackWithResult<T> {
void execute(T result, Exception exception) throws Exception;
}
private static String getCaller() {
return ReflectionHelper.getCaller(packageName -> !"org.jackhuang.hmcl.task".equals(packageName)).toString(); return ReflectionHelper.getCaller(packageName -> !"org.jackhuang.hmcl.task".equals(packageName)).toString();
} }
private static final class SimpleTask<T> extends Task<T> {
private final Callable<T> callable;
SimpleTask(Callable<T> callable) {
this.callable = callable;
}
@Override
public void execute() throws Exception {
setResult(callable.call());
}
}
private class UniApply<R> extends Task<R> {
private final ExceptionalFunction<T, R, ?> callable;
UniApply(ExceptionalFunction<T, R, ?> callable) {
this.callable = callable;
}
@Override
public Collection<Task<?>> getDependents() {
return Collections.singleton(Task.this);
}
@Override
public void execute() throws Exception {
setResult(callable.apply(Task.this.getResult()));
}
}
/**
* A task that combines two tasks and make sure [pred] runs before succ.
*
* @author huangyuhui
*/
private final class UniCompose<U> extends Task<U> {
private final boolean relyingOnDependents;
private Task<U> succ;
private final ExceptionalFunction<T, Task<U>, ?> fn;
/**
* A task that combines two tasks and make sure pred runs before succ.
*
* @param fn a callback that returns the task runs after pred, succ will be executed asynchronously. You can do something that relies on the result of pred.
* @param relyingOnDependents true if this task chain will be broken when task pred fails.
*/
UniCompose(ExceptionalSupplier<Task<U>, ?> fn, boolean relyingOnDependents) {
this(result -> fn.get(), relyingOnDependents);
}
/**
* A task that combines two tasks and make sure pred runs before succ.
*
* @param fn a callback that returns the task runs after pred, succ will be executed asynchronously. You can do something that relies on the result of pred.
* @param relyingOnDependents true if this task chain will be broken when task pred fails.
*/
UniCompose(ExceptionalFunction<T, Task<U>, ?> fn, boolean relyingOnDependents) {
this.fn = fn;
this.relyingOnDependents = relyingOnDependents;
setSignificance(TaskSignificance.MODERATE);
setName(fn.toString());
}
@Override
public void execute() throws Exception {
setName(fn.toString());
succ = fn.apply(Task.this.getResult());
if (succ != null)
succ.storeTo(this::setResult);
}
@Override
public Collection<Task<?>> getDependents() {
return Collections.singleton(Task.this);
}
@Override
public Collection<Task<?>> getDependencies() {
return succ == null ? Collections.emptySet() : Collections.singleton(succ);
}
@Override
public boolean isRelyingOnDependents() {
return relyingOnDependents;
}
}
} }

View File

@@ -25,16 +25,16 @@ import org.jackhuang.hmcl.event.Event;
*/ */
public class TaskEvent extends Event { public class TaskEvent extends Event {
private final Task task; private final Task<?> task;
private final boolean failed; private final boolean failed;
public TaskEvent(Object source, Task task, boolean failed) { public TaskEvent(Object source, Task<?> task, boolean failed) {
super(source); super(source);
this.task = task; this.task = task;
this.failed = failed; this.failed = failed;
} }
public Task getTask() { public Task<?> getTask() {
return task; return task;
} }

View File

@@ -17,12 +17,15 @@
*/ */
package org.jackhuang.hmcl.task; package org.jackhuang.hmcl.task;
import org.jackhuang.hmcl.util.*; import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.function.ExceptionalRunnable; import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
import java.util.*; import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level; import java.util.logging.Level;
@@ -32,15 +35,13 @@ import java.util.logging.Level;
*/ */
public final class TaskExecutor { public final class TaskExecutor {
private final Task firstTask; private final Task<?> firstTask;
private final List<TaskListener> taskListeners = new LinkedList<>(); private final List<TaskListener> taskListeners = new LinkedList<>();
private boolean canceled = false; private Exception exception;
private Exception lastException;
private final AtomicInteger totTask = new AtomicInteger(0); private final AtomicInteger totTask = new AtomicInteger(0);
private final ConcurrentLinkedQueue<Future<?>> workerQueue = new ConcurrentLinkedQueue<>(); private CompletableFuture<Boolean> future;
private Scheduler scheduler = Schedulers.newThread();
public TaskExecutor(Task task) { public TaskExecutor(Task<?> task) {
this.firstTask = task; this.firstTask = task;
} }
@@ -48,222 +49,208 @@ public final class TaskExecutor {
taskListeners.add(taskListener); taskListeners.add(taskListener);
} }
public boolean isCanceled() { public Exception getException() {
return canceled; return exception;
}
public Exception getLastException() {
return lastException;
}
public void setScheduler(Scheduler scheduler) {
this.scheduler = Objects.requireNonNull(scheduler);
} }
public TaskExecutor start() { public TaskExecutor start() {
taskListeners.forEach(TaskListener::onStart); taskListeners.forEach(TaskListener::onStart);
workerQueue.add(scheduler.schedule(() -> { future = executeTasks(Collections.singleton(firstTask))
boolean flag = executeTasks(Collections.singleton(firstTask)); .thenApplyAsync(exception -> {
taskListeners.forEach(it -> it.onStop(flag, this)); boolean success = exception == null;
}));
if (!success) {
// We log exception stacktrace because some of exceptions occurred because of bugs.
Logging.LOG.log(Level.WARNING, "An exception occurred in task execution", exception);
}
taskListeners.forEach(it -> it.onStop(success, this));
return success;
})
.exceptionally(e -> {
Lang.handleUncaughtException(resolveException(e));
return false;
});
return this; return this;
} }
public boolean test() { public boolean test() {
taskListeners.forEach(TaskListener::onStart); start();
AtomicBoolean flag = new AtomicBoolean(true);
Future<?> future = scheduler.schedule(() -> {
flag.set(executeTasks(Collections.singleton(firstTask)));
taskListeners.forEach(it -> it.onStop(flag.get(), this));
});
workerQueue.add(future);
try { try {
future.get(); return future.get();
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} catch (ExecutionException | CancellationException e) { } catch (ExecutionException ignore) {
// We have dealt with ExecutionException in exception handling and uncaught exception handler.
} catch (CancellationException e) {
Logging.LOG.log(Level.INFO, "Task " + firstTask + " has been cancelled.", e);
} }
return flag.get(); return false;
} }
/** /**
* Cancel the subscription ant interrupt all tasks. * Cancel the subscription ant interrupt all tasks.
*/ */
public synchronized void cancel() { public synchronized void cancel() {
canceled = true; if (future == null) {
throw new IllegalStateException("Cannot cancel a not started TaskExecutor");
while (!workerQueue.isEmpty()) {
Future<?> future = workerQueue.poll();
if (future != null)
future.cancel(true);
} }
future.cancel(true);
} }
private boolean executeTasks(Collection<? extends Task> tasks) throws InterruptedException { private CompletableFuture<Exception> executeTasks(Collection<Task<?>> tasks) {
if (tasks.isEmpty()) if (tasks == null || tasks.isEmpty())
return true; return CompletableFuture.completedFuture(null);
totTask.addAndGet(tasks.size()); return CompletableFuture.completedFuture(null)
AtomicBoolean success = new AtomicBoolean(true); .thenComposeAsync(unused -> {
CountDownLatch latch = new CountDownLatch(tasks.size()); totTask.addAndGet(tasks.size());
for (Task task : tasks) {
if (canceled)
return false;
Invoker invoker = new Invoker(task, latch, success);
try {
Future<?> future = scheduler.schedule(invoker);
if (future != null)
workerQueue.add(future);
} catch (RejectedExecutionException e) {
throw new InterruptedException();
}
}
if (canceled) return CompletableFuture.allOf(tasks.stream()
return false; .map(task -> CompletableFuture.completedFuture(null)
.thenComposeAsync(unused2 -> executeTask(task))
try { ).toArray(CompletableFuture<?>[]::new));
latch.await(); })
} catch (InterruptedException e) { .thenApplyAsync(unused -> (Exception) null)
return false; .exceptionally(throwable -> {
} Throwable resolved = resolveException(throwable);
return success.get() && !canceled; if (resolved instanceof Exception) {
return (Exception) resolved;
} else {
// If an error occurred, we just rethrow it.
throw new CompletionException(throwable);
}
});
} }
private boolean executeTask(Task task) { private CompletableFuture<?> executeTask(Task<?> task) {
if (canceled) { return CompletableFuture.completedFuture(null)
task.setState(Task.TaskState.FAILED); .thenComposeAsync(unused -> {
task.setLastException(new CancellationException()); task.setState(Task.TaskState.READY);
return false;
}
task.setState(Task.TaskState.READY); if (task.getSignificance().shouldLog())
Logging.LOG.log(Level.FINE, "Executing task: " + task.getName());
if (task.getSignificance().shouldLog()) taskListeners.forEach(it -> it.onReady(task));
Logging.LOG.log(Level.FINE, "Executing task: " + task.getName());
taskListeners.forEach(it -> it.onReady(task)); if (task.doPreExecute()) {
return CompletableFuture.runAsync(wrap(task::preExecute), task.getExecutor());
} else {
return CompletableFuture.completedFuture(null);
}
})
.thenComposeAsync(unused -> executeTasks(task.getDependents()))
.thenComposeAsync(dependentsException -> {
boolean isDependentsSucceeded = dependentsException == null;
boolean flag = false; if (!isDependentsSucceeded && task.isRelyingOnDependents()) {
task.setException(dependentsException);
rethrow(dependentsException);
}
try { if (isDependentsSucceeded)
if (task.doPreExecute()) { task.setDependentsSucceeded();
try {
task.getScheduler().schedule(task::preExecute).get();
} catch (ExecutionException e) {
if (e.getCause() instanceof Exception)
throw (Exception) e.getCause();
else
throw e;
}
}
Collection<? extends Task> dependents = task.getDependents(); return CompletableFuture.runAsync(wrap(() -> {
boolean doDependentsSucceeded = executeTasks(dependents); task.setState(Task.TaskState.RUNNING);
Exception dependentsException = dependents.stream().map(Task::getLastException).filter(Objects::nonNull).findAny().orElse(null); taskListeners.forEach(it -> it.onRunning(task));
if (!doDependentsSucceeded && task.isRelyingOnDependents() || canceled) { task.execute();
task.setLastException(dependentsException); }), task.getExecutor()).whenComplete((unused, throwable) -> {
throw new CancellationException(); task.setState(Task.TaskState.EXECUTED);
} rethrow(throwable);
});
})
.thenComposeAsync(unused -> executeTasks(task.getDependencies()))
.thenComposeAsync(dependenciesException -> {
boolean isDependenciesSucceeded = dependenciesException == null;
if (doDependentsSucceeded) if (isDependenciesSucceeded)
task.setDependentsSucceeded(); task.setDependenciesSucceeded();
try { if (task.doPostExecute()) {
task.getScheduler().schedule(() -> { return CompletableFuture.runAsync(wrap(task::postExecute), task.getExecutor())
task.setState(Task.TaskState.RUNNING); .thenApply(unused -> dependenciesException);
taskListeners.forEach(it -> it.onRunning(task)); } else {
task.execute(); return CompletableFuture.completedFuture(dependenciesException);
}).get(); }
} catch (ExecutionException e) { })
if (e.getCause() instanceof Exception) .thenAcceptAsync(dependenciesException -> {
throw (Exception) e.getCause(); boolean isDependenciesSucceeded = dependenciesException == null;
else
throw e;
} finally {
task.setState(Task.TaskState.EXECUTED);
}
Collection<? extends Task> dependencies = task.getDependencies(); if (!isDependenciesSucceeded && task.isRelyingOnDependencies()) {
boolean doDependenciesSucceeded = executeTasks(dependencies); Logging.LOG.severe("Subtasks failed for " + task.getName());
Exception dependenciesException = dependencies.stream().map(Task::getLastException).filter(Objects::nonNull).findAny().orElse(null); task.setException(dependenciesException);
rethrow(dependenciesException);
}
if (doDependenciesSucceeded) if (task.getSignificance().shouldLog()) {
task.setDependenciesSucceeded(); Logging.LOG.log(Level.FINER, "Task finished: " + task.getName());
}
if (task.doPostExecute()) { task.onDone().fireEvent(new TaskEvent(this, task, false));
try { taskListeners.forEach(it -> it.onFinished(task));
task.getScheduler().schedule(task::postExecute).get();
} catch (ExecutionException e) {
if (e.getCause() instanceof Exception)
throw (Exception) e.getCause();
else
throw e;
}
}
if (!doDependenciesSucceeded && task.isRelyingOnDependencies()) { task.setState(Task.TaskState.SUCCEEDED);
Logging.LOG.severe("Subtasks failed for " + task.getName()); })
task.setLastException(dependenciesException); .exceptionally(throwable -> {
throw new CancellationException(); Throwable resolved = resolveException(throwable);
} if (resolved instanceof Exception) {
Exception e = (Exception) resolved;
if (e instanceof InterruptedException) {
task.setException(e);
if (task.getSignificance().shouldLog()) {
Logging.LOG.log(Level.FINE, "Task aborted: " + task.getName());
}
task.onDone().fireEvent(new TaskEvent(this, task, true));
taskListeners.forEach(it -> it.onFailed(task, e));
} else {
task.setException(e);
exception = e;
if (task.getSignificance().shouldLog()) {
Logging.LOG.log(Level.FINE, "Task failed: " + task.getName(), e);
}
task.onDone().fireEvent(new TaskEvent(this, task, true));
taskListeners.forEach(it -> it.onFailed(task, e));
}
flag = true; task.setState(Task.TaskState.FAILED);
if (task.getSignificance().shouldLog()) { }
Logging.LOG.log(Level.FINER, "Task finished: " + task.getName());
}
task.onDone().fireEvent(new TaskEvent(this, task, false)); throw new CompletionException(resolved); // rethrow error
taskListeners.forEach(it -> it.onFinished(task)); });
} catch (InterruptedException e) {
task.setLastException(e);
if (task.getSignificance().shouldLog()) {
Logging.LOG.log(Level.FINE, "Task aborted: " + task.getName());
}
task.onDone().fireEvent(new TaskEvent(this, task, true));
taskListeners.forEach(it -> it.onFailed(task, e));
} catch (CancellationException | RejectedExecutionException e) {
if (task.getLastException() == null)
task.setLastException(e);
} catch (Exception e) {
task.setLastException(e);
lastException = e;
if (task.getSignificance().shouldLog()) {
Logging.LOG.log(Level.FINE, "Task failed: " + task.getName(), e);
}
task.onDone().fireEvent(new TaskEvent(this, task, true));
taskListeners.forEach(it -> it.onFailed(task, e));
}
task.setState(flag ? Task.TaskState.SUCCEEDED : Task.TaskState.FAILED);
return flag;
} }
public int getRunningTasks() { public int getRunningTasks() {
return totTask.get(); return totTask.get();
} }
private class Invoker implements ExceptionalRunnable<Exception> { private static Throwable resolveException(Throwable e) {
if (e instanceof ExecutionException || e instanceof CompletionException)
return resolveException(e.getCause());
else
return e;
}
private final Task task; private static void rethrow(Throwable e) {
private final CountDownLatch latch; if (e == null)
private final AtomicBoolean success; return;
if (e instanceof ExecutionException || e instanceof CompletionException) { // including UncheckedException and UncheckedThrowable
public Invoker(Task task, CountDownLatch latch, AtomicBoolean success) { rethrow(e.getCause());
this.task = task; } else if (e instanceof RuntimeException) {
this.latch = latch; throw (RuntimeException) e;
this.success = success; } else {
throw new CompletionException(e);
} }
}
@Override private static Runnable wrap(ExceptionalRunnable<?> runnable) {
public void run() { return () -> {
try { try {
Thread.currentThread().setName(task.getName()); runnable.run();
if (!executeTask(task)) } catch (Exception e) {
success.set(false); rethrow(e);
} finally {
latch.countDown();
} }
} };
} }
} }

View File

@@ -25,29 +25,57 @@ import java.util.EventListener;
*/ */
public abstract class TaskListener implements EventListener { public abstract class TaskListener implements EventListener {
/**
* Executed when a Task execution chain starts.
*/
public void onStart() { public void onStart() {
} }
public void onReady(Task task) { /**
* Executed before the task's pre-execution and dependents execution.
*
* TaskState of this task is READY.
*
* @param task the task that gets ready.
*/
public void onReady(Task<?> task) {
} }
public void onRunning(Task task) { /**
* Executed when the task's execution starts.
*
* TaskState of this task is RUNNING.
*
* @param task the task which is being run.
*/
public void onRunning(Task<?> task) {
} }
public void onFinished(Task task) { /**
* Executed after the task's dependencies and post-execution finished.
*
* TaskState of the task is EXECUTED.
*
* @param task the task which finishes its work.
*/
public void onFinished(Task<?> task) {
} }
public void onFailed(Task task, Throwable throwable) { /**
* Executed when an exception occurred during the task's execution.
*
* @param task the task which finishes its work.
*/
public void onFailed(Task<?> task, Throwable throwable) {
onFinished(task); onFinished(task);
} }
/**
* Executed when the task execution chain stopped.
*
* @param success true if no error occurred during task execution.
* @param executor the task executor with responsibility to the task execution.
*/
public void onStop(boolean success, TaskExecutor executor) { public void onStop(boolean success, TaskExecutor executor) {
} }
public static class DefaultTaskListener extends TaskListener {
private DefaultTaskListener() {
}
public static final DefaultTaskListener INSTANCE = new DefaultTaskListener();
}
} }

View File

@@ -1,247 +0,0 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.task;
import org.jackhuang.hmcl.util.function.ExceptionalConsumer;
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Consumer;
/**
* A task that has a result.
*
* @author huangyuhui
*/
public abstract class TaskResult<T> extends Task {
private T result;
private Consumer<T> resultConsumer;
@Override
public TaskResult<T> setName(String name) {
super.setName(name);
return this;
}
/**
* Returns the result of this task.
*
* The result will be generated only if the execution is completed.
*/
public T getResult() {
return result;
}
protected void setResult(T result) {
this.result = result;
if (resultConsumer != null)
resultConsumer.accept(result);
}
/**
* Sync the result of this task by given action.
*
* @param action the action to perform when result of this task changed
* @return this TaskResult
*/
public TaskResult<T> storeTo(Consumer<T> action) {
this.resultConsumer = action;
return this;
}
/**
* Returns a new TaskResult that, when this task completes
* normally, is executed with result of this task as the argument
* to the supplied function.
*
* @param fn the function returning a new TaskResult
* @param <U> the type of the returned TaskResult's result
* @return the TaskResult
*/
public <U, E extends Exception> TaskResult<U> thenCompose(ExceptionalFunction<T, TaskResult<U>, E> fn) {
return new TaskResult<U>() {
TaskResult<U> then;
@Override
public Collection<? extends Task> getDependents() {
return Collections.singleton(TaskResult.this);
}
@Override
public void execute() throws Exception {
then = fn.apply(TaskResult.this.getResult()).storeTo(this::setResult);
}
@Override
public Collection<? extends Task> getDependencies() {
return then == null ? Collections.emptyList() : Collections.singleton(then);
}
};
}
/**
* Returns a new Task that, when this task completes
* normally, is executed with this task as the argument
* to the supplied function.
*
* @param fn the function returning a new Task
* @return the Task
*/
public <E extends Exception> Task then(ExceptionalFunction<T, Task, E> fn) {
return new CoupleTask(this, () -> fn.apply(getResult()), true);
}
/**
* Returns a new TaskResult that, when this task completes
* normally, is executed using the default Scheduler, with this
* task's result as the argument to the supplied function.
*
* @param fn the function to use to compute the value of the returned TaskResult
* @param <U> the function's return type
* @return the new TaskResult
*/
public <U, E extends Exception> TaskResult<U> thenApply(ExceptionalFunction<T, U, E> fn) {
return thenApply(Schedulers.defaultScheduler(), fn);
}
/**
* Returns a new TaskResult that, when this task completes
* normally, is executed using the supplied Scheduler, with this
* task's result as the argument to the supplied function.
*
* @param scheduler the executor to use for asynchronous execution
* @param fn the function to use to compute the value of the returned TaskResult
* @param <U> the function's return type
* @return the new TaskResult
*/
public <U, E extends Exception> TaskResult<U> thenApply(Scheduler scheduler, ExceptionalFunction<T, U, E> fn) {
return thenApply(getCaller(), scheduler, fn);
}
/**
* Returns a new TaskResult that, when this task completes
* normally, is executed using the supplied Scheduler, with this
* task's result as the argument to the supplied function.
*
* @param name the name of this new TaskResult for displaying
* @param scheduler the executor to use for asynchronous execution
* @param fn the function to use to compute the value of the returned TaskResult
* @param <U> the function's return type
* @return the new TaskResult
*/
public <U, E extends Exception> TaskResult<U> thenApply(String name, Scheduler scheduler, ExceptionalFunction<T, U, E> fn) {
return new Subtask<>(name, scheduler, fn);
}
/**
* Returns a new Task that, when this task completes
* normally, is executed using the default Scheduler, with this
* task's result as the argument to the supplied action.
*
* @param action the action to perform before completing the
* returned Task
* @return the new Task
*/
public <E extends Exception> Task thenAccept(ExceptionalConsumer<T, E> action) {
return thenAccept(Schedulers.defaultScheduler(), action);
}
/**
* Returns a new Task that, when this task completes
* normally, is executed using the supplied Scheduler, with this
* task's result as the argument to the supplied action.
*
* @param action the action to perform before completing the returned Task
* @param scheduler the executor to use for asynchronous execution
* @return the new Task
*/
public <E extends Exception> Task thenAccept(Scheduler scheduler, ExceptionalConsumer<T, E> action) {
return new CoupleTask(this, () -> Task.of(scheduler, () -> action.accept(getResult())), true);
}
/**
* Returns a new Task with the same exception as this task, that executes
* the given actions when this task completes.
*
* <p>When this task is complete, the given success action is invoked with
* the result, the given failure action is invoked with the exception of
* this task. The returned task is completed when the action returns. If
* the supplied action itself encounters an exception, then the returned
* task exceptionally completes with this exception unless this task also
* completed exceptionally.
*
* @param success the action to perform when this task successfully completed
* @param failure the action to perform when this task exceptionally returned
* @return the new Task
*/
public <E1 extends Exception, E2 extends Exception> Task whenComplete(Scheduler scheduler, ExceptionalConsumer<T, E1> success, ExceptionalConsumer<Exception, E2> failure) {
return whenComplete(scheduler, () -> success.accept(getResult()), failure);
}
/**
* Returns a new Task with the same exception as this task, that executes
* the given action when this task completes.
*
* <p>When this task is complete, the given action is invoked with the
* result (or {@code null} if none), a boolean value represents the
* execution status of this task, and the exception (or {@code null}
* if none) of this task as arguments. The returned task is completed
* when the action returns. If the supplied action itself encounters an
* exception, then the returned task exceptionally completes with this
* exception unless this task also completed exceptionally.
*
* @param action the action to perform
* @return the new Task
*/
public Task whenComplete(Scheduler scheduler, FinalizedCallback<T> action) {
return whenComplete(scheduler, ((isDependentSucceeded, exception) -> action.execute(getResult(), isDependentSucceeded, exception)));
}
private class Subtask<R> extends TaskResult<R> {
private final Scheduler scheduler;
private final ExceptionalFunction<T, R, ?> callable;
public Subtask(String name, Scheduler scheduler, ExceptionalFunction<T, R, ?> callable) {
this.scheduler = scheduler;
this.callable = callable;
setName(name);
}
@Override
public Collection<? extends Task> getDependents() {
return Collections.singleton(TaskResult.this);
}
@Override
public Scheduler getScheduler() {
return scheduler;
}
@Override
public void execute() throws Exception {
setResult(callable.apply(TaskResult.this.getResult()));
}
}
public interface FinalizedCallback<V> {
void execute(V result, boolean isDependentSucceeded, Exception exception) throws Exception;
}
}

View File

@@ -1,77 +0,0 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
/**
* A map that support auto casting.
*
* @author huangyuhui
*/
public final class AutoTypingMap<K> {
private final Map<K, Object> impl;
public AutoTypingMap(Map<K, Object> impl) {
this.impl = impl;
}
/**
* Get the value associated with given {@code key} in the mapping.
*
* Be careful of the return type {@code <V>}, as you must ensure that {@code <V>} is correct
*
* @param key the key that the value associated with
* @param <V> the type of value which you must ensure type correction
* @return the value associated with given {@code key}
* @throws ClassCastException if the return type {@code <V>} is incorrect.
*/
@SuppressWarnings("unchecked")
public synchronized <V> V get(K key) throws ClassCastException {
return (V) impl.get(key);
}
public synchronized <V> Optional<V> getOptional(K key) {
return Optional.ofNullable(get(key));
}
public synchronized void set(K key, Object value) {
if (value != null)
impl.put(key, value);
}
public Collection<Object> values() {
return impl.values();
}
public Set<K> keys() {
return impl.keySet();
}
public boolean containsKey(K key) {
return impl.containsKey(key);
}
public Object remove(K key) {
return impl.remove(key);
}
}

View File

@@ -27,15 +27,11 @@ public interface ExceptionalRunnable<E extends Exception> {
void run() throws E; void run() throws E;
default Callable<?> toCallable() { default Callable<Void> toCallable() {
return () -> { return () -> {
run(); run();
return null; return null;
}; };
} }
static ExceptionalRunnable<?> fromRunnable(Runnable r) {
return r::run;
}
} }

View File

@@ -19,7 +19,6 @@ package org.jackhuang.hmcl.util.io;
import java.io.*; import java.io.*;
import java.net.*; import java.net.*;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;

View File

@@ -0,0 +1,107 @@
package org.jackhuang.hmcl.util;
import javafx.embed.swing.JFXPanel;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.util.platform.JavaVersion;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Test;
import java.util.concurrent.CancellationException;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
public class TaskTest {
/**
* TaskExecutor will not catch error and will be thrown to global handler.
*/
@Test
public void expectErrorUncaught() {
AtomicReference<Throwable> throwable = new AtomicReference<>();
Thread.setDefaultUncaughtExceptionHandler((t, e) -> throwable.set(e));
Assert.assertFalse(Task.composeAsync(() -> Task.allOf(
Task.allOf(Task.runAsync(() -> {
throw new Error();
}))
)).whenComplete(Assert::assertNull).test());
Assert.assertTrue("Error has not been thrown to uncaught exception handler", throwable.get() instanceof Error);
}
/**
*
*/
@Test
public void testWhenComplete() {
boolean result = Task.supplyAsync(() -> {
throw new IllegalStateException();
}).whenComplete(exception -> {
Assert.assertTrue(exception instanceof IllegalStateException);
}).test();
Assert.assertFalse("Task should fail at this case", result);
}
@Test
public void testWithCompose() {
AtomicBoolean bool = new AtomicBoolean();
boolean success = Task.supplyAsync(() -> {
throw new IllegalStateException();
}).withRun(() -> {
bool.set(true);
}).test();
Assert.assertTrue("Task should success because withRun will ignore previous exception", success);
Assert.assertTrue("withRun should be executed", bool.get());
}
@Test
public void testThenAccept() {
new JFXPanel(); // init JavaFX Toolkit
AtomicBoolean flag = new AtomicBoolean();
boolean result = Task.supplyAsync(JavaVersion::fromCurrentEnvironment)
.thenAccept(Schedulers.javafx(), javaVersion -> {
flag.set(true);
Assert.assertEquals(javaVersion, JavaVersion.fromCurrentEnvironment());
})
.test();
Assert.assertTrue("Task does not succeed", result);
Assert.assertTrue("ThenAccept has not been executed", flag.get());
}
@Test
public void testCancellation() {
Task<?> task = Task.runAsync(() -> Thread.sleep(200));
TaskExecutor executor = task.executor();
Lang.thread(() -> {
try {
Thread.sleep(100);
executor.cancel();
} catch (InterruptedException e) {
Assume.assumeNoException(e);
}
});
Assert.assertFalse("Task should fail because we have cancelled it", executor.test());
Assert.assertNull("CancellationException should not be recorded.", executor.getException());
Assert.assertNull("CancellationException should not be recorded.", task.getException());
}
@Test
public void testRejectedExecutionException() {
Schedulers.defaultScheduler();
Schedulers.shutdown();
Task<?> task = Task.runAsync(() -> {
Thread.sleep(1000);
});
boolean result = task.test();
Assert.assertFalse("Task should fail since ExecutorService is shut down and RejectedExecutionException should be thrown", result);
Assert.assertTrue("RejectedExecutionException should be recorded", task.getException() instanceof RejectedExecutionException);
}
}