From 165d77df30e7a81eea0e348632436b7e53335c0c Mon Sep 17 00:00:00 2001 From: Glavo Date: Fri, 23 Jan 2026 22:19:43 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=20FutureCallback=20(#5268)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jackhuang/hmcl/game/LauncherHelper.java | 6 ++--- .../hmcl/ui/construct/InputDialogPane.java | 20 +++++++++----- .../hmcl/ui/construct/PromptDialogPane.java | 16 +++++++---- .../hmcl/ui/download/DownloadPage.java | 4 +-- .../ui/download/ModpackSelectionPage.java | 16 +++++------ .../terracotta/TerracottaControllerPage.java | 8 +++--- .../hmcl/ui/versions/SchematicsPage.java | 12 ++++----- .../jackhuang/hmcl/ui/versions/Versions.java | 14 +++++----- .../hmcl/ui/versions/WorldListPage.java | 10 +++---- .../hmcl/ui/versions/WorldManageUIUtils.java | 12 ++++----- .../jackhuang/hmcl/util/FutureCallback.java | 27 +++++++++++-------- 11 files changed, 81 insertions(+), 64 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index 6cece7616..62f435842 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -325,13 +325,13 @@ public final class LauncherHelper { message = i18n("launch.failed.command_too_long"); } else if (ex instanceof ExecutionPolicyLimitException) { Controllers.prompt(new PromptDialogPane.Builder(i18n("launch.failed.execution_policy"), - (result, resolve, reject) -> { + (result, handler) -> { if (CommandBuilder.setExecutionPolicy()) { LOG.info("Set the ExecutionPolicy for the scope 'CurrentUser' to 'RemoteSigned'"); - resolve.run(); + handler.resolve(); } else { LOG.warning("Failed to set ExecutionPolicy"); - reject.accept(i18n("launch.failed.execution_policy.failed_to_set")); + handler.reject(i18n("launch.failed.execution_policy.failed_to_set")); } }) .addQuestion(new PromptDialogPane.Builder.HintQuestion(i18n("launch.failed.execution_policy.hint"))) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/InputDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/InputDialogPane.java index 8daab7bc5..692697013 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/InputDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/InputDialogPane.java @@ -72,13 +72,19 @@ public class InputDialogPane extends JFXDialogLayout implements DialogAware { acceptButton.setOnAction(e -> { acceptPane.showSpinner(); - onResult.call(textField.getText(), () -> { - acceptPane.hideSpinner(); - future.complete(textField.getText()); - fireEvent(new DialogCloseEvent()); - }, msg -> { - acceptPane.hideSpinner(); - lblCreationWarning.setText(msg); + onResult.call(textField.getText(), new FutureCallback.ResultHandler() { + @Override + public void resolve() { + acceptPane.hideSpinner(); + future.complete(textField.getText()); + fireEvent(new DialogCloseEvent()); + } + + @Override + public void reject(String reason) { + acceptPane.hideSpinner(); + lblCreationWarning.setText(reason); + } }); }); textField.setOnAction(event -> acceptButton.fire()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/PromptDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/PromptDialogPane.java index b23916321..25c700e66 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/PromptDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/PromptDialogPane.java @@ -117,11 +117,17 @@ public class PromptDialogPane extends DialogPane { protected void onAccept() { setLoading(); - builder.callback.call(builder.questions, () -> { - future.complete(builder.questions); - runInFX(this::onSuccess); - }, msg -> { - runInFX(() -> onFailure(msg)); + builder.callback.call(builder.questions, new FutureCallback.ResultHandler() { + @Override + public void resolve() { + future.complete(builder.questions); + runInFX(() -> onSuccess()); + } + + @Override + public void reject(String reason) { + runInFX(() -> onFailure(reason)); + } }); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java index aa17a4504..ff3f237f8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java @@ -134,7 +134,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage Path runDirectory = profile.getRepository().hasVersion(version) ? profile.getRepository().getRunDirectory(version) : profile.getRepository().getBaseDirectory(); - Controllers.prompt(i18n("archive.file.name"), (result, resolve, reject) -> { + Controllers.prompt(i18n("archive.file.name"), (result, handler) -> { Path dest = runDirectory.resolve(subdirectoryName).resolve(result); Controllers.taskDialog(Task.composeAsync(() -> { @@ -152,7 +152,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage Controllers.showToast(i18n("install.success")); } }), i18n("message.downloading"), TaskCancellationAction.NORMAL); - resolve.run(); + handler.resolve(); }, file.getFile().getFilename(), new Validator(i18n("install.new_game.malformed"), FileUtils::isNameValid)); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java index fd7e13984..50090a193 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java @@ -124,37 +124,37 @@ public final class ModpackSelectionPage extends VBox implements WizardPage { } private void onChooseRemoteFile() { - Controllers.prompt(i18n("modpack.choose.remote.tooltip"), (url, resolve, reject) -> { + Controllers.prompt(i18n("modpack.choose.remote.tooltip"), (url, handler) -> { try { if (url.endsWith("server-manifest.json")) { // if urlString ends with .json, we assume that the url is server-manifest.json Controllers.taskDialog(new GetTask(url).whenComplete(Schedulers.javafx(), (result, e) -> { ServerModpackManifest manifest = JsonUtils.fromMaybeMalformedJson(result, ServerModpackManifest.class); if (manifest == null) { - reject.accept(i18n("modpack.type.server.malformed")); + handler.reject(i18n("modpack.type.server.malformed")); } else if (e == null) { - resolve.run(); + handler.resolve(); controller.getSettings().put(MODPACK_SERVER_MANIFEST, manifest); controller.onNext(); } else { - reject.accept(e.getMessage()); + handler.reject(e.getMessage()); } }).executor(true), i18n("message.downloading"), TaskCancellationAction.NORMAL); } else { // otherwise we still consider the file as modpack zip file // since casually the url may not ends with ".zip" Path modpack = Files.createTempFile("modpack", ".zip"); - resolve.run(); + handler.resolve(); Controllers.taskDialog( new FileDownloadTask(url, modpack) .whenComplete(Schedulers.javafx(), e -> { if (e == null) { - resolve.run(); + handler.resolve(); controller.getSettings().put(MODPACK_FILE, modpack); controller.onNext(); } else { - reject.accept(e.getMessage()); + handler.reject(e.getMessage()); } }).executor(true), i18n("message.downloading"), @@ -162,7 +162,7 @@ public final class ModpackSelectionPage extends VBox implements WizardPage { ); } } catch (IOException e) { - reject.accept(e.getMessage()); + handler.reject(e.getMessage()); } }, "", new URLValidator()); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/TerracottaControllerPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/TerracottaControllerPage.java index 2ffb37890..ce24167ed 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/TerracottaControllerPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/TerracottaControllerPage.java @@ -245,19 +245,19 @@ public class TerracottaControllerPage extends StackPane { guest.setSubtitle(i18n("terracotta.status.waiting.guest.desc")); guest.setRightIcon(SVG.ARROW_FORWARD); FXUtils.onClicked(guest, () -> { - Controllers.prompt(i18n("terracotta.status.waiting.guest.prompt.title"), (code, resolve, reject) -> { + Controllers.prompt(i18n("terracotta.status.waiting.guest.prompt.title"), (code, handler) -> { Task task = TerracottaManager.setGuesting(code); if (task != null) { task.whenComplete(Schedulers.javafx(), (s, e) -> { if (e != null) { - reject.accept(i18n("terracotta.status.waiting.guest.prompt.invalid")); + handler.reject(i18n("terracotta.status.waiting.guest.prompt.invalid")); } else { - resolve.run(); + handler.resolve(); UI_STATE.set(s); } }).setSignificance(Task.TaskSignificance.MINOR).start(); } else { - resolve.run(); + handler.resolve(); } }); }); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/SchematicsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/SchematicsPage.java index 2d5673f9b..c50e4f686 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/SchematicsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/SchematicsPage.java @@ -163,30 +163,30 @@ public final class SchematicsPage extends ListPageBase impl Controllers.dialog(new InputDialogPane( i18n("schematics.create_directory.prompt"), "", - (result, resolve, reject) -> { + (result, handler) -> { if (StringUtils.isBlank(result)) { - reject.accept(i18n("schematics.create_directory.failed.empty_name")); + handler.reject(i18n("schematics.create_directory.failed.empty_name")); return; } if (result.contains("/") || result.contains("\\") || !FileUtils.isNameValid(result)) { - reject.accept(i18n("schematics.create_directory.failed.invalid_name")); + handler.reject(i18n("schematics.create_directory.failed.invalid_name")); return; } Path targetDir = parent.resolve(result); if (Files.exists(targetDir)) { - reject.accept(i18n("schematics.create_directory.failed.already_exists")); + handler.reject(i18n("schematics.create_directory.failed.already_exists")); return; } try { Files.createDirectories(targetDir); - resolve.run(); + handler.resolve(); refresh(); } catch (IOException e) { LOG.warning("Failed to create directory: " + targetDir, e); - reject.accept(i18n("schematics.create_directory.failed", targetDir)); + handler.reject(i18n("schematics.create_directory.failed", targetDir)); } })); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java index 83bbb7205..82c3c7a8a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java @@ -129,13 +129,13 @@ public final class Versions { } public static CompletableFuture renameVersion(Profile profile, String version) { - return Controllers.prompt(i18n("version.manage.rename.message"), (newName, resolve, reject) -> { + return Controllers.prompt(i18n("version.manage.rename.message"), (newName, handler) -> { if (newName.equals(version)) { - resolve.run(); + handler.resolve(); return; } if (profile.getRepository().renameVersion(version, newName)) { - resolve.run(); + handler.resolve(); profile.getRepository().refreshVersionsAsync() .thenRunAsync(Schedulers.javafx(), () -> { if (profile.getRepository().hasVersion(newName)) { @@ -143,7 +143,7 @@ public final class Versions { } }).start(); } else { - reject.accept(i18n("version.manage.rename.fail")); + handler.reject(i18n("version.manage.rename.fail")); } }, version, new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId), new Validator(i18n("install.new_game.already_exists"), newVersionName -> !profile.getRepository().versionIdConflicts(newVersionName) || newVersionName.equals(version))); @@ -159,16 +159,16 @@ public final class Versions { public static void duplicateVersion(Profile profile, String version) { Controllers.prompt( - new PromptDialogPane.Builder(i18n("version.manage.duplicate.prompt"), (res, resolve, reject) -> { + new PromptDialogPane.Builder(i18n("version.manage.duplicate.prompt"), (res, handler) -> { String newVersionName = ((PromptDialogPane.Builder.StringQuestion) res.get(1)).getValue(); boolean copySaves = ((PromptDialogPane.Builder.BooleanQuestion) res.get(2)).getValue(); Task.runAsync(() -> profile.getRepository().duplicateVersion(version, newVersionName, copySaves)) .thenComposeAsync(profile.getRepository().refreshVersionsAsync()) .whenComplete(Schedulers.javafx(), (result, exception) -> { if (exception == null) { - resolve.run(); + handler.resolve(); } else { - reject.accept(StringUtils.getStackTrace(exception)); + handler.reject(StringUtils.getStackTrace(exception)); if (!profile.getRepository().versionIdConflicts(newVersionName)) { profile.getRepository().removeVersionFromDisk(newVersionName); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPage.java index 3d3f74240..dc251c1fe 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPage.java @@ -156,18 +156,18 @@ public final class WorldListPage extends ListPageBase implements VersionP // Or too many input dialogs are popped. Task.supplyAsync(() -> new World(zipFile)) .whenComplete(Schedulers.javafx(), world -> { - Controllers.prompt(i18n("world.name.enter"), (name, resolve, reject) -> { + Controllers.prompt(i18n("world.name.enter"), (name, handler) -> { Task.runAsync(() -> world.install(savesDir, name)) .whenComplete(Schedulers.javafx(), () -> { - resolve.run(); + handler.resolve(); refresh(); }, e -> { if (e instanceof FileAlreadyExistsException) - reject.accept(i18n("world.import.failed", i18n("world.import.already_exists"))); + handler.reject(i18n("world.import.failed", i18n("world.import.already_exists"))); else if (e instanceof IOException && e.getCause() instanceof InvalidPathException) - reject.accept(i18n("world.import.failed", i18n("install.new_game.malformed"))); + handler.reject(i18n("world.import.failed", i18n("install.new_game.malformed"))); else - reject.accept(i18n("world.import.failed", e.getClass().getName() + ": " + e.getLocalizedMessage())); + handler.reject(i18n("world.import.failed", e.getClass().getName() + ": " + e.getLocalizedMessage())); }).start(); }, world.getWorldName(), new Validator(i18n("install.new_game.malformed"), FileUtils::isNameValid)); }, e -> { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManageUIUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManageUIUtils.java index 761144ce4..d91a269b9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManageUIUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManageUIUtils.java @@ -92,20 +92,20 @@ public final class WorldManageUIUtils { Controllers.dialog(new InputDialogPane( i18n("world.duplicate.prompt"), "", - (result, resolve, reject) -> { + (result, handler) -> { if (StringUtils.isBlank(result)) { - reject.accept(i18n("world.duplicate.failed.empty_name")); + handler.reject(i18n("world.duplicate.failed.empty_name")); return; } if (result.contains("/") || result.contains("\\") || !FileUtils.isNameValid(result)) { - reject.accept(i18n("world.duplicate.failed.invalid_name")); + handler.reject(i18n("world.duplicate.failed.invalid_name")); return; } Path targetDir = worldPath.resolveSibling(result); if (Files.exists(targetDir)) { - reject.accept(i18n("world.duplicate.failed.already_exists")); + handler.reject(i18n("world.duplicate.failed.already_exists")); return; } @@ -118,9 +118,9 @@ public final class WorldManageUIUtils { } ).whenComplete(Schedulers.javafx(), (throwable) -> { if (throwable == null) { - resolve.run(); + handler.resolve(); } else { - reject.accept(i18n("world.duplicate.failed")); + handler.reject(i18n("world.duplicate.failed")); LOG.warning("Failed to duplicate world " + world.getFile(), throwable); } }) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/FutureCallback.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/FutureCallback.java index 579c1ee0e..4abad2ac1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/FutureCallback.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/FutureCallback.java @@ -17,18 +17,23 @@ */ package org.jackhuang.hmcl.util; -import java.util.function.Consumer; - @FunctionalInterface public interface FutureCallback { - /** - * Callback of future, called after future finishes. - * This callback gives the feedback whether the result of future is acceptable or not, - * if not, giving the reason, and future will be relaunched when necessary. - * @param result result of the future - * @param resolve accept the result - * @param reject reject the result with failure reason - */ - void call(T result, Runnable resolve, Consumer reject); + /// Callback of future, called after future finishes. + /// This callback gives the feedback whether the result of future is acceptable or not, + /// if not, giving the reason, and future will be relaunched when necessary. + /// + /// @param result result of the future + /// @param handler handler to accept or reject the result + void call(T result, ResultHandler handler); + + interface ResultHandler { + + /// Accept the result. + void resolve(); + + /// Reject the result with given reason. + void reject(String reason); + } }