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 5d4ef973781eac558c1e8d749f751c04a67c4693..33b607227718e08f26d7ab5744bbbff806f33366 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -791,6 +791,7 @@ public abstract class PlayerList { entityplayer1.forceSetPositionRotation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); // CraftBukkit end + worldserver1.getChunkSource().addRegionTicket(net.minecraft.server.level.TicketType.POST_TELEPORT, new net.minecraft.world.level.ChunkPos(location.getBlockX() >> 4, location.getBlockZ() >> 4), 1, entityplayer.getId()); // Paper while (avoidSuffocation && !worldserver1.noCollision(entityplayer1) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) { 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 13e6fd62d7e9026a31455c5e2fa36392c7a0b249..c068e5fad2d9238201cada0d3a77ff2af1b8d8e1 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -171,6 +171,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, i // Paper end public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper + public boolean collisionLoadChunks = false; // Paper private CraftEntity bukkitEntity; public net.minecraft.server.level.ChunkMap.TrackedEntity tracker; // Paper diff --git a/src/main/java/net/minecraft/world/level/BlockCollisions.java b/src/main/java/net/minecraft/world/level/BlockCollisions.java index 8390ce194ccc692139c0e870c16a7fb76ac8ba68..d40bbc5ae6b87028a6dde400ea714249792da79a 100644 --- a/src/main/java/net/minecraft/world/level/BlockCollisions.java +++ b/src/main/java/net/minecraft/world/level/BlockCollisions.java @@ -66,22 +66,41 @@ public class BlockCollisions extends AbstractIterator { protected VoxelShape computeNext() { 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; // Paper + int j = this.cursor.nextY(); final int y = j; // Paper + int k = this.cursor.nextZ(); final int z = k; // Paper int l = this.cursor.getNextType(); if (l == 3) { continue; } + // Paper start - ensure we don't load chunks + final @Nullable Entity source = this.context instanceof net.minecraft.world.phys.shapes.EntityCollisionContext entityContext ? entityContext.getEntity() : null; + boolean far = source != null && net.minecraft.server.MCUtil.distanceSq(source.getX(), y, source.getZ(), x, y, z) > 14; + this.pos.set(x, y, z); - BlockGetter blockGetter = this.getChunk(i, k); - if (blockGetter == null) { + BlockState blockState; + if (this.collisionGetter instanceof net.minecraft.server.level.WorldGenRegion) { + BlockGetter blockGetter = this.getChunk(x, z); + if (blockGetter == null) { + continue; + } + blockState = blockGetter.getBlockState(this.pos); + } else if ((!far && source instanceof net.minecraft.server.level.ServerPlayer) || (source != null && source.collisionLoadChunks)) { + blockState = this.collisionGetter.getBlockState(this.pos); + } else { + blockState = this.collisionGetter.getTypeIfLoaded(this.pos); + } + + if (blockState == null) { + if (!(source instanceof net.minecraft.server.level.ServerPlayer) || source.level.paperConfig.preventMovingIntoUnloadedChunks) { + return Shapes.create(far ? source.getBoundingBox() : new AABB(new BlockPos(x, y, z))); + } + // Paper end continue; } - this.pos.set(i, j, k); - BlockState blockState = blockGetter.getBlockState(this.pos); - if (this.onlySuffocatingBlocks && !blockState.isSuffocating(blockGetter, this.pos) || l == 1 && !blockState.hasLargeCollisionShape() || l == 2 && !blockState.is(Blocks.MOVING_PISTON)) { + // Paper - moved up + if (/*this.onlySuffocatingBlocks && (!blockState.isSuffocating(blockGetter, this.pos)) ||*/ l == 1 && !blockState.hasLargeCollisionShape() || l == 2 && !blockState.is(Blocks.MOVING_PISTON)) { // Paper - onlySuffocatingBlocks is only true on the client, so we don't care about it here continue; } diff --git a/src/main/java/net/minecraft/world/level/CollisionGetter.java b/src/main/java/net/minecraft/world/level/CollisionGetter.java index 4c373d6c8ddd9f5db88888cd8dbbfc24eb5df793..56d94c94fb0d4dc468bb5d69be655ddd5c6b5360 100644 --- a/src/main/java/net/minecraft/world/level/CollisionGetter.java +++ b/src/main/java/net/minecraft/world/level/CollisionGetter.java @@ -44,11 +44,13 @@ public interface CollisionGetter extends BlockGetter { } default boolean noCollision(@Nullable Entity entity, AABB box) { + try { if (entity != null) entity.collisionLoadChunks = true; // Paper for(VoxelShape voxelShape : this.getBlockCollisions(entity, box)) { if (!voxelShape.isEmpty()) { return false; } } + } finally { if (entity != null) entity.collisionLoadChunks = false; } // Paper if (!this.getEntityCollisions(entity, box).isEmpty()) { return false;