From 80766964f2dee726b83d594c94362939f5628a7a Mon Sep 17 00:00:00 2001 From: Colin Godsey Date: Wed, 8 Aug 2018 10:10:06 -0600 Subject: [PATCH] Cache World Entity Type counts Optimizes mob spawning by keeping a count of entities by type diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldEntityList.java b/src/main/java/com/destroystokyo/paper/PaperWorldEntityList.java new file mode 100644 index 0000000000..35104542c5 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/PaperWorldEntityList.java @@ -0,0 +1,121 @@ +package com.destroystokyo.paper; + +import net.minecraft.server.Entity; +import net.minecraft.server.EntityInsentient; +import net.minecraft.server.EnumCreatureType; +import net.minecraft.server.IAnimal; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.World; +import net.minecraft.server.WorldServer; + +import java.util.ArrayList; +import java.util.Collection; + +public class PaperWorldEntityList extends ArrayList { + + private final WorldServer world; + private final int[] entityCounts = new int[EnumCreatureType.values().length]; + + + public PaperWorldEntityList(World world) { + this.world = (WorldServer) world; + } + + @Override + public boolean addAll(Collection c) { + for (Entity e : c) { + updateEntityCount(e, 1); + } + + return super.addAll(c); + } + + @Override + public boolean removeAll(Collection c) { + for (Object e : c) { + if (e instanceof Entity && ((Entity) e).getWorld() == world) { + updateEntityCount((Entity) e, -1); + } + } + + return super.removeAll(c); + } + + @Override + public boolean add(Entity e) { + updateEntityCount(e, 1); + + return super.add(e); + } + + @Override + public Entity remove(int index) { + guard(); + Entity entity = super.remove(index); + if (entity != null) updateEntityCount(entity, -1); + return entity; + } + + @Override + public boolean remove(Object o) { + guard(); + if (super.remove(o)) { + updateEntityCount((Entity) o, -1); + return true; + } + return false; + } + + private void guard() { + if (world.guardEntityList) { + throw new java.util.ConcurrentModificationException(); + } + } + + public int getCreatureCount(EnumCreatureType type) { + return entityCounts[type.ordinal()]; + } + + private void updateEntityCount(EnumCreatureType type, int amt) { + int count = entityCounts[type.ordinal()]; + + count += amt; + + if (count < 0) { + MinecraftServer.LOGGER.error("Paper - Entity count cache has gone negative"); + count = 0; + } + + entityCounts[type.ordinal()] = count; + } + + public void updateEntityCount(Entity entity, int amt) { + if (!(entity instanceof IAnimal)) return; + + if (entity instanceof EntityInsentient) { + EntityInsentient entityinsentient = (EntityInsentient) entity; + if (amt > 0 && entityinsentient.isDespawnableByDefault() && entityinsentient.isPersistent()) { + return; + } + } + if (amt < 0) { + if (!entity.hasBeenCounted) { + return; + } + // Only remove once, we remove from if the entity list is guarded, but may be called later + entity.hasBeenCounted = false; + } else { + if (entity.hasBeenCounted) { + return; + } + entity.hasBeenCounted = true; + } + + for (EnumCreatureType type : EnumCreatureType.values()) { + if (type.matches(entity)) { + updateEntityCount(type, amt); + break; + } + } + } +} diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java index 1554530966..4007f1ebb7 100644 --- a/src/main/java/net/minecraft/server/Entity.java +++ b/src/main/java/net/minecraft/server/Entity.java @@ -124,6 +124,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke private boolean az; public boolean dead; public boolean shouldBeRemoved; // Paper + public boolean hasBeenCounted = false; // Paper public float width; public float length; public float J; diff --git a/src/main/java/net/minecraft/server/EntityInsentient.java b/src/main/java/net/minecraft/server/EntityInsentient.java index 141db48f16..56542d531a 100644 --- a/src/main/java/net/minecraft/server/EntityInsentient.java +++ b/src/main/java/net/minecraft/server/EntityInsentient.java @@ -615,6 +615,7 @@ public abstract class EntityInsentient extends EntityLiving { return true; } + public boolean isDespawnableByDefault() { return isTypeNotPersistent(); } // Paper - sortof an obf helper, too annoying to change visibility here protected boolean isTypeNotPersistent() { return true; } diff --git a/src/main/java/net/minecraft/server/EnumCreatureType.java b/src/main/java/net/minecraft/server/EnumCreatureType.java index 8874a05be5..0af387c059 100644 --- a/src/main/java/net/minecraft/server/EnumCreatureType.java +++ b/src/main/java/net/minecraft/server/EnumCreatureType.java @@ -18,6 +18,8 @@ public enum EnumCreatureType { this.h = flag1; } + public boolean matches(Entity entity) { return innerClass().isAssignableFrom(entity.getClass()); } // Paper + public Class innerClass() { return this.a(); } // Paper - OBFHELPER public Class a() { return this.e; } diff --git a/src/main/java/net/minecraft/server/SpawnerCreature.java b/src/main/java/net/minecraft/server/SpawnerCreature.java index 95d98b65cf..387570ed67 100644 --- a/src/main/java/net/minecraft/server/SpawnerCreature.java +++ b/src/main/java/net/minecraft/server/SpawnerCreature.java @@ -114,7 +114,7 @@ public final class SpawnerCreature { if ((!enumcreaturetype.c() || flag1) && (enumcreaturetype.c() || flag) && (!enumcreaturetype.d() || flag2)) { k = limit * i / SpawnerCreature.b; // CraftBukkit - use per-world limits - int l1 = worldserver.a(enumcreaturetype.a(), k); + int l1 = worldserver.entityList.getCreatureCount(enumcreaturetype); // Paper - entity count cache if (l1 <= k) { BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(); diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java index 85570e4a5d..39175ea0ad 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -50,7 +50,8 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc private static final EnumDirection[] a = EnumDirection.values(); private int b = 63; // Spigot start - guard entity list from removals - public final List entityList = new java.util.ArrayList() + public final com.destroystokyo.paper.PaperWorldEntityList entityList = new com.destroystokyo.paper.PaperWorldEntityList(this); + /* // Paper start { @Override public Entity remove(int index) @@ -74,6 +75,7 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc } } }; + */ // Paper end // Spigot end protected final Set g = com.google.common.collect.Sets.newHashSet(); public Set getEntityUnloadQueue() { return g; };// Paper - OBFHELPER //public final List tileEntityList = Lists.newArrayList(); // Paper - remove unused list @@ -143,7 +145,7 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc public final com.destroystokyo.paper.PaperWorldConfig paperConfig; // Paper public final co.aikar.timings.WorldTimingsHandler timings; // Paper - private boolean guardEntityList; // Spigot + public boolean guardEntityList; // Spigot // Paper - public public static boolean haveWeSilencedAPhysicsCrash; public static String blockLocation; private org.spigotmc.TickLimiter entityLimiter; @@ -1187,6 +1189,7 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc this.getChunkAt(i, j).b(entity); } entity.shouldBeRemoved = true; // Paper + entityList.updateEntityCount(entity, -1); // Paper if (!guardEntityList) { // Spigot - It will get removed after the tick if we are ticking // Paper - always remove from current chunk above // CraftBukkit start - Decrement loop variable field if we've already ticked this entity -- 2.19.0