Improvements, fixes, LogPrintStream

The server list cell now has a border according to its connection state: Connecting/Loading: Yellow, Disconnecting: Orange, Failed: Red, Playing: Green

Some fixes with multithreading in the connection.
MultiBlockChange with AIR will not throw a NullPointerException
All calls to System.out and System.err will be logged with the logger
This commit is contained in:
Bixilon 2021-01-07 17:29:15 +01:00
parent caad618d3f
commit 26f96a3504
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
11 changed files with 176 additions and 94 deletions

View File

@ -112,6 +112,9 @@ public class VersionTweaker {
}
public static Block transformBlock(Block originalBlock, Chunk chunk, InChunkSectionLocation location, byte sectionHeight) {
if (originalBlock == null) {
return null;
}
switch (originalBlock.getFullIdentifier()) {
case "minecraft:grass" -> {
Block above = getBlockAbove(chunk, location, sectionHeight);

View File

@ -225,6 +225,7 @@ public class ServerListCell extends ListCell<Server> implements Initializable {
// clear all cells
setStyle(null);
this.root.getStyleClass().removeAll("list-cell-connected");
this.root.getStyleClass().removeAll("list-cell-connecting");
this.motdField.getChildren().clear();
this.brandField.setText("");
this.brandField.setTooltip(null);
@ -285,6 +286,7 @@ public class ServerListCell extends ListCell<Server> implements Initializable {
if (!this.canConnect || this.server.getLastPing() == null) {
return;
}
this.root.getStyleClass().add("list-cell-connecting");
new Thread(() -> {
Connection connection = new Connection(Connection.lastConnectionId++, this.server.getAddress(), new Player(Minosoft.getConfig().getSelectedAccount()));
Version version;
@ -314,19 +316,27 @@ public class ServerListCell extends ListCell<Server> implements Initializable {
return;
}
Platform.runLater(() -> {
this.root.getStyleClass().removeAll("list-cell-connecting");
this.root.getStyleClass().removeAll("list-cell-connected");
this.root.getStyleClass().removeAll("list-cell-disconnecting");
this.root.getStyleClass().removeAll("list-cell-failed");
this.root.getStyleClass().add(switch (connection.getConnectionState()) {
case CONNECTING, HANDSHAKING, LOGIN -> "list-cell-connecting";
case PLAY -> "list-cell-connected";
case DISCONNECTING -> "list-cell-disconnecting";
case FAILED, FAILED_NO_RETRY -> "list-cell-failed";
default -> "";
});
if (!connection.isConnected()) {
// maybe we got disconnected
if (!this.server.isConnected()) {
setStyle(null);
this.root.getStyleClass().removeAll("list-cell-connected");
this.optionsSessions.setDisable(true);
this.optionsConnect.setDisable(false);
return;
}
}
this.optionsConnect.setDisable(Minosoft.getConfig().getSelectedAccount() == connection.getPlayer().getAccount());
this.root.getStyleClass().add("list-cell-connected");
this.optionsSessions.setDisable(false);
});
}

View File

@ -168,7 +168,6 @@ public class Connection {
}
public void disconnect() {
setConnectionState(ConnectionStates.DISCONNECTING);
this.network.disconnect();
this.handleThread.interrupt();
}
@ -287,12 +286,14 @@ public class Connection {
}
public void setConnectionState(ConnectionStates state) {
if (this.state == state) {
return;
}
Log.verbose("ConnectionState changed: " + state);
ConnectionStates previousState = this.state;
this.state = state;
synchronized (state) {
if (this.state == state) {
return;
}
Log.verbose("ConnectionState changed: " + state);
this.state = state;
}
switch (state) {
case HANDSHAKING -> {
// get and add all events, that are connection specific
@ -435,8 +436,12 @@ public class Connection {
this.pong = pong;
}
public boolean shouldDisconnect() {
return getConnectionState() == ConnectionStates.DISCONNECTING || getConnectionState() == ConnectionStates.DISCONNECTED || getConnectionState() == ConnectionStates.FAILED || getConnectionState() == ConnectionStates.FAILED_NO_RETRY;
}
@Override
public String toString() {
return String.format("(id=%d, address=%s, account=\"%s\")", getConnectionId(), getAddress(), getPlayer().getAccount());
return String.format("(id=%d, address=%s, account=\"%s\")", getConnectionId(), getAddress(), ((this.player == null) ? null : getPlayer().getAccount()));
}
}

View File

@ -86,7 +86,7 @@ public abstract class Network {
if (this.connection.getConnectionState() == ConnectionStates.PLAY) {
throw new PacketParseException(e);
}
throw new RuntimeException(e);
throw new UnknownPacketException(e);
}
}

View File

@ -94,7 +94,7 @@ public class BlockingSocketNetwork extends Network {
initSendThread();
this.socketReceiveThread.setName(String.format("%d/SocketR", this.connection.getConnectionId()));
this.socketReceiveThread.setName(String.format("%d/SocketReceive", this.connection.getConnectionId()));
while (this.connection.getConnectionState() != ConnectionStates.DISCONNECTING) {
@ -107,13 +107,15 @@ public class BlockingSocketNetwork extends Network {
Log.printException(e, LogLevels.PROTOCOL);
}
}
disconnect();
this.connection.disconnect();
} catch (Throwable e) {
// Could not connect
this.connection.setConnectionState(ConnectionStates.DISCONNECTING);
if (this.socketSendThread != null) {
this.socketSendThread.interrupt();
}
if (e instanceof SocketException && e.getMessage().equals("Socket closed")) {
this.connection.setConnectionState(ConnectionStates.DISCONNECTED);
return;
}
Log.printException(e, LogLevels.PROTOCOL);
@ -131,7 +133,12 @@ public class BlockingSocketNetwork extends Network {
@Override
public void disconnect() {
if (this.connection.shouldDisconnect()) {
// already trying
return;
}
this.connection.setConnectionState(ConnectionStates.DISCONNECTING);
this.queue.clear();
try {
this.socket.close();
} catch (IOException e) {
@ -180,7 +187,7 @@ public class BlockingSocketNetwork extends Network {
}
} catch (IOException | InterruptedException ignored) {
}
}, String.format("%d/SocketS", this.connection.getConnectionId()));
}, String.format("%d/SocketSend", this.connection.getConnectionId()));
this.socketSendThread.start();
}

View File

@ -176,6 +176,7 @@ public class NonBlockingSocketNetwork extends Network {
return;
}
this.connection.setConnectionState(ConnectionStates.DISCONNECTING);
this.queue.clear();
try {
this.socketChannel.close();
} catch (IOException e) {

View File

@ -1,69 +0,0 @@
/*
* Minosoft
* Copyright (C) 2020 Moritz Zwerger
*
* 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 <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.protocol.protocol;
import com.google.common.collect.HashBiMap;
import java.util.HashMap;
import java.util.Map;
public final class Protocol {
private static final HashMap<ConnectionStates, HashBiMap<Packets.Serverbound, Integer>> SERVERBOUND_PACKET_MAPPING = new HashMap<>();
private static final HashMap<ConnectionStates, HashBiMap<Packets.Clientbound, Integer>> CLIENTBOUND_PACKET_MAPPING = new HashMap<>();
static {
SERVERBOUND_PACKET_MAPPING.put(ConnectionStates.HANDSHAKING, HashBiMap.create(Map.of(
Packets.Serverbound.HANDSHAKING_HANDSHAKE, 0x00
)));
SERVERBOUND_PACKET_MAPPING.put(ConnectionStates.STATUS, HashBiMap.create(Map.of(
Packets.Serverbound.STATUS_REQUEST, 0x00,
Packets.Serverbound.STATUS_PING, 0x01)
));
SERVERBOUND_PACKET_MAPPING.put(ConnectionStates.LOGIN, HashBiMap.create(Map.of(
Packets.Serverbound.LOGIN_LOGIN_START, 0x00,
Packets.Serverbound.LOGIN_ENCRYPTION_RESPONSE, 0x01,
Packets.Serverbound.LOGIN_PLUGIN_RESPONSE, 0x02
)));
SERVERBOUND_PACKET_MAPPING.put(ConnectionStates.PLAY, HashBiMap.create());
// clientbound
CLIENTBOUND_PACKET_MAPPING.put(ConnectionStates.STATUS, HashBiMap.create(Map.of(
Packets.Clientbound.STATUS_RESPONSE, 0x00,
Packets.Clientbound.STATUS_PONG, 0x01
)));
CLIENTBOUND_PACKET_MAPPING.put(ConnectionStates.LOGIN, HashBiMap.create(Map.of(
Packets.Clientbound.LOGIN_DISCONNECT, 0x00,
Packets.Clientbound.LOGIN_ENCRYPTION_REQUEST, 0x01,
Packets.Clientbound.LOGIN_LOGIN_SUCCESS, 0x02,
Packets.Clientbound.LOGIN_SET_COMPRESSION, 0x03,
Packets.Clientbound.LOGIN_PLUGIN_REQUEST, 0x04
)));
CLIENTBOUND_PACKET_MAPPING.put(ConnectionStates.PLAY, HashBiMap.create());
}
@SuppressWarnings("ConstantConditions")
public static int getPacketCommand(Packets.Serverbound packet) {
return SERVERBOUND_PACKET_MAPPING.get(packet.getState()).get(packet);
}
public static Packets.Clientbound getPacketByCommand(ConnectionStates state, int command) {
return CLIENTBOUND_PACKET_MAPPING.get(state).inverse().get(command);
}
}

View File

@ -0,0 +1,72 @@
/*
* Minosoft
* Copyright (C) 2020 Moritz Zwerger
*
* 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 <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.protocol.protocol
import com.google.common.collect.HashBiMap
import de.bixilon.minosoft.protocol.protocol.Packets.Clientbound
import de.bixilon.minosoft.protocol.protocol.Packets.Serverbound
import java.util.*
object Protocol {
private val SERVERBOUND_PACKET_MAPPING = HashMap<ConnectionStates, HashBiMap<Serverbound, Int>>()
private val CLIENTBOUND_PACKET_MAPPING = HashMap<ConnectionStates, HashBiMap<Clientbound, Int>>()
@JvmStatic
fun getPacketCommand(packet: Serverbound): Int {
return SERVERBOUND_PACKET_MAPPING[packet.state]!![packet]!!
}
@JvmStatic
fun getPacketByCommand(state: ConnectionStates, command: Int): Clientbound? {
return CLIENTBOUND_PACKET_MAPPING[state]?.inverse()?.get(command)
}
init {
SERVERBOUND_PACKET_MAPPING[ConnectionStates.HANDSHAKING] = HashBiMap.create(
mapOf(
Serverbound.HANDSHAKING_HANDSHAKE to 0x00
)
)
SERVERBOUND_PACKET_MAPPING[ConnectionStates.STATUS] = HashBiMap.create(
mapOf(
Serverbound.STATUS_REQUEST to 0x00,
Serverbound.STATUS_PING to 0x01
)
)
SERVERBOUND_PACKET_MAPPING[ConnectionStates.LOGIN] = HashBiMap.create(
mapOf(
Serverbound.LOGIN_LOGIN_START to 0x00,
Serverbound.LOGIN_ENCRYPTION_RESPONSE to 0x01,
Serverbound.LOGIN_PLUGIN_RESPONSE to 0x02
)
)
// clientbound
CLIENTBOUND_PACKET_MAPPING[ConnectionStates.STATUS] = HashBiMap.create(
mapOf(
Clientbound.STATUS_RESPONSE to 0x00,
Clientbound.STATUS_PONG to 0x01
)
)
CLIENTBOUND_PACKET_MAPPING[ConnectionStates.LOGIN] = HashBiMap.create(
mapOf(
Clientbound.LOGIN_DISCONNECT to 0x00,
Clientbound.LOGIN_ENCRYPTION_REQUEST to 0x01,
Clientbound.LOGIN_LOGIN_SUCCESS to 0x02,
Clientbound.LOGIN_SET_COMPRESSION to 0x03,
Clientbound.LOGIN_PLUGIN_REQUEST to 0x04
)
)
CLIENTBOUND_PACKET_MAPPING[ConnectionStates.PLAY] = HashBiMap.create()
}
}

View File

@ -18,6 +18,7 @@ import de.bixilon.minosoft.data.text.ChatColors;
import de.bixilon.minosoft.data.text.PostChatFormattingCodes;
import de.bixilon.minosoft.data.text.RGBColor;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.concurrent.LinkedBlockingQueue;
@ -25,9 +26,15 @@ public class Log {
public static final long MINOSOFT_START_TIME = System.currentTimeMillis();
private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private static final LinkedBlockingQueue<String> LOG_QUEUE = new LinkedBlockingQueue<>();
private static final PrintStream SYSTEM_ERR_STREAM = System.err;
private static final PrintStream SYSTEM_OUT_STREAM = System.out;
private static final PrintStream ERROR_PRINT_STREAM = new LogPrintStream(LogLevels.WARNING);
private static final PrintStream OUT_PRINT_STREAM = new LogPrintStream(LogLevels.INFO);
private static LogLevels level = LogLevels.PROTOCOL;
static {
System.setErr(ERROR_PRINT_STREAM);
System.setOut(OUT_PRINT_STREAM);
new Thread(() -> {
while (true) {
// something to print
@ -38,8 +45,7 @@ public class Log {
e.printStackTrace();
continue;
}
System.out.println(message);
System.out.flush();
SYSTEM_OUT_STREAM.println(message);
// ToDo: log to file
}
@ -50,6 +56,19 @@ public class Log {
log(level, "", color, message, format);
}
public static void log(LogLevels level, Object message, Object... format) {
log(level, "", switch (level) {
case GAME -> ChatColors.GREEN;
case FATAL -> ChatColors.DARK_RED;
case WARNING -> ChatColors.RED;
case DEBUG -> ChatColors.GRAY;
case VERBOSE -> ChatColors.YELLOW;
case PROTOCOL -> ChatColors.BLUE;
case MOJANG -> ChatColors.AQUA;
case INFO -> ChatColors.WHITE;
}, message, format);
}
public static void log(LogLevels level, String prefix, RGBColor color, Object message, Object... format) {
if (level.ordinal() > Log.level.ordinal()) {
// log level too low
@ -96,7 +115,7 @@ public class Log {
* @param message Raw message to log
*/
public static void game(Object message, Object... format) {
log(LogLevels.GAME, ChatColors.GREEN, message, format);
log(LogLevels.GAME, message, format);
}
/**
@ -105,7 +124,7 @@ public class Log {
* @param message Raw message to log
*/
public static void fatal(Object message, Object... format) {
log(LogLevels.FATAL, ChatColors.DARK_RED, message, format);
log(LogLevels.FATAL, message, format);
}
/**
@ -114,7 +133,7 @@ public class Log {
* @param message Raw message to log
*/
public static void warn(Object message, Object... format) {
log(LogLevels.WARNING, ChatColors.RED, message, format);
log(LogLevels.WARNING, message, format);
}
/**
@ -123,7 +142,7 @@ public class Log {
* @param message Raw message to log
*/
public static void debug(Object message, Object... format) {
log(LogLevels.DEBUG, ChatColors.GRAY, message, format);
log(LogLevels.DEBUG, message, format);
}
/**
@ -132,7 +151,7 @@ public class Log {
* @param message Raw message to log
*/
public static void verbose(Object message, Object... format) {
log(LogLevels.VERBOSE, ChatColors.YELLOW, message, format);
log(LogLevels.VERBOSE, message, format);
}
/**
@ -141,7 +160,7 @@ public class Log {
* @param message Raw message to log
*/
public static void protocol(Object message, Object... format) {
log(LogLevels.PROTOCOL, ChatColors.BLUE, message, format);
log(LogLevels.PROTOCOL, message, format);
}
/**
@ -150,7 +169,7 @@ public class Log {
* @param message Raw message to log
*/
public static void mojang(Object message, Object... format) {
log(LogLevels.MOJANG, ChatColors.AQUA, message, format);
log(LogLevels.MOJANG, message, format);
}
/**
@ -159,7 +178,7 @@ public class Log {
* @param message Raw message to log
*/
public static void info(Object message, Object... format) {
log(LogLevels.INFO, ChatColors.WHITE, message, format);
log(LogLevels.INFO, message, format);
}
public static LogLevels getLevel() {

View File

@ -0,0 +1,19 @@
package de.bixilon.minosoft.util.logging;
import java.io.OutputStream;
import java.io.PrintStream;
public class LogPrintStream extends PrintStream {
private final LogLevels level;
public LogPrintStream(LogLevels level) {
super(OutputStream.nullOutputStream());
this.level = level;
}
@Override
public void print(String s) {
Log.log(this.level, s);
}
}

View File

@ -189,6 +189,21 @@
-fx-border-width: 3px;
}
.list-cell-connecting {
-fx-border-color: yellow;
-fx-border-width: 3px;
}
.list-cell-disconnecting {
-fx-border-color: orange;
-fx-border-width: 3px;
}
.list-cell-failed {
-fx-border-color: red;
-fx-border-width: 3px;
}
.list-cell-selected {
-fx-border-color: #00aa00;
-fx-border-width: 3px;