Paper/Spigot-Server-Patches/0477-Optimise-entity-hard-collision-checking.patch

217 lines
11 KiB
Diff
Raw Normal View History

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 33456b0bb421d253aa8366210f41a9da7dcd1699..750fb07756f7e40b21f8ab0925f2e842aae50f7b 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();
@@ -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 0dbe2dce111673f94d0618c7ac80262189f2926f..324fd07bcee9fed7bdb5b2b47ef01ce341282c93 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<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 c8619af2cf43041e3eebec74e24c7f127a662efe..899c535c4056cd2375ab8f834f03267d405f4bda 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
Entity Activation Range 2.0! Major improvements to restoring behavior Calling this 2.0 as it's a pretty major improvement with more knobs to twist. This update fixes many things. The goal here is to restore vanilla behavior to some degree. Instead of permanent inactive pools of animals, let them show some signs of life some.... Yes this may reduce performance compared to before, but I hope it is minimal. Got to find a balance. Previous EAR logic really compromised vanilla behavior of mobs. This tries to restore it. Changes: 1) All monsters are now classed as Monster. Mojang has an interface, we should use it. - This now includes Shulker, Slimes, see #2 for Phantom and Ghast 2) Villagers and Flying Monsters now have their own separate activation range configs. - Villagers will default to your Animals config 3) Added a bunch of more immunities - Brand new entities are immune for a few seconds - Entities that recently traveled by portal are immune for few seconds - Entities that are leashed to a player are immune - Ender Signals are immune - Entities that are jumping, climbing, dying (lol) are immune - Minecarts are now always immune to the movement restriction 4) Villagers immunity received major overhaul... - Now has many immunities for Villager activities to let them do their work then go back inactive - Such as interacting with doors and workstations should be more normal now - Raids will trigger immunities, in that villagers will run and hide when bell rings. - Raid should keep the entire village immune during the raid to keep gameplay mechanics You can disable raids by game rule if you dont want raids Then the big one..... Wake Up Inactive Entities: One issue plagueing "farms" is that we no longer even let entities move now. Entities become lifeless. A new system has been introduced to wake up inactive entities every so often, to let them stretch their legs, eat some food, play with each other and experience the good entity life. Animals, Villagers, Monsters (Includes Pillagers), and Flying Monsters will now wake up every so often after staying inactive for a very long. This grants them a temporary immunity, that the goal is they will then find "stuff to do" by having a longer activity window. How many to wake up, how often they wake up, and for how long they wake up are all configurable. Current EAR Immunities really don't give some entities enough of a window to find work to then keep them immune for the work to even start. This system should help that. We will only wake up a few entities per tick on the first wave, restoring 1 per type per world per tick. So say you have 10 monsters qualify for inactive wake up, all 8 will wake up on the first eligible tick, and then the 9th will wake up on next tick, 10th on next tick. If for 5 ticks no more inactive wake up, our buffer will have built back up to 5, and then 5 can go next needed tick. This basically incrementally wakes them up, preventing too many from waking up in a single tick, to reduce impact to TPS.
2020-04-27 06:34:51 +02:00
@@ -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<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");