From f7a916d804a0d98856017ad2cd6904c34db9f19f Mon Sep 17 00:00:00 2001 From: Aikar Date: Sun, 24 Aug 2014 21:35:11 -0400 Subject: [PATCH] Optimize TileEntity Ticking Re-organizes the servers TileEntity Tick List to be bucketed based on their tick interval. We now will not even store a Tile Entity that is known to not have any tick function (half of them), skipping time spent iterating them and checking if they are valid and in a loaded chunk. In other words, a lot of "meta" time wasted on tile entities that would never do anything anyways. Then by reducing chests to 1 in 20 ticks, we cut out 95% of isLoaded checks and findPlayer calls on chests, and 100% of the checks for Signs, the 2 most popular Tile Entities. This cuts out a massive amount of checks revolving around TileEntity ticking. Servers with large amounts of TileEntities should see significant improvement. Finally, this then spreads out the ticking of reduced-rate TileEntities so that they do not all tick on the same tick, distributing the load of some TileEntities like Chest. diff --git a/src/main/java/net/minecraft/server/TileEntity.java b/src/main/java/net/minecraft/server/TileEntity.java index 3c5ec6f..27fedeb 100644 --- a/src/main/java/net/minecraft/server/TileEntity.java +++ b/src/main/java/net/minecraft/server/TileEntity.java @@ -56,6 +56,12 @@ public class TileEntity { } } } + + // Optimized TileEntity Tick changes + private static int tileEntityCounter = 0; + public boolean isAdded = false; + public int tileId = tileEntityCounter++; + // Spigot end public TileEntity() {} diff --git a/src/main/java/net/minecraft/server/TileEntityBeacon.java b/src/main/java/net/minecraft/server/TileEntityBeacon.java index 09313ea..198f908 100644 --- a/src/main/java/net/minecraft/server/TileEntityBeacon.java +++ b/src/main/java/net/minecraft/server/TileEntityBeacon.java @@ -45,7 +45,7 @@ public class TileEntityBeacon extends TileEntity implements IInventory { public TileEntityBeacon() {} public void h() { - if (this.world.getTime() % 80L == 0L) { + if (true || this.world.getTime() % 80L == 0L) { // PaperSpigot - controlled by Improved Tick handling this.y(); this.x(); } diff --git a/src/main/java/net/minecraft/server/TileEntityChest.java b/src/main/java/net/minecraft/server/TileEntityChest.java index c900caf..e27716b 100644 --- a/src/main/java/net/minecraft/server/TileEntityChest.java +++ b/src/main/java/net/minecraft/server/TileEntityChest.java @@ -255,7 +255,7 @@ public class TileEntityChest extends TileEntity implements IInventory { ++this.ticks; float f; - if (!this.world.isStatic && this.o != 0 && (this.ticks + this.x + this.y + this.z) % 200 == 0) { + if (!this.world.isStatic && this.o != 0 && (this.ticks + this.x + this.y + this.z) % 10 == 0) { // PaperSpigot Reduced 200 -> 10 interval due to reduced tick rate from Improved Tick Handling this.o = 0; f = 5.0F; List list = this.world.a(EntityHuman.class, AxisAlignedBB.a((double) ((float) this.x - f), (double) ((float) this.y - f), (double) ((float) this.z - f), (double) ((float) (this.x + 1) + f), (double) ((float) (this.y + 1) + f), (double) ((float) (this.z + 1) + f))); diff --git a/src/main/java/net/minecraft/server/TileEntityEnderChest.java b/src/main/java/net/minecraft/server/TileEntityEnderChest.java index b205ab7..d76a2b2 100644 --- a/src/main/java/net/minecraft/server/TileEntityEnderChest.java +++ b/src/main/java/net/minecraft/server/TileEntityEnderChest.java @@ -11,7 +11,7 @@ public class TileEntityEnderChest extends TileEntity { public void h() { super.h(); - if (++this.k % 20 * 4 == 0) { + if (++this.k % 4 == 0) { // PaperSpigot Reduced (20 * 4) -> 4 interval due to reduced tick rate from Improved Tick Handling this.world.playBlockAction(this.x, this.y, this.z, Blocks.ENDER_CHEST, 1, this.j); } diff --git a/src/main/java/net/minecraft/server/TileEntityLightDetector.java b/src/main/java/net/minecraft/server/TileEntityLightDetector.java index 143cffb..de33df0 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 { public TileEntityLightDetector() {} public void h() { - if (this.world != null && !this.world.isStatic && this.world.getTime() % 20L == 0L) { + if (this.world != null && !this.world.isStatic /*&& this.world.getTime() % 20L == 0L*/) { // PaperSpigot - interval controlled by Improved Tick Handling this.h = this.q(); if (this.h instanceof BlockDaylightDetector) { ((BlockDaylightDetector) this.h).e(this.world, this.x, this.y, this.z); diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java index 33c228b..3bf2af0 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -58,7 +58,7 @@ public abstract class World implements IBlockAccess { }; // Spigot end protected List f = new ArrayList(); - public Set tileEntityList = new HashSet(); // CraftBukkit - ArrayList -> HashSet + public Set tileEntityList = new org.spigotmc.WorldTileEntityList(this); // CraftBukkit - ArrayList -> HashSet private List a = new ArrayList(); private List b = new ArrayList(); public List players = new ArrayList(); diff --git a/src/main/java/org/spigotmc/WorldTileEntityList.java b/src/main/java/org/spigotmc/WorldTileEntityList.java new file mode 100644 index 0000000..e7fd3d3 --- /dev/null +++ b/src/main/java/org/spigotmc/WorldTileEntityList.java @@ -0,0 +1,174 @@ +package org.spigotmc; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import net.minecraft.server.*; +import net.minecraft.util.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 { + private static final TObjectIntHashMap> tileEntityTickIntervals = + new TObjectIntHashMap>() {{ + // Use -1 for no ticking + // These TE's have empty tick methods, doing nothing. Never bother ticking them. + for (Class ignored : new Class[]{ + TileEntityChest.class, // PaperSpigot - Don't tick chests either + TileEntityEnderChest.class, // PaperSpigot - Don't tick chests either + 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); + + // Slow things down that players won't notice due to craftbukkit "wall time" patches. + // These need to be investigated further before they can be safely used here + //put(TileEntityFurnace.class, 20); + //put(TileEntityBrewingStand.class, 10); + + // 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 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> tickList = Maps.newHashMap(); + private final WorldServer world; + + public WorldTileEntityList(World world) { + this.world = (WorldServer) world; + } + + + private Multimap getBucket(int interval) { + Multimap 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 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 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 { + private final Iterator>> intervalIterator; + private Map.Entry> intervalMap = null; + private Iterator 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 buckets = intervalMap.getValue(); + + int bucket = (int) (world.getTime() % interval); + + if (!buckets.isEmpty() && buckets.containsKey(bucket)) { + final Collection 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(); + } + } +} -- 1.9.1