From 62ff9606c11697fabc74d61e6043225690888ca5 Mon Sep 17 00:00:00 2001 From: s809 <43530948+s809@users.noreply.github.com> Date: Thu, 6 Jul 2023 21:57:02 +0500 Subject: [PATCH] Outline what interaction should somewhat look like (not an actual interaction) Fix client repeatedly registering handlers on reconnects --- .../core/network/NetworkClient.java | 63 ++++++++++++------- .../core/network/NetworkEventSource.java | 12 +++- .../core/network/NetworkServer.java | 36 +++++++---- .../core/network/messages/AckMessage.java | 28 +++++++++ .../core/network/messages/CloseMessage.java | 3 +- .../network/messages/CloseReasonMessage.java | 9 ++- .../core/network/messages/HelloMessage.java | 7 +-- .../network/messages/LodConfigMessage.java | 12 ++-- .../core/network/messages/Message.java | 22 ------- ...rIdMessage.java => PlayerUUIDMessage.java} | 6 +- .../messages/RequestChunksMessage.java | 17 +++++ .../network/protocol/INetworkMessage.java | 6 ++ .../core/network/protocol/INetworkObject.java | 26 ++++++++ .../core/network/protocol/MessageDecoder.java | 6 +- .../core/network/protocol/MessageEncoder.java | 5 +- .../core/network/protocol/MessageHandler.java | 15 +++-- .../network/protocol/MessageRegistry.java | 40 ++++++++---- .../core/world/DhClientWorld.java | 22 +++++++ .../distanthorizons/core/world/DhPlayer.java | 19 ------ .../core/world/DhRemotePlayer.java | 31 +++++++++ .../core/world/DhServerWorld.java | 57 ++++++++++------- .../minecraft/IMinecraftClientWrapper.java | 7 ++- 22 files changed, 310 insertions(+), 139 deletions(-) create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/messages/AckMessage.java delete mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/messages/Message.java rename core/src/main/java/com/seibel/distanthorizons/core/network/messages/{PlayerIdMessage.java => PlayerUUIDMessage.java} (69%) create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/messages/RequestChunksMessage.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/protocol/INetworkMessage.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/protocol/INetworkObject.java delete mode 100644 core/src/main/java/com/seibel/distanthorizons/core/world/DhPlayer.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/world/DhRemotePlayer.java diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkClient.java b/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkClient.java index 7fc6df361..f22102a2a 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkClient.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkClient.java @@ -2,6 +2,7 @@ package com.seibel.distanthorizons.core.network; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.network.messages.CloseMessage; +import com.seibel.distanthorizons.core.network.messages.CloseReasonMessage; import com.seibel.distanthorizons.core.network.messages.HelloMessage; import com.seibel.distanthorizons.core.network.protocol.DhNetworkChannelInitializer; import io.netty.bootstrap.Bootstrap; @@ -13,6 +14,7 @@ import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import org.apache.logging.log4j.Logger; +import java.net.InetSocketAddress; import java.util.concurrent.TimeUnit; public class NetworkClient extends NetworkEventSource implements AutoCloseable { @@ -21,14 +23,15 @@ public class NetworkClient extends NetworkEventSource implements AutoCloseable { private enum State { OPEN, RECONNECT, + RECONNECT_FORCE, + CLOSE_WAIT, CLOSED } private static final int FAILURE_RECONNECT_DELAY_SEC = 5; // TODO move to config of some sort - private final String host; - private final int port; + private final InetSocketAddress address; private final EventLoopGroup workerGroup = new NioEventLoopGroup(); private final Bootstrap clientBootstrap = new Bootstrap() @@ -37,19 +40,36 @@ public class NetworkClient extends NetworkEventSource implements AutoCloseable { .option(ChannelOption.SO_KEEPALIVE, true) .handler(new DhNetworkChannelInitializer(messageHandler)); - private State state = State.OPEN; + private State state; private Channel channel; public NetworkClient(String host, int port) { - this.host = host; - this.port = port; + this.address = new InetSocketAddress(host, port); + + registerHandlers(); connect(); } - private void connect() { - LOGGER.info("Connecting to {}:{}", host, port); + private void registerHandlers() { + registerHandler(HelloMessage.class, (msg, ctx) -> { + LOGGER.info("Connected to server: {}", ctx.channel().remoteAddress()); + }); - ChannelFuture connectFuture = clientBootstrap.connect(host, port); + registerHandler(CloseReasonMessage.class, (msg, ctx) -> { + LOGGER.info(msg.reason); + state = State.CLOSE_WAIT; + }); + + registerHandler(CloseMessage.class, (msg, ctx) -> { + LOGGER.info("Disconnected from server: {}", ctx.channel().remoteAddress()); + }); + } + + private void connect() { + LOGGER.info("Connecting to server: {}", address); + state = State.OPEN; + + ChannelFuture connectFuture = clientBootstrap.connect(address); connectFuture.addListener((ChannelFuture channelFuture) -> { if (!channelFuture.isSuccess()) return; channel.writeAndFlush(new HelloMessage()); @@ -57,24 +77,25 @@ public class NetworkClient extends NetworkEventSource implements AutoCloseable { channel = connectFuture.channel(); channel.closeFuture().addListener((ChannelFuture channelFuture) -> { - if (state == State.CLOSED) return; - - workerGroup.schedule(this::connect, state == State.RECONNECT ? 0 : FAILURE_RECONNECT_DELAY_SEC, TimeUnit.SECONDS); - state = State.OPEN; - }); - - registerHandler(HelloMessage.class, (msg, ctx) -> { - LOGGER.info("Connected."); - }); - - registerHandler(CloseMessage.class, (msg, ctx) -> { - LOGGER.info("Disconnected."); + switch (state) { + case CLOSE_WAIT: + close(); + break; + case OPEN: + state = State.RECONNECT; + workerGroup.schedule(this::connect, FAILURE_RECONNECT_DELAY_SEC, TimeUnit.SECONDS); + break; + case RECONNECT_FORCE: + state = State.RECONNECT; + workerGroup.schedule(this::connect, 0, TimeUnit.SECONDS); + break; + } }); } /** Kills the current connection, triggering auto-reconnection immediately. */ public void reconnect() { - state = State.RECONNECT; + state = State.RECONNECT_FORCE; channel.disconnect(); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkEventSource.java b/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkEventSource.java index df7338896..e14bdc1da 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkEventSource.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkEventSource.java @@ -1,7 +1,8 @@ package com.seibel.distanthorizons.core.network; +import com.seibel.distanthorizons.core.network.messages.AckMessage; import com.seibel.distanthorizons.core.network.messages.HelloMessage; -import com.seibel.distanthorizons.core.network.messages.Message; +import com.seibel.distanthorizons.core.network.protocol.INetworkMessage; import com.seibel.distanthorizons.core.network.protocol.MessageHandler; import com.seibel.distanthorizons.coreapi.ModInfo; import io.netty.channel.ChannelHandlerContext; @@ -25,10 +26,17 @@ public abstract class NetworkEventSource implements AutoCloseable { }); } - public void registerHandler(Class clazz, BiConsumer handler) { + public void registerHandler(Class clazz, BiConsumer handler) { messageHandler.registerHandler(clazz, handler); } + public void registerAckHandler(Class clazz, Consumer handler) { + messageHandler.registerHandler(AckMessage.class, (msg, ctx) -> { + if (msg.messageType == clazz) + handler.accept(ctx); + }); + } + public void close(String reason) throws Exception { closeReason = reason; close(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkServer.java b/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkServer.java index 1bcb84431..2c5813ba2 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkServer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkServer.java @@ -2,11 +2,13 @@ package com.seibel.distanthorizons.core.network; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.network.messages.CloseMessage; +import com.seibel.distanthorizons.core.network.messages.CloseReasonMessage; import com.seibel.distanthorizons.core.network.messages.HelloMessage; -import com.seibel.distanthorizons.core.network.protocol.*; +import com.seibel.distanthorizons.core.network.protocol.DhNetworkChannelInitializer; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; @@ -22,13 +24,28 @@ public class NetworkServer extends NetworkEventSource implements AutoCloseable { private final EventLoopGroup bossGroup = new NioEventLoopGroup(1); private final EventLoopGroup workerGroup = new NioEventLoopGroup(); - Channel channel; + private Channel channel; public NetworkServer(int port) { this.port = port; LOGGER.info("Starting server on port {}", port); + registerHandlers(); + bind(); + } + private void registerHandlers() { + registerHandler(HelloMessage.class, (msg, ctx) -> { + LOGGER.info("Client connected: {}", ctx.channel().remoteAddress()); + ctx.channel().writeAndFlush(new HelloMessage()); + }); + + registerHandler(CloseMessage.class, (msg, ctx) -> { + LOGGER.info("Client disconnected: {}", ctx.channel().remoteAddress()); + }); + } + + private void bind() { ServerBootstrap bootstrap = new ServerBootstrap() .group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) @@ -40,20 +57,17 @@ public class NetworkServer extends NetworkEventSource implements AutoCloseable { if (!channelFuture.isSuccess()) throw new RuntimeException("Failed to bind: " + channelFuture.cause()); - LOGGER.info("Server is ready"); + LOGGER.info("Server is started on port {}", port); }); channel = bindFuture.channel(); channel.closeFuture().addListener(future -> close()); + } - registerHandler(HelloMessage.class, (msg, ctx) -> { - LOGGER.info("Client connected: {}", ctx.channel().remoteAddress()); - ctx.channel().writeAndFlush(new HelloMessage()); - }); - - registerHandler(CloseMessage.class, (msg, ctx) -> { - LOGGER.info("Client disconnected: {}", ctx.channel().remoteAddress()); - }); + public void disconnectClient(ChannelHandlerContext ctx, String reason) { + ctx.channel().config().setAutoRead(false); + ctx.writeAndFlush(new CloseReasonMessage(reason)) + .addListener(future -> ctx.close()); } @Override diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/AckMessage.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/AckMessage.java new file mode 100644 index 000000000..7c9ed2154 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/AckMessage.java @@ -0,0 +1,28 @@ +package com.seibel.distanthorizons.core.network.messages; + +import com.seibel.distanthorizons.core.network.protocol.INetworkMessage; +import com.seibel.distanthorizons.core.network.protocol.MessageRegistry; +import io.netty.buffer.ByteBuf; + +/** + * Simple empty response message. + * This message is not sent automatically. + */ +public class AckMessage implements INetworkMessage { + public Class messageType; + + public AckMessage() { } + public AckMessage(Class messageType) { + this.messageType = messageType; + } + + @Override + public void encode(ByteBuf out) { + out.writeInt(MessageRegistry.INSTANCE.getMessageId(messageType)); + } + + @Override + public void decode(ByteBuf in) { + messageType = MessageRegistry.INSTANCE.getClassById(in.readInt()); + } +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/CloseMessage.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/CloseMessage.java index aed5933c6..8d81cae2a 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/CloseMessage.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/CloseMessage.java @@ -1,12 +1,13 @@ package com.seibel.distanthorizons.core.network.messages; +import com.seibel.distanthorizons.core.network.protocol.INetworkMessage; import io.netty.buffer.ByteBuf; /** * This is not a "real" message, and only used as indication of disconnection. * To send a "disconnect reason" message, use {@link CloseReasonMessage}. */ -public class CloseMessage extends Message { +public class CloseMessage implements INetworkMessage { @Override public void encode(ByteBuf out) { throw new UnsupportedOperationException("CloseMessage is not a real message, and must not be sent."); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/CloseReasonMessage.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/CloseReasonMessage.java index b6f467768..8dcfeb8b9 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/CloseReasonMessage.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/CloseReasonMessage.java @@ -1,21 +1,24 @@ package com.seibel.distanthorizons.core.network.messages; +import com.seibel.distanthorizons.core.network.protocol.INetworkMessage; +import com.seibel.distanthorizons.core.network.protocol.INetworkObject; import io.netty.buffer.ByteBuf; -public class CloseReasonMessage extends Message { +public class CloseReasonMessage implements INetworkMessage { public String reason; + public CloseReasonMessage() { } public CloseReasonMessage(String reason) { this.reason = reason; } @Override public void encode(ByteBuf out) { - encodeString(reason, out); + INetworkObject.encodeString(reason, out); } @Override public void decode(ByteBuf in) { - reason = decodeString(in); + reason = INetworkObject.decodeString(in); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/HelloMessage.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/HelloMessage.java index d52f17488..df4092176 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/HelloMessage.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/HelloMessage.java @@ -1,13 +1,10 @@ package com.seibel.distanthorizons.core.network.messages; -import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.network.protocol.INetworkMessage; import com.seibel.distanthorizons.coreapi.ModInfo; import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.Logger; -public class HelloMessage extends Message { +public class HelloMessage implements INetworkMessage { public int version = ModInfo.PROTOCOL_VERSION; @Override diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/LodConfigMessage.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/LodConfigMessage.java index 54b114d33..bff6c2ca2 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/LodConfigMessage.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/LodConfigMessage.java @@ -1,16 +1,20 @@ package com.seibel.distanthorizons.core.network.messages; +import com.seibel.distanthorizons.core.network.protocol.INetworkObject; +import com.seibel.distanthorizons.core.network.protocol.INetworkMessage; +import com.seibel.distanthorizons.core.world.DhRemotePlayer; import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -public class LodConfigMessage extends Message { +public class LodConfigMessage implements INetworkMessage { + public DhRemotePlayer.Config config; + @Override public void encode(ByteBuf out) { - + config.encode(out); } @Override public void decode(ByteBuf in) { - + config = INetworkObject.decode(new DhRemotePlayer.Config(), in); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/Message.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/Message.java deleted file mode 100644 index 4958f8e18..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/Message.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.seibel.distanthorizons.core.network.messages; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; - -import java.nio.charset.StandardCharsets; - -public abstract class Message { - public abstract void encode(ByteBuf out); - public abstract void decode(ByteBuf in); - - protected void encodeString(String str, ByteBuf out) { - out.writeShort(str.length()); - out.writeBytes(str.getBytes(StandardCharsets.UTF_8)); - } - - protected String decodeString(ByteBuf in) { - int length = in.readShort(); - return new String(in.readBytes(length).array(), StandardCharsets.UTF_8); - } -} - diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/PlayerIdMessage.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/PlayerUUIDMessage.java similarity index 69% rename from core/src/main/java/com/seibel/distanthorizons/core/network/messages/PlayerIdMessage.java rename to core/src/main/java/com/seibel/distanthorizons/core/network/messages/PlayerUUIDMessage.java index a071b8cda..64ed53c83 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/PlayerIdMessage.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/PlayerUUIDMessage.java @@ -1,13 +1,15 @@ package com.seibel.distanthorizons.core.network.messages; +import com.seibel.distanthorizons.core.network.protocol.INetworkMessage; import io.netty.buffer.ByteBuf; import java.util.UUID; -public class PlayerIdMessage extends Message { +public class PlayerUUIDMessage implements INetworkMessage { public UUID playerUUID; - public PlayerIdMessage(UUID playerUUID) { + public PlayerUUIDMessage() { } + public PlayerUUIDMessage(UUID playerUUID) { this.playerUUID = playerUUID; } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/RequestChunksMessage.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/RequestChunksMessage.java new file mode 100644 index 000000000..1fbd7f01a --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/RequestChunksMessage.java @@ -0,0 +1,17 @@ +package com.seibel.distanthorizons.core.network.messages; + +import com.seibel.distanthorizons.core.network.protocol.INetworkMessage; +import io.netty.buffer.ByteBuf; + +public class RequestChunksMessage implements INetworkMessage { + + @Override + public void encode(ByteBuf out) { + + } + + @Override + public void decode(ByteBuf in) { + + } +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/INetworkMessage.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/INetworkMessage.java new file mode 100644 index 000000000..c24b290df --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/INetworkMessage.java @@ -0,0 +1,6 @@ +package com.seibel.distanthorizons.core.network.protocol; + +public interface INetworkMessage extends INetworkObject { + // For now only used for constraining listeners +} + diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/INetworkObject.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/INetworkObject.java new file mode 100644 index 000000000..46f7ae01e --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/INetworkObject.java @@ -0,0 +1,26 @@ +package com.seibel.distanthorizons.core.network.protocol; + +import io.netty.buffer.ByteBuf; + +import java.nio.charset.StandardCharsets; + +public interface INetworkObject { + void encode(ByteBuf out); + + void decode(ByteBuf in); + + static T decode(T o, ByteBuf in) { + o.decode(in); + return o; + } + + static void encodeString(String str, ByteBuf out) { + out.writeShort(str.length()); + out.writeBytes(str.getBytes(StandardCharsets.UTF_8)); + } + + static String decodeString(ByteBuf in) { + int length = in.readShort(); + return in.readBytes(length).toString(StandardCharsets.UTF_8); + } +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageDecoder.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageDecoder.java index 092c20dbf..37ae3ba05 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageDecoder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageDecoder.java @@ -1,6 +1,5 @@ package com.seibel.distanthorizons.core.network.protocol; -import com.seibel.distanthorizons.core.network.messages.Message; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; @@ -10,8 +9,7 @@ import java.util.List; public class MessageDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { - Message message = MessageRegistry.INSTANCE.createMessage(in.readShort()); - message.decode(in); - out.add(message); + INetworkMessage message = MessageRegistry.INSTANCE.createMessage(in.readShort()); + out.add(INetworkObject.decode(message, in)); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageEncoder.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageEncoder.java index f1e54e4d1..9f09ab388 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageEncoder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageEncoder.java @@ -1,13 +1,12 @@ package com.seibel.distanthorizons.core.network.protocol; -import com.seibel.distanthorizons.core.network.messages.Message; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; -public class MessageEncoder extends MessageToByteEncoder { +public class MessageEncoder extends MessageToByteEncoder { @Override - protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws IllegalArgumentException { + protected void encode(ChannelHandlerContext ctx, INetworkMessage msg, ByteBuf out) throws IllegalArgumentException { out.writeShort(MessageRegistry.INSTANCE.getMessageId(msg)); msg.encode(out); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageHandler.java index e3e0b9e40..e4aa4a285 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageHandler.java @@ -2,7 +2,6 @@ package com.seibel.distanthorizons.core.network.protocol; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.network.messages.CloseMessage; -import com.seibel.distanthorizons.core.network.messages.Message; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; @@ -16,25 +15,25 @@ import java.util.Map; import java.util.function.BiConsumer; @ChannelHandler.Sharable -public class MessageHandler extends SimpleChannelInboundHandler { +public class MessageHandler extends SimpleChannelInboundHandler { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - private Map, List>> handlers = new HashMap<>(); + private final Map, List>> handlers = new HashMap<>(); - public void registerHandler(Class clazz, BiConsumer handler) { + public void registerHandler(Class clazz, BiConsumer handler) { handlers.computeIfAbsent(clazz, k -> new LinkedList<>()) - .add((BiConsumer) handler); + .add((BiConsumer) handler); } @Override - protected void channelRead0(ChannelHandlerContext ctx, Message msg) { - List> handlerList = handlers.get(msg.getClass()); + protected void channelRead0(ChannelHandlerContext ctx, INetworkMessage msg) { + List> handlerList = handlers.get(msg.getClass()); if (handlerList == null) { LOGGER.warn("Unhandled message type: {}", msg.getClass().getSimpleName()); return; } - for (BiConsumer handler : handlerList) + for (BiConsumer handler : handlerList) handler.accept(msg, ctx); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageRegistry.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageRegistry.java index f8e5b8061..f1ba66cef 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageRegistry.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageRegistry.java @@ -1,7 +1,8 @@ package com.seibel.distanthorizons.core.network.protocol; -import com.seibel.distanthorizons.core.network.messages.HelloMessage; -import com.seibel.distanthorizons.core.network.messages.Message; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.seibel.distanthorizons.core.network.messages.*; import java.util.HashMap; import java.util.Map; @@ -9,22 +10,35 @@ import java.util.function.Supplier; public class MessageRegistry { public static final MessageRegistry INSTANCE = new MessageRegistry() {{ - // Note: Make sure IDs are unique - // Also note: Removing IDs will break backwards compatibility - registerMessage(1, HelloMessage.class, HelloMessage::new); + // Note: Messages must have parameterless constructors + + // Keep messages below intact so client/server can disconnect if version does not match + registerMessage(HelloMessage.class, HelloMessage::new); + registerMessage(CloseReasonMessage.class, CloseReasonMessage::new); + + // Define your messages after this line + registerMessage(AckMessage.class, AckMessage::new); + registerMessage(PlayerUUIDMessage.class, PlayerUUIDMessage::new); + registerMessage(LodConfigMessage.class, LodConfigMessage::new); + registerMessage(RequestChunksMessage.class, RequestChunksMessage::new); }}; - private final Map> idToSupplier = new HashMap<>(); - private final Map, Integer> classToId = new HashMap<>(); + private final Map> idToSupplier = new HashMap<>(); + private final BiMap, Integer> classToId = HashBiMap.create(); private MessageRegistry() { } - public void registerMessage(int id, Class clazz, Supplier supplier) { + public void registerMessage(Class clazz, Supplier supplier) { + int id = idToSupplier.size() + 1; idToSupplier.put(id, supplier); classToId.put(clazz, id); } - public Message createMessage(int id) throws IllegalArgumentException { + public Class getClassById(int id) { + return classToId.inverse().get(id); + } + + public INetworkMessage createMessage(int id) throws IllegalArgumentException { try { return idToSupplier.get(id).get(); } catch (NullPointerException e) { @@ -32,7 +46,11 @@ public class MessageRegistry { } } - public int getMessageId(Message message) { - return classToId.get(message.getClass()); + public int getMessageId(INetworkMessage message) { + return getMessageId(message.getClass()); + } + + public int getMessageId(Class clazz) { + return classToId.get(clazz); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java index 5ec9d3320..2a65f80a3 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java @@ -5,6 +5,10 @@ import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure; import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.DhClientLevel; import com.seibel.distanthorizons.core.network.NetworkClient; +import com.seibel.distanthorizons.core.network.messages.HelloMessage; +import com.seibel.distanthorizons.core.network.messages.LodConfigMessage; +import com.seibel.distanthorizons.core.network.messages.PlayerUUIDMessage; +import com.seibel.distanthorizons.core.network.messages.RequestChunksMessage; import com.seibel.distanthorizons.core.util.ThreadUtil; import com.seibel.distanthorizons.core.util.objects.EventLoop; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; @@ -40,11 +44,29 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld // TODO server specific configs this.networkClient = new NetworkClient(MC_CLIENT.getCurrentServerIp(), 25049); + registerNetworkHandlers(); LOGGER.info("Started DhWorld of type "+this.environment); } + private void registerNetworkHandlers() { + networkClient.registerHandler(HelloMessage.class, (msg, ctx) -> { + ctx.writeAndFlush(new PlayerUUIDMessage(MC_CLIENT.getPlayerUUID())); + }); + // TODO Proper config handling + networkClient.registerAckHandler(PlayerUUIDMessage.class, ctx -> { + ctx.writeAndFlush(new LodConfigMessage()); + }); + networkClient.registerHandler(LodConfigMessage.class, (msg, ctx) -> { + + }); + + networkClient.registerAckHandler(LodConfigMessage.class, ctx -> { + // TODO Actually request chunks + ctx.writeAndFlush(new RequestChunksMessage()); + }); + } @Override public DhClientLevel getOrLoadLevel(ILevelWrapper wrapper) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhPlayer.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhPlayer.java deleted file mode 100644 index 4d641e49d..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhPlayer.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.seibel.distanthorizons.core.world; - -import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; -import io.netty.channel.ChannelHandlerContext; - -public class DhPlayer { - public IServerPlayerWrapper serverPlayer; - public Config config; - public ChannelHandlerContext ctx; - - public DhPlayer(IServerPlayerWrapper serverPlayer) { - this.serverPlayer = serverPlayer; - } - - public static class Config { - // TODO Replace this example with actually useful fields - public int renderDistance; - } -} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhRemotePlayer.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhRemotePlayer.java new file mode 100644 index 000000000..ec74b8aed --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhRemotePlayer.java @@ -0,0 +1,31 @@ +package com.seibel.distanthorizons.core.world; + +import com.seibel.distanthorizons.core.network.protocol.INetworkObject; +import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; + +public class DhRemotePlayer { + public IServerPlayerWrapper serverPlayer; + public Config config; + public ChannelHandlerContext ctx; + + public DhRemotePlayer(IServerPlayerWrapper serverPlayer) { + this.serverPlayer = serverPlayer; + } + + public static class Config implements INetworkObject { + // TODO Replace this example with actually useful fields + public int renderDistance; + + @Override + public void encode(ByteBuf out) { + out.writeInt(renderDistance); + } + + @Override + public void decode(ByteBuf in) { + renderDistance = in.readInt(); + } + } +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java index 2fbfadfd9..f55980ab9 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java @@ -1,13 +1,13 @@ package com.seibel.distanthorizons.core.world; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; import com.seibel.distanthorizons.core.file.structure.LocalSaveStructure; import com.seibel.distanthorizons.core.level.DhServerLevel; import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.network.NetworkServer; -import com.seibel.distanthorizons.core.network.messages.CloseMessage; -import com.seibel.distanthorizons.core.network.messages.CloseReasonMessage; -import com.seibel.distanthorizons.core.network.messages.HelloMessage; -import com.seibel.distanthorizons.core.network.messages.PlayerIdMessage; +import com.seibel.distanthorizons.core.network.messages.*; +import com.seibel.distanthorizons.core.network.messages.RequestChunksMessage; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; @@ -25,8 +25,8 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld public final LocalSaveStructure saveStructure; private final NetworkServer networkServer; - private final HashMap players; - private final HashMap connections; + private final HashMap playersByUUID; + private final BiMap playersByConnection; public DhServerWorld() @@ -38,46 +38,61 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld // TODO move to global config once server specific configs are implemented this.networkServer = new NetworkServer(25049); - this.players = new HashMap<>(); - this.connections = new HashMap<>(); + this.playersByUUID = new HashMap<>(); + this.playersByConnection = HashBiMap.create(); registerNetworkHandlers(); LOGGER.info("Started "+DhServerWorld.class.getSimpleName()+" of type "+this.environment); } private void registerNetworkHandlers() { - networkServer.registerHandler(PlayerIdMessage.class, (msg, ctx) -> { - DhPlayer dhPlayer = players.get(msg.playerUUID); + networkServer.registerHandler(CloseMessage.class, (msg, ctx) -> { + DhRemotePlayer dhPlayer = playersByConnection.remove(ctx); + if (dhPlayer != null) + dhPlayer.ctx = null; + }); + + networkServer.registerHandler(PlayerUUIDMessage.class, (msg, ctx) -> { + DhRemotePlayer dhPlayer = playersByUUID.get(msg.playerUUID); if (dhPlayer == null) { - ctx.writeAndFlush(new CloseReasonMessage("Player is not logged in.")) - .addListener(future -> ctx.close()); + networkServer.disconnectClient(ctx, "Player is not logged in."); return; } if (dhPlayer.ctx != null) { - ctx.writeAndFlush(new CloseReasonMessage("Another connection is already in use.")) - .addListener(future -> ctx.close()); + networkServer.disconnectClient(ctx, "Another connection is already in use."); return; } dhPlayer.ctx = ctx; - connections.put(ctx, dhPlayer); + playersByConnection.put(ctx, dhPlayer); + + ctx.writeAndFlush(new AckMessage(PlayerUUIDMessage.class)); }); - networkServer.registerHandler(CloseMessage.class, (msg, ctx) -> { - DhPlayer dhPlayer = connections.remove(ctx); - if (dhPlayer != null) - dhPlayer.ctx = null; + networkServer.registerHandler(LodConfigMessage.class, (msg, ctx) -> { + // TODO Take notice of received config + ctx.writeAndFlush(new AckMessage(LodConfigMessage.class)); + }); + + networkServer.registerHandler(RequestChunksMessage.class, (msg, ctx) -> { + // hasReceivedChunkRequest should be false somewhere ??? + // to avoid sending updates until client says at least something about its state }); } public void addPlayer(IServerPlayerWrapper serverPlayer) { - players.put(serverPlayer.getUUID(), new DhPlayer(serverPlayer)); + playersByUUID.put(serverPlayer.getUUID(), new DhRemotePlayer(serverPlayer)); } public void removePlayer(IServerPlayerWrapper serverPlayer) { - players.remove(serverPlayer.getUUID()); + DhRemotePlayer dhPlayer = playersByUUID.remove(serverPlayer.getUUID()); + ChannelHandlerContext ctx = playersByConnection.inverse().remove(dhPlayer); + if (ctx != null) { + ctx.writeAndFlush(new CloseReasonMessage("You are being disconnected.")) + .addListener(future -> ctx.close()); + } } @Override diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/minecraft/IMinecraftClientWrapper.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/minecraft/IMinecraftClientWrapper.java index 252a2bea6..be7875b93 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/minecraft/IMinecraftClientWrapper.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/minecraft/IMinecraftClientWrapper.java @@ -21,6 +21,7 @@ package com.seibel.distanthorizons.core.wrapperInterfaces.minecraft; import java.io.File; import java.util.ArrayList; +import java.util.UUID; import com.seibel.distanthorizons.core.pos.DhBlockPos; import com.seibel.distanthorizons.core.pos.DhChunkPos; @@ -70,11 +71,13 @@ public interface IMinecraftClientWrapper extends IBindable //=============// boolean playerExists(); - + + UUID getPlayerUUID(); + DhBlockPos getPlayerBlockPos(); DhChunkPos getPlayerChunkPos(); - + /** * Returns the level the client is currently in.
* Returns null if the client isn't in a level.