mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2025-12-19 06:59:24 +00:00
improve pregen cache
This commit is contained in:
@@ -142,6 +142,7 @@ slimJar {
|
||||
relocate("com.google.inject", "$lib.guice")
|
||||
relocate("org.dom4j", "$lib.dom4j")
|
||||
relocate("org.jaxen", "$lib.jaxen")
|
||||
relocate("com.github.benmanes.caffeine", "$lib.caffeine")
|
||||
}
|
||||
|
||||
tasks {
|
||||
|
||||
@@ -24,9 +24,11 @@ public interface PregenCache {
|
||||
|
||||
void write();
|
||||
|
||||
void trim(long unloadDuration);
|
||||
|
||||
static PregenCache create(File directory) {
|
||||
if (directory == null) return EMPTY;
|
||||
return new PregenCacheImpl(directory);
|
||||
return new PregenCacheImpl(directory, 16);
|
||||
}
|
||||
|
||||
default PregenCache sync() {
|
||||
@@ -51,19 +53,16 @@ public interface PregenCache {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cacheChunk(int x, int z) {
|
||||
|
||||
}
|
||||
public void cacheChunk(int x, int z) {}
|
||||
|
||||
@Override
|
||||
public void cacheRegion(int x, int z) {
|
||||
|
||||
}
|
||||
public void cacheRegion(int x, int z) {}
|
||||
|
||||
@Override
|
||||
public void write() {
|
||||
public void write() {}
|
||||
|
||||
}
|
||||
@Override
|
||||
public void trim(long unloadDuration) {}
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,220 +0,0 @@
|
||||
package com.volmit.iris.core.pregenerator.cache;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import com.github.benmanes.caffeine.cache.RemovalCause;
|
||||
import com.github.benmanes.caffeine.cache.Scheduler;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.util.data.KCache;
|
||||
import com.volmit.iris.util.data.Varint;
|
||||
import com.volmit.iris.util.documentation.ChunkCoordinates;
|
||||
import com.volmit.iris.util.documentation.RegionCoordinates;
|
||||
import com.volmit.iris.util.io.IO;
|
||||
import com.volmit.iris.util.parallel.HyperLock;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.jpountz.lz4.LZ4BlockInputStream;
|
||||
import net.jpountz.lz4.LZ4BlockOutputStream;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
class PregenCacheImpl implements PregenCache {
|
||||
private static final int SIZE = 32;
|
||||
private final File directory;
|
||||
private final HyperLock hyperLock = new HyperLock(SIZE * 2, true);
|
||||
private final LoadingCache<Pos, Plate> cache = Caffeine.newBuilder()
|
||||
.expireAfterAccess(10, TimeUnit.SECONDS)
|
||||
.executor(KCache.EXECUTOR)
|
||||
.scheduler(Scheduler.systemScheduler())
|
||||
.maximumSize(SIZE)
|
||||
.removalListener(this::onRemoval)
|
||||
.evictionListener(this::onRemoval)
|
||||
.build(this::load);
|
||||
|
||||
@ChunkCoordinates
|
||||
public boolean isChunkCached(int x, int z) {
|
||||
var plate = cache.get(new Pos(x >> 10, z >> 10));
|
||||
if (plate == null) return false;
|
||||
return plate.isCached((x >> 5) & 31, (z >> 5) & 31, r -> r.isCached(x & 31, z & 31));
|
||||
}
|
||||
|
||||
@RegionCoordinates
|
||||
public boolean isRegionCached(int x, int z) {
|
||||
var plate = cache.get(new Pos(x >> 5, z >> 5));
|
||||
if (plate == null) return false;
|
||||
return plate.isCached(x & 31, z & 31, Region::isCached);
|
||||
}
|
||||
|
||||
@ChunkCoordinates
|
||||
public void cacheChunk(int x, int z) {
|
||||
var plate = cache.get(new Pos(x >> 10, z >> 10));
|
||||
plate.cache((x >> 5) & 31, (z >> 5) & 31, r -> r.cache(x & 31, z & 31));
|
||||
}
|
||||
|
||||
@RegionCoordinates
|
||||
public void cacheRegion(int x, int z) {
|
||||
var plate = cache.get(new Pos(x >> 5, z >> 5));
|
||||
plate.cache(x & 31, z & 31, Region::cache);
|
||||
}
|
||||
|
||||
public void write() {
|
||||
cache.asMap().values().forEach(this::write);
|
||||
}
|
||||
|
||||
private Plate load(Pos key) {
|
||||
hyperLock.lock(key.x, key.z);
|
||||
try {
|
||||
File file = fileForPlate(key);
|
||||
if (!file.exists()) return new Plate(key);
|
||||
try (var in = new DataInputStream(new LZ4BlockInputStream(new FileInputStream(file)))) {
|
||||
return new Plate(key, in);
|
||||
} catch (IOException e){
|
||||
Iris.error("Failed to read pregen cache " + file);
|
||||
Iris.reportError(e);
|
||||
e.printStackTrace();
|
||||
return new Plate(key);
|
||||
}
|
||||
} finally {
|
||||
hyperLock.unlock(key.x, key.z);
|
||||
}
|
||||
}
|
||||
|
||||
private void write(Plate plate) {
|
||||
hyperLock.lock(plate.pos.x, plate.pos.z);
|
||||
try {
|
||||
File file = fileForPlate(plate.pos);
|
||||
try {
|
||||
IO.write(file, out -> new DataOutputStream(new LZ4BlockOutputStream(out)), plate::write);
|
||||
} catch (IOException e) {
|
||||
Iris.error("Failed to write pregen cache " + file);
|
||||
Iris.reportError(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
} finally {
|
||||
hyperLock.unlock(plate.pos.x, plate.pos.z);
|
||||
}
|
||||
}
|
||||
|
||||
private void onRemoval(@Nullable Pos key, @Nullable Plate plate, RemovalCause cause) {
|
||||
if (plate == null) return;
|
||||
write(plate);
|
||||
}
|
||||
|
||||
private File fileForPlate(Pos pos) {
|
||||
if (!directory.exists() && !directory.mkdirs())
|
||||
throw new IllegalStateException("Cannot create directory: " + directory.getAbsolutePath());
|
||||
return new File(directory, "c." + pos.x + "." + pos.z + ".lz4b");
|
||||
}
|
||||
|
||||
private static class Plate {
|
||||
private final Pos pos;
|
||||
private short count;
|
||||
private Region[] regions;
|
||||
|
||||
public Plate(Pos pos) {
|
||||
this.pos = pos;
|
||||
count = 0;
|
||||
regions = new Region[1024];
|
||||
}
|
||||
|
||||
public Plate(Pos pos, DataInput in) throws IOException {
|
||||
this.pos = pos;
|
||||
count = (short) Varint.readSignedVarInt(in);
|
||||
if (count == 1024) return;
|
||||
regions = new Region[1024];
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
if (in.readBoolean()) continue;
|
||||
regions[i] = new Region(in);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCached(int x, int z, Predicate<Region> predicate) {
|
||||
if (count == 1024) return true;
|
||||
Region region = regions[x * 32 + z];
|
||||
if (region == null) return false;
|
||||
return predicate.test(region);
|
||||
}
|
||||
|
||||
public void cache(int x, int z, Predicate<Region> predicate) {
|
||||
if (count == 1024) return;
|
||||
Region region = regions[x * 32 + z];
|
||||
if (region == null) regions[x * 32 + z] = region = new Region();
|
||||
if (predicate.test(region)) count++;
|
||||
}
|
||||
|
||||
public void write(DataOutput out) throws IOException {
|
||||
Varint.writeSignedVarInt(count, out);
|
||||
if (count == 1024) return;
|
||||
for (Region region : regions) {
|
||||
out.writeBoolean(region == null);
|
||||
if (region == null) continue;
|
||||
region.write(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Region {
|
||||
private short count;
|
||||
private long[] words;
|
||||
|
||||
public Region() {
|
||||
count = 0;
|
||||
words = new long[64];
|
||||
}
|
||||
|
||||
public Region(DataInput in) throws IOException {
|
||||
count = (short) Varint.readSignedVarInt(in);
|
||||
if (count == 1024) return;
|
||||
words = new long[64];
|
||||
for (int i = 0; i < 64; i++) {
|
||||
words[i] = Varint.readUnsignedVarLong(in);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean cache() {
|
||||
if (count == 1024) return false;
|
||||
count = 1024;
|
||||
words = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean cache(int x, int z) {
|
||||
if (count == 1024) return false;
|
||||
|
||||
int i = x * 32 + z;
|
||||
int w = i >> 6;
|
||||
long b = 1L << (i & 63);
|
||||
|
||||
var cur = (words[w] & b) != 0;
|
||||
if (cur) return false;
|
||||
|
||||
if (++count == 1024) {
|
||||
words = null;
|
||||
return true;
|
||||
} else words[w] |= b;
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isCached() {
|
||||
return count == 1024;
|
||||
}
|
||||
|
||||
public boolean isCached(int x, int z) {
|
||||
int i = x * 32 + z;
|
||||
return count == 1024 || (words[i >> 6] & 1L << (i & 63)) != 0;
|
||||
}
|
||||
|
||||
public void write(DataOutput out) throws IOException {
|
||||
Varint.writeSignedVarInt(count, out);
|
||||
if (isCached()) return;
|
||||
for (long word : words) {
|
||||
Varint.writeUnsignedVarLong(word, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record Pos(int x, int z) {}
|
||||
}
|
||||
@@ -1,11 +1,6 @@
|
||||
package com.volmit.iris.core.pregenerator.cache;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
class SynchronizedCache implements PregenCache {
|
||||
private final PregenCache cache;
|
||||
|
||||
record SynchronizedCache(PregenCache cache) implements PregenCache {
|
||||
@Override
|
||||
public boolean isThreadSafe() {
|
||||
return true;
|
||||
@@ -45,4 +40,11 @@ class SynchronizedCache implements PregenCache {
|
||||
cache.write();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trim(long unloadDuration) {
|
||||
synchronized (cache) {
|
||||
cache.trim(unloadDuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
package com.volmit.iris.core.service;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.Scheduler;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.core.pregenerator.cache.PregenCache;
|
||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.data.KCache;
|
||||
import com.volmit.iris.util.plugin.IrisService;
|
||||
import com.volmit.iris.util.scheduling.Looper;
|
||||
import lombok.NonNull;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
@@ -19,21 +17,33 @@ import org.bukkit.event.world.WorldUnloadEvent;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class GlobalCacheSVC implements IrisService {
|
||||
private static final Cache<String, PregenCache> REFERENCE_CACHE = Caffeine.newBuilder()
|
||||
.executor(KCache.EXECUTOR)
|
||||
.scheduler(Scheduler.systemScheduler())
|
||||
.weakValues()
|
||||
.build();
|
||||
private static final KMap<String, Reference<PregenCache>> REFERENCE_CACHE = new KMap<>();
|
||||
private final KMap<String, PregenCache> globalCache = new KMap<>();
|
||||
private transient boolean lastState;
|
||||
private static boolean disabled = true;
|
||||
private Looper trimmer;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
disabled = false;
|
||||
trimmer = new Looper() {
|
||||
@Override
|
||||
protected long loop() {
|
||||
var it = REFERENCE_CACHE.values().iterator();
|
||||
while (it.hasNext()) {
|
||||
var cache = it.next().get();
|
||||
if (cache == null) it.remove();
|
||||
else cache.trim(10_000);
|
||||
}
|
||||
return disabled ? -1 : 2_000;
|
||||
}
|
||||
};
|
||||
trimmer.start();
|
||||
lastState = !IrisSettings.get().getWorld().isGlobalPregenCache();
|
||||
if (lastState) return;
|
||||
Bukkit.getWorlds().forEach(this::createCache);
|
||||
@@ -42,6 +52,9 @@ public class GlobalCacheSVC implements IrisService {
|
||||
@Override
|
||||
public void onDisable() {
|
||||
disabled = true;
|
||||
try {
|
||||
trimmer.join();
|
||||
} catch (InterruptedException ignored) {}
|
||||
globalCache.qclear((world, cache) -> cache.write());
|
||||
}
|
||||
|
||||
@@ -76,6 +89,7 @@ public class GlobalCacheSVC implements IrisService {
|
||||
}
|
||||
|
||||
private void createCache(World world) {
|
||||
if (!IrisToolbelt.isIrisWorld(world)) return;
|
||||
globalCache.computeIfAbsent(world.getName(), GlobalCacheSVC::createDefault);
|
||||
}
|
||||
|
||||
@@ -99,7 +113,15 @@ public class GlobalCacheSVC implements IrisService {
|
||||
|
||||
@NonNull
|
||||
public static PregenCache createCache(@NonNull String worldName, @NonNull Function<String, PregenCache> provider) {
|
||||
return REFERENCE_CACHE.get(worldName, provider);
|
||||
PregenCache[] holder = new PregenCache[1];
|
||||
REFERENCE_CACHE.compute(worldName, (name, ref) -> {
|
||||
if (ref != null) {
|
||||
if ((holder[0] = ref.get()) != null)
|
||||
return ref;
|
||||
}
|
||||
return new WeakReference<>(holder[0] = provider.apply(worldName));
|
||||
});
|
||||
return holder[0];
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
||||
232
core/src/main/kotlin/com/volmit/iris/core/pregenerator/cache/PregenCacheImpl.kt
vendored
Normal file
232
core/src/main/kotlin/com/volmit/iris/core/pregenerator/cache/PregenCacheImpl.kt
vendored
Normal file
@@ -0,0 +1,232 @@
|
||||
package com.volmit.iris.core.pregenerator.cache
|
||||
|
||||
import com.volmit.iris.Iris
|
||||
import com.volmit.iris.util.data.Varint
|
||||
import com.volmit.iris.util.documentation.ChunkCoordinates
|
||||
import com.volmit.iris.util.documentation.RegionCoordinates
|
||||
import com.volmit.iris.util.io.IO
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.jpountz.lz4.LZ4BlockInputStream
|
||||
import net.jpountz.lz4.LZ4BlockOutputStream
|
||||
import java.io.*
|
||||
|
||||
class PregenCacheImpl(
|
||||
private val directory: File,
|
||||
private val maxSize: Int
|
||||
) : PregenCache {
|
||||
private val cache = Object2ObjectLinkedOpenHashMap<Pair<Int, Int>, Plate>()
|
||||
|
||||
@ChunkCoordinates
|
||||
override fun isChunkCached(x: Int, z: Int): Boolean {
|
||||
return this[x shr 10, z shr 10].isCached(
|
||||
(x shr 5) and 31,
|
||||
(z shr 5) and 31
|
||||
) { isCached(x and 31, z and 31) }
|
||||
}
|
||||
|
||||
@RegionCoordinates
|
||||
override fun isRegionCached(x: Int, z: Int): Boolean {
|
||||
return this[x shr 5, z shr 5].isCached(
|
||||
x and 31,
|
||||
z and 31,
|
||||
Region::isCached
|
||||
)
|
||||
}
|
||||
|
||||
@ChunkCoordinates
|
||||
override fun cacheChunk(x: Int, z: Int) {
|
||||
this[x shr 10, z shr 10].cache(
|
||||
(x shr 5) and 31,
|
||||
(z shr 5) and 31
|
||||
) { cache(x and 31, z and 31) }
|
||||
}
|
||||
|
||||
@RegionCoordinates
|
||||
override fun cacheRegion(x: Int, z: Int) {
|
||||
this[x shr 5, z shr 5].cache(
|
||||
x and 31,
|
||||
z and 31,
|
||||
Region::cache
|
||||
)
|
||||
}
|
||||
|
||||
override fun write() {
|
||||
if (cache.isEmpty()) return
|
||||
runBlocking {
|
||||
for (plate in cache.values) {
|
||||
if (!plate.dirty) continue
|
||||
launch(dispatcher) {
|
||||
writePlate(plate)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun trim(unloadDuration: Long) {
|
||||
if (cache.isEmpty()) return
|
||||
val threshold = System.currentTimeMillis() - unloadDuration
|
||||
runBlocking {
|
||||
val it = cache.values.iterator()
|
||||
while (it.hasNext()) {
|
||||
val plate = it.next()
|
||||
if (plate.lastAccess < threshold) it.remove()
|
||||
launch(dispatcher) {
|
||||
writePlate(plate)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private operator fun get(x: Int, z: Int): Plate {
|
||||
val key = x to z
|
||||
val plate = cache.getAndMoveToFirst(key)
|
||||
if (plate != null) return plate
|
||||
return readPlate(x, z).also {
|
||||
cache.putAndMoveToFirst(key, it)
|
||||
runBlocking {
|
||||
while (cache.size > maxSize) {
|
||||
val plate = cache.removeLast()
|
||||
launch(dispatcher) {
|
||||
writePlate(plate)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun readPlate(x: Int, z: Int): Plate {
|
||||
val file = fileForPlate(x, z)
|
||||
if (!file.exists()) return Plate(x, z)
|
||||
try {
|
||||
DataInputStream(LZ4BlockInputStream(file.inputStream())).use {
|
||||
return readPlate(x, z, it)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Iris.error("Failed to read pregen cache $file")
|
||||
e.printStackTrace()
|
||||
Iris.reportError(e)
|
||||
}
|
||||
return Plate(x, z)
|
||||
}
|
||||
|
||||
private fun writePlate(plate: Plate) {
|
||||
if (!plate.dirty) return
|
||||
val file = fileForPlate(plate.x, plate.z)
|
||||
try {
|
||||
IO.write(file, { DataOutputStream(LZ4BlockOutputStream(it)) }, plate::write)
|
||||
plate.dirty = false
|
||||
} catch (e: IOException) {
|
||||
Iris.error("Failed to write preen cache $file")
|
||||
e.printStackTrace()
|
||||
Iris.reportError(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun fileForPlate(x: Int, z: Int): File {
|
||||
check(!(!directory.exists() && !directory.mkdirs())) { "Cannot create directory: " + directory.absolutePath }
|
||||
return File(directory, "c.$x.$z.lz4b")
|
||||
}
|
||||
|
||||
private class Plate(
|
||||
val x: Int,
|
||||
val z: Int,
|
||||
private var count: Short = 0,
|
||||
private var regions: Array<Region?>? = arrayOfNulls(1024)
|
||||
) {
|
||||
var dirty: Boolean = false
|
||||
var lastAccess: Long = System.currentTimeMillis()
|
||||
|
||||
fun cache(x: Int, z: Int, predicate: Region.() -> Boolean): Boolean {
|
||||
lastAccess = System.currentTimeMillis()
|
||||
if (count == SIZE) return false
|
||||
val region = regions!!.run { this[x * 32 + z] ?: Region().also { this[x * 32 + z] = it } }
|
||||
if (!region.predicate()) return false
|
||||
if (++count == SIZE) regions = null
|
||||
dirty = true
|
||||
return true
|
||||
}
|
||||
|
||||
fun isCached(x: Int, z: Int, predicate: Region.() -> Boolean): Boolean {
|
||||
lastAccess = System.currentTimeMillis()
|
||||
if (count == SIZE) return true
|
||||
val region = regions!![x * 32 + z] ?: return false
|
||||
return region.predicate()
|
||||
}
|
||||
|
||||
fun write(dos: DataOutput) {
|
||||
Varint.writeSignedVarInt(count.toInt(), dos)
|
||||
regions?.forEach {
|
||||
dos.writeBoolean(it == null)
|
||||
it?.write(dos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Region(
|
||||
private var count: Short = 0,
|
||||
private var words: LongArray? = LongArray(64)
|
||||
) {
|
||||
fun cache(): Boolean {
|
||||
if (count == SIZE) return false
|
||||
count = SIZE
|
||||
words = null
|
||||
return true
|
||||
}
|
||||
|
||||
fun cache(x: Int, z: Int): Boolean {
|
||||
if (count == SIZE) return false
|
||||
val words = words ?: return false
|
||||
val i = x * 32 + z
|
||||
val w = i shr 6
|
||||
val b = 1L shl (i and 63)
|
||||
|
||||
val cur = (words[w] and b) != 0L
|
||||
if (cur) return false
|
||||
|
||||
if (++count == SIZE) {
|
||||
this.words = null
|
||||
return true
|
||||
} else {
|
||||
words[w] = words[w] or b
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
fun isCached(): Boolean = count == SIZE
|
||||
fun isCached(x: Int, z: Int): Boolean {
|
||||
val i = x * 32 + z
|
||||
return count == SIZE || (words!![i shr 6] and (1L shl (i and 63))) != 0L
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun write(dos: DataOutput) {
|
||||
Varint.writeSignedVarInt(count.toInt(), dos)
|
||||
words?.forEach { Varint.writeUnsignedVarLong(it, dos) }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val dispatcher = Dispatchers.IO.limitedParallelism(4)
|
||||
private const val SIZE: Short = 1024
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun readPlate(x: Int, z: Int, din: DataInput): Plate {
|
||||
val count = Varint.readSignedVarInt(din)
|
||||
if (count == 1024) return Plate(x, z, SIZE, null)
|
||||
return Plate(x, z, count.toShort(), Array(1024) {
|
||||
if (din.readBoolean()) null
|
||||
else readRegion(din)
|
||||
})
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun readRegion(din: DataInput): Region {
|
||||
val count = Varint.readSignedVarInt(din)
|
||||
return if (count == 1024) Region(SIZE, null)
|
||||
else Region(count.toShort(), LongArray(64) { Varint.readUnsignedVarLong(din) })
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user