From aa2e76930579e08f02deb52ab784371c7bad93fa Mon Sep 17 00:00:00 2001 From: Aikar 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 5eb55e5..b31973a 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 641db2a..22237c9 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -28,6 +28,19 @@ public abstract class World implements IBlockAccess { // Spigot start - guard entity list from removals public final List entityList = new java.util.ArrayList() { + // PaperSpigot start - move always activated entities to top of tick list + @Override + public boolean add(Entity e) + { + if (e.defaultActivationState) { + super.add(0, e); + return true; + } else { + return super.add(e); + } + } + // PaperSpigot end + @Override public Entity remove(int index) { @@ -53,7 +66,7 @@ public abstract class World implements IBlockAccess { // Spigot end protected final List g = Lists.newArrayList(); public final List h = Lists.newArrayList(); - public final List tileEntityList = Lists.newArrayList(); + public final Set tileEntityList = new org.github.paperspigot.WorldTileEntityList(this); // PaperSpigot private final List b = Lists.newArrayList(); private final List c = Lists.newArrayList(); public final List players = Lists.newArrayList(); @@ -219,7 +232,7 @@ public abstract class World implements IBlockAccess { this.getServer().addWorld(this.world); // CraftBukkit end this.keepSpawnInMemory = this.paperSpigotConfig.keepSpawnInMemory; // PaperSpigot - timings = new SpigotTimings.WorldTimingsHandler(this); // Spigot - code below can generate new world and access timings + timings = new SpigotTimings.WorldTimingsHandler(this); // Spigot - code below can generate new world and access timings this.entityLimiter = new org.spigotmc.TickLimiter(spigotConfig.entityMaxTickTime); this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime); } @@ -1394,9 +1407,21 @@ public abstract class World implements IBlockAccess { guardEntityList = true; // Spigot // CraftBukkit start - Use field for loop variable int entitiesThisCycle = 0; + + // PaperSpigot start - Compute minimum tick index + int minTickIndex = -1; + ListIterator e = entityList.listIterator(); + while (e.hasNext()) { + if (!e.next().defaultActivationState) { + minTickIndex = e.previousIndex(); + break; + } + } + // PaperSpigot end + if (tickPosition < 0) tickPosition = 0; for (entityLimiter.initTick(); - entitiesThisCycle < entityList.size() && (entitiesThisCycle % 10 == 0 || entityLimiter.shouldContinue()); + entitiesThisCycle < entityList.size() && (tickPosition <= minTickIndex || entitiesThisCycle % 10 == 0 || entityLimiter.shouldContinue()); // PaperSpigot tickPosition++, entitiesThisCycle++) { tickPosition = (tickPosition < entityList.size()) ? tickPosition : 0; entity = (Entity) this.entityList.get(this.tickPosition); @@ -1454,19 +1479,13 @@ public abstract class World implements IBlockAccess { this.c.clear(); } // CraftBukkit end + Iterator iterator = this.tileEntityList.iterator(); - // Spigot start - int tilesThisCycle = 0; - for (tileLimiter.initTick(); - tilesThisCycle < tileEntityList.size() && (tilesThisCycle % 10 == 0 || tileLimiter.shouldContinue()); - tileTickPosition++, tilesThisCycle++) { - tileTickPosition = (tileTickPosition < tileEntityList.size()) ? tileTickPosition : 0; - TileEntity tileentity = (TileEntity) this.tileEntityList.get(tileTickPosition); - // Spigot start + while (iterator.hasNext()) { + TileEntity tileentity = (TileEntity) iterator.next(); if (tileentity == null) { getServer().getLogger().severe("Spigot has detected a null entity and has removed it, preventing a crash"); - tilesThisCycle--; - this.tileEntityList.remove(tileTickPosition--); + iterator.remove(); continue; } // Spigot end @@ -1494,8 +1513,7 @@ public abstract class World implements IBlockAccess { } if (tileentity.x()) { - tilesThisCycle--; - this.tileEntityList.remove(tileTickPosition--); + iterator.remove(); this.h.remove(tileentity); if (this.isLoaded(tileentity.getPosition())) { this.getChunkAtWorldCoords(tileentity.getPosition()).e(tileentity.getPosition()); 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..66b6af0 --- /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 { + 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[]{ + 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 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(); + } + } +} -- 2.5.2