From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
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/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 054ece1d539d69a4b7eec57e681179343c7e75c3..6f35067c64f378e955261e763f2bda9a0a6d0153 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -218,6 +218,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
     // Paper end - Share random for entities to make them more random
     public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - Entity#getEntitySpawnReason
 
+    public boolean collisionLoadChunks = false; // Paper
     private @Nullable org.bukkit.craftbukkit.entity.CraftEntity bukkitEntity;
 
     public org.bukkit.craftbukkit.entity.CraftEntity getBukkitEntity() {
diff --git a/net/minecraft/world/level/BlockCollisions.java b/net/minecraft/world/level/BlockCollisions.java
index fd2c338db43aad070cc32c24891b40599c544ac9..2861ea4b699d403b1245f8be5a62503d366ded65 100644
--- a/net/minecraft/world/level/BlockCollisions.java
+++ b/net/minecraft/world/level/BlockCollisions.java
@@ -80,16 +80,37 @@ public class BlockCollisions<T> extends AbstractIterator<T> {
     @Override
     protected T computeNext() {
         while (this.cursor.advance()) {
-            int i = this.cursor.nextX();
-            int i1 = this.cursor.nextY();
-            int i2 = this.cursor.nextZ();
+            int i = this.cursor.nextX(); final int x = i; // Paper - OBFHELPER
+            int i1 = this.cursor.nextY(); final int y = i1; // Paper - OBFHELPER
+            int i2 = this.cursor.nextZ(); final int z = i2; // Paper - OBFHELPER
             int nextType = this.cursor.getNextType();
             if (nextType != 3) {
-                BlockGetter chunk = this.getChunk(i, i2);
-                if (chunk != null) {
-                    this.pos.set(i, i1, i2);
-                    BlockState blockState = chunk.getBlockState(this.pos);
-                    if ((!this.onlySuffocatingBlocks || blockState.isSuffocating(chunk, this.pos))
+                // Paper start - ensure we don't load chunks
+                // BlockGetter blockGetter = this.getChunk(i, k);
+                if (true) {
+                    @Nullable final Entity source = this.context instanceof net.minecraft.world.phys.shapes.EntityCollisionContext entityContext ? entityContext.getEntity() : null;
+                    final 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 (true // onlySuffocatingBlocks is only true on the client, so we don't care about it here
+                    // Paper end - ensure we don't load chunks
                         && (nextType != 1 || blockState.hasLargeCollisionShape())
                         && (nextType != 2 || blockState.is(Blocks.MOVING_PISTON))) {
                         VoxelShape collisionShape = this.context.getCollisionShape(blockState, this.collisionGetter, this.pos);
diff --git a/net/minecraft/world/level/CollisionGetter.java b/net/minecraft/world/level/CollisionGetter.java
index cb54c3aadd8f3c719d3f7ef1fda4aa517919b7c3..844f76a38884e823a558fe59c421ffd4711f80b4 100644
--- a/net/minecraft/world/level/CollisionGetter.java
+++ b/net/minecraft/world/level/CollisionGetter.java
@@ -50,11 +50,13 @@ public interface CollisionGetter extends BlockGetter {
     }
 
     default boolean noCollision(@Nullable Entity entity, AABB collisionBox, boolean checkLiquid) {
+        try { if (entity != null) entity.collisionLoadChunks = true; // Paper
         for (VoxelShape voxelShape : checkLiquid ? this.getBlockAndLiquidCollisions(entity, collisionBox) : this.getBlockCollisions(entity, collisionBox)) {
             if (!voxelShape.isEmpty()) {
                 return false;
             }
         }
+        } finally { if (entity != null) entity.collisionLoadChunks = false; } // Paper
 
         if (!this.getEntityCollisions(entity, collisionBox).isEmpty()) {
             return false;
