Minimal working client-server
No cleanup logic yet
This commit is contained in:
+3
-1
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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<SocketChannel> getInitializer() {
|
||||
return new ChannelInitializer<SocketChannel>() {
|
||||
@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));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
+11
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-7
@@ -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<Object> out) {
|
||||
Message message = messageRegistry.createMessage(in.readShort());
|
||||
Message message = MessageRegistry.INSTANCE.createMessage(in.readShort());
|
||||
message.decode(ctx, in);
|
||||
out.add(message);
|
||||
}
|
||||
|
||||
+1
-7
@@ -6,15 +6,9 @@ import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
|
||||
public class MessageEncoder extends MessageToByteEncoder<Message> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
+12
-9
@@ -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<Integer, Supplier<Message>> idToConstructor = new HashMap<Integer, Supplier<Message>>() {{
|
||||
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<Class<?>, Integer> classToId = idToConstructor.entrySet().stream()
|
||||
.collect(Collectors.toMap(
|
||||
e -> e.getValue().getClass(),
|
||||
Map.Entry::getKey
|
||||
));
|
||||
private final Map<Integer, Supplier<Message>> idToSupplier = new HashMap<>();
|
||||
private final Map<Class<?>, Integer> classToId = new HashMap<>();
|
||||
|
||||
private MessageRegistry() { }
|
||||
|
||||
public <T extends Message> void registerMessage(int id, Class<T> clazz, Supplier<T> supplier) {
|
||||
idToSupplier.put(id, (Supplier<Message>) 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");
|
||||
}
|
||||
|
||||
@@ -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<SocketChannel> getChannelInitializer(MessageHandlerSide side) {
|
||||
return new ChannelInitializer<SocketChannel>() {
|
||||
@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));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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<IClientLevelWrapper, DhClientLevel> 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<IServerLevelWrapper, DhServerLevel> 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user