From 11f449c45b4450a0c4641fe4e8de3b1abe1a1429 Mon Sep 17 00:00:00 2001
From: md_5 <md_5@live.com.au>
Date: Tue, 11 Jun 2013 12:56:02 +1000
Subject: [PATCH] Better Chunk Tick Selection


diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
index cd529ec..53ab411 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
@@ -62,7 +62,7 @@ public abstract class World implements IBlockAccess {
     public Scoreboard scoreboard = new Scoreboard(); // CraftBukkit - protected -> public
     public boolean isStatic;
     // CraftBukkit start - public, longhashset
-    protected LongHashSet chunkTickList = new LongHashSet();
+    // protected LongHashSet chunkTickList = new LongHashSet(); // Spigot
     private int L;
     public boolean allowMonsters;
     public boolean allowAnimals;
@@ -76,6 +76,30 @@ public abstract class World implements IBlockAccess {
     private boolean N;
     int[] I;
 
+    // Spigot start
+    protected final gnu.trove.map.hash.TLongShortHashMap chunkTickList;
+    protected float growthOdds = 100;
+    protected float modifiedOdds = 100;
+    private final byte chunkTickRadius;
+
+    public static long chunkToKey(int x, int z)
+    {
+        long k = ( ( ( (long) x ) & 0xFFFF0000L ) << 16 ) | ( ( ( (long) x ) & 0x0000FFFFL ) << 0 );
+        k     |= ( ( ( (long) z ) & 0xFFFF0000L ) << 32 ) | ( ( ( (long) z ) & 0x0000FFFFL ) << 16 );
+        return k;
+    }
+
+    public static int keyToX(long k)
+    {
+        return (int) ( ( ( k >> 16 ) & 0xFFFF0000 ) | ( k & 0x0000FFFF ) );
+    }
+
+    public static int keyToZ(long k)
+    {
+        return (int) ( ( ( k >> 32 ) & 0xFFFF0000L ) | ( ( k >> 16 ) & 0x0000FFFF ) );
+    }
+    // Spigot end
+
     public BiomeBase getBiome(int i, int j) {
         if (this.isLoaded(i, 0, j)) {
             Chunk chunk = this.getChunkAtWorldCoords(i, j);
@@ -125,6 +149,11 @@ public abstract class World implements IBlockAccess {
         this.ticksPerAnimalSpawns = this.getServer().getTicksPerAnimalSpawns(); // CraftBukkit
         this.ticksPerMonsterSpawns = this.getServer().getTicksPerMonsterSpawns(); // CraftBukkit
         // CraftBukkit end
+        // Spigot start
+        this.chunkTickRadius = (byte) ( ( this.getServer().getViewDistance() < 7 ) ? this.getServer().getViewDistance() : 7 );
+        this.chunkTickList = new gnu.trove.map.hash.TLongShortHashMap( spigotConfig.chunksPerTick * 5, 0.7f, Long.MIN_VALUE, Short.MIN_VALUE );
+        this.chunkTickList.setAutoCompactionFactor( 0 );
+        // Spigot end
 
         this.L = this.random.nextInt(12000);
         this.allowMonsters = true;
@@ -1903,24 +1932,44 @@ public abstract class World implements IBlockAccess {
         int j;
         int k;
 
+        // Spigot start
+        int optimalChunks = spigotConfig.chunksPerTick;
+        // Quick conditions to allow us to exist early
+        if ( optimalChunks <= 0 || players.isEmpty() )
+        {
+            return;
+        }
+        // Keep chunks with growth inside of the optimal chunk range
+        int chunksPerPlayer = Math.min( 200, Math.max( 1, (int) ( ( ( optimalChunks - players.size() ) / (double) players.size() ) + 0.5 ) ) );
+        int randRange = 3 + chunksPerPlayer / 30;
+        // Limit to normal tick radius - including view distance
+        randRange = ( randRange > chunkTickRadius ) ? chunkTickRadius : randRange;
+        // odds of growth happening vs growth happening in vanilla
+        this.growthOdds = this.modifiedOdds = Math.max( 35, Math.min( 100, ( ( chunksPerPlayer + 1 ) * 100F ) / 15F ) );
+        // Spigot end
         for (i = 0; i < this.players.size(); ++i) {
             entityhuman = (EntityHuman) this.players.get(i);
             j = MathHelper.floor(entityhuman.locX / 16.0D);
             k = MathHelper.floor(entityhuman.locZ / 16.0D);
             byte b0 = 7;
 
-            for (int l = -b0; l <= b0; ++l) {
-                for (int i1 = -b0; i1 <= b0; ++i1) {
-                    // CraftBukkit start - Don't tick chunks queued for unload
-                    ChunkProviderServer chunkProviderServer = ((WorldServer) entityhuman.world).chunkProviderServer;
-                    if (chunkProviderServer.unloadQueue.contains(l + j, i1 + k)) {
-                        continue;
-                    }
-                    // CraftBukkit end
-
-                    this.chunkTickList.add(org.bukkit.craftbukkit.util.LongHash.toLong(l + j, i1 + k)); // CraftBukkit
+            // Spigot start - Always update the chunk the player is on
+            long key = chunkToKey( j, k );
+            int existingPlayers = Math.max( 0, chunkTickList.get( key ) ); // filter out -1
+            chunkTickList.put(key, (short) (existingPlayers + 1));
+
+            // Check and see if we update the chunks surrounding the player this tick
+            for ( int chunk = 0; chunk < chunksPerPlayer; chunk++ )
+            {
+                int dx = ( random.nextBoolean() ? 1 : -1 ) * random.nextInt( randRange );
+                int dz = ( random.nextBoolean() ? 1 : -1 ) * random.nextInt( randRange );
+                long hash = chunkToKey( dx + j, dz + k );
+                if ( !chunkTickList.contains( hash ) && this.isChunkLoaded( dx + j, dz + k ) )
+                {
+                    chunkTickList.put( hash, (short) -1 ); // no players
                 }
             }
+            // Spigot End
         }
 
         this.methodProfiler.b();
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
index b9b967f..3a8856d 100644
--- a/src/main/java/net/minecraft/server/WorldServer.java
+++ b/src/main/java/net/minecraft/server/WorldServer.java
@@ -306,10 +306,20 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
         // CraftBukkit start
         // Iterator iterator = this.chunkTickList.iterator();
 
-        for (long chunkCoord : this.chunkTickList.popAll()) {
+        // Spigot start
+        for (gnu.trove.iterator.TLongShortIterator iter = chunkTickList.iterator(); iter.hasNext();) {
+            iter.advance();
+            long chunkCoord = iter.key();
+            int chunkX = World.keyToX(chunkCoord);
+            int chunkZ = World.keyToZ(chunkCoord);
+            // If unloaded, or in procedd of being unloaded, drop it
+            if ( ( !this.isChunkLoaded( chunkX, chunkZ ) ) || ( this.chunkProviderServer.unloadQueue.contains( chunkX, chunkZ ) ) )
+            {
+                iter.remove();
+                continue;
+            }
+            // Spigot end
             // ChunkCoordIntPair chunkcoordintpair = (ChunkCoordIntPair) iterator.next();
-            int chunkX = LongHash.msw(chunkCoord);
-            int chunkZ = LongHash.lsw(chunkCoord);
             int k = chunkX * 16;
             int l = chunkZ * 16;
 
@@ -400,6 +410,7 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
 
                         if (block.isTicking()) {
                             ++i;
+                            this.growthOdds = (iter.value() < 1) ? this.modifiedOdds : 100; // Spigot - grow fast if no players are in this chunk (value = player count)
                             block.a(this, k2 + k, i3 + chunksection.getYPosition(), l2 + l, this.random);
                         }
                     }
@@ -408,6 +419,12 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
 
             this.methodProfiler.b();
         }
+        // Spigot Start
+        if ( spigotConfig.clearChunksOnTick )
+        {
+            chunkTickList.clear();
+        }
+        // Spigot End
     }
 
     public boolean a(int i, int j, int k, Block block) {
diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java
index 961ddb4..6ba7f5c 100644
--- a/src/main/java/org/spigotmc/SpigotWorldConfig.java
+++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java
@@ -68,4 +68,15 @@ public class SpigotWorldConfig
         config.addDefault( "world-settings.default." + path, def );
         return config.getString( "world-settings." + worldName + "." + path, config.getString( "world-settings.default." + path ) );
     }
+
+    public int chunksPerTick;
+    public boolean clearChunksOnTick;
+    private void chunksPerTick()
+    {
+        chunksPerTick = getInt( "chunks-per-tick", 650 );
+        log( "Chunks to Grow per Tick: " + chunksPerTick );
+
+        clearChunksOnTick = getBoolean( "clear-tick-list", false );
+        log( "Clear tick list: " + false );
+    }
 }
-- 
1.8.3.2