Skip to content

Commit

Permalink
Convert to latest chunk format on send.
Browse files Browse the repository at this point in the history
This fixes some of the major lag that has been occurring since 1.13. Legacy chunk conversion on the client has been so slow that some people can't even join.
  • Loading branch information
SupremeMortal committed Apr 5, 2020
1 parent 0ae6d9b commit 1acd6a5
Show file tree
Hide file tree
Showing 10 changed files with 391 additions and 72 deletions.
1 change: 0 additions & 1 deletion src/main/java/cn/nukkit/level/format/anvil/Anvil.java
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,6 @@ public AsyncTask requestChunkTask(int x, int z) throws ChunkException {
}
// stream.putByte((byte) count); count is now sent in packet
for (int i = 0; i < count; i++) {
stream.putByte((byte) 0);
stream.put(sections[i].getBytes());
}
// for (byte height : chunk.getHeightMapArray()) {
Expand Down
18 changes: 7 additions & 11 deletions src/main/java/cn/nukkit/level/format/anvil/ChunkSection.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@
import cn.nukkit.level.format.anvil.util.NibbleArray;
import cn.nukkit.level.format.generic.EmptyChunkSection;
import cn.nukkit.nbt.tag.CompoundTag;
import cn.nukkit.utils.Binary;
import cn.nukkit.utils.ThreadCache;
import cn.nukkit.utils.Utils;
import cn.nukkit.utils.Zlib;
import cn.nukkit.utils.*;

import java.io.IOException;
import java.util.Arrays;
import java.util.function.IntConsumer;

/**
* author: MagicDroidX
Expand Down Expand Up @@ -319,14 +317,12 @@ private byte[] toXZY(char[] raw) {
@Override
public byte[] getBytes() {
synchronized (storage) {
BinaryStream stream = new BinaryStream();

byte[] ids = storage.getBlockIds();
byte[] data = storage.getBlockData();
byte[] merged = new byte[ids.length + data.length];

System.arraycopy(ids, 0, merged, 0, ids.length);
System.arraycopy(data, 0, merged, ids.length, data.length);
return merged;
stream.putByte((byte) 8); // Paletted chunk because Mojang messed up the old one
stream.putByte((byte) 1);
this.storage.writeTo(stream);
return stream.getBuffer();
}
}

Expand Down
11 changes: 11 additions & 0 deletions src/main/java/cn/nukkit/level/format/anvil/util/BlockStorage.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package cn.nukkit.level.format.anvil.util;

import cn.nukkit.level.GlobalBlockPalette;
import cn.nukkit.level.util.PalettedBlockStorage;
import cn.nukkit.utils.BinaryStream;
import com.google.common.base.Preconditions;

import java.util.Arrays;
Expand Down Expand Up @@ -91,6 +94,14 @@ public byte[] getBlockData() {
return blockData.getData();
}

public void writeTo(BinaryStream stream) {
PalettedBlockStorage storage = new PalettedBlockStorage();
for (int i = 0; i < SECTION_SIZE; i++) {
storage.setBlock(i, GlobalBlockPalette.getOrCreateRuntimeId(blockIds[i] & 0xff, blockData.get(i)));
}
storage.writeTo(stream);
}

public BlockStorage copy() {
return new BlockStorage(blockIds.clone(), blockData.copy());
}
Expand Down
48 changes: 19 additions & 29 deletions src/main/java/cn/nukkit/level/format/leveldb/LevelDB.java
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,24 @@ public AsyncTask requestChunkTask(int x, int z) {

long timestamp = chunk.getChanges();

byte[] tiles = new byte[0];
BinaryStream stream = new BinaryStream();
stream.putByte((byte) 0); // subchunk version

stream.put(chunk.getBlockIdArray());
stream.put(chunk.getBlockDataArray());
stream.put(chunk.getBlockSkyLightArray());
stream.put(chunk.getBlockLightArray());
stream.put(chunk.getHeightMapArray());
stream.put(chunk.getBiomeIdArray());

Map<Integer, Integer> extra = chunk.getBlockExtraDataArray();
stream.putLInt(extra.size());
if (!extra.isEmpty()) {
for (Integer key : extra.values()) {
stream.putLInt(key);
stream.putLShort(extra.get(key));
}
}

if (!chunk.getBlockEntities().isEmpty()) {
List<CompoundTag> tagList = new ArrayList<>();
Expand All @@ -182,39 +199,12 @@ public AsyncTask requestChunkTask(int x, int z) {
}

try {
tiles = NBTIO.write(tagList, ByteOrder.LITTLE_ENDIAN);
stream.put(NBTIO.write(tagList, ByteOrder.LITTLE_ENDIAN));
} catch (IOException e) {
throw new RuntimeException(e);
}
}

Map<Integer, Integer> extra = chunk.getBlockExtraDataArray();
BinaryStream extraData;
if (!extra.isEmpty()) {
extraData = new BinaryStream();
extraData.putLInt(extra.size());
for (Integer key : extra.values()) {
extraData.putLInt(key);
extraData.putLShort(extra.get(key));
}
} else {
extraData = null;
}

BinaryStream stream = new BinaryStream();
stream.put(chunk.getBlockIdArray());
stream.put(chunk.getBlockDataArray());
stream.put(chunk.getBlockSkyLightArray());
stream.put(chunk.getBlockLightArray());
stream.put(chunk.getHeightMapArray());
stream.put(chunk.getBiomeIdArray());
if (extraData != null) {
stream.put(extraData.getBuffer());
} else {
stream.putLInt(0);
}
stream.put(tiles);

this.getLevel().chunkRequestCallback(timestamp, x, z, 16, stream.getBuffer());

return null;
Expand Down
50 changes: 19 additions & 31 deletions src/main/java/cn/nukkit/level/format/mcregion/McRegion.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,24 @@ public AsyncTask requestChunkTask(int x, int z) throws ChunkException {

long timestamp = chunk.getChanges();

byte[] tiles = new byte[0];
BinaryStream stream = new BinaryStream();
stream.putByte((byte) 0); // subchunk version

stream.put(chunk.getBlockIdArray());
stream.put(chunk.getBlockDataArray());
stream.put(chunk.getBlockSkyLightArray());
stream.put(chunk.getBlockLightArray());
stream.put(chunk.getHeightMapArray());
stream.put(chunk.getBiomeIdArray());

Map<Integer, Integer> extra = chunk.getBlockExtraDataArray();
stream.putLInt(extra.size());
if (!extra.isEmpty()) {
for (Integer key : extra.values()) {
stream.putLInt(key);
stream.putLShort(extra.get(key));
}
}

if (!chunk.getBlockEntities().isEmpty()) {
List<CompoundTag> tagList = new ArrayList<>();
Expand All @@ -117,41 +134,12 @@ public AsyncTask requestChunkTask(int x, int z) throws ChunkException {
}

try {
tiles = NBTIO.write(tagList, ByteOrder.LITTLE_ENDIAN, true);
stream.put(NBTIO.write(tagList, ByteOrder.LITTLE_ENDIAN));
} catch (IOException e) {
throw new RuntimeException(e);
}
}

Map<Integer, Integer> extra = chunk.getBlockExtraDataArray();
BinaryStream extraData;
if (!extra.isEmpty()) {
extraData = new BinaryStream();
extraData.putLInt(extra.size());
for (Map.Entry<Integer, Integer> entry : extra.entrySet()) {
extraData.putLInt(entry.getKey());
extraData.putLShort(entry.getValue());
}
} else {
extraData = null;
}

BinaryStream stream = new BinaryStream();
stream.put(chunk.getBlockIdArray());
stream.put(chunk.getBlockDataArray());
stream.put(chunk.getBlockSkyLightArray());
stream.put(chunk.getBlockLightArray());
stream.put(chunk.getHeightMapArray());
stream.put(chunk.getBiomeIdArray());
if (extraData != null) {
stream.put(extraData.getBuffer());
} else {
stream.putLInt(0);
}
stream.put(tiles);

this.getLevel().chunkRequestCallback(timestamp, x, z, 16, stream.getBuffer());

return null;
}

Expand Down
16 changes: 16 additions & 0 deletions src/main/java/cn/nukkit/level/util/BitArray.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package cn.nukkit.level.util;

public interface BitArray {

void set(int index, int value);

int get(int index);

int size();

int[] getWords();

BitArrayVersion getVersion();

BitArray copy();
}
62 changes: 62 additions & 0 deletions src/main/java/cn/nukkit/level/util/BitArrayVersion.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package cn.nukkit.level.util;

public enum BitArrayVersion {
V16(16, 2, null),
V8(8, 4, V16),
V6(6, 5, V8), // 2 bit padding
V5(5, 6, V6), // 2 bit padding
V4(4, 8, V5),
V3(3, 10, V4), // 2 bit padding
V2(2, 16, V3),
V1(1, 32, V2);

final byte bits;
final byte entriesPerWord;
final int maxEntryValue;
final BitArrayVersion next;

BitArrayVersion(int bits, int entriesPerWord, BitArrayVersion next) {
this.bits = (byte) bits;
this.entriesPerWord = (byte) entriesPerWord;
this.maxEntryValue = (1 << this.bits) - 1;
this.next = next;
}

public static BitArrayVersion get(int version, boolean read) {
for (BitArrayVersion ver : values()) {
if ((!read && ver.entriesPerWord <= version) || (read && ver.bits == version)) {
return ver;
}
}
throw new IllegalArgumentException("Invalid palette version: " + version);
}

public BitArray createPalette(int size) {
return this.createPalette(size, new int[this.getWordsForSize(size)]);
}

public byte getId() {
return bits;
}

public int getWordsForSize(int size) {
return (size / entriesPerWord) + (size % entriesPerWord == 0 ? 0 : 1);
}

public int getMaxEntryValue() {
return maxEntryValue;
}

public BitArrayVersion next() {
return next;
}

public BitArray createPalette(int size, int[] words) {
if (this == V3 || this == V5 || this == V6) {
// Padded palettes aren't able to use bitwise operations due to their padding.
return new PaddedBitArray(this, size, words);
} else {
return new Pow2BitArray(this, size, words);
}
}
}
75 changes: 75 additions & 0 deletions src/main/java/cn/nukkit/level/util/PaddedBitArray.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package cn.nukkit.level.util;

import cn.nukkit.math.MathHelper;
import com.google.common.base.Preconditions;

import java.util.Arrays;

public class PaddedBitArray implements BitArray {

/**
* Array used to store data
*/
private final int[] words;

/**
* Palette version information
*/
private final BitArrayVersion version;

/**
* Number of entries in this palette (<b>not</b> the length of the words array that internally backs this palette)
*/
private final int size;

PaddedBitArray(BitArrayVersion version, int size, int[] words) {
this.size = size;
this.version = version;
this.words = words;
int expectedWordsLength = MathHelper.ceil((float) size / version.entriesPerWord);
if (words.length != expectedWordsLength) {
throw new IllegalArgumentException("Invalid length given for storage, got: " + words.length +
" but expected: " + expectedWordsLength);
}
}

@Override
public void set(int index, int value) {
Preconditions.checkElementIndex(index, this.size);
Preconditions.checkArgument(value >= 0 && value <= this.version.maxEntryValue,
"Max value: %s. Received value", this.version.maxEntryValue, value);
int arrayIndex = index / this.version.entriesPerWord;
int offset = (index % this.version.entriesPerWord) * this.version.bits;

this.words[arrayIndex] = this.words[arrayIndex] & ~(this.version.maxEntryValue << offset) | (value & this.version.maxEntryValue) << offset;
}

@Override
public int get(int index) {
Preconditions.checkElementIndex(index, this.size);
int arrayIndex = index / this.version.entriesPerWord;
int offset = (index % this.version.entriesPerWord) * this.version.bits;

return (this.words[arrayIndex] >>> offset) & this.version.maxEntryValue;
}

@Override
public int size() {
return this.size;
}

@Override
public int[] getWords() {
return this.words;
}

@Override
public BitArrayVersion getVersion() {
return this.version;
}

@Override
public BitArray copy() {
return new PaddedBitArray(this.version, this.size, Arrays.copyOf(this.words, this.words.length));
}
}
Loading

2 comments on commit 1acd6a5

@TanToan99
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my map have problem when using this version :(

92459015_282585922729478_8458837445503352832_n

@SupremeMortal
Copy link
Member Author

@SupremeMortal SupremeMortal commented on 1acd6a5 Apr 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm aware of the problem and working on a fix. Go back to the last build for now. It's only visual so it won't effect the chunks server side.

Please sign in to comment.