diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index e2c3d3d9d..963a780a6 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -73,8 +73,9 @@ - + + @@ -114,6 +115,11 @@ + + diff --git a/build.gradle.kts b/build.gradle.kts index 56cd7eda1..62ba66cd0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -40,6 +40,7 @@ dependencies { implementation("com.akuleshov7:ktoml-core:0.5.1") implementation("com.akuleshov7:ktoml-file:0.5.1") implementation("net.bytebuddy:byte-buddy-agent:1.14.12") + implementation("org.jctools:jctools-core:4.0.5") api("io.github.dockyardmc:bytesocks-client-java:1.0-SNAPSHOT") { exclude(module = "slf4j-api") diff --git a/src/main/kotlin/io/github/dockyardmc/DockyardServer.kt b/src/main/kotlin/io/github/dockyardmc/DockyardServer.kt index 78b2c23d8..5102d339d 100644 --- a/src/main/kotlin/io/github/dockyardmc/DockyardServer.kt +++ b/src/main/kotlin/io/github/dockyardmc/DockyardServer.kt @@ -97,7 +97,7 @@ class DockyardServer(configBuilder: Config.() -> Unit) { if (ConfigManager.config.implementationConfig.applyBlockPlacementRules) DefaultBlockHandlers().register() } - NetworkCompression.compressionThreshold = ConfigManager.config.networkCompressionThreshold + NetworkCompression.COMPRESSION_THRESHOLD = ConfigManager.config.networkCompressionThreshold WorldManager.loadDefaultWorld() Events.dispatch(ServerFinishLoadEvent(this)) diff --git a/src/main/kotlin/io/github/dockyardmc/apis/Hologram.kt b/src/main/kotlin/io/github/dockyardmc/apis/Hologram.kt index de53a7440..412a41985 100644 --- a/src/main/kotlin/io/github/dockyardmc/apis/Hologram.kt +++ b/src/main/kotlin/io/github/dockyardmc/apis/Hologram.kt @@ -63,8 +63,8 @@ class Hologram(spawnLocation: Location, builder: HologramBuilder) : Entity(spawn } fun addStaticLine(line: StaticContentLine) { - val lineIndex = lines.size lines.add(line) + if (viewers.isEmpty()) return viewers.forEach(::updateFull) } @@ -73,8 +73,8 @@ class Hologram(spawnLocation: Location, builder: HologramBuilder) : Entity(spawn } fun addPlayerLine(line: PlayerContentLine) { - val lineIndex = lines.size lines.add(line) + if (viewers.isEmpty()) return viewers.forEach(::updateFull) } diff --git a/src/main/kotlin/io/github/dockyardmc/apis/sidebar/Sidebar.kt b/src/main/kotlin/io/github/dockyardmc/apis/sidebar/Sidebar.kt index f90a5cffc..e0b33e36e 100644 --- a/src/main/kotlin/io/github/dockyardmc/apis/sidebar/Sidebar.kt +++ b/src/main/kotlin/io/github/dockyardmc/apis/sidebar/Sidebar.kt @@ -80,12 +80,14 @@ class Sidebar(initialTitle: String, initialLines: Map) : Viewa } fun setGlobalLine(index: Int, value: String) { + if (viewers.isEmpty()) return val before = indexToLineMap[index] as SidebarLine.Static? indexToLineMap[index] = SidebarLine.Static(value) if (before?.value != value) viewers.forEach { viewer -> sendLinePacket(viewer, index) } } fun setPlayerLine(index: Int, value: (Player) -> String) { + if (viewers.isEmpty()) return indexToLineMap[index] = SidebarLine.Player(value) viewers.forEach { viewer -> sendLinePacket(viewer, index) } } @@ -116,6 +118,7 @@ class Sidebar(initialTitle: String, initialLines: Map) : Viewa init { title.valueChanged { event -> + if (viewers.isEmpty()) return@valueChanged val packet = ClientboundScoreboardObjectivePacket(objective, ScoreboardMode.EDIT_TEXT, event.newValue, ScoreboardType.INTEGER) viewers.sendPacket(packet) } diff --git a/src/main/kotlin/io/github/dockyardmc/bindables/BindablePairMap.kt b/src/main/kotlin/io/github/dockyardmc/bindables/BindablePairMap.kt deleted file mode 100644 index 21860ded4..000000000 --- a/src/main/kotlin/io/github/dockyardmc/bindables/BindablePairMap.kt +++ /dev/null @@ -1,92 +0,0 @@ -package io.github.dockyardmc.bindables - -data class PairKey(val first: T, val second: T) - -class BindablePairMap(map: Map, V>) { - - constructor() : this(mutableMapOf()) - - private var innerMap: MutableMap, V> = mutableMapOf() - private var removeListener = mutableListOf>() - private var changeListener = mutableListOf>() - private var updateListener = mutableListOf>() - - init { - map.forEach(innerMap::put) - } - - // Custom operator for 2D-like key access - operator fun set(first: T, second: T, value: V) { - innerMap[PairKey(first, second)] = value - changeListener.forEach { it.unit.invoke(BindablePairMapItemSetEvent(first, second, value)) } - updateListener.forEach { it.unit.invoke() } - } - - val values: Map, V> - get() = innerMap.toMap() - - val size: Int - get() = innerMap.size - - fun addIfNotPresent(key: PairKey, value: V) { - if (!values.containsKey(key)) set(key.first, key.second, value) - } - - fun removeIfPresent(key: PairKey) { - if (values.contains(key)) remove(key) - } - - fun remove(key: PairKey) { - val item = innerMap[key] ?: return - innerMap.remove(key) - removeListener.forEach { it.unit.invoke(BindablePairMapItemRemovedEvent(key.first, key.second, item)) } - updateListener.forEach { it.unit.invoke() } - } - - operator fun contains(target: PairKey): Boolean = values.contains(target) - - class BindablePairMapItemSetEvent(val first: T, val second: T, val value: V) - class BindablePairMapItemRemovedEvent(val first: T, val second: T, val value: V) - - fun itemRemoved(function: (event: BindablePairMapItemRemovedEvent) -> Unit) { - removeListener.add(BindablePairMapItemRemoveListener(function)) - } - - fun itemSet(function: (event: BindablePairMapItemSetEvent) -> Unit) { - changeListener.add(BindablePairMapItemChangeListener(function)) - } - - fun mapUpdated(function: () -> Unit) { - updateListener.add(BindablePairMapUpdateListener(function)) - } - - fun setSilently(key: PairKey, value: V) { - innerMap[key] = value - } - - fun removeSilently(key: PairKey) { - innerMap.remove(key) - } - - fun triggerUpdate() { - updateListener.forEach { it.unit.invoke() } - } - - operator fun get(key: PairKey): V? = innerMap[key] - - fun clear(silent: Boolean = false) { - val map = innerMap.toMutableMap() - map.forEach { - if (silent) { - innerMap.remove(it.key) - } else { - remove(it.key) - } - } - updateListener.forEach { it.unit.invoke() } - } - - class BindablePairMapItemRemoveListener(val unit: (list: BindablePairMapItemRemovedEvent) -> Unit) - class BindablePairMapItemChangeListener(val unit: (list: BindablePairMapItemSetEvent) -> Unit) - class BindablePairMapUpdateListener(val unit: () -> Unit) -} diff --git a/src/main/kotlin/io/github/dockyardmc/data/DataComponentHasher.kt b/src/main/kotlin/io/github/dockyardmc/data/DataComponentHasher.kt deleted file mode 100644 index 9a3213d49..000000000 --- a/src/main/kotlin/io/github/dockyardmc/data/DataComponentHasher.kt +++ /dev/null @@ -1,21 +0,0 @@ -package io.github.dockyardmc.data - -import io.github.dockyardmc.item.TranscoderCRC32C - -object DataComponentHasher { - fun hash(component: DataComponent): Int { - val format: TranscoderCRC32C.HashContainer<*> = if (component.isSingleField) { - TranscoderCRC32C.HashContainerValue() - } else { - TranscoderCRC32C.HashContainerMap() - } - -// (component.getHashCodec() as Codec).writeTranscoded(TranscoderCRC32C, format, component, "") - - return when (format) { - is TranscoderCRC32C.HashContainerMap -> CRC32CHasher.ofMap(format.getValue()) - is TranscoderCRC32C.HashContainerValue -> format.getValue() - else -> throw IllegalArgumentException("not supported") - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/io/github/dockyardmc/data/DataComponentRegistry.kt b/src/main/kotlin/io/github/dockyardmc/data/DataComponentRegistry.kt index 5e9558983..4c655e1f4 100644 --- a/src/main/kotlin/io/github/dockyardmc/data/DataComponentRegistry.kt +++ b/src/main/kotlin/io/github/dockyardmc/data/DataComponentRegistry.kt @@ -4,12 +4,11 @@ import io.github.dockyardmc.data.components.* import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap -import java.util.concurrent.atomic.AtomicInteger import kotlin.reflect.KClass object DataComponentRegistry { - val protocolIdCounter = AtomicInteger() + var protocolIdCounter = 0 val dataComponentsById = Int2ObjectOpenHashMap>() val dataComponentsByIdentifier = Object2ObjectOpenHashMap>() @@ -113,13 +112,14 @@ object DataComponentRegistry { val SHULKER_COLOR = register("minecraft:shulker/color", ShulkerColorComponent::class) fun register(identifier: String, kclass: KClass): KClass { - val protocolId = protocolIdCounter.getAndIncrement() + val protocolId = protocolIdCounter dataComponentsById[protocolId] = kclass dataComponentsByIdReversed[kclass] = protocolId dataComponentsByIdentifier[identifier] = kclass dataComponentsByIdentifierReversed[kclass] = identifier + protocolIdCounter++ return kclass } } \ No newline at end of file diff --git a/src/main/kotlin/io/github/dockyardmc/data/components/PotionContentsComponent.kt b/src/main/kotlin/io/github/dockyardmc/data/components/PotionContentsComponent.kt index 91544098c..4884c5646 100644 --- a/src/main/kotlin/io/github/dockyardmc/data/components/PotionContentsComponent.kt +++ b/src/main/kotlin/io/github/dockyardmc/data/components/PotionContentsComponent.kt @@ -33,7 +33,7 @@ class PotionContentsComponent( override fun write(buffer: ByteBuf) { buffer.writeOptional(potion?.getProtocolId(), ByteBuf::writeVarInt) buffer.writeOptional(customColor, CustomColor::writePackedInt) - buffer.writeList(effects, ByteBuf::writeAppliedPotionEffect) + buffer.writeList(effects, AppliedPotionEffect::write) buffer.writeOptional(customName, ByteBuf::writeString) } @@ -42,7 +42,7 @@ class PotionContentsComponent( return PotionContentsComponent( buffer.readOptional(ByteBuf::readVarInt)?.let { PotionTypeRegistry.getByProtocolId(it) }, buffer.readOptional(ByteBuf::readInt)?.let { CustomColor.fromRGBInt(it) }, - buffer.readAppliedPotionEffectsList(), + buffer.readList(AppliedPotionEffect::read), buffer.readOptional(ByteBuf::readString) ) } diff --git a/src/main/kotlin/io/github/dockyardmc/events/Event.kt b/src/main/kotlin/io/github/dockyardmc/events/Event.kt index b4e0fcda6..bb162b655 100644 --- a/src/main/kotlin/io/github/dockyardmc/events/Event.kt +++ b/src/main/kotlin/io/github/dockyardmc/events/Event.kt @@ -17,13 +17,27 @@ interface Event { other: Set = setOf(), val isGlobalEvent: Boolean = false ) { - // what the fuck - val players = players + entities.filterIsInstance() - var entities = entities + players - val locations = locations + this.entities.map { it.location } - val worlds = worlds + this.locations.map { it.world } + // combining sets is expensive and is done in initialization of every event. + // In most cases, either none or only one is accessed. Let's make them lazy so they are + // computed only when needed + val players: Set by lazy { + players + entities.filterIsInstance() + } - val other: Set = this.players + this.entities + this.worlds + this.locations + other + val entities: Set by lazy { + entities + players + } + + val locations: Set by lazy { + locations + this.entities.map { entity -> entity.location } + } + val worlds: Set by lazy { + worlds + this.locations.map { location -> location.world } + } + + val other: Set by lazy { + this.players + this.entities + this.worlds + this.locations + other + } operator fun contains(element: Any) = other.contains(element) diff --git a/src/main/kotlin/io/github/dockyardmc/events/system/EventSystem.kt b/src/main/kotlin/io/github/dockyardmc/events/system/EventSystem.kt index d198c5750..3f53fac3e 100644 --- a/src/main/kotlin/io/github/dockyardmc/events/system/EventSystem.kt +++ b/src/main/kotlin/io/github/dockyardmc/events/system/EventSystem.kt @@ -3,12 +3,14 @@ package io.github.dockyardmc.events.system import io.github.dockyardmc.events.* import io.github.dockyardmc.events.EventListener import io.github.dockyardmc.utils.Disposable +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import java.util.* import kotlin.reflect.KClass +@Suppress("UNCHECKED_CAST") abstract class EventSystem : Disposable { - val eventMap = mutableMapOf, HandlerList>() + val eventMap = Object2ObjectOpenHashMap, HandlerList>() var filter = EventFilter.empty() val children = ObjectOpenHashSet() diff --git a/src/main/kotlin/io/github/dockyardmc/extentions/ExtendedByteBuf.kt b/src/main/kotlin/io/github/dockyardmc/extentions/ExtendedByteBuf.kt index a29e7c65f..57b60655c 100644 --- a/src/main/kotlin/io/github/dockyardmc/extentions/ExtendedByteBuf.kt +++ b/src/main/kotlin/io/github/dockyardmc/extentions/ExtendedByteBuf.kt @@ -2,21 +2,12 @@ package io.github.dockyardmc.extentions import cz.lukynka.prettylog.LogType import cz.lukynka.prettylog.log -import io.github.dockyardmc.item.ItemStack import io.github.dockyardmc.maths.positiveCeilDiv -import io.github.dockyardmc.maths.vectors.Vector3 -import io.github.dockyardmc.maths.vectors.Vector3d -import io.github.dockyardmc.maths.vectors.Vector3f -import io.github.dockyardmc.registry.AppliedPotionEffect -import io.github.dockyardmc.registry.AppliedPotionEffectSettings import io.github.dockyardmc.registry.Registry import io.github.dockyardmc.registry.RegistryEntry -import io.github.dockyardmc.registry.registries.* import io.github.dockyardmc.scroll.Component import io.github.dockyardmc.scroll.CustomColor import io.github.dockyardmc.scroll.extensions.toComponent -import io.github.dockyardmc.sounds.Sound -import io.github.dockyardmc.sounds.SoundEvent import io.netty.buffer.ByteBuf import io.netty.buffer.ByteBufInputStream import io.netty.buffer.Unpooled @@ -27,20 +18,11 @@ import net.kyori.adventure.nbt.CompoundBinaryTag import java.io.ByteArrayOutputStream import java.io.InputStream import java.nio.charset.StandardCharsets -import java.time.Instant import java.util.* -import kotlin.experimental.inv -private const val SEGMENT_BITS: Byte = 0x7F -private const val CONTINUE_BIT = 0x80 - -fun ByteBuf.writeOptionalOLD(item: Any?, unit: (ByteBuf) -> Unit) { - val isPresent = item != null - this.writeBoolean(isPresent) - if (isPresent) { - unit.invoke(this) - } -} +private const val SEGMENT_BITS: Int = 0x7F +private const val CONTINUE_BIT: Int = 0x80 +private const val MAXIMUM_VAR_INT_SIZE = 5 fun ByteBuf.readList(reader: (ByteBuf) -> T): List { val list = mutableListOf() @@ -64,18 +46,10 @@ fun ByteBuf.readTextComponent(): Component { return this.readNBTCompound().toComponent() } -fun ByteBuf.writeItemStackList(list: Collection) { - this.writeVarInt(list.size) - list.forEach { - it.write(this) - } -} - fun ByteBuf.writeColor(color: CustomColor) { this.writeInt(color.getPackedInt()) } - fun ByteBuf.readUUID(): UUID { val most = this.readLong() val least = this.readLong() @@ -176,16 +150,13 @@ fun ByteBuf.writeVarLong(long: Long): ByteBuf { } fun ByteBuf.readVarLong(): Long { - var b: Byte - var long = 0L - var iteration = 0 - do { - b = this.readByte() - long = long or ((b.toInt() and 0x7F).toLong() shl iteration++ * 7) - if (iteration <= 10) continue - throw RuntimeException("VarLong too big") - } while (hasContinuationBit(b)) - return long + var result = 0L + for (shift in 0 until 56 step 7) { + val b = this.readByte() + result = result or ((b.toLong() and 0x7F) shl shift) + if (b >= 0) return result + } + return result or ((this.readByte().toLong() and 0xFF) shl 56) } fun hasContinuationBit(byte: Byte): Boolean = byte.toInt() and 0x80 == 128 @@ -201,19 +172,27 @@ fun > ByteBuf.writeByteEnum(value: T) { this.writeByte(value.ordinal) } - fun ByteBuf.readVarInt(): Int { - var value = 0 - var position = 0 - var currentByte: Byte - while (this.isReadable) { - currentByte = readByte() - value = value or (currentByte.toInt() and SEGMENT_BITS.toInt() shl position) - if (currentByte.toInt() and CONTINUE_BIT == 0) break - position += 7 - if (position >= 32) throw java.lang.RuntimeException("VarInt is too big") + val readable = this.readableBytes() + if (readable == 0) throw DecoderException("Invalid VarInt") + + // decode only one byte first as this is the most common size of varints + var current = this.readByte().toInt() + if ((current and CONTINUE_BIT) != 128) { + return current } - return value + + // no point in while loop that has higher overhead instead of for loop with max size of the varint + val maxRead = MAXIMUM_VAR_INT_SIZE.coerceAtMost(readable) + var varInt = current and SEGMENT_BITS + for (i in 1..) { @@ -222,20 +201,52 @@ fun ByteBuf.writeStringArray(list: Collection) { } +// dark magic but its 2.05 nanoseconds per write +// https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/ +// little bit modified to write bytes directly because kotlin fucks up the byte order fun ByteBuf.writeVarInt(int: Int) { - var value = int - while (true) { - if (value and SEGMENT_BITS.inv().toInt() == 0) { - writeByte(value) - return + when { + // 1-byte + int and (-1 shl 7) == 0 -> { + this.writeByte(int) + } + + // 2-byte + int and (-1 shl 14) == 0 -> { + val w = (int and SEGMENT_BITS or CONTINUE_BIT) shl 8 or (int ushr 7) + this.writeShort(w) + } + + // 3-byte + int and (-1 shl 21) == 0 -> { + this.writeByte(int and SEGMENT_BITS or CONTINUE_BIT) + this.writeByte((int ushr 7) and SEGMENT_BITS or CONTINUE_BIT) + this.writeByte(int ushr 14) + } + + // 4-byte + int and (-1 shl 28) == 0 -> { + this.writeByte(int and SEGMENT_BITS or CONTINUE_BIT) + this.writeByte((int ushr 7) and SEGMENT_BITS or CONTINUE_BIT) + this.writeByte((int ushr 14) and SEGMENT_BITS or CONTINUE_BIT) + this.writeByte(int ushr 21) + } + + // 5-byte + else -> { + this.writeByte(int and SEGMENT_BITS or CONTINUE_BIT) + this.writeByte((int ushr 7) and SEGMENT_BITS or CONTINUE_BIT) + this.writeByte((int ushr 14) and SEGMENT_BITS or CONTINUE_BIT) + this.writeByte((int ushr 21) and SEGMENT_BITS or CONTINUE_BIT) + this.writeByte(int ushr 28) } - writeByte(value and SEGMENT_BITS.toInt() or CONTINUE_BIT) - value = value ushr 7 } } fun ByteBuf.readString() = readString(Short.MAX_VALUE.toInt()) + fun ByteBuf.readUtfAndLength() = readUtfAndLength(Short.MAX_VALUE.toInt()) + fun ByteBuf.readString(i: Int): String { val maxSize = i * 3 val size = this.readVarInt() @@ -247,6 +258,7 @@ fun ByteBuf.readString(i: Int): String { return string } +@Suppress("UNCHECKED_CAST") fun ByteBuf.readRegistryEntry(registry: Registry): T { return registry.getByProtocolId(this.readVarInt()) as T } @@ -255,14 +267,6 @@ fun ByteBuf.writeRegistryEntry(entry: RegistryEntry) { this.writeVarInt(entry.getProtocolId()) } -fun ByteBuf.readStringList(): List { - val list = mutableListOf() - for (i in 0 until this.readVarInt()) { - list.add(this.readString()) - } - return list -} - fun ByteBuf.readRemainingBytesAsByteArray(): ByteArray { val bytes = ByteArray(this.readableBytes()) this.readBytes(bytes) @@ -299,153 +303,8 @@ fun ByteBuf.toByteArraySafe(): ByteArray { return bytes } -fun ByteBuf.readAppliedPotionEffect(): AppliedPotionEffect { - val id = this.readVarInt() - val settings = AppliedPotionEffectSettings.read(this) - - val effect = PotionEffectRegistry.getByProtocolId(id) - - return AppliedPotionEffect(effect, settings) -} - -fun ByteBuf.writeAppliedPotionEffect(effect: AppliedPotionEffect) { - this.writeVarInt(PotionEffectRegistry[effect.effect.identifier].getProtocolId()) - effect.settings.write(this) -} - -fun ByteBuf.readAppliedPotionEffectsList(): List { - val list = mutableListOf() - for (i in 0 until this.readVarInt()) { - list.add(this.readAppliedPotionEffect()) - } - return list -} - - fun ByteArray.toByteBuf(): ByteBuf = Unpooled.copiedBuffer(this) -inline fun ByteBuf.readOptionalOrDefault(default: T): T { - val optional = this.readOptionalOrNull() ?: return default - return optional -} - -inline fun ByteBuf.readOptionalOrNull(): T? { - val isPresent = this.readBoolean() - if (!isPresent) return null - return when (T::class) { - Int::class -> this.readVarInt() as T - String::class -> this.readString() as T - Boolean::class -> this.readBoolean() as T - Float::class -> this.readFloat() as T - Double::class -> this.readDouble() as T - Long::class -> this.readLong() as T - UUID::class -> this.readUUID() as T - ItemStack::class -> ItemStack.read(this) as T - Byte::class -> this.readByte() as T - Vector3::class -> Vector3.read(this) as T - Vector3d::class -> Vector3d.read(this) as T - Vector3f::class -> Vector3f.read(this) as T - BinaryTag::class -> (this.readNBT() as CompoundBinaryTag) as T - CompoundBinaryTag::class -> this.readNBTCompound() as T - Sound::class -> Sound(SoundEvent.read(this).identifier) as T - EntityType::class -> EntityTypeRegistry[this.readString()] as T - PotionEffect::class -> PotionEffectRegistry.getByProtocolId(this.readVarInt()) as T - CustomColor::class -> CustomColor.fromRGBInt(this.readInt()) as T - else -> throw IllegalArgumentException("This primitive doesn't have serializer") - } -} - - -fun ByteBuf.readItemList(): MutableList { - val size = this.readVarInt() - val list = mutableListOf() - for (i in 0 until size) { - list.add(ItemRegistry[this.readString()]) - } - return list -} - -fun ByteBuf.readPotionEffectHolder(): PotionEffect { - val type = this.readVarInt() - if (type == 0) { - val identifier = this.readString() - return PotionEffectRegistry[identifier] - } - return PotionEffectRegistry.getByProtocolId(type - 1) -} - -fun ByteBuf.writeAppliedPotionEffectsList(list: Collection) { - this.writeVarInt(list.size) - list.forEach { this.writeAppliedPotionEffect(it) } -} - -fun ByteBuf.readCustomColor(): CustomColor { - return CustomColor.fromRGBInt(this.readInt()) -} - -fun ByteBuf.readCustomColorList(): List { - val list = mutableListOf() - for (i in 0 until this.readVarInt()) { - list.add(this.readCustomColor()) - } - return list -} - -fun ByteBuf.writeCustomColorList(list: Collection) { - this.writeVarInt(list.size) - list.forEach { - this.writeInt(it.getPackedInt()) - } -} - -fun ByteBuf.readEntityTypes(): List { - val present = this.readBoolean() - if (!present) return emptyList() - - val type = this.readVarInt() - 1 - if (type == -1) { - val identifier = this.readString() - return listOf(EntityTypeRegistry[identifier]) - } - val list = mutableListOf() - for (i in 0 until type) { - list.add(readEntityTypeHolder()) - } - return list -} - -fun ByteBuf.readEntityTypeHolder(): EntityType { - val type = this.readVarInt() - if (type == 0) { - val identifier = this.readString() - return EntityTypeRegistry[identifier] - } - return EntityTypeRegistry.getByProtocolId(type - 1) -} - -fun ByteBuf.readRepairable(): List { - val type = this.readVarInt() - 1 - if (type == -1) { - val identifier = this.readString() - return listOf() //TODO tag registry - } - val list = mutableListOf() - for (i in 0 until type) { - list.add(readItemHolder()) - } - return list -} - - -fun ByteBuf.readItemHolder(): Item { - val type = this.readVarInt() - if (type == 0) { - val identifier = this.readString() - return ItemRegistry[identifier] - } - return ItemRegistry.getByProtocolId(type - 1) -} - fun ByteBuf.writeByte(byte: Byte) { this.writeByte(byte.toInt()) } diff --git a/src/main/kotlin/io/github/dockyardmc/implementations/commands/ClearCommand.kt b/src/main/kotlin/io/github/dockyardmc/implementations/commands/ClearCommand.kt index cc0349461..61cc80388 100644 --- a/src/main/kotlin/io/github/dockyardmc/implementations/commands/ClearCommand.kt +++ b/src/main/kotlin/io/github/dockyardmc/implementations/commands/ClearCommand.kt @@ -9,7 +9,7 @@ class ClearCommand { init { Commands.add("/clear") { withPermission("dockyard.commands.clear") - withDescription("Clears your inventory") + withDescription("Clears inventory") addOptionalArgument("player", PlayerArgument()) execute { ctx -> val player = getArgumentOrNull("player") ?: ctx.getPlayerOrThrow() diff --git a/src/main/kotlin/io/github/dockyardmc/implementations/commands/EffectCommand.kt b/src/main/kotlin/io/github/dockyardmc/implementations/commands/EffectCommand.kt index acd4aa40d..98471f725 100644 --- a/src/main/kotlin/io/github/dockyardmc/implementations/commands/EffectCommand.kt +++ b/src/main/kotlin/io/github/dockyardmc/implementations/commands/EffectCommand.kt @@ -15,6 +15,7 @@ class EffectCommand { init { Commands.add("/effect") { withPermission("dockyard.commands.effect") + withDescription("Manages effects on players") addSubcommand("give") { addArgument("player", PlayerArgument()) diff --git a/src/main/kotlin/io/github/dockyardmc/implementations/commands/GamemodeCommand.kt b/src/main/kotlin/io/github/dockyardmc/implementations/commands/GamemodeCommand.kt index 19c89485d..98b065e4a 100644 --- a/src/main/kotlin/io/github/dockyardmc/implementations/commands/GamemodeCommand.kt +++ b/src/main/kotlin/io/github/dockyardmc/implementations/commands/GamemodeCommand.kt @@ -11,7 +11,7 @@ class GamemodeCommand { init { Commands.add("/gamemode") { - withDescription("Changes your gamemode") + withDescription("Manages game mode of players") withPermission("dockyard.commands.gamemode") addArgument("game_mode", EnumArgument(GameMode::class)) addOptionalArgument("player", PlayerArgument()) diff --git a/src/main/kotlin/io/github/dockyardmc/implementations/commands/ListCommand.kt b/src/main/kotlin/io/github/dockyardmc/implementations/commands/ListCommand.kt index 726334e7a..ae20c9f4e 100644 --- a/src/main/kotlin/io/github/dockyardmc/implementations/commands/ListCommand.kt +++ b/src/main/kotlin/io/github/dockyardmc/implementations/commands/ListCommand.kt @@ -7,6 +7,8 @@ class ListCommand { init { Commands.add("/list") { + withDescription("Lists all players online") + execute { ctx -> val size = PlayerManager.players.size if(size == 0) { diff --git a/src/main/kotlin/io/github/dockyardmc/implementations/commands/SchedulerCommand.kt b/src/main/kotlin/io/github/dockyardmc/implementations/commands/SchedulerCommand.kt index 00fe86ff9..3ee793ba2 100644 --- a/src/main/kotlin/io/github/dockyardmc/implementations/commands/SchedulerCommand.kt +++ b/src/main/kotlin/io/github/dockyardmc/implementations/commands/SchedulerCommand.kt @@ -12,7 +12,7 @@ class SchedulerCommand { init { Commands.add("/scheduler") { withPermission("dockyard.commands.scheduler") - withDescription("Lets you change tickrate/pause/resume scheduler of a world") + withDescription("Manages tickrate and state of world scheduler") addSubcommand("tickrate") { addArgument("world", WorldArgument()) diff --git a/src/main/kotlin/io/github/dockyardmc/implementations/commands/SoundCommand.kt b/src/main/kotlin/io/github/dockyardmc/implementations/commands/SoundCommand.kt index 9a9b342f4..e679c9a83 100644 --- a/src/main/kotlin/io/github/dockyardmc/implementations/commands/SoundCommand.kt +++ b/src/main/kotlin/io/github/dockyardmc/implementations/commands/SoundCommand.kt @@ -11,7 +11,7 @@ class SoundCommand { init { Commands.add("/playsound") { withPermission("dockyard.commands.playsound") - withDescription("Plays a sound to player") + withDescription("Plays sounds to players") addArgument("sound", SoundArgument()) addArgument("category", EnumArgument(SoundCategory::class)) diff --git a/src/main/kotlin/io/github/dockyardmc/motd/ServerStatus.kt b/src/main/kotlin/io/github/dockyardmc/motd/ServerStatus.kt index 70e7d03bd..309883c65 100644 --- a/src/main/kotlin/io/github/dockyardmc/motd/ServerStatus.kt +++ b/src/main/kotlin/io/github/dockyardmc/motd/ServerStatus.kt @@ -9,7 +9,6 @@ import io.github.dockyardmc.scroll.Component import io.github.dockyardmc.scroll.extensions.toComponent import io.github.dockyardmc.utils.DockyardBranding import kotlinx.serialization.Serializable -import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.io.File import java.net.URL @@ -35,8 +34,8 @@ object ServerStatusManager { fun getCache(ip: String?): ServerStatus { val endpoint = endpointCache[ip] - if(ip == null || endpoint == null) { - if(!::defaultCache.isInitialized) updateCache() + if (ip == null || endpoint == null) { + if (!::defaultCache.isInitialized) updateCache() return defaultCache } diff --git a/src/main/kotlin/io/github/dockyardmc/protocol/NetworkCompression.kt b/src/main/kotlin/io/github/dockyardmc/protocol/NetworkCompression.kt index 26d80e524..033bb33b4 100644 --- a/src/main/kotlin/io/github/dockyardmc/protocol/NetworkCompression.kt +++ b/src/main/kotlin/io/github/dockyardmc/protocol/NetworkCompression.kt @@ -2,46 +2,54 @@ package io.github.dockyardmc.protocol import cz.lukynka.prettylog.LogType import cz.lukynka.prettylog.log +import io.github.dockyardmc.utils.ObjectPool import java.io.ByteArrayOutputStream import java.util.zip.Deflater import java.util.zip.Inflater object NetworkCompression { - var compressionThreshold: Int = -1 + var COMPRESSION_THRESHOLD: Int = -1 + private val INFLATER_POOL = ObjectPool(::Inflater) + private val DEFLATER_POOL = ObjectPool(::Deflater) fun decompress(input: ByteArray): ByteArray { - val inflater = Inflater() + val inflater = INFLATER_POOL.get() inflater.setInput(input) val outputStream = ByteArrayOutputStream() val output = ByteArray(1024) try { - while(!inflater.finished()) { + while (!inflater.finished()) { val decompressionSize = inflater.inflate(output) outputStream.write(output, 0, decompressionSize) } } catch (ex: Exception) { log("Data of the input is not valid compressed data", LogType.ERROR) log(ex) + } finally { + inflater.reset() + INFLATER_POOL.add(inflater) } return outputStream.toByteArray() } fun compress(input: ByteArray): ByteArray { - val deflater = Deflater() + val deflater = DEFLATER_POOL.get() deflater.setInput(input) deflater.finish() val outputStream = ByteArrayOutputStream() val output = ByteArray(1024) - while(!deflater.finished()) { + while (!deflater.finished()) { val compressedSize = deflater.deflate(output) outputStream.write(output, 0, compressedSize) } + deflater.reset() + DEFLATER_POOL.add(deflater) return outputStream.toByteArray() } } \ No newline at end of file diff --git a/src/main/kotlin/io/github/dockyardmc/protocol/encoders/CompressionEncoder.kt b/src/main/kotlin/io/github/dockyardmc/protocol/encoders/CompressionEncoder.kt index f05d89c30..a454ada0d 100644 --- a/src/main/kotlin/io/github/dockyardmc/protocol/encoders/CompressionEncoder.kt +++ b/src/main/kotlin/io/github/dockyardmc/protocol/encoders/CompressionEncoder.kt @@ -15,7 +15,7 @@ class CompressionEncoder(val processor: PlayerNetworkManager) : MessageToByteEnc override fun encode(connection: ChannelHandlerContext, buffer: ByteBuf, out: ByteBuf) { try { val dataLength = buffer.readableBytes() - if (dataLength < NetworkCompression.compressionThreshold) { + if (dataLength < NetworkCompression.COMPRESSION_THRESHOLD) { out.writeVarInt(0) out.writeBytes(buffer) } else { diff --git a/src/main/kotlin/io/github/dockyardmc/protocol/packets/login/LoginHandler.kt b/src/main/kotlin/io/github/dockyardmc/protocol/packets/login/LoginHandler.kt index baa13a107..c39579fcb 100644 --- a/src/main/kotlin/io/github/dockyardmc/protocol/packets/login/LoginHandler.kt +++ b/src/main/kotlin/io/github/dockyardmc/protocol/packets/login/LoginHandler.kt @@ -128,9 +128,9 @@ class LoginHandler(var networkManager: PlayerNetworkManager) : PacketHandler(net list.add(texturesPropertyMap) player.profile = texturesPropertyMap - player.sendPacket(ClientboundSetCompressionPacket(NetworkCompression.compressionThreshold)) + player.sendPacket(ClientboundSetCompressionPacket(NetworkCompression.COMPRESSION_THRESHOLD)) - if(NetworkCompression.compressionThreshold > -1) { + if(NetworkCompression.COMPRESSION_THRESHOLD > -1) { val pipeline = connection.channel().pipeline() pipeline.addBefore(ChannelHandlers.RAW_PACKET_DECODER, ChannelHandlers.PACKET_COMPRESSION_DECODER, CompressionDecoder(player.networkManager)) pipeline.addBefore(ChannelHandlers.RAW_PACKET_ENCODER, ChannelHandlers.PACKET_COMPRESSION_ENCODER, CompressionEncoder(player.networkManager)) diff --git a/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundAddResourcepackPacket.kt b/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundAddResourcepackPacket.kt index d474060f0..726ac4163 100644 --- a/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundAddResourcepackPacket.kt +++ b/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundAddResourcepackPacket.kt @@ -1,11 +1,12 @@ package io.github.dockyardmc.protocol.packets.play.clientbound -import io.github.dockyardmc.extentions.writeOptionalOLD import io.github.dockyardmc.extentions.writeString import io.github.dockyardmc.extentions.writeTextComponent import io.github.dockyardmc.extentions.writeUUID import io.github.dockyardmc.protocol.packets.ClientboundPacket +import io.github.dockyardmc.protocol.writeOptional import io.github.dockyardmc.resourcepack.Resourcepack +import io.netty.buffer.ByteBuf class ClientboundAddResourcepackPacket(resourcepack: Resourcepack) : ClientboundPacket() { @@ -14,8 +15,6 @@ class ClientboundAddResourcepackPacket(resourcepack: Resourcepack) : Clientbound buffer.writeString(resourcepack.url) buffer.writeString("what") buffer.writeBoolean(resourcepack.required) - buffer.writeOptionalOLD(resourcepack.promptMessage) { - it.writeTextComponent(resourcepack.promptMessage!!) - } + buffer.writeOptional(resourcepack.promptMessage, ByteBuf::writeTextComponent) } } \ No newline at end of file diff --git a/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundPlayerInfoUpdatePacket.kt b/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundPlayerInfoUpdatePacket.kt index 84d14aefc..6a3ef1fc6 100644 --- a/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundPlayerInfoUpdatePacket.kt +++ b/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundPlayerInfoUpdatePacket.kt @@ -1,12 +1,10 @@ package io.github.dockyardmc.protocol.packets.play.clientbound -import io.github.dockyardmc.extentions.writeNBT -import io.github.dockyardmc.extentions.writeOptionalOLD -import io.github.dockyardmc.extentions.writeUUID -import io.github.dockyardmc.extentions.writeVarInt +import io.github.dockyardmc.extentions.* import io.github.dockyardmc.player.* import io.github.dockyardmc.protocol.packets.ClientboundPacket -import io.github.dockyardmc.scroll.extensions.toComponent +import io.github.dockyardmc.protocol.writeOptional +import io.netty.buffer.ByteBuf import kotlin.experimental.or class ClientboundPlayerInfoUpdatePacket(vararg updates: PlayerInfoUpdate) : ClientboundPacket() { @@ -26,9 +24,7 @@ class ClientboundPlayerInfoUpdatePacket(vararg updates: PlayerInfoUpdate) : Clie is SetListedInfoUpdateAction -> buffer.writeBoolean(updateAction.listed) is UpdateLatencyInfoUpdateAction -> buffer.writeVarInt(updateAction.ping) is SetDisplayNameInfoUpdateAction -> { - buffer.writeOptionalOLD(updateAction.displayName) { optional -> - optional.writeNBT(updateAction.displayName!!.toComponent().toNBT()) - } + buffer.writeOptional(updateAction.displayName, ByteBuf::writeTextComponent) } } } diff --git a/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundRemoveResourcepackPacket.kt b/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundRemoveResourcepackPacket.kt index 16c79a755..bd95f41f1 100644 --- a/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundRemoveResourcepackPacket.kt +++ b/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundRemoveResourcepackPacket.kt @@ -1,15 +1,14 @@ package io.github.dockyardmc.protocol.packets.play.clientbound -import io.github.dockyardmc.extentions.writeOptionalOLD import io.github.dockyardmc.extentions.writeUUID import io.github.dockyardmc.protocol.packets.ClientboundPacket +import io.github.dockyardmc.protocol.writeOptional +import io.netty.buffer.ByteBuf import java.util.* class ClientboundRemoveResourcepackPacket(uuid: UUID?) : ClientboundPacket() { init { - buffer.writeOptionalOLD(uuid) { - buffer.writeUUID(uuid!!) - } + buffer.writeOptional(uuid, ByteBuf::writeUUID) } } \ No newline at end of file diff --git a/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundSetContainerContentPacket.kt b/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundSetContainerContentPacket.kt index e00268ebf..3b08208ea 100644 --- a/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundSetContainerContentPacket.kt +++ b/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/clientbound/ClientboundSetContainerContentPacket.kt @@ -1,17 +1,17 @@ package io.github.dockyardmc.protocol.packets.play.clientbound -import io.github.dockyardmc.extentions.writeItemStackList import io.github.dockyardmc.extentions.writeVarInt import io.github.dockyardmc.item.ItemStack import io.github.dockyardmc.player.Player import io.github.dockyardmc.protocol.packets.ClientboundPacket +import io.github.dockyardmc.protocol.types.writeList class ClientboundSetContainerContentPacket(player: Player, items: List) : ClientboundPacket() { init { buffer.writeVarInt(if(player.currentlyOpenScreen != null) 1 else 0) buffer.writeVarInt(0) - buffer.writeItemStackList(items) + buffer.writeList(items, ItemStack::write) player.inventory.cursorItem.value.write(buffer) } } \ No newline at end of file diff --git a/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/serverbound/ServerboundPickItemFromEntityPacket.kt b/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/serverbound/ServerboundPickItemFromEntityPacket.kt index 2a0d0bf9e..43201fe44 100644 --- a/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/serverbound/ServerboundPickItemFromEntityPacket.kt +++ b/src/main/kotlin/io/github/dockyardmc/protocol/packets/play/serverbound/ServerboundPickItemFromEntityPacket.kt @@ -1,12 +1,12 @@ package io.github.dockyardmc.protocol.packets.play.serverbound import io.github.dockyardmc.entity.EntityManager +import io.github.dockyardmc.events.Event import io.github.dockyardmc.events.Events import io.github.dockyardmc.events.PlayerPickItemFromEntityEvent import io.github.dockyardmc.extentions.readVarInt import io.github.dockyardmc.protocol.PlayerNetworkManager import io.github.dockyardmc.protocol.packets.ServerboundPacket -import io.github.dockyardmc.utils.getPlayerEventContext import io.netty.buffer.ByteBuf import io.netty.channel.ChannelHandlerContext @@ -15,10 +15,7 @@ class ServerboundPickItemFromEntityPacket(val entityId: Int, val includeData: Bo override fun handle(processor: PlayerNetworkManager, connection: ChannelHandlerContext, size: Int, id: Int) { val entity = EntityManager.getByIdOrNull(entityId) ?: return - val context = getPlayerEventContext(processor.player) - val contextEntitiesMutable = context.entities.toMutableSet() - contextEntitiesMutable.add(entity) - context.entities = contextEntitiesMutable + val context = Event.Context(players = setOf(processor.player), entities = setOf(entity)) val event = PlayerPickItemFromEntityEvent(processor.player, entity, includeData, context) Events.dispatch(event) diff --git a/src/main/kotlin/io/github/dockyardmc/protocol/types/ConsumeEffect.kt b/src/main/kotlin/io/github/dockyardmc/protocol/types/ConsumeEffect.kt index 69df552bf..9ee41e8b1 100644 --- a/src/main/kotlin/io/github/dockyardmc/protocol/types/ConsumeEffect.kt +++ b/src/main/kotlin/io/github/dockyardmc/protocol/types/ConsumeEffect.kt @@ -3,7 +3,9 @@ package io.github.dockyardmc.protocol.types import io.github.dockyardmc.data.CRC32CHasher import io.github.dockyardmc.data.HashHolder import io.github.dockyardmc.data.StaticHash -import io.github.dockyardmc.extentions.* +import io.github.dockyardmc.extentions.read +import io.github.dockyardmc.extentions.readVarInt +import io.github.dockyardmc.extentions.writeVarInt import io.github.dockyardmc.protocol.DataComponentHashable import io.github.dockyardmc.protocol.NetworkReadable import io.github.dockyardmc.protocol.NetworkWritable @@ -51,7 +53,7 @@ interface ConsumeEffect : NetworkWritable, DataComponentHashable { override fun write(buffer: ByteBuf) { buffer.writeVarInt(ConsumeEffect.effects.getByValue(this::class)) - buffer.writeList(effects, ByteBuf::writeAppliedPotionEffect) + buffer.writeList(effects, AppliedPotionEffect::write) buffer.writeFloat(probability) } @@ -59,7 +61,7 @@ interface ConsumeEffect : NetworkWritable, DataComponentHashable { const val ID = 0 override fun read(buffer: ByteBuf): ApplyEffects { - return ApplyEffects(buffer.readList(ByteBuf::readAppliedPotionEffect), buffer.readFloat()) + return ApplyEffects(buffer.readList(AppliedPotionEffect::read), buffer.readFloat()) } } diff --git a/src/main/kotlin/io/github/dockyardmc/registry/PotionEffects.kt b/src/main/kotlin/io/github/dockyardmc/registry/PotionEffects.kt index d609f5c96..05136ad82 100644 --- a/src/main/kotlin/io/github/dockyardmc/registry/PotionEffects.kt +++ b/src/main/kotlin/io/github/dockyardmc/registry/PotionEffects.kt @@ -2,10 +2,13 @@ package io.github.dockyardmc.registry import io.github.dockyardmc.data.CRC32CHasher import io.github.dockyardmc.data.HashHolder +import io.github.dockyardmc.extentions.readRegistryEntry import io.github.dockyardmc.extentions.readVarInt -import io.github.dockyardmc.extentions.writeOptionalOLD import io.github.dockyardmc.extentions.writeVarInt import io.github.dockyardmc.protocol.DataComponentHashable +import io.github.dockyardmc.protocol.NetworkReadable +import io.github.dockyardmc.protocol.NetworkWritable +import io.github.dockyardmc.protocol.writeOptional import io.github.dockyardmc.registry.registries.PotionEffect import io.github.dockyardmc.registry.registries.PotionEffectRegistry import io.github.dockyardmc.scheduler.runnables.inWholeMinecraftTicks @@ -59,7 +62,18 @@ data class AppliedPotionEffect( var effect: PotionEffect, val settings: AppliedPotionEffectSettings, var startTime: Long? = null, -) : DataComponentHashable { +) : DataComponentHashable, NetworkWritable { + + override fun write(buffer: ByteBuf) { + effect.write(buffer) + settings.write(buffer) + } + + companion object : NetworkReadable { + override fun read(buffer: ByteBuf): AppliedPotionEffect { + return AppliedPotionEffect(buffer.readRegistryEntry(PotionEffectRegistry), AppliedPotionEffectSettings.read(buffer)) + } + } override fun hashStruct(): HashHolder { return CRC32CHasher.of { @@ -68,6 +82,7 @@ data class AppliedPotionEffect( //start field is only for server-side use } } + } data class AppliedPotionEffectSettings( @@ -111,8 +126,6 @@ data class AppliedPotionEffectSettings( buffer.writeBoolean(isAmbient) buffer.writeBoolean(showIcon) buffer.writeBoolean(showIcon) - buffer.writeOptionalOLD(hiddenEffect) { - write(it) - } + buffer.writeOptional(hiddenEffect, AppliedPotionEffectSettings::write) } } \ No newline at end of file diff --git a/src/main/kotlin/io/github/dockyardmc/schematics/Schematic.kt b/src/main/kotlin/io/github/dockyardmc/schematics/Schematic.kt index f5dfc150c..b99de588d 100644 --- a/src/main/kotlin/io/github/dockyardmc/schematics/Schematic.kt +++ b/src/main/kotlin/io/github/dockyardmc/schematics/Schematic.kt @@ -5,6 +5,7 @@ package io.github.dockyardmc.schematics import cz.lukynka.prettylog.LogType import cz.lukynka.prettylog.log import io.github.dockyardmc.extentions.readVarInt +import io.github.dockyardmc.extentions.reversed import io.github.dockyardmc.extentions.toByteBuf import io.github.dockyardmc.location.Location import io.github.dockyardmc.registry.Blocks @@ -13,17 +14,21 @@ import io.github.dockyardmc.maths.vectors.Vector3 import io.github.dockyardmc.world.chunk.Chunk import io.github.dockyardmc.world.World import io.github.dockyardmc.world.block.Block +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap +import it.unimi.dsi.fastutil.objects.ObjectArrayList +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import java.util.concurrent.CompletableFuture data class Schematic( var size: Vector3, var offset: Vector3, - var pallete: MutableMap, + var palette: Object2IntOpenHashMap, var blocks: ByteArray, ) { companion object { - val empty = Schematic(Vector3(), Vector3(), mutableMapOf(), ByteArray(0)) + val empty = Schematic(Vector3(), Vector3(), Object2IntOpenHashMap(), ByteArray(0)) + val RED_STAINED_GLASS = Blocks.RED_STAINED_GLASS.toBlock() } } @@ -38,11 +43,10 @@ fun World.placeSchematic( location: Location, ) { val blocks = schematic.blocks.toByteBuf() - val updateChunks = mutableSetOf() - val loadChunk = mutableSetOf>() - val batchBlockUpdate = mutableListOf>() - - val flippedPallet = schematic.pallete.entries.associateBy({ it.value }) { it.key } + val updateChunks = ObjectOpenHashSet() + val loadChunk = ObjectOpenHashSet>() + val batchBlockUpdate = ObjectArrayList>() + val flippedPallet = schematic.palette.reversed() for (y in 0 until schematic.size.y) { for (z in 0 until schematic.size.z) { @@ -50,7 +54,7 @@ fun World.placeSchematic( val placeLoc = Location(x, y, z, location.world).add(location) val id = blocks.readVarInt() - val block = flippedPallet[id] ?: Blocks.RED_STAINED_GLASS.toBlock() + val block = flippedPallet[id] ?: Schematic.RED_STAINED_GLASS val chunkX = ChunkUtils.getChunkCoordinate(placeLoc.x) val chunkZ = ChunkUtils.getChunkCoordinate(placeLoc.z) diff --git a/src/main/kotlin/io/github/dockyardmc/schematics/SchematicReader.kt b/src/main/kotlin/io/github/dockyardmc/schematics/SchematicReader.kt index 5c7c947e1..87847d001 100644 --- a/src/main/kotlin/io/github/dockyardmc/schematics/SchematicReader.kt +++ b/src/main/kotlin/io/github/dockyardmc/schematics/SchematicReader.kt @@ -3,6 +3,7 @@ package io.github.dockyardmc.schematics import io.github.dockyardmc.maths.vectors.Vector3 import io.github.dockyardmc.scroll.extensions.contains import io.github.dockyardmc.world.block.Block +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap import net.kyori.adventure.nbt.BinaryTagIO import net.kyori.adventure.nbt.CompoundBinaryTag import net.kyori.adventure.nbt.IntBinaryTag @@ -11,7 +12,7 @@ import java.io.File object SchematicReader { - val READER = BinaryTagIO.reader(Long.MAX_VALUE) + val READER = BinaryTagIO.unlimitedReader() fun read(file: File): Schematic { if (!file.exists()) throw Exception("File $file does not exist!") @@ -54,23 +55,20 @@ object SchematicReader { blockArray = nbt.getByteArray("BlockData") } - val blocks = mutableMapOf() + val blocks = Object2IntOpenHashMap() pallet.forEach { entry -> val id = (entry.value as IntBinaryTag).value() val block = Block.getBlockFromStateString(entry.key) blocks[block] = id } - val schematic = Schematic( size = Vector3(width, height, length), offset = offset, - pallete = blocks.toMutableMap(), + palette = blocks, blocks = blockArray.copyOf() ) -// if(ConfigManager.config.implementationConfig.cacheSchematics) { -// cache[getFileHash(file, "SHA-256")] = schematic -// } + return schematic } } \ No newline at end of file diff --git a/src/main/kotlin/io/github/dockyardmc/utils/ObjectPool.kt b/src/main/kotlin/io/github/dockyardmc/utils/ObjectPool.kt new file mode 100644 index 000000000..2c1c54073 --- /dev/null +++ b/src/main/kotlin/io/github/dockyardmc/utils/ObjectPool.kt @@ -0,0 +1,46 @@ +package io.github.dockyardmc.utils + +import org.jctools.queues.MessagePassingQueue +import org.jctools.queues.MpmcUnboundedXaddArrayQueue +import java.lang.ref.Cleaner +import java.lang.ref.SoftReference +import java.util.function.UnaryOperator + +class ObjectPool(val supplier: () -> T, val sanitizer: UnaryOperator = UnaryOperator.identity()) { + companion object { + const val QUEUE_SIZE = 32_768 + val CLEANER = Cleaner.create() + } + + val size: Int get() = pool.size() + private val pool: MessagePassingQueue> = MpmcUnboundedXaddArrayQueue(QUEUE_SIZE) + + fun get(): T { + var ref: SoftReference? + while (pool.poll().also { ref = it } != null) { + val result = ref?.get() + if (result != null) { + return result + } + } + return supplier.invoke() + } + + fun add(obj: T) { + this.pool.offer(SoftReference(sanitizer.apply(obj))) + } + + fun clear() { + this.pool.clear() + } + + fun register(ref: Any, obj: T) { + CLEANER.register(ref, BufferedCleaner(this, obj)) + } + + class BufferedCleaner(val pool: ObjectPool, val obj: T) : Runnable { + override fun run() { + this.pool.add(obj) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/dockyard/tests/network/VarIntTest.kt b/src/test/kotlin/io/github/dockyard/tests/network/VarIntTest.kt new file mode 100644 index 000000000..02ba3f73d --- /dev/null +++ b/src/test/kotlin/io/github/dockyard/tests/network/VarIntTest.kt @@ -0,0 +1,32 @@ +package io.github.dockyard.tests.network + +import io.github.dockyardmc.extentions.readVarInt +import io.github.dockyardmc.extentions.writeVarInt +import io.netty.buffer.Unpooled +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +class VarIntTest { + + @Test + fun testBoundaryValues() { + testValue(0) + testValue(127) + testValue(128) + testValue(16383) + testValue(16384) + testValue(2097151) + testValue(2097152) + testValue(268435455) + testValue(268435456) + testValue(Int.MAX_VALUE) + testValue(Int.MIN_VALUE) + } + + private fun testValue(value: Int) { + val buffer = Unpooled.buffer() + buffer.writeVarInt(value) + val read = buffer.readVarInt() + assertEquals(value, read) + } +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/dockyard/tests/schematic/SchematicTest.kt b/src/test/kotlin/io/github/dockyard/tests/schematic/SchematicTest.kt index 871d4e719..f1782348a 100644 --- a/src/test/kotlin/io/github/dockyard/tests/schematic/SchematicTest.kt +++ b/src/test/kotlin/io/github/dockyard/tests/schematic/SchematicTest.kt @@ -19,17 +19,14 @@ class SchematicTest { } @Test - fun testParsing() { + fun testSchematicReadingAndPlacing() { val world = WorldManager.mainWorld assertDoesNotThrow { val schematic = SchematicReader.read(Resources.getFile("test.schem").readBytes()) - world.placeSchematic(schematic, world.locationAt(0, 0, 0)) - - assertEquals(Blocks.RED_WOOL, world.locationAt(0, 0, 0).block.registryBlock) - assertEquals(Blocks.NETHER_BRICK_FENCE, world.locationAt(4, 1, 5).block.registryBlock) - assertEquals(Blocks.STONE, world.locationAt(27, 1, 23).block.registryBlock) - } + assertEquals(Blocks.RED_WOOL, world.locationAt(0, 0, 0).block.registryBlock) + assertEquals(Blocks.NETHER_BRICK_FENCE, world.locationAt(4, 1, 5).block.registryBlock) + assertEquals(Blocks.STONE, world.locationAt(27, 1, 23).block.registryBlock) } } \ No newline at end of file