2020-05-06 09:48:49 +00:00
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
2020-04-16 04:40:47 +00:00
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/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
2020-06-26 01:53:21 +00:00
index cb24f27bc443880b18ffc36236f6ec7174c9d493..b65ae2d6919a67498d0646c5522735086fec00c1 100644
2020-04-16 04:40:47 +00:00
--- a/src/main/java/net/minecraft/server/Chunk.java
+++ b/src/main/java/net/minecraft/server/Chunk.java
2020-06-26 01:53:21 +00:00
@@ -91,6 +91,54 @@ public class Chunk implements IChunkAccess {
2020-04-16 04:40:47 +00:00
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();
+ }
+ }
+
2020-06-26 01:53:21 +00:00
+ public final void getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List<Entity> into, Predicate<Entity> predicate) {
2020-04-16 04:40:47 +00:00
+ // 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
+
2020-06-26 01:53:21 +00:00
+ if (entity1 != entity && entity1.getBoundingBox().intersects(axisalignedbb) && (predicate == null || predicate.test(entity1))) {
2020-04-16 04:40:47 +00:00
+ 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];
+
2020-06-26 01:53:21 +00:00
+ if (entitycomplexpart != entity && entitycomplexpart.getBoundingBox().intersects(axisalignedbb) && (predicate == null || predicate.test(entitycomplexpart))) {
2020-04-16 04:40:47 +00:00
+ into.add(entitycomplexpart);
+ }
+ }
+ }
+ }
+ }
+ }
+ // Paper 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();
2020-06-26 01:53:21 +00:00
@@ -546,7 +594,7 @@ public class Chunk implements IChunkAccess {
2020-04-16 04:40:47 +00:00
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]++;
2020-06-26 01:53:21 +00:00
@@ -583,7 +631,7 @@ public class Chunk implements IChunkAccess {
2020-04-16 04:40:47 +00:00
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
2020-06-26 01:53:21 +00:00
index 8b7f59577416468a89a99954e2a00b3704ffbd40..bf01f4ef7831491ecb29b86f6d459d1e3a767554 100644
2020-04-16 04:40:47 +00:00
--- a/src/main/java/net/minecraft/server/Entity.java
+++ b/src/main/java/net/minecraft/server/Entity.java
2020-06-26 01:53:21 +00:00
@@ -212,6 +212,40 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
2020-04-16 04:40:47 +00:00
}
// CraftBukkit end
+ // Paper 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 {
2020-06-26 01:53:21 +00:00
+ Object getHardCollisionBoxMethod = Entity.class.getMethod("ay");
2020-04-16 04:40:47 +00:00
+ Object getHardCollisionBoxEntityMethod = Entity.class.getMethod("j", Entity.class);
2020-06-26 01:53:21 +00:00
+ if (!this.getClass().getMethod("ay").equals(getHardCollisionBoxMethod)) {
2020-04-16 04:40:47 +00:00
+ 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
2020-06-26 01:53:21 +00:00
index ac12d782539a5f502e8996eb39eb2ddbab46f756..43e3755f0fa75b5f72ec620f3f6647ef6bce38c5 100644
2020-04-16 04:40:47 +00:00
--- a/src/main/java/net/minecraft/server/EntityEnderDragon.java
+++ b/src/main/java/net/minecraft/server/EntityEnderDragon.java
2020-06-26 01:53:21 +00:00
@@ -844,6 +844,7 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster {
2020-04-16 04:40:47 +00:00
@Override
public void checkDespawn() {}
2020-06-26 01:53:21 +00:00
+ public final EntityComplexPart[] getComplexParts() { return this.eK(); } // Paper - OBFHELPER
public EntityComplexPart[] eK() {
2020-04-16 04:40:47 +00:00
return this.children;
}
diff --git a/src/main/java/net/minecraft/server/IEntityAccess.java b/src/main/java/net/minecraft/server/IEntityAccess.java
2020-06-26 01:53:21 +00:00
index 74d4c28246e7db850e6d993e07a84b2a6ca24ce2..24705eef5514bfb078bea6f87e5457e73cee2d5e 100644
2020-04-16 04:40:47 +00:00
--- a/src/main/java/net/minecraft/server/IEntityAccess.java
+++ b/src/main/java/net/minecraft/server/IEntityAccess.java
2020-06-26 01:53:21 +00:00
@@ -53,24 +53,36 @@ public interface IEntityAccess {
return this.b(oclass, axisalignedbb, IEntitySelector.g);
2020-04-16 04:40:47 +00:00
}
+ // Paper start - optimise hard collision
+ /**
+ * Not guaranteed to only return hard colliding entites
+ */
2020-06-26 01:53:21 +00:00
+ default List<Entity> getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate<Entity> predicate) {
2020-04-16 04:40:47 +00:00
+ return this.getEntities(entity, axisalignedbb);
+ }
+ // Paper end - optimise hard collision
+
2020-06-26 01:53:21 +00:00
default Stream<VoxelShape> c(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate<Entity> predicate) {
2020-04-16 04:40:47 +00:00
if (axisalignedbb.a() < 1.0E-7D) {
return Stream.empty();
} else {
AxisAlignedBB axisalignedbb1 = axisalignedbb.g(1.0E-7D);
2020-06-26 01:53:21 +00:00
- return this.getEntities(entity, axisalignedbb1, predicate.and((entity1) -> {
+ // Paper start
+ Predicate<Entity> effectivePredicate = predicate.and((entity1) -> {
2020-04-16 04:40:47 +00:00
return entity == null || !entity.isSameVehicle(entity1);
2020-06-26 01:53:21 +00:00
- })).stream().flatMap((entity1) -> {
+ });
+
+ return ((entity != null && entity.hardCollides()) ? this.getEntities(entity, axisalignedbb, effectivePredicate) : this.getHardCollidingEntities(entity, axisalignedbb1, effectivePredicate)).stream().flatMap((entity1) -> {
if (entity != null) {
- AxisAlignedBB axisalignedbb2 = entity.j(entity1);
+ AxisAlignedBB axisalignedbb2 = entity.j(entity1); // Paper - diff on change, hard collision box method
if (axisalignedbb2 != null && axisalignedbb2.c(axisalignedbb1)) {
return Stream.of(entity1.ay(), axisalignedbb2);
}
}
2020-04-16 04:40:47 +00:00
2020-06-26 01:53:21 +00:00
- return Stream.of(entity1.ay());
+ return Stream.of(entity1.ay()); // Paper - diff on change, hard collision box method
}).filter(Objects::nonNull).map(VoxelShapes::a);
}
}
2020-04-16 04:40:47 +00:00
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
2020-06-26 01:53:21 +00:00
index 91294ff7e86a997cc81f287517b2ff5529fc0a91..cafa47b90320249f84cd1cc4aca1908a4982bffb 100644
2020-04-16 04:40:47 +00:00
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
2020-06-26 01:53:21 +00:00
@@ -1105,6 +1105,32 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
2020-04-16 04:40:47 +00:00
return this.getChunkAt(i, j, ChunkStatus.FULL, false);
}
+ // Paper start - optimise hard collision handling
+ @Override
2020-06-26 01:53:21 +00:00
+ public List<Entity> getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate<Entity> predicate) {
2020-04-16 04:40:47 +00:00
+ // copied from below
+ List<Entity> 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);
+
2020-06-26 01:53:21 +00:00
+ ChunkProviderServer chunkProvider = ((WorldServer)this).getChunkProvider();
2020-04-16 04:40:47 +00:00
+
+ for (int i1 = i; i1 <= j; ++i1) {
+ for (int j1 = k; j1 <= l; ++j1) {
+ Chunk chunk = chunkProvider.getChunkAtIfLoadedMainThread(i1, j1);
+
+ if (chunk != null) {
2020-06-26 01:53:21 +00:00
+ chunk.getHardCollidingEntities(entity, axisalignedbb, list, predicate);
2020-04-16 04:40:47 +00:00
+ }
+ }
+ }
+
+ return list;
+ }
+ // Paper end - optimise hard collision handling
+
@Override
public List<Entity> getEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, @Nullable Predicate<? super Entity> predicate) {
this.getMethodProfiler().c("getEntities");