If the request to shut down the server is received while we are in a watchdog hang, immediately treat it as a crash and begin the shutdown process. Shutdown process is now improved to also shutdown cleanly when not using restart scripts either. If a server is deadlocked, a server owner can send SIGHUP (or any other signal the JVM understands to shut down as it currently does) and the watchdog will no longer need to wait until the full timeout, allowing you to trigger a close process and try to shut the server down gracefully, saving player and world data. Previously there was no way to trigger this outside of waiting for a full watchdog timeout, which may be set to a really long time... Additionally, fix everything to do with shutting the server down asynchronously. Previously, nearly everything about the process was fragile and unsafe. Main might not have actually been frozen, and might still be manipulating state. Or, some reuest might ask main to do something in the shutdown but main is dead. Or worse, other things might start closing down items such as the Console or Thread Pool before we are fully shutdown. This change tries to resolve all of these issues by moving everything into the stop method and guaranteeing only one thread is stopping the server. We then issue Thread Death to the main thread of another thread initiates the stop process. We have to ensure Thread Death propagates correctly though to stop main completely. This is to ensure that if main isn't truely stuck, it's not manipulating state we are trying to save.
220 lines
11 KiB
Diff
220 lines
11 KiB
Diff
From b5b07ca3fba55030c3fad46887a64b39da500b32 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 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<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 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<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 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<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 46e19d916..09b03afff 100644
|
|
--- a/src/main/java/net/minecraft/server/World.java
|
|
+++ b/src/main/java/net/minecraft/server/World.java
|
|
@@ -1172,6 +1172,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");
|
|
--
|
|
2.25.1
|
|
|