papermc/Spigot-Server-Patches/0477-Optimise-entity-hard-collision-checking.patch
Aikar 9fa6ba267c
Optimize Light Engine
Massive update to light to improve performance and chunk loading/generation.

1) Massive bit packing/unpacking optimizations and inlining.
  A lot of performance has to do with constant packing and unpacking of bits.
  We now inline a most bit operations, and re-use base x/y/z bits in many places.
  This helps with cpu level processing to just do all the math at once instead
  of having to jump in and out of function calls.

  This much logic also is likely over the JVM Inline limit for JIT too.
2) Applied a few of JellySquid's Phosphor mod optimizations such as
  - ensuring we don't notify neighbor chunks when neighbor chunk doesn't need to be notified
  - reduce hasLight checks in initializing light, and prob some more, they are tagged JellySquid where phosphor influence was used.
3) Optimize hot path accesses to getting updating chunk to have less branching
4) Optimize getBlock accesses to have less branching, and less unpacking
5) Have a separate urgent bucket for chunk light tasks. These tasks will always cut in line over non blocking light tasks.
6) Retain chunk priority while light tasks are enqueued. So if a task comes in at high priority but the queue is full
   of tasks already at a lower priority, before the task was simply added to the end. Now it can cut in line to the front.
   this applies for both urgent and non urgent tasks.
7) Buffer non urgent tasks even if queueUpdate is called multiple times to improve efficiency.
8) Fix NPE risk that crashes server in getting nibble data

Fixes #3489
Fixes #3363
2020-06-07 20:31:29 -04:00

216 lines
11 KiB
Diff

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/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
index 74d110874d0a9c1603d6b2d77753179ba49811ea..cf86ce24e12068d6ff7ae43cb1fd6fe665c24932 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<Entity> 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<Block> ticklist, TickList<FluidType> ticklist1, long i, @Nullable ChunkSection[] achunksection, @Nullable Consumer<Chunk> consumer) {
this.sections = new ChunkSection[16];
this.e = Maps.newHashMap();
@@ -545,7 +593,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]++;
@@ -582,7 +630,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 7fb34a86dc35cb4bf51e1ce5220e56642096fece..f20f798f0f7bb765ffdab8672f4bf77a60fa52d2 100644
--- a/src/main/java/net/minecraft/server/Entity.java
+++ b/src/main/java/net/minecraft/server/Entity.java
@@ -211,6 +211,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<Class<? extends Entity>, 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 5bf99e0028b886a6ba1d2f39f8dc4cd3c9aa0943..aecdaacfc7da560759bc513680d76f55820d5046 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 4157e50e4d99c029759bffcb48a8d645487554c8..5135308fb6137a34ed6fd061f0a210de6de4e81c 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<Entity> getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb) {
+ return this.getEntities(entity, axisalignedbb);
+ }
+ // Paper end - optimise hard collision
+
default Stream<VoxelShape> b(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Set<Entity> set) {
if (axisalignedbb.a() < 1.0E-7D) {
return Stream.empty();
} else {
AxisAlignedBB axisalignedbb1 = axisalignedbb.g(1.0E-7D);
- Stream<AxisAlignedBB> stream = this.getEntities(entity, axisalignedbb1).stream().filter((entity1) -> { // Paper - decompile fix
+ Stream<AxisAlignedBB> 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 8cf191ec7f7c825e3d0996b7e224956a569ab2ba..9f4268202653f6f0ebed49cd67ae691a8b18ccd2 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
@@ -1179,6 +1179,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<Entity> getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb) {
+ // 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);
+
+ 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<Entity> getEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, @Nullable Predicate<? super Entity> predicate) {
this.getMethodProfiler().c("getEntities");