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.
*/
public class HMCLModpackExportTask extends Task {
public class HMCLModpackExportTask extends Task<Void> {
private final DefaultGameRepository repository;
private final String version;
private final List<String> whitelist;

View File

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

View File

@@ -103,7 +103,7 @@ public final class LauncherHelper {
try {
checkGameState(profile, setting, version, () -> {
Controllers.dialog(launchingStepsPane);
Schedulers.newThread().schedule(this::launch0);
Schedulers.newThread().execute(this::launch0);
});
} catch (InterruptedException ignore) {
}
@@ -122,15 +122,15 @@ public final class LauncherHelper {
Version version = MaintainTask.maintain(repository.getResolvedVersion(selectedVersion));
Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version));
TaskExecutor executor = Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES))
.then(() -> {
TaskExecutor executor = Task.runAsync(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES))
.thenCompose(() -> {
if (setting.isNotCheckGame())
return null;
else
return dependencyManager.checkGameCompletionAsync(version);
})
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.MODS)))
.then(() -> {
.thenRun(Schedulers.javafx(), () -> emitStatus(LoadingState.MODS))
.thenCompose(() -> {
try {
ModpackConfiguration<?> configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion));
if ("Curse".equals(configuration.getType()))
@@ -141,8 +141,8 @@ public final class LauncherHelper {
return null;
}
})
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN)))
.thenCompose(() -> Task.ofResult(i18n("account.methods"), () -> {
.thenRun(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN))
.thenSupply(i18n("account.methods"), () -> {
try {
return account.logIn();
} catch (CredentialExpiredException e) {
@@ -152,7 +152,7 @@ public final class LauncherHelper {
LOG.warning("Authentication failed, try playing offline: " + e);
return account.playOffline().orElseThrow(() -> e);
}
}))
})
.thenApply(Schedulers.javafx(), authInfo -> {
emitStatus(LoadingState.LAUNCHING);
return authInfo;
@@ -164,7 +164,7 @@ public final class LauncherHelper {
setting.toLaunchOptions(profile.getGameDir()),
launcherVisibility == LauncherVisibility.CLOSE
? 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
if (scriptFile == null) {
@@ -200,7 +200,7 @@ public final class LauncherHelper {
final AtomicInteger finished = new AtomicInteger(0);
@Override
public void onFinished(Task task) {
public void onFinished(Task<?> task) {
finished.incrementAndGet();
int runningTasks = executor.getRunningTasks();
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.
if (!Controllers.isStopped()) {
launchingStepsPane.fireEvent(new DialogCloseEvent());
Exception ex = executor.getLastException();
Exception ex = executor.getException();
if (ex != null) {
String message;
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;
public LaunchTask(ExceptionalSupplier<T, Exception> supplier) {
@@ -440,7 +440,6 @@ public final class LauncherHelper {
*/
class HMCLProcessListener implements ProcessListener {
private final VersionSetting setting;
private final Map<String, String> forbiddenTokens;
private ManagedProcess process;
private boolean lwjgl;
@@ -449,8 +448,7 @@ public final class LauncherHelper {
private final LinkedList<Pair<String, Log4jLevel>> logs;
private final CountDownLatch latch = new CountDownLatch(1);
public HMCLProcessListener(AuthInfo authInfo, VersionSetting setting, boolean detectWindow) {
this.setting = setting;
public HMCLProcessListener(AuthInfo authInfo, boolean detectWindow) {
this.detectWindow = detectWindow;
if (authInfo == null)

View File

@@ -86,7 +86,7 @@ public final class ModpackHelper {
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);
ExceptionalRunnable<?> success = () -> {
@@ -117,11 +117,11 @@ public final class ModpackHelper {
else if (modpack.getManifest() instanceof MultiMCInstanceConfiguration)
return new MultiMCModpackInstallTask(profile.getDependency(), zipFile, modpack, ((MultiMCInstanceConfiguration) modpack.getManifest()), name)
.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);
}
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);
switch (configuration.getType()) {

View File

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

View File

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

View File

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

View File

@@ -397,7 +397,7 @@ public final class FXUtils {
*/
@SuppressWarnings("unchecked")
@Deprecated
public static void bindEnum(JFXComboBox<?> comboBox, Property<? extends Enum> property) {
public static void bindEnum(JFXComboBox<?> comboBox, Property<? extends Enum<?>> property) {
unbindEnum(comboBox);
ChangeListener<Number> listener = (a, b, newValue) ->
((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) {
File modpackFile = new File("modpack.zip").getAbsoluteFile();
if (modpackFile.exists()) {
Task.ofResult(() -> CompressingUtils.findSuitableEncoding(modpackFile.toPath()))
Task.supplyAsync(() -> CompressingUtils.findSuitableEncoding(modpackFile.toPath()))
.thenApply(encoding -> ModpackHelper.readModpackManifest(modpackFile.toPath(), encoding))
.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 -> {
Controllers.taskDialog(executor, i18n("modpack.installing"));
executor.start();

View File

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

View File

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

View File

@@ -197,7 +197,7 @@ public class AddAccountPane extends StackPane {
AccountFactory<?> factory = cboType.getSelectionModel().getSelectedItem();
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 -> {
int oldIndex = Accounts.getAccounts().indexOf(account);
if (oldIndex == -1) {

View File

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

View File

@@ -17,7 +17,6 @@
*/
package org.jackhuang.hmcl.ui.construct;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
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 {
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 totTasks = new ReadOnlyIntegerWrapper();
@@ -72,12 +72,12 @@ public final class TaskListPane extends StackPane {
}
@Override
public void onReady(Task task) {
public void onReady(Task<?> task) {
Platform.runLater(() -> totTasks.set(totTasks.getValue() + 1));
}
@Override
public void onRunning(Task task) {
public void onRunning(Task<?> task) {
if (!task.getSignificance().shouldShow())
return;
@@ -113,7 +113,7 @@ public final class TaskListPane extends StackPane {
}
@Override
public void onFinished(Task task) {
public void onFinished(Task<?> task) {
ProgressListNode node = nodes.remove(task);
if (node == null)
return;
@@ -125,7 +125,7 @@ public final class TaskListPane extends StackPane {
}
@Override
public void onFailed(Task task, Throwable throwable) {
public void onFailed(Task<?> task, Throwable throwable) {
ProgressListNode node = nodes.remove(task);
if (node == null)
return;
@@ -142,7 +142,7 @@ public final class TaskListPane extends StackPane {
private final Label title = new Label();
private final Label state = new Label();
public ProgressListNode(Task task) {
public ProgressListNode(Task<?> task) {
bar.progressProperty().bind(task.progressProperty());
title.setText(task.getName());
state.textProperty().bind(task.messageProperty());

View File

@@ -21,7 +21,6 @@ import com.jfoenix.controls.JFXButton;
import com.jfoenix.svg.SVGGlyph;
import javafx.beans.binding.Bindings;
import javafx.collections.ListChangeListener;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
@@ -56,7 +55,6 @@ public class DecoratorSkin extends SkinBase<Decorator> {
private double xOffset, yOffset, newX, newY, initX, initY;
private boolean allowMove, isDragging;
private BoundingBox originalBox, maximizedBox;
/**
* 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.task.DownloadException;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
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("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"))
ret = ret.thenCompose(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("forge")));
@@ -107,7 +106,7 @@ public final class InstallerWizardProvider implements WizardProvider {
if (settings.containsKey("optifine"))
ret = ret.thenCompose(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("optifine")));
return ret.then(profile.getRepository().refreshVersionsAsync());
return ret.thenCompose(profile.getRepository().refreshVersionsAsync());
}
@Override

View File

@@ -71,7 +71,7 @@ public class ModpackInstallWizardProvider implements WizardProvider {
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))
return null;
@@ -93,7 +93,7 @@ public class ModpackInstallWizardProvider implements WizardProvider {
return null;
} else {
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();
Task.ofResult(() -> CompressingUtils.findSuitableEncoding(selectedFile.toPath()))
Task.supplyAsync(() -> CompressingUtils.findSuitableEncoding(selectedFile.toPath()))
.thenApply(encoding -> manifest = ModpackHelper.readModpackManifest(selectedFile.toPath(), encoding))
.whenComplete(Schedulers.javafx(), manifest -> {
spinnerPane.hideSpinner();

View File

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

View File

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

View File

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

View File

@@ -26,11 +26,9 @@ import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Profiles;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.FileItem;
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 java.io.File;
import java.nio.file.Paths;
import java.util.Optional;
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());
newList.remove(library);
new MaintainTask(version.setLibraries(newList))
.then(maintainedVersion -> new VersionJsonSaveTask(profile.getRepository(), maintainedVersion))
.with(profile.getRepository().refreshVersionsAsync())
.with(Task.of(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId)))
.thenCompose(maintainedVersion -> new VersionJsonSaveTask(profile.getRepository(), maintainedVersion))
.withCompose(profile.getRepository().refreshVersionsAsync())
.withRun(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId))
.start();
};

View File

@@ -86,15 +86,15 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
private void loadMods(ModManager modManager) {
this.modManager = modManager;
Task.ofResult(() -> {
Task.supplyAsync(() -> {
synchronized (ModListPage.this) {
runInFX(() -> loadingProperty().set(true));
modManager.refreshMods();
return new LinkedList<>(modManager.getMods());
}
}).whenComplete(Schedulers.javafx(), (list, isDependentSucceeded, exception) -> {
}).whenComplete(Schedulers.javafx(), (list, exception) -> {
loadingProperty().set(false);
if (isDependentSucceeded)
if (exception == null)
FXUtils.onWeakChangeAndOperate(parentTab.getSelectionModel().selectedItemProperty(), newValue -> {
if (newValue != null && newValue.getUserData() == ModListPage.this)
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> failed = new LinkedList<>();
if (res == null) return;
Task.of(() -> {
Task.runAsync(() -> {
for (File file : res) {
try {
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.
}
}
}).with(Task.of(Schedulers.javafx(), () -> {
}).withRun(Schedulers.javafx(), () -> {
List<String> prompt = new LinkedList<>();
if (!succeeded.isEmpty())
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)));
Controllers.dialog(String.join("\n", prompt), i18n("mods.add"));
loadMods(modManager);
})).start();
}).start();
}
public void setParentTab(JFXTabPane parentTab) {

View File

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

View File

@@ -19,7 +19,6 @@ package org.jackhuang.hmcl.ui.versions;
import javafx.stage.FileChooser;
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.LauncherHelper;
import org.jackhuang.hmcl.game.Version;

View File

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

View File

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

View File

@@ -23,7 +23,6 @@ import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
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 {
@Override
default void handleTask(Map<String, Object> settings, Task task) {
default void handleTask(Map<String, Object> settings, Task<?> task) {
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(it -> {
it.fireEvent(new DialogCloseEvent());
onEnd();
@@ -70,11 +70,11 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
else if (!settings.containsKey("forbid_success_message"))
Controllers.dialog(i18n("message.success"), null, MessageType.FINE, () -> onEnd());
} else {
if (executor.getLastException() == null)
if (executor.getException() == null)
return;
String appendix = StringUtils.getStackTrace(executor.getLastException());
String appendix = StringUtils.getStackTrace(executor.getException());
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)
Controllers.dialog(appendix, (String) settings.get("failure_message"), MessageType.ERROR, () -> onEnd());
else if (!settings.containsKey("forbid_failure_message"))

View File

@@ -111,7 +111,7 @@ public class WizardController implements Navigation {
public void onFinish() {
Object result = provider.finish(settings);
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);
}

View File

@@ -27,5 +27,5 @@ public interface WizardDisplayer {
void onEnd();
void onCancel();
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.JsonParseException;
import javafx.application.Platform;
import javafx.scene.layout.Region;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.task.Task;
@@ -101,7 +101,7 @@ public final class UpdateHandler {
return;
}
Task task = new HMCLDownloadTask(version, downloaded);
Task<?> task = new HMCLDownloadTask(version, downloaded);
TaskExecutor executor = task.executor();
Controllers.taskDialog(executor, i18n("message.downloading"));
@@ -122,7 +122,7 @@ public final class UpdateHandler {
}
} else {
Throwable e = executor.getLastException();
Exception e = executor.getException();
LOG.log(Level.WARNING, "Failed to update to " + version, e);
Platform.runLater(() -> Controllers.dialog(e.toString(), i18n("update.failed"), MessageType.ERROR));
}

View File

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

View File

@@ -19,9 +19,7 @@ package org.jackhuang.hmcl.download;
import org.jackhuang.hmcl.download.game.*;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.ParallelTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import org.jackhuang.hmcl.util.gson.JsonUtils;
@@ -48,16 +46,16 @@ public class DefaultGameBuilder extends GameBuilder {
}
@Override
public Task buildAsync() {
public Task<?> buildAsync() {
return new VersionJsonDownloadTask(gameVersion, dependencyManager).thenCompose(rawJson -> {
Version original = JsonUtils.GSON.fromJson(rawJson, Version.class);
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 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"))
libraryTask = libraryTask.thenCompose(libraryTaskHelper(gameVersion, "forge"));
@@ -70,17 +68,17 @@ public class DefaultGameBuilder extends GameBuilder {
libraryTask = libraryTask.thenCompose(dependencyManager.installLibraryAsync(remoteVersion));
return libraryTask;
}).whenComplete((isDependentSucceeded, exception) -> {
if (!isDependentSucceeded)
}).whenComplete(exception -> {
if (exception != null)
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));
}
protected Task downloadGameAsync(String gameVersion, Version version) {
protected Task<Void> downloadGameAsync(String gameVersion, Version version) {
return new GameDownloadTask(dependencyManager, gameVersion, version);
}

View File

@@ -46,7 +46,7 @@ public interface DependencyManager {
*
* @return the task to check game completion.
*/
Task checkGameCompletionAsync(Version version);
Task<?> checkGameCompletionAsync(Version version);
/**
* Check if the game is complete.
@@ -54,7 +54,7 @@ public interface DependencyManager {
*
* @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.
@@ -71,7 +71,7 @@ public interface DependencyManager {
* @param libraryVersion the version of being installed 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.
@@ -81,7 +81,7 @@ public interface DependencyManager {
* @param libraryVersion the remote version of being installed 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.

View File

@@ -72,5 +72,5 @@ public abstract class GameBuilder {
/**
* @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;
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.*;
public class MaintainTask extends TaskResult<Version> {
public class MaintainTask extends Task<Version> {
private final Version version;

View File

@@ -18,7 +18,6 @@
package org.jackhuang.hmcl.download;
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.liteloader.LiteLoaderVersionList;
import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList;

View File

@@ -62,19 +62,19 @@ public abstract class VersionList<T extends RemoteVersion> {
* @param downloadProvider DownloadProvider
* @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 downloadProvider DownloadProvider
* @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);
}
public Task loadAsync(DownloadProvider downloadProvider) {
return Task.ofThen(() -> {
public Task<?> loadAsync(DownloadProvider downloadProvider) {
return Task.composeAsync(() -> {
lock.readLock().lock();
boolean loaded;
@@ -87,8 +87,8 @@ public abstract class VersionList<T extends RemoteVersion> {
});
}
public Task loadAsync(String gameVersion, DownloadProvider downloadProvider) {
return Task.ofThen(() -> {
public Task<?> loadAsync(String gameVersion, DownloadProvider downloadProvider) {
return Task.composeAsync(() -> {
lock.readLock().lock();
boolean loaded;

View File

@@ -46,21 +46,21 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
}
@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.");
}
@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.");
}
@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));
return new Task() {
return new Task<Void>() {
@Override
public Collection<Task> getDependents() {
public Collection<Task<?>> getDependents() {
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.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
@@ -43,14 +42,14 @@ import java.util.Optional;
*
* @author huangyuhui
*/
public final class ForgeInstallTask extends TaskResult<Version> {
public final class ForgeInstallTask extends Task<Version> {
private final DefaultDependencyManager dependencyManager;
private final Version version;
private Path installer;
private final ForgeRemoteVersion remote;
private Task dependent;
private TaskResult<Version> dependency;
private Task<Void> dependent;
private Task<Version> dependency;
public ForgeInstallTask(DefaultDependencyManager dependencyManager, Version version, ForgeRemoteVersion remoteVersion) {
this.dependencyManager = dependencyManager;
@@ -83,12 +82,12 @@ public final class ForgeInstallTask extends TaskResult<Version> {
}
@Override
public Collection<Task> getDependents() {
public Collection<Task<?>> getDependents() {
return Collections.singleton(dependent);
}
@Override
public Collection<Task> getDependencies() {
public Collection<Task<?>> getDependencies() {
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 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));
if (!gameVersion.isPresent()) throw new IOException();
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.game.*;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
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.Logging.LOG;
public class ForgeNewInstallTask extends TaskResult<Version> {
public class ForgeNewInstallTask extends Task<Version> {
private final DefaultDependencyManager dependencyManager;
private final DefaultGameRepository gameRepository;
private final Version version;
private final Path installer;
private final List<Task> dependents = new LinkedList<>();
private final List<Task> dependencies = new LinkedList<>();
private final List<Task<?>> dependents = new LinkedList<>();
private final List<Task<?>> dependencies = new LinkedList<>();
private ForgeNewInstallProfile profile;
private Version forgeVersion;
public ForgeNewInstallTask(DefaultDependencyManager dependencyManager, Version version, Path installer) {
ForgeNewInstallTask(DefaultDependencyManager dependencyManager, Version version, Path installer) {
this.dependencyManager = dependencyManager;
this.gameRepository = dependencyManager.getGameRepository();
this.version = version;
@@ -83,12 +82,12 @@ public class ForgeNewInstallTask extends TaskResult<Version> {
}
@Override
public Collection<Task> getDependents() {
public Collection<Task<?>> getDependents() {
return dependents;
}
@Override
public List<Task> getDependencies() {
public Collection<Task<?>> getDependencies() {
return dependencies;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -55,11 +55,11 @@ public final class GameVersionList extends VersionList<GameRemoteVersion> {
}
@Override
public Task refreshAsync(DownloadProvider downloadProvider) {
public Task<?> refreshAsync(DownloadProvider downloadProvider) {
GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.getVersionListURL()));
return new Task() {
return new Task<Void>() {
@Override
public Collection<Task> getDependents() {
public Collection<Task<?>> getDependents() {
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.Library;
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.gson.JsonUtils;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
@@ -31,7 +31,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
public class LibrariesUniqueTask extends TaskResult<Version> {
public class LibrariesUniqueTask extends Task<Version> {
private final 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.Hex.encodeHex;
public class LibraryDownloadTask extends Task {
public class LibraryDownloadTask extends Task<Void> {
private FileDownloadTask task;
protected final File jar;
protected final DefaultCacheRepository cacheRepository;
@@ -74,7 +74,7 @@ public class LibraryDownloadTask extends Task {
}
@Override
public Collection<? extends Task> getDependents() {
public Collection<Task<?>> getDependents() {
if (cached) return Collections.emptyList();
else return Collections.singleton(task);
}
@@ -91,7 +91,7 @@ public class LibraryDownloadTask extends Task {
if (!isDependentsSucceeded()) {
// Since FileDownloadTask wraps the actual exception with DownloadException.
// We should extract it letting the error message clearer.
Throwable t = task.getLastException();
Exception t = task.getException();
if (t instanceof DownloadException)
throw new LibraryDownloadException(library, t.getCause());
else

View File

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

View File

@@ -33,7 +33,6 @@ import javax.xml.parsers.DocumentBuilderFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
/**
*
@@ -52,11 +51,11 @@ public final class LiteLoaderBMCLVersionList extends VersionList<LiteLoaderRemot
}
@Override
public Task refreshAsync(DownloadProvider downloadProvider) {
public Task<?> refreshAsync(DownloadProvider downloadProvider) {
GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL(LITELOADER_LIST)));
return new Task() {
return new Task<Void>() {
@Override
public Collection<Task> getDependents() {
public Collection<Task<?>> getDependents() {
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.Version;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.Lang;
import java.util.Collection;
@@ -36,13 +35,13 @@ import java.util.List;
*
* @author huangyuhui
*/
public final class LiteLoaderInstallTask extends TaskResult<Version> {
public final class LiteLoaderInstallTask extends Task<Version> {
private final DefaultDependencyManager dependencyManager;
private final Version version;
private final LiteLoaderRemoteVersion remote;
private final List<Task> dependents = new LinkedList<>();
private final List<Task> dependencies = new LinkedList<>();
private final List<Task<?>> dependents = new LinkedList<>();
private final List<Task<?>> dependencies = new LinkedList<>();
public LiteLoaderInstallTask(DefaultDependencyManager dependencyManager, Version version, LiteLoaderRemoteVersion remoteVersion) {
this.dependencyManager = dependencyManager;
@@ -51,12 +50,12 @@ public final class LiteLoaderInstallTask extends TaskResult<Version> {
}
@Override
public Collection<Task> getDependents() {
public Collection<Task<?>> getDependents() {
return dependents;
}
@Override
public Collection<Task> getDependencies() {
public Collection<Task<?>> getDependencies() {
return dependencies;
}

View File

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

View File

@@ -46,11 +46,11 @@ public final class OptiFineBMCLVersionList extends VersionList<OptiFineRemoteVer
}
@Override
public Task refreshAsync(DownloadProvider downloadProvider) {
public Task<?> refreshAsync(DownloadProvider downloadProvider) {
GetTask task = new GetTask(NetworkUtils.toURL("http://bmclapi2.bangbang93.com/optifine/versionlist"));
return new Task() {
return new Task<Void>() {
@Override
public Collection<Task> getDependents() {
public Collection<Task<?>> getDependents() {
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.game.*;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
@@ -44,14 +43,14 @@ import static org.jackhuang.hmcl.util.Lang.getOrDefault;
*
* @author huangyuhui
*/
public final class OptiFineInstallTask extends TaskResult<Version> {
public final class OptiFineInstallTask extends Task<Version> {
private final DefaultDependencyManager dependencyManager;
private final Version version;
private final OptiFineRemoteVersion remote;
private final Path installer;
private final List<Task> dependents = new LinkedList<>();
private final List<Task> dependencies = new LinkedList<>();
private final List<Task<?>> dependents = new LinkedList<>();
private final List<Task<?>> dependencies = new LinkedList<>();
public OptiFineInstallTask(DefaultDependencyManager dependencyManager, Version version, OptiFineRemoteVersion remoteVersion) {
this(dependencyManager, version, remoteVersion, null);
@@ -65,12 +64,12 @@ public final class OptiFineInstallTask extends TaskResult<Version> {
}
@Override
public Collection<Task> getDependents() {
public Collection<Task<?>> getDependents() {
return dependents;
}
@Override
public Collection<Task> getDependencies() {
public Collection<Task<?>> getDependencies() {
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 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);
Optional<String> gameVersion = GameVersion.minecraftVersion(jar);
if (!gameVersion.isPresent()) throw new IOException();

View File

@@ -77,8 +77,8 @@ public interface GameRepository extends VersionProvider {
*/
void refreshVersions();
default Task refreshVersionsAsync() {
return Task.of(this::refreshVersions);
default Task<Void> refreshVersionsAsync() {
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.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.stream.Collectors;

View File

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

View File

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

View File

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

View File

@@ -46,15 +46,15 @@ import java.util.Optional;
*
* @author huangyuhui
*/
public final class MultiMCModpackInstallTask extends Task {
public final class MultiMCModpackInstallTask extends Task<Void> {
private final File zipFile;
private final Modpack modpack;
private final MultiMCInstanceConfiguration manifest;
private final String name;
private final DefaultGameRepository repository;
private final List<Task> dependencies = new LinkedList<>();
private final List<Task> dependents = new LinkedList<>();
private final List<Task<?>> dependencies = new LinkedList<>();
private final List<Task<?>> dependents = new LinkedList<>();
public MultiMCModpackInstallTask(DefaultDependencyManager dependencyManager, File zipFile, Modpack modpack, MultiMCInstanceConfiguration manifest, String name) {
this.zipFile = zipFile;
@@ -91,7 +91,7 @@ public final class MultiMCModpackInstallTask extends Task {
}
@Override
public List<Task> getDependencies() {
public List<Task<?>> getDependencies() {
return dependencies;
}
@@ -126,7 +126,7 @@ public final class MultiMCModpackInstallTask extends Task {
}
@Override
public List<Task> getDependents() {
public List<Task<?>> getDependents() {
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.security.MessageDigest;
import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.logging.Level;
import static java.util.Objects.requireNonNull;
@@ -44,7 +45,7 @@ import static org.jackhuang.hmcl.util.DigestUtils.getDigest;
*
* @author huangyuhui
*/
public class FileDownloadTask extends Task {
public class FileDownloadTask extends Task<Void> {
public static class IntegrityCheck {
private String algorithm;
@@ -121,6 +122,7 @@ public class FileDownloadTask extends Task {
this.retry = retry;
setName(file.getName());
setExecutor(Schedulers.io());
}
private void closeFiles() {
@@ -142,11 +144,6 @@ public class FileDownloadTask extends Task {
stream = null;
}
@Override
public Scheduler getScheduler() {
return Schedulers.io();
}
public EventManager<FailedEvent<URL>> getOnFailed() {
return onFailed;
}

View File

@@ -30,46 +30,37 @@ import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.concurrent.Executor;
import java.util.logging.Level;
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 Charset charset;
private final int retry;
private final String id;
private CacheRepository repository = CacheRepository.getInstance();
public GetTask(URL url) {
this(url, ID);
this(url, UTF_8);
}
public GetTask(URL url, String id) {
this(url, id, UTF_8);
public GetTask(URL url, Charset charset) {
this(url, charset, 5);
}
public GetTask(URL url, String id, Charset charset) {
this(url, id, charset, 5);
}
public GetTask(URL url, String id, Charset charset, int retry) {
public GetTask(URL url, Charset charset, int retry) {
this.url = url;
this.charset = charset;
this.retry = retry;
this.id = id;
setName(url.toString());
}
@Override
public Scheduler getScheduler() {
return Schedulers.io();
setExecutor(Schedulers.io());
}
public GetTask setCacheRepository(CacheRepository repository) {
@@ -138,9 +129,4 @@ public final class GetTask extends TaskResult<String> {
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;
import javafx.application.Platform;
import org.jackhuang.hmcl.util.Logging;
import javax.swing.*;
import java.util.concurrent.*;
/**
@@ -32,7 +34,7 @@ public final class Schedulers {
private static volatile ExecutorService CACHED_EXECUTOR;
private static synchronized ExecutorService getCachedExecutorService() {
public static synchronized Executor newThread() {
if (CACHED_EXECUTOR == null)
CACHED_EXECUTOR = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60, TimeUnit.SECONDS, new SynchronousQueue<>(), Executors.defaultThreadFactory());
@@ -42,7 +44,7 @@ public final class Schedulers {
private static volatile ExecutorService IO_EXECUTOR;
private static synchronized ExecutorService getIOExecutorService() {
public static synchronized Executor io() {
if (IO_EXECUTOR == null)
IO_EXECUTOR = Executors.newFixedThreadPool(6, runnable -> {
Thread thread = Executors.defaultThreadFactory().newThread(runnable);
@@ -55,7 +57,7 @@ public final class Schedulers {
private static volatile ExecutorService SINGLE_EXECUTOR;
private static synchronized ExecutorService getSingleExecutorService() {
public static synchronized Executor computation() {
if (SINGLE_EXECUTOR == null)
SINGLE_EXECUTOR = Executors.newSingleThreadExecutor(runnable -> {
Thread thread = Executors.defaultThreadFactory().newThread(runnable);
@@ -66,54 +68,18 @@ public final class Schedulers {
return SINGLE_EXECUTOR;
}
private static final Scheduler IMMEDIATE = new SchedulerImpl(Runnable::run);
public static Scheduler immediate() {
return IMMEDIATE;
public static Executor javafx() {
return Platform::runLater;
}
private static Scheduler NEW_THREAD;
public static synchronized Scheduler newThread() {
if (NEW_THREAD == null)
NEW_THREAD = new SchedulerExecutorService(getCachedExecutorService());
return NEW_THREAD;
public static Executor swing() {
return SwingUtilities::invokeLater;
}
private static Scheduler IO;
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() {
public static Executor defaultScheduler() {
return newThread();
}
static final Scheduler NONE = new SchedulerImpl(any -> {});
public static synchronized void shutdown() {
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.ReflectionHelper;
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.ExceptionalSupplier;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.logging.Level;
/**
@@ -41,50 +45,65 @@ import java.util.logging.Level;
*
* @author huangyuhui
*/
public abstract class Task {
public abstract class Task<T> {
private final EventManager<TaskEvent> onDone = new EventManager<>();
private TaskSignificance significance = TaskSignificance.MAJOR;
/**
* True if not logging when executing this task.
*/
private TaskSignificance significance = TaskSignificance.MAJOR;
public final TaskSignificance getSignificance() {
return significance;
}
public void setSignificance(TaskSignificance significance) {
public final void setSignificance(TaskSignificance significance) {
this.significance = significance;
}
// state
private TaskState state = TaskState.READY;
public TaskState getState() {
public final TaskState getState() {
return state;
}
void setState(TaskState state) {
final void setState(TaskState state) {
this.state = state;
}
private Exception lastException;
public Exception getLastException() {
return lastException;
}
void setLastException(Exception e) {
lastException = e;
}
// last exception
private Exception exception;
/**
* 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() {
return Schedulers.defaultScheduler();
public final Exception getException() {
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;
public boolean isDependentsSucceeded() {
@@ -95,6 +114,7 @@ public abstract class Task {
dependentsSucceeded = true;
}
// dependencies succeeded
private boolean dependenciesSucceeded = false;
public boolean isDependenciesSucceeded() {
@@ -123,17 +143,57 @@ public abstract class Task {
return true;
}
// name
private String name = getClass().getName();
public String getName() {
return name;
}
public Task setName(String name) {
public Task<T> setName(String name) {
this.name = name;
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() {
return false;
}
@@ -171,7 +231,7 @@ public abstract class Task {
/**
* The collection of sub-tasks that should execute **before** this task running.
*/
public Collection<? extends Task> getDependents() {
public Collection<Task<?>> getDependents() {
return Collections.emptySet();
}
@@ -179,7 +239,7 @@ public abstract class Task {
* The collection of sub-tasks that should execute **after** this task running.
* Will not be executed if execution fails.
*/
public Collection<? extends Task> getDependencies() {
public Collection<Task<?>> getDependencies() {
return Collections.emptySet();
}
@@ -232,15 +292,15 @@ public abstract class Task {
if (getSignificance().shouldLog())
Logging.LOG.log(Level.FINE, "Executing task: " + getName());
for (Task task : getDependents())
for (Task<?> task : getDependents())
doSubTask(task);
execute();
for (Task task : getDependencies())
for (Task<?> task : getDependencies())
doSubTask(task);
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);
progress.bind(task.progress);
task.run();
@@ -273,61 +333,240 @@ public abstract class Task {
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
* normally, is executed using the default Scheduler.
* 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 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
* @return the new TaskResult
* @return the new Task
*/
public final <U> TaskResult<U> thenSupply(Callable<U> fn) {
return thenCompose(() -> Task.ofResult(fn));
public <U, E extends Exception> Task<U> thenApply(ExceptionalFunction<T, U, E> 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.
*
* @param fn the function returning a new TaskResult
* @param <U> the type of the returned TaskResult's result
* @return the TaskResult
* @param other the another Task
* @param <U> the type of the returned Task's result
* @return the Task
*/
public final <U> TaskResult<U> thenCompose(ExceptionalSupplier<TaskResult<U>, ?> fn) {
return new TaskResult<U>() {
TaskResult<U> then;
@Override
public Collection<? extends Task> getDependents() {
return Collections.singleton(Task.this);
}
@Override
public void execute() throws Exception {
then = fn.get().storeTo(this::setResult);
}
@Override
public Collection<? extends Task> getDependencies() {
return then == null ? Collections.emptyList() : Collections.singleton(then);
}
};
public final <U> Task<U> thenCompose(Task<U> other) {
return thenCompose(() -> other);
}
/**
* Returns a new Task that, when this task completes
* normally, is executed.
*
* @param fn the function returning a new Task
* @param <U> the type of the returned Task's result
* @return the Task
*/
public final <U> Task<U> thenCompose(ExceptionalSupplier<Task<U>, ?> fn) {
return new UniCompose<>(fn, true);
}
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) {
return new CoupleTask(this, b, false);
public final <U> Task<U> withCompose(Task<U> other) {
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
* @return the new Task
*/
public final Task whenComplete(FinalizedCallback action) {
public final Task<Void> whenComplete(FinalizedCallback action) {
return whenComplete(Schedulers.defaultScheduler(), action);
}
@@ -361,35 +600,33 @@ public abstract class Task {
* with this exception unless this task also completed exceptionally.
*
* @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
*/
public final Task whenComplete(Scheduler scheduler, FinalizedCallback action) {
return new Task() {
public final Task<Void> whenComplete(Executor executor, FinalizedCallback action) {
return new Task<Void>() {
{
setSignificance(TaskSignificance.MODERATE);
}
@Override
public Scheduler getScheduler() {
return scheduler;
}
@Override
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()) {
setSignificance(TaskSignificance.MINOR);
if (Task.this.getLastException() == null)
if (Task.this.getException() == null)
throw new CancellationException();
else
throw Task.this.getLastException();
throw Task.this.getException();
}
}
@Override
public Collection<Task> getDependents() {
public Collection<Task<?>> getDependents() {
return Collections.singleton(Task.this);
}
@@ -397,7 +634,26 @@ public abstract class Task {
public boolean isRelyingOnDependents() {
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
* @return the new Task
*/
public final <E1 extends Exception, E2 extends Exception> Task whenComplete(Scheduler scheduler, ExceptionalRunnable<E1> success, ExceptionalConsumer<Exception, E2> failure) {
return whenComplete(scheduler, (isDependentSucceeded, exception) -> {
if (isDependentSucceeded) {
public final <E1 extends Exception, E2 extends Exception> Task<Void> whenComplete(Executor executor, ExceptionalRunnable<E1> success, ExceptionalConsumer<Exception, E2> failure) {
return whenComplete(executor, exception -> {
if (exception == null) {
if (success != null)
try {
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) {
return of(name, Schedulers.defaultScheduler(), closure);
public static Task<Void> runAsync(ExceptionalRunnable<?> closure) {
return runAsync(Schedulers.defaultScheduler(), closure);
}
public static Task of(Scheduler scheduler, ExceptionalRunnable<?> closure) {
return of(getCaller(), scheduler, closure);
public static Task<Void> runAsync(String name, ExceptionalRunnable<?> closure) {
return runAsync(name, Schedulers.defaultScheduler(), closure);
}
public static Task of(String name, Scheduler scheduler, ExceptionalRunnable<?> closure) {
return new SimpleTask(name, closure, scheduler);
public static Task<Void> runAsync(Executor executor, ExceptionalRunnable<?> closure) {
return runAsync(getCaller(), executor, closure);
}
public static Task ofThen(ExceptionalSupplier<Task, ?> b) {
return new CoupleTask(null, b, true);
public static Task<Void> runAsync(String name, Executor executor, ExceptionalRunnable<?> closure) {
return new SimpleTask<>(closure.toCallable()).setExecutor(executor).setName(name);
}
public static <V> TaskResult<V> ofResult(Callable<V> callable) {
return ofResult(getCaller(), callable);
}
public static <T> Task<T> composeAsync(ExceptionalSupplier<Task<T>, ?> fn) {
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
public Task get() {
return t;
public void execute() throws Exception {
then = fn.get();
if (then != null)
then.storeTo(this::setResult);
}
@Override
public String toString() {
return t.getName();
public Collection<Task<?>> getDependencies() {
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 {
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();
}
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 {
private final Task task;
private final Task<?> task;
private final boolean failed;
public TaskEvent(Object source, Task task, boolean failed) {
public TaskEvent(Object source, Task<?> task, boolean failed) {
super(source);
this.task = task;
this.failed = failed;
}
public Task getTask() {
public Task<?> getTask() {
return task;
}

View File

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

View File

@@ -25,29 +25,57 @@ import java.util.EventListener;
*/
public abstract class TaskListener implements EventListener {
/**
* Executed when a Task execution chain starts.
*/
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);
}
/**
* 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 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;
default Callable<?> toCallable() {
default Callable<Void> toCallable() {
return () -> {
run();
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.net.*;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
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);
}
}