From 8e823d14fadf4eb9eab85ff1da2da88811fa255c Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sun, 8 Mar 2015 01:56:22 -0600
Subject: [PATCH] Optimize TileEntity Ticking


diff --git a/src/main/java/net/minecraft/server/TileEntity.java b/src/main/java/net/minecraft/server/TileEntity.java
index c268a40..1971941 100644
--- a/src/main/java/net/minecraft/server/TileEntity.java
+++ b/src/main/java/net/minecraft/server/TileEntity.java
@@ -21,6 +21,12 @@ public abstract class TileEntity {
     private int h;
     protected Block e;
 
+    // PaperSpigot start - Optimized TileEntity tick changes
+    private static int tileEntityCounter = 0;
+    public boolean isAdded = false;
+    public int tileId = tileEntityCounter++;
+    // PaperSpigot end
+
     public TileEntity() {
         this.position = BlockPosition.ZERO;
         this.h = -1;
diff --git a/src/main/java/net/minecraft/server/TileEntityBeacon.java b/src/main/java/net/minecraft/server/TileEntityBeacon.java
index 4f280dd..3ea1b62 100644
--- a/src/main/java/net/minecraft/server/TileEntityBeacon.java
+++ b/src/main/java/net/minecraft/server/TileEntityBeacon.java
@@ -48,7 +48,7 @@ public class TileEntityBeacon extends TileEntityContainer implements IUpdatePlay
     public TileEntityBeacon() {}
 
     public void c() {
-        if (this.world.getTime() % 80L == 0L) {
+        if (true || this.world.getTime() % 80L == 0L) { // PaperSpigot - Controlled by improved tick handling
             this.m();
         }
 
diff --git a/src/main/java/net/minecraft/server/TileEntityChest.java b/src/main/java/net/minecraft/server/TileEntityChest.java
index 189fa93..07891ec 100644
--- a/src/main/java/net/minecraft/server/TileEntityChest.java
+++ b/src/main/java/net/minecraft/server/TileEntityChest.java
@@ -246,7 +246,7 @@ public class TileEntityChest extends TileEntityContainer implements IUpdatePlaye
         ++this.n;
         float f;
 
-        if (!this.world.isClientSide && this.l != 0 && (this.n + i + j + k) % 200 == 0) {
+        if (!this.world.isClientSide && this.l != 0 && (this.n + i + j + k) % 10 == 0) { // PaperSpigot - Reduced 200 -> 10 due to reduced interval from improved tick handling
             this.l = 0;
             f = 5.0F;
             List list = this.world.a(EntityHuman.class, new AxisAlignedBB((double) ((float) i - f), (double) ((float) j - f), (double) ((float) k - f), (double) ((float) (i + 1) + f), (double) ((float) (j + 1) + f), (double) ((float) (k + 1) + f)));
diff --git a/src/main/java/net/minecraft/server/TileEntityEnderChest.java b/src/main/java/net/minecraft/server/TileEntityEnderChest.java
index 794cdc8..f712885 100644
--- a/src/main/java/net/minecraft/server/TileEntityEnderChest.java
+++ b/src/main/java/net/minecraft/server/TileEntityEnderChest.java
@@ -10,7 +10,7 @@ public class TileEntityEnderChest extends TileEntity implements IUpdatePlayerLis
     public TileEntityEnderChest() {}
 
     public void c() {
-        if (++this.h % 20 * 4 == 0) {
+        if (++this.h % 4 == 0) { // PaperSpigot - Reduced (20 * 4) -> 4 interval due to reduced tick rate from improved tick handling
             this.world.playBlockAction(this.position, Blocks.ENDER_CHEST, 1, this.g);
         }
 
diff --git a/src/main/java/net/minecraft/server/TileEntityLightDetector.java b/src/main/java/net/minecraft/server/TileEntityLightDetector.java
index f75e2de..7119612 100644
--- a/src/main/java/net/minecraft/server/TileEntityLightDetector.java
+++ b/src/main/java/net/minecraft/server/TileEntityLightDetector.java
@@ -5,7 +5,7 @@ public class TileEntityLightDetector extends TileEntity implements IUpdatePlayer
     public TileEntityLightDetector() {}
 
     public void c() {
-        if (this.world != null && !this.world.isClientSide && this.world.getTime() % 20L == 0L) {
+        if (this.world != null && !this.world.isClientSide /*&& this.world.getTime() % 20L == 0L*/)  {  // PaperSpigot - interval controlled by improved tick handling
             this.e = this.w();
             if (this.e instanceof BlockDaylightDetector) {
                 ((BlockDaylightDetector) this.e).f(this.world, this.position);
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
index c93f575..e04603f 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
@@ -53,7 +53,7 @@ public abstract class World implements IBlockAccess {
     // Spigot end
     protected final List<Entity> g = Lists.newArrayList();
     public final List<TileEntity> h = Lists.newArrayList();
-    public final List<TileEntity> tileEntityList = Lists.newArrayList();
+    public final Set<TileEntity> tileEntityList = new org.github.paperspigot.WorldTileEntityList(this); // PaperSpigot
     private final List<TileEntity> b = Lists.newArrayList();
     private final List<TileEntity> c = Lists.newArrayList();
     public final List<EntityHuman> players = Lists.newArrayList();
diff --git a/src/main/java/org/github/paperspigot/WorldTileEntityList.java b/src/main/java/org/github/paperspigot/WorldTileEntityList.java
new file mode 100644
index 0000000..5af5dcc
--- /dev/null
+++ b/src/main/java/org/github/paperspigot/WorldTileEntityList.java
@@ -0,0 +1,168 @@
+package org.github.paperspigot;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import net.minecraft.server.*;
+import gnu.trove.map.hash.TObjectIntHashMap;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+
+public class WorldTileEntityList extends HashSet<TileEntity> {
+    private static final TObjectIntHashMap<Class<? extends TileEntity>> tileEntityTickIntervals =
+            new TObjectIntHashMap<Class<? extends TileEntity>>() {{
+                // Use -1 for no ticking
+                // These TE's have empty tick methods, doing nothing. Never bother ticking them.
+                for (Class<? extends TileEntity> ignored : new Class[]{
+                        BlockJukeBox.TileEntityRecordPlayer.class,
+                        TileEntityDispenser.class,
+                        TileEntityDropper.class,
+                        TileEntitySign.class,
+                        TileEntityNote.class,
+                        TileEntityEnderPortal.class,
+                        TileEntityCommand.class,
+                        TileEntitySkull.class,
+                        TileEntityComparator.class,
+                        TileEntityFlowerPot.class
+                }) {
+                    put(ignored, -1);
+                }
+
+                // does findPlayer lookup, so this helps performance to slow down
+                put(TileEntityEnchantTable.class, 20);
+
+                // Vanilla controlled values - These are checks already done in vanilla, so don't tick on ticks we know
+                // won't do anything anyways
+                put(TileEntityBeacon.class, 80);
+                put(TileEntityLightDetector.class, 20);
+            }};
+
+    private static int getInterval(Class<? extends TileEntity> cls) {
+        int tickInterval = tileEntityTickIntervals.get(cls);
+        return tickInterval != 0 ? tickInterval : 1;
+    }
+
+    private static int getBucketId(TileEntity entity, Integer interval) {
+        return entity.tileId % interval;
+    }
+
+    private final Map<Integer, Multimap<Integer, TileEntity>> tickList = Maps.newHashMap();
+    private final WorldServer world;
+
+    public WorldTileEntityList(World world) {
+        this.world = (WorldServer) world;
+    }
+
+
+    private Multimap<Integer, TileEntity> getBucket(int interval) {
+        Multimap<Integer, TileEntity> intervalBucket = tickList.get(interval);
+        if (intervalBucket == null) {
+            intervalBucket = ArrayListMultimap.create();
+            tickList.put(interval, intervalBucket);
+        }
+        return intervalBucket;
+    }
+
+    /**
+     * Adds the TileEntity to the tick list only if it is expected to tick
+     */
+    @Override
+    public boolean add(TileEntity entity) {
+        if (entity.isAdded) {
+            return false;
+        }
+
+        int interval = getInterval(entity.getClass());
+        if (interval > 0) {
+            entity.isAdded = true;
+            int bucket = getBucketId(entity, interval);
+            Multimap<Integer, TileEntity> typeBucket = getBucket(interval);
+            return typeBucket.put(bucket, entity);
+        }
+        return false;
+    }
+
+    @Override
+    public boolean remove(Object o) {
+        if (!(o instanceof TileEntity)) {
+            return false;
+        }
+        TileEntity entity = (TileEntity) o;
+        if (!entity.isAdded) {
+            return false;
+        }
+        entity.isAdded = false;
+        int interval = getInterval(entity.getClass());
+        int bucket = getBucketId(entity, interval);
+        Multimap<Integer, TileEntity> typeBucket = getBucket(interval);
+        return typeBucket.remove(bucket, entity);
+    }
+
+    @Override
+    public Iterator iterator() {
+        return new WorldTileEntityIterator();
+    }
+
+    @Override
+    public boolean contains(Object o) {
+        return o instanceof TileEntity && ((TileEntity) o).isAdded;
+    }
+
+    private class WorldTileEntityIterator implements Iterator<TileEntity> {
+        private final Iterator<Map.Entry<Integer, Multimap<Integer, TileEntity>>> intervalIterator;
+        private Map.Entry<Integer, Multimap<Integer, TileEntity>> intervalMap = null;
+        private Iterator<TileEntity> listIterator = null;
+
+        protected WorldTileEntityIterator() {
+            intervalIterator = tickList.entrySet().iterator();
+            nextInterval();
+        }
+
+        private boolean nextInterval() {
+            listIterator = null;
+            if (intervalIterator.hasNext()) {
+                intervalMap = intervalIterator.next();
+
+                final Integer interval = intervalMap.getKey();
+                final Multimap<Integer, TileEntity> buckets = intervalMap.getValue();
+
+                int bucket = (int) (world.getTime() % interval);
+
+                if (!buckets.isEmpty() && buckets.containsKey(bucket)) {
+                    final Collection<TileEntity> tileList = buckets.get(bucket);
+
+                    if (tileList != null && !tileList.isEmpty()) {
+                        listIterator = tileList.iterator();
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+
+        }
+
+        @Override
+        public boolean hasNext() {
+            do {
+                if (listIterator != null && listIterator.hasNext()) {
+                    return true;
+                }
+            } while (nextInterval());
+            return false;
+        }
+
+        @Override
+        public TileEntity next() {
+            return listIterator.next();
+        }
+
+        @Override
+        public void remove() {
+            listIterator.remove();
+        }
+    }
+}
\ No newline at end of file
-- 
1.9.5.msysgit.1