add: more brief launching steps

This commit is contained in:
huanghongxun
2020-02-19 13:34:40 +08:00
parent a144a5eee0
commit 1ea9170b2e
14 changed files with 310 additions and 210 deletions

View File

@@ -20,11 +20,7 @@ package org.jackhuang.hmcl.game;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.auth.*;
import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.CharacterDeletedException;
import org.jackhuang.hmcl.auth.CredentialExpiredException;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloadException; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloadException;
import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.MaintainTask; import org.jackhuang.hmcl.download.MaintainTask;
@@ -51,11 +47,7 @@ import org.jackhuang.hmcl.ui.LogWindow;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent; import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType; import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane; import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
import org.jackhuang.hmcl.util.Log4jLevel; import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter; import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
import org.jackhuang.hmcl.util.io.ResponseCodeException; import org.jackhuang.hmcl.util.io.ResponseCodeException;
import org.jackhuang.hmcl.util.platform.CommandBuilder; import org.jackhuang.hmcl.util.platform.CommandBuilder;
@@ -68,16 +60,9 @@ import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.Collections; import java.util.*;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import static org.jackhuang.hmcl.setting.ConfigHolder.config; import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Lang.mapOf;
@@ -102,9 +87,11 @@ public final class LauncherHelper {
this.setting = profile.getVersionSetting(selectedVersion); this.setting = profile.getVersionSetting(selectedVersion);
this.launcherVisibility = setting.getLauncherVisibility(); this.launcherVisibility = setting.getLauncherVisibility();
this.showLogs = setting.isShowLogs(); this.showLogs = setting.isShowLogs();
this.launchingStepsPane.setTitle(i18n("version.launch"));
} }
private final TaskExecutorDialogPane launchingStepsPane = new TaskExecutorDialogPane(it -> {}); private final TaskExecutorDialogPane launchingStepsPane = new TaskExecutorDialogPane(it -> {});
private CountDownLatch launchingLatch;
public void setTestMode() { public void setTestMode() {
launcherVisibility = LauncherVisibility.KEEP; launcherVisibility = LauncherVisibility.KEEP;
@@ -140,15 +127,15 @@ public final class LauncherHelper {
Version version = MaintainTask.maintain(repository, repository.getResolvedVersion(selectedVersion)); Version version = MaintainTask.maintain(repository, repository.getResolvedVersion(selectedVersion));
Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version)); Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version));
TaskExecutor executor = Task.runAsync(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES)) TaskExecutor executor = Task.runAsync(() -> {
.thenComposeAsync(() -> { })
.thenComposeAsync(() -> Task.composeAsync(() -> {
if (setting.isNotCheckGame()) if (setting.isNotCheckGame())
return null; return null;
else else
return dependencyManager.checkGameCompletionAsync(version); return dependencyManager.checkGameCompletionAsync(version);
}) }).withStage("launch.state.dependencies"))
.thenRunAsync(Schedulers.javafx(), () -> emitStatus(LoadingState.MODS)) .thenComposeAsync(() -> Task.composeAsync(null, () -> {
.thenComposeAsync(() -> {
try { try {
ModpackConfiguration<?> configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion)); ModpackConfiguration<?> configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion));
if ("Curse".equals(configuration.getType())) if ("Curse".equals(configuration.getType()))
@@ -160,9 +147,8 @@ public final class LauncherHelper {
} catch (IOException e) { } catch (IOException e) {
return null; return null;
} }
}) }).withStage("launch.state.modpack"))
.thenRunAsync(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN)) .thenComposeAsync(() -> Task.supplyAsync((String) null, () -> {
.thenSupplyAsync(i18n("launch.state.logging_in"), () -> {
try { try {
return account.logIn(); return account.logIn();
} catch (CredentialExpiredException e) { } catch (CredentialExpiredException e) {
@@ -172,31 +158,27 @@ public final class LauncherHelper {
LOG.warning("Authentication failed, try playing offline: " + e); LOG.warning("Authentication failed, try playing offline: " + e);
return account.playOffline().orElseThrow(() -> e); return account.playOffline().orElseThrow(() -> e);
} }
}) }).withStage("launch.state.logging_in"))
.thenApplyAsync(Schedulers.javafx(), authInfo -> { .thenComposeAsync(authInfo -> Task.supplyAsync((String) null, () -> {
emitStatus(LoadingState.LAUNCHING); return new HMCLGameLauncher(
return authInfo; repository,
}) version.getPatches().isEmpty() ? repository.getResolvedVersion(selectedVersion) : version,
.thenApplyAsync(authInfo -> new HMCLGameLauncher( authInfo,
repository, setting.toLaunchOptions(profile.getGameDir(), !setting.isNotCheckJVM()),
version.getPatches().isEmpty() ? repository.getResolvedVersion(selectedVersion) : version, launcherVisibility == LauncherVisibility.CLOSE
authInfo, ? null // Unnecessary to start listening to game process output when close launcher immediately after game launched.
setting.toLaunchOptions(profile.getGameDir(), !setting.isNotCheckJVM()), : new HMCLProcessListener(authInfo, gameVersion.isPresent())
launcherVisibility == LauncherVisibility.CLOSE );
? null // Unnecessary to start listening to game process output when close launcher immediately after game launched. }).thenComposeAsync(launcher -> { // launcher is prev task's result
: new HMCLProcessListener(authInfo, gameVersion.isPresent())
))
.thenComposeAsync(launcher -> { // launcher is prev task's result
if (scriptFile == null) { if (scriptFile == null) {
return new LaunchTask<>(launcher::launch).setName(i18n("version.launch")); return Task.supplyAsync((String) null, launcher::launch);
} else { } else {
return new LaunchTask<ManagedProcess>(() -> { return Task.supplyAsync((String) null, () -> {
launcher.makeLaunchScript(scriptFile); launcher.makeLaunchScript(scriptFile);
return null; return null;
}).setName(i18n("version.launch_script")); });
} }
}) }).thenAcceptAsync(process -> { // process is LaunchTask's result
.thenAcceptAsync(process -> { // process is LaunchTask's result
if (scriptFile == null) { if (scriptFile == null) {
PROCESSES.add(process); PROCESSES.add(process);
if (launcherVisibility == LauncherVisibility.CLOSE) if (launcherVisibility == LauncherVisibility.CLOSE)
@@ -212,19 +194,18 @@ public final class LauncherHelper {
Controllers.dialog(i18n("version.launch_script.success", scriptFile.getAbsolutePath())); Controllers.dialog(i18n("version.launch_script.success", scriptFile.getAbsolutePath()));
}); });
} }
}) }).thenRunAsync(null, Schedulers.defaultScheduler(), () -> {
launchingLatch = new CountDownLatch(1);
launchingLatch.await();
}).withStage("launch.state.waiting_launching"))
.cancellableExecutor(); .cancellableExecutor();
launchingStepsPane.setExecutor(executor, false); launchingStepsPane.setExecutor(executor, Lang.immutableListOf(
"launch.state.dependencies",
"launch.state.modpack",
"launch.state.logging_in",
"launch.state.waiting_launching"), false);
executor.addTaskListener(new TaskListener() { executor.addTaskListener(new TaskListener() {
final AtomicInteger finished = new AtomicInteger(0);
@Override
public void onFinished(Task<?> task) {
finished.incrementAndGet();
int runningTasks = executor.getRunningTasks();
Platform.runLater(() -> launchingStepsPane.setProgress(1.0 * finished.get() / runningTasks));
}
@Override @Override
public void onStop(boolean success, TaskExecutor executor) { public void onStop(boolean success, TaskExecutor executor) {
@@ -432,15 +413,6 @@ public final class LauncherHelper {
onAccept.run(); onAccept.run();
} }
public void emitStatus(LoadingState state) {
if (state == LoadingState.DONE) {
launchingStepsPane.fireEvent(new DialogCloseEvent());
}
launchingStepsPane.setTitle(state.getLocalizedMessage());
launchingStepsPane.setSubtitle((state.ordinal() + 1) + " / " + LoadingState.values().length);
}
private void checkExit() { private void checkExit() {
switch (launcherVisibility) { switch (launcherVisibility) {
case HIDE_AND_REOPEN: case HIDE_AND_REOPEN:
@@ -465,19 +437,6 @@ public final class LauncherHelper {
} }
} }
private static class LaunchTask<T> extends Task<T> {
private final ExceptionalSupplier<T, Exception> supplier;
public LaunchTask(ExceptionalSupplier<T, Exception> supplier) {
this.supplier = supplier;
}
@Override
public void execute() throws Exception {
setResult(supplier.get());
}
}
/** /**
* The managed process listener. * The managed process listener.
* Guarantee that one [JavaProcess], one [HMCLProcessListener]. * Guarantee that one [JavaProcess], one [HMCLProcessListener].
@@ -534,7 +493,7 @@ public final class LauncherHelper {
// these codes will be executed. // these codes will be executed.
if (Controllers.getStage() != null) { if (Controllers.getStage() != null) {
Controllers.getStage().hide(); Controllers.getStage().hide();
emitStatus(LoadingState.DONE); launchingLatch.countDown();
} }
}); });
break; break;
@@ -543,7 +502,7 @@ public final class LauncherHelper {
break; break;
case KEEP: case KEEP:
Platform.runLater(() -> { Platform.runLater(() -> {
emitStatus(LoadingState.DONE); launchingLatch.countDown();
}); });
break; break;
case HIDE: case HIDE:
@@ -552,7 +511,7 @@ public final class LauncherHelper {
// these codes will be executed. // these codes will be executed.
if (Controllers.getStage() != null) { if (Controllers.getStage() != null) {
Controllers.getStage().close(); Controllers.getStage().close();
emitStatus(LoadingState.DONE); launchingLatch.countDown();
} }
}); });
break; break;

View File

@@ -248,18 +248,13 @@ public final class Controllers {
return pane; return pane;
} }
public static Region taskDialog(TaskExecutor executor, String title) { public static TaskExecutorDialogPane taskDialog(TaskExecutor executor, String title) {
return taskDialog(executor, title, ""); return taskDialog(executor, title, null);
} }
public static Region taskDialog(TaskExecutor executor, String title, String subtitle) { public static TaskExecutorDialogPane taskDialog(TaskExecutor executor, String title, Consumer<Region> onCancel) {
return taskDialog(executor, title, subtitle, null);
}
public static Region taskDialog(TaskExecutor executor, String title, String subtitle, Consumer<Region> onCancel) {
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(onCancel); TaskExecutorDialogPane pane = new TaskExecutorDialogPane(onCancel);
pane.setTitle(title); pane.setTitle(title);
pane.setSubtitle(subtitle);
pane.setExecutor(executor); pane.setExecutor(executor);
dialog(pane); dialog(pane);
return pane; return pane;

View File

@@ -71,6 +71,10 @@ public final class SVG {
return createSVGPath("M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z", fill, width, height); return createSVGPath("M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z", fill, width, height);
} }
public static Node dotsHorizontal(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M16,12A2,2 0 0,1 18,10A2,2 0 0,1 20,12A2,2 0 0,1 18,14A2,2 0 0,1 16,12M10,12A2,2 0 0,1 12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12M4,12A2,2 0 0,1 6,10A2,2 0 0,1 8,12A2,2 0 0,1 6,14A2,2 0 0,1 4,12Z", fill, width, height);
}
public static Node delete(ObjectBinding<? extends Paint> fill, double width, double height) { public static Node delete(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z", fill, width, height); return createSVGPath("M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z", fill, width, height);
} }
@@ -170,4 +174,8 @@ public final class SVG {
public static Node check(ObjectBinding<? extends Paint> fill, double width, double height) { public static Node check(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z", fill, width, height); return createSVGPath("M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z", fill, width, height);
} }
public static Node arrowRight(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z", fill, width, height);
}
} }

View File

@@ -53,22 +53,37 @@ public class AdvancedListBox extends ScrollPane {
return this; return this;
} }
public AdvancedListBox remove(Node child) { public AdvancedListBox add(int index, Node child) {
if (child instanceof Pane) if (child instanceof Pane || child instanceof AdvancedListItem)
container.getChildren().remove(child); container.getChildren().add(index, child);
else { else {
StackPane pane = null; StackPane pane = new StackPane();
for (Node node : container.getChildren()) pane.getStyleClass().add("advanced-list-box-item");
pane.getChildren().setAll(child);
container.getChildren().add(index, pane);
}
return this;
}
public AdvancedListBox remove(Node child) {
container.getChildren().remove(indexOf(child));
return this;
}
public int indexOf(Node child) {
if (child instanceof Pane) {
return container.getChildren().indexOf(child);
} else {
for (int i = 0; i < container.getChildren().size(); ++i) {
Node node = container.getChildren().get(i);
if (node instanceof StackPane) { if (node instanceof StackPane) {
ObservableList<Node> list = ((StackPane) node).getChildren(); ObservableList<Node> list = ((StackPane) node).getChildren();
if (list.size() == 1 && list.get(0) == child) if (list.size() == 1 && list.get(0) == child)
pane = (StackPane) node; return i;
} }
if (pane == null) }
throw new Error(); return -1;
container.getChildren().remove(pane);
} }
return this;
} }
public AdvancedListBox startCategory(String category) { public AdvancedListBox startCategory(String category) {

View File

@@ -18,18 +18,19 @@
package org.jackhuang.hmcl.ui.construct; package org.jackhuang.hmcl.ui.construct;
import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXProgressBar;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.TaskExecutor; import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.task.TaskListener; import org.jackhuang.hmcl.task.TaskListener;
import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.FXUtils;
import java.util.Collections;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -38,14 +39,11 @@ import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
public class TaskExecutorDialogPane extends StackPane { public class TaskExecutorDialogPane extends StackPane {
private TaskExecutor executor; private TaskExecutor executor;
private Consumer<Region> onCancel; private Consumer<Region> onCancel;
private final Consumer<FileDownloadTask.SpeedEvent> speedEventHandler;
@FXML
private JFXProgressBar progressBar;
@FXML @FXML
private Label lblTitle; private Label lblTitle;
@FXML @FXML
private Label lblSubtitle;
@FXML
private Label lblProgress; private Label lblProgress;
@FXML @FXML
private JFXButton btnCancel; private JFXButton btnCancel;
@@ -62,10 +60,24 @@ public class TaskExecutorDialogPane extends StackPane {
onCancel.accept(this); onCancel.accept(this);
}); });
lblProgress.textProperty().bind(Bindings.createStringBinding( speedEventHandler = speedEvent -> {
() -> taskListPane.finishedTasksProperty().get() + "/" + taskListPane.totTasksProperty().get(), String unit = "B/s";
taskListPane.finishedTasksProperty(), taskListPane.totTasksProperty() double speed = speedEvent.getSpeed();
)); if (speed > 1024) {
speed /= 1024;
unit = "KB/s";
}
if (speed > 1024) {
speed /= 1024;
unit = "MB/s";
}
double finalSpeed = speed;
String finalUnit = unit;
Platform.runLater(() -> {
lblProgress.setText(String.format("%.1f", finalSpeed) + " " + finalUnit);
});
};
FileDownloadTask.speedEvent.channel(FileDownloadTask.SpeedEvent.class).registerWeak(speedEventHandler);
} }
public void setExecutor(TaskExecutor executor) { public void setExecutor(TaskExecutor executor) {
@@ -73,10 +85,18 @@ public class TaskExecutorDialogPane extends StackPane {
} }
public void setExecutor(TaskExecutor executor, boolean autoClose) { public void setExecutor(TaskExecutor executor, boolean autoClose) {
setExecutor(executor, Collections.emptyList(), autoClose);
}
public void setExecutor(TaskExecutor executor, List<String> stages) {
setExecutor(executor, stages, true);
}
public void setExecutor(TaskExecutor executor, List<String> stages, boolean autoClose) {
this.executor = executor; this.executor = executor;
if (executor != null) { if (executor != null) {
taskListPane.setExecutor(executor); taskListPane.setExecutor(executor, stages);
if (autoClose) if (autoClose)
executor.addTaskListener(new TaskListener() { executor.addTaskListener(new TaskListener() {
@@ -100,25 +120,6 @@ public class TaskExecutorDialogPane extends StackPane {
lblTitle.setText(currentState); lblTitle.setText(currentState);
} }
public StringProperty subtitleProperty() {
return lblSubtitle.textProperty();
}
public String getSubtitle() {
return lblSubtitle.getText();
}
public void setSubtitle(String subtitle) {
lblSubtitle.setText(subtitle);
}
public void setProgress(double progress) {
if (progress == Double.MAX_VALUE)
progressBar.setVisible(false);
else
progressBar.setProgress(progress);
}
public void setCancel(Consumer<Region> onCancel) { public void setCancel(Consumer<Region> onCancel) {
this.onCancel = onCancel; this.onCancel = onCancel;

View File

@@ -21,6 +21,8 @@ import com.jfoenix.controls.JFXProgressBar;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.property.ReadOnlyIntegerProperty; import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper; import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane; import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
@@ -37,20 +39,22 @@ import org.jackhuang.hmcl.mod.ModpackUpdateTask;
import org.jackhuang.hmcl.mod.curse.CurseCompletionTask; import org.jackhuang.hmcl.mod.curse.CurseCompletionTask;
import org.jackhuang.hmcl.mod.curse.CurseInstallTask; import org.jackhuang.hmcl.mod.curse.CurseInstallTask;
import org.jackhuang.hmcl.mod.multimc.MultiMCModpackInstallTask; import org.jackhuang.hmcl.mod.multimc.MultiMCModpackInstallTask;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor; import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.task.TaskListener; import org.jackhuang.hmcl.task.TaskListener;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import java.util.HashMap; import java.util.*;
import java.util.Map; import java.util.stream.Collectors;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class TaskListPane extends StackPane { public final class TaskListPane extends StackPane {
private final AdvancedListBox listBox = new AdvancedListBox(); private final AdvancedListBox listBox = new AdvancedListBox();
private final Map<Task<?>, ProgressListNode> nodes = new HashMap<>(); private final Map<Task<?>, ProgressListNode> nodes = new HashMap<>();
private final ReadOnlyIntegerWrapper finishedTasks = new ReadOnlyIntegerWrapper(); private final List<StageNode> stageNodes = new ArrayList<>();
private final ReadOnlyIntegerWrapper totTasks = new ReadOnlyIntegerWrapper();
public TaskListPane() { public TaskListPane() {
listBox.setSpacing(0); listBox.setSpacing(0);
@@ -58,33 +62,35 @@ public final class TaskListPane extends StackPane {
getChildren().setAll(listBox); getChildren().setAll(listBox);
} }
public ReadOnlyIntegerProperty finishedTasksProperty() {
return finishedTasks.getReadOnlyProperty();
}
public ReadOnlyIntegerProperty totTasksProperty() {
return totTasks.getReadOnlyProperty();
}
public void setExecutor(TaskExecutor executor) { public void setExecutor(TaskExecutor executor) {
setExecutor(executor, Collections.emptyList());
}
public void setExecutor(TaskExecutor executor, List<String> stages) {
executor.addTaskListener(new TaskListener() { executor.addTaskListener(new TaskListener() {
@Override @Override
public void onStart() { public void onStart() {
Platform.runLater(() -> { Platform.runLater(() -> {
stageNodes.clear();
listBox.clear(); listBox.clear();
finishedTasks.set(0); stageNodes.addAll(stages.stream().map(StageNode::new).collect(Collectors.toList()));
totTasks.set(0); stageNodes.forEach(listBox::add);
}); });
} }
@Override @Override
public void onReady(Task<?> task) { public void onReady(Task<?> task) {
Platform.runLater(() -> totTasks.set(totTasks.getValue() + 1)); if (task instanceof Task.StageTask) {
Platform.runLater(() -> {
stageNodes.stream().filter(x -> x.stage.equals(task.getStage())).findAny().ifPresent(StageNode::begin);
});
}
} }
@Override @Override
public void onRunning(Task<?> task) { public void onRunning(Task<?> task) {
if (!task.getSignificance().shouldShow()) if (!task.getSignificance().shouldShow() || task.getName() == null)
return; return;
if (task instanceof GameAssetDownloadTask) { if (task instanceof GameAssetDownloadTask) {
@@ -117,34 +123,77 @@ public final class TaskListPane extends StackPane {
ProgressListNode node = new ProgressListNode(task); ProgressListNode node = new ProgressListNode(task);
nodes.put(task, node); nodes.put(task, node);
Platform.runLater(() -> listBox.add(node)); Platform.runLater(() -> {
StageNode stageNode = stageNodes.stream().filter(x -> x.stage.equals(task.getStage())).findAny().orElse(null);
listBox.add(listBox.indexOf(stageNode) + 1, node);
});
} }
@Override @Override
public void onFinished(Task<?> task) { public void onFinished(Task<?> task) {
if (task instanceof Task.StageTask) {
Platform.runLater(() -> {
stageNodes.stream().filter(x -> x.stage.equals(task.getStage())).findAny().ifPresent(StageNode::succeed);
});
}
ProgressListNode node = nodes.remove(task); ProgressListNode node = nodes.remove(task);
if (node == null) if (node == null)
return; return;
node.unbind(); node.unbind();
Platform.runLater(() -> { Platform.runLater(() -> {
listBox.remove(node); listBox.remove(node);
finishedTasks.set(finishedTasks.getValue() + 1);
}); });
} }
@Override @Override
public void onFailed(Task<?> task, Throwable throwable) { public void onFailed(Task<?> task, Throwable throwable) {
if (task instanceof Task.StageTask) {
Platform.runLater(() -> {
stageNodes.stream().filter(x -> x.stage.equals(task.getStage())).findAny().ifPresent(StageNode::fail);
});
}
ProgressListNode node = nodes.remove(task); ProgressListNode node = nodes.remove(task);
if (node == null) if (node == null)
return; return;
Platform.runLater(() -> { Platform.runLater(() -> {
node.setThrowable(throwable); node.setThrowable(throwable);
finishedTasks.set(finishedTasks.getValue() + 1);
}); });
} }
}); });
} }
private static class StageNode extends BorderPane {
private final String stage;
private final Label title = new Label();
private boolean started = false;
public StageNode(String stage) {
this.stage = stage;
title.setText(i18n(stage));
BorderPane.setAlignment(title, Pos.CENTER_LEFT);
BorderPane.setMargin(title, new Insets(0, 0, 0, 8));
setPadding(new Insets(0, 0, 8, 4));
setCenter(title);
setLeft(SVG.dotsHorizontal(Theme.blackFillBinding(), 14, 14));
}
public void begin() {
if (started) return;
started = true;
setLeft(FXUtils.limitingSize(SVG.arrowRight(Theme.blackFillBinding(), 14, 14), 14, 14));
}
public void fail() {
setLeft(FXUtils.limitingSize(SVG.close(Theme.blackFillBinding(), 14, 14), 14, 14));
}
public void succeed() {
setLeft(FXUtils.limitingSize(SVG.check(Theme.blackFillBinding(), 14, 14), 14, 14));
}
}
private static class ProgressListNode extends BorderPane { private static class ProgressListNode extends BorderPane {
private final JFXProgressBar bar = new JFXProgressBar(); private final JFXProgressBar bar = new JFXProgressBar();
private final Label title = new Label(); private final Label title = new Label();

View File

@@ -42,7 +42,6 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
}); });
pane.setTitle(i18n("message.doing")); pane.setTitle(i18n("message.doing"));
pane.setProgress(Double.MAX_VALUE);
if (settings.containsKey("title")) { if (settings.containsKey("title")) {
Object title = settings.get("title"); Object title = settings.get("title");
if (title instanceof StringProperty) if (title instanceof StringProperty)
@@ -51,14 +50,6 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
pane.setTitle((String) title); pane.setTitle((String) title);
} }
if (settings.containsKey("subtitle")) {
Object subtitle = settings.get("subtitle");
if (subtitle instanceof StringProperty)
pane.subtitleProperty().bind((StringProperty) subtitle);
else if (subtitle instanceof String)
pane.setSubtitle((String) subtitle);
}
runInFX(() -> { runInFX(() -> {
TaskExecutor executor = task.cancellableExecutor(new TaskListener() { TaskExecutor executor = task.cancellableExecutor(new TaskListener() {
@Override @Override

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.JFXButton?> <?import com.jfoenix.controls.JFXButton?>
<?import com.jfoenix.controls.JFXProgressBar?>
<?import javafx.scene.control.Label?> <?import javafx.scene.control.Label?>
<?import javafx.scene.control.ScrollPane?> <?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
@@ -11,13 +10,9 @@
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
type="StackPane" FXUtils.limitWidth="500" FXUtils.limitHeight="300"> type="StackPane" FXUtils.limitWidth="500" FXUtils.limitHeight="300">
<BorderPane> <BorderPane>
<top>
<JFXProgressBar fx:id="progressBar" StackPane.alignment="TOP_CENTER" />
</top>
<center> <center>
<VBox alignment="TOP_CENTER" style="-fx-padding: 16px;"> <VBox style="-fx-padding: 16px;">
<Label fx:id="lblTitle" style="-fx-font-size: 20px;" /> <Label fx:id="lblTitle" style="-fx-font-size: 14px; -fx-font-weight: BOLD;" />
<Label fx:id="lblSubtitle" style="-fx-font-size: 14px;" />
<ScrollPane fitToHeight="true" fitToWidth="true" VBox.vgrow="ALWAYS"> <ScrollPane fitToHeight="true" fitToWidth="true" VBox.vgrow="ALWAYS">
<TaskListPane fx:id="taskListPane" /> <TaskListPane fx:id="taskListPane" />

View File

@@ -179,10 +179,10 @@ launch.failed.decompressing_natives=無法解壓縮遊戲資源庫。
launch.failed.download_library=無法下載遊戲相依元件 %s。 launch.failed.download_library=無法下載遊戲相依元件 %s。
launch.failed.executable_permission=無法為啟動檔案新增執行權限。 launch.failed.executable_permission=無法為啟動檔案新增執行權限。
launch.failed.exited_abnormally=遊戲非正常退出,請查看記錄檔案,或聯絡他人尋求幫助。 launch.failed.exited_abnormally=遊戲非正常退出,請查看記錄檔案,或聯絡他人尋求幫助。
launch.state.dependencies=正在處理遊戲相依元件 launch.state.dependencies=處理遊戲相依元件
launch.state.done=啟動完成 launch.state.done=啟動完成
launch.state.logging_in=登入 launch.state.logging_in=登入
launch.state.modpack=正在下載必要檔案 launch.state.modpack=下載必要檔案
launch.state.waiting_launching=等待遊戲啟動 launch.state.waiting_launching=等待遊戲啟動
launch.wrong_javadir=Java 路徑錯誤,將自動重設為預設 Java 路徑。 launch.wrong_javadir=Java 路徑錯誤,將自動重設為預設 Java 路徑。

View File

@@ -180,10 +180,10 @@ launch.failed.decompressing_natives=未能解压游戏本地库。
launch.failed.download_library=未能下载游戏依赖 %s. launch.failed.download_library=未能下载游戏依赖 %s.
launch.failed.executable_permission=未能为启动文件添加执行权限。 launch.failed.executable_permission=未能为启动文件添加执行权限。
launch.failed.exited_abnormally=游戏非正常退出,请查看日志文件,或联系他人寻求帮助。 launch.failed.exited_abnormally=游戏非正常退出,请查看日志文件,或联系他人寻求帮助。
launch.state.dependencies=正在处理游戏依赖 launch.state.dependencies=处理游戏依赖
launch.state.done=启动完成 launch.state.done=启动完成
launch.state.logging_in=登录 launch.state.logging_in=登录
launch.state.modpack=正在下载必要文件 launch.state.modpack=下载必要文件
launch.state.waiting_launching=等待游戏启动 launch.state.waiting_launching=等待游戏启动
launch.wrong_javadir=错误的 Java 路径,将自动重置为默认 Java 路径。 launch.wrong_javadir=错误的 Java 路径,将自动重置为默认 Java 路径。

View File

@@ -24,7 +24,10 @@ import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.concurrent.*; import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level; import java.util.logging.Level;
/** /**
@@ -42,7 +45,7 @@ public final class AsyncTaskExecutor extends TaskExecutor {
@Override @Override
public TaskExecutor start() { public TaskExecutor start() {
taskListeners.forEach(TaskListener::onStart); taskListeners.forEach(TaskListener::onStart);
future = executeTasks(Collections.singleton(firstTask)) future = executeTasks(null, Collections.singleton(firstTask))
.thenApplyAsync(exception -> { .thenApplyAsync(exception -> {
boolean success = exception == null; boolean success = exception == null;
@@ -95,7 +98,7 @@ public final class AsyncTaskExecutor extends TaskExecutor {
future.cancel(true); future.cancel(true);
} }
private CompletableFuture<Exception> executeTasks(Collection<Task<?>> tasks) { private CompletableFuture<Exception> executeTasks(Task<?> parentTask, Collection<Task<?>> tasks) {
if (tasks == null || tasks.isEmpty()) if (tasks == null || tasks.isEmpty())
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
@@ -105,7 +108,7 @@ public final class AsyncTaskExecutor extends TaskExecutor {
return CompletableFuture.allOf(tasks.stream() return CompletableFuture.allOf(tasks.stream()
.map(task -> CompletableFuture.completedFuture(null) .map(task -> CompletableFuture.completedFuture(null)
.thenComposeAsync(unused2 -> executeTask(task)) .thenComposeAsync(unused2 -> executeTask(parentTask, task))
).toArray(CompletableFuture<?>[]::new)); ).toArray(CompletableFuture<?>[]::new));
}) })
.thenApplyAsync(unused -> (Exception) null) .thenApplyAsync(unused -> (Exception) null)
@@ -120,11 +123,13 @@ public final class AsyncTaskExecutor extends TaskExecutor {
}); });
} }
private CompletableFuture<?> executeTask(Task<?> task) { private CompletableFuture<?> executeTask(Task<?> parentTask, Task<?> task) {
return CompletableFuture.completedFuture(null) return CompletableFuture.completedFuture(null)
.thenComposeAsync(unused -> { .thenComposeAsync(unused -> {
task.setCancelled(this::isCancelled); task.setCancelled(this::isCancelled);
task.setState(Task.TaskState.READY); task.setState(Task.TaskState.READY);
if (parentTask != null && task.getStage() == null)
task.setStage(parentTask.getStage());
if (task.getSignificance().shouldLog()) if (task.getSignificance().shouldLog())
Logging.LOG.log(Level.FINE, "Executing task: " + task.getName()); Logging.LOG.log(Level.FINE, "Executing task: " + task.getName());
@@ -137,7 +142,7 @@ public final class AsyncTaskExecutor extends TaskExecutor {
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }
}) })
.thenComposeAsync(unused -> executeTasks(task.getDependents())) .thenComposeAsync(unused -> executeTasks(task, task.getDependents()))
.thenComposeAsync(dependentsException -> { .thenComposeAsync(dependentsException -> {
boolean isDependentsSucceeded = dependentsException == null; boolean isDependentsSucceeded = dependentsException == null;
@@ -158,7 +163,7 @@ public final class AsyncTaskExecutor extends TaskExecutor {
rethrow(throwable); rethrow(throwable);
}); });
}) })
.thenComposeAsync(unused -> executeTasks(task.getDependencies())) .thenComposeAsync(unused -> executeTasks(task, task.getDependencies()))
.thenComposeAsync(dependenciesException -> { .thenComposeAsync(dependenciesException -> {
boolean isDependenciesSucceeded = dependenciesException == null; boolean isDependenciesSucceeded = dependenciesException == null;

View File

@@ -23,14 +23,7 @@ import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.CancellationException; import java.util.concurrent.*;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level; import java.util.logging.Level;
@@ -47,7 +40,7 @@ public class CancellableTaskExecutor extends TaskExecutor {
public TaskExecutor start() { public TaskExecutor start() {
taskListeners.forEach(TaskListener::onStart); taskListeners.forEach(TaskListener::onStart);
workerQueue.add(Schedulers.schedule(scheduler, wrap(() -> { workerQueue.add(Schedulers.schedule(scheduler, wrap(() -> {
boolean flag = executeTasks(Collections.singleton(firstTask)); boolean flag = executeTasks(null, Collections.singleton(firstTask));
taskListeners.forEach(it -> it.onStop(flag, this)); taskListeners.forEach(it -> it.onStop(flag, this));
}))); })));
return this; return this;
@@ -58,7 +51,7 @@ public class CancellableTaskExecutor extends TaskExecutor {
taskListeners.forEach(TaskListener::onStart); taskListeners.forEach(TaskListener::onStart);
AtomicBoolean flag = new AtomicBoolean(true); AtomicBoolean flag = new AtomicBoolean(true);
Future<?> future = Schedulers.schedule(scheduler, wrap(() -> { Future<?> future = Schedulers.schedule(scheduler, wrap(() -> {
flag.set(executeTasks(Collections.singleton(firstTask))); flag.set(executeTasks(null, Collections.singleton(firstTask)));
taskListeners.forEach(it -> it.onStop(flag.get(), this)); taskListeners.forEach(it -> it.onStop(flag.get(), this));
})); }));
workerQueue.add(future); workerQueue.add(future);
@@ -82,7 +75,7 @@ public class CancellableTaskExecutor extends TaskExecutor {
} }
} }
private boolean executeTasks(Collection<? extends Task<?>> tasks) throws InterruptedException { private boolean executeTasks(Task<?> parentTask, Collection<? extends Task<?>> tasks) throws InterruptedException {
if (tasks.isEmpty()) if (tasks.isEmpty())
return true; return true;
@@ -92,7 +85,7 @@ public class CancellableTaskExecutor extends TaskExecutor {
for (Task<?> task : tasks) { for (Task<?> task : tasks) {
if (cancelled.get()) if (cancelled.get())
return false; return false;
Invoker invoker = new Invoker(task, latch, success); Invoker invoker = new Invoker(parentTask, task, latch, success);
try { try {
Future<?> future = Schedulers.schedule(scheduler, invoker); Future<?> future = Schedulers.schedule(scheduler, invoker);
workerQueue.add(future); workerQueue.add(future);
@@ -112,7 +105,7 @@ public class CancellableTaskExecutor extends TaskExecutor {
return success.get() && !cancelled.get(); return success.get() && !cancelled.get();
} }
private boolean executeTask(Task<?> task) { private boolean executeTask(Task<?> parentTask, Task<?> task) {
task.setCancelled(this::isCancelled); task.setCancelled(this::isCancelled);
if (cancelled.get()) { if (cancelled.get()) {
@@ -122,6 +115,8 @@ public class CancellableTaskExecutor extends TaskExecutor {
} }
task.setState(Task.TaskState.READY); task.setState(Task.TaskState.READY);
if (parentTask != null && task.getStage() == null)
task.setStage(parentTask.getStage());
if (task.getSignificance().shouldLog()) if (task.getSignificance().shouldLog())
Logging.LOG.log(Level.FINE, "Executing task: " + task.getName()); Logging.LOG.log(Level.FINE, "Executing task: " + task.getName());
@@ -140,8 +135,12 @@ public class CancellableTaskExecutor extends TaskExecutor {
} }
Collection<? extends Task<?>> dependents = task.getDependents(); Collection<? extends Task<?>> dependents = task.getDependents();
boolean doDependentsSucceeded = executeTasks(dependents); boolean doDependentsSucceeded = executeTasks(task, dependents);
Exception dependentsException = dependents.stream().map(Task::getException).filter(Objects::nonNull).findAny().orElse(null); Exception dependentsException = dependents.stream().map(Task::getException)
.filter(Objects::nonNull)
.filter(x -> !(x instanceof CancellationException))
.filter(x -> !(x instanceof InterruptedException))
.findAny().orElse(null);
if (!doDependentsSucceeded && task.isRelyingOnDependents() || cancelled.get()) { if (!doDependentsSucceeded && task.isRelyingOnDependents() || cancelled.get()) {
task.setException(dependentsException); task.setException(dependentsException);
throw new CancellationException(); throw new CancellationException();
@@ -163,8 +162,12 @@ public class CancellableTaskExecutor extends TaskExecutor {
} }
Collection<? extends Task<?>> dependencies = task.getDependencies(); Collection<? extends Task<?>> dependencies = task.getDependencies();
boolean doDependenciesSucceeded = executeTasks(dependencies); boolean doDependenciesSucceeded = executeTasks(task, dependencies);
Exception dependenciesException = dependencies.stream().map(Task::getException).filter(Objects::nonNull).findAny().orElse(null); Exception dependenciesException = dependencies.stream().map(Task::getException)
.filter(Objects::nonNull)
.filter(x -> !(x instanceof CancellationException))
.filter(x -> !(x instanceof InterruptedException))
.findAny().orElse(null);
if (doDependenciesSucceeded) if (doDependenciesSucceeded)
task.setDependenciesSucceeded(); task.setDependenciesSucceeded();
@@ -250,11 +253,13 @@ public class CancellableTaskExecutor extends TaskExecutor {
private class Invoker implements Runnable { private class Invoker implements Runnable {
private final Task<?> parentTask;
private final Task<?> task; private final Task<?> task;
private final CountDownLatch latch; private final CountDownLatch latch;
private final AtomicBoolean success; private final AtomicBoolean success;
public Invoker(Task<?> task, CountDownLatch latch, AtomicBoolean success) { public Invoker(Task<?> parentTask, Task<?> task, CountDownLatch latch, AtomicBoolean success) {
this.parentTask = parentTask;
this.task = task; this.task = task;
this.latch = latch; this.latch = latch;
this.success = success; this.success = success;
@@ -264,7 +269,7 @@ public class CancellableTaskExecutor extends TaskExecutor {
public void run() { public void run() {
try { try {
Thread.currentThread().setName(task.getName()); Thread.currentThread().setName(task.getName());
if (!executeTask(task)) if (!executeTask(parentTask, task))
success.set(false); success.set(false);
} finally { } finally {
latch.countDown(); latch.countDown();

View File

@@ -17,13 +17,11 @@
*/ */
package org.jackhuang.hmcl.task; package org.jackhuang.hmcl.task;
import org.jackhuang.hmcl.event.Event;
import org.jackhuang.hmcl.event.EventBus;
import org.jackhuang.hmcl.util.CacheRepository; import org.jackhuang.hmcl.util.CacheRepository;
import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.io.ChecksumMismatchException; import org.jackhuang.hmcl.util.io.*;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.IOUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jackhuang.hmcl.util.io.ResponseCodeException;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -35,10 +33,8 @@ import java.net.URL;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.util.ArrayList; import java.util.*;
import java.util.Collections; import java.util.concurrent.atomic.AtomicInteger;
import java.util.List;
import java.util.Optional;
import java.util.logging.Level; import java.util.logging.Level;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
@@ -279,7 +275,7 @@ public class FileDownloadTask extends Task<Void> {
updateProgress(downloaded, contentLength); updateProgress(downloaded, contentLength);
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
if (now - lastTime >= 1000) { if (now - lastTime >= 1000) {
updateMessage((downloaded - lastDownloaded) / 1024 + "KB/s"); updateDownloadSpeed(downloaded - lastDownloaded);
lastDownloaded = downloaded; lastDownloaded = downloaded;
lastTime = now; lastTime = now;
} }
@@ -337,4 +333,39 @@ public class FileDownloadTask extends Task<Void> {
throw new DownloadException(urls.get(0), exception); throw new DownloadException(urls.get(0), exception);
} }
private static final Timer timer = new Timer("DownloadSpeedRecorder", true);
private static final AtomicInteger downloadSpeed = new AtomicInteger(0);
public static final EventBus speedEvent = new EventBus();
static {
timer.schedule(new TimerTask() {
@Override
public void run() {
speedEvent.fireEvent(new SpeedEvent(speedEvent, downloadSpeed.getAndSet(0)));
}
}, 0, 1000);
}
private static void updateDownloadSpeed(int speed) {
downloadSpeed.addAndGet(speed);
}
public static class SpeedEvent extends Event {
private final int speed;
public SpeedEvent(Object source, int speed) {
super(source);
this.speed = speed;
}
/**
* Download speed in byte/sec.
* @return
*/
public int getSpeed() {
return speed;
}
}
} }

View File

@@ -79,6 +79,25 @@ public abstract class Task<T> {
return cancelled != null ? cancelled.get() : false; return cancelled != null ? cancelled.get() : false;
} }
// stage
private String stage = null;
/**
* Stage of task implies the goal of this task, for grouping tasks.
* Stage will inherit from the parent task.
*/
public String getStage() {
return stage;
}
/**
* You must initialize stage in preExecute.
* @param stage the stage
*/
public void setStage(String stage) {
this.stage = stage;
}
// state // state
private TaskState state = TaskState.READY; private TaskState state = TaskState.READY;
@@ -344,18 +363,18 @@ public abstract class Task<T> {
} }
public final TaskExecutor cancellableExecutor() { public final TaskExecutor cancellableExecutor() {
return new CancellableTaskExecutor(this); return new AsyncTaskExecutor(this);
} }
public final TaskExecutor cancellableExecutor(boolean start) { public final TaskExecutor cancellableExecutor(boolean start) {
TaskExecutor executor = new CancellableTaskExecutor(this); TaskExecutor executor = new AsyncTaskExecutor(this);
if (start) if (start)
executor.start(); executor.start();
return executor; return executor;
} }
public final TaskExecutor cancellableExecutor(TaskListener taskListener) { public final TaskExecutor cancellableExecutor(TaskListener taskListener) {
TaskExecutor executor = new CancellableTaskExecutor(this); TaskExecutor executor = new AsyncTaskExecutor(this);
executor.addTaskListener(taskListener); executor.addTaskListener(taskListener);
return executor; return executor;
} }
@@ -743,6 +762,12 @@ public abstract class Task<T> {
return whenComplete(executor, () -> success.accept(getResult()), failure); return whenComplete(executor, () -> success.accept(getResult()), failure);
} }
public Task<T> withStage(String stage) {
StageTask<T> task = new StageTask<>(this);
task.setStage(stage);
return task;
}
public static Task<Void> runAsync(ExceptionalRunnable<?> closure) { public static Task<Void> runAsync(ExceptionalRunnable<?> closure) {
return runAsync(Schedulers.defaultScheduler(), closure); return runAsync(Schedulers.defaultScheduler(), closure);
} }
@@ -760,6 +785,10 @@ public abstract class Task<T> {
} }
public static <T> Task<T> composeAsync(ExceptionalSupplier<Task<T>, ?> fn) { public static <T> Task<T> composeAsync(ExceptionalSupplier<Task<T>, ?> fn) {
return composeAsync(getCaller(), fn);
}
public static <T> Task<T> composeAsync(String name, ExceptionalSupplier<Task<T>, ?> fn) {
return new Task<T>() { return new Task<T>() {
Task<T> then; Task<T> then;
@@ -774,7 +803,7 @@ public abstract class Task<T> {
public Collection<Task<?>> getDependencies() { public Collection<Task<?>> getDependencies() {
return then == null ? Collections.emptySet() : Collections.singleton(then); return then == null ? Collections.emptySet() : Collections.singleton(then);
} }
}; }.setName(name);
} }
public static <V> Task<V> supplyAsync(Callable<V> callable) { public static <V> Task<V> supplyAsync(Callable<V> callable) {
@@ -960,4 +989,21 @@ public abstract class Task<T> {
return relyingOnDependents; return relyingOnDependents;
} }
} }
public static class StageTask<T> extends Task<T> {
private final Task<T> task;
StageTask(Task<T> task) {
this.task = task;
}
@Override
public Collection<Task<?>> getDependents() {
return Collections.singleton(task);
}
@Override
public void execute() throws Exception {
setResult(task.getResult());
}
}
} }