forked from openrs2/openrs2
parent
f87d89fe7c
commit
6023569ce0
@ -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…
Reference in new issue