From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Aikar Date: Thu, 2 Apr 2020 02:37:57 -0400 Subject: [PATCH] Optimize Collision to not load chunks The collision code takes an AABB and generates a cuboid of checks rather than a cylinder, so at high velocity this can generate a lot of chunk checks. Treat an unloaded chunk as a collision for entities, and also for players if the "prevent moving into unloaded chunks" setting is enabled. If that serting is not enabled, collisions will be ignored for players, since movement will load only the chunk the player enters anyways and avoids loading massive amounts of surrounding chunks due to large AABB lookups. diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java index dfdde9722bc0d83916779014b7718eef2c01b3db..86c5549196a4e9011c5240e7918b466c299be4a3 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -59,12 +59,23 @@ import net.minecraft.server.MCUtil; import net.minecraft.server.MinecraftServer; import net.minecraft.server.PlayerAdvancements; import net.minecraft.server.ServerScoreboard; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.ServerPlayerGameMode; +import net.minecraft.server.level.TicketType; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.server.network.ServerLoginPacketListenerImpl; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.stats.ServerStatsCounter; +import net.minecraft.stats.Stats; import net.minecraft.tags.BlockTags; import net.minecraft.tags.Tag; import net.minecraft.util.Mth; import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; +import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.GameType; import net.minecraft.world.level.Level; @@ -90,15 +101,6 @@ import io.papermc.paper.adventure.PaperAdventure; // Paper import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import net.minecraft.server.dedicated.DedicatedServer; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.server.level.ServerPlayerGameMode; -import net.minecraft.server.network.ServerGamePacketListenerImpl; -import net.minecraft.server.network.ServerLoginPacketListenerImpl; -import net.minecraft.sounds.SoundEvents; -import net.minecraft.sounds.SoundSource; -import net.minecraft.stats.ServerStatsCounter; -import net.minecraft.stats.Stats; import org.bukkit.craftbukkit.CraftServer; import org.bukkit.craftbukkit.CraftWorld; @@ -805,6 +807,7 @@ public abstract class PlayerList { entityplayer1.forceSetPositionRotation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); // CraftBukkit end + worldserver1.getChunkSource().addRegionTicket(TicketType.POST_TELEPORT, new ChunkPos(location.getBlockX() >> 4, location.getBlockZ() >> 4), 1, entityplayer.getId()); // Paper while (avoidSuffocation && !worldserver1.noCollision(entityplayer1) && entityplayer1.getY() < 256.0D) { entityplayer1.setPos(entityplayer1.getX(), entityplayer1.getY() + 1.0D, entityplayer1.getZ()); } diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java index 7e198b94f349d4c4d61502f5ad8c60686800d88f..b8dcc91a191f25ca578e0858abf6c1b874fee15d 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -168,6 +168,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s private CraftEntity bukkitEntity; ChunkMap.TrackedEntity tracker; // Paper + public boolean collisionLoadChunks = false; // Paper public Throwable addedToWorldStack; // Paper - entity debug public CraftEntity getBukkitEntity() { if (bukkitEntity == null) { diff --git a/src/main/java/net/minecraft/world/level/CollisionGetter.java b/src/main/java/net/minecraft/world/level/CollisionGetter.java index d9e69195ee0af4dfb90bf0e8f4cc65e63f7ecf5b..1b52f2a0ce9cb847d7d57b38f4b8b6bed8de2cd9 100644 --- a/src/main/java/net/minecraft/world/level/CollisionGetter.java +++ b/src/main/java/net/minecraft/world/level/CollisionGetter.java @@ -54,7 +54,9 @@ public interface CollisionGetter extends BlockGetter { } default boolean noCollision(@Nullable Entity entity, AABB axisalignedbb, Predicate predicate) { + try { if (entity != null) entity.collisionLoadChunks = true; // Paper return this.getCollisions(entity, axisalignedbb, predicate).allMatch(VoxelShape::isEmpty); + } finally { if (entity != null) entity.collisionLoadChunks = false; } // Paper } Stream getEntityCollisions(@Nullable Entity entity, AABB axisalignedbb, Predicate predicate); diff --git a/src/main/java/net/minecraft/world/level/CollisionSpliterator.java b/src/main/java/net/minecraft/world/level/CollisionSpliterator.java index 7208c61da48ce5e735810b6268490584e1d5c260..feca9ff34936686c0665ae0dbc926869087df3a7 100644 --- a/src/main/java/net/minecraft/world/level/CollisionSpliterator.java +++ b/src/main/java/net/minecraft/world/level/CollisionSpliterator.java @@ -7,6 +7,9 @@ import java.util.function.Consumer; import javax.annotation.Nullable; import net.minecraft.core.BlockPos; import net.minecraft.core.Cursor3D; +import net.minecraft.server.MCUtil; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.WorldGenRegion; import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.block.Blocks; @@ -21,13 +24,13 @@ import net.minecraft.world.phys.shapes.VoxelShape; public class CollisionSpliterator extends AbstractSpliterator { @Nullable - private final Entity source; + private final Entity source; final Entity getEntity() { return this.source; } // Paper - OBFHELPER private final AABB box; private final CollisionContext context; private final Cursor3D cursor; - private final BlockPos.MutableBlockPos pos; + private final BlockPos.MutableBlockPos pos; final BlockPos.MutableBlockPos getMutablePos() { return this.pos; } // Paper - OBFHELPER private final VoxelShape entityShape; - private final CollisionGetter collisionGetter; + private final CollisionGetter collisionGetter; final CollisionGetter getCollisionAccess() { return this.collisionGetter; } // Paper - OBFHELPER private boolean needsBorderCheck; private final BiPredicate predicate; @@ -64,23 +67,37 @@ public class CollisionSpliterator extends AbstractSpliterator { boolean collisionCheck(Consumer consumer) { while (true) { if (this.cursor.advance()) { - int i = this.cursor.nextX(); - int j = this.cursor.nextY(); - int k = this.cursor.nextZ(); + int i = this.cursor.nextX(); final int x = i; + int j = this.cursor.nextY(); final int y = j; + int k = this.cursor.nextZ(); final int z = k; int l = this.cursor.getNextType(); if (l == 3) { continue; } - BlockGetter iblockaccess = this.getChunk(i, k); - - if (iblockaccess == null) { + // Paper start - ensure we don't load chunks + Entity entity = this.getEntity(); + BlockPos.MutableBlockPos blockposition_mutableblockposition = this.getMutablePos(); + boolean far = entity != null && MCUtil.distanceSq(entity.getX(), y, entity.getZ(), x, y, z) > 14; + blockposition_mutableblockposition.setValues(x, y, z); + + boolean isRegionLimited = this.getCollisionAccess() instanceof WorldGenRegion; + BlockState iblockdata = isRegionLimited ? Blocks.VOID_AIR.defaultBlockState() : ((!far && entity instanceof ServerPlayer) || (entity != null && entity.collisionLoadChunks) + ? this.getCollisionAccess().getBlockState(blockposition_mutableblockposition) + : this.getCollisionAccess().getTypeIfLoaded(blockposition_mutableblockposition) + ); + + if (iblockdata == null) { + if (!(entity instanceof ServerPlayer) || entity.level.paperConfig.preventMovingIntoUnloadedChunks) { + VoxelShape voxelshape3 = Shapes.of(far ? entity.getBoundingBox() : new AABB(new BlockPos(x, y, z))); + consumer.accept(voxelshape3); + return true; + } continue; } - - this.pos.set(i, j, k); - BlockState iblockdata = iblockaccess.getBlockState(this.pos); + // Paper - moved up + // Paper end if (!this.predicate.test(iblockdata, this.pos) || l == 1 && !iblockdata.hasLargeCollisionShape() || l == 2 && !iblockdata.is(Blocks.MOVING_PISTON)) { continue; diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java index fa2942d0b0424390daee2121f8959034c5352e0b..c14d5ebe16a693834ed218af8f737714065b2e17 100644 --- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java +++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java @@ -249,7 +249,8 @@ public final class Shapes { if (k2 < 3) { blockposition_mutableblockposition.set(enumaxiscycle1, i2, j2, l1); - BlockState iblockdata = world.getBlockState(blockposition_mutableblockposition); + BlockState iblockdata = world.getTypeIfLoaded(blockposition_mutableblockposition); // Paper + if (iblockdata == null) return 0.0D; // Paper if ((k2 != 1 || iblockdata.hasLargeCollisionShape()) && (k2 != 2 || iblockdata.is(Blocks.MOVING_PISTON))) { initial = iblockdata.getCollisionShape((BlockGetter) world, blockposition_mutableblockposition, context).collide(enumdirection_enumaxis2, box.move((double) (-blockposition_mutableblockposition.getX()), (double) (-blockposition_mutableblockposition.getY()), (double) (-blockposition_mutableblockposition.getZ())), initial);