Replace Properties with InvocationDispatcher & Make config saving async
This commit is contained in:
@@ -80,9 +80,6 @@ public final class Launcher extends Application {
|
|||||||
try {
|
try {
|
||||||
Logging.start(LOG_DIRECTORY);
|
Logging.start(LOG_DIRECTORY);
|
||||||
|
|
||||||
// NetworkUtils.setUserAgentSupplier(() -> "Hello Minecraft! Launcher");
|
|
||||||
Constants.UI_THREAD_SCHEDULER = Constants.JAVAFX_UI_THREAD_SCHEDULER;
|
|
||||||
|
|
||||||
LOG.info("*** " + Metadata.TITLE + " ***");
|
LOG.info("*** " + Metadata.TITLE + " ***");
|
||||||
LOG.info("Operating System: " + System.getProperty("os.name") + ' ' + OperatingSystem.SYSTEM_VERSION);
|
LOG.info("Operating System: " + System.getProperty("os.name") + ' ' + OperatingSystem.SYSTEM_VERSION);
|
||||||
LOG.info("Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor"));
|
LOG.info("Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor"));
|
||||||
|
|||||||
@@ -27,9 +27,11 @@ import java.nio.file.Paths;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import org.jackhuang.hmcl.util.InvocationDispatcher;
|
||||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
import static org.jackhuang.hmcl.util.Lang.thread;
|
||||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||||
|
|
||||||
public final class ConfigHolder {
|
public final class ConfigHolder {
|
||||||
@@ -43,6 +45,19 @@ public final class ConfigHolder {
|
|||||||
private static Config configInstance;
|
private static Config configInstance;
|
||||||
private static boolean newlyCreated;
|
private static boolean newlyCreated;
|
||||||
|
|
||||||
|
private static InvocationDispatcher<String> configWriter = new InvocationDispatcher<>(content -> {
|
||||||
|
thread(() -> {
|
||||||
|
LOG.info("Saving config");
|
||||||
|
try {
|
||||||
|
synchronized (configLocation) {
|
||||||
|
Files.write(configLocation, content.get().getBytes(UTF_8));
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.log(Level.SEVERE, "Failed to save config", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
public static Config config() {
|
public static Config config() {
|
||||||
if (configInstance == null) {
|
if (configInstance == null) {
|
||||||
throw new IllegalStateException("Configuration hasn't been loaded");
|
throw new IllegalStateException("Configuration hasn't been loaded");
|
||||||
@@ -69,7 +84,7 @@ public final class ConfigHolder {
|
|||||||
Settings.init();
|
Settings.init();
|
||||||
|
|
||||||
if (newlyCreated) {
|
if (newlyCreated) {
|
||||||
saveConfig();
|
markConfigDirty();
|
||||||
|
|
||||||
// hide the config file on windows
|
// hide the config file on windows
|
||||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||||
@@ -123,22 +138,7 @@ public final class ConfigHolder {
|
|||||||
return new Config();
|
return new Config();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void saveConfig() throws IOException {
|
|
||||||
LOG.info("Saving config");
|
|
||||||
try {
|
|
||||||
Files.write(configLocation, configInstance.toJson().getBytes(UTF_8));
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.log(Level.SEVERE, "Failed to save config", e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void markConfigDirty() {
|
static void markConfigDirty() {
|
||||||
// TODO: change this to async
|
configWriter.accept(configInstance.toJson());
|
||||||
try {
|
|
||||||
saveConfig();
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
// ignore it as it has been logged
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.task;
|
package org.jackhuang.hmcl.task;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||||
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
||||||
import javafx.beans.property.ReadOnlyStringProperty;
|
import javafx.beans.property.ReadOnlyStringProperty;
|
||||||
@@ -26,12 +27,10 @@ import org.jackhuang.hmcl.util.*;
|
|||||||
import org.jackhuang.hmcl.util.function.ExceptionalConsumer;
|
import org.jackhuang.hmcl.util.function.ExceptionalConsumer;
|
||||||
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
|
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
|
||||||
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
|
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
|
||||||
import org.jackhuang.hmcl.util.javafx.Properties;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
@@ -176,8 +175,8 @@ public abstract class Task {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private long lastTime = Long.MIN_VALUE;
|
private long lastTime = Long.MIN_VALUE;
|
||||||
private final AtomicReference<Double> progressUpdate = new AtomicReference<>();
|
|
||||||
private final ReadOnlyDoubleWrapper progress = new ReadOnlyDoubleWrapper(this, "progress", -1);
|
private final ReadOnlyDoubleWrapper progress = new ReadOnlyDoubleWrapper(this, "progress", -1);
|
||||||
|
private final InvocationDispatcher<Double> progressUpdate = InvocationDispatcher.runOn(Platform::runLater, progress::set);
|
||||||
|
|
||||||
public ReadOnlyDoubleProperty progressProperty() {
|
public ReadOnlyDoubleProperty progressProperty() {
|
||||||
return progress.getReadOnlyProperty();
|
return progress.getReadOnlyProperty();
|
||||||
@@ -198,18 +197,18 @@ public abstract class Task {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void updateProgressImmediately(double progress) {
|
protected void updateProgressImmediately(double progress) {
|
||||||
Properties.updateAsync(this.progress, progress, progressUpdate);
|
progressUpdate.accept(progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final AtomicReference<String> messageUpdate = new AtomicReference<>();
|
|
||||||
private final ReadOnlyStringWrapper message = new ReadOnlyStringWrapper(this, "message", null);
|
private final ReadOnlyStringWrapper message = new ReadOnlyStringWrapper(this, "message", null);
|
||||||
|
private final InvocationDispatcher<String> messageUpdate = InvocationDispatcher.runOn(Platform::runLater, message::set);
|
||||||
|
|
||||||
public final ReadOnlyStringProperty messageProperty() {
|
public final ReadOnlyStringProperty messageProperty() {
|
||||||
return message.getReadOnlyProperty();
|
return message.getReadOnlyProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final void updateMessage(String newMessage) {
|
protected final void updateMessage(String newMessage) {
|
||||||
Properties.updateAsync(message, newMessage, messageUpdate);
|
messageUpdate.accept(newMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void run() throws Exception {
|
public final void run() throws Exception {
|
||||||
|
|||||||
@@ -17,10 +17,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.util;
|
package org.jackhuang.hmcl.util;
|
||||||
|
|
||||||
import org.jackhuang.hmcl.task.Schedulers;
|
|
||||||
import java.awt.*;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constants.
|
* Constants.
|
||||||
*
|
*
|
||||||
@@ -34,20 +30,4 @@ public final class Constants {
|
|||||||
public static final String DEFAULT_LIBRARY_URL = "https://libraries.minecraft.net/";
|
public static final String DEFAULT_LIBRARY_URL = "https://libraries.minecraft.net/";
|
||||||
public static final String DEFAULT_VERSION_DOWNLOAD_URL = "https://s3.amazonaws.com/Minecraft.Download/versions/";
|
public static final String DEFAULT_VERSION_DOWNLOAD_URL = "https://s3.amazonaws.com/Minecraft.Download/versions/";
|
||||||
public static final String DEFAULT_INDEX_URL = "https://s3.amazonaws.com/Minecraft.Download/indexes/";
|
public static final String DEFAULT_INDEX_URL = "https://s3.amazonaws.com/Minecraft.Download/indexes/";
|
||||||
|
|
||||||
public static Consumer<Runnable> UI_THREAD_SCHEDULER = s -> Schedulers.computation().schedule(s::run);
|
|
||||||
|
|
||||||
public static final Consumer<Runnable> SWING_UI_THREAD_SCHEDULER = s -> {
|
|
||||||
if (EventQueue.isDispatchThread())
|
|
||||||
s.run();
|
|
||||||
else
|
|
||||||
EventQueue.invokeLater(s);
|
|
||||||
};
|
|
||||||
|
|
||||||
public static final Consumer<Runnable> JAVAFX_UI_THREAD_SCHEDULER = s -> {
|
|
||||||
if (javafx.application.Platform.isFxApplicationThread())
|
|
||||||
s.run();
|
|
||||||
else
|
|
||||||
javafx.application.Platform.runLater(s);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher.
|
||||||
|
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||||
|
*
|
||||||
|
* 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 {http://www.gnu.org/licenses/}.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jackhuang.hmcl.util;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When {@link #accept(ARG)} is called, this class invokes the handler on another thread.
|
||||||
|
* If {@link #accept(ARG)} is called more than one time before the handler starts processing,
|
||||||
|
* the handler will only be invoked once, taking the latest argument as its input.
|
||||||
|
*
|
||||||
|
* @author yushijinhun
|
||||||
|
*/
|
||||||
|
public class InvocationDispatcher<ARG> implements Consumer<ARG> {
|
||||||
|
|
||||||
|
public static <ARG> InvocationDispatcher<ARG> runOn(Consumer<Runnable> executor, Consumer<ARG> action) {
|
||||||
|
return new InvocationDispatcher<>(arg -> executor.accept(() -> {
|
||||||
|
action.accept(arg.get());
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Consumer<Supplier<ARG>> handler;
|
||||||
|
|
||||||
|
private AtomicReference<Optional<ARG>> pendingArg = new AtomicReference<>();
|
||||||
|
|
||||||
|
public InvocationDispatcher(Consumer<Supplier<ARG>> handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(ARG arg) {
|
||||||
|
if (pendingArg.getAndSet(Optional.ofNullable(arg)) == null) {
|
||||||
|
handler.accept(() -> pendingArg.getAndSet(null).orElse(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* Hello Minecraft! Launcher.
|
|
||||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
|
||||||
*
|
|
||||||
* 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 {http://www.gnu.org/licenses/}.
|
|
||||||
*/
|
|
||||||
package org.jackhuang.hmcl.util.javafx;
|
|
||||||
|
|
||||||
import javafx.beans.property.Property;
|
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
import org.jackhuang.hmcl.util.Constants;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author huangyuhui
|
|
||||||
*/
|
|
||||||
public final class Properties {
|
|
||||||
|
|
||||||
private Properties() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> void updateAsync(Property<? super T> property, T newValue, AtomicReference<T> update) {
|
|
||||||
if (update.getAndSet(newValue) == null)
|
|
||||||
Constants.UI_THREAD_SCHEDULER.accept(() ->
|
|
||||||
property.setValue(update.getAndSet(null)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user