From d608b1e2af6bc1d8e853aff5c466138653bb7ef2 Mon Sep 17 00:00:00 2001
From: Mike Primm <mike@primmhome.com>
Date: Wed, 24 Apr 2013 01:43:33 -0500
Subject: [PATCH] Improve NextTickList Performance

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 acf8838..1e3e0f8 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 db0345d..1f864a2 100644
--- a/src/main/java/net/minecraft/server/WorldServer.java
+++ b/src/main/java/net/minecraft/server/WorldServer.java
@@ -25,8 +25,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 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 boolean savingDisabled;
     private boolean N;
@@ -36,7 +36,8 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
     private NoteDataList[] R = new NoteDataList[] { new NoteDataList((EmptyClass2) null), new NoteDataList((EmptyClass2) null)};
     private int S;
     private static final StructurePieceTreasure[] T = 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 List U = new ArrayList();
+    private ArrayList<NextTickListEntry> pendingTickEntries = new ArrayList<NextTickListEntry>(); // Spigot
+    private int nextPendingTickEntry; // Spigot
     private IntHashMap entitiesById;
 
     // CraftBukkit start
@@ -54,13 +55,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 org.bukkit.craftbukkit.util.LongObjectHashMap<Set<NextTickListEntry>>();
         }
 
-        if (this.M == null) {
-            this.M = new TreeSet();
+        if (this.tickEntryQueue == null) {
+            this.tickEntryQueue = new TreeSet<NextTickListEntry>();
         }
+        // Spigot end
 
         this.P = new org.bukkit.craftbukkit.CraftTravelAgent(this); // CraftBukkit
         this.scoreboard = new ScoreboardServer(minecraftserver);
@@ -440,9 +443,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.U.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) {
@@ -476,10 +486,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
         }
     }
 
@@ -491,10 +500,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() {
@@ -514,11 +522,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) {
@@ -534,23 +543,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.U.add(nextticklistentry);
+                // Spigot start
+                this.removeNextTickIfNeeded(nextticklistentry);
+                this.pendingTickEntries.add(nextticklistentry);
+                // Spigot end
             }
 
             this.methodProfiler.b();
             this.methodProfiler.a("ticking");
-            Iterator iterator = this.U.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)) {
@@ -581,52 +591,18 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
             }
 
             this.methodProfiler.b();
-            this.U.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.U.iterator();
-                /* CraftBukkit start - Comment out debug spam
-                if (!this.U.isEmpty()) {
-                    System.out.println(this.U.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
     }
 
     /* CraftBukkit start - We prevent spawning in general, so this butchering is not needed
@@ -698,13 +674,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 org.bukkit.craftbukkit.util.LongObjectHashMap<Set<NextTickListEntry>>();
         }
 
-        if (this.M == null) {
-            this.M = new TreeSet();
+        if (this.tickEntryQueue == null) {
+            this.tickEntryQueue = new TreeSet<NextTickListEntry>();
         }
+        // Spigot end
 
         this.b(worldsettings);
         super.a(worldsettings);
@@ -991,4 +969,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<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.x, chunk.z);
+        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.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<NextTickListEntry>();
+                    }
+                    list.add(ent);
+                }
+            }
+        }
+        return list;
+    }
+    // Spigot end 
 }
-- 
1.8.1.2