papermc/CraftBukkit-Patches/0013-Compressed-Nibble-Arrays.patch
md_5 fa4078a2d6 "It Compiles" - Update Spigot to Minecraft 1.7.2 proper. See below for full release notes (MUST READ).
This is a lightly tested build. You are encouraged to keep backups at all times. Please attempt to report all issues to IRC. The following features are intentionally missing from this build and will be added as soon as humanly possible.
- BungeeCord IP forwarding
- Firing of AsyncLoginEvent in offline mode
- A few custom kick / other hardcoded messages

As always this build comes with no warranty.
Thanks for your support.
~md_5
2013-12-01 17:16:56 +11:00

394 lines
17 KiB
Diff

From cb6a4e3598f76edb4743ae4041a135b7913793bc Mon Sep 17 00:00:00 2001
From: Mike Primm <mike@primmhome.com>
Date: Sun, 13 Jan 2013 03:49:07 -0800
Subject: [PATCH] Compressed Nibble Arrays
Implement 'lightening' of NibbleArrays - only allocate
buffers when non-trivial value Saving from 40-45% of memory use by chunk
section data.
Finish up NibbleArray lightening work - use for Snapshots, reduce copies
Fix nibble handling with NBT - arrays aren't copied by NBTByteArray
diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
index 1e1499e..76b081a 100644
--- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java
+++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
@@ -225,15 +225,15 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
nbttagcompound1.setByte("Y", (byte) (chunksection.getYPosition() >> 4 & 255));
nbttagcompound1.setByteArray("Blocks", chunksection.getIdArray());
if (chunksection.getExtendedIdArray() != null) {
- nbttagcompound1.setByteArray("Add", chunksection.getExtendedIdArray().a);
+ nbttagcompound1.setByteArray("Add", chunksection.getExtendedIdArray().getValueArray()); // Spigot
}
- nbttagcompound1.setByteArray("Data", chunksection.getDataArray().a);
- nbttagcompound1.setByteArray("BlockLight", chunksection.getEmittedLightArray().a);
+ nbttagcompound1.setByteArray("Data", chunksection.getDataArray().getValueArray()); // Spigot
+ nbttagcompound1.setByteArray("BlockLight", chunksection.getEmittedLightArray().getValueArray()); // Spigot
if (flag) {
- nbttagcompound1.setByteArray("SkyLight", chunksection.getSkyLightArray().a);
+ nbttagcompound1.setByteArray("SkyLight", chunksection.getSkyLightArray().getValueArray()); // Spigot
} else {
- nbttagcompound1.setByteArray("SkyLight", new byte[chunksection.getEmittedLightArray().a.length]);
+ nbttagcompound1.setByteArray("SkyLight", new byte[chunksection.getEmittedLightArray().getValueArray().length]); // Spigot
}
nbttaglist.add(nbttagcompound1);
diff --git a/src/main/java/net/minecraft/server/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java
index a05efa0..360de1a 100644
--- a/src/main/java/net/minecraft/server/ChunkSection.java
+++ b/src/main/java/net/minecraft/server/ChunkSection.java
@@ -140,7 +140,8 @@ public class ChunkSection {
}
}
} else {
- byte[] ext = this.extBlockIds.a;
+ this.extBlockIds.forceToNonTrivialArray(); // Spigot
+ byte[] ext = this.extBlockIds.getValueArray();
for (int off = 0, off2 = 0; off < blkIds.length;) {
byte extid = ext[off2];
int l = (blkIds[off] & 0xFF) | ((extid & 0xF) << 8); // Even data
@@ -171,6 +172,12 @@ public class ChunkSection {
off++;
off2++;
}
+ // Spigot start
+ this.extBlockIds.detectAndProcessTrivialArray();
+ if (this.extBlockIds.isTrivialArray() && (this.extBlockIds.getTrivialArrayValue() == 0)) {
+ this.extBlockIds = null;
+ }
+ // Spigot end
}
this.nonEmptyBlockCount = cntNonEmpty;
this.tickingBlockCount = cntTicking;
@@ -224,12 +231,11 @@ public class ChunkSection {
public void setExtendedIdArray(NibbleArray nibblearray) {
// CraftBukkit start - Don't hang on to an empty nibble array
boolean empty = true;
- for (int i = 0; i < nibblearray.a.length; i++) {
- if (nibblearray.a[i] != 0) {
- empty = false;
- break;
- }
+ // Spigot start
+ if ((!nibblearray.isTrivialArray()) || (nibblearray.getTrivialArrayValue() != 0)) {
+ empty = false;
}
+ // Spigot end
if (empty) {
return;
@@ -253,11 +259,11 @@ public class ChunkSection {
// CraftBukkit start - Validate array lengths
private NibbleArray validateNibbleArray(NibbleArray nibbleArray) {
- if (nibbleArray != null && nibbleArray.a.length < 2048) {
- byte[] newArray = new byte[2048];
- System.arraycopy(nibbleArray.a, 0, newArray, 0, ((nibbleArray.a.length > 2048) ? 2048 : nibbleArray.a.length));
- nibbleArray = new NibbleArray(newArray, 4);
+ // Spigot start - fix for more awesome nibble arrays
+ if (nibbleArray != null && nibbleArray.getByteLength() < 2048) {
+ nibbleArray.resizeArray(2048);
}
+ // Spigot end
return nibbleArray;
}
diff --git a/src/main/java/net/minecraft/server/NibbleArray.java b/src/main/java/net/minecraft/server/NibbleArray.java
index 5d75a54..c9bc20c 100644
--- a/src/main/java/net/minecraft/server/NibbleArray.java
+++ b/src/main/java/net/minecraft/server/NibbleArray.java
@@ -1,13 +1,117 @@
package net.minecraft.server;
+import java.util.Arrays; // Spigot
+
public class NibbleArray {
- public final byte[] a;
+ private byte[] a; // Spigot - remove final, make private (anyone directly accessing this is broken already)
private final int b;
private final int c;
+ // Spigot start
+ private byte trivialValue;
+ private byte trivialByte;
+ private int length;
+ private static final int LEN2K = 2048; // Universal length used right now - optimize around this
+ private static final byte[][] TrivLen2k;
+
+ static {
+ TrivLen2k = new byte[16][];
+ for (int i = 0; i < 16; i++) {
+ TrivLen2k[i] = new byte[LEN2K];
+ Arrays.fill(TrivLen2k[i], (byte) (i | (i << 4)));
+ }
+ }
+
+ // Try to convert array to trivial array
+ public void detectAndProcessTrivialArray() {
+ trivialValue = (byte) (a[0] & 0xF);
+ trivialByte = (byte) (trivialValue | (trivialValue << 4));
+ for (int i = 0; i < a.length; i++) {
+ if (a[i] != trivialByte) return;
+ }
+ // All values matches, so array is trivial
+ this.length = a.length;
+ this.a = null;
+ }
+
+ // Force array to non-trivial state
+ public void forceToNonTrivialArray() {
+ if (this.a == null) {
+ this.a = new byte[this.length];
+ if (this.trivialByte != 0) {
+ Arrays.fill(this.a, this.trivialByte);
+ }
+ }
+ }
+
+ // Test if array is in trivial state
+ public boolean isTrivialArray() {
+ return (this.a == null);
+ }
+
+ // Get value of all elements (only valid if array is in trivial state)
+ public int getTrivialArrayValue() {
+ return this.trivialValue;
+ }
+
+ // Get logical length of byte array for nibble data (whether trivial or non-trivial)
+ public int getByteLength() {
+ if (this.a == null) {
+ return this.length;
+ } else {
+ return this.a.length;
+ }
+ }
+
+ // Return byte encoding of array (whether trivial or non-trivial) - returns read-only array if trivial (do not modify!)
+ public byte[] getValueArray() {
+ if (this.a != null) {
+ return this.a;
+ } else {
+ byte[] rslt;
+
+ if (this.length == LEN2K) { // All current uses are 2k long, but be safe
+ rslt = TrivLen2k[this.trivialValue];
+ } else {
+ rslt = new byte[this.length];
+ if (this.trivialByte != 0) {
+ Arrays.fill(rslt, this.trivialByte);
+ }
+ }
+ return rslt;
+ }
+ }
+
+ // Copy byte representation of array to given offset in given byte array
+ public int copyToByteArray(byte[] dest, int off) {
+ if (this.a == null) {
+ Arrays.fill(dest, off, off + this.length, this.trivialByte);
+ return off + this.length;
+ } else {
+ System.arraycopy(this.a, 0, dest, off, this.a.length);
+ return off + this.a.length;
+ }
+ }
+
+ // Resize array to given byte length
+ public void resizeArray(int len) {
+ if (this.a == null) {
+ this.length = len;
+ } else if (this.a.length != len) {
+ byte[] newa = new byte[len];
+ System.arraycopy(this.a, 0, newa, 0, ((this.a.length > len) ? len : this.a.length));
+ this.a = newa;
+ }
+ }
+ // Spigot end
public NibbleArray(int i, int j) {
- this.a = new byte[i >> 1];
+ // Spigot start
+ //this.a = new byte[i >> 1];
+ this.a = null; // Start off as trivial value (all same zero value)
+ this.length = i >> 1;
+ this.trivialByte = this.trivialValue = 0;
+ // Spigot end
this.b = j;
this.c = j + 4;
}
@@ -16,9 +120,11 @@ public class NibbleArray {
this.a = abyte;
this.b = i;
this.c = i + 4;
+ detectAndProcessTrivialArray(); // Spigot
}
public int a(int i, int j, int k) {
+ if (this.a == null) return this.trivialValue; // Spigot
int l = j << this.c | k << this.b | i;
int i1 = l >> 1;
int j1 = l & 1;
@@ -27,6 +133,18 @@ public class NibbleArray {
}
public void a(int i, int j, int k, int l) {
+ // Spigot start
+ if (this.a == null) {
+ if (l != this.trivialValue) { // Not same as trivial value, array no longer trivial
+ this.a = new byte[this.length];
+ if (this.trivialByte != 0) {
+ Arrays.fill(this.a, this.trivialByte);
+ }
+ } else {
+ return;
+ }
+ }
+ // Spigot end
int i1 = j << this.c | k << this.b | i;
int j1 = i1 >> 1;
int k1 = i1 & 1;
diff --git a/src/main/java/net/minecraft/server/OldChunkLoader.java b/src/main/java/net/minecraft/server/OldChunkLoader.java
index fcb9912..6ee28cc 100644
--- a/src/main/java/net/minecraft/server/OldChunkLoader.java
+++ b/src/main/java/net/minecraft/server/OldChunkLoader.java
@@ -94,9 +94,11 @@ public class OldChunkLoader {
nbttagcompound1.setByte("Y", (byte) (k & 255));
nbttagcompound1.setByteArray("Blocks", abyte);
- nbttagcompound1.setByteArray("Data", nibblearray.a);
- nbttagcompound1.setByteArray("SkyLight", nibblearray1.a);
- nbttagcompound1.setByteArray("BlockLight", nibblearray2.a);
+ // Spigot start - a -> getValueArray() accessor
+ nbttagcompound1.setByteArray("Data", nibblearray.getValueArray());
+ nbttagcompound1.setByteArray("SkyLight", nibblearray1.getValueArray());
+ nbttagcompound1.setByteArray("BlockLight", nibblearray2.getValueArray());
+ // Spigot end
nbttaglist.add(nbttagcompound1);
}
}
diff --git a/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java b/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java
index c7b799a..856e825 100644
--- a/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java
+++ b/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java
@@ -138,16 +138,16 @@ public class PacketPlayOutMapChunk extends Packet {
for (l = 0; l < achunksection.length; ++l) {
if (achunksection[l] != null && (!flag || !achunksection[l].isEmpty()) && (i & 1 << l) != 0) {
nibblearray = achunksection[l].getDataArray();
- System.arraycopy(nibblearray.a, 0, abyte, j, nibblearray.a.length);
- j += nibblearray.a.length;
+ nibblearray.copyToByteArray(abyte, j);
+ j += nibblearray.getByteLength();
}
}
for (l = 0; l < achunksection.length; ++l) {
if (achunksection[l] != null && (!flag || !achunksection[l].isEmpty()) && (i & 1 << l) != 0) {
nibblearray = achunksection[l].getEmittedLightArray();
- System.arraycopy(nibblearray.a, 0, abyte, j, nibblearray.a.length);
- j += nibblearray.a.length;
+ nibblearray.copyToByteArray(abyte, j);
+ j += nibblearray.getByteLength();
}
}
@@ -155,8 +155,8 @@ public class PacketPlayOutMapChunk extends Packet {
for (l = 0; l < achunksection.length; ++l) {
if (achunksection[l] != null && (!flag || !achunksection[l].isEmpty()) && (i & 1 << l) != 0) {
nibblearray = achunksection[l].getSkyLightArray();
- System.arraycopy(nibblearray.a, 0, abyte, j, nibblearray.a.length);
- j += nibblearray.a.length;
+ nibblearray.copyToByteArray(abyte, j);
+ j += nibblearray.getByteLength();
}
}
}
@@ -165,8 +165,8 @@ public class PacketPlayOutMapChunk extends Packet {
for (l = 0; l < achunksection.length; ++l) {
if (achunksection[l] != null && (!flag || !achunksection[l].isEmpty()) && achunksection[l].getExtendedIdArray() != null && (i & 1 << l) != 0) {
nibblearray = achunksection[l].getExtendedIdArray();
- System.arraycopy(nibblearray.a, 0, abyte, j, nibblearray.a.length);
- j += nibblearray.a.length;
+ nibblearray.copyToByteArray(abyte, j);
+ j += nibblearray.getByteLength();
}
}
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
index b2c6ef4..55f5225 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
@@ -174,7 +174,18 @@ public class CraftChunk implements Chunk {
}
if (cs[i].getExtendedIdArray() != null) { /* If we've got extended IDs */
- byte[] extids = cs[i].getExtendedIdArray().a;
+ // Spigot start
+ if (cs[i].getExtendedIdArray().isTrivialArray()) {
+ int tval = cs[i].getExtendedIdArray().getTrivialArrayValue();
+ if (tval != 0) {
+ tval = tval << 8;
+ for (int j = 0; j < 4096; j++) {
+ blockids[j] |= tval;
+ }
+ }
+ } else {
+ byte[] extids = cs[i].getExtendedIdArray().getValueArray();
+ // Spigot end
for (int j = 0; j < 2048; j++) {
short b = (short) (extids[j] & 0xFF);
@@ -186,21 +197,42 @@ public class CraftChunk implements Chunk {
blockids[j<<1] |= (b & 0x0F) << 8;
blockids[(j<<1)+1] |= (b & 0xF0) << 4;
}
+ } // Spigot
}
sectionBlockIDs[i] = blockids;
/* Get block data nibbles */
- sectionBlockData[i] = new byte[2048];
- System.arraycopy(cs[i].getDataArray().a, 0, sectionBlockData[i], 0, 2048);
+ // Spigot start
+ if (cs[i].getDataArray().isTrivialArray() && (cs[i].getDataArray().getTrivialArrayValue() == 0)) {
+ sectionBlockData[i] = emptyData;
+ } else {
+ sectionBlockData[i] = new byte[2048];
+ cs[i].getDataArray().copyToByteArray(sectionBlockData[i], 0);
+ }
if (cs[i].getSkyLightArray() == null) {
sectionSkyLights[i] = emptyData;
+ }
+ else if (cs[i].getSkyLightArray().isTrivialArray()) {
+ if (cs[i].getSkyLightArray().getTrivialArrayValue() == 0) {
+ sectionSkyLights[i] = emptyData;
+ } else if (cs[i].getSkyLightArray().getTrivialArrayValue() == 15) {
+ sectionSkyLights[i] = emptySkyLight;
+ } else {
+ sectionSkyLights[i] = new byte[2048];
+ cs[i].getSkyLightArray().copyToByteArray(sectionSkyLights[i], 0);
+ }
} else {
sectionSkyLights[i] = new byte[2048];
- System.arraycopy(cs[i].getSkyLightArray().a, 0, sectionSkyLights[i], 0, 2048);
+ cs[i].getSkyLightArray().copyToByteArray(sectionSkyLights[i], 0);
+ }
+ if (cs[i].getEmittedLightArray().isTrivialArray() && (cs[i].getEmittedLightArray().getTrivialArrayValue() == 0)) {
+ sectionEmitLights[i] = emptyData;
+ } else {
+ sectionEmitLights[i] = new byte[2048];
+ cs[i].getEmittedLightArray().copyToByteArray(sectionEmitLights[i], 0);
}
- sectionEmitLights[i] = new byte[2048];
- System.arraycopy(cs[i].getEmittedLightArray().a, 0, sectionEmitLights[i], 0, 2048);
+ // Spigot end
}
}
--
1.8.3.2