KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/network/Connection.kt

168 lines
4.7 KiB
Kotlin

package ru.dbotthepony.kstarbound.network
import com.google.common.util.concurrent.ThreadFactoryBuilder
import io.netty.channel.Channel
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelInboundHandlerAdapter
import io.netty.channel.ChannelOption
import io.netty.channel.nio.NioEventLoopGroup
import it.unimi.dsi.fastutil.ints.IntAVLTreeSet
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.io.StreamCodec
import ru.dbotthepony.kommons.io.VarIntValueCodec
import ru.dbotthepony.kstarbound.network.syncher.BasicNetworkedElement
import ru.dbotthepony.kstarbound.network.syncher.GroupElement
import ru.dbotthepony.kstarbound.network.syncher.MasterElement
import ru.dbotthepony.kstarbound.network.syncher.networkedSignedInt
import ru.dbotthepony.kstarbound.player.Avatar
import ru.dbotthepony.kstarbound.server.ServerChannels
import ru.dbotthepony.kstarbound.world.entities.PlayerEntity
import java.io.Closeable
import java.util.*
import kotlin.properties.Delegates
abstract class Connection(val side: ConnectionSide, val type: ConnectionType) : ChannelInboundHandlerAdapter(), Closeable {
abstract override fun channelRead(ctx: ChannelHandlerContext, msg: Any)
var avatar: Avatar? = null
var character: PlayerEntity? = null
val rpc = JsonRPC()
var entityIDRange: IntRange by Delegates.notNull()
private set
var connectionID: Int = -1
set(value) {
require(value in 1 .. ServerChannels.MAX_PLAYERS) { "Connection ID is out of range: $value" }
field = value
entityIDRange = value * -65536 .. 65535
}
var nickname: String = ""
val hasChannel get() = ::channel.isInitialized
lateinit var channel: Channel
private set
var isLegacy: Boolean = true
protected set
private val handshakeValidator = PacketRegistry.HANDSHAKE.Validator(side)
private val handshakeSerializer = PacketRegistry.HANDSHAKE.Serializer(side)
private val nativeValidator = PacketRegistry.NATIVE.Validator(side)
private val nativeSerializer = PacketRegistry.NATIVE.Serializer(side)
private val legacyValidator = PacketRegistry.LEGACY.Validator(side)
private val legacySerializer = PacketRegistry.LEGACY.Serializer(side)
var isConnected = false
private set
open fun setupLegacy() {
isConnected = true
LOGGER.info("Handshake successful on ${channel.remoteAddress()}, channel is using legacy protocol")
if (type == ConnectionType.MEMORY) {
channel.pipeline().remove(handshakeValidator)
channel.pipeline().addFirst(legacyValidator)
} else {
channel.pipeline().remove(handshakeSerializer)
channel.pipeline().addFirst(legacySerializer)
}
isLegacy = true
}
open fun setupNative() {
isConnected = true
LOGGER.info("Handshake successful on ${channel.remoteAddress()}, channel is using native protocol")
if (type == ConnectionType.MEMORY) {
channel.pipeline().remove(handshakeValidator)
channel.pipeline().addFirst(nativeValidator)
} else {
channel.pipeline().remove(handshakeSerializer)
channel.pipeline().addFirst(nativeSerializer)
}
isLegacy = false
inGame()
}
protected open fun onChannelClosed() {
LOGGER.info("$this is terminated")
}
fun bind(channel: Channel) {
channel.config().setOption(ChannelOption.TCP_NODELAY, true)
this.channel = channel
if (type == ConnectionType.MEMORY)
channel.pipeline().addFirst(handshakeValidator)
else
channel.pipeline().addFirst(handshakeSerializer)
channel.pipeline().addLast(this)
channel.closeFuture().addListener {
onChannelClosed()
}
}
// one connection can be reused for second channel (while first is being closed)
override fun isSharable(): Boolean {
return true
}
override fun ensureNotSharable() {
}
abstract fun inGame()
fun send(packet: IPacket) {
if (channel.isOpen && isConnected) {
channel.write(packet)
}
}
fun sendAndFlush(packet: IPacket) {
if (channel.isOpen && isConnected) {
channel.write(packet)
flush()
}
}
open fun flush() {
channel.flush()
}
abstract fun disconnect(reason: String = "")
override fun close() {
channel.close()
}
val windowXMin = networkedSignedInt()
val windowYMin = networkedSignedInt()
val windowWidth = networkedSignedInt()
val windowHeight = networkedSignedInt()
val playerID = networkedSignedInt()
// holy shit
val clientPresenceEntities = BasicNetworkedElement(IntAVLTreeSet(), StreamCodec.Collection(VarIntValueCodec) { IntAVLTreeSet() })
val clientStateGroup = MasterElement(GroupElement(windowXMin, windowYMin, windowWidth, windowHeight, playerID, clientPresenceEntities))
companion object {
private val EMPTY_UUID = UUID(0L, 0L)
private val LOGGER = LogManager.getLogger()
val NIO_POOL by lazy {
NioEventLoopGroup(ThreadFactoryBuilder().setDaemon(true).setNameFormat("Starbound Network IO %d").build())
}
}
}