diff --git a/Spigot-Server-Patches/0479-Optimise-entity-hard-collision-checking.patch b/Spigot-Server-Patches/0479-Optimise-entity-hard-collision-checking.patch new file mode 100644 index 000000000..ede8383a3 --- /dev/null +++ b/Spigot-Server-Patches/0479-Optimise-entity-hard-collision-checking.patch @@ -0,0 +1,219 @@ +From 9714a144f0d9467d7ad571d35a8603bc1acf1e94 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 719c6f3e7..d745eae42 100644 +--- a/src/main/java/net/minecraft/server/Chunk.java ++++ b/src/main/java/net/minecraft/server/Chunk.java +@@ -92,6 +92,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(); +@@ -539,7 +587,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]++; +@@ -576,7 +624,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 9cb4e5a1e..96a47dd1c 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 af10fc36e..2887cb14e 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 4157e50e4..5135308fb 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 d554d4cf0..5fa045998 100644 +--- a/src/main/java/net/minecraft/server/World.java ++++ b/src/main/java/net/minecraft/server/World.java +@@ -1170,6 +1170,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.25.1 +