From cc35e50edc53f619bdc562c4d53a33ef7100e5c0 Mon Sep 17 00:00:00 2001 From: s809 <43530948+s809@users.noreply.github.com> Date: Mon, 26 Jun 2023 20:59:25 +0500 Subject: [PATCH 01/13] Move everything to core --- .../core/level/DhClientServerLevel.java | 2 +- .../core/level/DhServerLevel.java | 2 +- .../core/level/ServerLevelModule.java | 2 +- .../core/network/LodClient.java | 24 +++++++ .../core/network/LodServer.java | 64 +++++++++++++++++++ .../messageHandling/MessageDecoder.java | 23 +++++++ .../messageHandling/MessageHandler.java | 27 ++++++++ .../messageHandling/MessageHandlerSide.java | 6 ++ .../messageHandling/MessageRegistry.java | 35 ++++++++++ .../core/network/messages/HelloMessage.java | 26 ++++++++ .../core/network/messages/Message.java | 19 ++++++ 11 files changed, 227 insertions(+), 3 deletions(-) create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/LodClient.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/LodServer.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageDecoder.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandler.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandlerSide.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageRegistry.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/messages/HelloMessage.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/messages/Message.java diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java index bc037860f..1d0f7ca5c 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java @@ -39,7 +39,7 @@ public class DhClientServerLevel extends DhLevel implements IDhClientLevel, IDhS { LOGGER.warn("unable to create data folder."); } - serverside = new ServerLevelModule(this, serverLevelWrapper, saveStructure); + serverside = new ServerLevelModule(this, serverLevelWrapper, saveStructure, false); clientside = new ClientLevelModule(this); LOGGER.info("Started "+DhClientServerLevel.class.getSimpleName()+" for "+ serverLevelWrapper +" with saves at "+saveStructure); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java index 8434d4c8b..b10c10cfd 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java @@ -25,7 +25,7 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel { LOGGER.warn("unable to create data folder."); } - serverside = new ServerLevelModule(this, serverLevelWrapper, saveStructure); + serverside = new ServerLevelModule(this, serverLevelWrapper, saveStructure, true); LOGGER.info("Started DHLevel for {} with saves at {}", serverLevelWrapper, saveStructure); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java b/core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java index 81e5be59c..2bfb94322 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java @@ -64,7 +64,7 @@ public class ServerLevelModule { public final AppliedConfigState worldGeneratorEnabledConfig; private final AtomicReference worldGenStateRef = new AtomicReference<>(); - public ServerLevelModule(IDhServerLevel parent, IServerLevelWrapper levelWrapper, AbstractSaveStructure saveStructure) + public ServerLevelModule(IDhServerLevel parent, IServerLevelWrapper levelWrapper, AbstractSaveStructure saveStructure, boolean startLodServer) { this.parent = parent; this.levelWrapper = levelWrapper; diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/LodClient.java b/core/src/main/java/com/seibel/distanthorizons/core/network/LodClient.java new file mode 100644 index 000000000..9bab5d702 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/LodClient.java @@ -0,0 +1,24 @@ +package com.seibel.distanthorizons.core.network; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +//import net.minecraft.client.Minecraft; + +public class LodClient { +// public static final LodClient INSTANCE = new LodClient(); +// +// private final Minecraft client; +// private final NetworkHandler networkHandler; +// +// private LodClient() { +// client = Minecraft.getInstance(); +// this.networkHandler = new NetworkHandler(); +// } + + public void connect() { + + } + + public void disconnect() { + + } +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/LodServer.java b/core/src/main/java/com/seibel/distanthorizons/core/network/LodServer.java new file mode 100644 index 000000000..3673a0109 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/LodServer.java @@ -0,0 +1,64 @@ +package com.seibel.distanthorizons.core.network; + +import com.seibel.distanthorizons.core.network.messageHandling.MessageDecoder; +import com.seibel.distanthorizons.core.network.messageHandling.MessageHandler; +import com.seibel.distanthorizons.core.network.messageHandling.MessageHandlerSide; +import com.seibel.distanthorizons.core.network.messageHandling.MessageRegistry; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; + +public class LodServer { + // TODO move to config of some sort + static final int PORT = 25049; + + public LodServer(/* initial settings */) { + + } + + public void start() throws InterruptedException { + EventLoopGroup bossGroup = new NioEventLoopGroup(1); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + try { + ServerBootstrap b = new ServerBootstrap(); + b.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .handler(new LoggingHandler(LogLevel.INFO)) + .childHandler(getInitializer()); + + b.bind(PORT).sync().channel().closeFuture().sync(); + } finally { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + } + + public void stop() { + + } + + private ChannelInitializer getInitializer() { + return new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) { + ChannelPipeline pipeline = ch.pipeline(); + + MessageRegistry messageRegistry = new MessageRegistry(); + + // Encoding + pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 2, 0, 2)); + pipeline.addLast(new MessageDecoder(messageRegistry)); + // TODO packet encoder + + pipeline.addLast(new MessageHandler(MessageHandlerSide.SERVER)); + } + }; + } +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageDecoder.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageDecoder.java new file mode 100644 index 000000000..0a35f4fc0 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageDecoder.java @@ -0,0 +1,23 @@ +package com.seibel.distanthorizons.core.network.messageHandling; + +import com.seibel.distanthorizons.core.network.messages.Message; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; + +import java.util.List; + +public class MessageDecoder extends ByteToMessageDecoder { + private MessageRegistry messageRegistry; + + public MessageDecoder(MessageRegistry messageRegistry) { + this.messageRegistry = messageRegistry; + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { + Message message = messageRegistry.createMessage(in.readShort()); + message.decode(in); + out.add(message); + } +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandler.java new file mode 100644 index 000000000..20817f27b --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandler.java @@ -0,0 +1,27 @@ +package com.seibel.distanthorizons.core.network.messageHandling; + +import com.seibel.distanthorizons.core.network.messages.Message; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +public class MessageHandler extends SimpleChannelInboundHandler { + private final MessageHandlerSide side; + + public MessageHandler(MessageHandlerSide side) { + this.side = side; + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, Message msg) { + switch (side) { + case CLIENT: + msg.handle_Client(ctx); + break; + case SERVER: + msg.handle_Server(ctx); + break; + default: + throw new IllegalStateException("Invalid handler side"); + } + } +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandlerSide.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandlerSide.java new file mode 100644 index 000000000..36eb6216c --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandlerSide.java @@ -0,0 +1,6 @@ +package com.seibel.distanthorizons.core.network.messageHandling; + +public enum MessageHandlerSide { + CLIENT, + SERVER +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageRegistry.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageRegistry.java new file mode 100644 index 000000000..77b849501 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageRegistry.java @@ -0,0 +1,35 @@ +package com.seibel.distanthorizons.core.network.messageHandling; + +import com.seibel.distanthorizons.core.network.messages.HelloMessage; +import com.seibel.distanthorizons.core.network.messages.Message; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class MessageRegistry { + private final Map> idToConstructor = new HashMap>() {{ + // Note: Make sure IDs are unique + // Also note: Removing IDs will break backwards compatibility + put(1, HelloMessage::new); + }}; + + private final Map, Integer> classToId = idToConstructor.entrySet().stream() + .collect(Collectors.toMap( + e -> e.getValue().getClass(), + Map.Entry::getKey + )); + + public Message createMessage(int id) throws IllegalArgumentException { + try { + return idToConstructor.get(id).get(); + } catch (NullPointerException e) { + throw new IllegalArgumentException("Invalid message ID"); + } + } + + public int getMessageId(Message message) { + return classToId.get(message.getClass()); + } +} 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 new file mode 100644 index 000000000..e96673f95 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/HelloMessage.java @@ -0,0 +1,26 @@ +package com.seibel.distanthorizons.core.network.messages; + +import com.seibel.distanthorizons.coreapi.ModInfo; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; + +// This message is critical to maintain backwards compatibility +// as it's used to receive version BEFORE everything else. +public class HelloMessage extends Message { + public int version = ModInfo.PROTOCOL_VERSION; + + @Override + public void encode(ByteBuf out) { + out.writeInt(version); + } + + @Override + public void decode(ByteBuf in) { + version = in.readInt(); + } + + @Override + public void handle_Server(ChannelHandlerContext ctx) { + // TODO Adjust message handling to client's version + } +} 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 new file mode 100644 index 000000000..6be22c006 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/Message.java @@ -0,0 +1,19 @@ +package com.seibel.distanthorizons.core.network.messages; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; + +public abstract class Message { + public Message() { } + + public abstract void encode(ByteBuf out); + public abstract void decode(ByteBuf in); + + public void handle_Server(ChannelHandlerContext ctx) { + throw new UnsupportedOperationException(); + } + public void handle_Client(ChannelHandlerContext ctx) { + throw new UnsupportedOperationException(); + } +} + From aee752a112d5f8fffa120845d3a1383c079f6f43 Mon Sep 17 00:00:00 2001 From: s809 <43530948+s809@users.noreply.github.com> Date: Mon, 26 Jun 2023 21:56:44 +0500 Subject: [PATCH 02/13] Add encoder --- .../core/network/LodServer.java | 16 ++++++++------- .../messageHandling/MessageHandlerSide.java | 6 ------ .../core/network/messages/HelloMessage.java | 6 +++--- .../core/network/messages/Message.java | 4 ++-- .../MessageDecoder.java | 6 +++--- .../core/network/protocol/MessageEncoder.java | 20 +++++++++++++++++++ .../MessageHandler.java | 2 +- .../network/protocol/MessageHandlerSide.java | 6 ++++++ .../MessageRegistry.java | 2 +- 9 files changed, 45 insertions(+), 23 deletions(-) delete mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandlerSide.java rename core/src/main/java/com/seibel/distanthorizons/core/network/{messageHandling => protocol}/MessageDecoder.java (80%) create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageEncoder.java rename core/src/main/java/com/seibel/distanthorizons/core/network/{messageHandling => protocol}/MessageHandler.java (92%) create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageHandlerSide.java rename core/src/main/java/com/seibel/distanthorizons/core/network/{messageHandling => protocol}/MessageRegistry.java (94%) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/LodServer.java b/core/src/main/java/com/seibel/distanthorizons/core/network/LodServer.java index 3673a0109..0866ecaa7 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/LodServer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/LodServer.java @@ -1,9 +1,6 @@ package com.seibel.distanthorizons.core.network; -import com.seibel.distanthorizons.core.network.messageHandling.MessageDecoder; -import com.seibel.distanthorizons.core.network.messageHandling.MessageHandler; -import com.seibel.distanthorizons.core.network.messageHandling.MessageHandlerSide; -import com.seibel.distanthorizons.core.network.messageHandling.MessageRegistry; +import com.seibel.distanthorizons.core.network.protocol.*; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; @@ -12,8 +9,10 @@ import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.codec.LengthFieldPrepender; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; +import org.jetbrains.annotations.NotNull; public class LodServer { // TODO move to config of some sort @@ -47,15 +46,18 @@ public class LodServer { private ChannelInitializer getInitializer() { return new ChannelInitializer() { @Override - public void initChannel(SocketChannel ch) { + public void initChannel(@NotNull SocketChannel ch) { ChannelPipeline pipeline = ch.pipeline(); MessageRegistry messageRegistry = new MessageRegistry(); // Encoding - pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 2, 0, 2)); + pipeline.addLast(new LengthFieldBasedFrameDecoder(Short.MAX_VALUE, 0, Short.BYTES, 0, Short.BYTES)); pipeline.addLast(new MessageDecoder(messageRegistry)); - // TODO packet encoder + + // Encoder + pipeline.addLast(new LengthFieldPrepender(Short.BYTES)); + pipeline.addLast(new MessageEncoder(messageRegistry)); pipeline.addLast(new MessageHandler(MessageHandlerSide.SERVER)); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandlerSide.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandlerSide.java deleted file mode 100644 index 36eb6216c..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandlerSide.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.seibel.distanthorizons.core.network.messageHandling; - -public enum MessageHandlerSide { - CLIENT, - SERVER -} 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 e96673f95..d5b64398a 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 @@ -5,17 +5,17 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; // This message is critical to maintain backwards compatibility -// as it's used to receive version BEFORE everything else. +// as it's used to receive version before everything else. public class HelloMessage extends Message { public int version = ModInfo.PROTOCOL_VERSION; @Override - public void encode(ByteBuf out) { + public void encode(ChannelHandlerContext ctx, ByteBuf out) { out.writeInt(version); } @Override - public void decode(ByteBuf in) { + public void decode(ChannelHandlerContext ctx, ByteBuf in) { version = in.readInt(); } 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 index 6be22c006..ac665966f 100644 --- 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 @@ -6,8 +6,8 @@ import io.netty.channel.ChannelHandlerContext; public abstract class Message { public Message() { } - public abstract void encode(ByteBuf out); - public abstract void decode(ByteBuf in); + public abstract void encode(ChannelHandlerContext ctx, ByteBuf out); + public abstract void decode(ChannelHandlerContext ctx, ByteBuf in); public void handle_Server(ChannelHandlerContext ctx) { throw new UnsupportedOperationException(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageDecoder.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageDecoder.java similarity index 80% rename from core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageDecoder.java rename to core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageDecoder.java index 0a35f4fc0..de2b4c7d1 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageDecoder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageDecoder.java @@ -1,4 +1,4 @@ -package com.seibel.distanthorizons.core.network.messageHandling; +package com.seibel.distanthorizons.core.network.protocol; import com.seibel.distanthorizons.core.network.messages.Message; import io.netty.buffer.ByteBuf; @@ -8,7 +8,7 @@ import io.netty.handler.codec.ByteToMessageDecoder; import java.util.List; public class MessageDecoder extends ByteToMessageDecoder { - private MessageRegistry messageRegistry; + private final MessageRegistry messageRegistry; public MessageDecoder(MessageRegistry messageRegistry) { this.messageRegistry = messageRegistry; @@ -17,7 +17,7 @@ public class MessageDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { Message message = messageRegistry.createMessage(in.readShort()); - message.decode(in); + message.decode(ctx, in); out.add(message); } } 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 new file mode 100644 index 000000000..43603e25a --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageEncoder.java @@ -0,0 +1,20 @@ +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 { + private final MessageRegistry messageRegistry; + + public MessageEncoder(MessageRegistry messageRegistry) { + this.messageRegistry = messageRegistry; + } + + @Override + protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws IllegalArgumentException { + out.writeShort(messageRegistry.getMessageId(msg)); + msg.encode(ctx, out); + } +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageHandler.java similarity index 92% rename from core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandler.java rename to core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageHandler.java index 20817f27b..19ed385d7 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageHandler.java @@ -1,4 +1,4 @@ -package com.seibel.distanthorizons.core.network.messageHandling; +package com.seibel.distanthorizons.core.network.protocol; import com.seibel.distanthorizons.core.network.messages.Message; import io.netty.channel.ChannelHandlerContext; diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageHandlerSide.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageHandlerSide.java new file mode 100644 index 000000000..3e44e1642 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageHandlerSide.java @@ -0,0 +1,6 @@ +package com.seibel.distanthorizons.core.network.protocol; + +public enum MessageHandlerSide { + CLIENT, + SERVER +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageRegistry.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageRegistry.java similarity index 94% rename from core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageRegistry.java rename to core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageRegistry.java index 77b849501..f6f702461 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageRegistry.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageRegistry.java @@ -1,4 +1,4 @@ -package com.seibel.distanthorizons.core.network.messageHandling; +package com.seibel.distanthorizons.core.network.protocol; import com.seibel.distanthorizons.core.network.messages.HelloMessage; import com.seibel.distanthorizons.core.network.messages.Message; From 2358797a820ef44a137dc271b4be1cec6bdfbcaa Mon Sep 17 00:00:00 2001 From: s809 <43530948+s809@users.noreply.github.com> Date: Wed, 28 Jun 2023 22:46:49 +0500 Subject: [PATCH 03/13] Minimal working client-server No cleanup logic yet --- .../transformers/ChunkToLodBuilder.java | 4 +- .../core/network/LodClient.java | 24 ------ .../core/network/LodServer.java | 66 ---------------- .../core/network/NetworkClient.java | 77 +++++++++++++++++++ .../core/network/NetworkServer.java | 45 +++++++++++ .../core/network/messages/HelloMessage.java | 12 ++- .../core/network/protocol/MessageDecoder.java | 8 +- .../core/network/protocol/MessageEncoder.java | 8 +- .../network/protocol/MessageRegistry.java | 21 ++--- .../core/util/NetworkUtil.java | 30 ++++++++ .../core/world/DhClientWorld.java | 7 +- .../core/world/DhServerWorld.java | 4 +- 12 files changed, 189 insertions(+), 117 deletions(-) delete mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/LodClient.java delete mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/LodServer.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/NetworkClient.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/NetworkServer.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/util/NetworkUtil.java diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java index 6ef86d61e..3841e293b 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java @@ -77,8 +77,10 @@ public class ChunkToLodBuilder implements AutoCloseable { return; } - else if (!MC.playerExists()) + else if (MC == null || !MC.playerExists()) { + // TODO handle server side properly + // MC hasn't finished loading (or is currently unloaded) // can be uncommented if tasks aren't being cleared correctly diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/LodClient.java b/core/src/main/java/com/seibel/distanthorizons/core/network/LodClient.java deleted file mode 100644 index 9bab5d702..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/LodClient.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.seibel.distanthorizons.core.network; - -import io.netty.handler.codec.LengthFieldBasedFrameDecoder; -//import net.minecraft.client.Minecraft; - -public class LodClient { -// public static final LodClient INSTANCE = new LodClient(); -// -// private final Minecraft client; -// private final NetworkHandler networkHandler; -// -// private LodClient() { -// client = Minecraft.getInstance(); -// this.networkHandler = new NetworkHandler(); -// } - - public void connect() { - - } - - public void disconnect() { - - } -} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/LodServer.java b/core/src/main/java/com/seibel/distanthorizons/core/network/LodServer.java deleted file mode 100644 index 0866ecaa7..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/LodServer.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.seibel.distanthorizons.core.network; - -import com.seibel.distanthorizons.core.network.protocol.*; -import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelPipeline; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.SocketChannel; -import io.netty.channel.socket.nio.NioServerSocketChannel; -import io.netty.handler.codec.LengthFieldBasedFrameDecoder; -import io.netty.handler.codec.LengthFieldPrepender; -import io.netty.handler.logging.LogLevel; -import io.netty.handler.logging.LoggingHandler; -import org.jetbrains.annotations.NotNull; - -public class LodServer { - // TODO move to config of some sort - static final int PORT = 25049; - - public LodServer(/* initial settings */) { - - } - - public void start() throws InterruptedException { - EventLoopGroup bossGroup = new NioEventLoopGroup(1); - EventLoopGroup workerGroup = new NioEventLoopGroup(); - try { - ServerBootstrap b = new ServerBootstrap(); - b.group(bossGroup, workerGroup) - .channel(NioServerSocketChannel.class) - .handler(new LoggingHandler(LogLevel.INFO)) - .childHandler(getInitializer()); - - b.bind(PORT).sync().channel().closeFuture().sync(); - } finally { - bossGroup.shutdownGracefully(); - workerGroup.shutdownGracefully(); - } - } - - public void stop() { - - } - - private ChannelInitializer getInitializer() { - return new ChannelInitializer() { - @Override - public void initChannel(@NotNull SocketChannel ch) { - ChannelPipeline pipeline = ch.pipeline(); - - MessageRegistry messageRegistry = new MessageRegistry(); - - // Encoding - pipeline.addLast(new LengthFieldBasedFrameDecoder(Short.MAX_VALUE, 0, Short.BYTES, 0, Short.BYTES)); - pipeline.addLast(new MessageDecoder(messageRegistry)); - - // Encoder - pipeline.addLast(new LengthFieldPrepender(Short.BYTES)); - pipeline.addLast(new MessageEncoder(messageRegistry)); - - pipeline.addLast(new MessageHandler(MessageHandlerSide.SERVER)); - } - }; - } -} 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 new file mode 100644 index 000000000..6ed422cc6 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkClient.java @@ -0,0 +1,77 @@ +package com.seibel.distanthorizons.core.network; + +import com.seibel.distanthorizons.core.network.messages.HelloMessage; +import com.seibel.distanthorizons.core.util.NetworkUtil; +import com.seibel.distanthorizons.core.network.protocol.MessageHandlerSide; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; + +import java.io.Closeable; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +public class NetworkClient implements Closeable { + private enum State { + OPEN, + RECONNECT, + 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 State state = State.OPEN; + + private final Bootstrap clientBootstrap; + private final EventLoopGroup workerGroup = new NioEventLoopGroup(); + private Channel channel; + + public NetworkClient(String host, int port) { + this.host = host; + this.port = port; + + clientBootstrap = new Bootstrap() + .group(workerGroup) + .channel(NioSocketChannel.class) + .option(ChannelOption.SO_KEEPALIVE, true) + .handler(NetworkUtil.getChannelInitializer(MessageHandlerSide.CLIENT)); + connect(); + } + + private void connect() { + ChannelFuture connectFuture = clientBootstrap.connect(host, port); + connectFuture.addListener((ChannelFuture channelFuture) -> { + if (!channelFuture.isSuccess()) return; + + channel = channelFuture.channel(); + channel.writeAndFlush(new HelloMessage()); + }); + + 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; + }); + } + + /** Kills the current connection, triggering auto-reconnection immediately. */ + public void reconnect() { + state = State.RECONNECT; + channel.disconnect(); + } + + @Override + public void close() throws IOException { + state = State.CLOSED; + workerGroup.shutdownGracefully(); + } +} 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 new file mode 100644 index 000000000..3149d7e1a --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkServer.java @@ -0,0 +1,45 @@ +package com.seibel.distanthorizons.core.network; + +import com.seibel.distanthorizons.core.network.protocol.*; +import com.seibel.distanthorizons.core.util.NetworkUtil; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; + +import java.io.Closeable; +import java.io.IOException; + +public class NetworkServer implements Closeable { + // TODO move to config of some sort + private final int port; + + EventLoopGroup bossGroup = new NioEventLoopGroup(1); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + + public NetworkServer(int port) { + this.port = port; + + ServerBootstrap b = new ServerBootstrap() + .group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .handler(new LoggingHandler(LogLevel.INFO)) + .childHandler(NetworkUtil.getChannelInitializer(MessageHandlerSide.SERVER)); + + b.bind(port) + .addListener((ChannelFuture channelFuture) -> { + if (!channelFuture.isSuccess()) + throw new RuntimeException("Failed to bind: " + channelFuture); + }) + .channel().closeFuture().addListener(future -> close()); + } + + @Override + public void close() throws IOException { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } +} 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 d5b64398a..c790e72a9 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,12 +1,17 @@ package com.seibel.distanthorizons.core.network.messages; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; 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; // This message is critical to maintain backwards compatibility // as it's used to receive version before everything else. public class HelloMessage extends Message { + private static final Logger LOGGER = DhLoggerBuilder.getLogger(); + public int version = ModInfo.PROTOCOL_VERSION; @Override @@ -21,6 +26,11 @@ public class HelloMessage extends Message { @Override public void handle_Server(ChannelHandlerContext ctx) { - // TODO Adjust message handling to client's version + LOGGER.log(Level.INFO, "Client version: " + version); + } + + @Override + public void handle_Client(ChannelHandlerContext ctx) { + LOGGER.log(Level.INFO, "Server version: " + version); } } 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 de2b4c7d1..82b653c56 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 @@ -8,15 +8,9 @@ import io.netty.handler.codec.ByteToMessageDecoder; import java.util.List; public class MessageDecoder extends ByteToMessageDecoder { - private final MessageRegistry messageRegistry; - - public MessageDecoder(MessageRegistry messageRegistry) { - this.messageRegistry = messageRegistry; - } - @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { - Message message = messageRegistry.createMessage(in.readShort()); + Message message = MessageRegistry.INSTANCE.createMessage(in.readShort()); message.decode(ctx, in); out.add(message); } 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 43603e25a..5b491ccc7 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 @@ -6,15 +6,9 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; public class MessageEncoder extends MessageToByteEncoder { - private final MessageRegistry messageRegistry; - - public MessageEncoder(MessageRegistry messageRegistry) { - this.messageRegistry = messageRegistry; - } - @Override protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws IllegalArgumentException { - out.writeShort(messageRegistry.getMessageId(msg)); + out.writeShort(MessageRegistry.INSTANCE.getMessageId(msg)); msg.encode(ctx, out); } } 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 f6f702461..e02ba0b20 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 @@ -6,24 +6,27 @@ import com.seibel.distanthorizons.core.network.messages.Message; import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; -import java.util.stream.Collectors; public class MessageRegistry { - private final Map> idToConstructor = new HashMap>() {{ + public static final MessageRegistry INSTANCE = new MessageRegistry() {{ // Note: Make sure IDs are unique // Also note: Removing IDs will break backwards compatibility - put(1, HelloMessage::new); + registerMessage(1, HelloMessage.class, HelloMessage::new); }}; - private final Map, Integer> classToId = idToConstructor.entrySet().stream() - .collect(Collectors.toMap( - e -> e.getValue().getClass(), - Map.Entry::getKey - )); + private final Map> idToSupplier = new HashMap<>(); + private final Map, Integer> classToId = new HashMap<>(); + + private MessageRegistry() { } + + public void registerMessage(int id, Class clazz, Supplier supplier) { + idToSupplier.put(id, (Supplier) supplier); + classToId.put(clazz, id); + } public Message createMessage(int id) throws IllegalArgumentException { try { - return idToConstructor.get(id).get(); + return idToSupplier.get(id).get(); } catch (NullPointerException e) { throw new IllegalArgumentException("Invalid message ID"); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/NetworkUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/NetworkUtil.java new file mode 100644 index 000000000..a67d68bb1 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/NetworkUtil.java @@ -0,0 +1,30 @@ +package com.seibel.distanthorizons.core.util; + +import com.seibel.distanthorizons.core.network.protocol.*; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.codec.LengthFieldPrepender; +import org.jetbrains.annotations.NotNull; + +public class NetworkUtil { + public static ChannelInitializer getChannelInitializer(MessageHandlerSide side) { + return new ChannelInitializer() { + @Override + public void initChannel(@NotNull SocketChannel ch) { + ChannelPipeline pipeline = ch.pipeline(); + + // Encoding + pipeline.addLast(new LengthFieldBasedFrameDecoder(Short.MAX_VALUE, 0, Short.BYTES, 0, Short.BYTES)); + pipeline.addLast(new MessageDecoder()); + + // Encoder + pipeline.addLast(new LengthFieldPrepender(Short.BYTES)); + pipeline.addLast(new MessageEncoder()); + + pipeline.addLast(new MessageHandler(side)); + } + }; + } +} 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 08b12a129..90247a8e8 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 @@ -3,6 +3,7 @@ package com.seibel.distanthorizons.core.world; 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.util.ThreadUtil; import com.seibel.distanthorizons.core.util.objects.EventLoop; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; @@ -17,6 +18,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld { private final HashMap levels; public final ClientOnlySaveStructure saveStructure; + private final NetworkClient networkClient; // TODO why does this executor have 2 threads? public ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("DH Client World Ticker Thread", 2); @@ -27,8 +29,11 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld public DhClientWorld() { super(EWorldEnvironment.Client_Only); - this.saveStructure = new ClientOnlySaveStructure(); + + this.saveStructure = new ClientOnlySaveStructure(); this.levels = new HashMap<>(); + this.networkClient = new NetworkClient("127.0.0.1", 25049); + LOGGER.info("Started DhWorld of type "+this.environment); } 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 aa0574629..c32dcbe3d 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 @@ -3,6 +3,7 @@ package com.seibel.distanthorizons.core.world; 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.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; @@ -15,7 +16,7 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld { private final HashMap levels; public final LocalSaveStructure saveStructure; - + private final NetworkServer networkServer; public DhServerWorld() @@ -24,6 +25,7 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld this.saveStructure = new LocalSaveStructure(); this.levels = new HashMap<>(); + this.networkServer = new NetworkServer(25049); LOGGER.info("Started "+DhServerWorld.class.getSimpleName()+" of type "+this.environment); } From 8bae0386129cef5b7ced414e0b0d8bf0e88766f2 Mon Sep 17 00:00:00 2001 From: s809 <43530948+s809@users.noreply.github.com> Date: Mon, 26 Jun 2023 20:59:25 +0500 Subject: [PATCH 04/13] Move everything to core --- .../core/level/ServerLevelModule.java | 2 +- .../core/network/LodClient.java | 24 +++++++ .../core/network/LodServer.java | 64 +++++++++++++++++++ .../messageHandling/MessageDecoder.java | 23 +++++++ .../messageHandling/MessageHandler.java | 27 ++++++++ .../messageHandling/MessageHandlerSide.java | 6 ++ .../messageHandling/MessageRegistry.java | 35 ++++++++++ .../core/network/messages/HelloMessage.java | 26 ++++++++ .../core/network/messages/Message.java | 19 ++++++ 9 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/LodClient.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/LodServer.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageDecoder.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandler.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandlerSide.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageRegistry.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/messages/HelloMessage.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/messages/Message.java diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java b/core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java index 350adb7e5..4c0846abd 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java @@ -64,7 +64,7 @@ public class ServerLevelModule { public final AppliedConfigState worldGeneratorEnabledConfig; private final AtomicReference worldGenStateRef = new AtomicReference<>(); - public ServerLevelModule(IDhServerLevel parent, IServerLevelWrapper levelWrapper, AbstractSaveStructure saveStructure) + public ServerLevelModule(IDhServerLevel parent, IServerLevelWrapper levelWrapper, AbstractSaveStructure saveStructure, boolean startLodServer) { this.parent = parent; this.levelWrapper = levelWrapper; diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/LodClient.java b/core/src/main/java/com/seibel/distanthorizons/core/network/LodClient.java new file mode 100644 index 000000000..9bab5d702 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/LodClient.java @@ -0,0 +1,24 @@ +package com.seibel.distanthorizons.core.network; + +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +//import net.minecraft.client.Minecraft; + +public class LodClient { +// public static final LodClient INSTANCE = new LodClient(); +// +// private final Minecraft client; +// private final NetworkHandler networkHandler; +// +// private LodClient() { +// client = Minecraft.getInstance(); +// this.networkHandler = new NetworkHandler(); +// } + + public void connect() { + + } + + public void disconnect() { + + } +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/LodServer.java b/core/src/main/java/com/seibel/distanthorizons/core/network/LodServer.java new file mode 100644 index 000000000..3673a0109 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/LodServer.java @@ -0,0 +1,64 @@ +package com.seibel.distanthorizons.core.network; + +import com.seibel.distanthorizons.core.network.messageHandling.MessageDecoder; +import com.seibel.distanthorizons.core.network.messageHandling.MessageHandler; +import com.seibel.distanthorizons.core.network.messageHandling.MessageHandlerSide; +import com.seibel.distanthorizons.core.network.messageHandling.MessageRegistry; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; + +public class LodServer { + // TODO move to config of some sort + static final int PORT = 25049; + + public LodServer(/* initial settings */) { + + } + + public void start() throws InterruptedException { + EventLoopGroup bossGroup = new NioEventLoopGroup(1); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + try { + ServerBootstrap b = new ServerBootstrap(); + b.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .handler(new LoggingHandler(LogLevel.INFO)) + .childHandler(getInitializer()); + + b.bind(PORT).sync().channel().closeFuture().sync(); + } finally { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + } + + public void stop() { + + } + + private ChannelInitializer getInitializer() { + return new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) { + ChannelPipeline pipeline = ch.pipeline(); + + MessageRegistry messageRegistry = new MessageRegistry(); + + // Encoding + pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 2, 0, 2)); + pipeline.addLast(new MessageDecoder(messageRegistry)); + // TODO packet encoder + + pipeline.addLast(new MessageHandler(MessageHandlerSide.SERVER)); + } + }; + } +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageDecoder.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageDecoder.java new file mode 100644 index 000000000..0a35f4fc0 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageDecoder.java @@ -0,0 +1,23 @@ +package com.seibel.distanthorizons.core.network.messageHandling; + +import com.seibel.distanthorizons.core.network.messages.Message; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; + +import java.util.List; + +public class MessageDecoder extends ByteToMessageDecoder { + private MessageRegistry messageRegistry; + + public MessageDecoder(MessageRegistry messageRegistry) { + this.messageRegistry = messageRegistry; + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { + Message message = messageRegistry.createMessage(in.readShort()); + message.decode(in); + out.add(message); + } +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandler.java new file mode 100644 index 000000000..20817f27b --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandler.java @@ -0,0 +1,27 @@ +package com.seibel.distanthorizons.core.network.messageHandling; + +import com.seibel.distanthorizons.core.network.messages.Message; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +public class MessageHandler extends SimpleChannelInboundHandler { + private final MessageHandlerSide side; + + public MessageHandler(MessageHandlerSide side) { + this.side = side; + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, Message msg) { + switch (side) { + case CLIENT: + msg.handle_Client(ctx); + break; + case SERVER: + msg.handle_Server(ctx); + break; + default: + throw new IllegalStateException("Invalid handler side"); + } + } +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandlerSide.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandlerSide.java new file mode 100644 index 000000000..36eb6216c --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandlerSide.java @@ -0,0 +1,6 @@ +package com.seibel.distanthorizons.core.network.messageHandling; + +public enum MessageHandlerSide { + CLIENT, + SERVER +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageRegistry.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageRegistry.java new file mode 100644 index 000000000..77b849501 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageRegistry.java @@ -0,0 +1,35 @@ +package com.seibel.distanthorizons.core.network.messageHandling; + +import com.seibel.distanthorizons.core.network.messages.HelloMessage; +import com.seibel.distanthorizons.core.network.messages.Message; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class MessageRegistry { + private final Map> idToConstructor = new HashMap>() {{ + // Note: Make sure IDs are unique + // Also note: Removing IDs will break backwards compatibility + put(1, HelloMessage::new); + }}; + + private final Map, Integer> classToId = idToConstructor.entrySet().stream() + .collect(Collectors.toMap( + e -> e.getValue().getClass(), + Map.Entry::getKey + )); + + public Message createMessage(int id) throws IllegalArgumentException { + try { + return idToConstructor.get(id).get(); + } catch (NullPointerException e) { + throw new IllegalArgumentException("Invalid message ID"); + } + } + + public int getMessageId(Message message) { + return classToId.get(message.getClass()); + } +} 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 new file mode 100644 index 000000000..e96673f95 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/HelloMessage.java @@ -0,0 +1,26 @@ +package com.seibel.distanthorizons.core.network.messages; + +import com.seibel.distanthorizons.coreapi.ModInfo; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; + +// This message is critical to maintain backwards compatibility +// as it's used to receive version BEFORE everything else. +public class HelloMessage extends Message { + public int version = ModInfo.PROTOCOL_VERSION; + + @Override + public void encode(ByteBuf out) { + out.writeInt(version); + } + + @Override + public void decode(ByteBuf in) { + version = in.readInt(); + } + + @Override + public void handle_Server(ChannelHandlerContext ctx) { + // TODO Adjust message handling to client's version + } +} 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 new file mode 100644 index 000000000..6be22c006 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/Message.java @@ -0,0 +1,19 @@ +package com.seibel.distanthorizons.core.network.messages; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; + +public abstract class Message { + public Message() { } + + public abstract void encode(ByteBuf out); + public abstract void decode(ByteBuf in); + + public void handle_Server(ChannelHandlerContext ctx) { + throw new UnsupportedOperationException(); + } + public void handle_Client(ChannelHandlerContext ctx) { + throw new UnsupportedOperationException(); + } +} + From 8378b1f09a7a79d6bdaa5dd459d5556c96d7d508 Mon Sep 17 00:00:00 2001 From: s809 <43530948+s809@users.noreply.github.com> Date: Mon, 26 Jun 2023 21:56:44 +0500 Subject: [PATCH 05/13] Add encoder --- .../core/network/LodServer.java | 16 ++++++++------- .../messageHandling/MessageHandlerSide.java | 6 ------ .../core/network/messages/HelloMessage.java | 6 +++--- .../core/network/messages/Message.java | 4 ++-- .../MessageDecoder.java | 6 +++--- .../core/network/protocol/MessageEncoder.java | 20 +++++++++++++++++++ .../MessageHandler.java | 2 +- .../network/protocol/MessageHandlerSide.java | 6 ++++++ .../MessageRegistry.java | 2 +- 9 files changed, 45 insertions(+), 23 deletions(-) delete mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandlerSide.java rename core/src/main/java/com/seibel/distanthorizons/core/network/{messageHandling => protocol}/MessageDecoder.java (80%) create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageEncoder.java rename core/src/main/java/com/seibel/distanthorizons/core/network/{messageHandling => protocol}/MessageHandler.java (92%) create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageHandlerSide.java rename core/src/main/java/com/seibel/distanthorizons/core/network/{messageHandling => protocol}/MessageRegistry.java (94%) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/LodServer.java b/core/src/main/java/com/seibel/distanthorizons/core/network/LodServer.java index 3673a0109..0866ecaa7 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/LodServer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/LodServer.java @@ -1,9 +1,6 @@ package com.seibel.distanthorizons.core.network; -import com.seibel.distanthorizons.core.network.messageHandling.MessageDecoder; -import com.seibel.distanthorizons.core.network.messageHandling.MessageHandler; -import com.seibel.distanthorizons.core.network.messageHandling.MessageHandlerSide; -import com.seibel.distanthorizons.core.network.messageHandling.MessageRegistry; +import com.seibel.distanthorizons.core.network.protocol.*; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; @@ -12,8 +9,10 @@ import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.codec.LengthFieldPrepender; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; +import org.jetbrains.annotations.NotNull; public class LodServer { // TODO move to config of some sort @@ -47,15 +46,18 @@ public class LodServer { private ChannelInitializer getInitializer() { return new ChannelInitializer() { @Override - public void initChannel(SocketChannel ch) { + public void initChannel(@NotNull SocketChannel ch) { ChannelPipeline pipeline = ch.pipeline(); MessageRegistry messageRegistry = new MessageRegistry(); // Encoding - pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 2, 0, 2)); + pipeline.addLast(new LengthFieldBasedFrameDecoder(Short.MAX_VALUE, 0, Short.BYTES, 0, Short.BYTES)); pipeline.addLast(new MessageDecoder(messageRegistry)); - // TODO packet encoder + + // Encoder + pipeline.addLast(new LengthFieldPrepender(Short.BYTES)); + pipeline.addLast(new MessageEncoder(messageRegistry)); pipeline.addLast(new MessageHandler(MessageHandlerSide.SERVER)); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandlerSide.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandlerSide.java deleted file mode 100644 index 36eb6216c..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandlerSide.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.seibel.distanthorizons.core.network.messageHandling; - -public enum MessageHandlerSide { - CLIENT, - SERVER -} 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 e96673f95..d5b64398a 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 @@ -5,17 +5,17 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; // This message is critical to maintain backwards compatibility -// as it's used to receive version BEFORE everything else. +// as it's used to receive version before everything else. public class HelloMessage extends Message { public int version = ModInfo.PROTOCOL_VERSION; @Override - public void encode(ByteBuf out) { + public void encode(ChannelHandlerContext ctx, ByteBuf out) { out.writeInt(version); } @Override - public void decode(ByteBuf in) { + public void decode(ChannelHandlerContext ctx, ByteBuf in) { version = in.readInt(); } 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 index 6be22c006..ac665966f 100644 --- 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 @@ -6,8 +6,8 @@ import io.netty.channel.ChannelHandlerContext; public abstract class Message { public Message() { } - public abstract void encode(ByteBuf out); - public abstract void decode(ByteBuf in); + public abstract void encode(ChannelHandlerContext ctx, ByteBuf out); + public abstract void decode(ChannelHandlerContext ctx, ByteBuf in); public void handle_Server(ChannelHandlerContext ctx) { throw new UnsupportedOperationException(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageDecoder.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageDecoder.java similarity index 80% rename from core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageDecoder.java rename to core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageDecoder.java index 0a35f4fc0..de2b4c7d1 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageDecoder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageDecoder.java @@ -1,4 +1,4 @@ -package com.seibel.distanthorizons.core.network.messageHandling; +package com.seibel.distanthorizons.core.network.protocol; import com.seibel.distanthorizons.core.network.messages.Message; import io.netty.buffer.ByteBuf; @@ -8,7 +8,7 @@ import io.netty.handler.codec.ByteToMessageDecoder; import java.util.List; public class MessageDecoder extends ByteToMessageDecoder { - private MessageRegistry messageRegistry; + private final MessageRegistry messageRegistry; public MessageDecoder(MessageRegistry messageRegistry) { this.messageRegistry = messageRegistry; @@ -17,7 +17,7 @@ public class MessageDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { Message message = messageRegistry.createMessage(in.readShort()); - message.decode(in); + message.decode(ctx, in); out.add(message); } } 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 new file mode 100644 index 000000000..43603e25a --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageEncoder.java @@ -0,0 +1,20 @@ +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 { + private final MessageRegistry messageRegistry; + + public MessageEncoder(MessageRegistry messageRegistry) { + this.messageRegistry = messageRegistry; + } + + @Override + protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws IllegalArgumentException { + out.writeShort(messageRegistry.getMessageId(msg)); + msg.encode(ctx, out); + } +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageHandler.java similarity index 92% rename from core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandler.java rename to core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageHandler.java index 20817f27b..19ed385d7 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageHandler.java @@ -1,4 +1,4 @@ -package com.seibel.distanthorizons.core.network.messageHandling; +package com.seibel.distanthorizons.core.network.protocol; import com.seibel.distanthorizons.core.network.messages.Message; import io.netty.channel.ChannelHandlerContext; diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageHandlerSide.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageHandlerSide.java new file mode 100644 index 000000000..3e44e1642 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageHandlerSide.java @@ -0,0 +1,6 @@ +package com.seibel.distanthorizons.core.network.protocol; + +public enum MessageHandlerSide { + CLIENT, + SERVER +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageRegistry.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageRegistry.java similarity index 94% rename from core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageRegistry.java rename to core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageRegistry.java index 77b849501..f6f702461 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/messageHandling/MessageRegistry.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageRegistry.java @@ -1,4 +1,4 @@ -package com.seibel.distanthorizons.core.network.messageHandling; +package com.seibel.distanthorizons.core.network.protocol; import com.seibel.distanthorizons.core.network.messages.HelloMessage; import com.seibel.distanthorizons.core.network.messages.Message; From 1f9aa2f70b8165b2f20d8ba7b87e80a2331e433b Mon Sep 17 00:00:00 2001 From: s809 <43530948+s809@users.noreply.github.com> Date: Wed, 28 Jun 2023 22:46:49 +0500 Subject: [PATCH 06/13] Minimal working client-server No cleanup logic yet --- .../transformers/ChunkToLodBuilder.java | 4 +- .../core/network/LodClient.java | 24 ------ .../core/network/LodServer.java | 66 ---------------- .../core/network/NetworkClient.java | 77 +++++++++++++++++++ .../core/network/NetworkServer.java | 45 +++++++++++ .../core/network/messages/HelloMessage.java | 12 ++- .../core/network/protocol/MessageDecoder.java | 8 +- .../core/network/protocol/MessageEncoder.java | 8 +- .../network/protocol/MessageRegistry.java | 21 ++--- .../core/util/NetworkUtil.java | 30 ++++++++ .../core/world/DhClientWorld.java | 8 +- .../core/world/DhServerWorld.java | 4 +- 12 files changed, 190 insertions(+), 117 deletions(-) delete mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/LodClient.java delete mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/LodServer.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/NetworkClient.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/NetworkServer.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/util/NetworkUtil.java diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java index 6ef86d61e..3841e293b 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java @@ -77,8 +77,10 @@ public class ChunkToLodBuilder implements AutoCloseable { return; } - else if (!MC.playerExists()) + else if (MC == null || !MC.playerExists()) { + // TODO handle server side properly + // MC hasn't finished loading (or is currently unloaded) // can be uncommented if tasks aren't being cleared correctly diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/LodClient.java b/core/src/main/java/com/seibel/distanthorizons/core/network/LodClient.java deleted file mode 100644 index 9bab5d702..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/LodClient.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.seibel.distanthorizons.core.network; - -import io.netty.handler.codec.LengthFieldBasedFrameDecoder; -//import net.minecraft.client.Minecraft; - -public class LodClient { -// public static final LodClient INSTANCE = new LodClient(); -// -// private final Minecraft client; -// private final NetworkHandler networkHandler; -// -// private LodClient() { -// client = Minecraft.getInstance(); -// this.networkHandler = new NetworkHandler(); -// } - - public void connect() { - - } - - public void disconnect() { - - } -} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/LodServer.java b/core/src/main/java/com/seibel/distanthorizons/core/network/LodServer.java deleted file mode 100644 index 0866ecaa7..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/LodServer.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.seibel.distanthorizons.core.network; - -import com.seibel.distanthorizons.core.network.protocol.*; -import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelPipeline; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.SocketChannel; -import io.netty.channel.socket.nio.NioServerSocketChannel; -import io.netty.handler.codec.LengthFieldBasedFrameDecoder; -import io.netty.handler.codec.LengthFieldPrepender; -import io.netty.handler.logging.LogLevel; -import io.netty.handler.logging.LoggingHandler; -import org.jetbrains.annotations.NotNull; - -public class LodServer { - // TODO move to config of some sort - static final int PORT = 25049; - - public LodServer(/* initial settings */) { - - } - - public void start() throws InterruptedException { - EventLoopGroup bossGroup = new NioEventLoopGroup(1); - EventLoopGroup workerGroup = new NioEventLoopGroup(); - try { - ServerBootstrap b = new ServerBootstrap(); - b.group(bossGroup, workerGroup) - .channel(NioServerSocketChannel.class) - .handler(new LoggingHandler(LogLevel.INFO)) - .childHandler(getInitializer()); - - b.bind(PORT).sync().channel().closeFuture().sync(); - } finally { - bossGroup.shutdownGracefully(); - workerGroup.shutdownGracefully(); - } - } - - public void stop() { - - } - - private ChannelInitializer getInitializer() { - return new ChannelInitializer() { - @Override - public void initChannel(@NotNull SocketChannel ch) { - ChannelPipeline pipeline = ch.pipeline(); - - MessageRegistry messageRegistry = new MessageRegistry(); - - // Encoding - pipeline.addLast(new LengthFieldBasedFrameDecoder(Short.MAX_VALUE, 0, Short.BYTES, 0, Short.BYTES)); - pipeline.addLast(new MessageDecoder(messageRegistry)); - - // Encoder - pipeline.addLast(new LengthFieldPrepender(Short.BYTES)); - pipeline.addLast(new MessageEncoder(messageRegistry)); - - pipeline.addLast(new MessageHandler(MessageHandlerSide.SERVER)); - } - }; - } -} 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 new file mode 100644 index 000000000..6ed422cc6 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkClient.java @@ -0,0 +1,77 @@ +package com.seibel.distanthorizons.core.network; + +import com.seibel.distanthorizons.core.network.messages.HelloMessage; +import com.seibel.distanthorizons.core.util.NetworkUtil; +import com.seibel.distanthorizons.core.network.protocol.MessageHandlerSide; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; + +import java.io.Closeable; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +public class NetworkClient implements Closeable { + private enum State { + OPEN, + RECONNECT, + 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 State state = State.OPEN; + + private final Bootstrap clientBootstrap; + private final EventLoopGroup workerGroup = new NioEventLoopGroup(); + private Channel channel; + + public NetworkClient(String host, int port) { + this.host = host; + this.port = port; + + clientBootstrap = new Bootstrap() + .group(workerGroup) + .channel(NioSocketChannel.class) + .option(ChannelOption.SO_KEEPALIVE, true) + .handler(NetworkUtil.getChannelInitializer(MessageHandlerSide.CLIENT)); + connect(); + } + + private void connect() { + ChannelFuture connectFuture = clientBootstrap.connect(host, port); + connectFuture.addListener((ChannelFuture channelFuture) -> { + if (!channelFuture.isSuccess()) return; + + channel = channelFuture.channel(); + channel.writeAndFlush(new HelloMessage()); + }); + + 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; + }); + } + + /** Kills the current connection, triggering auto-reconnection immediately. */ + public void reconnect() { + state = State.RECONNECT; + channel.disconnect(); + } + + @Override + public void close() throws IOException { + state = State.CLOSED; + workerGroup.shutdownGracefully(); + } +} 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 new file mode 100644 index 000000000..3149d7e1a --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkServer.java @@ -0,0 +1,45 @@ +package com.seibel.distanthorizons.core.network; + +import com.seibel.distanthorizons.core.network.protocol.*; +import com.seibel.distanthorizons.core.util.NetworkUtil; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; + +import java.io.Closeable; +import java.io.IOException; + +public class NetworkServer implements Closeable { + // TODO move to config of some sort + private final int port; + + EventLoopGroup bossGroup = new NioEventLoopGroup(1); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + + public NetworkServer(int port) { + this.port = port; + + ServerBootstrap b = new ServerBootstrap() + .group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .handler(new LoggingHandler(LogLevel.INFO)) + .childHandler(NetworkUtil.getChannelInitializer(MessageHandlerSide.SERVER)); + + b.bind(port) + .addListener((ChannelFuture channelFuture) -> { + if (!channelFuture.isSuccess()) + throw new RuntimeException("Failed to bind: " + channelFuture); + }) + .channel().closeFuture().addListener(future -> close()); + } + + @Override + public void close() throws IOException { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } +} 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 d5b64398a..c790e72a9 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,12 +1,17 @@ package com.seibel.distanthorizons.core.network.messages; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; 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; // This message is critical to maintain backwards compatibility // as it's used to receive version before everything else. public class HelloMessage extends Message { + private static final Logger LOGGER = DhLoggerBuilder.getLogger(); + public int version = ModInfo.PROTOCOL_VERSION; @Override @@ -21,6 +26,11 @@ public class HelloMessage extends Message { @Override public void handle_Server(ChannelHandlerContext ctx) { - // TODO Adjust message handling to client's version + LOGGER.log(Level.INFO, "Client version: " + version); + } + + @Override + public void handle_Client(ChannelHandlerContext ctx) { + LOGGER.log(Level.INFO, "Server version: " + version); } } 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 de2b4c7d1..82b653c56 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 @@ -8,15 +8,9 @@ import io.netty.handler.codec.ByteToMessageDecoder; import java.util.List; public class MessageDecoder extends ByteToMessageDecoder { - private final MessageRegistry messageRegistry; - - public MessageDecoder(MessageRegistry messageRegistry) { - this.messageRegistry = messageRegistry; - } - @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { - Message message = messageRegistry.createMessage(in.readShort()); + Message message = MessageRegistry.INSTANCE.createMessage(in.readShort()); message.decode(ctx, in); out.add(message); } 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 43603e25a..5b491ccc7 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 @@ -6,15 +6,9 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; public class MessageEncoder extends MessageToByteEncoder { - private final MessageRegistry messageRegistry; - - public MessageEncoder(MessageRegistry messageRegistry) { - this.messageRegistry = messageRegistry; - } - @Override protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws IllegalArgumentException { - out.writeShort(messageRegistry.getMessageId(msg)); + out.writeShort(MessageRegistry.INSTANCE.getMessageId(msg)); msg.encode(ctx, out); } } 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 f6f702461..e02ba0b20 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 @@ -6,24 +6,27 @@ import com.seibel.distanthorizons.core.network.messages.Message; import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; -import java.util.stream.Collectors; public class MessageRegistry { - private final Map> idToConstructor = new HashMap>() {{ + public static final MessageRegistry INSTANCE = new MessageRegistry() {{ // Note: Make sure IDs are unique // Also note: Removing IDs will break backwards compatibility - put(1, HelloMessage::new); + registerMessage(1, HelloMessage.class, HelloMessage::new); }}; - private final Map, Integer> classToId = idToConstructor.entrySet().stream() - .collect(Collectors.toMap( - e -> e.getValue().getClass(), - Map.Entry::getKey - )); + private final Map> idToSupplier = new HashMap<>(); + private final Map, Integer> classToId = new HashMap<>(); + + private MessageRegistry() { } + + public void registerMessage(int id, Class clazz, Supplier supplier) { + idToSupplier.put(id, (Supplier) supplier); + classToId.put(clazz, id); + } public Message createMessage(int id) throws IllegalArgumentException { try { - return idToConstructor.get(id).get(); + return idToSupplier.get(id).get(); } catch (NullPointerException e) { throw new IllegalArgumentException("Invalid message ID"); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/NetworkUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/NetworkUtil.java new file mode 100644 index 000000000..a67d68bb1 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/NetworkUtil.java @@ -0,0 +1,30 @@ +package com.seibel.distanthorizons.core.util; + +import com.seibel.distanthorizons.core.network.protocol.*; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.codec.LengthFieldPrepender; +import org.jetbrains.annotations.NotNull; + +public class NetworkUtil { + public static ChannelInitializer getChannelInitializer(MessageHandlerSide side) { + return new ChannelInitializer() { + @Override + public void initChannel(@NotNull SocketChannel ch) { + ChannelPipeline pipeline = ch.pipeline(); + + // Encoding + pipeline.addLast(new LengthFieldBasedFrameDecoder(Short.MAX_VALUE, 0, Short.BYTES, 0, Short.BYTES)); + pipeline.addLast(new MessageDecoder()); + + // Encoder + pipeline.addLast(new LengthFieldPrepender(Short.BYTES)); + pipeline.addLast(new MessageEncoder()); + + pipeline.addLast(new MessageHandler(side)); + } + }; + } +} 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 1c9126f19..cebe0fec3 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 @@ -3,6 +3,7 @@ package com.seibel.distanthorizons.core.world; 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.util.ThreadUtil; import com.seibel.distanthorizons.core.util.objects.EventLoop; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; @@ -18,6 +19,8 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld private final ConcurrentHashMap levels; public final ClientOnlySaveStructure saveStructure; + private final NetworkClient networkClient; + // TODO why does this executor have 2 threads? public ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("DH Client World Ticker Thread", 2); public EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick); @@ -27,8 +30,11 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld public DhClientWorld() { super(EWorldEnvironment.Client_Only); - this.saveStructure = new ClientOnlySaveStructure(); + + this.saveStructure = new ClientOnlySaveStructure(); this.levels = new ConcurrentHashMap<>(); + this.networkClient = new NetworkClient("127.0.0.1", 25049); + LOGGER.info("Started DhWorld of type "+this.environment); } 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 aa0574629..c32dcbe3d 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 @@ -3,6 +3,7 @@ package com.seibel.distanthorizons.core.world; 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.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; @@ -15,7 +16,7 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld { private final HashMap levels; public final LocalSaveStructure saveStructure; - + private final NetworkServer networkServer; public DhServerWorld() @@ -24,6 +25,7 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld this.saveStructure = new LocalSaveStructure(); this.levels = new HashMap<>(); + this.networkServer = new NetworkServer(25049); LOGGER.info("Started "+DhServerWorld.class.getSimpleName()+" of type "+this.environment); } From e660c466f887a4d7e6c0da180cb6768d51338d71 Mon Sep 17 00:00:00 2001 From: s809 <43530948+s809@users.noreply.github.com> Date: Thu, 29 Jun 2023 14:54:10 +0500 Subject: [PATCH 07/13] Remove unused param --- .../seibel/distanthorizons/core/level/ServerLevelModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java b/core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java index 4c0846abd..350adb7e5 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java @@ -64,7 +64,7 @@ public class ServerLevelModule { public final AppliedConfigState worldGeneratorEnabledConfig; private final AtomicReference worldGenStateRef = new AtomicReference<>(); - public ServerLevelModule(IDhServerLevel parent, IServerLevelWrapper levelWrapper, AbstractSaveStructure saveStructure, boolean startLodServer) + public ServerLevelModule(IDhServerLevel parent, IServerLevelWrapper levelWrapper, AbstractSaveStructure saveStructure) { this.parent = parent; this.levelWrapper = levelWrapper; From 2fc76e28426f61d2db65e62c06ceb11969e33b04 Mon Sep 17 00:00:00 2001 From: s809 <43530948+s809@users.noreply.github.com> Date: Thu, 29 Jun 2023 20:58:43 +0500 Subject: [PATCH 08/13] Minimal ping-pong but less rigid impl DhClientWorld#close is never fired smh --- .../core/network/NetworkClient.java | 46 +++++++++------ .../core/network/NetworkEventSource.java | 36 ++++++++++++ .../core/network/NetworkServer.java | 58 +++++++++++++------ .../core/network/messages/HelloMessage.java | 12 ---- .../core/network/messages/Message.java | 7 --- .../protocol/DhNetworkChannelInitializer.java | 31 ++++++++++ .../core/network/protocol/MessageHandler.java | 52 ++++++++++++----- .../network/protocol/MessageRegistry.java | 6 +- .../core/util/NetworkUtil.java | 30 ---------- .../core/world/DhClientWorld.java | 12 +++- .../core/world/DhServerWorld.java | 4 ++ 11 files changed, 192 insertions(+), 102 deletions(-) create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/NetworkEventSource.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/protocol/DhNetworkChannelInitializer.java delete mode 100644 core/src/main/java/com/seibel/distanthorizons/core/util/NetworkUtil.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 6ed422cc6..bd553ed78 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 @@ -1,8 +1,8 @@ package com.seibel.distanthorizons.core.network; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.network.messages.HelloMessage; -import com.seibel.distanthorizons.core.util.NetworkUtil; -import com.seibel.distanthorizons.core.network.protocol.MessageHandlerSide; +import com.seibel.distanthorizons.core.network.protocol.DhNetworkChannelInitializer; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; @@ -10,12 +10,13 @@ import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; +import org.apache.logging.log4j.Logger; -import java.io.Closeable; -import java.io.IOException; import java.util.concurrent.TimeUnit; -public class NetworkClient implements Closeable { +public class NetworkClient extends NetworkEventSource implements AutoCloseable { + private static final Logger LOGGER = DhLoggerBuilder.getLogger(); + private enum State { OPEN, RECONNECT, @@ -27,30 +28,29 @@ public class NetworkClient implements Closeable { // TODO move to config of some sort private final String host; private final int port; - private State state = State.OPEN; - private final Bootstrap clientBootstrap; private final EventLoopGroup workerGroup = new NioEventLoopGroup(); + private final Bootstrap clientBootstrap = new Bootstrap() + .group(workerGroup) + .channel(NioSocketChannel.class) + .option(ChannelOption.SO_KEEPALIVE, true) + .handler(new DhNetworkChannelInitializer(messageHandler)); + + private State state = State.OPEN; private Channel channel; public NetworkClient(String host, int port) { this.host = host; this.port = port; - - clientBootstrap = new Bootstrap() - .group(workerGroup) - .channel(NioSocketChannel.class) - .option(ChannelOption.SO_KEEPALIVE, true) - .handler(NetworkUtil.getChannelInitializer(MessageHandlerSide.CLIENT)); connect(); } private void connect() { + LOGGER.info("Connecting to {}:{}", host, port); + ChannelFuture connectFuture = clientBootstrap.connect(host, port); connectFuture.addListener((ChannelFuture channelFuture) -> { if (!channelFuture.isSuccess()) return; - - channel = channelFuture.channel(); channel.writeAndFlush(new HelloMessage()); }); @@ -61,6 +61,14 @@ public class NetworkClient implements Closeable { 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"); + }); + + registerDisconnectHandler(ctx -> { + LOGGER.info("Disconnected"); + }); } /** Kills the current connection, triggering auto-reconnection immediately. */ @@ -70,8 +78,12 @@ public class NetworkClient implements Closeable { } @Override - public void close() throws IOException { + public void close() { + if (closeReason != null) + LOGGER.error(closeReason); + state = State.CLOSED; - workerGroup.shutdownGracefully(); + workerGroup.shutdownGracefully().syncUninterruptibly(); + channel.close().syncUninterruptibly(); } } 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 new file mode 100644 index 000000000..7729eafac --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkEventSource.java @@ -0,0 +1,36 @@ +package com.seibel.distanthorizons.core.network; + +import com.seibel.distanthorizons.core.network.messages.HelloMessage; +import com.seibel.distanthorizons.core.network.messages.Message; +import com.seibel.distanthorizons.core.network.protocol.MessageHandler; +import com.seibel.distanthorizons.coreapi.ModInfo; +import io.netty.channel.ChannelHandlerContext; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +public abstract class NetworkEventSource implements AutoCloseable { + protected final MessageHandler messageHandler = new MessageHandler(); + protected String closeReason = null; + + public NetworkEventSource() { + registerHandler(HelloMessage.class, (msg, ctx) -> { + if (msg.version != ModInfo.PROTOCOL_VERSION) { + try { + closeReason = "Protocol version mismatch"; + close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + } + + public void registerHandler(Class clazz, BiConsumer handler) { + messageHandler.registerHandler(clazz, handler); + } + + public void registerDisconnectHandler(Consumer handler) { + messageHandler.registerDisconnectHandler(handler); + } +} 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 3149d7e1a..48fc1b7c5 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 @@ -1,45 +1,67 @@ package com.seibel.distanthorizons.core.network; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.network.messages.HelloMessage; import com.seibel.distanthorizons.core.network.protocol.*; -import com.seibel.distanthorizons.core.util.NetworkUtil; import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; +import org.apache.logging.log4j.Logger; -import java.io.Closeable; -import java.io.IOException; +public class NetworkServer extends NetworkEventSource implements AutoCloseable { + private static final Logger LOGGER = DhLoggerBuilder.getLogger(); -public class NetworkServer implements Closeable { // TODO move to config of some sort private final int port; - EventLoopGroup bossGroup = new NioEventLoopGroup(1); - EventLoopGroup workerGroup = new NioEventLoopGroup(); + private final EventLoopGroup bossGroup = new NioEventLoopGroup(1); + private final EventLoopGroup workerGroup = new NioEventLoopGroup(); + Channel channel; public NetworkServer(int port) { this.port = port; - ServerBootstrap b = new ServerBootstrap() + LOGGER.info("Starting server on port {}", port); + + ServerBootstrap bootstrap = new ServerBootstrap() .group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) - .handler(new LoggingHandler(LogLevel.INFO)) - .childHandler(NetworkUtil.getChannelInitializer(MessageHandlerSide.SERVER)); + .handler(new LoggingHandler(LogLevel.DEBUG)) + .childHandler(new DhNetworkChannelInitializer(messageHandler)); - b.bind(port) - .addListener((ChannelFuture channelFuture) -> { - if (!channelFuture.isSuccess()) - throw new RuntimeException("Failed to bind: " + channelFuture); - }) - .channel().closeFuture().addListener(future -> close()); + ChannelFuture bindFuture = bootstrap.bind(port); + bindFuture.addListener((ChannelFuture channelFuture) -> { + if (!channelFuture.isSuccess()) + throw new RuntimeException("Failed to bind: " + channelFuture.cause()); + + LOGGER.info("Server is ready"); + }); + + 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()); + }); + + registerDisconnectHandler(ctx -> { + LOGGER.info("Client disconnected: {}", ctx.channel().remoteAddress()); + }); } @Override - public void close() throws IOException { - bossGroup.shutdownGracefully(); - workerGroup.shutdownGracefully(); + public void close() { + if (closeReason != null) + LOGGER.error(closeReason); + + bossGroup.shutdownGracefully().syncUninterruptibly(); + workerGroup.shutdownGracefully().syncUninterruptibly(); + channel.close().syncUninterruptibly(); } } 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 c790e72a9..c29490b23 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 @@ -7,8 +7,6 @@ import io.netty.channel.ChannelHandlerContext; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; -// This message is critical to maintain backwards compatibility -// as it's used to receive version before everything else. public class HelloMessage extends Message { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); @@ -23,14 +21,4 @@ public class HelloMessage extends Message { public void decode(ChannelHandlerContext ctx, ByteBuf in) { version = in.readInt(); } - - @Override - public void handle_Server(ChannelHandlerContext ctx) { - LOGGER.log(Level.INFO, "Client version: " + version); - } - - @Override - public void handle_Client(ChannelHandlerContext ctx) { - LOGGER.log(Level.INFO, "Server version: " + version); - } } 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 index ac665966f..decb24d7e 100644 --- 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 @@ -8,12 +8,5 @@ public abstract class Message { public abstract void encode(ChannelHandlerContext ctx, ByteBuf out); public abstract void decode(ChannelHandlerContext ctx, ByteBuf in); - - public void handle_Server(ChannelHandlerContext ctx) { - throw new UnsupportedOperationException(); - } - public void handle_Client(ChannelHandlerContext ctx) { - throw new UnsupportedOperationException(); - } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/DhNetworkChannelInitializer.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/DhNetworkChannelInitializer.java new file mode 100644 index 000000000..fa102b35d --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/DhNetworkChannelInitializer.java @@ -0,0 +1,31 @@ +package com.seibel.distanthorizons.core.network.protocol; + +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.codec.LengthFieldPrepender; +import org.jetbrains.annotations.NotNull; + +public class DhNetworkChannelInitializer extends ChannelInitializer { + private final MessageHandler messageHandler; + + public DhNetworkChannelInitializer(MessageHandler messageHandler) { + this.messageHandler = messageHandler; + } + + @Override + public void initChannel(@NotNull SocketChannel ch) { + ChannelPipeline pipeline = ch.pipeline(); + + // Encoding + pipeline.addLast(new LengthFieldBasedFrameDecoder(Short.MAX_VALUE, 0, Short.BYTES, 0, Short.BYTES)); + pipeline.addLast(new MessageDecoder()); + + // Encoder + pipeline.addLast(new LengthFieldPrepender(Short.BYTES)); + pipeline.addLast(new MessageEncoder()); + + pipeline.addLast(messageHandler); + } +} 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 19ed385d7..90e84f1b6 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 @@ -1,27 +1,53 @@ package com.seibel.distanthorizons.core.network.protocol; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.network.messages.HelloMessage; import com.seibel.distanthorizons.core.network.messages.Message; +import com.seibel.distanthorizons.coreapi.ModInfo; +import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +@ChannelHandler.Sharable public class MessageHandler extends SimpleChannelInboundHandler { - private final MessageHandlerSide side; + private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - public MessageHandler(MessageHandlerSide side) { - this.side = side; + private Map, List>> handlers = new HashMap<>(); + private List> disconnectHandlers = new LinkedList<>(); + + public void registerHandler(Class clazz, BiConsumer handler) { + handlers.computeIfAbsent(clazz, k -> new LinkedList<>()) + .add((BiConsumer) handler); + } + + public void registerDisconnectHandler(Consumer handler) { + disconnectHandlers.add(handler); } @Override - public void channelRead0(ChannelHandlerContext ctx, Message msg) { - switch (side) { - case CLIENT: - msg.handle_Client(ctx); - break; - case SERVER: - msg.handle_Server(ctx); - break; - default: - throw new IllegalStateException("Invalid handler side"); + protected void channelRead0(ChannelHandlerContext ctx, Message msg) { + List> handlerList = handlers.get(msg.getClass()); + if (handlerList == null) { + LOGGER.warn("Unhandled message type: {}", msg.getClass().getSimpleName()); + return; } + + for (BiConsumer handler : handlerList) + handler.accept(msg, ctx); + } + + @Override + public void channelInactive(@NotNull ChannelHandlerContext ctx) { + for (Consumer handler : disconnectHandlers) + handler.accept(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 e02ba0b20..f8e5b8061 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 @@ -14,13 +14,13 @@ public class MessageRegistry { registerMessage(1, HelloMessage.class, HelloMessage::new); }}; - private final Map> idToSupplier = new HashMap<>(); - private final Map, Integer> classToId = new HashMap<>(); + private final Map> idToSupplier = new HashMap<>(); + private final Map, Integer> classToId = new HashMap<>(); private MessageRegistry() { } public void registerMessage(int id, Class clazz, Supplier supplier) { - idToSupplier.put(id, (Supplier) supplier); + idToSupplier.put(id, supplier); classToId.put(clazz, id); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/NetworkUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/NetworkUtil.java deleted file mode 100644 index a67d68bb1..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/NetworkUtil.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.seibel.distanthorizons.core.util; - -import com.seibel.distanthorizons.core.network.protocol.*; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelPipeline; -import io.netty.channel.socket.SocketChannel; -import io.netty.handler.codec.LengthFieldBasedFrameDecoder; -import io.netty.handler.codec.LengthFieldPrepender; -import org.jetbrains.annotations.NotNull; - -public class NetworkUtil { - public static ChannelInitializer getChannelInitializer(MessageHandlerSide side) { - return new ChannelInitializer() { - @Override - public void initChannel(@NotNull SocketChannel ch) { - ChannelPipeline pipeline = ch.pipeline(); - - // Encoding - pipeline.addLast(new LengthFieldBasedFrameDecoder(Short.MAX_VALUE, 0, Short.BYTES, 0, Short.BYTES)); - pipeline.addLast(new MessageDecoder()); - - // Encoder - pipeline.addLast(new LengthFieldPrepender(Short.BYTES)); - pipeline.addLast(new MessageEncoder()); - - pipeline.addLast(new MessageHandler(side)); - } - }; - } -} 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 cebe0fec3..5ec9d3320 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 @@ -1,11 +1,13 @@ package com.seibel.distanthorizons.core.world; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; 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.util.ThreadUtil; import com.seibel.distanthorizons.core.util.objects.EventLoop; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; @@ -16,6 +18,8 @@ import java.util.concurrent.ExecutorService; public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld { + private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); + private final ConcurrentHashMap levels; public final ClientOnlySaveStructure saveStructure; @@ -32,8 +36,10 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld super(EWorldEnvironment.Client_Only); this.saveStructure = new ClientOnlySaveStructure(); - this.levels = new ConcurrentHashMap<>(); - this.networkClient = new NetworkClient("127.0.0.1", 25049); + this.levels = new ConcurrentHashMap<>(); + + // TODO server specific configs + this.networkClient = new NetworkClient(MC_CLIENT.getCurrentServerIp(), 25049); LOGGER.info("Started DhWorld of type "+this.environment); } @@ -105,6 +111,8 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld @Override public void close() { + this.networkClient.close(); + this.saveAndFlush().join(); for (DhClientLevel dhClientLevel : this.levels.values()) { 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 c32dcbe3d..dda84d78e 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 @@ -25,6 +25,8 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld this.saveStructure = new LocalSaveStructure(); this.levels = new HashMap<>(); + + // TODO move to global config once server specific configs are implemented this.networkServer = new NetworkServer(25049); LOGGER.info("Started "+DhServerWorld.class.getSimpleName()+" of type "+this.environment); @@ -90,6 +92,8 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld @Override public void close() { + this.networkServer.close(); + for (DhServerLevel level : this.levels.values()) { LOGGER.info("Unloading level " + level.getLevelWrapper().getDimensionType().getDimensionName()); From 748b484377d580e259fdc45812a8a82d4d722c24 Mon Sep 17 00:00:00 2001 From: s809 <43530948+s809@users.noreply.github.com> Date: Fri, 30 Jun 2023 22:05:02 +0500 Subject: [PATCH 09/13] The real server side (not tested) --- .../core/api/internal/ServerApi.java | 21 +++++++- .../core/network/NetworkClient.java | 7 +-- .../core/network/NetworkEventSource.java | 8 +-- .../core/network/NetworkServer.java | 3 +- .../core/network/messages/CloseMessage.java | 20 ++++++++ .../network/messages/CloseReasonMessage.java | 21 ++++++++ .../core/network/messages/HelloMessage.java | 6 +-- .../network/messages/LodConfigMessage.java | 16 ++++++ .../core/network/messages/Message.java | 18 +++++-- .../network/messages/PlayerIdMessage.java | 24 +++++++++ .../core/network/protocol/MessageDecoder.java | 2 +- .../core/network/protocol/MessageEncoder.java | 2 +- .../core/network/protocol/MessageHandler.java | 12 +---- .../distanthorizons/core/world/DhPlayer.java | 19 +++++++ .../core/world/DhServerWorld.java | 50 ++++++++++++++++++- .../misc/IServerPlayerWrapper.java | 9 ++++ 16 files changed, 207 insertions(+), 31 deletions(-) create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/messages/CloseMessage.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/messages/CloseReasonMessage.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/messages/LodConfigMessage.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/messages/PlayerIdMessage.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/world/DhPlayer.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/misc/IServerPlayerWrapper.java diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ServerApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ServerApi.java index b6b8b8040..35fef56f8 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ServerApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ServerApi.java @@ -21,6 +21,7 @@ package com.seibel.distanthorizons.core.api.internal; import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelLoadEvent; import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelUnloadEvent; +import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector; import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.world.AbstractDhWorld; @@ -159,5 +160,23 @@ public class ServerApi } } } - + + public void serverPlayerJoinEvent(IServerPlayerWrapper player) + { + IDhServerWorld serverWorld = SharedApi.getIDhServerWorld(); + if (serverWorld instanceof DhServerWorld) + { + LOGGER.debug("Waiting for player to connect: " + player.getUUID()); + ((DhServerWorld) serverWorld).addPlayer(player); + } + } + public void serverPlayerDisconnectEvent(IServerPlayerWrapper player) + { + IDhServerWorld serverWorld = SharedApi.getIDhServerWorld(); + if (serverWorld instanceof DhServerWorld) + { + LOGGER.debug("Removing player from connect wait list: " + player.getUUID()); + ((DhServerWorld) serverWorld).removePlayer(player); + } + } } 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 bd553ed78..7fc6df361 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 @@ -1,6 +1,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.HelloMessage; import com.seibel.distanthorizons.core.network.protocol.DhNetworkChannelInitializer; import io.netty.bootstrap.Bootstrap; @@ -63,11 +64,11 @@ public class NetworkClient extends NetworkEventSource implements AutoCloseable { }); registerHandler(HelloMessage.class, (msg, ctx) -> { - LOGGER.info("Connected"); + LOGGER.info("Connected."); }); - registerDisconnectHandler(ctx -> { - LOGGER.info("Disconnected"); + registerHandler(CloseMessage.class, (msg, ctx) -> { + LOGGER.info("Disconnected."); }); } 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 7729eafac..df7338896 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 @@ -17,8 +17,7 @@ public abstract class NetworkEventSource implements AutoCloseable { registerHandler(HelloMessage.class, (msg, ctx) -> { if (msg.version != ModInfo.PROTOCOL_VERSION) { try { - closeReason = "Protocol version mismatch"; - close(); + close("Protocol version mismatch"); } catch (Exception e) { throw new RuntimeException(e); } @@ -30,7 +29,8 @@ public abstract class NetworkEventSource implements AutoCloseable { messageHandler.registerHandler(clazz, handler); } - public void registerDisconnectHandler(Consumer handler) { - messageHandler.registerDisconnectHandler(handler); + 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 48fc1b7c5..1bcb84431 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 @@ -1,6 +1,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.HelloMessage; import com.seibel.distanthorizons.core.network.protocol.*; import io.netty.bootstrap.ServerBootstrap; @@ -50,7 +51,7 @@ public class NetworkServer extends NetworkEventSource implements AutoCloseable { ctx.channel().writeAndFlush(new HelloMessage()); }); - registerDisconnectHandler(ctx -> { + registerHandler(CloseMessage.class, (msg, ctx) -> { LOGGER.info("Client disconnected: {}", ctx.channel().remoteAddress()); }); } 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 new file mode 100644 index 000000000..aed5933c6 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/CloseMessage.java @@ -0,0 +1,20 @@ +package com.seibel.distanthorizons.core.network.messages; + +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 { + @Override + public void encode(ByteBuf out) { + throw new UnsupportedOperationException("CloseMessage is not a real message, and must not be sent."); + } + + @Override + public void decode(ByteBuf in) { + throw new UnsupportedOperationException("CloseMessage is not a real message, and must not be received."); + } +} + 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 new file mode 100644 index 000000000..b6f467768 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/CloseReasonMessage.java @@ -0,0 +1,21 @@ +package com.seibel.distanthorizons.core.network.messages; + +import io.netty.buffer.ByteBuf; + +public class CloseReasonMessage extends Message { + public String reason; + + public CloseReasonMessage(String reason) { + this.reason = reason; + } + + @Override + public void encode(ByteBuf out) { + encodeString(reason, out); + } + + @Override + public void decode(ByteBuf in) { + reason = 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 c29490b23..d52f17488 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 @@ -8,17 +8,15 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; public class HelloMessage extends Message { - private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - public int version = ModInfo.PROTOCOL_VERSION; @Override - public void encode(ChannelHandlerContext ctx, ByteBuf out) { + public void encode(ByteBuf out) { out.writeInt(version); } @Override - public void decode(ChannelHandlerContext ctx, ByteBuf in) { + public void decode(ByteBuf in) { version = in.readInt(); } } 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 new file mode 100644 index 000000000..54b114d33 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/LodConfigMessage.java @@ -0,0 +1,16 @@ +package com.seibel.distanthorizons.core.network.messages; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; + +public class LodConfigMessage extends Message { + @Override + public void encode(ByteBuf out) { + + } + + @Override + public void decode(ByteBuf 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 index decb24d7e..4958f8e18 100644 --- 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 @@ -3,10 +3,20 @@ package com.seibel.distanthorizons.core.network.messages; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; -public abstract class Message { - public Message() { } +import java.nio.charset.StandardCharsets; - public abstract void encode(ChannelHandlerContext ctx, ByteBuf out); - public abstract void decode(ChannelHandlerContext ctx, ByteBuf in); +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/PlayerIdMessage.java new file mode 100644 index 000000000..a071b8cda --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/PlayerIdMessage.java @@ -0,0 +1,24 @@ +package com.seibel.distanthorizons.core.network.messages; + +import io.netty.buffer.ByteBuf; + +import java.util.UUID; + +public class PlayerIdMessage extends Message { + public UUID playerUUID; + + public PlayerIdMessage(UUID playerUUID) { + this.playerUUID = playerUUID; + } + + @Override + public void encode(ByteBuf out) { + out.writeLong(playerUUID.getMostSignificantBits()); + out.writeLong(playerUUID.getLeastSignificantBits()); + } + + @Override + public void decode(ByteBuf in) { + playerUUID = new UUID(in.readLong(), in.readLong()); + } +} 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 82b653c56..092c20dbf 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 @@ -11,7 +11,7 @@ public class MessageDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { Message message = MessageRegistry.INSTANCE.createMessage(in.readShort()); - message.decode(ctx, in); + message.decode(in); out.add(message); } } 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 5b491ccc7..f1e54e4d1 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 @@ -9,6 +9,6 @@ public class MessageEncoder extends MessageToByteEncoder { @Override protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws IllegalArgumentException { out.writeShort(MessageRegistry.INSTANCE.getMessageId(msg)); - msg.encode(ctx, out); + 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 90e84f1b6..e3e0b9e40 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 @@ -1,9 +1,8 @@ package com.seibel.distanthorizons.core.network.protocol; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; -import com.seibel.distanthorizons.core.network.messages.HelloMessage; +import com.seibel.distanthorizons.core.network.messages.CloseMessage; import com.seibel.distanthorizons.core.network.messages.Message; -import com.seibel.distanthorizons.coreapi.ModInfo; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; @@ -15,24 +14,18 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.function.BiConsumer; -import java.util.function.Consumer; @ChannelHandler.Sharable public class MessageHandler extends SimpleChannelInboundHandler { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private Map, List>> handlers = new HashMap<>(); - private List> disconnectHandlers = new LinkedList<>(); public void registerHandler(Class clazz, BiConsumer handler) { handlers.computeIfAbsent(clazz, k -> new LinkedList<>()) .add((BiConsumer) handler); } - public void registerDisconnectHandler(Consumer handler) { - disconnectHandlers.add(handler); - } - @Override protected void channelRead0(ChannelHandlerContext ctx, Message msg) { List> handlerList = handlers.get(msg.getClass()); @@ -47,7 +40,6 @@ public class MessageHandler extends SimpleChannelInboundHandler { @Override public void channelInactive(@NotNull ChannelHandlerContext ctx) { - for (Consumer handler : disconnectHandlers) - handler.accept(ctx); + channelRead0(ctx, new CloseMessage()); } } 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 new file mode 100644 index 000000000..4d641e49d --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhPlayer.java @@ -0,0 +1,19 @@ +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/DhServerWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java index dda84d78e..2fbfadfd9 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 @@ -4,20 +4,30 @@ 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.util.LodUtil; +import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; +import io.netty.channel.ChannelHandlerContext; import java.io.File; import java.util.HashMap; +import java.util.UUID; import java.util.concurrent.CompletableFuture; public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld { private final HashMap levels; public final LocalSaveStructure saveStructure; + private final NetworkServer networkServer; - + private final HashMap players; + private final HashMap connections; + public DhServerWorld() { @@ -28,11 +38,47 @@ 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<>(); + 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); + + if (dhPlayer == null) { + ctx.writeAndFlush(new CloseReasonMessage("Player is not logged in.")) + .addListener(future -> ctx.close()); + return; + } + + if (dhPlayer.ctx != null) { + ctx.writeAndFlush(new CloseReasonMessage("Another connection is already in use.")) + .addListener(future -> ctx.close()); + return; + } + + dhPlayer.ctx = ctx; + connections.put(ctx, dhPlayer); + }); + + networkServer.registerHandler(CloseMessage.class, (msg, ctx) -> { + DhPlayer dhPlayer = connections.remove(ctx); + if (dhPlayer != null) + dhPlayer.ctx = null; + }); + } - + public void addPlayer(IServerPlayerWrapper serverPlayer) { + players.put(serverPlayer.getUUID(), new DhPlayer(serverPlayer)); + } + + public void removePlayer(IServerPlayerWrapper serverPlayer) { + players.remove(serverPlayer.getUUID()); + } @Override public DhServerLevel getOrLoadLevel(ILevelWrapper wrapper) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/misc/IServerPlayerWrapper.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/misc/IServerPlayerWrapper.java new file mode 100644 index 000000000..7ac391ecd --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/misc/IServerPlayerWrapper.java @@ -0,0 +1,9 @@ +package com.seibel.distanthorizons.core.wrapperInterfaces.misc; + +import com.seibel.distanthorizons.api.interfaces.IDhApiUnsafeWrapper; + +import java.util.UUID; + +public interface IServerPlayerWrapper extends IDhApiUnsafeWrapper { + UUID getUUID(); +} 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 10/13] 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. From 83ab7e46b3185170fba7e3a5dbd5b7018d8a6948 Mon Sep 17 00:00:00 2001 From: s809 <43530948+s809@users.noreply.github.com> Date: Sat, 8 Jul 2023 12:25:20 +0500 Subject: [PATCH 11/13] Base is pretty much finished --- .../core/network/NetworkClient.java | 24 +++++++++++++++---- .../core/network/NetworkServer.java | 16 +++++++------ .../network/messages/LodConfigMessage.java | 5 ++++ .../protocol/DhNetworkChannelInitializer.java | 16 ++++++++----- .../network/protocol/ExceptionHandler.java | 16 +++++++++++++ .../core/network/protocol/MessageDecoder.java | 2 ++ .../core/network/protocol/MessageEncoder.java | 2 ++ .../core/network/protocol/MessageHandler.java | 2 ++ .../protocol/OutboundExceptionRouter.java | 14 +++++++++++ .../core/world/DhClientWorld.java | 2 +- .../core/world/DhServerWorld.java | 9 ++++--- 11 files changed, 85 insertions(+), 23 deletions(-) create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/protocol/ExceptionHandler.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/protocol/OutboundExceptionRouter.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 f22102a2a..2253f7ea9 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 @@ -5,6 +5,7 @@ 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 com.seibel.distanthorizons.core.network.protocol.MessageHandler; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; @@ -29,6 +30,7 @@ public class NetworkClient extends NetworkEventSource implements AutoCloseable { } private static final int FAILURE_RECONNECT_DELAY_SEC = 5; + private static final int FAILURE_RECONNECT_ATTEMPTS = 5; // TODO move to config of some sort private final InetSocketAddress address; @@ -42,6 +44,7 @@ public class NetworkClient extends NetworkEventSource implements AutoCloseable { private State state; private Channel channel; + private int reconnectAttempts = FAILURE_RECONNECT_ATTEMPTS; public NetworkClient(String host, int port) { this.address = new InetSocketAddress(host, port); @@ -62,6 +65,8 @@ public class NetworkClient extends NetworkEventSource implements AutoCloseable { registerHandler(CloseMessage.class, (msg, ctx) -> { LOGGER.info("Disconnected from server: {}", ctx.channel().remoteAddress()); + if (state == State.CLOSE_WAIT) + close(); }); } @@ -71,21 +76,31 @@ public class NetworkClient extends NetworkEventSource implements AutoCloseable { ChannelFuture connectFuture = clientBootstrap.connect(address); connectFuture.addListener((ChannelFuture channelFuture) -> { - if (!channelFuture.isSuccess()) return; + if (!channelFuture.isSuccess()) { + LOGGER.warn("Connection failed: {0}", channelFuture.cause()); + return; + } channel.writeAndFlush(new HelloMessage()); }); channel = connectFuture.channel(); channel.closeFuture().addListener((ChannelFuture channelFuture) -> { switch (state) { - case CLOSE_WAIT: - close(); - break; case OPEN: + reconnectAttempts--; + LOGGER.info("Reconnection attempts left: {} of {}", reconnectAttempts, FAILURE_RECONNECT_ATTEMPTS); + if (reconnectAttempts == 0) { + state = State.CLOSE_WAIT; + return; + } + state = State.RECONNECT; workerGroup.schedule(this::connect, FAILURE_RECONNECT_DELAY_SEC, TimeUnit.SECONDS); break; case RECONNECT_FORCE: + LOGGER.info("Reconnecting forcefully."); + reconnectAttempts = FAILURE_RECONNECT_ATTEMPTS; + state = State.RECONNECT; workerGroup.schedule(this::connect, 0, TimeUnit.SECONDS); break; @@ -104,6 +119,7 @@ public class NetworkClient extends NetworkEventSource implements AutoCloseable { if (closeReason != null) LOGGER.error(closeReason); + if (state == State.CLOSED) return; state = State.CLOSED; workerGroup.shutdownGracefully().syncUninterruptibly(); channel.close().syncUninterruptibly(); 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 2c5813ba2..3d3c70a4d 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 @@ -6,10 +6,7 @@ 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.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.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.logging.LogLevel; @@ -25,6 +22,7 @@ public class NetworkServer extends NetworkEventSource implements AutoCloseable { private final EventLoopGroup bossGroup = new NioEventLoopGroup(1); private final EventLoopGroup workerGroup = new NioEventLoopGroup(); private Channel channel; + private boolean isClosed = false; public NetworkServer(int port) { this.port = port; @@ -67,7 +65,7 @@ public class NetworkServer extends NetworkEventSource implements AutoCloseable { public void disconnectClient(ChannelHandlerContext ctx, String reason) { ctx.channel().config().setAutoRead(false); ctx.writeAndFlush(new CloseReasonMessage(reason)) - .addListener(future -> ctx.close()); + .addListener(ChannelFutureListener.CLOSE); } @Override @@ -75,8 +73,12 @@ public class NetworkServer extends NetworkEventSource implements AutoCloseable { if (closeReason != null) LOGGER.error(closeReason); - bossGroup.shutdownGracefully().syncUninterruptibly(); + if (isClosed) return; + isClosed = true; + + LOGGER.info("Shutting down the server."); workerGroup.shutdownGracefully().syncUninterruptibly(); - channel.close().syncUninterruptibly(); + bossGroup.shutdownGracefully().syncUninterruptibly(); + LOGGER.info("Server is closed."); } } 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 bff6c2ca2..43eb092dc 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 @@ -8,6 +8,11 @@ import io.netty.buffer.ByteBuf; public class LodConfigMessage implements INetworkMessage { public DhRemotePlayer.Config config; + public LodConfigMessage() { } + public LodConfigMessage(DhRemotePlayer.Config config) { + this.config = config; + } + @Override public void encode(ByteBuf out) { config.encode(out); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/DhNetworkChannelInitializer.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/DhNetworkChannelInitializer.java index fa102b35d..c0283fef5 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/DhNetworkChannelInitializer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/DhNetworkChannelInitializer.java @@ -1,10 +1,11 @@ package com.seibel.distanthorizons.core.network.protocol; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelPipeline; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import io.netty.channel.*; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.LengthFieldPrepender; +import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; public class DhNetworkChannelInitializer extends ChannelInitializer { @@ -18,14 +19,17 @@ public class DhNetworkChannelInitializer extends ChannelInitializer { @Override 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 e4aa4a285..0ca377314 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 @@ -27,6 +27,8 @@ public class MessageHandler extends SimpleChannelInboundHandler @Override protected void channelRead0(ChannelHandlerContext ctx, INetworkMessage msg) { + LOGGER.trace("Received message: {}", msg.getClass().getSimpleName()); + List> handlerList = handlers.get(msg.getClass()); if (handlerList == null) { LOGGER.warn("Unhandled message type: {}", msg.getClass().getSimpleName()); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/OutboundExceptionRouter.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/OutboundExceptionRouter.java new file mode 100644 index 000000000..e222aeb10 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/OutboundExceptionRouter.java @@ -0,0 +1,14 @@ +package com.seibel.distanthorizons.core.network.protocol; + +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOutboundHandlerAdapter; +import io.netty.channel.ChannelPromise; + +public class OutboundExceptionRouter extends ChannelOutboundHandlerAdapter { + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + promise.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + super.write(ctx, msg, promise); + } +} 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 2a65f80a3..66ef535a2 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 @@ -56,7 +56,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld // TODO Proper config handling networkClient.registerAckHandler(PlayerUUIDMessage.class, ctx -> { - ctx.writeAndFlush(new LodConfigMessage()); + ctx.writeAndFlush(new LodConfigMessage(new DhRemotePlayer.Config())); }); networkClient.registerHandler(LodConfigMessage.class, (msg, ctx) -> { 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 f55980ab9..e76a53c1b 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 @@ -72,11 +72,12 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld }); networkServer.registerHandler(LodConfigMessage.class, (msg, ctx) -> { - // TODO Take notice of received config + // TODO Take notice of received config and possibly echo back a constrained version ctx.writeAndFlush(new AckMessage(LodConfigMessage.class)); }); networkServer.registerHandler(RequestChunksMessage.class, (msg, ctx) -> { + LOGGER.info("RequestChunksMessage"); // hasReceivedChunkRequest should be false somewhere ??? // to avoid sending updates until client says at least something about its state }); @@ -89,10 +90,8 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld public void removePlayer(IServerPlayerWrapper serverPlayer) { 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()); - } + if (ctx != null) + networkServer.disconnectClient(ctx, "You are being disconnected."); } @Override From 730216b761be28b160d78bb58263232772f6d6ad Mon Sep 17 00:00:00 2001 From: s809 <43530948+s809@users.noreply.github.com> Date: Sat, 8 Jul 2023 16:22:54 +0500 Subject: [PATCH 12/13] Fix log string --- .../com/seibel/distanthorizons/core/network/NetworkClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2253f7ea9..a3fc3138e 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 @@ -77,7 +77,7 @@ public class NetworkClient extends NetworkEventSource implements AutoCloseable { ChannelFuture connectFuture = clientBootstrap.connect(address); connectFuture.addListener((ChannelFuture channelFuture) -> { if (!channelFuture.isSuccess()) { - LOGGER.warn("Connection failed: {0}", channelFuture.cause()); + LOGGER.warn("Connection failed: {}", channelFuture.cause()); return; } channel.writeAndFlush(new HelloMessage()); From ed9cccccea4f323ffea9b3681a67c5f3371dadbf Mon Sep 17 00:00:00 2001 From: James Seibel Date: Tue, 11 Jul 2023 08:49:08 -0500 Subject: [PATCH 13/13] refactoring --- .../core/api/internal/ServerApi.java | 10 +- .../core/network/NetworkClient.java | 171 ++++++++++-------- .../core/network/NetworkEventSource.java | 81 +++++---- .../core/network/NetworkServer.java | 159 +++++++++------- .../core/network/messages/AckMessage.java | 24 ++- .../core/network/messages/CloseMessage.java | 14 +- .../network/messages/CloseReasonMessage.java | 32 ++-- .../core/network/messages/HelloMessage.java | 18 +- .../network/messages/LodConfigMessage.java | 25 --- .../network/messages/PlayerUUIDMessage.java | 27 +-- .../messages/RemotePlayerConfigMessage.java | 23 +++ .../messages/RequestChunksMessage.java | 10 +- .../core/network/objects/RemotePlayer.java | 34 ++++ ...dlerSide.java => EMessageHandlerSide.java} | 7 +- .../network/protocol/INetworkMessage.java | 6 +- .../core/network/protocol/INetworkObject.java | 43 +++-- .../core/network/protocol/MessageDecoder.java | 13 +- .../core/network/protocol/MessageEncoder.java | 17 +- .../core/network/protocol/MessageHandler.java | 63 ++++--- .../network/protocol/MessageRegistry.java | 90 ++++----- ...er.java => NetworkChannelInitializer.java} | 34 ++-- ...dler.java => NetworkExceptionHandler.java} | 15 +- .../NetworkOutboundExceptionRouter.java | 17 ++ .../protocol/OutboundExceptionRouter.java | 14 -- .../core/world/DhClientWorld.java | 14 +- .../core/world/DhRemotePlayer.java | 31 ---- .../core/world/DhServerWorld.java | 98 +++++----- .../misc/IServerPlayerWrapper.java | 3 +- 28 files changed, 602 insertions(+), 491 deletions(-) delete mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/messages/LodConfigMessage.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/messages/RemotePlayerConfigMessage.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/objects/RemotePlayer.java rename core/src/main/java/com/seibel/distanthorizons/core/network/protocol/{MessageHandlerSide.java => EMessageHandlerSide.java} (53%) rename core/src/main/java/com/seibel/distanthorizons/core/network/protocol/{DhNetworkChannelInitializer.java => NetworkChannelInitializer.java} (54%) rename core/src/main/java/com/seibel/distanthorizons/core/network/protocol/{ExceptionHandler.java => NetworkExceptionHandler.java} (55%) create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/protocol/NetworkOutboundExceptionRouter.java delete mode 100644 core/src/main/java/com/seibel/distanthorizons/core/network/protocol/OutboundExceptionRouter.java delete mode 100644 core/src/main/java/com/seibel/distanthorizons/core/world/DhRemotePlayer.java diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ServerApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ServerApi.java index 35fef56f8..4a0379701 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ServerApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ServerApi.java @@ -37,9 +37,6 @@ import org.apache.logging.log4j.Logger; /** * This holds the methods that should be called by the host mod loader (Fabric, * Forge, etc.). Specifically server events. - * - * @author James Seibel - * @version 2022-9-16 */ public class ServerApi { @@ -160,11 +157,11 @@ public class ServerApi } } } - + public void serverPlayerJoinEvent(IServerPlayerWrapper player) { IDhServerWorld serverWorld = SharedApi.getIDhServerWorld(); - if (serverWorld instanceof DhServerWorld) + if (serverWorld instanceof DhServerWorld) // TODO add support for DhClientServerWorld's (lan worlds) as well { LOGGER.debug("Waiting for player to connect: " + player.getUUID()); ((DhServerWorld) serverWorld).addPlayer(player); @@ -173,10 +170,11 @@ public class ServerApi public void serverPlayerDisconnectEvent(IServerPlayerWrapper player) { IDhServerWorld serverWorld = SharedApi.getIDhServerWorld(); - if (serverWorld instanceof DhServerWorld) + if (serverWorld instanceof DhServerWorld) // TODO add support for DhClientServerWorld's (lan worlds) as well { LOGGER.debug("Removing player from connect wait list: " + player.getUUID()); ((DhServerWorld) serverWorld).removePlayer(player); } } + } 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 a3fc3138e..f9b4b9dbc 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 @@ -4,8 +4,7 @@ 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 com.seibel.distanthorizons.core.network.protocol.MessageHandler; +import com.seibel.distanthorizons.core.network.protocol.NetworkChannelInitializer; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; @@ -18,110 +17,138 @@ import org.apache.logging.log4j.Logger; import java.net.InetSocketAddress; import java.util.concurrent.TimeUnit; -public class NetworkClient extends NetworkEventSource implements AutoCloseable { +public class NetworkClient extends NetworkEventSource implements AutoCloseable +{ private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - - private enum State { + + private enum EConnectionState + { OPEN, RECONNECT, RECONNECT_FORCE, CLOSE_WAIT, CLOSED } - + private static final int FAILURE_RECONNECT_DELAY_SEC = 5; private static final int FAILURE_RECONNECT_ATTEMPTS = 5; - - // TODO move to config of some sort + + // TODO move to payload of some sort private final InetSocketAddress address; - + private final EventLoopGroup workerGroup = new NioEventLoopGroup(); private final Bootstrap clientBootstrap = new Bootstrap() - .group(workerGroup) + .group(this.workerGroup) .channel(NioSocketChannel.class) .option(ChannelOption.SO_KEEPALIVE, true) - .handler(new DhNetworkChannelInitializer(messageHandler)); - - private State state; + .handler(new NetworkChannelInitializer(this.messageHandler)); + + private EConnectionState connectionState; private Channel channel; private int reconnectAttempts = FAILURE_RECONNECT_ATTEMPTS; - - public NetworkClient(String host, int port) { + + + + public NetworkClient(String host, int port) + { this.address = new InetSocketAddress(host, port); - - registerHandlers(); - connect(); + + this.registerHandlers(); + this.connect(); } - - private void registerHandlers() { - registerHandler(HelloMessage.class, (msg, ctx) -> { - LOGGER.info("Connected to server: {}", ctx.channel().remoteAddress()); + + private void registerHandlers() + { + this.registerHandler(HelloMessage.class, (helloMessage, channelContext) -> + { + LOGGER.info("Connected to server: "+channelContext.channel().remoteAddress()); }); - - registerHandler(CloseReasonMessage.class, (msg, ctx) -> { - LOGGER.info(msg.reason); - state = State.CLOSE_WAIT; + + this.registerHandler(CloseReasonMessage.class, (closeReasonMessage, channelContext) -> + { + LOGGER.info(closeReasonMessage.reason); + this.connectionState = EConnectionState.CLOSE_WAIT; }); - - registerHandler(CloseMessage.class, (msg, ctx) -> { - LOGGER.info("Disconnected from server: {}", ctx.channel().remoteAddress()); - if (state == State.CLOSE_WAIT) - close(); + + this.registerHandler(CloseMessage.class, (closeMessage, channelContext) -> + { + LOGGER.info("Disconnected from server: "+channelContext.channel().remoteAddress()); + if (this.connectionState == EConnectionState.CLOSE_WAIT) + { + this.close(); + } }); } - private void connect() { - LOGGER.info("Connecting to server: {}", address); - state = State.OPEN; + private void connect() + { + LOGGER.info("Connecting to server: "+this.address); + this.connectionState = EConnectionState.OPEN; - ChannelFuture connectFuture = clientBootstrap.connect(address); - connectFuture.addListener((ChannelFuture channelFuture) -> { - if (!channelFuture.isSuccess()) { - LOGGER.warn("Connection failed: {}", channelFuture.cause()); + ChannelFuture connectFuture = this.clientBootstrap.connect(this.address); + connectFuture.addListener((ChannelFuture channelFuture) -> + { + if (!channelFuture.isSuccess()) + { + LOGGER.warn("Connection failed: "+channelFuture.cause()); return; } - channel.writeAndFlush(new HelloMessage()); + + this.channel.writeAndFlush(new HelloMessage()); }); - - channel = connectFuture.channel(); - channel.closeFuture().addListener((ChannelFuture channelFuture) -> { - switch (state) { - case OPEN: - reconnectAttempts--; - LOGGER.info("Reconnection attempts left: {} of {}", reconnectAttempts, FAILURE_RECONNECT_ATTEMPTS); - if (reconnectAttempts == 0) { - state = State.CLOSE_WAIT; - return; - } - - state = State.RECONNECT; - workerGroup.schedule(this::connect, FAILURE_RECONNECT_DELAY_SEC, TimeUnit.SECONDS); - break; - case RECONNECT_FORCE: - LOGGER.info("Reconnecting forcefully."); - reconnectAttempts = FAILURE_RECONNECT_ATTEMPTS; - - state = State.RECONNECT; - workerGroup.schedule(this::connect, 0, TimeUnit.SECONDS); - break; - } + + this.channel = connectFuture.channel(); + this. channel.closeFuture().addListener((ChannelFuture channelFuture) -> + { + switch (this.connectionState) + { + case OPEN: + this.reconnectAttempts--; + LOGGER.info("Reconnection attempts left: ["+this.reconnectAttempts+"] of ["+FAILURE_RECONNECT_ATTEMPTS+"]."); + if (this.reconnectAttempts == 0) + { + this.connectionState = EConnectionState.CLOSE_WAIT; + return; + } + + this.connectionState = EConnectionState.RECONNECT; + this.workerGroup.schedule(this::connect, FAILURE_RECONNECT_DELAY_SEC, TimeUnit.SECONDS); + break; + + case RECONNECT_FORCE: + LOGGER.info("Reconnecting forcefully."); + this.reconnectAttempts = FAILURE_RECONNECT_ATTEMPTS; + + this.connectionState = EConnectionState.RECONNECT; + this.workerGroup.schedule(this::connect, 0, TimeUnit.SECONDS); + break; + } }); } /** Kills the current connection, triggering auto-reconnection immediately. */ - public void reconnect() { - state = State.RECONNECT_FORCE; - channel.disconnect(); + public void reconnect() + { + this.connectionState = EConnectionState.RECONNECT_FORCE; + this.channel.disconnect(); } @Override - public void close() { - if (closeReason != null) - LOGGER.error(closeReason); + public void close() + { + if (this.closeReason != null) + { + LOGGER.error(this.closeReason); + } - if (state == State.CLOSED) return; - state = State.CLOSED; - workerGroup.shutdownGracefully().syncUninterruptibly(); - channel.close().syncUninterruptibly(); + if (this.connectionState == EConnectionState.CLOSED) + { + return; + } + + this.connectionState = EConnectionState.CLOSED; + this.workerGroup.shutdownGracefully().syncUninterruptibly(); + this.channel.close().syncUninterruptibly(); } + } 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 e14bdc1da..f12967cb6 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,44 +1,63 @@ package com.seibel.distanthorizons.core.network; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.network.messages.AckMessage; import com.seibel.distanthorizons.core.network.messages.HelloMessage; 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; +import org.apache.logging.log4j.Logger; import java.util.function.BiConsumer; import java.util.function.Consumer; -public abstract class NetworkEventSource implements AutoCloseable { - protected final MessageHandler messageHandler = new MessageHandler(); - protected String closeReason = null; - - public NetworkEventSource() { - registerHandler(HelloMessage.class, (msg, ctx) -> { - if (msg.version != ModInfo.PROTOCOL_VERSION) { - try { - close("Protocol version mismatch"); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - }); - } - - 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(); - } +public abstract class NetworkEventSource implements AutoCloseable +{ + private static final Logger LOGGER = DhLoggerBuilder.getLogger(); + + protected final MessageHandler messageHandler = new MessageHandler(); + protected String closeReason = null; + + + + public NetworkEventSource() + { + this.registerHandler(HelloMessage.class, (helloMessage, channelContext) -> + { + if (helloMessage.version != ModInfo.PROTOCOL_VERSION) + { + try + { + String closeReason = "Ignoring message from channel ["+channelContext.name()+"], due to version mismatch. Expected version: ["+ModInfo.PROTOCOL_VERSION+"], received version: ["+helloMessage.version+"]."; + LOGGER.info(closeReason); + this.close(closeReason); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + }); + } + + public void registerHandler(Class clazz, BiConsumer handler) { this.messageHandler.registerHandler(clazz, handler); } + + public void registerAckHandler(Class clazz, Consumer handler) + { + this.messageHandler.registerHandler(AckMessage.class, (ackMessage, channelContext) -> + { + if (ackMessage.messageType == clazz) + { + handler.accept(channelContext); + } + }); + } + + public void close(String reason) throws Exception + { + this.closeReason = reason; + this.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 3d3c70a4d..b66e79879 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 @@ -1,10 +1,10 @@ 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.CloseMessage; import com.seibel.distanthorizons.core.network.messages.HelloMessage; -import com.seibel.distanthorizons.core.network.protocol.DhNetworkChannelInitializer; +import com.seibel.distanthorizons.core.network.protocol.NetworkChannelInitializer; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; @@ -13,72 +13,91 @@ import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import org.apache.logging.log4j.Logger; -public class NetworkServer extends NetworkEventSource implements AutoCloseable { - private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - - // TODO move to config of some sort - private final int port; - - private final EventLoopGroup bossGroup = new NioEventLoopGroup(1); - private final EventLoopGroup workerGroup = new NioEventLoopGroup(); - private Channel channel; - private boolean isClosed = false; - - 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) - .handler(new LoggingHandler(LogLevel.DEBUG)) - .childHandler(new DhNetworkChannelInitializer(messageHandler)); - - ChannelFuture bindFuture = bootstrap.bind(port); - bindFuture.addListener((ChannelFuture channelFuture) -> { - if (!channelFuture.isSuccess()) - throw new RuntimeException("Failed to bind: " + channelFuture.cause()); - - LOGGER.info("Server is started on port {}", port); - }); - - channel = bindFuture.channel(); - channel.closeFuture().addListener(future -> close()); - } - - public void disconnectClient(ChannelHandlerContext ctx, String reason) { - ctx.channel().config().setAutoRead(false); - ctx.writeAndFlush(new CloseReasonMessage(reason)) - .addListener(ChannelFutureListener.CLOSE); - } - - @Override - public void close() { - if (closeReason != null) - LOGGER.error(closeReason); - - if (isClosed) return; - isClosed = true; - - LOGGER.info("Shutting down the server."); - workerGroup.shutdownGracefully().syncUninterruptibly(); - bossGroup.shutdownGracefully().syncUninterruptibly(); - LOGGER.info("Server is closed."); - } +public class NetworkServer extends NetworkEventSource implements AutoCloseable +{ + private static final Logger LOGGER = DhLoggerBuilder.getLogger(); + + // TODO move to the config + private final int port; + + private final EventLoopGroup bossGroup = new NioEventLoopGroup(1); + private final EventLoopGroup workerGroup = new NioEventLoopGroup(); + private Channel channel; + private boolean isClosed = false; + + + + public NetworkServer(int port) + { + this.port = port; + + LOGGER.info("Starting server on port "+port); + this.registerHandlers(); + this.bind(); + } + + private void registerHandlers() + { + this.registerHandler(HelloMessage.class, (helloMessage, channelContext) -> + { + LOGGER.info("Client connected: "+channelContext.channel().remoteAddress()); + channelContext.channel().writeAndFlush(new HelloMessage()); + }); + + this.registerHandler(CloseMessage.class, (closeMessage, channelContext) -> + { + LOGGER.info("Client disconnected: "+channelContext.channel().remoteAddress()); + }); + } + + private void bind() + { + ServerBootstrap bootstrap = new ServerBootstrap() + .group(this.bossGroup, this.workerGroup) + .channel(NioServerSocketChannel.class) + .handler(new LoggingHandler(LogLevel.DEBUG)) + .childHandler(new NetworkChannelInitializer(this.messageHandler)); + + ChannelFuture bindFuture = bootstrap.bind(this.port); + bindFuture.addListener((ChannelFuture channelFuture) -> + { + if (!channelFuture.isSuccess()) + { + throw new RuntimeException("Failed to bind: " + channelFuture.cause()); + } + + LOGGER.info("Server is started on port "+this.port); + }); + + this.channel = bindFuture.channel(); + this.channel.closeFuture().addListener(future -> this.close()); + } + + public void disconnectClient(ChannelHandlerContext ctx, String reason) + { + ctx.channel().config().setAutoRead(false); + ctx.writeAndFlush(new CloseReasonMessage(reason)) + .addListener(ChannelFutureListener.CLOSE); + } + + @Override + public void close() + { + if (this.closeReason != null) + { + LOGGER.error(this.closeReason); + } + + if (this.isClosed) + { + return; + } + this.isClosed = true; + + LOGGER.info("Shutting down the network server."); + this.workerGroup.shutdownGracefully().syncUninterruptibly(); + this.bossGroup.shutdownGracefully().syncUninterruptibly(); + LOGGER.info("Network server has been closed."); + } + } 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 index 7c9ed2154..a846352c7 100644 --- 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 @@ -8,21 +8,19 @@ import io.netty.buffer.ByteBuf; * Simple empty response message. * This message is not sent automatically. */ -public class AckMessage implements INetworkMessage { +public class AckMessage implements INetworkMessage +{ public Class messageType; - + + + public AckMessage() { } - public AckMessage(Class messageType) { - this.messageType = messageType; - } - + public AckMessage(Class messageType) { this.messageType = messageType; } + @Override - public void encode(ByteBuf out) { - out.writeInt(MessageRegistry.INSTANCE.getMessageId(messageType)); - } - + public void encode(ByteBuf out) { out.writeInt(MessageRegistry.INSTANCE.getMessageId(this.messageType)); } + @Override - public void decode(ByteBuf in) { - messageType = MessageRegistry.INSTANCE.getClassById(in.readInt()); - } + public void decode(ByteBuf in) { this.messageType = MessageRegistry.INSTANCE.getMessageClassById(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 8d81cae2a..f42aa3515 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 @@ -4,18 +4,16 @@ 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. + * This is not a "real" message, and only used to indicate a disconnection. * To send a "disconnect reason" message, use {@link CloseReasonMessage}. */ -public class CloseMessage implements INetworkMessage { +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."); - } + public void encode(ByteBuf out) { throw new UnsupportedOperationException("CloseMessage is not a real message, and must not be sent."); } @Override - public void decode(ByteBuf in) { - throw new UnsupportedOperationException("CloseMessage is not a real message, and must not be received."); - } + public void decode(ByteBuf in) { throw new UnsupportedOperationException("CloseMessage is not a real message, and must not be received."); } + } 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 8dcfeb8b9..0671eb3c9 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 @@ -4,21 +4,19 @@ import com.seibel.distanthorizons.core.network.protocol.INetworkMessage; import com.seibel.distanthorizons.core.network.protocol.INetworkObject; import io.netty.buffer.ByteBuf; -public class CloseReasonMessage implements INetworkMessage { - public String reason; - - public CloseReasonMessage() { } - public CloseReasonMessage(String reason) { - this.reason = reason; - } - - @Override - public void encode(ByteBuf out) { - INetworkObject.encodeString(reason, out); - } - - @Override - public void decode(ByteBuf in) { - reason = INetworkObject.decodeString(in); - } +public class CloseReasonMessage implements INetworkMessage +{ + public String reason; + + + + public CloseReasonMessage() { } + public CloseReasonMessage(String reason) { this.reason = reason; } + + @Override + public void encode(ByteBuf out) { INetworkObject.encodeString(this.reason, out); } + + @Override + public void decode(ByteBuf in) { this.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 df4092176..155f97dec 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 @@ -4,16 +4,16 @@ import com.seibel.distanthorizons.core.network.protocol.INetworkMessage; import com.seibel.distanthorizons.coreapi.ModInfo; import io.netty.buffer.ByteBuf; -public class HelloMessage implements INetworkMessage { +public class HelloMessage implements INetworkMessage +{ public int version = ModInfo.PROTOCOL_VERSION; - + + + @Override - public void encode(ByteBuf out) { - out.writeInt(version); - } - + public void encode(ByteBuf out) { out.writeInt(this.version); } + @Override - public void decode(ByteBuf in) { - version = in.readInt(); - } + public void decode(ByteBuf in) { this.version = in.readInt(); } + } 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 deleted file mode 100644 index 43eb092dc..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/LodConfigMessage.java +++ /dev/null @@ -1,25 +0,0 @@ -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; - -public class LodConfigMessage implements INetworkMessage { - public DhRemotePlayer.Config config; - - public LodConfigMessage() { } - public LodConfigMessage(DhRemotePlayer.Config config) { - this.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/PlayerUUIDMessage.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/PlayerUUIDMessage.java index 64ed53c83..09f15fd2c 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/PlayerUUIDMessage.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/PlayerUUIDMessage.java @@ -5,22 +5,23 @@ import io.netty.buffer.ByteBuf; import java.util.UUID; -public class PlayerUUIDMessage implements INetworkMessage { +public class PlayerUUIDMessage implements INetworkMessage +{ public UUID playerUUID; - + + + public PlayerUUIDMessage() { } - public PlayerUUIDMessage(UUID playerUUID) { - this.playerUUID = playerUUID; + public PlayerUUIDMessage(UUID playerUUID) { this.playerUUID = playerUUID; } + + @Override + public void encode(ByteBuf out) + { + out.writeLong(this.playerUUID.getMostSignificantBits()); + out.writeLong(this.playerUUID.getLeastSignificantBits()); } @Override - public void encode(ByteBuf out) { - out.writeLong(playerUUID.getMostSignificantBits()); - out.writeLong(playerUUID.getLeastSignificantBits()); - } - - @Override - public void decode(ByteBuf in) { - playerUUID = new UUID(in.readLong(), in.readLong()); - } + public void decode(ByteBuf in) { this.playerUUID = new UUID(in.readLong(), in.readLong()); } + } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/messages/RemotePlayerConfigMessage.java b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/RemotePlayerConfigMessage.java new file mode 100644 index 000000000..8f0910801 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/messages/RemotePlayerConfigMessage.java @@ -0,0 +1,23 @@ +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.network.objects.RemotePlayer; +import io.netty.buffer.ByteBuf; + +public class RemotePlayerConfigMessage implements INetworkMessage +{ + public RemotePlayer.Payload payload; + + + + public RemotePlayerConfigMessage() { } + public RemotePlayerConfigMessage(RemotePlayer.Payload payload) { this.payload = payload; } + + @Override + public void encode(ByteBuf out) { this.payload.encode(out); } + + @Override + public void decode(ByteBuf in) { this.payload = INetworkObject.decode(new RemotePlayer.Payload(), in); } + +} 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 index 1fbd7f01a..15312601e 100644 --- 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 @@ -3,15 +3,19 @@ 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 { +public class RequestChunksMessage implements INetworkMessage +{ @Override - public void encode(ByteBuf out) { + public void encode(ByteBuf out) + { } @Override - public void decode(ByteBuf in) { + public void decode(ByteBuf in) + { } + } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/objects/RemotePlayer.java b/core/src/main/java/com/seibel/distanthorizons/core/network/objects/RemotePlayer.java new file mode 100644 index 000000000..30f65397d --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/objects/RemotePlayer.java @@ -0,0 +1,34 @@ +package com.seibel.distanthorizons.core.network.objects; + +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 RemotePlayer +{ + public IServerPlayerWrapper serverPlayer; + public Payload payload; + public ChannelHandlerContext channelContext; + + + + public RemotePlayer(IServerPlayerWrapper serverPlayer) { this.serverPlayer = serverPlayer; } + + public static class Payload implements INetworkObject + { + // TODO Replace this example with useful fields, + // this should include any information the server needs to know about the connected client + public int renderDistance; + + + + @Override + public void encode(ByteBuf out) { out.writeInt(this.renderDistance); } + + @Override + public void decode(ByteBuf in) { this.renderDistance = in.readInt(); } + + } + +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageHandlerSide.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/EMessageHandlerSide.java similarity index 53% rename from core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageHandlerSide.java rename to core/src/main/java/com/seibel/distanthorizons/core/network/protocol/EMessageHandlerSide.java index 3e44e1642..6b8f6d760 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageHandlerSide.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/EMessageHandlerSide.java @@ -1,6 +1,11 @@ package com.seibel.distanthorizons.core.network.protocol; -public enum MessageHandlerSide { +/** + * CLIENT,
+ * SERVER,
+ */ +public enum EMessageHandlerSide +{ CLIENT, SERVER } 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 index c24b290df..270baef88 100644 --- 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 @@ -1,6 +1,8 @@ package com.seibel.distanthorizons.core.network.protocol; -public interface INetworkMessage extends INetworkObject { - // For now only used for constraining listeners +/** For now this is only used for constraining listeners */ +public interface INetworkMessage extends INetworkObject +{ + } 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 index 46f7ae01e..087e05b75 100644 --- 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 @@ -4,23 +4,28 @@ 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); - } +public interface INetworkObject +{ + void encode(ByteBuf out); + + void decode(ByteBuf in); + + static T decode(T obj, ByteBuf inputByteBuf) + { + obj.decode(inputByteBuf); + return obj; + } + + static void encodeString(String inputString, ByteBuf outputByteBuf) + { + outputByteBuf.writeShort(inputString.length()); + outputByteBuf.writeBytes(inputString.getBytes(StandardCharsets.UTF_8)); + } + + static String decodeString(ByteBuf inputByteBuf) + { + int length = inputByteBuf.readShort(); + return inputByteBuf.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 e03afe8cb..19eb98722 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,17 +1,18 @@ package com.seibel.distanthorizons.core.network.protocol; -import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; -import org.apache.logging.log4j.Logger; import java.util.List; -public class MessageDecoder extends ByteToMessageDecoder { +public class MessageDecoder extends ByteToMessageDecoder +{ @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { - INetworkMessage message = MessageRegistry.INSTANCE.createMessage(in.readShort()); - out.add(INetworkObject.decode(message, in)); + protected void decode(ChannelHandlerContext channelContext, ByteBuf inputByteBuf, List outputDecodedObjectList) + { + INetworkMessage message = MessageRegistry.INSTANCE.createMessage(inputByteBuf.readShort()); + outputDecodedObjectList.add(INetworkObject.decode(message, inputByteBuf)); } + } 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 7e266eef6..ddb7ed5f3 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,15 +1,16 @@ package com.seibel.distanthorizons.core.network.protocol; -import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; -import org.apache.logging.log4j.Logger; -public class MessageEncoder extends MessageToByteEncoder { - @Override - protected void encode(ChannelHandlerContext ctx, INetworkMessage msg, ByteBuf out) throws IllegalArgumentException { - out.writeShort(MessageRegistry.INSTANCE.getMessageId(msg)); - msg.encode(out); - } +public class MessageEncoder extends MessageToByteEncoder +{ + @Override + protected void encode(ChannelHandlerContext channelContext, INetworkMessage message, ByteBuf outputByteBuf) throws IllegalArgumentException + { + outputByteBuf.writeShort(MessageRegistry.INSTANCE.getMessageId(message)); + message.encode(outputByteBuf); + } + } 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 0ca377314..9fc9626d2 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 @@ -15,32 +15,39 @@ import java.util.Map; import java.util.function.BiConsumer; @ChannelHandler.Sharable -public class MessageHandler extends SimpleChannelInboundHandler { - private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - - private final Map, List>> handlers = new HashMap<>(); - - public void registerHandler(Class clazz, BiConsumer handler) { - handlers.computeIfAbsent(clazz, k -> new LinkedList<>()) - .add((BiConsumer) handler); - } - - @Override - protected void channelRead0(ChannelHandlerContext ctx, INetworkMessage msg) { - LOGGER.trace("Received message: {}", msg.getClass().getSimpleName()); - - List> handlerList = handlers.get(msg.getClass()); - if (handlerList == null) { - LOGGER.warn("Unhandled message type: {}", msg.getClass().getSimpleName()); - return; - } - - for (BiConsumer handler : handlerList) - handler.accept(msg, ctx); - } - - @Override - public void channelInactive(@NotNull ChannelHandlerContext ctx) { - channelRead0(ctx, new CloseMessage()); - } +public class MessageHandler extends SimpleChannelInboundHandler +{ + private static final Logger LOGGER = DhLoggerBuilder.getLogger(); + + private final Map, List>> handlers = new HashMap<>(); + + + + public void registerHandler(Class handlerClass, BiConsumer handlerImplementation) + { + this.handlers.computeIfAbsent(handlerClass, missingHandlerClass -> new LinkedList<>()) + .add((BiConsumer) handlerImplementation); + } + + @Override + protected void channelRead0(ChannelHandlerContext channelContext, INetworkMessage message) + { + LOGGER.trace("Received message: "+message.getClass().getSimpleName()); + + List> handlerList = this.handlers.get(message.getClass()); + if (handlerList == null) + { + LOGGER.warn("Unhandled message type: "+message.getClass().getSimpleName()); + return; + } + + for (BiConsumer handler : handlerList) + { + handler.accept(message, channelContext); + } + } + + @Override + public void channelInactive(@NotNull ChannelHandlerContext channelContext) { this.channelRead0(channelContext, new CloseMessage()); } + } 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 f1ba66cef..ea952c4ad 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 @@ -8,49 +8,55 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; -public class MessageRegistry { - public static final MessageRegistry INSTANCE = new MessageRegistry() {{ - // 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); - }}; - +public class MessageRegistry +{ + public static final MessageRegistry INSTANCE = new MessageRegistry(); + private final Map> idToSupplier = new HashMap<>(); private final BiMap, Integer> classToId = HashBiMap.create(); - - private MessageRegistry() { } - - public void registerMessage(Class clazz, Supplier supplier) { - int id = idToSupplier.size() + 1; - idToSupplier.put(id, supplier); - classToId.put(clazz, id); - } - - 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) { - throw new IllegalArgumentException("Invalid message ID"); - } - } - - public int getMessageId(INetworkMessage message) { - return getMessageId(message.getClass()); - } - - public int getMessageId(Class clazz) { - return classToId.get(clazz); + + + + private MessageRegistry() + { + // Note: Messages must have parameterless constructors + + // Keep messages below intact so client/server can disconnect if version does not match + this.registerMessage(HelloMessage.class, HelloMessage::new); + this.registerMessage(CloseReasonMessage.class, CloseReasonMessage::new); + + // Define your messages after this line + this.registerMessage(AckMessage.class, AckMessage::new); + this.registerMessage(PlayerUUIDMessage.class, PlayerUUIDMessage::new); + this.registerMessage(RemotePlayerConfigMessage.class, RemotePlayerConfigMessage::new); + this.registerMessage(RequestChunksMessage.class, RequestChunksMessage::new); + } + + + + public void registerMessage(Class clazz, Supplier supplier) + { + int id = this.idToSupplier.size() + 1; + this.idToSupplier.put(id, supplier); + this.classToId.put(clazz, id); } + + public Class getMessageClassById(int messageId) { return this.classToId.inverse().get(messageId); } + + public INetworkMessage createMessage(int messageId) throws IllegalArgumentException + { + try + { + return this.idToSupplier.get(messageId).get(); + } + catch (NullPointerException e) + { + throw new IllegalArgumentException("Invalid message ID"); + } + } + + public int getMessageId(INetworkMessage message) { return this.getMessageId(message.getClass()); } + + public int getMessageId(Class messageClass) { return this.classToId.get(messageClass); } + } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/DhNetworkChannelInitializer.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/NetworkChannelInitializer.java similarity index 54% rename from core/src/main/java/com/seibel/distanthorizons/core/network/protocol/DhNetworkChannelInitializer.java rename to core/src/main/java/com/seibel/distanthorizons/core/network/protocol/NetworkChannelInitializer.java index c0283fef5..f021396b6 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/DhNetworkChannelInitializer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/NetworkChannelInitializer.java @@ -1,35 +1,37 @@ package com.seibel.distanthorizons.core.network.protocol; -import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import io.netty.channel.*; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.LengthFieldPrepender; -import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; -public class DhNetworkChannelInitializer extends ChannelInitializer { +/** used when creating a network channel */ +public class NetworkChannelInitializer extends ChannelInitializer +{ private final MessageHandler messageHandler; - - public DhNetworkChannelInitializer(MessageHandler messageHandler) { - this.messageHandler = messageHandler; - } - + + + + public NetworkChannelInitializer(MessageHandler messageHandler) { this.messageHandler = messageHandler; } + @Override - public void initChannel(@NotNull SocketChannel ch) { - ChannelPipeline pipeline = ch.pipeline(); - + public void initChannel(@NotNull SocketChannel socketChannel) + { + ChannelPipeline pipeline = socketChannel.pipeline(); + // Encoder pipeline.addLast(new LengthFieldPrepender(Short.BYTES)); pipeline.addLast(new MessageEncoder()); - pipeline.addLast(new OutboundExceptionRouter()); - + pipeline.addLast(new NetworkOutboundExceptionRouter()); + // Decoder pipeline.addLast(new LengthFieldBasedFrameDecoder(Short.MAX_VALUE, 0, Short.BYTES, 0, Short.BYTES)); pipeline.addLast(new MessageDecoder()); - + // Handler - pipeline.addLast(messageHandler); - pipeline.addLast(new ExceptionHandler()); + pipeline.addLast(this.messageHandler); + pipeline.addLast(new NetworkExceptionHandler()); } + } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/ExceptionHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/NetworkExceptionHandler.java similarity index 55% rename from core/src/main/java/com/seibel/distanthorizons/core/network/protocol/ExceptionHandler.java rename to core/src/main/java/com/seibel/distanthorizons/core/network/protocol/NetworkExceptionHandler.java index d722119b4..365e0e434 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/ExceptionHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/NetworkExceptionHandler.java @@ -5,12 +5,15 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import org.apache.logging.log4j.Logger; -public class ExceptionHandler extends ChannelInboundHandlerAdapter { +public class NetworkExceptionHandler extends ChannelInboundHandlerAdapter +{ private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - + @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { - LOGGER.error("Exception caught on channel", cause); - ctx.close(); - } + public void exceptionCaught(ChannelHandlerContext channelContext, Throwable cause) + { + LOGGER.error("Exception caught in channel: ["+channelContext.name()+"].", cause); + channelContext.close(); + } + } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/NetworkOutboundExceptionRouter.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/NetworkOutboundExceptionRouter.java new file mode 100644 index 000000000..dee8614e8 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/NetworkOutboundExceptionRouter.java @@ -0,0 +1,17 @@ +package com.seibel.distanthorizons.core.network.protocol; + +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOutboundHandlerAdapter; +import io.netty.channel.ChannelPromise; + +public class NetworkOutboundExceptionRouter extends ChannelOutboundHandlerAdapter +{ + @Override + public void write(ChannelHandlerContext channelContext, Object messageObj, ChannelPromise promise) throws Exception + { + promise.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + super.write(channelContext, messageObj, promise); + } + +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/OutboundExceptionRouter.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/OutboundExceptionRouter.java deleted file mode 100644 index e222aeb10..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/OutboundExceptionRouter.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.seibel.distanthorizons.core.network.protocol; - -import io.netty.channel.ChannelFutureListener; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelOutboundHandlerAdapter; -import io.netty.channel.ChannelPromise; - -public class OutboundExceptionRouter extends ChannelOutboundHandlerAdapter { - @Override - public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { - promise.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); - super.write(ctx, msg, promise); - } -} 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 56efb821d..8e29caec1 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,10 +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.*; import com.seibel.distanthorizons.core.network.messages.PlayerUUIDMessage; -import com.seibel.distanthorizons.core.network.messages.RequestChunksMessage; +import com.seibel.distanthorizons.core.network.messages.RemotePlayerConfigMessage; +import com.seibel.distanthorizons.core.network.objects.RemotePlayer; import com.seibel.distanthorizons.core.util.ThreadUtil; import com.seibel.distanthorizons.core.util.objects.EventLoop; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; @@ -54,15 +54,15 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld ctx.writeAndFlush(new PlayerUUIDMessage(MC_CLIENT.getPlayerUUID())); }); - // TODO Proper config handling + // TODO Proper payload handling networkClient.registerAckHandler(PlayerUUIDMessage.class, ctx -> { - ctx.writeAndFlush(new LodConfigMessage(new DhRemotePlayer.Config())); + ctx.writeAndFlush(new RemotePlayerConfigMessage(new RemotePlayer.Payload())); }); - networkClient.registerHandler(LodConfigMessage.class, (msg, ctx) -> { + networkClient.registerHandler(RemotePlayerConfigMessage.class, (msg, ctx) -> { }); - networkClient.registerAckHandler(LodConfigMessage.class, ctx -> { + networkClient.registerAckHandler(RemotePlayerConfigMessage.class, ctx -> { // TODO Actually request chunks ctx.writeAndFlush(new RequestChunksMessage()); }); 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 deleted file mode 100644 index ec74b8aed..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhRemotePlayer.java +++ /dev/null @@ -1,31 +0,0 @@ -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 e76a53c1b..dbbea1b33 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 @@ -8,6 +8,7 @@ import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.network.NetworkServer; import com.seibel.distanthorizons.core.network.messages.*; import com.seibel.distanthorizons.core.network.messages.RequestChunksMessage; +import com.seibel.distanthorizons.core.network.objects.RemotePlayer; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; @@ -23,11 +24,12 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld { private final HashMap levels; public final LocalSaveStructure saveStructure; - + private final NetworkServer networkServer; - private final HashMap playersByUUID; - private final BiMap playersByConnection; - + private final HashMap playersByUUID; + private final BiMap playersByConnection; + + public DhServerWorld() { @@ -35,63 +37,75 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld this.saveStructure = new LocalSaveStructure(); this.levels = new HashMap<>(); - - // TODO move to global config once server specific configs are implemented + + // TODO move to global payload once server specific configs are implemented this.networkServer = new NetworkServer(25049); this.playersByUUID = new HashMap<>(); this.playersByConnection = HashBiMap.create(); - registerNetworkHandlers(); + this.registerNetworkHandlers(); LOGGER.info("Started "+DhServerWorld.class.getSimpleName()+" of type "+this.environment); } - private void registerNetworkHandlers() { - networkServer.registerHandler(CloseMessage.class, (msg, ctx) -> { - DhRemotePlayer dhPlayer = playersByConnection.remove(ctx); + private void registerNetworkHandlers() + { + this.networkServer.registerHandler(CloseMessage.class, (closeMessage, channelContext) -> + { + RemotePlayer dhPlayer = this.playersByConnection.remove(channelContext); if (dhPlayer != null) - dhPlayer.ctx = null; + { + dhPlayer.channelContext = null; + } }); - - networkServer.registerHandler(PlayerUUIDMessage.class, (msg, ctx) -> { - DhRemotePlayer dhPlayer = playersByUUID.get(msg.playerUUID); - - if (dhPlayer == null) { - networkServer.disconnectClient(ctx, "Player is not logged in."); + + this.networkServer.registerHandler(PlayerUUIDMessage.class, (playerUUIDMessage, channelContext) -> + { + RemotePlayer dhPlayer = this.playersByUUID.get(playerUUIDMessage.playerUUID); + + if (dhPlayer == null) + { + this.networkServer.disconnectClient(channelContext, "Player is not logged in."); return; } - - if (dhPlayer.ctx != null) { - networkServer.disconnectClient(ctx, "Another connection is already in use."); + + if (dhPlayer.channelContext != null) + { + this.networkServer.disconnectClient(channelContext, "Another connection is already in use."); return; } - - dhPlayer.ctx = ctx; - playersByConnection.put(ctx, dhPlayer); - - ctx.writeAndFlush(new AckMessage(PlayerUUIDMessage.class)); + + dhPlayer.channelContext = channelContext; + this.playersByConnection.put(channelContext, dhPlayer); + + channelContext.writeAndFlush(new AckMessage(PlayerUUIDMessage.class)); }); - - networkServer.registerHandler(LodConfigMessage.class, (msg, ctx) -> { - // TODO Take notice of received config and possibly echo back a constrained version - ctx.writeAndFlush(new AckMessage(LodConfigMessage.class)); + + this.networkServer.registerHandler(RemotePlayerConfigMessage.class, (dhRemotePlayerConfigMessage, channelContext) -> + { + // TODO Take notice of received payload and possibly echo back a constrained version + channelContext.writeAndFlush(new AckMessage(RemotePlayerConfigMessage.class)); }); - - networkServer.registerHandler(RequestChunksMessage.class, (msg, ctx) -> { + + this.networkServer.registerHandler(RequestChunksMessage.class, (msg, ctx) -> + { LOGGER.info("RequestChunksMessage"); // hasReceivedChunkRequest should be false somewhere ??? // to avoid sending updates until client says at least something about its state }); } - public void addPlayer(IServerPlayerWrapper serverPlayer) { - playersByUUID.put(serverPlayer.getUUID(), new DhRemotePlayer(serverPlayer)); + public void addPlayer(IServerPlayerWrapper serverPlayer) + { + this.playersByUUID.put(serverPlayer.getUUID(), new RemotePlayer(serverPlayer)); } - - public void removePlayer(IServerPlayerWrapper serverPlayer) { - DhRemotePlayer dhPlayer = playersByUUID.remove(serverPlayer.getUUID()); - ChannelHandlerContext ctx = playersByConnection.inverse().remove(dhPlayer); - if (ctx != null) - networkServer.disconnectClient(ctx, "You are being disconnected."); + public void removePlayer(IServerPlayerWrapper serverPlayer) + { + RemotePlayer dhPlayer = this.playersByUUID.remove(serverPlayer.getUUID()); + ChannelHandlerContext channelContext = this.playersByConnection.inverse().remove(dhPlayer); + if (channelContext != null) + { + this.networkServer.disconnectClient(channelContext, "You are being disconnected."); + } } @Override @@ -102,11 +116,11 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld return null; } - return this.levels.computeIfAbsent((IServerLevelWrapper) wrapper, (w) -> + return this.levels.computeIfAbsent((IServerLevelWrapper) wrapper, (serverLevelWrapper) -> { File levelFile = this.saveStructure.getLevelFolder(wrapper); LodUtil.assertTrue(levelFile != null); - return new DhServerLevel(this.saveStructure, w); + return new DhServerLevel(this.saveStructure, serverLevelWrapper); }); } @@ -164,6 +178,4 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld LOGGER.info("Closed DhWorld of type "+this.environment); } - - } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/misc/IServerPlayerWrapper.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/misc/IServerPlayerWrapper.java index 7ac391ecd..329ca7c54 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/misc/IServerPlayerWrapper.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/misc/IServerPlayerWrapper.java @@ -4,6 +4,7 @@ import com.seibel.distanthorizons.api.interfaces.IDhApiUnsafeWrapper; import java.util.UUID; -public interface IServerPlayerWrapper extends IDhApiUnsafeWrapper { +public interface IServerPlayerWrapper extends IDhApiUnsafeWrapper +{ UUID getUUID(); }