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
## 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
```json
{

View File

@ -13,9 +13,34 @@
package de.bixilon.minosoft;
import de.bixilon.minosoft.util.OSUtil;
import java.io.File;
public class Config {
public static final String configFileName = "config.json";
public static final boolean skipAuthentication = false; // only for offline development
public static final boolean colorLog = true;
public static final String configFileName = "config.json"; // Path of the minosoft base configuration (located in AppData/Minosoft/config)
public static final boolean skipAuthentication = false; // disables all connections to mojang
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;
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.logging.Log;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Dialog;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
@ -33,14 +37,32 @@ import javafx.util.Callback;
import java.io.IOException;
public class Launcher extends Application {
private static ProgressBar progressBar;
private static Dialog<Boolean> progressDialog;
public static void start() {
Log.info("Starting launcher...");
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
public void start(Stage primaryStage) throws IOException {
Log.info("Starting launcher...");
Log.info("Preparing main window...");
GUITools.versionList.setCellFactory(new Callback<>() {
@Override
@ -74,6 +96,18 @@ public class Launcher extends Application {
if (Minosoft.getSelectedAccount() == null) {
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.game.datatypes.objectLoader.versions.Versions;
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.logging.Log;
import de.bixilon.minosoft.logging.LogLevels;
import de.bixilon.minosoft.modding.event.EventManager;
import de.bixilon.minosoft.modding.loading.ModLoader;
import de.bixilon.minosoft.util.OSUtil;
import de.bixilon.minosoft.util.Util;
import de.bixilon.minosoft.util.mojang.api.MojangAccount;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
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 MojangAccount selectedAccount;
public static ArrayList<Server> serverList;
public static final HashSet<EventManager> eventManagers = new HashSet<>();
static Configuration config;
public static Configuration config;
public static void main(String[] args) {
// init log thread
Log.initThread();
Log.info("Starting...");
setConfigFolder();
Log.info("Reading config file...");
try {
config = new Configuration(Config.configFileName);
@ -58,52 +59,49 @@ public class Minosoft {
// set log level from config
Log.setLevel(LogLevels.valueOf(config.getString(GameConfiguration.GENERAL_LOG_LEVEL)));
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();
new Thread(() -> {
ArrayList<Callable<Boolean>> startCallables = new ArrayList<>();
startCallables.add(() -> {
Log.info("Loading versions.json...");
long mappingStartLoadingTime = System.currentTimeMillis();
try {
ModLoader.loadMods();
} catch (Exception exception) {
exception.printStackTrace();
Versions.load(Util.readJsonAsset("mapping/versions.json"));
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}, "ModLoader").start();
Launcher.start();
Log.info(String.format("Loaded versions mapping in %dms", (System.currentTimeMillis() - mappingStartLoadingTime)));
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();
}
}
/**
* Sets Config.homeDir to the correct folder per OS
*/
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;
private static void countDownStart() {
startStatus.countDown();
Launcher.setProgressBar((int) startStatus.getCount());
}
public static void checkClientToken() {
@ -130,6 +128,9 @@ public class Minosoft {
}
config.putString(GameConfiguration.ACCOUNT_SELECTED, account.getUserId());
selectedAccount = account;
if (MainWindow.accountMenu2 != null) {
MainWindow.accountMenu2.setText(String.format("Account (%s)", account.getPlayerName()));
}
account.saveToConfig();
}
@ -148,4 +149,19 @@ public class Minosoft {
public static MojangAccount getSelectedAccount() {
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
public Menu accountMenu;
public static Menu accountMenu2;
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
serversPane.setCenter(ServerListCell.listView);
accountMenu2 = accountMenu;
if (Minosoft.getSelectedAccount() != null) {
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.stage.Modality;
import javafx.stage.Stage;
import javafx.util.Pair;
import java.io.IOException;
import java.net.URL;
@ -349,7 +348,7 @@ public class ServerListCell extends ListCell<Server> implements Initializable {
public void showInfo() {
Dialog<Pair<String, String>> dialog = new Dialog<>();
Dialog<?> dialog = new Dialog<>();
dialog.setTitle("View server info: " + server.getName());
ButtonType loginButtonType = ButtonType.CLOSE;

View File

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

View File

@ -32,6 +32,7 @@ public class ModLoader {
static final LinkedList<MinosoftMod> mods = new LinkedList<>();
public static void loadMods() throws Exception {
Log.verbose("Start loading mods...");
// load all jars, parse the mod.json
// sort the list and prioritize
// 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.verbose("Loading all mods finished!");
}
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.HashMap;
import java.util.HashSet;
public class PluginChannelHandler {
final HashMap<String, ArrayList<ChannelHandler>> channels = new HashMap<>();
final HashMap<String, ArrayList<LoginChannelHandler>> loginChannels = new HashMap<>();
final HashMap<String, HashSet<ChannelHandler>> channels = new HashMap<>();
final HashMap<String, HashSet<LoginChannelHandler>> loginChannels = new HashMap<>();
final ArrayList<String> registeredClientChannels = new ArrayList<>();
final ArrayList<String> registeredServerChannels = new ArrayList<>();
final Connection connection;
@ -37,7 +38,7 @@ public class PluginChannelHandler {
public void registerClientHandler(String name, ChannelHandler handler) {
if (channels.get(name) == null) {
// no channel with that name was registered yet
ArrayList<ChannelHandler> handlerList = new ArrayList<>();
HashSet<ChannelHandler> handlerList = new HashSet<>();
handlerList.add(handler);
channels.put(name, handlerList);
return;
@ -50,7 +51,7 @@ public class PluginChannelHandler {
public void registerLoginClientHandler(String name, LoginChannelHandler handler) {
if (loginChannels.get(name) == null) {
// no channel with that name was registered yet
ArrayList<LoginChannelHandler> handlerList = new ArrayList<>();
HashSet<LoginChannelHandler> handlerList = new HashSet<>();
handlerList.add(handler);
loginChannels.put(name, handlerList);
return;

View File

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

View File

@ -26,8 +26,8 @@ import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
@ -202,7 +202,7 @@ public final class Util {
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));
phaseLoader.invokeAll(callables);
}