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 461c27292af06a5150de8ec263d0c8527e8c5278..37245ff682837e7e8c9647f4afe30f0dd87cb384 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -935,6 +935,7 @@ public abstract class PlayerList { entityplayer1.setShiftKeyDown(false); entityplayer1.forceSetPositionRotation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + 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((Entity) entityplayer1) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) { // CraftBukkit end 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 a66fe080ee73171090abec48352ad0bd457a2a6f..d4c8f2cb1e3adf45863226ae9ad31968bc3445c9 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -242,6 +242,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - Entity#getEntitySpawnReason public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper + public boolean collisionLoadChunks = false; // Paper private CraftEntity bukkitEntity; public @org.jetbrains.annotations.Nullable 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 cd342fa776b5d84e52aa1660c19be9ab45eaad2d..f6cc3d550df8481086acdcb62f44a1051ae79f80 100644 --- a/src/main/java/net/minecraft/world/level/BlockCollisions.java +++ b/src/main/java/net/minecraft/world/level/BlockCollisions.java @@ -66,18 +66,37 @@ public class BlockCollisions extends AbstractIterator { @Override protected T computeNext() { while (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 - OBFHELPER + int j = this.cursor.nextY(); final int y = j; // Paper - OBFHELPER + int k = this.cursor.nextZ(); final int z = k; // Paper - OBFHELPER int l = this.cursor.getNextType(); if (l != 3) { - BlockGetter blockGetter = this.getChunk(i, k); - if (blockGetter != null) { - 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 start - ensure we don't load chunks + // BlockGetter blockGetter = this.getChunk(i, k); + if (true) { + final @Nullable Entity source = this.context instanceof net.minecraft.world.phys.shapes.EntityCollisionContext entityContext ? entityContext.getEntity() : null; + boolean far = source != null && io.papermc.paper.util.MCUtil.distanceSq(source.getX(), y, source.getZ(), x, y, z) > 14; + this.pos.set(x, y, z); + 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.getBlockStateIfLoaded(this.pos); + } + if (blockState == null) { + if (!(source instanceof net.minecraft.server.level.ServerPlayer) || source.level().paperConfig().chunks.preventMovingIntoUnloadedChunks) { + return this.resultProvider.apply(new BlockPos.MutableBlockPos(x, y, z), Shapes.create(far ? source.getBoundingBox() : new AABB(new BlockPos(x, y, z)))); + } + continue; + } + 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 + // Paper end VoxelShape voxelShape = blockState.getCollisionShape(this.collisionGetter, this.pos, this.context); if (voxelShape == Shapes.block()) { if (this.box.intersects(i, j, k, i + 1.0, j + 1.0, k + 1.0)) { diff --git a/src/main/java/net/minecraft/world/level/CollisionGetter.java b/src/main/java/net/minecraft/world/level/CollisionGetter.java index ea9a5e643d7ccce4e4d20644901c22cf63c6d890..69754fbebb51f7f804c97a749fd65ce15ae22f1b 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;