From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Wed, 15 Apr 2020 18:08:53 -0700
Subject: [PATCH] Optimise entity hard collision checking

Very few entities actually hard collide, so store them in their own
entity slices and provide a special getEntites type call just for them.
This reduces entity collision checking impact (in my testing) by 25%
for crammed entities (shove 130 cows into an 8x6 area in one chunk).
Less crammed entities are likely to show significantly less benefit.
Effectively, this patch optimises crammed entity situations.

diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 3912a524c6ada9c3b6d4abddc1578368c5391bee..b32b0ebf283a8face528ded0b845270953f536f1 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -322,6 +322,41 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne
     }
     // Paper end - optimise entity tracking
 
+    // Tuinity start
+    /**
+     * Overriding this field will cause memory leaks.
+     */
+    private final boolean hardCollides;
+
+    private static final java.util.Map<Class<? extends Entity>, Boolean> cachedOverrides = java.util.Collections.synchronizedMap(new java.util.WeakHashMap<>());
+    {
+        Boolean hardCollides = cachedOverrides.get(this.getClass());
+        if (hardCollides == null) {
+            try {
+                java.lang.reflect.Method getHardCollisionBoxEntityMethod = Entity.class.getMethod("j", Entity.class);
+                java.lang.reflect.Method hasHardCollisionBoxMethod = Entity.class.getMethod("aZ");
+                if (!this.getClass().getMethod(hasHardCollisionBoxMethod.getName(), hasHardCollisionBoxMethod.getParameterTypes()).equals(hasHardCollisionBoxMethod)
+                        || !this.getClass().getMethod(getHardCollisionBoxEntityMethod.getName(), getHardCollisionBoxEntityMethod.getParameterTypes()).equals(getHardCollisionBoxEntityMethod)) {
+                    hardCollides = Boolean.TRUE;
+                } else {
+                    hardCollides = Boolean.FALSE;
+                }
+                cachedOverrides.put(this.getClass(), hardCollides);
+            }
+            catch (ThreadDeath thr) { throw thr; }
+            catch (Throwable thr) {
+                // shouldn't happen, just explode
+                throw new RuntimeException(thr);
+            }
+        }
+        this.hardCollides = hardCollides.booleanValue();
+    }
+
+    public final boolean hardCollides() {
+        return this.hardCollides;
+    }
+    // Tuinity end
+
     public Entity(EntityTypes<?> entitytypes, World world) {
         this.id = Entity.entityCount.incrementAndGet();
         this.passengers = Lists.newArrayList();
@@ -2094,11 +2129,11 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne
         return EnumInteractionResult.PASS;
     }
 
-    public boolean j(Entity entity) {
+    public boolean j(Entity entity) { // Tuinity - diff on change, hard colliding entities override this
         return entity.aZ() && !this.isSameVehicle(entity);
     }
 
-    public boolean aZ() {
+    public boolean aZ() {// Tuinity - diff on change, hard colliding entities override this
         return false;
     }
 
diff --git a/src/main/java/net/minecraft/world/level/IEntityAccess.java b/src/main/java/net/minecraft/world/level/IEntityAccess.java
index 1ff9e771788a4ab52129070e355ca48df2949470..4b0736d1f805b82e031e7cd7077b59365c196ebe 100644
--- a/src/main/java/net/minecraft/world/level/IEntityAccess.java
+++ b/src/main/java/net/minecraft/world/level/IEntityAccess.java
@@ -64,16 +64,25 @@ public interface IEntityAccess {
         return this.b(oclass, axisalignedbb, IEntitySelector.g);
     }
 
+    // Tuinity start - optimise hard collision
+    /**
+     * Not guaranteed to only return hard colliding entities
+     */
+    default List<Entity> getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate<Entity> predicate) {
+        return this.getEntities(entity, axisalignedbb, predicate);
+    }
+    // Tuinity end - optimise hard collision
+
     default Stream<VoxelShape> c(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate<Entity> predicate) {
         if (axisalignedbb.a() < 1.0E-7D) {
             return Stream.empty();
         } else {
             AxisAlignedBB axisalignedbb1 = axisalignedbb.g(1.0E-7D);
 
-            return this.getEntities(entity, axisalignedbb1, predicate.and((entity1) -> {
+            predicate = predicate.and((entity1) -> { // Tuinity - optimise entity hard collisions
                 boolean flag;
 
-                if (entity1.getBoundingBox().c(axisalignedbb1)) {
+                if (true || entity1.getBoundingBox().c(axisalignedbb1)) { // Tuinity - always true, wtf did they think this.getEntities(entity, axisalignedbb1) does?
                     label25:
                     {
                         if (entity == null) {
@@ -91,7 +100,7 @@ public interface IEntityAccess {
 
                 flag = false;
                 return flag;
-            })).stream().map(Entity::getBoundingBox).map(VoxelShapes::a);
+            }); return ((entity != null && entity.hardCollides()) ? this.getEntities(entity, axisalignedbb1, predicate) : this.getHardCollidingEntities(entity, axisalignedbb1, predicate)).stream().map(Entity::getBoundingBox).map(VoxelShapes::a); // Tuinity - optimise entity hard collisions
         }
     }
 
diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java
index 89d43183cf6c628c124aba486db69336f1e017df..bf7ca5c956a2a3a9cd2bfde7a0e4e8805d618797 100644
--- a/src/main/java/net/minecraft/world/level/World.java
+++ b/src/main/java/net/minecraft/world/level/World.java
@@ -1144,6 +1144,35 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
         return this.getChunkAt(i, j, ChunkStatus.FULL, false);
     }
 
+    // Tuinity start - optimise hard collision handling
+    @Override
+    public List<Entity> getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate<Entity> predicate) {
+        return this.getHardCollidingEntities(entity, axisalignedbb, predicate, Lists.newArrayList());
+    }
+
+    public List<Entity> getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate<Entity> predicate, List<Entity> list) {
+        // copied from below
+        int i = MathHelper.floor((axisalignedbb.minX - 2.0D) / 16.0D);
+        int j = MathHelper.floor((axisalignedbb.maxX + 2.0D) / 16.0D);
+        int k = MathHelper.floor((axisalignedbb.minZ - 2.0D) / 16.0D);
+        int l = MathHelper.floor((axisalignedbb.maxZ + 2.0D) / 16.0D);
+
+        net.minecraft.server.level.ChunkProviderServer chunkProvider = ((WorldServer)this).getChunkProvider();
+
+        for (int i1 = i; i1 <= j; ++i1) {
+            for (int j1 = k; j1 <= l; ++j1) {
+                Chunk chunk = chunkProvider.getChunkAtIfLoadedMainThread(i1, j1);
+
+                if (chunk != null) {
+                    chunk.getHardCollidingEntities(entity, axisalignedbb, list, predicate);
+                }
+            }
+        }
+
+        return list;
+    }
+    // Tuinity end - optimise hard collision handling
+
     @Override
     public List<Entity> getEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, @Nullable Predicate<? super Entity> predicate) {
         this.getMethodProfiler().c("getEntities");
diff --git a/src/main/java/net/minecraft/world/level/chunk/Chunk.java b/src/main/java/net/minecraft/world/level/chunk/Chunk.java
index 0727b12b5ff146b4efa9204bf4f495f2f1aa20b9..a0b732a68d9961fc8a42e2aaf0d513605552bd98 100644
--- a/src/main/java/net/minecraft/world/level/chunk/Chunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/Chunk.java
@@ -137,6 +137,56 @@ public class Chunk implements IChunkAccess {
     private final int[] inventoryEntityCounts = new int[16];
     // Paper end
 
+    // Tuinity start - optimise hard collision handling
+    final com.destroystokyo.paper.util.maplist.EntityList[] hardCollidingEntities = new com.destroystokyo.paper.util.maplist.EntityList[16];
+
+    {
+        for (int i = 0, len = this.hardCollidingEntities.length; i < len; ++i) {
+            this.hardCollidingEntities[i] = new com.destroystokyo.paper.util.maplist.EntityList();
+        }
+    }
+
+    public final void getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List<Entity> into, Predicate<Entity> predicate) {
+        // copied from getEntities
+        int min = MathHelper.floor((axisalignedbb.minY - 2.0D) / 16.0D);
+        int max = MathHelper.floor((axisalignedbb.maxY + 2.0D) / 16.0D);
+
+        min = MathHelper.clamp(min, 0, this.hardCollidingEntities.length - 1);
+        max = MathHelper.clamp(max, 0, this.hardCollidingEntities.length - 1);
+
+        for (int k = min; k <= max; ++k) {
+            com.destroystokyo.paper.util.maplist.EntityList entityList = this.hardCollidingEntities[k];
+            Entity[] entities = entityList.getRawData();
+
+            for (int i = 0, len = entityList.size(); i < len; ++i) {
+                Entity entity1 = entities[i];
+                if (entity1.shouldBeRemoved) continue; // Paper
+
+                if (entity1 != entity && entity1.getBoundingBox().intersects(axisalignedbb)) {
+                    if (predicate == null || predicate.test(entity1)) {
+                        into.add(entity1);
+                    }
+
+                    if (!(entity1 instanceof EntityEnderDragon)) {
+                        continue;
+                    }
+
+                    EntityComplexPart[] aentitycomplexpart = ((EntityEnderDragon)entity1).children;
+                    int l = aentitycomplexpart.length;
+
+                    for (int i1 = 0; i1 < l; ++i1) {
+                        EntityComplexPart entitycomplexpart = aentitycomplexpart[i1];
+
+                        if (entitycomplexpart != entity && entitycomplexpart.getBoundingBox().intersects(axisalignedbb) && (predicate == null || predicate.test(entitycomplexpart))) {
+                            into.add(entitycomplexpart);
+                        }
+                    }
+                }
+            }
+        }
+    }
+    // Tuinity end - optimise hard collision handling
+
     public Chunk(World world, ChunkCoordIntPair chunkcoordintpair, BiomeStorage biomestorage, ChunkConverter chunkconverter, TickList<Block> ticklist, TickList<FluidType> ticklist1, long i, @Nullable ChunkSection[] achunksection, @Nullable Consumer<Chunk> consumer) {
         this.sections = new ChunkSection[16];
         this.e = Maps.newHashMap();
@@ -641,7 +691,7 @@ public class Chunk implements IChunkAccess {
         entity.chunkY = k;
         entity.chunkZ = this.loc.z;
         this.entities.add(entity); // Paper - per chunk entity list
-        this.entitySlices[k].add(entity);
+        this.entitySlices[k].add(entity); if (entity.hardCollides()) this.hardCollidingEntities[k].add(entity); // Tuinity - optimise hard colliding entities
         // Paper start
         if (entity instanceof EntityItem) {
             itemCounts[k]++;
@@ -679,7 +729,7 @@ public class Chunk implements IChunkAccess {
             entity.entitySlice = null;
             entity.inChunk = false;
         }
-        if (!this.entitySlices[i].remove(entity)) {
+        if (entity.hardCollides()) this.hardCollidingEntities[i].remove(entity); if (!this.entitySlices[i].remove(entity)) { // Tuinity - optimise hard colliding entities
             return;
         }
         if (entity instanceof EntityItem) {
