diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java index 823904be1..b6a8a7755 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java @@ -58,6 +58,7 @@ public class FaweBukkit implements IFawe, Listener { private boolean listeningImages; private BukkitImageListener imageListener; private CFIPacketListener packetListener; + private final boolean chunksStretched; public VaultUtil getVault() { return this.vault; @@ -99,6 +100,8 @@ public class FaweBukkit implements IFawe, Listener { // The tick limiter new ChunkListener_9(); }); + + chunksStretched = Integer.parseInt(Bukkit.getMinecraftVersion().split("\\.")[1]) >= 16; } @Override // Please don't delete this again, it's WIP @@ -302,6 +305,11 @@ public class FaweBukkit implements IFawe, Listener { return null; } + @Override + public boolean isChunksStretched() { + return chunksStretched; + } + private void setupPlotSquared() { Plugin plotSquared = this.plugin.getServer().getPluginManager().getPlugin("PlotSquared"); if (plotSquared == null) diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_1/BukkitAdapter_1_16_1.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_1/BukkitAdapter_1_16_1.java index 7b1549d65..a8e096e0c 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_1/BukkitAdapter_1_16_1.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_1/BukkitAdapter_1_16_1.java @@ -5,7 +5,7 @@ import com.boydti.fawe.FaweCache; import com.boydti.fawe.bukkit.adapter.DelegateLock; import com.boydti.fawe.bukkit.adapter.NMSAdapter; import com.boydti.fawe.config.Settings; -import com.boydti.fawe.object.collection.BitArray; +import com.boydti.fawe.object.collection.BitArrayUnstretched; import com.boydti.fawe.util.MathMan; import com.boydti.fawe.util.ReflectionUtils; import com.boydti.fawe.util.TaskManager; @@ -232,11 +232,13 @@ public final class BukkitAdapter_1_16_1 extends NMSAdapter { bitsPerEntry = Math.max(bitsPerEntry, 1); // For some reason minecraft needs 4096 bits to store 0 entries } - final int blockBitArrayEnd = (bitsPerEntry * 4096) >> 6; + final int blocksPerLong = MathMan.floorZero((double) 64 / bitsPerEntry); + final int blockBitArrayEnd = MathMan.ceilZero((float) 4096 / blocksPerLong); + if (num_palette == 1) { for (int i = 0; i < blockBitArrayEnd; i++) blockStates[i] = 0; } else { - final BitArray bitArray = new BitArray(bitsPerEntry, 4096, blockStates); + final BitArrayUnstretched bitArray = new BitArrayUnstretched(bitsPerEntry, blockStates); bitArray.fromRaw(blocksCopy); } diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_1/BukkitGetBlocks_1_16_1.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_1/BukkitGetBlocks_1_16_1.java index b07589339..a4eed61aa 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_1/BukkitGetBlocks_1_16_1.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_1/BukkitGetBlocks_1_16_1.java @@ -10,7 +10,7 @@ import com.boydti.fawe.bukkit.adapter.DelegateLock; import com.boydti.fawe.bukkit.adapter.mc1_16_1.nbt.LazyCompoundTag_1_16_1; import com.boydti.fawe.config.Settings; import com.boydti.fawe.object.collection.AdaptedMap; -import com.boydti.fawe.object.collection.BitArray; +import com.boydti.fawe.object.collection.BitArrayUnstretched; import com.google.common.base.Suppliers; import com.google.common.collect.Iterables; import com.sk89q.jnbt.Tag; @@ -553,7 +553,7 @@ public class BukkitGetBlocks_1_16_1 extends CharGetBlocks { final int bitsPerEntry = (int) BukkitAdapter_1_16_1.fieldBitsPerEntry.get(bits); final long[] blockStates = bits.a(); - new BitArray(bitsPerEntry, 4096, blockStates).toRaw(data); + new BitArrayUnstretched(bitsPerEntry, blockStates).toRaw(data); int num_palette; if (palette instanceof DataPaletteLinear) { diff --git a/worldedit-core/src/main/java/com/boydti/fawe/FaweCache.java b/worldedit-core/src/main/java/com/boydti/fawe/FaweCache.java index ee43fdc36..859503291 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/FaweCache.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/FaweCache.java @@ -1,7 +1,6 @@ package com.boydti.fawe; import static com.google.common.base.Preconditions.checkNotNull; -import static org.slf4j.LoggerFactory.getLogger; import com.boydti.fawe.beta.IChunkSet; import com.boydti.fawe.beta.Trimable; @@ -9,6 +8,7 @@ import com.boydti.fawe.beta.implementation.queue.Pool; import com.boydti.fawe.beta.implementation.queue.QueuePool; import com.boydti.fawe.config.Settings; import com.boydti.fawe.object.collection.BitArray; +import com.boydti.fawe.object.collection.BitArrayUnstretched; import com.boydti.fawe.object.collection.CleanableThreadLocal; import com.boydti.fawe.object.collection.VariableThreadLocal; import com.boydti.fawe.object.exception.FaweBlockBagException; @@ -299,6 +299,102 @@ public enum FaweCache implements Trimable { } } + /** + * Convert raw int array to unstretched palette (1.16) + * @param layerOffset + * @param blocks + * @return palette + */ + public Palette toPaletteUnstretched(int layerOffset, char[] blocks) { + return toPaletteUnstretched(layerOffset, null, blocks); + } + + /** + * Convert raw int array to unstretched palette (1.16) + * @param layerOffset + * @param blocks + * @return palette + */ + public Palette toPaletteUnstretched(int layerOffset, int[] blocks) { + return toPaletteUnstretched(layerOffset, blocks, null); + } + + private Palette toPaletteUnstretched(int layerOffset, int[] blocksInts, char[] blocksChars) { + int[] blockToPalette = BLOCK_TO_PALETTE.get(); + int[] paletteToBlock = PALETTE_TO_BLOCK.get(); + long[] blockStates = BLOCK_STATES.get(); + int[] blocksCopy = SECTION_BLOCKS.get(); + + try { + int num_palette = 0; + int blockIndexStart = layerOffset << 12; + int blockIndexEnd = blockIndexStart + 4096; + if (blocksChars != null) { + for (int i = blockIndexStart, j = 0; i < blockIndexEnd; i++, j++) { + int ordinal = blocksChars[i]; + int palette = blockToPalette[ordinal]; + if (palette == Integer.MAX_VALUE) { + blockToPalette[ordinal] = palette = num_palette; + paletteToBlock[num_palette] = ordinal; + num_palette++; + } + blocksCopy[j] = palette; + } + } else if (blocksInts != null) { + for (int i = blockIndexStart, j = 0; i < blockIndexEnd; i++, j++) { + int ordinal = blocksInts[i]; + int palette = blockToPalette[ordinal]; + if (palette == Integer.MAX_VALUE) { + // BlockState state = BlockTypesCache.states[ordinal]; + blockToPalette[ordinal] = palette = num_palette; + paletteToBlock[num_palette] = ordinal; + num_palette++; + } + blocksCopy[j] = palette; + } + } else { + throw new IllegalArgumentException(); + } + + for (int i = 0; i < num_palette; i++) { + blockToPalette[paletteToBlock[i]] = Integer.MAX_VALUE; + } + + // BlockStates + int bitsPerEntry = MathMan.log2nlz(num_palette - 1); + if (Settings.IMP.PROTOCOL_SUPPORT_FIX || num_palette != 1) { + bitsPerEntry = Math.max(bitsPerEntry, 4); // Protocol support breaks <4 bits per entry + } else { + bitsPerEntry = Math.max(bitsPerEntry, 1); // For some reason minecraft needs 4096 bits to store 0 entries + } + int blocksPerLong = MathMan.floorZero((double) 64 / bitsPerEntry); + int blockBitArrayEnd = MathMan.ceilZero((float) 4096 / blocksPerLong); + if (num_palette == 1) { + // Set a value, because minecraft needs it for some reason + blockStates[0] = 0; + blockBitArrayEnd = 1; + } else { + BitArrayUnstretched bitArray = new BitArrayUnstretched(bitsPerEntry, blockStates); + bitArray.fromRaw(blocksCopy); + } + + // Construct palette + Palette palette = PALETTE_CACHE.get(); + palette.bitsPerEntry = bitsPerEntry; + palette.paletteToBlockLength = num_palette; + palette.paletteToBlock = paletteToBlock; + + palette.blockStatesLength = blockBitArrayEnd; + palette.blockStates = blockStates; + + return palette; + } catch (Throwable e) { + e.printStackTrace(); + Arrays.fill(blockToPalette, Integer.MAX_VALUE); + throw e; + } + } + /* * Vector cache */ diff --git a/worldedit-core/src/main/java/com/boydti/fawe/IFawe.java b/worldedit-core/src/main/java/com/boydti/fawe/IFawe.java index 20a15faab..6941fb728 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/IFawe.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/IFawe.java @@ -41,4 +41,8 @@ public interface IFawe { Preloader getPreloader(); + default boolean isChunksStretched() { + return true; + } + } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/IBlocks.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/IBlocks.java index c042e7a23..42fbac580 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/IBlocks.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/IBlocks.java @@ -45,11 +45,11 @@ public interface IBlocks extends Trimable { IBlocks reset(); - default byte[] toByteArray(boolean full) { - return toByteArray(null, getBitMask(), full); + default byte[] toByteArray(boolean full, boolean stretched) { + return toByteArray(null, getBitMask(), full, stretched); } - default byte[] toByteArray(byte[] buffer, int bitMask, boolean full) { + default byte[] toByteArray(byte[] buffer, int bitMask, boolean full, boolean stretched) { if (buffer == null) { buffer = new byte[1024]; } @@ -81,7 +81,12 @@ public interface IBlocks extends Trimable { } sectionWriter.writeShort(nonEmpty); // non empty - FaweCache.Palette palette = FaweCache.IMP.toPalette(0, ids); + FaweCache.Palette palette; + if (stretched) { + palette = FaweCache.IMP.toPalette(0, ids); + } else { + palette = FaweCache.IMP.toPaletteUnstretched(0, ids); + } sectionWriter.writeByte(palette.bitsPerEntry); // bits per block sectionWriter.writeVarInt(palette.paletteToBlockLength); diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/packet/ChunkPacket.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/packet/ChunkPacket.java index b904c5cd6..5638c962e 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/packet/ChunkPacket.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/packet/ChunkPacket.java @@ -1,10 +1,12 @@ package com.boydti.fawe.beta.implementation.packet; +import com.boydti.fawe.Fawe; import com.boydti.fawe.FaweCache; import com.boydti.fawe.beta.IBlocks; import com.boydti.fawe.object.FaweOutputStream; import com.boydti.fawe.object.io.FastByteArrayOutputStream; import com.sk89q.jnbt.CompoundTag; +import com.sk89q.worldedit.WorldEdit; import java.util.HashMap; import java.util.function.Function; @@ -64,7 +66,7 @@ public class ChunkPacket implements Function, Supplier { if (sectionBytes == null) { IBlocks tmpChunk = getChunk(); byte[] buf = FaweCache.IMP.BYTE_BUFFER_8192.get(); - sectionBytes = tmpChunk.toByteArray(buf, tmpChunk.getBitMask(), this.full); + sectionBytes = tmpChunk.toByteArray(buf, tmpChunk.getBitMask(), this.full, Fawe.imp().isChunksStretched()); } tmp = sectionBytes; } @@ -72,6 +74,7 @@ public class ChunkPacket implements Function, Supplier { return tmp; } + public Object getNativePacket() { return nativePacket; } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/collection/BitArrayUnstretched.java b/worldedit-core/src/main/java/com/boydti/fawe/object/collection/BitArrayUnstretched.java new file mode 100644 index 000000000..9572eecc9 --- /dev/null +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/collection/BitArrayUnstretched.java @@ -0,0 +1,115 @@ +package com.boydti.fawe.object.collection; + +import com.boydti.fawe.util.MathMan; + +public final class BitArrayUnstretched { + + private final long[] data; + private final int bitsPerEntry; + private final int maxSeqLocIndex; + private final int emptyBitCount; + private final long mask; + private final int longLen; + + public BitArrayUnstretched(int bitsPerEntry, long[] buffer) { + this.bitsPerEntry = bitsPerEntry; + this.mask = (1L << bitsPerEntry) - 1L; + this.emptyBitCount = 64 % bitsPerEntry; + this.maxSeqLocIndex = 64 - (bitsPerEntry + emptyBitCount); + final int blocksPerLong = MathMan.floorZero((double) 64 / bitsPerEntry); + this.longLen = MathMan.ceilZero((float) 4096 / blocksPerLong); + if (buffer.length < longLen) { + this.data = new long[longLen]; + } else { + this.data = buffer; + } + } + + public long[] getData() { + return data; + } + + public final void set(int index, int value) { + if (longLen == 0) return; + int bitIndexStart = index * bitsPerEntry + MathMan.floorZero((double) index / longLen) * emptyBitCount; + int longIndexStart = bitIndexStart >> 6; + int localBitIndexStart = bitIndexStart & 63; + this.data[longIndexStart] = this.data[longIndexStart] & ~(mask << localBitIndexStart) | (long) value << localBitIndexStart; + } + + public final int get(int index) { + if (longLen == 0) return 0; + int bitIndexStart = index * bitsPerEntry + MathMan.floorZero((double) index / longLen) * emptyBitCount; + + int longIndexStart = bitIndexStart >> 6; + + int localBitIndexStart = bitIndexStart & 63; + return (int)(this.data[longIndexStart] >>> localBitIndexStart & mask); + } + + public int getLength() { + return longLen; + } + + public final void fromRaw(int[] arr) { + final long[] data = this.data; + final int bitsPerEntry = this.bitsPerEntry; + final int maxSeqLocIndex = this.maxSeqLocIndex; + + int localStart = 0; + int arrI = 0; + long l = 0; + for (int i = 0; i < longLen; i++) { + int lastVal; + for (; localStart <= maxSeqLocIndex && arrI < 4096; localStart += bitsPerEntry) { + lastVal = arr[arrI++]; + l |= ((long) lastVal << localStart); + } + localStart = 0; + data[i] = l; + l = 0; + } + } + + public final int[] toRaw() { + return toRaw(new int[4096]); + } + + public final int[] toRaw(int[] buffer) { + final long[] data = this.data; + final int bitsPerEntry = this.bitsPerEntry; + final int maxSeqLocIndex = this.maxSeqLocIndex; + + int localStart = 0; + int arrI = 0; + for (int i = 0; i < longLen; i++) { + long l = data[i]; + char lastVal; + for (; localStart <= maxSeqLocIndex && arrI < 4096; localStart += bitsPerEntry) { + lastVal = (char) (l >>> localStart & this.mask); + buffer[arrI++] = lastVal; + } + localStart = 0; + } + return buffer; + } + + public final char[] toRaw(char[] buffer) { + final long[] data = this.data; + final int bitsPerEntry = this.bitsPerEntry; + final int maxSeqLocIndex = this.maxSeqLocIndex; + + int localStart = 0; + int arrI = 0; + for (int i = 0; i < longLen; i++) { + long l = data[i]; + char lastVal; + for (; localStart <= maxSeqLocIndex && arrI < 4096; localStart += bitsPerEntry) { + lastVal = (char) (l >>> localStart & this.mask); + buffer[arrI++] = lastVal; + } + localStart = 0; + } + return buffer; + } +}