From 2cd65f7b6d5b4a4feca664d8bf958e097a21e2b0 Mon Sep 17 00:00:00 2001 From: Spottedleaf 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/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java index d802acef71..09137d8785 100644 --- a/src/main/java/net/minecraft/server/Chunk.java +++ b/src/main/java/net/minecraft/server/Chunk.java @@ -90,6 +90,54 @@ public class Chunk implements IChunkAccess { private final int[] inventoryEntityCounts = new int[16]; // Paper end + // Paper 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 into) { + // 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)) { + into.add(entity1); + + if (!(entity1 instanceof EntityEnderDragon)) { + continue; + } + + EntityComplexPart[] aentitycomplexpart = ((EntityEnderDragon) entity1).getComplexParts(); + int l = aentitycomplexpart.length; + + for (int i1 = 0; i1 < l; ++i1) { + EntityComplexPart entitycomplexpart = aentitycomplexpart[i1]; + + if (entitycomplexpart != entity && entitycomplexpart.getBoundingBox().intersects(axisalignedbb)) { + into.add(entitycomplexpart); + } + } + } + } + } + } + // Paper end - optimise hard collision handling + public Chunk(World world, ChunkCoordIntPair chunkcoordintpair, BiomeStorage biomestorage, ChunkConverter chunkconverter, TickList ticklist, TickList ticklist1, long i, @Nullable ChunkSection[] achunksection, @Nullable Consumer consumer) { this.sections = new ChunkSection[16]; this.e = Maps.newHashMap(); @@ -525,7 +573,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); // Paper - optimise hard colliding entities // Paper start if (entity instanceof EntityItem) { itemCounts[k]++; @@ -562,7 +610,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)) { // Paper - optimise hard colliding entities return; } if (entity instanceof EntityItem) { diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java index d88e851c4a..89e5fbe51d 100644 --- a/src/main/java/net/minecraft/server/Entity.java +++ b/src/main/java/net/minecraft/server/Entity.java @@ -210,6 +210,40 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke } // CraftBukkit end + // Paper start + /** + * Overriding this field will cause memory leaks. + */ + private final boolean hardCollides; + + private static final java.util.Map, Boolean> cachedOverrides = java.util.Collections.synchronizedMap(new java.util.WeakHashMap<>()); + { + Boolean hardCollides = cachedOverrides.get(this.getClass()); + if (hardCollides == null) { + try { + Object getHardCollisionBoxMethod = Entity.class.getMethod("au"); + Object getHardCollisionBoxEntityMethod = Entity.class.getMethod("j", Entity.class); + if (!this.getClass().getMethod("au").equals(getHardCollisionBoxMethod)) { + hardCollides = Boolean.TRUE; + } else if (!this.getClass().getMethod("j", Entity.class).equals(getHardCollisionBoxEntityMethod)) { + hardCollides = Boolean.TRUE; + } else { + hardCollides = Boolean.FALSE; + } + cachedOverrides.put(this.getClass(), hardCollides); + } catch (Throwable thr) { + // shouldn't happen, just explode + throw new RuntimeException(thr); + } + } + this.hardCollides = hardCollides.booleanValue(); + } + + public final boolean hardCollides() { + return this.hardCollides; + } + // Paper end + public Entity(EntityTypes entitytypes, World world) { this.id = Entity.entityCount.incrementAndGet(); this.passengers = Lists.newArrayList(); diff --git a/src/main/java/net/minecraft/server/EntityEnderDragon.java b/src/main/java/net/minecraft/server/EntityEnderDragon.java index af10fc36e0..2887cb14e4 100644 --- a/src/main/java/net/minecraft/server/EntityEnderDragon.java +++ b/src/main/java/net/minecraft/server/EntityEnderDragon.java @@ -847,6 +847,7 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster { @Override public void checkDespawn() {} + public final EntityComplexPart[] getComplexParts() { return this.eo(); } // Paper - OBFHELPER public EntityComplexPart[] eo() { return this.children; } diff --git a/src/main/java/net/minecraft/server/IEntityAccess.java b/src/main/java/net/minecraft/server/IEntityAccess.java index 4157e50e4d..5135308fb6 100644 --- a/src/main/java/net/minecraft/server/IEntityAccess.java +++ b/src/main/java/net/minecraft/server/IEntityAccess.java @@ -42,17 +42,26 @@ public interface IEntityAccess { return this.b(oclass, axisalignedbb, IEntitySelector.f); } + // Paper start - optimise hard collision + /** + * Not guaranteed to only return hard colliding entites + */ + default List getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb) { + return this.getEntities(entity, axisalignedbb); + } + // Paper end - optimise hard collision + default Stream b(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Set set) { if (axisalignedbb.a() < 1.0E-7D) { return Stream.empty(); } else { AxisAlignedBB axisalignedbb1 = axisalignedbb.g(1.0E-7D); - Stream stream = this.getEntities(entity, axisalignedbb1).stream().filter((entity1) -> { // Paper - decompile fix + Stream stream = ((entity != null && entity.hardCollides()) ? this.getEntities(entity, axisalignedbb) : this.getHardCollidingEntities(entity, axisalignedbb1)).stream().filter((entity1) -> { // Paper - decompile fix // Paper - optimise hard collision return !set.contains(entity1); }).filter((entity1) -> { return entity == null || !entity.isSameVehicle(entity1); }).flatMap((entity1) -> { - return Stream.of(entity1.au(), entity == null ? null : entity.j(entity1)); + return Stream.of(entity1.au(), entity == null ? null : entity.j(entity1)); // Paper - optimise hard collision - diff on change, these are the methods that only hard colliding entities override }).filter(Objects::nonNull); return stream.filter(axisalignedbb1::c).map(VoxelShapes::a); diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java index c8619af2cf..899c535c40 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -1178,6 +1178,32 @@ public abstract class World implements GeneratorAccess, AutoCloseable { return this.getChunkAt(i, j, ChunkStatus.FULL, false); } + // Paper start - optimise hard collision handling + @Override + public List getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb) { + // copied from below + List list = Lists.newArrayList(); + 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); + + ChunkProviderServer chunkProvider = ((ChunkProviderServer)this.chunkProvider); + + 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); + } + } + } + + return list; + } + // Paper end - optimise hard collision handling + @Override public List getEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, @Nullable Predicate predicate) { this.getMethodProfiler().c("getEntities"); -- 2.26.2