Outline what interaction should somewhat look like

(not an actual interaction)
Fix client repeatedly registering handlers on reconnects
This commit is contained in:
s809
2023-07-06 21:57:02 +05:00
parent 748b484377
commit 62ff9606c1
22 changed files with 310 additions and 139 deletions
@@ -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();
}
@@ -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 <T extends Message> void registerHandler(Class<T> clazz, BiConsumer<T, ChannelHandlerContext> handler) {
public <T extends INetworkMessage> void registerHandler(Class<T> clazz, BiConsumer<T, ChannelHandlerContext> handler) {
messageHandler.registerHandler(clazz, handler);
}
public <T extends INetworkMessage> void registerAckHandler(Class<T> clazz, Consumer<ChannelHandlerContext> handler) {
messageHandler.registerHandler(AckMessage.class, (msg, ctx) -> {
if (msg.messageType == clazz)
handler.accept(ctx);
});
}
public void close(String reason) throws Exception {
closeReason = reason;
close();
@@ -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
@@ -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<? extends INetworkMessage> messageType;
public AckMessage() { }
public AckMessage(Class<? extends INetworkMessage> 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());
}
}
@@ -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.");
@@ -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);
}
}
@@ -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
@@ -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);
}
}
@@ -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);
}
}
@@ -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;
}
@@ -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) {
}
}
@@ -0,0 +1,6 @@
package com.seibel.distanthorizons.core.network.protocol;
public interface INetworkMessage extends INetworkObject {
// For now only used for constraining listeners
}
@@ -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 extends INetworkObject> 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);
}
}
@@ -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<Object> 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));
}
}
@@ -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<Message> {
public class MessageEncoder extends MessageToByteEncoder<INetworkMessage> {
@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);
}
@@ -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<Message> {
public class MessageHandler extends SimpleChannelInboundHandler<INetworkMessage> {
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private Map<Class<? extends Message>, List<BiConsumer<Message, ChannelHandlerContext>>> handlers = new HashMap<>();
private final Map<Class<? extends INetworkMessage>, List<BiConsumer<INetworkMessage, ChannelHandlerContext>>> handlers = new HashMap<>();
public <T extends Message> void registerHandler(Class<T> clazz, BiConsumer<T, ChannelHandlerContext> handler) {
public <T extends INetworkMessage> void registerHandler(Class<T> clazz, BiConsumer<T, ChannelHandlerContext> handler) {
handlers.computeIfAbsent(clazz, k -> new LinkedList<>())
.add((BiConsumer<Message, ChannelHandlerContext>) handler);
.add((BiConsumer<INetworkMessage, ChannelHandlerContext>) handler);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Message msg) {
List<BiConsumer<Message, ChannelHandlerContext>> handlerList = handlers.get(msg.getClass());
protected void channelRead0(ChannelHandlerContext ctx, INetworkMessage msg) {
List<BiConsumer<INetworkMessage, ChannelHandlerContext>> handlerList = handlers.get(msg.getClass());
if (handlerList == null) {
LOGGER.warn("Unhandled message type: {}", msg.getClass().getSimpleName());
return;
}
for (BiConsumer<Message, ChannelHandlerContext> handler : handlerList)
for (BiConsumer<INetworkMessage, ChannelHandlerContext> handler : handlerList)
handler.accept(msg, ctx);
}
@@ -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<Integer, Supplier<? extends Message>> idToSupplier = new HashMap<>();
private final Map<Class<? extends Message>, Integer> classToId = new HashMap<>();
private final Map<Integer, Supplier<? extends INetworkMessage>> idToSupplier = new HashMap<>();
private final BiMap<Class<? extends INetworkMessage>, Integer> classToId = HashBiMap.create();
private MessageRegistry() { }
public <T extends Message> void registerMessage(int id, Class<T> clazz, Supplier<T> supplier) {
public <T extends INetworkMessage> void registerMessage(Class<T> clazz, Supplier<T> supplier) {
int id = idToSupplier.size() + 1;
idToSupplier.put(id, supplier);
classToId.put(clazz, id);
}
public Message createMessage(int id) throws IllegalArgumentException {
public Class<? extends INetworkMessage> 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<? extends INetworkMessage> clazz) {
return classToId.get(clazz);
}
}
@@ -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)
@@ -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;
}
}
@@ -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();
}
}
}
@@ -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<UUID, DhPlayer> players;
private final HashMap<ChannelHandlerContext, DhPlayer> connections;
private final HashMap<UUID, DhRemotePlayer> playersByUUID;
private final BiMap<ChannelHandlerContext, DhRemotePlayer> 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
@@ -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. <br>
* Returns null if the client isn't in a level.