diff --git a/libs.gradle b/libs.gradle index 691322b..402450a 100644 --- a/libs.gradle +++ b/libs.gradle @@ -19,7 +19,8 @@ ext { yaml : 'org.yaml:snakeyaml:1.28', json : 'com.eclipsesource.minimal-json:minimal-json:0.9.5', ioutils : 'commons-io:commons-io:2.6', - jopt : 'net.sf.jopt-simple:jopt-simple:6.0-alpha-3' + jopt : 'net.sf.jopt-simple:jopt-simple:6.0-alpha-3', + objpool : 'org.apache.commons:commons-pool2:2.9.0' ] libs.logger = [ diff --git a/protocol/build.gradle b/protocol/build.gradle index 03ac861..0e4b3c6 100644 --- a/protocol/build.gradle +++ b/protocol/build.gradle @@ -1,9 +1,9 @@ apply from: rootDir.toPath().resolve('logic.gradle').toFile() dependencies { - api libs.netty - api libs.reactor + implementation libs.netty implementation libs.json + implementation libs.objpool testImplementation libs.lang3 } diff --git a/protocol/src/main/java/mc/protocol/ChannelContext.java b/protocol/src/main/java/mc/protocol/ChannelContext.java deleted file mode 100644 index 51d7a89..0000000 --- a/protocol/src/main/java/mc/protocol/ChannelContext.java +++ /dev/null @@ -1,20 +0,0 @@ -package mc.protocol; - -import io.netty.channel.ChannelHandlerContext; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import mc.protocol.packets.ClientSidePacket; - -@RequiredArgsConstructor -public class ChannelContext

{ - - @Getter - private final ChannelHandlerContext ctx; - - @Getter - private final P packet; - - public void setState(State state) { - ctx.channel().attr(NetworkAttributes.STATE).set(state); - } -} diff --git a/protocol/src/main/java/mc/protocol/NettyConnectionContext.java b/protocol/src/main/java/mc/protocol/NettyConnectionContext.java new file mode 100644 index 0000000..23fe235 --- /dev/null +++ b/protocol/src/main/java/mc/protocol/NettyConnectionContext.java @@ -0,0 +1,52 @@ +package mc.protocol; + +import io.netty.channel.ChannelHandlerContext; +import lombok.EqualsAndHashCode; +import lombok.Setter; +import lombok.experimental.Accessors; +import mc.protocol.api.ConnectionContext; +import mc.protocol.packets.ServerSidePacket; +import mc.protocol.pool.Passivable; + +@EqualsAndHashCode +public class NettyConnectionContext implements ConnectionContext, Passivable { + + @Accessors(chain = true) + @Setter + private ChannelHandlerContext ctx; + + @Override + public State getState() { + return ctx.channel().attr(NetworkAttributes.STATE).get(); + } + + @Override + public void setState(State state) { + ctx.channel().attr(NetworkAttributes.STATE).set(state); + } + + @Override + public void send(ServerSidePacket packet) { + ctx.write(packet); + } + + @Override + public void sendNow(ServerSidePacket packet) { + ctx.writeAndFlush(packet); + } + + @Override + public void flushSending() { + ctx.flush(); + } + + @Override + public void disconnect() { + ctx.disconnect(); + } + + @Override + public void passivate() { + this.ctx = null; + } +} diff --git a/protocol/src/main/java/mc/protocol/NettyServer.java b/protocol/src/main/java/mc/protocol/NettyServer.java index 2c92645..70ff2cb 100644 --- a/protocol/src/main/java/mc/protocol/NettyServer.java +++ b/protocol/src/main/java/mc/protocol/NettyServer.java @@ -1,22 +1,46 @@ package mc.protocol; import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import mc.protocol.di.DaggerProtocolComponent; -import mc.protocol.di.ProtocolComponent; +import mc.protocol.api.ConnectionContext; +import mc.protocol.api.Server; +import mc.protocol.event.EventBus; +import mc.protocol.io.codec.ProtocolDecoder; +import mc.protocol.io.codec.ProtocolEncoder; +import mc.protocol.io.codec.ProtocolSplitter; +import mc.protocol.packets.ClientSidePacket; + +import javax.annotation.Nonnull; +import javax.inject.Provider; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Consumer; @Slf4j @RequiredArgsConstructor -public class NettyServer { +public class NettyServer implements Server { - private final ServerBootstrap serverBootstrap; + private final Provider protocolDecoderProvider; + private final Provider packetInboundHandlerProvider; + private final EventBus eventBus; + private Consumer consumerNewConnection; + private Consumer consumerDisconnect; + @Override public void bind(String host, int port) { log.info("Network starting: {}:{}", host, port); try { - serverBootstrap.bind(host, port).sync().channel().closeFuture().sync(); + createServerBootstrap().bind(host, port).sync().channel().closeFuture().sync(); } catch (InterruptedException e) { if (log.isTraceEnabled()) { log.trace("{}: {}", e.getClass().getSimpleName(), e.getMessage(), e); @@ -24,8 +48,56 @@ public class NettyServer { } } - public static NettyServer createServer() { - ProtocolComponent component = DaggerProtocolComponent.create(); - return component.getNettyServer(); + @Override + public void onNewConnect(Consumer consumer) { + this.consumerNewConnection = consumer; + } + + @Override + public void onDisonnect(Consumer consumer) { + this.consumerDisconnect = consumer; + } + + @Override + @SuppressWarnings("java:S2326") // Сонар, ты бредишь + public

void listenPacket(State state, Class

packetClass, EventBus.EventHandler

eventHandler) { + this.eventBus.subscribe(state, packetClass, eventHandler); + } + + private ServerBootstrap createServerBootstrap() { + ServerBootstrap bootstrap = new ServerBootstrap(); + + bootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup()) + .channel(NioServerSocketChannel.class) + .childHandler(createChannelChannelInitializer()); + + return bootstrap; + } + + private ChannelInitializer createChannelChannelInitializer() { + return new ChannelInitializer<>() { + @Override + protected void initChannel(@Nonnull SocketChannel socketChannel) { + ChannelPipeline pipeline = socketChannel.pipeline(); + createChannelHandlerMap().forEach(pipeline::addLast); + } + }; + } + + private Map createChannelHandlerMap() { + Map map = new LinkedHashMap<>(); + + map.put("packet_splitter", new ProtocolSplitter()); + map.put("logger", new LoggingHandler(LogLevel.DEBUG)); + + ProtocolDecoder protocolDecoder = protocolDecoderProvider.get(); + protocolDecoder.setConsumerNewConnection(consumerNewConnection); + protocolDecoder.setConsumerDisconnect(consumerDisconnect); + map.put("packet_decoder", protocolDecoder); + + map.put("packet_encoder", new ProtocolEncoder()); + map.put("packet_handler", packetInboundHandlerProvider.get()); + + return map; } } diff --git a/protocol/src/main/java/mc/protocol/PacketInboundHandler.java b/protocol/src/main/java/mc/protocol/PacketInboundHandler.java index a99fb4d..81a3e0d 100644 --- a/protocol/src/main/java/mc/protocol/PacketInboundHandler.java +++ b/protocol/src/main/java/mc/protocol/PacketInboundHandler.java @@ -4,19 +4,25 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import lombok.RequiredArgsConstructor; import mc.protocol.packets.ClientSidePacket; -import reactor.core.publisher.Sinks; +import mc.protocol.event.EventBus; +import mc.protocol.pool.PacketPool; +import org.apache.commons.pool2.ObjectPool; @RequiredArgsConstructor public class PacketInboundHandler extends SimpleChannelInboundHandler { - @SuppressWarnings("rawtypes") - @Override - protected void channelRead0(ChannelHandlerContext ctx, ClientSidePacket packet) { - Sinks.Many packetSinks = ctx.channel().attr(NetworkAttributes.STATE) - .get().getPacketSinks(packet.getClass()); + private final ObjectPool poolNettyConnectionContext; + private final PacketPool poolPackets; + private final EventBus eventBus; - if (packetSinks != null) { - packetSinks.tryEmitNext(new ChannelContext<>(ctx, packet)); - } + @Override + protected void channelRead0(ChannelHandlerContext ctx, ClientSidePacket packet) throws Exception { + State state = ctx.channel().attr(NetworkAttributes.STATE).get(); + + NettyConnectionContext context = poolNettyConnectionContext.borrowObject().setCtx(ctx); + eventBus.emit(state, context, packet); + + poolNettyConnectionContext.returnObject(context); + poolPackets.returnObject(packet); } } diff --git a/protocol/src/main/java/mc/protocol/State.java b/protocol/src/main/java/mc/protocol/State.java index 180f4d7..6159b43 100644 --- a/protocol/src/main/java/mc/protocol/State.java +++ b/protocol/src/main/java/mc/protocol/State.java @@ -8,12 +8,9 @@ import mc.protocol.packets.PingPacket; import mc.protocol.packets.ServerSidePacket; import mc.protocol.packets.client.*; import mc.protocol.packets.server.*; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Sinks; import javax.annotation.Nullable; import java.util.Collections; -import java.util.HashMap; import java.util.Map; @RequiredArgsConstructor @@ -80,12 +77,10 @@ public enum State { @Getter private final int id; + @Getter private final Map> clientSidePackets; private final Map, Integer> serverSidePackets; - @SuppressWarnings("rawtypes") - private final Map, Sinks.Many> observedMap = new HashMap<>(); - State(int id, Map> clientSidePackets) { this.id = id; this.clientSidePackets = clientSidePackets; @@ -101,16 +96,4 @@ public enum State { public Integer getServerSidePacketId(Class clazz) { return serverSidePackets == null ? null : serverSidePackets.get(clazz); } - - - @SuppressWarnings("rawtypes") - public

Sinks.Many getPacketSinks(Class

packetClass) { - return observedMap.get(packetClass); - } - - @SuppressWarnings("unchecked") - public

Flux> packetFlux(Class

packetClass) { - return observedMap.computeIfAbsent(packetClass, aClass -> Sinks.many().multicast().directBestEffort()) - .asFlux().map(ChannelContext.class::cast); - } } diff --git a/protocol/src/main/java/mc/protocol/api/ConnectionContext.java b/protocol/src/main/java/mc/protocol/api/ConnectionContext.java new file mode 100644 index 0000000..5cf5c3d --- /dev/null +++ b/protocol/src/main/java/mc/protocol/api/ConnectionContext.java @@ -0,0 +1,16 @@ +package mc.protocol.api; + +import mc.protocol.State; +import mc.protocol.packets.ServerSidePacket; + +public interface ConnectionContext { + + State getState(); + void setState(State state); + + void send(ServerSidePacket packet); + void sendNow(ServerSidePacket packet); + void flushSending(); + + void disconnect(); +} diff --git a/protocol/src/main/java/mc/protocol/api/Server.java b/protocol/src/main/java/mc/protocol/api/Server.java new file mode 100644 index 0000000..93b213f --- /dev/null +++ b/protocol/src/main/java/mc/protocol/api/Server.java @@ -0,0 +1,18 @@ +package mc.protocol.api; + +import mc.protocol.State; +import mc.protocol.packets.ClientSidePacket; +import mc.protocol.event.EventBus; + +import java.util.function.Consumer; + +public interface Server { + + void bind(String host, int port); + + void onNewConnect(Consumer consumer); + void onDisonnect(Consumer consumer); + + @SuppressWarnings("java:S2326") // Сонар, ты бредишь +

void listenPacket(State state, Class

packetClass, EventBus.EventHandler

eventHandler); +} diff --git a/protocol/src/main/java/mc/protocol/di/PoolModule.java b/protocol/src/main/java/mc/protocol/di/PoolModule.java new file mode 100644 index 0000000..756c6c2 --- /dev/null +++ b/protocol/src/main/java/mc/protocol/di/PoolModule.java @@ -0,0 +1,42 @@ +package mc.protocol.di; + +import dagger.Module; +import dagger.Provides; +import mc.protocol.NettyConnectionContext; +import mc.protocol.State; +import mc.protocol.packets.ClientSidePacket; +import mc.protocol.packets.UnknownPacket; +import mc.protocol.pool.NettyConnectionContextFactory; +import mc.protocol.pool.PacketFactory; +import mc.protocol.pool.PacketPool; +import org.apache.commons.pool2.ObjectPool; +import org.apache.commons.pool2.impl.GenericObjectPool; + +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Module +public class PoolModule { + + @Provides + @ServerScope + @SuppressWarnings({ "rawtypes", "unchecked" }) + PacketPool providePacketPool() { + Map, ObjectPool> map = Stream.of(State.values()) + .flatMap(state -> state.getClientSidePackets().values().stream()) + .distinct() + .collect(Collectors.toMap( + packetClass -> packetClass, + packetClass -> new GenericObjectPool(new PacketFactory<>(packetClass)))); + map.put(UnknownPacket.class, new GenericObjectPool(new PacketFactory<>(UnknownPacket.class))); + + return new PacketPool(map); + } + + @Provides + @ServerScope + ObjectPool providePoolNettyConnectionContext() { + return new GenericObjectPool<>(new NettyConnectionContextFactory()); + } +} diff --git a/protocol/src/main/java/mc/protocol/di/ProtocolComponent.java b/protocol/src/main/java/mc/protocol/di/ProtocolComponent.java index a433211..da7725e 100644 --- a/protocol/src/main/java/mc/protocol/di/ProtocolComponent.java +++ b/protocol/src/main/java/mc/protocol/di/ProtocolComponent.java @@ -1,11 +1,14 @@ package mc.protocol.di; import dagger.Component; -import mc.protocol.NettyServer; +import mc.protocol.api.Server; -@Component(modules = ProtocolModule.class) +@Component(modules = { + ProtocolModule.class, + PoolModule.class +}) @ServerScope public interface ProtocolComponent { - NettyServer getNettyServer(); + Server getServer(); } diff --git a/protocol/src/main/java/mc/protocol/di/ProtocolModule.java b/protocol/src/main/java/mc/protocol/di/ProtocolModule.java index 3e8a8df..26de985 100644 --- a/protocol/src/main/java/mc/protocol/di/ProtocolModule.java +++ b/protocol/src/main/java/mc/protocol/di/ProtocolModule.java @@ -2,68 +2,58 @@ package mc.protocol.di; import dagger.Module; import dagger.Provides; -import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelPipeline; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.SocketChannel; -import io.netty.channel.socket.nio.NioServerSocketChannel; -import io.netty.handler.logging.LogLevel; -import io.netty.handler.logging.LoggingHandler; +import lombok.RequiredArgsConstructor; +import mc.protocol.NettyConnectionContext; import mc.protocol.NettyServer; import mc.protocol.PacketInboundHandler; +import mc.protocol.api.Server; +import mc.protocol.event.EventBus; +import mc.protocol.event.SimpleEventBus; import mc.protocol.io.codec.ProtocolDecoder; -import mc.protocol.io.codec.ProtocolEncoder; -import mc.protocol.io.codec.ProtocolSplitter; +import mc.protocol.pool.PacketPool; +import org.apache.commons.pool2.ObjectPool; -import javax.annotation.Nonnull; import javax.inject.Provider; -import java.util.LinkedHashMap; -import java.util.Map; @Module +@RequiredArgsConstructor public class ProtocolModule { + private final boolean readUnknownPackets; + @Provides - NettyServer provideServer(ServerBootstrap serverBootstrap) { - return new NettyServer(serverBootstrap); + @ServerScope + Server provideServer( + Provider protocolDecoderProvider, + Provider packetInboundHandlerProvider, + EventBus eventBus + ) { + return new NettyServer(protocolDecoderProvider, packetInboundHandlerProvider, eventBus); } @Provides - ServerBootstrap provideServerBootstrap(ChannelInitializer channelChannelInitializer) { - ServerBootstrap bootstrap = new ServerBootstrap(); - - bootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup()) - .channel(NioServerSocketChannel.class) - .childHandler(channelChannelInitializer); - - return bootstrap; + @ServerScope + ProtocolDecoder provideProtocolDecoder( + ObjectPool poolNettyConnectionContext, + PacketPool poolPackets + ) { + return new ProtocolDecoder(readUnknownPackets, poolNettyConnectionContext, poolPackets); } @Provides - ChannelInitializer provideChannelChannelInitializer( - Provider> channelHandlerMapProvider) { - - return new ChannelInitializer<>() { - @Override - protected void initChannel(@Nonnull SocketChannel socketChannel) { - ChannelPipeline pipeline = socketChannel.pipeline(); - channelHandlerMapProvider.get().forEach(pipeline::addLast); - } - }; + @ServerScope + PacketInboundHandler providePacketInboundHandler( + ObjectPool poolNettyConnectionContext, + PacketPool packetPool, + EventBus eventBus + ) { + return new PacketInboundHandler(poolNettyConnectionContext, packetPool, eventBus); } @Provides - Map provideChannelHandlerMap() { - Map map = new LinkedHashMap<>(); - - map.put("packet_splitter", new ProtocolSplitter()); - map.put("logger", new LoggingHandler(LogLevel.DEBUG)); - map.put("packet_decoder", new ProtocolDecoder(true)); - map.put("packet_encoder", new ProtocolEncoder()); - map.put("packet_handler", new PacketInboundHandler()); - - return map; + @ServerScope + EventBus provideEventBus() { + return new SimpleEventBus(); } + } diff --git a/protocol/src/main/java/mc/protocol/event/EventBus.java b/protocol/src/main/java/mc/protocol/event/EventBus.java new file mode 100644 index 0000000..e9d6e66 --- /dev/null +++ b/protocol/src/main/java/mc/protocol/event/EventBus.java @@ -0,0 +1,17 @@ +package mc.protocol.event; + +import mc.protocol.State; +import mc.protocol.api.ConnectionContext; +import mc.protocol.packets.ClientSidePacket; + +public interface EventBus { + +

void subscribe(State state, Class

packetClass, EventHandler

eventHandler); + +

void emit(State state, ConnectionContext channelContext, P packet); + + @FunctionalInterface + interface EventHandler

{ + void handle(ConnectionContext channelContext, P packet); + } +} diff --git a/protocol/src/main/java/mc/protocol/event/SimpleEventBus.java b/protocol/src/main/java/mc/protocol/event/SimpleEventBus.java new file mode 100644 index 0000000..1c0c576 --- /dev/null +++ b/protocol/src/main/java/mc/protocol/event/SimpleEventBus.java @@ -0,0 +1,26 @@ +package mc.protocol.event; + +import mc.protocol.State; +import mc.protocol.api.ConnectionContext; +import mc.protocol.packets.ClientSidePacket; +import mc.protocol.utils.Table; + +@SuppressWarnings({ "rawtypes", "unchecked" }) +public class SimpleEventBus implements EventBus { + + private final Table, EventHandler> table = new Table<>(); + + @Override + public

void subscribe(State state, Class

packetClass, EventHandler

eventHandler) { + table.put(state, packetClass, eventHandler); + } + + @Override + public

void emit(State state, ConnectionContext channelContext, P packet) { + EventHandler eventHandler = table.getColumnAndRow(state, packet.getClass()); + + if (eventHandler != null) { + eventHandler.handle(channelContext, packet); + } + } +} diff --git a/protocol/src/main/java/mc/protocol/io/codec/ProtocolDecoder.java b/protocol/src/main/java/mc/protocol/io/codec/ProtocolDecoder.java index b7ee9d5..1e2e6ab 100644 --- a/protocol/src/main/java/mc/protocol/io/codec/ProtocolDecoder.java +++ b/protocol/src/main/java/mc/protocol/io/codec/ProtocolDecoder.java @@ -4,31 +4,51 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; import lombok.RequiredArgsConstructor; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import mc.protocol.NettyConnectionContext; import mc.protocol.NetworkAttributes; import mc.protocol.State; +import mc.protocol.api.ConnectionContext; import mc.protocol.io.NetByteBuf; import mc.protocol.packets.ClientSidePacket; import mc.protocol.packets.UnknownPacket; +import mc.protocol.pool.PacketPool; +import org.apache.commons.pool2.ObjectPool; +import javax.annotation.Nonnull; import java.util.List; import java.util.Objects; +import java.util.function.Consumer; -@Slf4j @RequiredArgsConstructor +@Slf4j public class ProtocolDecoder extends ByteToMessageDecoder { private final boolean readUnknownPackets; + private final ObjectPool poolNettyConnectionContext; + private final PacketPool poolPackets; + + @Setter + private Consumer consumerNewConnection; + @Setter + private Consumer consumerDisconnect; @Override - public void channelActive(ChannelHandlerContext ctx) throws Exception { - ctx.channel().attr(NetworkAttributes.STATE).set(State.HANDSHAKING); + public void channelActive(@Nonnull ChannelHandlerContext ctx) throws Exception { + NettyConnectionContext context = poolNettyConnectionContext.borrowObject().setCtx(ctx); + consumerNewConnection.accept(context); + + poolNettyConnectionContext.returnObject(context); super.channelActive(ctx); } @Override - public void channelInactive(ChannelHandlerContext ctx) throws Exception { - ctx.channel().attr(NetworkAttributes.STATE).set(null); + public void channelInactive(@Nonnull ChannelHandlerContext ctx) throws Exception { + NettyConnectionContext context = poolNettyConnectionContext.borrowObject().setCtx(ctx); + consumerDisconnect.accept(context); + + poolNettyConnectionContext.returnObject(context); super.channelInactive(ctx); } @@ -43,14 +63,17 @@ public class ProtocolDecoder extends ByteToMessageDecoder { log.warn("Unknown packet: State {} ; Id 0x{}", state, packetIdAsHexcode(packetId)); if (readUnknownPackets) { - UnknownPacket unknownPacket = new UnknownPacket(state, packetId, netByteBuf.readableBytes()); + UnknownPacket unknownPacket = poolPackets.borrowObject(UnknownPacket.class); + unknownPacket.setState(state); + unknownPacket.setId(packetId); + unknownPacket.setDataSize(netByteBuf.readableBytes()); unknownPacket.readSelf(netByteBuf); out.add(unknownPacket); } else { netByteBuf.skipBytes(netByteBuf.readableBytes()); } } else { - ClientSidePacket packet = packetClass.getDeclaredConstructor().newInstance(); + ClientSidePacket packet = poolPackets.borrowObject(packetClass); packet.readSelf(netByteBuf); log.debug("IN: {}:{}", state, packet); out.add(packet); diff --git a/protocol/src/main/java/mc/protocol/packets/ClientSidePacket.java b/protocol/src/main/java/mc/protocol/packets/ClientSidePacket.java index 6629750..1a10cd9 100644 --- a/protocol/src/main/java/mc/protocol/packets/ClientSidePacket.java +++ b/protocol/src/main/java/mc/protocol/packets/ClientSidePacket.java @@ -1,11 +1,12 @@ package mc.protocol.packets; import mc.protocol.io.NetByteBuf; +import mc.protocol.pool.Passivable; /** * Пакеты отправляемые клиентом. */ -public interface ClientSidePacket extends Packet { +public interface ClientSidePacket extends Packet, Passivable { void readSelf(NetByteBuf netByteBuf); } diff --git a/protocol/src/main/java/mc/protocol/packets/EmptyPacket.java b/protocol/src/main/java/mc/protocol/packets/EmptyPacket.java index 60c800f..0e3a740 100644 --- a/protocol/src/main/java/mc/protocol/packets/EmptyPacket.java +++ b/protocol/src/main/java/mc/protocol/packets/EmptyPacket.java @@ -13,4 +13,9 @@ public abstract class EmptyPacket implements ClientSidePacket, ServerSidePacket public void writeSelf(NetByteBuf netByteBuf) { // empty } + + @Override + public void passivate() { + // pass + } } diff --git a/protocol/src/main/java/mc/protocol/packets/PingPacket.java b/protocol/src/main/java/mc/protocol/packets/PingPacket.java index f40b121..3b1ee48 100644 --- a/protocol/src/main/java/mc/protocol/packets/PingPacket.java +++ b/protocol/src/main/java/mc/protocol/packets/PingPacket.java @@ -34,6 +34,11 @@ public class PingPacket implements ClientSidePacket, ServerSidePacket { payload = netByteBuf.readLong(); } + @Override + public void passivate() { + this.payload = null; + } + @Override public void writeSelf(NetByteBuf netByteBuf) { netByteBuf.writeLong(payload); diff --git a/protocol/src/main/java/mc/protocol/packets/UnknownPacket.java b/protocol/src/main/java/mc/protocol/packets/UnknownPacket.java index aef7b74..6722e5f 100644 --- a/protocol/src/main/java/mc/protocol/packets/UnknownPacket.java +++ b/protocol/src/main/java/mc/protocol/packets/UnknownPacket.java @@ -1,17 +1,19 @@ package mc.protocol.packets; import lombok.Data; +import lombok.NoArgsConstructor; import lombok.ToString; import mc.protocol.State; import mc.protocol.io.NetByteBuf; +@NoArgsConstructor @Data @ToString(exclude = "rawData") public class UnknownPacket implements ClientSidePacket { - private final State state; - private final int id; - private final int dataSize; + private State state; + private int id; + private int dataSize; private byte[] rawData; @Override @@ -19,4 +21,12 @@ public class UnknownPacket implements ClientSidePacket { rawData = new byte[dataSize]; netByteBuf.readBytes(rawData); } + + @Override + public void passivate() { + this.state = null; + this.id = 0; + this.dataSize = 0; + this.rawData = null; + } } diff --git a/protocol/src/main/java/mc/protocol/packets/client/CPlayerPositionAndLookPacket.java b/protocol/src/main/java/mc/protocol/packets/client/CPlayerPositionAndLookPacket.java index ed5c002..d7be871 100644 --- a/protocol/src/main/java/mc/protocol/packets/client/CPlayerPositionAndLookPacket.java +++ b/protocol/src/main/java/mc/protocol/packets/client/CPlayerPositionAndLookPacket.java @@ -51,6 +51,13 @@ public class CPlayerPositionAndLookPacket implements ClientSidePacket { this.onGround = netByteBuf.readBoolean(); } + @Override + public void passivate() { + this.position = null; + this.look = null; + this.onGround = false; + } + public double getYPositionHead() { return this.position.getY() + 1.62f; } diff --git a/protocol/src/main/java/mc/protocol/packets/client/ClientSettingsPacket.java b/protocol/src/main/java/mc/protocol/packets/client/ClientSettingsPacket.java index 680db2c..b22914d 100644 --- a/protocol/src/main/java/mc/protocol/packets/client/ClientSettingsPacket.java +++ b/protocol/src/main/java/mc/protocol/packets/client/ClientSettingsPacket.java @@ -1,8 +1,8 @@ package mc.protocol.packets.client; import lombok.*; -import mc.protocol.ChatMode; -import mc.protocol.MainHand; +import mc.protocol.utils.ChatMode; +import mc.protocol.utils.MainHand; import mc.protocol.io.NetByteBuf; import mc.protocol.packets.ClientSidePacket; @@ -64,6 +64,16 @@ public class ClientSettingsPacket implements ClientSidePacket { this.mainHand = MainHand.valueById(netByteBuf.readVarInt()); } + @Override + public void passivate() { + this.locale = null; + this.viewDistance = 0; + this.chatMode = null; + this.chatColors = false; + this.$displayedSkinPartsBitMask = 0; + this.mainHand = null; + } + public boolean isCapeEnabled() { return ($displayedSkinPartsBitMask & 0x01) > 0; } diff --git a/protocol/src/main/java/mc/protocol/packets/client/HandshakePacket.java b/protocol/src/main/java/mc/protocol/packets/client/HandshakePacket.java index ccb86b5..005c6b9 100644 --- a/protocol/src/main/java/mc/protocol/packets/client/HandshakePacket.java +++ b/protocol/src/main/java/mc/protocol/packets/client/HandshakePacket.java @@ -47,4 +47,12 @@ public class HandshakePacket implements ClientSidePacket { nextState = State.getById(netByteBuf.readVarInt()); } + @Override + public void passivate() { + this.protocolVersion = 0; + this.host = null; + this.port = 0; + this.nextState = null; + } + } diff --git a/protocol/src/main/java/mc/protocol/packets/client/LoginStartPacket.java b/protocol/src/main/java/mc/protocol/packets/client/LoginStartPacket.java index 77775b6..0462715 100644 --- a/protocol/src/main/java/mc/protocol/packets/client/LoginStartPacket.java +++ b/protocol/src/main/java/mc/protocol/packets/client/LoginStartPacket.java @@ -34,4 +34,9 @@ public class LoginStartPacket implements ClientSidePacket { this.name = netByteBuf.readString(); } + @Override + public void passivate() { + this.name = null; + } + } diff --git a/protocol/src/main/java/mc/protocol/packets/client/PlayerLookPacket.java b/protocol/src/main/java/mc/protocol/packets/client/PlayerLookPacket.java index 0f6ec16..35568fa 100644 --- a/protocol/src/main/java/mc/protocol/packets/client/PlayerLookPacket.java +++ b/protocol/src/main/java/mc/protocol/packets/client/PlayerLookPacket.java @@ -39,4 +39,10 @@ public class PlayerLookPacket implements ClientSidePacket { this.onGround = netByteBuf.readBoolean(); } + + @Override + public void passivate() { + this.look = null; + this.onGround = false; + } } diff --git a/protocol/src/main/java/mc/protocol/packets/client/PlayerPositionPacket.java b/protocol/src/main/java/mc/protocol/packets/client/PlayerPositionPacket.java index abbc609..3be5633 100644 --- a/protocol/src/main/java/mc/protocol/packets/client/PlayerPositionPacket.java +++ b/protocol/src/main/java/mc/protocol/packets/client/PlayerPositionPacket.java @@ -43,6 +43,12 @@ public class PlayerPositionPacket implements ClientSidePacket { this.onGround = netByteBuf.readBoolean(); } + @Override + public void passivate() { + this.position = null; + this.onGround = false; + } + public double getYPositionHead() { return this.position.getY() + 1.62f; } diff --git a/protocol/src/main/java/mc/protocol/packets/client/PluginMessagePacket.java b/protocol/src/main/java/mc/protocol/packets/client/PluginMessagePacket.java index 622bd4e..2ae5ab0 100644 --- a/protocol/src/main/java/mc/protocol/packets/client/PluginMessagePacket.java +++ b/protocol/src/main/java/mc/protocol/packets/client/PluginMessagePacket.java @@ -39,4 +39,10 @@ public class PluginMessagePacket implements ClientSidePacket { this.rawData = new byte[netByteBuf.readableBytes()]; netByteBuf.readBytes(this.rawData); } + + @Override + public void passivate() { + this.channelName = null; + this.rawData = null; + } } diff --git a/protocol/src/main/java/mc/protocol/packets/client/TeleportConfirmPacket.java b/protocol/src/main/java/mc/protocol/packets/client/TeleportConfirmPacket.java index eee3df4..fd120e9 100644 --- a/protocol/src/main/java/mc/protocol/packets/client/TeleportConfirmPacket.java +++ b/protocol/src/main/java/mc/protocol/packets/client/TeleportConfirmPacket.java @@ -33,4 +33,9 @@ public class TeleportConfirmPacket implements ClientSidePacket { public void readSelf(NetByteBuf netByteBuf) { this.teleportId = netByteBuf.readVarInt(); } + + @Override + public void passivate() { + this.teleportId = 0; + } } diff --git a/protocol/src/main/java/mc/protocol/packets/server/JoinGamePacket.java b/protocol/src/main/java/mc/protocol/packets/server/JoinGamePacket.java index 936bc82..a427359 100644 --- a/protocol/src/main/java/mc/protocol/packets/server/JoinGamePacket.java +++ b/protocol/src/main/java/mc/protocol/packets/server/JoinGamePacket.java @@ -1,9 +1,9 @@ package mc.protocol.packets.server; import lombok.Data; -import mc.protocol.Difficulty; -import mc.protocol.GameMode; -import mc.protocol.LevelType; +import mc.protocol.utils.Difficulty; +import mc.protocol.utils.GameMode; +import mc.protocol.utils.LevelType; import mc.protocol.io.NetByteBuf; import mc.protocol.packets.ServerSidePacket; diff --git a/protocol/src/main/java/mc/protocol/pool/NettyConnectionContextFactory.java b/protocol/src/main/java/mc/protocol/pool/NettyConnectionContextFactory.java new file mode 100644 index 0000000..df211e0 --- /dev/null +++ b/protocol/src/main/java/mc/protocol/pool/NettyConnectionContextFactory.java @@ -0,0 +1,24 @@ +package mc.protocol.pool; + +import mc.protocol.NettyConnectionContext; +import org.apache.commons.pool2.BasePooledObjectFactory; +import org.apache.commons.pool2.PooledObject; +import org.apache.commons.pool2.impl.DefaultPooledObject; + +public class NettyConnectionContextFactory extends BasePooledObjectFactory { + + @Override + public NettyConnectionContext create() throws Exception { + return new NettyConnectionContext(); + } + + @Override + public PooledObject wrap(NettyConnectionContext context) { + return new DefaultPooledObject<>(context); + } + + @Override + public void passivateObject(PooledObject pooledObj) { + pooledObj.getObject().passivate(); + } +} diff --git a/protocol/src/main/java/mc/protocol/pool/PacketFactory.java b/protocol/src/main/java/mc/protocol/pool/PacketFactory.java new file mode 100644 index 0000000..f0caf1a --- /dev/null +++ b/protocol/src/main/java/mc/protocol/pool/PacketFactory.java @@ -0,0 +1,30 @@ +package mc.protocol.pool; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import mc.protocol.packets.ClientSidePacket; +import org.apache.commons.pool2.BasePooledObjectFactory; +import org.apache.commons.pool2.PooledObject; +import org.apache.commons.pool2.impl.DefaultPooledObject; + +@Slf4j +@RequiredArgsConstructor +public class PacketFactory

extends BasePooledObjectFactory

{ + + private final Class

clazz; + + @Override + public P create() throws Exception { + return clazz.getDeclaredConstructor().newInstance(); + } + + @Override + public PooledObject

wrap(P packet) { + return new DefaultPooledObject<>(packet); + } + + @Override + public void passivateObject(PooledObject

pooledPacket) { + pooledPacket.getObject().passivate(); + } +} diff --git a/protocol/src/main/java/mc/protocol/pool/PacketPool.java b/protocol/src/main/java/mc/protocol/pool/PacketPool.java new file mode 100644 index 0000000..932fec1 --- /dev/null +++ b/protocol/src/main/java/mc/protocol/pool/PacketPool.java @@ -0,0 +1,26 @@ +package mc.protocol.pool; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import mc.protocol.packets.ClientSidePacket; +import org.apache.commons.pool2.ObjectPool; + +import java.util.Map; + +@Slf4j +@RequiredArgsConstructor +public class PacketPool { + + @SuppressWarnings("rawtypes") + private final Map, ObjectPool> mapPoolPackets; + + @SuppressWarnings("unchecked") + public

P borrowObject(Class

packetClass) throws Exception { + return (P) mapPoolPackets.get(packetClass).borrowObject(); + } + + @SuppressWarnings("unchecked") + public

void returnObject(P packet) throws Exception { + mapPoolPackets.get(packet.getClass()).returnObject(packet); + } +} diff --git a/protocol/src/main/java/mc/protocol/pool/Passivable.java b/protocol/src/main/java/mc/protocol/pool/Passivable.java new file mode 100644 index 0000000..856f776 --- /dev/null +++ b/protocol/src/main/java/mc/protocol/pool/Passivable.java @@ -0,0 +1,6 @@ +package mc.protocol.pool; + +public interface Passivable { + + void passivate(); +} diff --git a/protocol/src/main/java/mc/protocol/ChatMode.java b/protocol/src/main/java/mc/protocol/utils/ChatMode.java similarity index 92% rename from protocol/src/main/java/mc/protocol/ChatMode.java rename to protocol/src/main/java/mc/protocol/utils/ChatMode.java index 33c7bcc..992862a 100644 --- a/protocol/src/main/java/mc/protocol/ChatMode.java +++ b/protocol/src/main/java/mc/protocol/utils/ChatMode.java @@ -1,4 +1,4 @@ -package mc.protocol; +package mc.protocol.utils; import javax.annotation.Nullable; diff --git a/protocol/src/main/java/mc/protocol/Difficulty.java b/protocol/src/main/java/mc/protocol/utils/Difficulty.java similarity index 87% rename from protocol/src/main/java/mc/protocol/Difficulty.java rename to protocol/src/main/java/mc/protocol/utils/Difficulty.java index def20c1..dcca038 100644 --- a/protocol/src/main/java/mc/protocol/Difficulty.java +++ b/protocol/src/main/java/mc/protocol/utils/Difficulty.java @@ -1,4 +1,4 @@ -package mc.protocol; +package mc.protocol.utils; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/protocol/src/main/java/mc/protocol/GameMode.java b/protocol/src/main/java/mc/protocol/utils/GameMode.java similarity index 88% rename from protocol/src/main/java/mc/protocol/GameMode.java rename to protocol/src/main/java/mc/protocol/utils/GameMode.java index b224ea6..eb8b92d 100644 --- a/protocol/src/main/java/mc/protocol/GameMode.java +++ b/protocol/src/main/java/mc/protocol/utils/GameMode.java @@ -1,4 +1,4 @@ -package mc.protocol; +package mc.protocol.utils; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/protocol/src/main/java/mc/protocol/LevelType.java b/protocol/src/main/java/mc/protocol/utils/LevelType.java similarity index 91% rename from protocol/src/main/java/mc/protocol/LevelType.java rename to protocol/src/main/java/mc/protocol/utils/LevelType.java index 2b99f36..eaa50da 100644 --- a/protocol/src/main/java/mc/protocol/LevelType.java +++ b/protocol/src/main/java/mc/protocol/utils/LevelType.java @@ -1,4 +1,4 @@ -package mc.protocol; +package mc.protocol.utils; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/protocol/src/main/java/mc/protocol/MainHand.java b/protocol/src/main/java/mc/protocol/utils/MainHand.java similarity index 91% rename from protocol/src/main/java/mc/protocol/MainHand.java rename to protocol/src/main/java/mc/protocol/utils/MainHand.java index 15ea441..3dec589 100644 --- a/protocol/src/main/java/mc/protocol/MainHand.java +++ b/protocol/src/main/java/mc/protocol/utils/MainHand.java @@ -1,4 +1,4 @@ -package mc.protocol; +package mc.protocol.utils; import javax.annotation.Nullable; diff --git a/protocol/src/main/java/mc/protocol/utils/Table.java b/protocol/src/main/java/mc/protocol/utils/Table.java new file mode 100644 index 0000000..acc1302 --- /dev/null +++ b/protocol/src/main/java/mc/protocol/utils/Table.java @@ -0,0 +1,23 @@ +package mc.protocol.utils; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; + +public class Table { + + private final Map> map = new HashMap<>(); + + @Nullable + public V getColumnAndRow(C column, R row) { + if (!map.containsKey(column)) { + return null; + } + + return map.get(column).get(row); + } + + public void put(C column, R row, V value) { + map.computeIfAbsent(column, c -> new HashMap<>()).put(row, value); + } +} diff --git a/server/src/main/java/mc/server/Main.java b/server/src/main/java/mc/server/Main.java index fedfe80..e330fec 100644 --- a/server/src/main/java/mc/server/Main.java +++ b/server/src/main/java/mc/server/Main.java @@ -7,8 +7,11 @@ import joptsimple.OptionParser; import joptsimple.OptionSet; import joptsimple.util.PathConverter; import lombok.extern.slf4j.Slf4j; -import mc.protocol.NettyServer; import mc.protocol.State; +import mc.protocol.api.Server; +import mc.protocol.di.DaggerProtocolComponent; +import mc.protocol.di.ProtocolComponent; +import mc.protocol.di.ProtocolModule; import mc.protocol.packets.PingPacket; import mc.protocol.packets.client.HandshakePacket; import mc.protocol.packets.client.LoginStartPacket; @@ -46,14 +49,21 @@ public class Main { Config config = serverComponent.getConfig(); - NettyServer server = NettyServer.createServer(); + ProtocolComponent protocolComponent = DaggerProtocolComponent.builder() + .protocolModule(new ProtocolModule(true)) + .build(); + + Server server = protocolComponent.getServer(); PacketHandler packetHandler = serverComponent.getPacketHandler(); - State.HANDSHAKING.packetFlux(HandshakePacket.class).subscribe(packetHandler::onHandshake); - State.STATUS.packetFlux(PingPacket.class).subscribe(packetHandler::onKeepAlive); - State.STATUS.packetFlux(StatusServerRequestPacket.class).subscribe(packetHandler::onServerStatus); - State.LOGIN.packetFlux(LoginStartPacket.class).subscribe(packetHandler::onLoginStart); - State.PLAY.packetFlux(PingPacket.class).subscribe(packetHandler::onKeepAlivePlay); + server.onNewConnect(connectionContext -> connectionContext.setState(State.HANDSHAKING)); + server.onDisonnect(connectionContext -> connectionContext.setState(null)); + + server.listenPacket(State.HANDSHAKING, HandshakePacket.class, packetHandler::onHandshake); + server.listenPacket(State.STATUS, PingPacket.class, packetHandler::onKeepAlive); + server.listenPacket(State.STATUS, StatusServerRequestPacket.class, packetHandler::onServerStatus); + server.listenPacket(State.LOGIN, LoginStartPacket.class, packetHandler::onLoginStart); + server.listenPacket(State.PLAY, PingPacket.class, packetHandler::onKeepAlivePlay); server.bind(config.server().host(), config.server().port()); } diff --git a/server/src/main/java/mc/server/PacketHandler.java b/server/src/main/java/mc/server/PacketHandler.java index 49ebee7..bf35af9 100644 --- a/server/src/main/java/mc/server/PacketHandler.java +++ b/server/src/main/java/mc/server/PacketHandler.java @@ -3,6 +3,7 @@ package mc.server; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import mc.protocol.*; +import mc.protocol.api.ConnectionContext; import mc.protocol.model.Location; import mc.protocol.model.Look; import mc.protocol.model.ServerInfo; @@ -12,6 +13,9 @@ import mc.protocol.packets.client.LoginStartPacket; import mc.protocol.packets.client.StatusServerRequestPacket; import mc.protocol.packets.server.*; import mc.protocol.serializer.TextSerializer; +import mc.protocol.utils.Difficulty; +import mc.protocol.utils.GameMode; +import mc.protocol.utils.LevelType; import mc.server.config.Config; import org.apache.commons.io.IOUtils; @@ -27,22 +31,24 @@ import java.util.UUID; @RequiredArgsConstructor public class PacketHandler { - private final Config config; private final Random random = new Random(System.currentTimeMillis()); + private final Config config; - public void onHandshake(ChannelContext channel) { - channel.setState(channel.getPacket().getNextState()); + public void onHandshake(ConnectionContext context, HandshakePacket packet) { + context.setState(packet.getNextState()); } - public void onKeepAlive(ChannelContext channel) { - channel.getCtx().writeAndFlush(channel.getPacket()).channel().disconnect(); + public void onKeepAlive(ConnectionContext context, PingPacket packet) { + context.sendNow(packet); + context.disconnect(); } - public void onKeepAlivePlay(ChannelContext channel) { - channel.getCtx().writeAndFlush(channel.getPacket()); + public void onKeepAlivePlay(ConnectionContext context, PingPacket packet) { + context.sendNow(packet); } - public void onServerStatus(ChannelContext channel) { + @SuppressWarnings("unused") + public void onServerStatus(ConnectionContext context, StatusServerRequestPacket packet) { ServerInfo serverInfo = new ServerInfo(); serverInfo.version().name(ProtocolConstant.PROTOCOL_NAME); serverInfo.version().protocol(ProtocolConstant.PROTOCOL_NUMBER); @@ -58,18 +64,16 @@ public class PacketHandler { StatusServerResponse response = new StatusServerResponse(); response.setInfo(serverInfo); - channel.getCtx().writeAndFlush(response); + context.sendNow(response); } - public void onLoginStart(ChannelContext channel) { - LoginStartPacket loginStartPacket = channel.getPacket(); - + public void onLoginStart(ConnectionContext context, LoginStartPacket loginStartPacket) { var loginSuccessPacket = new LoginSuccessPacket(); loginSuccessPacket.setUuid(UUID.randomUUID()); loginSuccessPacket.setName(loginStartPacket.getName()); - channel.getCtx().writeAndFlush(loginSuccessPacket); - channel.setState(State.PLAY); + context.sendNow(loginSuccessPacket); + context.setState(State.PLAY); var joinGamePacket = new JoinGamePacket(); joinGamePacket.setEntityId(random.nextInt()); @@ -78,14 +82,14 @@ public class PacketHandler { joinGamePacket.setDifficulty(Difficulty.PEACEFUL); joinGamePacket.setLevelType(LevelType.FLAT); - channel.getCtx().write(joinGamePacket); + context.send(joinGamePacket); Location spawnLocation = new Location(0d, 63d, 0d); var spawnPositionPacket = new SpawnPositionPacket(); spawnPositionPacket.setSpawn(spawnLocation); - channel.getCtx().write(spawnPositionPacket); + context.send(spawnPositionPacket); var playerAbilitiesPacket = new PlayerAbilitiesPacket(); playerAbilitiesPacket.setCatFly(true); @@ -95,29 +99,29 @@ public class PacketHandler { playerAbilitiesPacket.setFieldOfView(0.0f); playerAbilitiesPacket.setFlyingSpeed(0.05f); - channel.getCtx().write(playerAbilitiesPacket); + context.send(playerAbilitiesPacket); - channel.getCtx().flush(); + context.flushSending(); var chunkDataPacket = new ChunkDataPacket(); chunkDataPacket.setX(0); chunkDataPacket.setZ(0); - channel.getCtx().writeAndFlush(chunkDataPacket); + context.sendNow(chunkDataPacket); var playerPositionAndLookPacket = new SPlayerPositionAndLookPacket(); playerPositionAndLookPacket.setPosition(spawnLocation); playerPositionAndLookPacket.setLook(new Look(0f, 0f)); playerPositionAndLookPacket.setTeleportId(random.nextInt()); - channel.getCtx().write(playerPositionAndLookPacket); + context.send(playerPositionAndLookPacket); PingPacket pingPacket = new PingPacket(); pingPacket.setPayload(System.currentTimeMillis()); - channel.getCtx().write(pingPacket); + context.send(pingPacket); - channel.getCtx().flush(); + context.flushSending(); } private static String faviconToBase64(Path iconPath) {