From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Aikar Date: Mon, 11 Aug 2014 21:46:33 -0500 Subject: [PATCH] Optimize TileEntity ticking Re-organizes the servers TileEntity Tick List to be bucketed by type. This allows the server to skip buckets of Tile Entities 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. This change also adds control into the interval of every TileEntity, giving the server owner control on how fast a TileEntity ticks, slowing it down if they must (Such as chest), to improve performance. diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/Chunk.java +++ b/src/main/java/net/minecraft/server/Chunk.java @@ -0,0 +0,0 @@ public class Chunk { public final int locX; public final int locZ; private boolean w; + public org.github.paperspigot.ChunkTileEntityList tileEntityTickList; // PaperSpigot public Map tileEntities; public List[] entitySlices; public boolean done; @@ -0,0 +0,0 @@ public class Chunk { this.b = new int[256]; this.c = new boolean[256]; this.tileEntities = new HashMap(); + tileEntityTickList = new org.github.paperspigot.ChunkTileEntityList(world); // PaperSpigot this.x = 4096; this.entitySlices = new List[16]; this.world = world; @@ -0,0 +0,0 @@ public class Chunk { } // Spigot End - this.world.a(tileentity); + // this.world.a(tileentity); // Handled by Improved Tick List (Only loaded chunks iterate) } for (int i = 0; i < this.entitySlices.length; ++i) { diff --git a/src/main/java/net/minecraft/server/TileEntity.java b/src/main/java/net/minecraft/server/TileEntity.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/TileEntity.java +++ b/src/main/java/net/minecraft/server/TileEntity.java @@ -0,0 +0,0 @@ public class TileEntity { public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getTileEntityTimings(this); // Spigot private static final Logger a = LogManager.getLogger(); + public boolean isAdded = false; // PaperSpigot - optimize contains checks private static Map i = new HashMap(); private static Map j = new HashMap(); protected World world; diff --git a/src/main/java/net/minecraft/server/TileEntityBeacon.java b/src/main/java/net/minecraft/server/TileEntityBeacon.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/TileEntityBeacon.java +++ b/src/main/java/net/minecraft/server/TileEntityBeacon.java @@ -0,0 +0,0 @@ public class TileEntityBeacon extends TileEntity implements IInventory { public TileEntityBeacon() {} public void h() { - if (this.world.getTime() % 80L == 0L) { + // if (this.world.getTime() % 80L == 0L) { // PaperSpigot - controlled by Improved Tick handling this.y(); this.x(); - } + // } // PaperSpigot } private void x() { diff --git a/src/main/java/net/minecraft/server/TileEntityChest.java b/src/main/java/net/minecraft/server/TileEntityChest.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/TileEntityChest.java +++ b/src/main/java/net/minecraft/server/TileEntityChest.java @@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/TileEntityEnderChest.java +++ b/src/main/java/net/minecraft/server/TileEntityEnderChest.java @@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/TileEntityLightDetector.java +++ b/src/main/java/net/minecraft/server/TileEntityLightDetector.java @@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -0,0 +0,0 @@ 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.github.paperspigot.WorldTileEntityList(this); // CraftBukkit - ArrayList -> HashSet // PaperSpigot private List a = new ArrayList(); private List b = new ArrayList(); public List players = new ArrayList(); @@ -0,0 +0,0 @@ public abstract class World implements IBlockAccess { if (tileentity.r()) { iterator.remove(); - if (this.isChunkLoaded(tileentity.x >> 4, tileentity.z >> 4)) { + // if (this.isChunkLoaded(tileentity.x >> 4, tileentity.z >> 4)) { // PaperSpigot - will always be loaded Chunk chunk = this.getChunkAt(tileentity.x >> 4, tileentity.z >> 4); if (chunk != null) { chunk.f(tileentity.x & 15, tileentity.y, tileentity.z & 15); } - } + // } // PaperSpigot } } diff --git a/src/main/java/org/github/paperspigot/ChunkTileEntityList.java b/src/main/java/org/github/paperspigot/ChunkTileEntityList.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 --- /dev/null +++ b/src/main/java/org/github/paperspigot/ChunkTileEntityList.java @@ -0,0 +0,0 @@ +package org.github.paperspigot; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; +import net.minecraft.server.*; +import net.minecraft.util.gnu.trove.map.hash.TObjectIntHashMap; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +public class ChunkTileEntityList { + final ListMultimap tickList = ArrayListMultimap.create(); + + private static final TObjectIntHashMap tileEntityTickIntervals = new TObjectIntHashMap() {{ + // Use 0 for no ticking + // These TE's have empty tick methods, doing nothing. Never bother ticking them. + for (Class ignored : new Class[]{ + TileEntityRecordPlayer.class, + TileEntityDispenser.class, + TileEntityDropper.class, + TileEntitySign.class, + TileEntityNote.class, + TileEntityEnderPortal.class, + TileEntityCommand.class, + TileEntitySkull.class, + TileEntityComparator.class, + TileEntityFlowerPot.class + }) { + put(ignored, 0); + } + + // does findPlayer lookup, so this helps performance to slow down + put(TileEntityChest.class, 20); + put(TileEntityEnderChest.class, 20); + put(TileEntityEnchantTable.class, 20); + + // Slow things down that players won't notice due to craftbukkit "wall time" patches. + put(TileEntityFurnace.class, 10); + 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); + }}; + + public static Integer getInterval(Class cls) { + Integer tickInterval = tileEntityTickIntervals.get(cls); + return tickInterval != null ? tickInterval : 1; + } + + private final World world; + + public ChunkTileEntityList(World world) { + this.world = world; + } + + public Iterator iterator() { + return new ChunkTileEntityIterator(); + } + + + public boolean add(TileEntity entity) { + entity.isAdded = true; + return tickList.put(entity.getClass(), entity); + } + + public boolean remove(TileEntity entity) { + entity.isAdded = false; + return tickList.remove(entity.getClass(), entity); + } + + private class ChunkTileEntityIterator implements Iterator { + Iterator>> typeIterator; + Map.Entry> curType = null; + Iterator listIterator = null; + + { + typeIterator = tickList.asMap().entrySet().iterator(); + nextType(); + } + + private boolean nextType() { + if (typeIterator.hasNext()) { + curType = typeIterator.next(); + final Integer interval = ChunkTileEntityList.getInterval(curType.getKey()); + if (world.getTime() % interval != 0) { + listIterator = curType.getValue().iterator(); + } else { + listIterator = null; + } + return true; + } else { + curType = null; + listIterator = null; + return false; + } + } + + @Override + public boolean hasNext() { + do { + if (listIterator != null && listIterator.hasNext()) { + return true; + } + } while (nextType()); + return false; + } + + @Override + public Object next() { + return listIterator.next(); + } + + @Override + public void remove() { + listIterator.remove(); + } + } +} \ No newline at end of file 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 --- /dev/null +++ b/src/main/java/org/github/paperspigot/WorldTileEntityList.java @@ -0,0 +0,0 @@ +package org.github.paperspigot; + +import com.google.common.collect.Sets; +import net.minecraft.server.Chunk; +import net.minecraft.server.TileEntity; +import net.minecraft.server.World; +import net.minecraft.server.WorldServer; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +public class WorldTileEntityList extends HashSet { + final Set tickList = Sets.newHashSet(); + private final WorldServer world; + + public WorldTileEntityList(World world) { + this.world = (WorldServer) world; + } + + /** + * Adds the TileEntity to the tick list only if it is expected to tick + */ + @Override + public boolean add(Object o) { + return o instanceof TileEntity && ChunkTileEntityList.getInterval(o.getClass()) > 0 && add((TileEntity) o); + } + + private boolean add(TileEntity entity) { + if (entity.isAdded) { + return false; + } + + Chunk chunk = world.getChunkIfLoaded(entity.x >> 4, entity.z >> 4); + return chunk != null && chunk.tileEntityTickList.add(entity); + } + + @Override + public boolean remove(Object o) { + if (!(o instanceof TileEntity)) { + return false; + } + TileEntity entity = (TileEntity) o; + Chunk chunk = world.getChunkIfLoaded(entity.x >> 4, entity.z >> 4); + if (chunk != null) { + return chunk.tileEntityTickList.remove(entity); + } + return true; + } + + @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 { + Iterator chunkIterator; + Iterator chunkTileEntityListIterator; + + { + chunkIterator = world.chunkProviderServer.chunks.values().iterator(); + nextChunk(); + } + + private boolean nextChunk() { + if (chunkIterator.hasNext()) { + final Chunk chunk = chunkIterator.next(); + // Skip chunks queued for unload, and save isLoaded checks + if (!world.chunkProviderServer.unloadQueue.contains(chunk.locX, chunk.locZ)) { + chunkTileEntityListIterator = chunk.tileEntityTickList.iterator(); + } else { + chunkTileEntityListIterator = null; + } + return true; + } else { + chunkTileEntityListIterator = null; + return false; + } + } + + @Override + public boolean hasNext() { + do { + if (chunkTileEntityListIterator != null && chunkTileEntityListIterator.hasNext()) { + return true; + } + } while (nextChunk()); + return false; + } + + @Override + public Object next() { + return chunkTileEntityListIterator.next(); + } + + @Override + public void remove() { + chunkTileEntityListIterator.remove(); + } + } +} \ No newline at end of file --