From 45cacdd49b261afbdcd0fdb1caa12bbb17f12f94 Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Sun, 5 May 2013 23:41:11 -0500 Subject: [PATCH] Reimpliment next-tick-list scale improvements --- CraftBukkit-Patches/0002-mc-dev-imports.patch | 62 ++++ ...k-list-performance-on-chunk-unloads-.patch | 312 ++++++++++++++++++ 2 files changed, 374 insertions(+) create mode 100644 CraftBukkit-Patches/0041-Improve-next-tick-list-performance-on-chunk-unloads-.patch diff --git a/CraftBukkit-Patches/0002-mc-dev-imports.patch b/CraftBukkit-Patches/0002-mc-dev-imports.patch index 9224b34..525a76d 100644 --- a/CraftBukkit-Patches/0002-mc-dev-imports.patch +++ b/CraftBukkit-Patches/0002-mc-dev-imports.patch @@ -151,6 +151,68 @@ index 0000000..d88f864 + return this.b.getProperty(s + ".name", ""); + } +} +diff --git a/src/main/java/net/minecraft/server/NextTickListEntry.java b/src/main/java/net/minecraft/server/NextTickListEntry.java +new file mode 100644 +index 0000000..52a70a1 +--- /dev/null ++++ b/src/main/java/net/minecraft/server/NextTickListEntry.java +@@ -0,0 +1,56 @@ ++package net.minecraft.server; ++ ++public class NextTickListEntry implements Comparable { ++ ++ private static long g = 0L; ++ public int a; ++ public int b; ++ public int c; ++ public int d; ++ public long e; ++ public int f; ++ private long h; ++ ++ public NextTickListEntry(int i, int j, int k, int l) { ++ this.h = (long) (g++); ++ this.a = i; ++ this.b = j; ++ this.c = k; ++ this.d = l; ++ } ++ ++ public boolean equals(Object object) { ++ if (!(object instanceof NextTickListEntry)) { ++ return false; ++ } else { ++ NextTickListEntry nextticklistentry = (NextTickListEntry) object; ++ ++ return this.a == nextticklistentry.a && this.b == nextticklistentry.b && this.c == nextticklistentry.c && Block.b(this.d, nextticklistentry.d); ++ } ++ } ++ ++ public int hashCode() { ++ return (this.a * 1024 * 1024 + this.c * 1024 + this.b) * 256; ++ } ++ ++ public NextTickListEntry a(long i) { ++ this.e = i; ++ return this; ++ } ++ ++ public void a(int i) { ++ this.f = i; ++ } ++ ++ public int compareTo(NextTickListEntry nextticklistentry) { ++ return this.e < nextticklistentry.e ? -1 : (this.e > nextticklistentry.e ? 1 : (this.f != nextticklistentry.f ? this.f - nextticklistentry.f : (this.h < nextticklistentry.h ? -1 : (this.h > nextticklistentry.h ? 1 : 0)))); ++ } ++ ++ public String toString() { ++ return this.d + ": (" + this.a + ", " + this.b + ", " + this.c + "), " + this.e + ", " + this.f + ", " + this.h; ++ } ++ ++ public int compareTo(Object object) { ++ return this.compareTo((NextTickListEntry) object); ++ } ++} diff --git a/src/main/java/net/minecraft/server/NibbleArray.java b/src/main/java/net/minecraft/server/NibbleArray.java new file mode 100644 index 0000000..5d75a54 diff --git a/CraftBukkit-Patches/0041-Improve-next-tick-list-performance-on-chunk-unloads-.patch b/CraftBukkit-Patches/0041-Improve-next-tick-list-performance-on-chunk-unloads-.patch new file mode 100644 index 0000000..40bdc21 --- /dev/null +++ b/CraftBukkit-Patches/0041-Improve-next-tick-list-performance-on-chunk-unloads-.patch @@ -0,0 +1,312 @@ +From 5cca431dbcda5512e12ccb887ae8e8e741c57b18 Mon Sep 17 00:00:00 2001 +From: Mike Primm +Date: Wed, 24 Apr 2013 01:43:33 -0500 +Subject: [PATCH] Improve next-tick-list performance on chunk unloads, large + queues + + +diff --git a/src/main/java/net/minecraft/server/NextTickListEntry.java b/src/main/java/net/minecraft/server/NextTickListEntry.java +index 52a70a1..08a4240 100644 +--- a/src/main/java/net/minecraft/server/NextTickListEntry.java ++++ b/src/main/java/net/minecraft/server/NextTickListEntry.java +@@ -30,7 +30,7 @@ public class NextTickListEntry implements Comparable { + } + + public int hashCode() { +- return (this.a * 1024 * 1024 + this.c * 1024 + this.b) * 256; ++ return (this.a * 257) ^ this.b ^ (this.c * 60217); // Spigot - better hash + } + + public NextTickListEntry a(long i) { +diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java +index a2f7fee..b0c2c96 100644 +--- a/src/main/java/net/minecraft/server/WorldServer.java ++++ b/src/main/java/net/minecraft/server/WorldServer.java +@@ -28,8 +28,8 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate + private final MinecraftServer server; + public EntityTracker tracker; // CraftBukkit - private final -> public + private final PlayerChunkMap manager; +- private Set L; +- private TreeSet M; ++ private LongObjectHashMap> tickEntriesByChunk; // Spigot - switch to something better for chunk-wise access ++ private TreeSet tickEntryQueue; // Spigot + public ChunkProviderServer chunkProviderServer; + public boolean savingDisabled; + private boolean N; +@@ -38,7 +38,8 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate + private NoteDataList[] Q = new NoteDataList[] { new NoteDataList((EmptyClass2) null), new NoteDataList((EmptyClass2) null)}; + private int R = 0; + private static final StructurePieceTreasure[] S = new StructurePieceTreasure[] { new StructurePieceTreasure(Item.STICK.id, 0, 1, 3, 10), new StructurePieceTreasure(Block.WOOD.id, 0, 1, 3, 10), new StructurePieceTreasure(Block.LOG.id, 0, 1, 3, 10), new StructurePieceTreasure(Item.STONE_AXE.id, 0, 1, 1, 3), new StructurePieceTreasure(Item.WOOD_AXE.id, 0, 1, 1, 5), new StructurePieceTreasure(Item.STONE_PICKAXE.id, 0, 1, 1, 3), new StructurePieceTreasure(Item.WOOD_PICKAXE.id, 0, 1, 1, 5), new StructurePieceTreasure(Item.APPLE.id, 0, 2, 3, 5), new StructurePieceTreasure(Item.BREAD.id, 0, 2, 3, 3)}; +- private ArrayList T = new ArrayList(); ++ private ArrayList pendingTickEntries = new ArrayList(); // Spigot ++ private int nextPendingTickEntry; // Spigot + private IntHashMap entitiesById; + + // CraftBukkit start +@@ -56,13 +57,15 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate + this.entitiesById = new IntHashMap(); + } + +- if (this.L == null) { +- this.L = new HashSet(); ++ // Spigot start ++ if (this.tickEntriesByChunk == null) { ++ this.tickEntriesByChunk = new LongObjectHashMap>(); + } + +- if (this.M == null) { +- this.M = new TreeSet(); ++ if (this.tickEntryQueue == null) { ++ this.tickEntryQueue = new TreeSet(); + } ++ // Spigot end + + this.P = new org.bukkit.craftbukkit.CraftTravelAgent(this); // CraftBukkit + this.scoreboard = new ScoreboardServer(minecraftserver); +@@ -444,9 +447,16 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate + } + + public boolean a(int i, int j, int k, int l) { +- NextTickListEntry nextticklistentry = new NextTickListEntry(i, j, k, l); +- +- return this.T.contains(nextticklistentry); ++ // Spigot start ++ int te_cnt = this.pendingTickEntries.size(); ++ for (int idx = this.nextPendingTickEntry; idx < te_cnt; idx++) { ++ NextTickListEntry ent = this.pendingTickEntries.get(idx); ++ if ((ent.a == i) && (ent.b == j) && (ent.c == k) && Block.b(ent.d, l)) { ++ return true; ++ } ++ } ++ return false; ++ // Spigot end + } + + public void a(int i, int j, int k, int l, int i1) { +@@ -479,10 +489,9 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate + nextticklistentry.a(j1); + } + +- if (!this.L.contains(nextticklistentry)) { +- this.L.add(nextticklistentry); +- this.M.add(nextticklistentry); +- } ++ // Spigot start ++ addNextTickIfNeeded(nextticklistentry); ++ // Spigot end + } + } + +@@ -494,10 +503,9 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate + nextticklistentry.a((long) i1 + this.worldData.getTime()); + } + +- if (!this.L.contains(nextticklistentry)) { +- this.L.add(nextticklistentry); +- this.M.add(nextticklistentry); +- } ++ // Spigot start ++ addNextTickIfNeeded(nextticklistentry); ++ // Spigot end + } + + public void tickEntities() { +@@ -517,11 +525,12 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate + } + + public boolean a(boolean flag) { +- int i = this.M.size(); ++ // Spigot start ++ int i = this.tickEntryQueue.size(); + +- if (i != this.L.size()) { +- throw new IllegalStateException("TickNextTick list out of synch"); +- } else { ++ this.nextPendingTickEntry = 0; ++ { ++ // Spigot end + if (i > 1000) { + // CraftBukkit start - If the server has too much to process over time, try to alleviate that + if (i > 20 * 1000) { +@@ -537,23 +546,24 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate + NextTickListEntry nextticklistentry; + + for (int j = 0; j < i; ++j) { +- nextticklistentry = (NextTickListEntry) this.M.first(); ++ nextticklistentry = (NextTickListEntry) this.tickEntryQueue.first(); // Spigot + if (!flag && nextticklistentry.e > this.worldData.getTime()) { + break; + } + +- this.M.remove(nextticklistentry); +- this.L.remove(nextticklistentry); +- this.T.add(nextticklistentry); ++ // Spigot start ++ this.removeNextTickIfNeeded(nextticklistentry); ++ this.pendingTickEntries.add(nextticklistentry); ++ // Spigot end + } + + this.methodProfiler.b(); + this.methodProfiler.a("ticking"); +- Iterator iterator = this.T.iterator(); +- +- while (iterator.hasNext()) { +- nextticklistentry = (NextTickListEntry) iterator.next(); +- iterator.remove(); ++ // Spigot start ++ for (int j = 0, te_cnt = this.pendingTickEntries.size(); j < te_cnt; j++) { ++ nextticklistentry = pendingTickEntries.get(j); ++ this.nextPendingTickEntry = j + 1; // treat this as dequeued ++ // Spigot end + byte b0 = 0; + + if (this.e(nextticklistentry.a - b0, nextticklistentry.b - b0, nextticklistentry.c - b0, nextticklistentry.a + b0, nextticklistentry.b + b0, nextticklistentry.c + b0)) { +@@ -584,52 +594,18 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate + } + + this.methodProfiler.b(); +- this.T.clear(); +- return !this.M.isEmpty(); +- } ++ // Spigot start ++ this.pendingTickEntries.clear(); ++ this.nextPendingTickEntry = 0; ++ return !this.tickEntryQueue.isEmpty(); ++ // Spigot end ++ } + } + + public List a(Chunk chunk, boolean flag) { +- ArrayList arraylist = null; +- ChunkCoordIntPair chunkcoordintpair = chunk.l(); +- int i = (chunkcoordintpair.x << 4) - 2; +- int j = i + 16 + 2; +- int k = (chunkcoordintpair.z << 4) - 2; +- int l = k + 16 + 2; +- +- for (int i1 = 0; i1 < 2; ++i1) { +- Iterator iterator; +- +- if (i1 == 0) { +- iterator = this.M.iterator(); +- } else { +- iterator = this.T.iterator(); +- /* CraftBukkit start - Comment out debug spam +- if (!this.T.isEmpty()) { +- System.out.println(this.T.size()); +- } +- // CraftBukkit end */ +- } +- +- while (iterator.hasNext()) { +- NextTickListEntry nextticklistentry = (NextTickListEntry) iterator.next(); +- +- if (nextticklistentry.a >= i && nextticklistentry.a < j && nextticklistentry.c >= k && nextticklistentry.c < l) { +- if (flag) { +- this.L.remove(nextticklistentry); +- iterator.remove(); +- } +- +- if (arraylist == null) { +- arraylist = new ArrayList(); +- } +- +- arraylist.add(nextticklistentry); +- } +- } +- } +- +- return arraylist; ++ // Spigot start ++ return this.getNextTickEntriesForChunk(chunk, flag); ++ // Spigot end + } + + public void entityJoinedWorld(Entity entity, boolean flag) { +@@ -706,13 +682,15 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate + this.entitiesById = new IntHashMap(); + } + +- if (this.L == null) { +- this.L = new HashSet(); ++ // Spigot start ++ if (this.tickEntriesByChunk == null) { ++ this.tickEntriesByChunk = new LongObjectHashMap>(); + } + +- if (this.M == null) { +- this.M = new TreeSet(); ++ if (this.tickEntryQueue == null) { ++ this.tickEntryQueue = new TreeSet(); + } ++ // Spigot end + + this.b(worldsettings); + super.a(worldsettings); +@@ -999,4 +977,62 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate + return this.setTypeIdAndData(x, y, z, typeId, data, 3); + } + // CraftBukkit end ++ // Spigot start ++ private void addNextTickIfNeeded(NextTickListEntry ent) { ++ long coord = LongHash.toLong(ent.a >> 4, ent.c >> 4); ++ Set chunkset = this.tickEntriesByChunk.get(coord); ++ if (chunkset == null) { ++ chunkset = new HashSet(); ++ this.tickEntriesByChunk.put(coord, chunkset); ++ } else if (chunkset.contains(ent)) { ++ return; ++ } ++ chunkset.add(ent); ++ this.tickEntryQueue.add(ent); ++ } ++ ++ private void removeNextTickIfNeeded(NextTickListEntry ent) { ++ long coord = LongHash.toLong(ent.a >> 4, ent.c >> 4); ++ Set chunkset = this.tickEntriesByChunk.get(coord); ++ if (chunkset != null) { ++ chunkset.remove(ent); ++ if (chunkset.isEmpty()) { ++ this.tickEntriesByChunk.remove(coord); ++ } ++ } ++ this.tickEntryQueue.remove(ent); ++ } ++ ++ private List getNextTickEntriesForChunk(Chunk chunk, boolean remove) { ++ long coord = LongHash.toLong(chunk.x, chunk.z); ++ Set chunkset = this.tickEntriesByChunk.get(coord); ++ List list = null; ++ if (chunkset != null) { ++ list = new ArrayList(chunkset); ++ if (remove) { ++ this.tickEntriesByChunk.remove(coord); ++ this.tickEntryQueue.removeAll(list); ++ chunkset.clear(); ++ } ++ } ++ // See if any on list of ticks being processed now ++ if (this.nextPendingTickEntry < this.pendingTickEntries.size()) { ++ int xmin = (chunk.x << 4); ++ int xmax = xmin + 16; ++ int zmin = (chunk.z << 4); ++ int zmax = zmin + 16; ++ int te_cnt = this.pendingTickEntries.size(); ++ for (int i = this.nextPendingTickEntry; i < te_cnt; i++) { ++ NextTickListEntry ent = this.pendingTickEntries.get(i); ++ if ((ent.a >= xmin) && (ent.a < xmax) && (ent.c >= zmin) && (ent.c < zmax)) { ++ if (list == null) { ++ list = new ArrayList(); ++ } ++ list.add(ent); ++ } ++ } ++ } ++ return list; ++ } ++ // Spigot end + } +-- +1.8.1.3 +