Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94ca81035b | ||
|
|
1ab414e702 | ||
|
|
16f6e89e45 | ||
|
|
22f6bfd845 | ||
|
|
c4823b3e55 | ||
|
|
e34ec9f801 | ||
|
|
d2e87ba024 | ||
|
|
ffb50423af |
No files matched your search
@@ -64,6 +64,7 @@ import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
@@ -433,26 +434,34 @@ public final class Controllers {
|
||||
dialog(new MessageDialogPane.Builder(text, title, type).actionOrCancel(actionButton, cancel).build());
|
||||
}
|
||||
|
||||
public static void confirmActionDanger(String text, String title, Runnable resolve, Runnable cancel) {
|
||||
JFXButton btnYes = new JFXButton(i18n("button.ok"));
|
||||
btnYes.getStyleClass().add("dialog-error");
|
||||
btnYes.setOnAction(e -> resolve.run());
|
||||
btnYes.setDisable(true);
|
||||
public static void confirmWithCountdown(String text, String title, int seconds, MessageType messageType,
|
||||
@Nullable Runnable ok, @Nullable Runnable cancel) {
|
||||
if (seconds <= 0)
|
||||
throw new IllegalArgumentException("Seconds must be greater than 0");
|
||||
|
||||
int countdown = 10;
|
||||
KeyFrame[] keyFrames = new KeyFrame[countdown + 1];
|
||||
for (int i = 0; i < countdown; i++) {
|
||||
JFXButton btnOk = new JFXButton(i18n("button.ok"));
|
||||
btnOk.getStyleClass().add(messageType == MessageType.WARNING || messageType == MessageType.ERROR
|
||||
? "dialog-error"
|
||||
: "dialog-accept");
|
||||
|
||||
if (ok != null)
|
||||
btnOk.setOnAction(e -> ok.run());
|
||||
btnOk.setDisable(true);
|
||||
|
||||
KeyFrame[] keyFrames = new KeyFrame[seconds + 1];
|
||||
for (int i = 0; i < seconds; i++) {
|
||||
keyFrames[i] = new KeyFrame(Duration.seconds(i),
|
||||
new KeyValue(btnYes.textProperty(), i18n("button.ok.countdown", countdown - i)));
|
||||
new KeyValue(btnOk.textProperty(), i18n("button.ok.countdown", seconds - i)));
|
||||
}
|
||||
keyFrames[countdown] = new KeyFrame(Duration.seconds(countdown),
|
||||
new KeyValue(btnYes.textProperty(), i18n("button.ok")),
|
||||
new KeyValue(btnYes.disableProperty(), false));
|
||||
keyFrames[seconds] = new KeyFrame(Duration.seconds(seconds),
|
||||
new KeyValue(btnOk.textProperty(), i18n("button.ok")),
|
||||
new KeyValue(btnOk.disableProperty(), false));
|
||||
|
||||
Timeline timeline = new Timeline(keyFrames);
|
||||
confirmAction(text, title, MessageType.WARNING, btnYes, () -> {
|
||||
confirmAction(text, title, messageType, btnOk, () -> {
|
||||
timeline.stop();
|
||||
cancel.run();
|
||||
if (cancel != null)
|
||||
cancel.run();
|
||||
});
|
||||
timeline.play();
|
||||
}
|
||||
|
||||
@@ -1411,12 +1411,16 @@ public final class FXUtils {
|
||||
}
|
||||
|
||||
public static void copyText(String text) {
|
||||
copyText(text, i18n("message.copied"));
|
||||
}
|
||||
|
||||
public static void copyText(String text, @Nullable String toastMessage) {
|
||||
ClipboardContent content = new ClipboardContent();
|
||||
content.putString(text);
|
||||
Clipboard.getSystemClipboard().setContent(content);
|
||||
|
||||
if (!Controllers.isStopped()) {
|
||||
Controllers.showToast(i18n("message.copied"));
|
||||
if (toastMessage != null && !Controllers.isStopped()) {
|
||||
Controllers.showToast(toastMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -264,10 +264,12 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
};
|
||||
|
||||
if (factory instanceof OfflineAccountFactory && username != null && (!USERNAME_CHECKER_PATTERN.matcher(username).matches() || username.length() > 16)) {
|
||||
Controllers.confirmActionDanger(i18n("account.methods.offline.name.invalid"), i18n("message.warning"), doCreate, () -> {
|
||||
body.setDisable(false);
|
||||
spinner.hideSpinner();
|
||||
});
|
||||
Controllers.confirmWithCountdown(i18n("account.methods.offline.name.invalid"), i18n("message.warning"), 10,
|
||||
MessageDialogPane.MessageType.WARNING,
|
||||
doCreate, () -> {
|
||||
body.setDisable(false);
|
||||
spinner.hideSpinner();
|
||||
});
|
||||
} else {
|
||||
doCreate.run();
|
||||
}
|
||||
|
||||
@@ -34,11 +34,8 @@ import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
@@ -138,13 +135,13 @@ public class TerracottaControllerPage extends StackPane {
|
||||
TerracottaState state = UI_STATE.get(), next;
|
||||
if (state instanceof TerracottaState.Uninitialized || state instanceof TerracottaState.Preparing preparing && preparing.hasInstallFence()) {
|
||||
if (state instanceof TerracottaState.Uninitialized uninitialized && !uninitialized.hasLegacy()) {
|
||||
Controllers.confirmActionDanger(i18n("terracotta.confirm.desc"), i18n("terracotta.confirm.title"), () -> {
|
||||
TerracottaState.Preparing s = TerracottaManager.install(path);
|
||||
if (s != null) {
|
||||
UI_STATE.set(s);
|
||||
}
|
||||
}, () -> {
|
||||
});
|
||||
Controllers.confirmWithCountdown(i18n("terracotta.confirm.desc"), i18n("terracotta.confirm.title"), 5,
|
||||
MessageDialogPane.MessageType.INFO, () -> {
|
||||
TerracottaState.Preparing s = TerracottaManager.install(path);
|
||||
if (s != null) {
|
||||
UI_STATE.set(s);
|
||||
}
|
||||
}, null);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -202,7 +199,7 @@ public class TerracottaControllerPage extends StackPane {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Controllers.confirmActionDanger(i18n("terracotta.confirm.desc"), i18n("terracotta.confirm.title"), () -> {
|
||||
Controllers.confirmWithCountdown(i18n("terracotta.confirm.desc"), i18n("terracotta.confirm.title"), 5, MessageDialogPane.MessageType.INFO, () -> {
|
||||
globalConfig().setTerracottaAgreementVersion(1);
|
||||
TerracottaState.Preparing s = TerracottaManager.install(null);
|
||||
if (s != null) {
|
||||
@@ -342,6 +339,7 @@ public class TerracottaControllerPage extends StackPane {
|
||||
return;
|
||||
} else {
|
||||
String cs = hostOK.getCode();
|
||||
copyCode(cs);
|
||||
|
||||
statusProperty.set(i18n("terracotta.status.host_ok"));
|
||||
progressProperty.set(1);
|
||||
@@ -350,36 +348,24 @@ public class TerracottaControllerPage extends StackPane {
|
||||
code.setAlignment(Pos.CENTER);
|
||||
{
|
||||
Label desc = new Label(i18n("terracotta.status.host_ok.code"));
|
||||
{
|
||||
ClipboardContent cp = new ClipboardContent();
|
||||
cp.putString(cs);
|
||||
Clipboard.getSystemClipboard().setContent(cp);
|
||||
}
|
||||
desc.setMouseTransparent(true);
|
||||
|
||||
// FIXME: The implementation to display Room Code is ambiguous. Consider using a clearer JavaFX Element in the future.
|
||||
TextField label = new TextField(cs);
|
||||
label.setEditable(false);
|
||||
label.setFocusTraversable(false);
|
||||
Label label = new Label(cs);
|
||||
label.setMouseTransparent(true);
|
||||
label.setStyle("-fx-font-size: 24");
|
||||
label.setAlignment(Pos.CENTER);
|
||||
label.setStyle("-fx-background-color: transparent; -fx-border-color: transparent;");
|
||||
VBox.setMargin(label, new Insets(10, 0, 10, 0));
|
||||
label.setScaleX(1.8);
|
||||
label.setScaleY(1.8);
|
||||
holder.add(FXUtils.onWeakChange(label.selectedTextProperty(), string -> {
|
||||
if (string != null && !string.isEmpty() && !cs.equals(string)) {
|
||||
label.selectAll();
|
||||
}
|
||||
}));
|
||||
|
||||
code.getChildren().setAll(desc, label);
|
||||
}
|
||||
FXUtils.onClicked(code, () -> FXUtils.copyText(cs));
|
||||
code.setCursor(Cursor.HAND);
|
||||
FXUtils.onClicked(code, () -> copyCode(cs));
|
||||
|
||||
LineButton copy = LineButton.of();
|
||||
copy.setLeftIcon(SVG.CONTENT_COPY);
|
||||
copy.setTitle(i18n("terracotta.status.host_ok.code.copy"));
|
||||
copy.setSubtitle(i18n("terracotta.status.host_ok.code.desc"));
|
||||
FXUtils.onClicked(copy, () -> FXUtils.copyText(cs));
|
||||
FXUtils.onClicked(copy, () -> copyCode(cs));
|
||||
|
||||
LineButton back = LineButton.of();
|
||||
back.setLeftIcon(SVG.ARROW_BACK);
|
||||
@@ -601,6 +587,10 @@ public class TerracottaControllerPage extends StackPane {
|
||||
return locals;
|
||||
}
|
||||
|
||||
private void copyCode(String code) {
|
||||
FXUtils.copyText(code, i18n("terracotta.status.host_ok.code.copy.toast"));
|
||||
}
|
||||
|
||||
private static final class LineButton extends RipplerContainer {
|
||||
private final WeakListenerHolder holder = new WeakListenerHolder();
|
||||
|
||||
|
||||
@@ -19,22 +19,26 @@ package org.jackhuang.hmcl.ui.terracotta;
|
||||
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.terracotta.TerracottaMetadata;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.construct.PageAware;
|
||||
import org.jackhuang.hmcl.ui.construct.TabHeader;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class TerracottaPage extends DecoratorAnimatedPage implements DecoratorPage, PageAware {
|
||||
@@ -53,32 +57,25 @@ public class TerracottaPage extends DecoratorAnimatedPage implements DecoratorPa
|
||||
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
|
||||
});
|
||||
|
||||
AdvancedListItem feedback = new AdvancedListItem();
|
||||
feedback.setLeftGraphic(wrap(SVG.FEEDBACK));
|
||||
feedback.setActionButtonVisible(false);
|
||||
feedback.setTitle(i18n("terracotta.feedback.title"));
|
||||
feedback.setOnAction(e -> FXUtils.openLink(TerracottaMetadata.FEEDBACK_LINK));
|
||||
|
||||
AdvancedListItem chatItem = new AdvancedListItem();
|
||||
chatItem.setLeftGraphic(wrap(SVG.CHAT));
|
||||
chatItem.setActionButtonVisible(false);
|
||||
chatItem.setTitle(i18n("chat"));
|
||||
chatItem.setOnAction(e -> FXUtils.openLink(Metadata.GROUPS_URL));
|
||||
|
||||
AdvancedListItem easytierItem = new AdvancedListItem();
|
||||
easytierItem.setLeftGraphic(wrap(SVG.HOST));
|
||||
easytierItem.setActionButtonVisible(false);
|
||||
easytierItem.setTitle(i18n("terracotta.easytier"));
|
||||
easytierItem.setOnAction(e -> FXUtils.openLink("https://easytier.cn/"));
|
||||
BorderPane left = new BorderPane();
|
||||
FXUtils.setLimitWidth(left, 200);
|
||||
VBox.setVgrow(left, Priority.ALWAYS);
|
||||
setLeft(left);
|
||||
|
||||
AdvancedListBox sideBar = new AdvancedListBox()
|
||||
.addNavigationDrawerTab(tab, statusPage, i18n("terracotta.status"), SVG.TUNE)
|
||||
.startCategory(i18n("help").toUpperCase(Locale.ROOT))
|
||||
.add(feedback)
|
||||
.add(chatItem)
|
||||
.add(easytierItem);
|
||||
FXUtils.setLimitWidth(sideBar, 200);
|
||||
setLeft(sideBar);
|
||||
.addNavigationDrawerTab(tab, statusPage, i18n("terracotta.status"), SVG.TUNE);
|
||||
left.setTop(sideBar);
|
||||
|
||||
AdvancedListBox toolbar = new AdvancedListBox()
|
||||
.addNavigationDrawerItem(i18n("version.launch"), SVG.ROCKET_LAUNCH, () -> {
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
Versions.launch(profile, profile.getSelectedVersion(), LauncherHelper::setKeep);
|
||||
})
|
||||
.addNavigationDrawerItem(i18n("terracotta.feedback.title"), SVG.FEEDBACK, () -> FXUtils.openLink(TerracottaMetadata.FEEDBACK_LINK))
|
||||
.addNavigationDrawerItem(i18n("terracotta.easytier"), SVG.HOST, () -> FXUtils.openLink("https://easytier.cn/"))
|
||||
.addNavigationDrawerItem(i18n("chat"), SVG.CHAT, () -> FXUtils.openLink(Metadata.GROUPS_URL));
|
||||
BorderPane.setMargin(toolbar, new Insets(0, 0, 12, 0));
|
||||
left.setBottom(toolbar);
|
||||
|
||||
setCenter(transitionPane);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import javafx.beans.property.*;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
@@ -238,10 +237,6 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
super(control);
|
||||
|
||||
{
|
||||
BorderPane left = new BorderPane();
|
||||
FXUtils.setLimitWidth(left, 200);
|
||||
setLeft(left);
|
||||
|
||||
AdvancedListItem versionSettingsItem = new AdvancedListItem();
|
||||
versionSettingsItem.getStyleClass().add("navigation-drawer-item");
|
||||
versionSettingsItem.setTitle(i18n("settings.game"));
|
||||
|
||||
@@ -1460,6 +1460,7 @@ terracotta.status.host_starting.back=This will stop creating the room.
|
||||
terracotta.status.host_ok=Room created
|
||||
terracotta.status.host_ok.code=Invitation code (Copied)
|
||||
terracotta.status.host_ok.code.copy=Copy invitation code
|
||||
terracotta.status.host_ok.code.copy.toast=Invitation code has been copied to clipboard
|
||||
terracotta.status.host_ok.code.desc=Please remind your friends to select Guest mode in HMCL - Multiplayer or PCL CE and enter this invitation code.
|
||||
terracotta.status.host_ok.back=This will also close the room, other guests will leave and cannot rejoin.
|
||||
terracotta.status.guest_starting=Joining room
|
||||
|
||||
@@ -1248,6 +1248,7 @@ terracotta.status.host_starting.back=這將會取消建立房間。
|
||||
terracotta.status.host_ok=已建立房間
|
||||
terracotta.status.host_ok.code=邀請碼 (已自動複製到剪貼簿)
|
||||
terracotta.status.host_ok.code.copy=複製邀請碼
|
||||
terracotta.status.host_ok.code.copy.toast=已將邀請碼複製到剪貼簿
|
||||
terracotta.status.host_ok.code.desc=請提醒您的朋友在 HMCL 或 PCL CE 多人遊戲功能中選擇房客模式,並輸入該邀請碼。
|
||||
terracotta.status.host_ok.back=這將同時徹底關閉房間,其他房客將退出並不再能重新加入該房間。
|
||||
terracotta.status.guest_starting=正在加入房間
|
||||
|
||||
@@ -1258,6 +1258,7 @@ terracotta.status.host_starting.back=这将会取消创建房间。
|
||||
terracotta.status.host_ok=已启动房间
|
||||
terracotta.status.host_ok.code=邀请码 (已自动复制到剪贴板)
|
||||
terracotta.status.host_ok.code.copy=复制邀请码
|
||||
terracotta.status.host_ok.code.copy.toast=已将邀请码复制到剪贴板
|
||||
terracotta.status.host_ok.code.desc=请提醒您的朋友在 HMCL 或 PCL CE 多人联机功能中选择房客模式,并输入该邀请码。
|
||||
terracotta.status.host_ok.back=这将同时彻底关闭房间,其他房客将退出并不再能重新加入该房间。
|
||||
terracotta.status.guest_starting=正在加入房间
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"version_legacy": "0\\.3\\.[89]-rc\\.([0-9]|10)",
|
||||
"version_legacy": "0\\.3\\.([89]-rc\\.([0-9]|10)|10)",
|
||||
"version_recent": [
|
||||
"0.3.9-rc.10"
|
||||
"0.3.10"
|
||||
],
|
||||
"version_latest": "0.3.10",
|
||||
"version_latest": "0.3.11",
|
||||
"classifiers": {
|
||||
"linux-arm64": "sha256:001b6b1e220e98dffd84198a3e16855644cad27900562c78d610b0bf50138d17",
|
||||
"linux-x86_64": "sha256:b133ddb796ff52879fb0c38e68ef7ea968d3eaec77f27ea274c701888e795782",
|
||||
"macos-arm64": "sha256:4bb2eb432ea0ee3e3dca6b1019d8864aa14bedfe20b3d804865e216e36e856df",
|
||||
"macos-arm64.pkg": "sha256:e4bf3d2014e417565e3ab0df579f4280e0af42bc28516f640067a52dca77098f",
|
||||
"macos-x86_64": "sha256:17a2cc46d884951a88da6f3f0725e38ffca6fdfd1da021807121c29b944bd830",
|
||||
"macos-x86_64.pkg": "sha256:a4f08a706605262485c1889dfae00554fba753141f6d6ada76e29bfc3e0338aa",
|
||||
"windows-arm64.exe": "sha256:245e0faf2ee1cdc5e430b5c18fbab4c4e5e3f333f0de51a621ae772db3a16924",
|
||||
"windows-x86_64.exe": "sha256:ab99ddb729860c939b2085d8852fd62007b86d97568b687cf2a7147718b64eea"
|
||||
"linux-arm64": "sha256:cd321f22b200b212ba21eb7582bfe0e1bcadb528d19c70307a2aea9596118d33",
|
||||
"linux-x86_64": "sha256:d4b15ec7cba7c9332b84f6acbf247e779543f958f9b240e9707bd6f37973e12a",
|
||||
"macos-arm64": "sha256:8456ad2c016e7c5c6f5814d98203a89ec38d13cdaafe94d328e313404ea16a2f",
|
||||
"macos-arm64.pkg": "sha256:f85f86c563d4460ba97ed048fee22fa0e1398454443576c56b0afeebb98e8691",
|
||||
"macos-x86_64": "sha256:cb1f7f5d5876a9177c426bece5ad0845e1b25d3430533646b2b9c93d97d37d79",
|
||||
"macos-x86_64.pkg": "sha256:e54bc35be7c8c543362b1b428e7d40d2116f1a4e57ade1c09cbef6239efb67ae",
|
||||
"windows-arm64.exe": "sha256:40c628bafb48d51c90f47037f8c4caedfadb52421e64b70011f448d1d2f553a3",
|
||||
"windows-x86_64.exe": "sha256:de518b2b4e60336fd272092326799966efc4e72acd65576d83e61d4d242fd61d"
|
||||
},
|
||||
"downloads": [
|
||||
"https://github.com/burningtnt/Terracotta/releases/download/v${version}/terracotta-${version}-${classifier}",
|
||||
|
||||
@@ -17,9 +17,10 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.task;
|
||||
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public abstract class CompletableFutureTask<T> extends Task<T> {
|
||||
|
||||
@@ -29,18 +30,11 @@ public abstract class CompletableFutureTask<T> extends Task<T> {
|
||||
|
||||
public abstract CompletableFuture<T> getFuture(TaskCompletableFuture executor);
|
||||
|
||||
protected static Throwable resolveException(Throwable e) {
|
||||
if (e instanceof ExecutionException || e instanceof CompletionException)
|
||||
return resolveException(e.getCause());
|
||||
else
|
||||
return e;
|
||||
}
|
||||
|
||||
public static class CustomException extends RuntimeException {}
|
||||
|
||||
protected static CompletableFuture<Void> breakable(CompletableFuture<?> future) {
|
||||
return future.thenApplyAsync(unused1 -> (Void) null).exceptionally(throwable -> {
|
||||
if (resolveException(throwable) instanceof CustomException) return null;
|
||||
if (Lang.resolveException(throwable) instanceof CustomException) return null;
|
||||
else throw new CompletionException(throwable);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -345,16 +345,11 @@ public abstract class Task<T> {
|
||||
}
|
||||
|
||||
private final ReadOnlyStringWrapper message = new ReadOnlyStringWrapper(this, "message", null);
|
||||
private final InvocationDispatcher<String> messageUpdate = InvocationDispatcher.runOn(Platform::runLater, message::set);
|
||||
|
||||
public final ReadOnlyStringProperty messageProperty() {
|
||||
return message.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
protected final void updateMessage(String newMessage) {
|
||||
messageUpdate.accept(newMessage);
|
||||
}
|
||||
|
||||
public final T run() throws Exception {
|
||||
if (getSignificance().shouldLog())
|
||||
LOG.trace("Executing task: " + getName());
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/// When [#accept(T)] is called, this class invokes the handler on another thread.
|
||||
@@ -28,6 +29,16 @@ import java.util.function.Consumer;
|
||||
/// @author yushijinhun
|
||||
public final class InvocationDispatcher<T> implements Consumer<T> {
|
||||
|
||||
private static final VarHandle PENDING_ARG_HANDLE;
|
||||
static {
|
||||
try {
|
||||
PENDING_ARG_HANDLE = MethodHandles.lookup()
|
||||
.findVarHandle(InvocationDispatcher.class, "pendingArg", Holder.class);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new ExceptionInInitializerError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// @param executor The executor must dispatch all tasks to a single thread.
|
||||
public static <T> InvocationDispatcher<T> runOn(Executor executor, Consumer<T> action) {
|
||||
return new InvocationDispatcher<>(executor, action);
|
||||
@@ -35,7 +46,10 @@ public final class InvocationDispatcher<T> implements Consumer<T> {
|
||||
|
||||
private final Executor executor;
|
||||
private final Consumer<T> action;
|
||||
private final AtomicReference<Holder<T>> pendingArg = new AtomicReference<>();
|
||||
|
||||
/// @see #PENDING_ARG_HANDLE
|
||||
@SuppressWarnings("unused")
|
||||
private volatile Holder<T> pendingArg;
|
||||
|
||||
private InvocationDispatcher(Executor executor, Consumer<T> action) {
|
||||
this.executor = executor;
|
||||
@@ -44,12 +58,15 @@ public final class InvocationDispatcher<T> implements Consumer<T> {
|
||||
|
||||
@Override
|
||||
public void accept(T t) {
|
||||
if (pendingArg.getAndSet(new Holder<>(t)) == null) {
|
||||
if (PENDING_ARG_HANDLE.getAndSet(this, new Holder<>(t)) == null) {
|
||||
executor.execute(() -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
var holder = (Holder<T>) PENDING_ARG_HANDLE.getAndSet(this, (Holder<T>) null);
|
||||
|
||||
// If the executor supports multiple underlying threads,
|
||||
// we need to add synchronization, but for now we can omit it :)
|
||||
// synchronized (InvocationDispatcher.this)
|
||||
action.accept(pendingArg.getAndSet(null).value);
|
||||
action.accept(holder.value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,4 +15,4 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
versionRoot=3.7
|
||||
versionRoot=3.8
|
||||
Reference in new issue
Block a user