diff --git a/src/main/java/net/minecraft/network/play/server/S21PacketChunkData.java b/src/main/java/net/minecraft/network/play/server/S21PacketChunkData.java index ca2c547..4b75bad 100644 --- a/src/main/java/net/minecraft/network/play/server/S21PacketChunkData.java +++ b/src/main/java/net/minecraft/network/play/server/S21PacketChunkData.java @@ -4,6 +4,7 @@ import cpw.mods.fml.relauncher.SideOnly; import java.io.IOException; +import java.util.Arrays; import java.util.concurrent.Semaphore; import java.util.zip.DataFormatException; import java.util.zip.Deflater; @@ -16,6 +17,8 @@ import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.NibbleArray; import net.minecraft.world.chunk.storage.ExtendedBlockStorage; +import org.ultramine.server.chunk.ChunkSnapshot; +import org.ultramine.server.internal.UMHooks; public class S21PacketChunkData extends Packet { @@ -32,6 +35,7 @@ private Semaphore deflateGate; private static final byte[] unloadSequence = new byte[] {0x78, (byte) 0x9C, 0x63, 0x64, 0x1C, (byte) 0xD9, 0x00, 0x00, (byte) 0x81, (byte) 0x80, 0x01, 0x01}; + private ChunkSnapshot chunkSnapshot; public S21PacketChunkData() {} @@ -46,6 +50,14 @@ this.field_149278_f = extracted.field_150282_a; this.deflateGate = new Semaphore(1); } + + private S21PacketChunkData(ChunkSnapshot chunkSnapshot) + { + this.field_149284_a = chunkSnapshot.getX(); + this.field_149282_b = chunkSnapshot.getZ(); + this.field_149279_g = true; + this.chunkSnapshot = chunkSnapshot; + } private S21PacketChunkData(int cx, int cz) //for unload { @@ -62,10 +74,29 @@ Deflater deflater = new Deflater(7); try { + if(chunkSnapshot != null) + { + UMHooks.ChunkPacketData data = UMHooks.extractAndDeflateChunkPacketData(deflater, chunkSnapshot); + field_149281_e = data.data; + field_149285_h = data.length; + + this.field_149280_d = data.ebsMask; + this.field_149283_c = data.ebsMask; + this.field_149278_f = null; + chunkSnapshot.release(); + chunkSnapshot = null; + return; + } deflater.setInput(this.field_149278_f, 0, this.field_149278_f.length); deflater.finish(); - byte[] deflated = new byte[this.field_149278_f.length]; - this.field_149285_h = deflater.deflate(deflated); + byte[] deflated = new byte[4096]; + int dataLen = 0; + while (!deflater.finished()) { + if(dataLen == deflated.length) + deflated = Arrays.copyOf(deflated, deflated.length * 2); + dataLen += deflater.deflate(deflated, dataLen, deflated.length - dataLen); + } + this.field_149285_h = dataLen; this.field_149281_e = deflated; } finally @@ -310,6 +341,11 @@ { return new S21PacketChunkData(chunk, true, 65535); } + + public static S21PacketChunkData makeForSend(ChunkSnapshot chunkSnapshot) + { + return new S21PacketChunkData(chunkSnapshot); + } public static S21PacketChunkData makeForUnload(Chunk chunk) { diff --git a/src/main/java/net/minecraft/world/chunk/storage/ExtendedBlockStorage.java b/src/main/java/net/minecraft/world/chunk/storage/ExtendedBlockStorage.java index 2756e86..d5d9e12 100644 --- a/src/main/java/net/minecraft/world/chunk/storage/ExtendedBlockStorage.java +++ b/src/main/java/net/minecraft/world/chunk/storage/ExtendedBlockStorage.java @@ -34,10 +34,12 @@ this(p_i1997_1_, p_i1997_2_, true); } - public ExtendedBlockStorage(int yBase, MemSlot slot) + public ExtendedBlockStorage(MemSlot slot, int yBase, int blockRefCount, int tickRefCount) { - this.yBase = yBase; this.slot = slot; + this.yBase = yBase; + this.blockRefCount = blockRefCount; + this.tickRefCount = tickRefCount; } public Block getBlockByExtId(int p_150819_1_, int p_150819_2_, int p_150819_3_) @@ -246,7 +248,7 @@ public ExtendedBlockStorage copy() { slot.getClass(); //NPE - return new ExtendedBlockStorage(yBase, slot.copy()); + return new ExtendedBlockStorage(slot.copy(), yBase, blockRefCount, tickRefCount); } public void release() @@ -255,4 +257,9 @@ this.slot = null; slotLocal.release(); } + + public void incBlockRefCount() + { + blockRefCount++; + } } \ No newline at end of file diff --git a/src/main/java/org/ultramine/server/chunk/ChunkSendManager.java b/src/main/java/org/ultramine/server/chunk/ChunkSendManager.java index 5ce209d..01b7d1c 100644 --- a/src/main/java/org/ultramine/server/chunk/ChunkSendManager.java +++ b/src/main/java/org/ultramine/server/chunk/ChunkSendManager.java @@ -459,12 +459,12 @@ private class CompressAndSendChunkTask implements Runnable { private final ChunkIdStruct chunkId; - private final S21PacketChunkData packet; + private final ChunkSnapshot chunkSnapshot; public CompressAndSendChunkTask(ChunkIdStruct chunkId) { this.chunkId = chunkId; - this.packet = S21PacketChunkData.makeForSend(chunkId.chunk); //must be sync + this.chunkSnapshot = ChunkSnapshot.of(chunkId.chunk); // must be sync } private boolean checkActual() @@ -483,9 +483,13 @@ public void run() { if(!checkActual()) + { + chunkSnapshot.release(); return; - - packet.deflate(); + } + + S21PacketChunkData packet = S21PacketChunkData.makeForSend(chunkSnapshot); // may be async for chunk snapshot + packet.deflate(); // chunkSnapshot released here //Нужно одновременно отправить чанк и добавить его в список sendingStage2, чтобы можно было корректно отменить отправку: //(Если чанк есть в списке sendingStage2, посылать пакет на отгрузку. В ином случае просто удалиь из списка sending) diff --git a/src/main/java/org/ultramine/server/chunk/ChunkSnapshot.java b/src/main/java/org/ultramine/server/chunk/ChunkSnapshot.java new file mode 100644 index 0000000..4dc6e4d --- /dev/null +++ b/src/main/java/org/ultramine/server/chunk/ChunkSnapshot.java @@ -0,0 +1,145 @@ +package org.ultramine.server.chunk; + +import net.minecraft.block.Block; +import net.minecraft.init.Blocks; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.storage.ExtendedBlockStorage; + +import java.util.Arrays; + +public class ChunkSnapshot +{ + private final int x; + private final int z; + private final boolean worldHasNoSky; + private final ExtendedBlockStorage[] ebsArr; + private final byte[] biomeArray; + + private ChunkSnapshot(int x, int z, boolean worldHasNoSky, ExtendedBlockStorage[] ebsArr, byte[] biomeArray) + { + this.x = x; + this.z = z; + this.worldHasNoSky = worldHasNoSky; + this.ebsArr = ebsArr; + this.biomeArray = biomeArray; + } + + public static ChunkSnapshot of(Chunk chunk) + { + ExtendedBlockStorage[] ebsOld = chunk.getBlockStorageArray(); + ExtendedBlockStorage[] ebsNew = new ExtendedBlockStorage[ebsOld.length]; + for(int i = 0; i < ebsOld.length; i++) + ebsNew[i] = ebsOld[i] == null ? null : ebsOld[i].copy(); + byte[] biomeArray = chunk.getBiomeArray(); + return new ChunkSnapshot(chunk.xPosition, chunk.zPosition, chunk.worldObj.provider.hasNoSky, ebsNew, Arrays.copyOf(biomeArray, biomeArray.length)); + } + + public int getX() + { + return x; + } + + public int getZ() + { + return z; + } + + public boolean isWorldHasNoSky() + { + return worldHasNoSky; + } + + public ExtendedBlockStorage[] getEbsArr() + { + return ebsArr; + } + + public byte[] getBiomeArray() + { + return biomeArray; + } + + public void release() + { + for(ExtendedBlockStorage ebs : ebsArr) + if(ebs != null) + ebs.release(); + } + + public ChunkSnapshot copy() + { + ExtendedBlockStorage[] ebsOld = ebsArr; + ExtendedBlockStorage[] ebsNew = new ExtendedBlockStorage[ebsOld.length]; + for(int i = 0; i < ebsOld.length; i++) + ebsNew[i] = ebsOld[i] == null ? null : ebsOld[i].copy(); + byte[] biomeArray = this.biomeArray; + return new ChunkSnapshot(getX(), getZ(), isWorldHasNoSky(), ebsNew, Arrays.copyOf(biomeArray, biomeArray.length)); + } + + private static void rangeCheck(int x, int z) + { + if((x & 0xFFFFFFF0) != 0 || (z & 0xFFFFFFF0) != 0) + throw new IllegalArgumentException(); + } + + public Block getBlock(int x, int y, int z) + { + rangeCheck(x, z); + ExtendedBlockStorage ebs = ebsArr[y >> 4]; + if(ebs != null) + return ebs.getBlockByExtId(x, y & 15, z); + return Blocks.air; + } + + public int getBlockId(int x, int y, int z) + { + rangeCheck(x, z); + ExtendedBlockStorage ebs = ebsArr[y >> 4]; + if(ebs != null) + return ebs.getSlot().getBlockId(x, y & 15, z); + return 0; + } + + public int getBlockMeta(int x, int y, int z) + { + rangeCheck(x, z); + ExtendedBlockStorage ebs = ebsArr[y >> 4]; + if(ebs != null) + return ebs.getExtBlockMetadata(x, y & 15, z); + return 0; + } + + public int getBlockIdAndMeta(int x, int y, int z) + { + rangeCheck(x, z); + ExtendedBlockStorage ebs = ebsArr[y >> 4]; + if(ebs != null) + return ebs.getSlot().getBlockIdAndMeta(x, y & 15, z); + return 0; + } + + public void setBlock(int x, int y, int z, int blockId, int meta) + { + rangeCheck(x, z); + ExtendedBlockStorage ebs = ebsArr[y >> 4]; + if(ebs == null) + ebs = ebsArr[y >> 4] = new ExtendedBlockStorage(y >> 4 << 4, true); + ebs.getSlot().setBlockIdAndMeta(x, y & 15, z, blockId, meta); + if(ebs.isEmpty()) + ebs.incBlockRefCount(); + } + + public void setBlock(int x, int y, int z, Block block, int meta) + { + setBlock(x, y, z, Block.getIdFromBlock(block), meta); + } + + public int getTopFilledSegment() + { + for (int i = ebsArr.length - 1; i >= 0; --i) + if(ebsArr[i] != null) + return ebsArr[i].getYLocation(); + + return 0; + } +} diff --git a/src/main/java/org/ultramine/server/internal/UMHooks.java b/src/main/java/org/ultramine/server/internal/UMHooks.java index 8fb5c10..e942968 100644 --- a/src/main/java/org/ultramine/server/internal/UMHooks.java +++ b/src/main/java/org/ultramine/server/internal/UMHooks.java @@ -1,7 +1,9 @@ package org.ultramine.server.internal; +import java.util.Arrays; import java.util.List; import java.util.UUID; +import java.util.zip.Deflater; import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.registry.LanguageRegistry; @@ -18,9 +20,11 @@ import net.minecraft.util.IChatComponent; import net.minecraft.world.WorldServer; import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.storage.ExtendedBlockStorage; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.ultramine.server.chunk.ChunkGenerationQueue; +import org.ultramine.server.chunk.ChunkSnapshot; import org.ultramine.server.event.WorldEventProxy; import org.ultramine.server.event.WorldUpdateObject; @@ -242,4 +246,136 @@ if(hasRemovedEntitiesTotal) world.loadedEntityList.removeIf(LambdaHolder.ENTITY_REMOVAL_PREDICATE); } + + public static ChunkPacketData extractAndDeflateChunkPacketData(Deflater deflater, ChunkSnapshot chunkSnapshot) + { + return new ChunkPacker(deflater, chunkSnapshot).pack(); + } + + private static class ChunkPacker + { + private static final ThreadLocal LOCAL_BUFFER = ThreadLocal.withInitial(LambdaHolder.newByteArray(4096)); + private static final byte[] EMPTY_CHUNK_SEQUENCE = {120, -38, -19, -63, 49, 1, 0, 0, 0, -62, -96, -11, 79, 109, 13, 15, -96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -128, 119, 3, 48, 0, 0, 1}; + private final Deflater deflater; + private final ChunkSnapshot chunkSnapshot; + private byte[] data; + private int dataLen; + + private ChunkPacker(Deflater deflater, ChunkSnapshot chunkSnapshot) + { + this.deflater = deflater; + this.chunkSnapshot = chunkSnapshot; + } + + public ChunkPacketData pack() + { + ChunkSnapshot chunkSnapshot = this.chunkSnapshot; + ExtendedBlockStorage[] ebsArr = chunkSnapshot.getEbsArr(); + int mask = 0; + + for(int i = 0; i < ebsArr.length; ++i) + { + ExtendedBlockStorage ebs = ebsArr[i]; + if(ebs != null && !ebs.isEmpty()) + mask |= 1 << i; + } + + if(mask == 0) + return new ChunkPacketData(EMPTY_CHUNK_SEQUENCE, EMPTY_CHUNK_SEQUENCE.length, 1); // Simulates empty 0-level EBS + + this.data = new byte[4096]; + byte[] buf = LOCAL_BUFFER.get(); + + for(int i = 0; i < ebsArr.length; ++i) + { + ExtendedBlockStorage ebs = ebsArr[i]; + if(ebs != null && !ebs.isEmpty()) + { + ebs.getSlot().copyLSB(buf, 0); + write(buf, 4096); + } + } + + for(int i = 0; i < ebsArr.length; ++i) + { + ExtendedBlockStorage ebs = ebsArr[i]; + if(ebs != null && !ebs.isEmpty()) + { + ebs.getSlot().copyBlockMetadata(buf, 0); + write(buf, 2048); + } + } + + for(int i = 0; i < ebsArr.length; ++i) + { + ExtendedBlockStorage ebs = ebsArr[i]; + if(ebs != null && !ebs.isEmpty()) + { + ebs.getSlot().copyBlocklight(buf, 0); + write(buf, 2048); + } + } + + if(!chunkSnapshot.isWorldHasNoSky()) + { + for(int i = 0; i < ebsArr.length; ++i) + { + ExtendedBlockStorage ebs = ebsArr[i]; + if(ebs != null && !ebs.isEmpty()) + { + ebs.getSlot().copySkylight(buf, 0); + write(buf, 2048); + } + } + } + + for(int i = 0; i < ebsArr.length; ++i) + { + ExtendedBlockStorage ebs = ebsArr[i]; + if(ebs != null && !ebs.isEmpty()) + { + ebs.getSlot().copyMSB(buf, 0); + write(buf, 2048); + } + } + + write(chunkSnapshot.getBiomeArray(), chunkSnapshot.getBiomeArray().length); + + deflater.finish(); + while (!deflater.finished()) { + deflate(); + } + + return new ChunkPacketData(data, dataLen, mask); + } + + private void write(byte[] src, int srcLen) + { + deflater.setInput(src, 0, srcLen); + while (!deflater.needsInput()) { + deflate(); + } + } + + private void deflate() + { + if(dataLen == data.length) + data = Arrays.copyOf(data, data.length * 2); + dataLen += deflater.deflate(data, dataLen, data.length - dataLen); + } + } + + public static class ChunkPacketData + { + public final byte[] data; + public final int length; + public final int ebsMask; + + public ChunkPacketData(byte[] data, int length, int ebsMask) + { + this.data = data; + this.length = length; + this.ebsMask = ebsMask; + } + } }