Enhanced Named Binary Tag implementation writen in Kotlin.
-
Support for Varied Data Types: Primitive and complex types.
-
Custom Type Creation: Easily create new types as you need.
-
Optimized Memory Utilization: Uses specialized types to decrease memory usage.
-
Compact Design: This is designed to be simple, so, is simple.
-
Multiple Compression: Various compressions formats. GZIP, ZIP, ZLIB, ZSTD, NONE.
-
Kotlin Serialization: Implementation of Kotlinx Serialization as NBT
- Interoperability: Ability to interoperate with this NBT implementation to Minecraft NBT
- SNBT Support: Support for SNBT (Stringified Named Binary Tag)
NBT is stored in jitpack repository, so all you need is here:
repositories {
maven("https://jitpack.io")
}
dependencies {
implementation("com.github.uinnn:nbt:1.0.1")
}
The Minecraft NBT is very nice for their use, perhaps some bad things, it fit in their purpose, but we can always made things bett 9751 er! Minecraft NBT overview:
- Empty
- Byte
- Short
- Int
- Long
- Float
- Double
- ByteArray
- String
- List
- Compound
- IntArray
- LongArray - Introduced in version 1.13+
In addition to these previous NBT provided by Minecraft, this implementation introduces 15 new types:
- Char: Simple Char.
- Boolean: Minecraft uses a Byte Tag to store boolean, in this implementation, we use a Byte too, but, with support to get the 8 bits of the byte, making possible to store 8 boolean in one Boolean Tag. Optimizing this way 8x more memory
- ArraySet: A Set implementation using an array.
- HashSet: A Set implementation using hashes
- ShortArray: Simple ShortArray.
- FloatArray: Simple FloatArray.
- DoubleArray: Simple DoubleArray.
- CharArray: Simple CharArray.
- BooleanArray: Default BooleanArray Type. Stores 1 boolean per byte
- PackedBooleanArray: Packed BooleanArray Type. Stores up to 8 boolean per byte, similar to BitSet
- UUID: Simple UUID.
- 24 Bits: An 24 bits (3 bytes) stored integer.
- 40 Bits: An 40 bits (5 bytes) stored long.
- 48 Bits: An 48 bits (6 bytes) stored long.
- 56 Bits: An 56 bits (7 bytes) stored long.
We use Kotlin inline classes, so no more extra memory allocation.
Arrays Type (ShortArray, IntArray, LongArray, etc...) is firstly buffered to ByteArray and them serialized, this enhances overall performance and makes the compression algorithms better.
We use FastUtil collection types, enhancing more the performance.
Simple overall usage
val compound = compound {
set("Name", "Test")
set("Version", "1.12")
set("Patch", 7)
// multiple booleans
set("Available", true, false, false)
set("Authors", tagListOf("uin", "other"))
}
// write
TagIO.writeStream(stream, compound)
TagIO.writeFile(file, compound)
// read
val loaded = TagIO.readStream(stream)
val loaded = TagIO.readFile(file)
val name = compound.string("Name")
val version = compound.string("Version", default = "1.0")
val authors = compound.stringList("Authors")
// getting multiple booleans
val available = compound.booleanTag("Available")
val availableForWindows = available[0]
val availableForLinux = available[1]
val availableForMac = available[2]
// usage with Kotlinx Serialization
@Serializable
data class A(val value: Int, val value2: String)
@Serializable
data class B(val a: A, val b: A)
val a = A(1, "a")
val b = A(2, "b")
val encoded = TagIO.encodeToBytes(B(a, b))
val decoded = TagIO.decodeFromBytes<B>(encoded)
println(decoded) // B(a=A(value=1, value2="a"), b=A(value=2, value2="b"))
// other encode functions
TagIO.encodeToStream(stream, a)
TagIO.encodeToFile(file, a)
In this example we gonna create a UUID NBT type!
// don't forget to set as inline class!
@JvmInline
value class UUIDTag(val value: UUID) : Tag {
// this allows we get any value in the tag if we don't know the type
override val genericValue get() = value
// the type of this tag
override val type get() = Type
// the write function is used to save tag value to data output
override fun write(data: DataOutput) {
data.writeLong(value.mostSignificantBits)
data.writeLong(value.leastSignificantBits)
}
// creates a copy of the tag, in some cases this can be useful
// but not for this example
override fun copy(): UUIDTag = this
override fun toString() = value.toString()
// we also need to create a type to loading them back.
object Type : TagType<UUIDTag>() {
override fun load(data: DataInput): UUIDTag {
return UUIDTag(UUID(data.readLong(), data.readLong()))
}
}
}
// registering the type
TagTypes.registerType(UUIDTag.Type)