From f0c97f24b8861a940177550dc7d438dcc2c52a47 Mon Sep 17 00:00:00 2001
From: Mike Primm <mike@primmhome.com>
Date: Sat, 18 Jan 2014 13:57:03 -0600
Subject: [PATCH] Port Optimize NextTickList processing patch for 1.7.x

---
 ...097-Optimize-NextTickList-processing.patch | 308 ++++++++++++++++++
 1 file changed, 308 insertions(+)
 create mode 100644 CraftBukkit-Patches/0097-Optimize-NextTickList-processing.patch

diff --git a/CraftBukkit-Patches/0097-Optimize-NextTickList-processing.patch b/CraftBukkit-Patches/0097-Optimize-NextTickList-processing.patch
new file mode 100644
index 0000000000..d956a4010e
--- /dev/null
+++ b/CraftBukkit-Patches/0097-Optimize-NextTickList-processing.patch
@@ -0,0 +1,308 @@
+From dc7f9783926c0f66e83fcca98161f4d513dccf04 Mon Sep 17 00:00:00 2001
+From: Mike Primm <mike@primmhome.com>
+Date: Sat, 18 Jan 2014 13:54:38 -0600
+Subject: [PATCH] Optimize NextTickList processing
+
+
+diff --git a/src/main/java/net/minecraft/server/NextTickListEntry.java b/src/main/java/net/minecraft/server/NextTickListEntry.java
+index 06934a1..d90acfc 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 9672508..74a3294 100644
+--- a/src/main/java/net/minecraft/server/WorldServer.java
++++ b/src/main/java/net/minecraft/server/WorldServer.java
+@@ -29,8 +29,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 M;
+-    private TreeSet N;
++    private org.bukkit.craftbukkit.util.LongObjectHashMap<Set<NextTickListEntry>> tickEntriesByChunk; // Spigot - switch to something better for chunk-wise access
++    private TreeSet<NextTickListEntry> tickEntryQueue; // Spigot    public ChunkProviderServer chunkProviderServer;
+     public ChunkProviderServer chunkProviderServer;
+     public boolean savingDisabled;
+     private boolean O;
+@@ -40,7 +40,8 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
+     private NoteDataList[] S = new NoteDataList[] { new NoteDataList((EmptyClass2) null), new NoteDataList((EmptyClass2) null)};
+     private int T;
+     private static final StructurePieceTreasure[] U = new StructurePieceTreasure[] { new StructurePieceTreasure(Items.STICK, 0, 1, 3, 10), new StructurePieceTreasure(Item.getItemOf(Blocks.WOOD), 0, 1, 3, 10), new StructurePieceTreasure(Item.getItemOf(Blocks.LOG), 0, 1, 3, 10), new StructurePieceTreasure(Items.STONE_AXE, 0, 1, 1, 3), new StructurePieceTreasure(Items.WOOD_AXE, 0, 1, 1, 5), new StructurePieceTreasure(Items.STONE_PICKAXE, 0, 1, 1, 3), new StructurePieceTreasure(Items.WOOD_PICKAXE, 0, 1, 1, 5), new StructurePieceTreasure(Items.APPLE, 0, 2, 3, 5), new StructurePieceTreasure(Items.BREAD, 0, 2, 3, 3), new StructurePieceTreasure(Item.getItemOf(Blocks.LOG2), 0, 1, 3, 10)};
+-    private List V = new ArrayList();
++    private ArrayList<NextTickListEntry> pendingTickEntries = new ArrayList<NextTickListEntry>(); // Spigot
++    private int nextPendingTickEntry; // Spigot
+     private IntHashMap entitiesById;
+ 
+     // CraftBukkit start
+@@ -59,13 +60,15 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
+             this.entitiesById = new IntHashMap();
+         }
+ 
+-        if (this.M == null) {
+-            this.M = new HashSet();
++        // Spigot start
++        if (this.tickEntriesByChunk == null) {
++            this.tickEntriesByChunk = new org.bukkit.craftbukkit.util.LongObjectHashMap<Set<NextTickListEntry>>();
+         }
+ 
+-        if (this.N == null) {
+-            this.N = new TreeSet();
++        if (this.tickEntryQueue == null) {
++            this.tickEntryQueue = new TreeSet<NextTickListEntry>();
+         }
++        // Spigot end
+ 
+         this.Q = new org.bukkit.craftbukkit.CraftTravelAgent(this); // CraftBukkit
+         this.scoreboard = new ScoreboardServer(minecraftserver);
+@@ -445,9 +448,16 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
+     }
+ 
+     public boolean a(int i, int j, int k, Block block) {
+-        NextTickListEntry nextticklistentry = new NextTickListEntry(i, j, k, block);
+-
+-        return this.V.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.a(ent.a(), block)) {
++                return true;
++            }
++        }
++        return false;
++        // Spigot end        
+     }
+ 
+     public void a(int i, int j, int k, Block block, int l) {
+@@ -481,10 +491,9 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
+                 nextticklistentry.a(i1);
+             }
+ 
+-            if (!this.M.contains(nextticklistentry)) {
+-                this.M.add(nextticklistentry);
+-                this.N.add(nextticklistentry);
+-            }
++            // Spigot start
++            addNextTickIfNeeded(nextticklistentry);
++            // Spigot end
+         }
+     }
+ 
+@@ -496,10 +505,9 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
+             nextticklistentry.a((long) l + this.worldData.getTime());
+         }
+ 
+-        if (!this.M.contains(nextticklistentry)) {
+-            this.M.add(nextticklistentry);
+-            this.N.add(nextticklistentry);
+-        }
++        // Spigot start
++        addNextTickIfNeeded(nextticklistentry);
++        // Spigot end
+     }
+ 
+     public void tickEntities() {
+@@ -519,11 +527,11 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
+     }
+ 
+     public boolean a(boolean flag) {
+-        int i = this.N.size();
+-
+-        if (i != this.M.size()) {
+-            throw new IllegalStateException("TickNextTick list out of synch");
+-        } else {
++        // Spigot start
++        int i = this.tickEntryQueue.size(); 
++        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) {
+@@ -539,23 +547,24 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
+             NextTickListEntry nextticklistentry;
+ 
+             for (int j = 0; j < i; ++j) {
+-                nextticklistentry = (NextTickListEntry) this.N.first();
++                nextticklistentry = (NextTickListEntry) this.tickEntryQueue.first(); // Spigot
+                 if (!flag && nextticklistentry.d > this.worldData.getTime()) {
+                     break;
+                 }
+ 
+-                this.N.remove(nextticklistentry);
+-                this.M.remove(nextticklistentry);
+-                this.V.add(nextticklistentry);
++                // Spigot start
++                this.removeNextTickIfNeeded(nextticklistentry);
++                this.pendingTickEntries.add(nextticklistentry);
++                // Spigot end
+             }
+ 
+             this.methodProfiler.b();
+             this.methodProfiler.a("ticking");
+-            Iterator iterator = this.V.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.b(nextticklistentry.a - b0, nextticklistentry.b - b0, nextticklistentry.c - b0, nextticklistentry.a + b0, nextticklistentry.b + b0, nextticklistentry.c + b0)) {
+@@ -586,50 +595,18 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
+             }
+ 
+             this.methodProfiler.b();
+-            this.V.clear();
+-            return !this.N.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.N.iterator();
+-            } else {
+-                iterator = this.V.iterator();
+-                if (!this.V.isEmpty()) {
+-                    a.debug("toBeTicked = " + this.V.size());
+-                }
+-            }
+-
+-            while (iterator.hasNext()) {
+-                NextTickListEntry nextticklistentry = (NextTickListEntry) iterator.next();
+-
+-                if (nextticklistentry.a >= i && nextticklistentry.a < j && nextticklistentry.c >= k && nextticklistentry.c < l) {
+-                    if (flag) {
+-                        this.M.remove(nextticklistentry);
+-                        iterator.remove();
+-                    }
+-
+-                    if (arraylist == null) {
+-                        arraylist = new ArrayList();
+-                    }
+-
+-                    arraylist.add(nextticklistentry);
+-                }
+-            }
+-        }
+-
+-        return arraylist;
++        // Spigot start
++        return this.getNextTickEntriesForChunk(chunk, flag);
++        // Spigot end
+     }
+ 
+     /* CraftBukkit start - We prevent spawning in general, so this butchering is not needed
+@@ -701,13 +678,15 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
+             this.entitiesById = new IntHashMap();
+         }
+ 
+-        if (this.M == null) {
+-            this.M = new HashSet();
++        // Spigot start
++        if (this.tickEntriesByChunk == null) {
++            this.tickEntriesByChunk = new org.bukkit.craftbukkit.util.LongObjectHashMap<Set<NextTickListEntry>>();
+         }
+ 
+-        if (this.N == null) {
+-            this.N = new TreeSet();
++        if (this.tickEntryQueue == null) {
++            this.tickEntryQueue = new TreeSet<NextTickListEntry>();
+         }
++        // Spigot end
+ 
+         this.b(worldsettings);
+         super.a(worldsettings);
+@@ -1037,4 +1016,62 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
+         return Block.b(getType(x, y, z));
+     }
+     // CraftBukkit end
++    // Spigot start
++    private void addNextTickIfNeeded(NextTickListEntry ent) {
++        long coord = LongHash.toLong(ent.a >> 4, ent.c >> 4);
++        Set<NextTickListEntry> chunkset = this.tickEntriesByChunk.get(coord);
++        if (chunkset == null) {
++            chunkset = new HashSet<NextTickListEntry>();
++            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<NextTickListEntry> chunkset = this.tickEntriesByChunk.get(coord);
++        if (chunkset != null) {
++            chunkset.remove(ent);
++            if (chunkset.isEmpty()) {
++                this.tickEntriesByChunk.remove(coord);
++            }
++        }
++        this.tickEntryQueue.remove(ent);
++    }
++    
++    private List<NextTickListEntry> getNextTickEntriesForChunk(Chunk chunk, boolean remove) {
++        long coord = LongHash.toLong(chunk.locX, chunk.locZ);
++        Set<NextTickListEntry> chunkset = this.tickEntriesByChunk.get(coord);
++        List<NextTickListEntry> list = null;
++        if (chunkset != null) {
++            list = new ArrayList<NextTickListEntry>(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.locX << 4);
++            int xmax = xmin + 16;
++            int zmin = (chunk.locZ << 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<NextTickListEntry>();
++                    }
++                    list.add(ent);
++                }
++            }
++        }
++        return list;
++    }
++    // Spigot end 
+ }
+-- 
+1.8.3.4 (Apple Git-47)
+