Skip to content
This repository has been archived by the owner on Aug 31, 2019. It is now read-only.

Add async chunk compressing and caching patch #94

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
376 changes: 376 additions & 0 deletions CraftBukkit/0078-Async-chunk-compressing-and-caching.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,376 @@
From 826b942676748450d24fa98cfed70acdf924f911 Mon Sep 17 00:00:00 2001
From: mrapple <tony@oc.tc>
Date: Thu, 31 Jul 2014 15:52:00 -0500
Subject: [PATCH] Async chunk compressing and caching


diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
index ade0c35..f079f40 100644
--- a/src/main/java/net/minecraft/server/Chunk.java
+++ b/src/main/java/net/minecraft/server/Chunk.java
@@ -40,6 +40,8 @@ public class Chunk {
public int r;
public long s;
private int x;
+ public PacketPlayOutMapChunkBulk cachedPacket;
+ public PacketPlayOutMapChunk cachedEmptyPacket;

public Chunk(World world, int i, int j) {
this.sections = new ChunkSection[16];
diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java
index 7bb4fbc..ff7a393 100644
--- a/src/main/java/net/minecraft/server/EntityPlayer.java
+++ b/src/main/java/net/minecraft/server/EntityPlayer.java
@@ -9,6 +9,7 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

+import com.google.common.collect.Lists;
import net.minecraft.util.com.google.common.collect.Sets;
import net.minecraft.util.com.mojang.authlib.GameProfile;
import net.minecraft.util.io.netty.buffer.Unpooled;
@@ -20,6 +21,7 @@ import org.apache.logging.log4j.Logger;
import org.bukkit.Bukkit;
import org.bukkit.WeatherType;
import org.bukkit.craftbukkit.CraftWorld;
+import org.bukkit.craftbukkit.chunkio.QueuedChunkPacket;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.craftbukkit.event.CraftEventFactory;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
@@ -234,15 +236,30 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
}

if (!arraylist.isEmpty()) {
- this.playerConnection.sendPacket(new PacketPlayOutMapChunkBulk(arraylist));
+ // CraftBukkit start - use cached chunks
+ // this.playerConnection.sendPacket(new PacketPlayOutMapChunkBulk(arraylist));
+ List<Packet> packets = Lists.newArrayList();
+ iterator1 = arraylist.iterator();
+
+ while (iterator1.hasNext()) {
+ chunk = (Chunk) iterator1.next();
+ if (chunk.cachedPacket == null) chunk.cachedPacket = new PacketPlayOutMapChunkBulk(Lists.newArrayList(chunk));
+ packets.add(chunk.cachedPacket);
+ }
+
Iterator iterator2 = arraylist1.iterator();

while (iterator2.hasNext()) {
TileEntity tileentity = (TileEntity) iterator2.next();

- this.b(tileentity);
+ // this.b(tileentity);
+ Packet updatePacket = tileentity.getUpdatePacket();
+ if (updatePacket != null) packets.add(updatePacket);
}

+ ((WorldServer) this.world).getPlayerChunkMap().queuedChunkThread.chunks.add(new QueuedChunkPacket(Lists.newArrayList(this), packets));
+ // CraftBukkit end
+
iterator2 = arraylist.iterator();

while (iterator2.hasNext()) {
diff --git a/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java b/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java
index e1757ac..e17aae8 100644
--- a/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java
+++ b/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java
@@ -16,6 +16,18 @@ public class PacketPlayOutMapChunk extends Packet {
private boolean inflatedBuffer;
private int size;
private static byte[] buildBuffer = new byte[196864];
+ // CraftBukkit start
+ static final ThreadLocal<Deflater> localDeflater = new ThreadLocal<Deflater>() {
+ @Override
+ protected Deflater initialValue() {
+ // Don't use higher compression level, slows things down too much
+ return new Deflater(4); // Spigot 6 -> 4
+ }
+ };
+ private Chunk chunk;
+ private int i;
+ private boolean compressed;
+ // CraftBukkit end

public PacketPlayOutMapChunk() {}

@@ -23,8 +35,11 @@ public class PacketPlayOutMapChunk extends Packet {
this.a = chunk.locX;
this.b = chunk.locZ;
this.inflatedBuffer = flag;
+
+ this.chunk = chunk;
+ this.i = i;
+ /* CraftBukkit - moved to compress()
ChunkMap chunkmap = a(chunk, flag, i);
- Deflater deflater = new Deflater(4); // Spigot -1 -> 4

this.d = chunkmap.c;
this.c = chunkmap.b;
@@ -38,6 +53,28 @@ public class PacketPlayOutMapChunk extends Packet {
} finally {
deflater.end();
}
+ */
+ }
+
+ public void compress() {
+ if (compressed) return;
+
+ Deflater deflater = localDeflater.get();
+ ChunkMap chunkmap = a(this.chunk, this.inflatedBuffer, this.i);
+
+ this.d = chunkmap.c;
+ this.c = chunkmap.b;
+
+ this.buffer = chunkmap.a;
+
+ deflater.reset();
+ deflater.setInput(chunkmap.a, 0, chunkmap.a.length);
+ deflater.finish();
+
+ this.e = new byte[chunkmap.a.length];
+ this.size = deflater.deflate(this.e);
+
+ compressed = true;
}

public static int c() {
diff --git a/src/main/java/net/minecraft/server/PacketPlayOutMapChunkBulk.java b/src/main/java/net/minecraft/server/PacketPlayOutMapChunkBulk.java
index bf3a139..216d2d4 100644
--- a/src/main/java/net/minecraft/server/PacketPlayOutMapChunkBulk.java
+++ b/src/main/java/net/minecraft/server/PacketPlayOutMapChunkBulk.java
@@ -25,6 +25,8 @@ public class PacketPlayOutMapChunkBulk extends Packet {
return new Deflater(4); // Spigot 6 -> 4
}
};
+ private List<Chunk> list;
+ private boolean compressed;
// CraftBukkit end

public PacketPlayOutMapChunkBulk() {}
@@ -38,6 +40,9 @@ public class PacketPlayOutMapChunkBulk extends Packet {
this.d = new int[i];
this.inflatedBuffers = new byte[i][];
this.h = !list.isEmpty() && !((Chunk) list.get(0)).world.worldProvider.g;
+
+ this.list = list;
+ /* CraftBukkit start - Moved to compress()
int j = 0;

for (int k = 0; k < i; ++k) {
@@ -60,7 +65,6 @@ public class PacketPlayOutMapChunkBulk extends Packet {
this.inflatedBuffers[k] = chunkmap.a;
}

- /* CraftBukkit start - Moved to compress()
Deflater deflater = new Deflater(-1);

try {
@@ -76,8 +80,27 @@ public class PacketPlayOutMapChunkBulk extends Packet {

// Add compression method
public void compress() {
- if (this.buffer != null) {
- return;
+ if (this.compressed) return;
+
+ int j = 0;
+ for (int k = 0; k < this.list.size(); ++k) {
+ Chunk chunk = (Chunk) this.list.get(k);
+ ChunkMap chunkmap = PacketPlayOutMapChunk.a(chunk, true, '\uffff');
+
+ if (buildBuffer.length < j + chunkmap.a.length) {
+ byte[] abyte = new byte[j + chunkmap.a.length];
+
+ System.arraycopy(buildBuffer, 0, abyte, 0, buildBuffer.length);
+ buildBuffer = abyte;
+ }
+
+ System.arraycopy(chunkmap.a, 0, buildBuffer, j, chunkmap.a.length);
+ j += chunkmap.a.length;
+ this.a[k] = chunk.locX;
+ this.b[k] = chunk.locZ;
+ this.c[k] = chunkmap.b;
+ this.d[k] = chunkmap.c;
+ this.inflatedBuffers[k] = chunkmap.a;
}

Deflater deflater = localDeflater.get();
@@ -87,6 +110,7 @@ public class PacketPlayOutMapChunkBulk extends Packet {

this.buffer = new byte[this.buildBuffer.length + 100];
this.size = deflater.deflate(this.buffer);
+ this.compressed = true;
}
// CraftBukkit end

diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
index 3cd8066..76c7d9b 100644
--- a/src/main/java/net/minecraft/server/PlayerChunk.java
+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
@@ -4,7 +4,10 @@ import java.util.ArrayList;
import java.util.List;

// CraftBukkit start
+import com.google.common.collect.Lists;
import org.bukkit.craftbukkit.chunkio.ChunkIOExecutor;
+import org.bukkit.craftbukkit.chunkio.QueuedChunkPacket;
+
import java.util.HashMap;
// CraftBukkit end

@@ -85,7 +88,8 @@ class PlayerChunk {
Chunk chunk = PlayerChunkMap.a(this.playerChunkMap).getChunkAt(this.location.x, this.location.z);

if (chunk.k()) {
- entityplayer.playerConnection.sendPacket(new PacketPlayOutMapChunk(chunk, true, 0));
+ if (chunk.cachedEmptyPacket == null) chunk.cachedEmptyPacket = new PacketPlayOutMapChunk(chunk, true, 0);
+ this.playerChunkMap.queuedChunkThread.chunks.add(new QueuedChunkPacket(Lists.newArrayList(entityplayer), Lists.newArrayList(chunk.cachedEmptyPacket)));
}

this.players.remove(entityplayer); // CraftBukkit
@@ -116,6 +120,9 @@ class PlayerChunk {
}

public void a(int i, int j, int k) {
+ // CraftBukkit - invalidate cached packet
+ PlayerChunkMap.a(this.playerChunkMap).getChunkAt(this.location.x, this.location.z).cachedPacket = null;
+
if (this.dirtyCount == 0) {
PlayerChunkMap.d(this.playerChunkMap).add(this);
}
@@ -164,7 +171,14 @@ class PlayerChunk {
if (this.dirtyCount == 64) {
i = this.location.x * 16;
j = this.location.z * 16;
- this.sendAll(new PacketPlayOutMapChunk(PlayerChunkMap.a(this.playerChunkMap).getChunkAt(this.location.x, this.location.z), (this.f == 0xFFFF), this.f)); // CraftBukkit - send everything (including biome) if all sections flagged
+ // CraftBukkit start - use cached chunk packets
+ // this.sendAll(new PacketPlayOutMapChunk(PlayerChunkMap.a(this.playerChunkMap).getChunkAt(this.location.x, this.location.z), (this.f == 0xFFFF), this.f)); // CraftBukkit - send everything (including biome) if all sections flagged
+ List<Packet> packets = Lists.newArrayList();
+
+ Chunk chunk = PlayerChunkMap.a(this.playerChunkMap).getChunkAt(this.location.x, this.location.z);
+ if (chunk.cachedPacket == null) chunk.cachedPacket = new PacketPlayOutMapChunkBulk(Lists.newArrayList(chunk));
+ packets.add(chunk.cachedPacket);
+ // CraftBukkit end

for (k = 0; k < 16; ++k) {
if ((this.f & 1 << k) != 0) {
@@ -172,10 +186,16 @@ class PlayerChunk {
List list = PlayerChunkMap.a(this.playerChunkMap).getTileEntities(i, l, j, i + 16, l + 16, j + 16);

for (int i1 = 0; i1 < list.size(); ++i1) {
- this.sendTileEntity((TileEntity) list.get(i1));
+ // CraftBukkit start - add update packet behind chunk packet
+ // this.sendTileEntity((TileEntity) list.get(i1));
+ Packet updatePacket = ((TileEntity) list.get(i1)).getUpdatePacket();
+ if (updatePacket != null) packets.add(updatePacket);
+ // CraftBukkit end
}
}
}
+
+ this.playerChunkMap.queuedChunkThread.chunks.add(new QueuedChunkPacket(Lists.newArrayList(this.b), packets)); // CraftBukkit
} else {
this.sendAll(new PacketPlayOutMultiBlockChange(this.dirtyCount, this.dirtyBlocks, PlayerChunkMap.a(this.playerChunkMap).getChunkAt(this.location.x, this.location.z)));

diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
index ae53635..eedeab5 100644
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
@@ -12,6 +12,7 @@ import java.util.LinkedList;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.bukkit.craftbukkit.chunkio.QueuedChunkThread;

public class PlayerChunkMap {

@@ -25,10 +26,16 @@ public class PlayerChunkMap {
private long h;
private final int[][] i = new int[][] { { 1, 0}, { 0, 1}, { -1, 0}, { 0, -1}};
private boolean wasNotEmpty; // CraftBukkit - add field
+ public QueuedChunkThread queuedChunkThread = new QueuedChunkThread();

public PlayerChunkMap(WorldServer worldserver) {
this.world = worldserver;
this.a(worldserver.getMinecraftServer().getPlayerList().s());
+
+ // CraftBukkit - start queued chunk thread
+ Thread queuedChunkThread = new Thread(this.queuedChunkThread);
+ queuedChunkThread.setDaemon(true);
+ queuedChunkThread.start();
}

public WorldServer a() {
diff --git a/src/main/java/org/bukkit/craftbukkit/chunkio/QueuedChunkPacket.java b/src/main/java/org/bukkit/craftbukkit/chunkio/QueuedChunkPacket.java
new file mode 100644
index 0000000..7d31380
--- /dev/null
+++ b/src/main/java/org/bukkit/craftbukkit/chunkio/QueuedChunkPacket.java
@@ -0,0 +1,36 @@
+package org.bukkit.craftbukkit.chunkio;
+
+import java.util.List;
+
+import net.minecraft.server.*;
+
+public class QueuedChunkPacket implements Runnable {
+ private final List<EntityPlayer> players;
+ private final List<? extends Packet> packets;
+
+ public QueuedChunkPacket(final List<EntityPlayer> players, final List<? extends Packet> packets) {
+ this.players = players;
+ this.packets = packets;
+ }
+
+ @Override
+ public void run() {
+ for (Packet packet : this.packets) {
+ if (packet instanceof PacketPlayOutMapChunk) {
+ PacketPlayOutMapChunk chunkPacket = (PacketPlayOutMapChunk) packet;
+
+ chunkPacket.compress();
+ } else if (packet instanceof PacketPlayOutMapChunkBulk) {
+ PacketPlayOutMapChunkBulk chunkPacket = (PacketPlayOutMapChunkBulk) packet;
+
+ chunkPacket.compress();
+ }
+
+ for(EntityPlayer player : this.players) {
+ if (player.playerConnection.isDisconnected()) continue;
+
+ player.playerConnection.sendPacket(packet);
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/bukkit/craftbukkit/chunkio/QueuedChunkThread.java b/src/main/java/org/bukkit/craftbukkit/chunkio/QueuedChunkThread.java
new file mode 100644
index 0000000..5f2d160
--- /dev/null
+++ b/src/main/java/org/bukkit/craftbukkit/chunkio/QueuedChunkThread.java
@@ -0,0 +1,21 @@
+package org.bukkit.craftbukkit.chunkio;
+
+import java.util.concurrent.LinkedBlockingDeque;
+
+import net.minecraft.server.Chunk;
+import net.minecraft.server.MinecraftServer;
+
+public class QueuedChunkThread implements Runnable {
+ public LinkedBlockingDeque<QueuedChunkPacket> chunks = new LinkedBlockingDeque<QueuedChunkPacket>();
+
+ @Override
+ public void run() {
+ while (MinecraftServer.getServer().isRunning()) {
+ try {
+ this.chunks.take().run();
+ } catch(InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
--
1.8.5.2 (Apple Git-48)