Add code for packing and unpacking groups

Signed-off-by: Graham <gpe@openrs2.org>
pull/132/head
Graham 3 years ago
parent f87d89fe7c
commit 6023569ce0
  1. 99
      cache/src/main/kotlin/org/openrs2/cache/Group.kt
  2. 180
      cache/src/test/kotlin/org/openrs2/cache/GroupTest.kt

@ -0,0 +1,99 @@
package org.openrs2.cache
import io.netty.buffer.ByteBuf
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap
import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMaps
import org.openrs2.buffer.use
public object Group {
public fun unpack(input: ByteBuf, group: Js5Index.Group): Int2ObjectSortedMap<ByteBuf> {
require(group.size >= 1)
val singleEntry = group.singleOrNull()
if (singleEntry != null) {
return Int2ObjectSortedMaps.singleton(singleEntry.id, input.retain())
}
require(input.isReadable)
val stripes = input.getUnsignedByte(input.writerIndex() - 1)
var dataIndex = input.readerIndex()
val trailerIndex = input.writerIndex() - (stripes * group.size * 4) - 1
require(trailerIndex >= dataIndex)
input.readerIndex(trailerIndex)
val lens = IntArray(group.size)
for (i in 0 until stripes) {
var prevLen = 0
for (j in lens.indices) {
prevLen += input.readInt()
lens[j] += prevLen
}
}
input.readerIndex(trailerIndex)
val files = Int2ObjectAVLTreeMap<ByteBuf>()
try {
for ((i, entry) in group.withIndex()) {
files[entry.id] = input.alloc().buffer(lens[i], lens[i])
}
for (i in 0 until stripes) {
var prevLen = 0
for (entry in group) {
prevLen += input.readInt()
input.getBytes(dataIndex, files[entry.id], prevLen)
dataIndex += prevLen
}
}
check(dataIndex == trailerIndex)
// consume stripes byte too
input.skipBytes(1)
check(!input.isReadable)
files.values.forEach(ByteBuf::retain)
return files
} finally {
files.values.forEach(ByteBuf::release)
}
}
// TODO(gpe): support multiple stripes (tricky, as the best sizes are
// probably specific to the format we're packing...)
public fun pack(files: Int2ObjectSortedMap<ByteBuf>): ByteBuf {
require(files.isNotEmpty())
val first = files.values.first()
if (files.size == 1) {
return first
}
first.alloc().buffer().use { output ->
if (files.values.all { !it.isReadable }) {
output.writeByte(0)
return output.retain()
}
for (file in files.values) {
output.writeBytes(file, file.readerIndex(), file.readableBytes())
}
var prevLen = 0
for (file in files.values) {
val len = file.readableBytes()
output.writeInt(len - prevLen)
prevLen = len
}
output.writeByte(1)
return output.retain()
}
}
}

@ -0,0 +1,180 @@
package org.openrs2.cache
import io.netty.buffer.ByteBuf
import io.netty.buffer.Unpooled
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMaps
import org.openrs2.buffer.use
import org.openrs2.buffer.wrappedBuffer
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
object GroupTest {
private val index = Js5Index(Js5Protocol.ORIGINAL)
private val zeroFiles = index.createOrGet(0)
private val oneFile = index.createOrGet(1).apply {
createOrGet(1)
}
private val multipleFiles = index.createOrGet(2).apply {
createOrGet(0)
createOrGet(1)
createOrGet(3)
}
@Test
fun testPackEmpty() {
assertFailsWith<IllegalArgumentException> {
Group.pack(Int2ObjectSortedMaps.emptyMap()).release()
}
}
@Test
fun testUnpackEmpty() {
assertFailsWith<IllegalArgumentException> {
val files = Group.unpack(Unpooled.EMPTY_BUFFER, zeroFiles)
files.values.forEach(ByteBuf::release)
}
assertFailsWith<IllegalArgumentException> {
val files = Group.unpack(Unpooled.EMPTY_BUFFER, multipleFiles)
files.values.forEach(ByteBuf::release)
}
}
@Test
fun testPackSingle() {
wrappedBuffer(0, 1, 2, 3).use { buf ->
assertEquals(buf, Group.pack(Int2ObjectSortedMaps.singleton(1, buf)))
}
}
@Test
fun testUnpackSingle() {
wrappedBuffer(0, 1, 2, 3).use { buf ->
val expected = Int2ObjectSortedMaps.singleton(1, buf)
val actual = Group.unpack(buf.slice(), oneFile)
try {
assertEquals(expected, actual)
} finally {
actual.values.forEach(ByteBuf::release)
}
}
}
@Test
fun testPackZeroStripes() {
val files = Int2ObjectAVLTreeMap<ByteBuf>()
files[0] = Unpooled.EMPTY_BUFFER
files[1] = Unpooled.EMPTY_BUFFER
files[3] = Unpooled.EMPTY_BUFFER
wrappedBuffer(0).use { expected ->
Group.pack(files).use { actual ->
assertEquals(expected, actual)
}
}
}
@Test
fun testUnpackZeroStripes() {
val expected = Int2ObjectAVLTreeMap<ByteBuf>()
expected[0] = Unpooled.EMPTY_BUFFER
expected[1] = Unpooled.EMPTY_BUFFER
expected[3] = Unpooled.EMPTY_BUFFER
wrappedBuffer(0).use { buf ->
val actual = Group.unpack(buf, multipleFiles)
try {
assertEquals(expected, actual)
} finally {
actual.values.forEach(ByteBuf::release)
}
}
}
@Test
fun testPackOneStripe() {
val files = Int2ObjectAVLTreeMap<ByteBuf>()
try {
files[0] = wrappedBuffer(0, 1, 2)
files[1] = wrappedBuffer(3, 4, 5, 6, 7)
files[3] = wrappedBuffer(8, 9)
wrappedBuffer(
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
0, 0, 0, 3,
0, 0, 0, 2,
0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFD.toByte(),
1
).use { expected ->
Group.pack(files).use { actual ->
assertEquals(expected, actual)
}
}
} finally {
files.values.forEach(ByteBuf::release)
}
}
@Test
fun testUnpackOneStripe() {
val expected = Int2ObjectAVLTreeMap<ByteBuf>()
try {
expected[0] = wrappedBuffer(0, 1, 2)
expected[1] = wrappedBuffer(3, 4, 5, 6, 7)
expected[3] = wrappedBuffer(8, 9)
wrappedBuffer(
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
0, 0, 0, 3,
0, 0, 0, 2,
0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFD.toByte(),
1
).use { buf ->
val actual = Group.unpack(buf, multipleFiles)
try {
assertEquals(expected, actual)
} finally {
actual.values.forEach(ByteBuf::release)
}
}
} finally {
expected.values.forEach(ByteBuf::release)
}
}
@Test
fun testUnpackMultipleStripes() {
val expected = Int2ObjectAVLTreeMap<ByteBuf>()
try {
expected[0] = wrappedBuffer(0, 1, 2)
expected[1] = wrappedBuffer(3, 4, 5, 6, 7)
expected[3] = wrappedBuffer(8, 9)
wrappedBuffer(
0, 1,
3, 4,
8, 9,
2,
5, 6, 7,
0, 0, 0, 2,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 1,
0, 0, 0, 2,
0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFD.toByte(),
2
).use { buf ->
val actual = Group.unpack(buf, multipleFiles)
try {
assertEquals(expected, actual)
} finally {
actual.values.forEach(ByteBuf::release)
}
}
} finally {
expected.values.forEach(ByteBuf::release)
}
}
}
Loading…
Cancel
Save