improve performance, bug fixes, add start dialog progress, load much more async, relative time log

This commit is contained in:
Bixilon 2020-10-11 15:57:26 +02:00
parent 6e27430994
commit 2203e44d05
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
11 changed files with 148 additions and 62 deletions

View File

@ -1,7 +1,7 @@
# Modding # Modding
## mod.json ## mod.json
In your jar file (the mod) must be a file called `mod.json`. In the root folder of your jar file (the mod) must be a file called `mod.json`. It contains metadata for the mod loader.
### Example ### Example
```json ```json
{ {

View File

@ -13,9 +13,34 @@
package de.bixilon.minosoft; package de.bixilon.minosoft;
import de.bixilon.minosoft.util.OSUtil;
import java.io.File;
public class Config { public class Config {
public static final String configFileName = "config.json"; public static final String configFileName = "config.json"; // Path of the minosoft base configuration (located in AppData/Minosoft/config)
public static final boolean skipAuthentication = false; // only for offline development public static final boolean skipAuthentication = false; // disables all connections to mojang
public static final boolean colorLog = true; public static final boolean colorLog = true; // the log should be colored with ANSI (does not affect chat components)
public static final boolean logRelativeTime = false; // prefix all log messages with the relative start time in milliseconds instead of the formatted time
public static String homeDir; public static String homeDir;
static {
// Sets Config.homeDir to the correct folder per OS
homeDir = System.getProperty("user.home");
if (!homeDir.endsWith(File.separator)) {
homeDir += "/";
}
homeDir += switch (OSUtil.getOS()) {
case LINUX -> ".local/share/minosoft/";
case WINDOWS -> "AppData/Roaming/Minosoft/";
case MAC -> "Library/Application Support/Minosoft/";
case OTHER -> ".minosoft/";
};
File folder = new File(homeDir);
if (!folder.exists() && !folder.mkdirs()) {
// failed creating folder
throw new RuntimeException(String.format("Could not create home folder (%s)!", homeDir));
}
}
} }

View File

@ -20,12 +20,16 @@ import de.bixilon.minosoft.gui.main.Server;
import de.bixilon.minosoft.gui.main.ServerListCell; import de.bixilon.minosoft.gui.main.ServerListCell;
import de.bixilon.minosoft.logging.Log; import de.bixilon.minosoft.logging.Log;
import javafx.application.Application; import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.control.Dialog;
import javafx.scene.control.ListCell; import javafx.scene.control.ListCell;
import javafx.scene.control.ListView; import javafx.scene.control.ListView;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.util.Callback; import javafx.util.Callback;
@ -33,14 +37,32 @@ import javafx.util.Callback;
import java.io.IOException; import java.io.IOException;
public class Launcher extends Application { public class Launcher extends Application {
private static ProgressBar progressBar;
private static Dialog<Boolean> progressDialog;
public static void start() { public static void start() {
Log.info("Starting launcher...");
launch(); launch();
Log.info("Launcher started!");
}
protected static void setProgressBar(int jobsLeft) {
Platform.runLater(() -> {
if (progressBar == null || progressDialog == null) {
return;
}
if (jobsLeft == 0) {
progressDialog.setResult(Boolean.TRUE);
progressDialog.close();
return;
}
progressBar.setProgress(1.0F / jobsLeft);
});
} }
@Override @Override
public void start(Stage primaryStage) throws IOException { public void start(Stage primaryStage) throws IOException {
Log.info("Starting launcher..."); Log.info("Preparing main window...");
GUITools.versionList.setCellFactory(new Callback<>() { GUITools.versionList.setCellFactory(new Callback<>() {
@Override @Override
@ -74,6 +96,18 @@ public class Launcher extends Application {
if (Minosoft.getSelectedAccount() == null) { if (Minosoft.getSelectedAccount() == null) {
MainWindow.manageAccounts(); MainWindow.manageAccounts();
} }
Log.info("Launcher started!"); Log.info("Main window prepared!");
if (Minosoft.getStartUpJobsLeft() == 0) {
return;
}
progressDialog = new Dialog<>();
progressDialog.setHeaderText("Minosoft is still starting up...");
progressDialog.setTitle("Starting up");
GridPane grid = new GridPane();
progressBar = new ProgressBar();
progressBar.setProgress(1.0D / 5);
grid.add(progressBar, 0, 0);
progressDialog.getDialogPane().setContent(grid);
progressDialog.show();
} }
} }

View File

@ -18,34 +18,35 @@ import de.bixilon.minosoft.config.Configuration;
import de.bixilon.minosoft.config.GameConfiguration; import de.bixilon.minosoft.config.GameConfiguration;
import de.bixilon.minosoft.game.datatypes.objectLoader.versions.Versions; import de.bixilon.minosoft.game.datatypes.objectLoader.versions.Versions;
import de.bixilon.minosoft.gui.main.AccountListCell; import de.bixilon.minosoft.gui.main.AccountListCell;
import de.bixilon.minosoft.gui.main.MainWindow;
import de.bixilon.minosoft.gui.main.Server; import de.bixilon.minosoft.gui.main.Server;
import de.bixilon.minosoft.logging.Log; import de.bixilon.minosoft.logging.Log;
import de.bixilon.minosoft.logging.LogLevels; import de.bixilon.minosoft.logging.LogLevels;
import de.bixilon.minosoft.modding.event.EventManager; import de.bixilon.minosoft.modding.event.EventManager;
import de.bixilon.minosoft.modding.loading.ModLoader; import de.bixilon.minosoft.modding.loading.ModLoader;
import de.bixilon.minosoft.util.OSUtil;
import de.bixilon.minosoft.util.Util; import de.bixilon.minosoft.util.Util;
import de.bixilon.minosoft.util.mojang.api.MojangAccount; import de.bixilon.minosoft.util.mojang.api.MojangAccount;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
public class Minosoft { public class Minosoft {
public static final HashSet<EventManager> eventManagers = new HashSet<>();
private static final CountDownLatch startStatus = new CountDownLatch(2); // number of critical components (wait for them before other "big" actions)
public static HashBiMap<String, MojangAccount> accountList; public static HashBiMap<String, MojangAccount> accountList;
public static MojangAccount selectedAccount; public static MojangAccount selectedAccount;
public static ArrayList<Server> serverList; public static ArrayList<Server> serverList;
public static final HashSet<EventManager> eventManagers = new HashSet<>(); public static Configuration config;
static Configuration config;
public static void main(String[] args) { public static void main(String[] args) {
// init log thread // init log thread
Log.initThread(); Log.initThread();
Log.info("Starting..."); Log.info("Starting...");
setConfigFolder();
Log.info("Reading config file..."); Log.info("Reading config file...");
try { try {
config = new Configuration(Config.configFileName); config = new Configuration(Config.configFileName);
@ -58,52 +59,49 @@ public class Minosoft {
// set log level from config // set log level from config
Log.setLevel(LogLevels.valueOf(config.getString(GameConfiguration.GENERAL_LOG_LEVEL))); Log.setLevel(LogLevels.valueOf(config.getString(GameConfiguration.GENERAL_LOG_LEVEL)));
Log.info(String.format("Logging info with level: %s", Log.getLevel())); Log.info(String.format("Logging info with level: %s", Log.getLevel()));
Log.info("Loading versions.json...");
long mappingStartLoadingTime = System.currentTimeMillis();
try {
Versions.load(Util.readJsonAsset("mapping/versions.json"));
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
Log.info(String.format("Loaded versions mapping in %dms", (System.currentTimeMillis() - mappingStartLoadingTime)));
Log.debug("Refreshing client token...");
checkClientToken();
accountList = config.getMojangAccounts();
selectAccount(accountList.get(config.getString(GameConfiguration.ACCOUNT_SELECTED)));
serverList = config.getServers(); serverList = config.getServers();
new Thread(() -> { ArrayList<Callable<Boolean>> startCallables = new ArrayList<>();
startCallables.add(() -> {
Log.info("Loading versions.json...");
long mappingStartLoadingTime = System.currentTimeMillis();
try { try {
ModLoader.loadMods(); Versions.load(Util.readJsonAsset("mapping/versions.json"));
} catch (Exception exception) { } catch (IOException e) {
exception.printStackTrace(); e.printStackTrace();
System.exit(1);
} }
}, "ModLoader").start(); Log.info(String.format("Loaded versions mapping in %dms", (System.currentTimeMillis() - mappingStartLoadingTime)));
Launcher.start(); countDownStart(); // (another) critical component was loaded
return true;
});
startCallables.add(() -> {
Log.debug("Refreshing client token...");
checkClientToken();
accountList = config.getMojangAccounts();
selectAccount(accountList.get(config.getString(GameConfiguration.ACCOUNT_SELECTED)));
return true;
});
startCallables.add(() -> {
ModLoader.loadMods();
countDownStart(); // (another) critical component was loaded
return true;
});
startCallables.add(() -> {
Launcher.start();
return true;
});
try {
Util.executeInThreadPool("Start", startCallables);
} catch (Exception e) {
e.printStackTrace();
}
} }
/** private static void countDownStart() {
* Sets Config.homeDir to the correct folder per OS startStatus.countDown();
*/ Launcher.setProgressBar((int) startStatus.getCount());
public static void setConfigFolder() {
String path = System.getProperty("user.home");
if (!path.endsWith(File.separator)) {
path += "/";
}
path += switch (OSUtil.getOS()) {
case LINUX -> ".local/share/minosoft/";
case WINDOWS -> "AppData/Roaming/Minosoft/";
case MAC -> "Library/Application Support/Minosoft/";
case OTHER -> ".minosoft/";
};
File folder = new File(path);
if (!folder.exists() && !folder.mkdirs()) {
// failed creating folder
throw new RuntimeException(String.format("Could not create home folder (%s)!", path));
}
Config.homeDir = path;
} }
public static void checkClientToken() { public static void checkClientToken() {
@ -130,6 +128,9 @@ public class Minosoft {
} }
config.putString(GameConfiguration.ACCOUNT_SELECTED, account.getUserId()); config.putString(GameConfiguration.ACCOUNT_SELECTED, account.getUserId());
selectedAccount = account; selectedAccount = account;
if (MainWindow.accountMenu2 != null) {
MainWindow.accountMenu2.setText(String.format("Account (%s)", account.getPlayerName()));
}
account.saveToConfig(); account.saveToConfig();
} }
@ -148,4 +149,19 @@ public class Minosoft {
public static MojangAccount getSelectedAccount() { public static MojangAccount getSelectedAccount() {
return selectedAccount; return selectedAccount;
} }
/**
* Waits until all critical components are started
*/
public static void waitForStartup() {
try {
startStatus.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static int getStartUpJobsLeft() {
return (int) startStatus.getCount();
}
} }

View File

@ -42,9 +42,12 @@ public class MainWindow implements Initializable {
@FXML @FXML
public Menu accountMenu; public Menu accountMenu;
public static Menu accountMenu2;
@Override @Override
public void initialize(URL url, ResourceBundle resourceBundle) { public void initialize(URL url, ResourceBundle resourceBundle) {
serversPane.setCenter(ServerListCell.listView); serversPane.setCenter(ServerListCell.listView);
accountMenu2 = accountMenu;
if (Minosoft.getSelectedAccount() != null) { if (Minosoft.getSelectedAccount() != null) {
accountMenu.setText(String.format("Account (%s)", Minosoft.getSelectedAccount().getPlayerName())); accountMenu.setText(String.format("Account (%s)", Minosoft.getSelectedAccount().getPlayerName()));
} }

View File

@ -41,7 +41,6 @@ import javafx.scene.text.Text;
import javafx.scene.text.TextFlow; import javafx.scene.text.TextFlow;
import javafx.stage.Modality; import javafx.stage.Modality;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.util.Pair;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
@ -349,7 +348,7 @@ public class ServerListCell extends ListCell<Server> implements Initializable {
public void showInfo() { public void showInfo() {
Dialog<Pair<String, String>> dialog = new Dialog<>(); Dialog<?> dialog = new Dialog<>();
dialog.setTitle("View server info: " + server.getName()); dialog.setTitle("View server info: " + server.getName());
ButtonType loginButtonType = ButtonType.CLOSE; ButtonType loginButtonType = ButtonType.CLOSE;

View File

@ -24,6 +24,7 @@ import java.util.concurrent.LinkedBlockingQueue;
public class Log { public class Log {
final static SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); final static SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
final static LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>(); final static LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>();
final static long startTime = System.currentTimeMillis();
static LogLevels level = LogLevels.PROTOCOL; static LogLevels level = LogLevels.PROTOCOL;
public static void initThread() { public static void initThread() {
@ -64,7 +65,11 @@ public class Log {
} }
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append("["); builder.append("[");
builder.append(timeFormat.format(System.currentTimeMillis())); if (Config.logRelativeTime) {
builder.append(System.currentTimeMillis() - startTime);
} else {
builder.append(timeFormat.format(System.currentTimeMillis()));
}
builder.append("] ["); builder.append("] [");
builder.append(Thread.currentThread().getName()); builder.append(Thread.currentThread().getName());
builder.append("] ["); builder.append("] [");

View File

@ -32,6 +32,7 @@ public class ModLoader {
static final LinkedList<MinosoftMod> mods = new LinkedList<>(); static final LinkedList<MinosoftMod> mods = new LinkedList<>();
public static void loadMods() throws Exception { public static void loadMods() throws Exception {
Log.verbose("Start loading mods...");
// load all jars, parse the mod.json // load all jars, parse the mod.json
// sort the list and prioritize // sort the list and prioritize
// load all lists and dependencies async // load all lists and dependencies async
@ -85,6 +86,7 @@ public class ModLoader {
Log.warn(String.format("An error occurred while loading %s", instance.getInfo())); Log.warn(String.format("An error occurred while loading %s", instance.getInfo()));
} }
}); });
Log.verbose("Loading all mods finished!");
} }
public static MinosoftMod loadMod(File file) { public static MinosoftMod loadMod(File file) {

View File

@ -22,10 +22,11 @@ import de.bixilon.minosoft.protocol.protocol.OutByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
public class PluginChannelHandler { public class PluginChannelHandler {
final HashMap<String, ArrayList<ChannelHandler>> channels = new HashMap<>(); final HashMap<String, HashSet<ChannelHandler>> channels = new HashMap<>();
final HashMap<String, ArrayList<LoginChannelHandler>> loginChannels = new HashMap<>(); final HashMap<String, HashSet<LoginChannelHandler>> loginChannels = new HashMap<>();
final ArrayList<String> registeredClientChannels = new ArrayList<>(); final ArrayList<String> registeredClientChannels = new ArrayList<>();
final ArrayList<String> registeredServerChannels = new ArrayList<>(); final ArrayList<String> registeredServerChannels = new ArrayList<>();
final Connection connection; final Connection connection;
@ -37,7 +38,7 @@ public class PluginChannelHandler {
public void registerClientHandler(String name, ChannelHandler handler) { public void registerClientHandler(String name, ChannelHandler handler) {
if (channels.get(name) == null) { if (channels.get(name) == null) {
// no channel with that name was registered yet // no channel with that name was registered yet
ArrayList<ChannelHandler> handlerList = new ArrayList<>(); HashSet<ChannelHandler> handlerList = new HashSet<>();
handlerList.add(handler); handlerList.add(handler);
channels.put(name, handlerList); channels.put(name, handlerList);
return; return;
@ -50,7 +51,7 @@ public class PluginChannelHandler {
public void registerLoginClientHandler(String name, LoginChannelHandler handler) { public void registerLoginClientHandler(String name, LoginChannelHandler handler) {
if (loginChannels.get(name) == null) { if (loginChannels.get(name) == null) {
// no channel with that name was registered yet // no channel with that name was registered yet
ArrayList<LoginChannelHandler> handlerList = new ArrayList<>(); HashSet<LoginChannelHandler> handlerList = new HashSet<>();
handlerList.add(handler); handlerList.add(handler);
loginChannels.put(name, handlerList); loginChannels.put(name, handlerList);
return; return;

View File

@ -92,6 +92,7 @@ public class Connection {
this.desiredVersionNumber = protocolId; this.desiredVersionNumber = protocolId;
Thread resolveThread = new Thread(() -> { Thread resolveThread = new Thread(() -> {
Minosoft.waitForStartup(); // wait until mappings are loaded
if (desiredVersionNumber != -1) { if (desiredVersionNumber != -1) {
setVersion(Versions.getVersionById(desiredVersionNumber)); setVersion(Versions.getVersionById(desiredVersionNumber));
} }
@ -212,7 +213,7 @@ public class Connection {
return false; return false;
} }
void startHandlingThread() { private void startHandlingThread() {
handleThread = new Thread(() -> { handleThread = new Thread(() -> {
while (isConnected()) { while (isConnected()) {
ClientboundPacket packet; ClientboundPacket packet;
@ -242,7 +243,7 @@ public class Connection {
return pluginChannelHandler; return pluginChannelHandler;
} }
public void registerDefaultChannels() { private void registerDefaultChannels() {
// MC|Brand // MC|Brand
getPluginChannelHandler().registerClientHandler(DefaultPluginChannels.MC_BRAND.getChangeableIdentifier().get(version.getProtocolVersion()), (handler, buffer) -> { getPluginChannelHandler().registerClientHandler(DefaultPluginChannels.MC_BRAND.getChangeableIdentifier().get(version.getProtocolVersion()), (handler, buffer) -> {
String serverVersion; String serverVersion;

View File

@ -26,8 +26,8 @@ import java.net.URL;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@ -202,7 +202,7 @@ public final class Util {
fileOutputStream.close(); fileOutputStream.close();
} }
public static <T> void executeInThreadPool(String name, HashSet<Callable<T>> callables) throws InterruptedException { public static <T> void executeInThreadPool(String name, Collection<Callable<T>> callables) throws InterruptedException {
ExecutorService phaseLoader = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), getThreadFactory(name)); ExecutorService phaseLoader = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), getThreadFactory(name));
phaseLoader.invokeAll(callables); phaseLoader.invokeAll(callables);
} }