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 109cefef2a39aaae50ca745d907729d066aa9033..d4ae0a201838dbc9c73f7c1a8a67072b344b71bc 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -820,6 +820,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 7f08a5a9c853c1f54e1d3b142c52cfa5dfb8c091..04635faeb265852df7ec3ea9b04c8ae24c2fcd58 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -240,6 +240,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper 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 f2c423154ed6a00882a46d93b69ed4f6ba73782c..a3eaf80b020c3bbc0306c5d17659ee661dfd275b 100644 --- a/src/main/java/net/minecraft/world/level/BlockCollisions.java +++ b/src/main/java/net/minecraft/world/level/BlockCollisions.java @@ -65,22 +65,41 @@ public class BlockCollisions extends AbstractIterator { protected T 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 && io.papermc.paper.util.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.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)))); + } + // 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 140d10807a3a6806578cd203ba58383590c2f2c0..c476e37df8a75d77f5093b2a449e04f25ef2c2dd 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;