(Almost) all patches applied

This commit is contained in:
Nassim Jahnke 2024-04-25 14:23:55 +02:00
parent 2debcaff9d
commit 6da0d8cc91
No known key found for this signature in database
GPG key ID: EF6771C01F6EF02F
10 changed files with 73 additions and 123 deletions

View file

@ -61,7 +61,7 @@ index ed8e875fff01c6b464fbaefbb0a3f417f9d67a72..aba5f694b25507c9ab2e214bc1f25e0a
public <T extends Entity> List<? extends T> getEntities(EntityTypeTest<Entity, T> filter, Predicate<? super T> predicate) {
diff --git a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java
index 7984f17cd9c4cef8100909b6c33b3144c8096fcf..868c9bbb12c8cfe76abb62774cf08102b727063b 100644
index 0382b6597a130d746f8954a93a756a9d1ac81d50..cb39c629af1827078f35904a373d35a63fea17ff 100644
--- a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java
+++ b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java
@@ -116,7 +116,13 @@ public class WorldUpgrader {

View file

@ -81,51 +81,10 @@ index abb9a86cd42a34cf722a312068134e820ac21956..3b6ebe8f9575783a1607eb6667554ca6
}
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 88fc56fa521c36accd807ca1704136f29733e52f..32076a765d48d59b339d600f69afa85edbcf833c 100644
index 88fc56fa521c36accd807ca1704136f29733e52f..a771f78f63e4f26c0ba411a3c355f8dfbb5f4a61 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -45,7 +45,6 @@ import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.Connection;
import net.minecraft.network.ConnectionProtocol;
import net.minecraft.network.TickablePacketListener;
-import net.minecraft.network.chat.ChatDecorator;
import net.minecraft.network.chat.ChatType;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.LastSeenMessages;
@@ -63,7 +62,6 @@ import net.minecraft.network.protocol.PacketUtils;
import net.minecraft.network.protocol.common.ServerboundClientInformationPacket;
import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket;
import net.minecraft.network.protocol.configuration.ConfigurationProtocols;
-import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
import net.minecraft.network.protocol.game.ClientboundBlockChangedAckPacket;
import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundCommandSuggestionsPacket;
@@ -185,7 +183,6 @@ import net.minecraft.world.level.block.entity.CrafterBlockEntity;
import net.minecraft.world.level.block.entity.JigsawBlockEntity;
import net.minecraft.world.level.block.entity.SignBlockEntity;
import net.minecraft.world.level.block.entity.StructureBlockEntity;
-import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
@@ -216,7 +213,6 @@ import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.craftbukkit.event.CraftEventFactory;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.craftbukkit.inventory.CraftItemType;
-import org.bukkit.craftbukkit.util.CraftChatMessage;
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
import org.bukkit.craftbukkit.util.LazyPlayerSet;
import org.bukkit.craftbukkit.util.Waitable;
@@ -231,8 +227,6 @@ import org.bukkit.event.inventory.InventoryCreativeEvent;
import org.bukkit.event.inventory.InventoryType.SlotType;
import org.bukkit.event.inventory.SmithItemEvent;
import org.bukkit.event.player.AsyncPlayerChatEvent;
-import org.bukkit.event.player.PlayerAnimationEvent;
-import org.bukkit.event.player.PlayerAnimationType;
import org.bukkit.event.player.PlayerChatEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerInteractAtEntityEvent;
@@ -2010,6 +2004,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
@@ -2010,6 +2010,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
}
if (cancelled) {
@ -133,7 +92,7 @@ index 88fc56fa521c36accd807ca1704136f29733e52f..32076a765d48d59b339d600f69afa85e
this.player.getBukkitEntity().updateInventory(); // SPIGOT-2524
return;
}
@@ -2794,7 +2789,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
@@ -2794,7 +2795,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
// Entity in bucket - SPIGOT-4048 and SPIGOT-6859a
if ((entity instanceof Bucketable && entity instanceof LivingEntity && origItem != null && origItem.asItem() == Items.WATER_BUCKET) && (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem)) {

View file

@ -0,0 +1,74 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sun, 13 Aug 2023 15:41:52 -0700
Subject: [PATCH] Improve performance of mass crafts
When the server crafts all available items in CraftingMenu or InventoryMenu the game
checks either 4 or 9 times for each individual craft for a matching recipe for that container.
This check can be expensive if 64 total crafts are being performed with the recipe matching logic
being run 64 * 9 + 64 times. A breakdown of those times is below. This patch caches the last matching
recipe so that it is checked first and only if it doesn't match does the rest of the matching logic run.
Shift-click crafts are processed one at a time, so shift clicking on an item in the result of a iron block craft
where all the 9 inputs are full stacks of iron will run 64 iron block crafts. For each of those crafts, the
'remaining' blocks are calculated. This is due to recipes that have leftover items like buckets. This is done
for each craft, and done once to get the full 9 leftover items which are usually air. Then 1 item is removed
from each of the 9 inputs and each time that happens, logic is triggered to update the result itemstack. So
for each craft, that logic is run 9 times (hence the 64 * 9). The + 64 is from the 64 checks for remaining items.
After this patch, the full iteration over all recipes checking for a match should run once for a full craft to find the
initial recipe match. Then that recipe will be checked first for all future recipe match checks.
diff --git a/src/main/java/net/minecraft/world/inventory/CraftingMenu.java b/src/main/java/net/minecraft/world/inventory/CraftingMenu.java
index 7a0c1a55a211035bbca7b97293e94b04ae308bae..c3800bdd5096cb06e085e28f6bf0f65586ecf11e 100644
--- a/src/main/java/net/minecraft/world/inventory/CraftingMenu.java
+++ b/src/main/java/net/minecraft/world/inventory/CraftingMenu.java
@@ -76,7 +76,8 @@ public class CraftingMenu extends RecipeBookMenu<CraftingContainer> {
if (!world.isClientSide) {
ServerPlayer entityplayer = (ServerPlayer) player;
ItemStack itemstack = ItemStack.EMPTY;
- Optional<RecipeHolder<CraftingRecipe>> optional = world.getServer().getRecipeManager().getRecipeFor(RecipeType.CRAFTING, craftingInventory, world);
+ final RecipeHolder<?> currentRecipe = craftingInventory.getCurrentRecipe(); // Paper - Perf: Improve mass crafting; check last recipe used first
+ Optional<RecipeHolder<CraftingRecipe>> optional = currentRecipe == null ? world.getServer().getRecipeManager().getRecipeFor(RecipeType.CRAFTING, craftingInventory, world) : world.getServer().getRecipeManager().getRecipeFor(RecipeType.CRAFTING, craftingInventory, world, currentRecipe.id()).map(com.mojang.datafixers.util.Pair::getSecond); // Paper - Perf: Improve mass crafting; check last recipe used first
if (optional.isPresent()) {
RecipeHolder<CraftingRecipe> recipeholder = (RecipeHolder) optional.get();
diff --git a/src/main/java/net/minecraft/world/inventory/ResultSlot.java b/src/main/java/net/minecraft/world/inventory/ResultSlot.java
index fef82418358ecf19d367dafbec159bd78ea97494..f17ff5988d826c8fad68f6bf7bac1d06edae01ae 100644
--- a/src/main/java/net/minecraft/world/inventory/ResultSlot.java
+++ b/src/main/java/net/minecraft/world/inventory/ResultSlot.java
@@ -58,7 +58,7 @@ public class ResultSlot extends Slot {
@Override
public void onTake(Player player, ItemStack stack) {
this.checkTakeAchievements(stack);
- NonNullList<ItemStack> nonNullList = player.level().getRecipeManager().getRemainingItemsFor(RecipeType.CRAFTING, this.craftSlots, player.level());
+ NonNullList<ItemStack> nonNullList = player.level().getRecipeManager().getRemainingItemsFor(RecipeType.CRAFTING, this.craftSlots, player.level(), this.craftSlots.getCurrentRecipe() != null ? this.craftSlots.getCurrentRecipe().id() : null); // Paper - Perf: Improve mass crafting; check last recipe used first
for (int i = 0; i < nonNullList.size(); i++) {
ItemStack itemStack = this.craftSlots.getItem(i);
diff --git a/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java b/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java
index 0126b88f60904dfbf1e29eb3b89a985061d91f91..a42c7be84e10a06303a7fc23236ac4d84e8323c6 100644
--- a/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java
+++ b/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java
@@ -116,6 +116,7 @@ public class RecipeManager extends SimpleJsonResourceReloadListener {
RecipeHolder<T> recipeholder = this.byKeyTyped(type, id);
if (recipeholder != null && recipeholder.value().matches(inventory, world)) {
+ inventory.setCurrentRecipe(recipeholder); // Paper - Perf: Improve mass crafting
return Optional.of(recipeholder);
}
}
@@ -147,7 +148,12 @@ public class RecipeManager extends SimpleJsonResourceReloadListener {
}
public <C extends Container, T extends Recipe<C>> NonNullList<ItemStack> getRemainingItemsFor(RecipeType<T> type, C inventory, Level world) {
- Optional<RecipeHolder<T>> optional = this.getRecipeFor(type, inventory, world);
+ // Paper start - Perf: Improve mass crafting;; check last recipe used first
+ return this.getRemainingItemsFor(type, inventory, world, null);
+ }
+ public <C extends Container, T extends Recipe<C>> NonNullList<ItemStack> getRemainingItemsFor(RecipeType<T> type, C inventory, Level world, @Nullable ResourceLocation firstToCheck) {
+ Optional<RecipeHolder<T>> optional = firstToCheck == null ? this.getRecipeFor(type, inventory, world) : this.getRecipeFor(type, inventory, world, firstToCheck).map(Pair::getSecond);
+ // Paper end - Perf: Improve mass crafting
if (optional.isPresent()) {
return ((RecipeHolder) optional.get()).value().getRemainingItems(inventory);

View file

@ -0,0 +1,521 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Tue, 12 Sep 2023 06:50:16 -0700
Subject: [PATCH] Actually optimise explosions
The vast majority of blocks an explosion of power ~4 tries
to destroy are duplicates. The core of the block destroying
part of this patch is to cache the block state, resistance, and
whether it should explode - as those will not change.
The other part of this patch is to optimise the visibility
percentage calculation. The new visibility calculation takes
advantage of the block caching already done by the explosion logic.
It continues to update the cache as the visibility calculation
uses many rays which can overlap significantly.
Effectively, the patch uses a lot of caching to eliminate
redundant operations.
Performance benchmarking explosions is challenging, as it varies
depending on the power, the number of nearby entities, and the
nearby terrain. This means that no benchmark can cover all the cases.
I decided to test a giant block of TNT, as that's where the optimisations
would be needed the most.
I tested using a 50x10x50 block of TNT above ground
and determined the following:
Vanilla time per explosion: 2.27ms
Lithium time per explosion: 1.07ms
This patch time per explosion: 0.45ms
The results indicate that this logic is 5 times faster than Vanilla
and 2.3 times faster than Lithium.
diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java
index 6bd6533e314b64d3512cbb46f5174a4956c323b2..46a14da698eff141b7379aa9c159e6a5c5f7d84c 100644
--- a/src/main/java/net/minecraft/world/level/Explosion.java
+++ b/src/main/java/net/minecraft/world/level/Explosion.java
@@ -112,6 +112,271 @@ public class Explosion {
this.yield = this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F; // CraftBukkit
}
+ // Paper start - optimise collisions
+ private static final double[] CACHED_RAYS;
+ static {
+ final it.unimi.dsi.fastutil.doubles.DoubleArrayList rayCoords = new it.unimi.dsi.fastutil.doubles.DoubleArrayList();
+
+ for (int x = 0; x <= 15; ++x) {
+ for (int y = 0; y <= 15; ++y) {
+ for (int z = 0; z <= 15; ++z) {
+ if ((x == 0 || x == 15) || (y == 0 || y == 15) || (z == 0 || z == 15)) {
+ double xDir = (double)((float)x / 15.0F * 2.0F - 1.0F);
+ double yDir = (double)((float)y / 15.0F * 2.0F - 1.0F);
+ double zDir = (double)((float)z / 15.0F * 2.0F - 1.0F);
+
+ double mag = Math.sqrt(
+ xDir * xDir + yDir * yDir + zDir * zDir
+ );
+
+ rayCoords.add((xDir / mag) * (double)0.3F);
+ rayCoords.add((yDir / mag) * (double)0.3F);
+ rayCoords.add((zDir / mag) * (double)0.3F);
+ }
+ }
+ }
+ }
+
+ CACHED_RAYS = rayCoords.toDoubleArray();
+ }
+
+ private static final int CHUNK_CACHE_SHIFT = 2;
+ private static final int CHUNK_CACHE_MASK = (1 << CHUNK_CACHE_SHIFT) - 1;
+ private static final int CHUNK_CACHE_WIDTH = 1 << CHUNK_CACHE_SHIFT;
+
+ private static final int BLOCK_EXPLOSION_CACHE_SHIFT = 3;
+ private static final int BLOCK_EXPLOSION_CACHE_MASK = (1 << BLOCK_EXPLOSION_CACHE_SHIFT) - 1;
+ private static final int BLOCK_EXPLOSION_CACHE_WIDTH = 1 << BLOCK_EXPLOSION_CACHE_SHIFT;
+
+ // resistance = (res + 0.3F) * 0.3F;
+ // so for resistance = 0, we need res = -0.3F
+ private static final Float ZERO_RESISTANCE = Float.valueOf(-0.3f);
+ private it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<ExplosionBlockCache> blockCache = null;
+
+ public static final class ExplosionBlockCache {
+
+ public final long key;
+ public final BlockPos immutablePos;
+ public final BlockState blockState;
+ public final FluidState fluidState;
+ public final float resistance;
+ public final boolean outOfWorld;
+ public Boolean shouldExplode; // null -> not called yet
+ public net.minecraft.world.phys.shapes.VoxelShape cachedCollisionShape;
+
+ public ExplosionBlockCache(long key, BlockPos immutablePos, BlockState blockState, FluidState fluidState, float resistance,
+ boolean outOfWorld) {
+ this.key = key;
+ this.immutablePos = immutablePos;
+ this.blockState = blockState;
+ this.fluidState = fluidState;
+ this.resistance = resistance;
+ this.outOfWorld = outOfWorld;
+ }
+ }
+
+ private long[] chunkPosCache = null;
+ private net.minecraft.world.level.chunk.LevelChunk[] chunkCache = null;
+
+ private ExplosionBlockCache getOrCacheExplosionBlock(final int x, final int y, final int z,
+ final long key, final boolean calculateResistance) {
+ ExplosionBlockCache ret = this.blockCache.get(key);
+ if (ret != null) {
+ return ret;
+ }
+
+ BlockPos pos = new BlockPos(x, y, z);
+
+ if (!this.level.isInWorldBounds(pos)) {
+ ret = new ExplosionBlockCache(key, pos, null, null, 0.0f, true);
+ } else {
+ net.minecraft.world.level.chunk.LevelChunk chunk;
+ long chunkKey = io.papermc.paper.util.CoordinateUtils.getChunkKey(x >> 4, z >> 4);
+ int chunkCacheKey = ((x >> 4) & CHUNK_CACHE_MASK) | (((z >> 4) << CHUNK_CACHE_SHIFT) & (CHUNK_CACHE_MASK << CHUNK_CACHE_SHIFT));
+ if (this.chunkPosCache[chunkCacheKey] == chunkKey) {
+ chunk = this.chunkCache[chunkCacheKey];
+ } else {
+ this.chunkPosCache[chunkCacheKey] = chunkKey;
+ this.chunkCache[chunkCacheKey] = chunk = this.level.getChunk(x >> 4, z >> 4);
+ }
+
+ BlockState blockState = chunk.getBlockStateFinal(x, y, z);
+ FluidState fluidState = blockState.getFluidState();
+
+ Optional<Float> resistance = !calculateResistance ? Optional.empty() : this.damageCalculator.getBlockExplosionResistance((Explosion)(Object)this, this.level, pos, blockState, fluidState);
+
+ ret = new ExplosionBlockCache(
+ key, pos, blockState, fluidState,
+ (resistance.orElse(ZERO_RESISTANCE).floatValue() + 0.3f) * 0.3f,
+ false
+ );
+ }
+
+ this.blockCache.put(key, ret);
+
+ return ret;
+ }
+
+ private boolean clipsAnything(final Vec3 from, final Vec3 to,
+ final io.papermc.paper.util.CollisionUtil.LazyEntityCollisionContext context,
+ final ExplosionBlockCache[] blockCache,
+ final BlockPos.MutableBlockPos currPos) {
+ // assume that context.delegated = false
+ final double adjX = io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON * (from.x - to.x);
+ final double adjY = io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON * (from.y - to.y);
+ final double adjZ = io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON * (from.z - to.z);
+
+ if (adjX == 0.0 && adjY == 0.0 && adjZ == 0.0) {
+ return false;
+ }
+
+ final double toXAdj = to.x - adjX;
+ final double toYAdj = to.y - adjY;
+ final double toZAdj = to.z - adjZ;
+ final double fromXAdj = from.x + adjX;
+ final double fromYAdj = from.y + adjY;
+ final double fromZAdj = from.z + adjZ;
+
+ int currX = Mth.floor(fromXAdj);
+ int currY = Mth.floor(fromYAdj);
+ int currZ = Mth.floor(fromZAdj);
+
+ final double diffX = toXAdj - fromXAdj;
+ final double diffY = toYAdj - fromYAdj;
+ final double diffZ = toZAdj - fromZAdj;
+
+ final double dxDouble = Math.signum(diffX);
+ final double dyDouble = Math.signum(diffY);
+ final double dzDouble = Math.signum(diffZ);
+
+ final int dx = (int)dxDouble;
+ final int dy = (int)dyDouble;
+ final int dz = (int)dzDouble;
+
+ final double normalizedDiffX = diffX == 0.0 ? Double.MAX_VALUE : dxDouble / diffX;
+ final double normalizedDiffY = diffY == 0.0 ? Double.MAX_VALUE : dyDouble / diffY;
+ final double normalizedDiffZ = diffZ == 0.0 ? Double.MAX_VALUE : dzDouble / diffZ;
+
+ double normalizedCurrX = normalizedDiffX * (diffX > 0.0 ? (1.0 - Mth.frac(fromXAdj)) : Mth.frac(fromXAdj));
+ double normalizedCurrY = normalizedDiffY * (diffY > 0.0 ? (1.0 - Mth.frac(fromYAdj)) : Mth.frac(fromYAdj));
+ double normalizedCurrZ = normalizedDiffZ * (diffZ > 0.0 ? (1.0 - Mth.frac(fromZAdj)) : Mth.frac(fromZAdj));
+
+ for (;;) {
+ currPos.set(currX, currY, currZ);
+
+ // ClipContext.Block.COLLIDER -> BlockBehaviour.BlockStateBase::getCollisionShape
+ // ClipContext.Fluid.NONE -> ignore fluids
+
+ // read block from cache
+ final long key = BlockPos.asLong(currX, currY, currZ);
+
+ final int cacheKey =
+ (currX & BLOCK_EXPLOSION_CACHE_MASK) |
+ (currY & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT) |
+ (currZ & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT + BLOCK_EXPLOSION_CACHE_SHIFT);
+ ExplosionBlockCache cachedBlock = blockCache[cacheKey];
+ if (cachedBlock == null || cachedBlock.key != key) {
+ blockCache[cacheKey] = cachedBlock = this.getOrCacheExplosionBlock(currX, currY, currZ, key, false);
+ }
+
+ final BlockState blockState = cachedBlock.blockState;
+ if (blockState != null && !blockState.emptyCollisionShape()) {
+ net.minecraft.world.phys.shapes.VoxelShape collision = cachedBlock.cachedCollisionShape;
+ if (collision == null) {
+ collision = blockState.getConstantCollisionShape();
+ if (collision == null) {
+ collision = blockState.getCollisionShape(this.level, currPos, context);
+ if (!context.isDelegated()) {
+ // if it was not delegated during this call, assume that for any future ones it will not be delegated
+ // again, and cache the result
+ cachedBlock.cachedCollisionShape = collision;
+ }
+ } else {
+ cachedBlock.cachedCollisionShape = collision;
+ }
+ }
+
+ if (!collision.isEmpty() && collision.clip(from, to, currPos) != null) {
+ return true;
+ }
+ }
+
+ if (normalizedCurrX > 1.0 && normalizedCurrY > 1.0 && normalizedCurrZ > 1.0) {
+ return false;
+ }
+
+ // inc the smallest normalized coordinate
+
+ if (normalizedCurrX < normalizedCurrY) {
+ if (normalizedCurrX < normalizedCurrZ) {
+ currX += dx;
+ normalizedCurrX += normalizedDiffX;
+ } else {
+ // x < y && x >= z <--> z < y && z <= x
+ currZ += dz;
+ normalizedCurrZ += normalizedDiffZ;
+ }
+ } else if (normalizedCurrY < normalizedCurrZ) {
+ // y <= x && y < z
+ currY += dy;
+ normalizedCurrY += normalizedDiffY;
+ } else {
+ // y <= x && z <= y <--> z <= y && z <= x
+ currZ += dz;
+ normalizedCurrZ += normalizedDiffZ;
+ }
+ }
+ }
+
+ private float getSeenFraction(final Vec3 source, final Entity target,
+ final ExplosionBlockCache[] blockCache,
+ final BlockPos.MutableBlockPos blockPos) {
+ final AABB boundingBox = target.getBoundingBox();
+ final double diffX = boundingBox.maxX - boundingBox.minX;
+ final double diffY = boundingBox.maxY - boundingBox.minY;
+ final double diffZ = boundingBox.maxZ - boundingBox.minZ;
+
+ final double incX = 1.0 / (diffX * 2.0 + 1.0);
+ final double incY = 1.0 / (diffY * 2.0 + 1.0);
+ final double incZ = 1.0 / (diffZ * 2.0 + 1.0);
+
+ if (incX < 0.0 || incY < 0.0 || incZ < 0.0) {
+ return 0.0f;
+ }
+
+ final double offX = (1.0 - Math.floor(1.0 / incX) * incX) * 0.5 + boundingBox.minX;
+ final double offY = boundingBox.minY;
+ final double offZ = (1.0 - Math.floor(1.0 / incZ) * incZ) * 0.5 + boundingBox.minZ;
+
+ final io.papermc.paper.util.CollisionUtil.LazyEntityCollisionContext context = new io.papermc.paper.util.CollisionUtil.LazyEntityCollisionContext(target);
+
+ int totalRays = 0;
+ int missedRays = 0;
+
+ for (double dx = 0.0; dx <= 1.0; dx += incX) {
+ final double fromX = Math.fma(dx, diffX, offX);
+ for (double dy = 0.0; dy <= 1.0; dy += incY) {
+ final double fromY = Math.fma(dy, diffY, offY);
+ for (double dz = 0.0; dz <= 1.0; dz += incZ) {
+ ++totalRays;
+
+ final Vec3 from = new Vec3(
+ fromX,
+ fromY,
+ Math.fma(dz, diffZ, offZ)
+ );
+
+ if (!this.clipsAnything(from, source, context, blockCache, blockPos)) {
+ ++missedRays;
+ }
+ }
+ }
+ }
+
+ return (float)missedRays / (float)totalRays;
+ }
+ // Paper end - optimise collisions
+
private ExplosionDamageCalculator makeDamageCalculator(@Nullable Entity entity) {
return (ExplosionDamageCalculator) (entity == null ? Explosion.EXPLOSION_DAMAGE_CALCULATOR : new EntityBasedExplosionDamageCalculator(entity));
}
@@ -172,40 +437,88 @@ public class Explosion {
int i;
int j;
- for (int k = 0; k < 16; ++k) {
- for (i = 0; i < 16; ++i) {
- for (j = 0; j < 16; ++j) {
- if (k == 0 || k == 15 || i == 0 || i == 15 || j == 0 || j == 15) {
- double d0 = (double) ((float) k / 15.0F * 2.0F - 1.0F);
- double d1 = (double) ((float) i / 15.0F * 2.0F - 1.0F);
- double d2 = (double) ((float) j / 15.0F * 2.0F - 1.0F);
- double d3 = Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2);
-
- d0 /= d3;
- d1 /= d3;
- d2 /= d3;
+ // Paper start - optimise explosions
+ this.blockCache = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>();
+
+ this.chunkPosCache = new long[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH];
+ java.util.Arrays.fill(this.chunkPosCache, ChunkPos.INVALID_CHUNK_POS);
+
+ this.chunkCache = new net.minecraft.world.level.chunk.LevelChunk[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH];
+
+ final ExplosionBlockCache[] blockCache = new ExplosionBlockCache[BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH];
+ // use initial cache value that is most likely to be used: the source position
+ final ExplosionBlockCache initialCache;
+ {
+ final int blockX = Mth.floor(this.x);
+ final int blockY = Mth.floor(this.y);
+ final int blockZ = Mth.floor(this.z);
+
+ final long key = BlockPos.asLong(blockX, blockY, blockZ);
+
+ initialCache = this.getOrCacheExplosionBlock(blockX, blockY, blockZ, key, true);
+ }
+ // only ~1/3rd of the loop iterations in vanilla will result in a ray, as it is iterating the perimeter of
+ // a 16x16x16 cube
+ // we can cache the rays and their normals as well, so that we eliminate the excess iterations / checks and
+ // calculations in one go
+ // additional aggressive caching of block retrieval is very significant, as at low power (i.e tnt) most
+ // block retrievals are not unique
+ for (int ray = 0, len = CACHED_RAYS.length; ray < len;) {
+ {
+ {
+ {
+ ExplosionBlockCache cachedBlock = initialCache;
+
+ double d0 = CACHED_RAYS[ray];
+ double d1 = CACHED_RAYS[ray + 1];
+ double d2 = CACHED_RAYS[ray + 2];
+ ray += 3;
+ // Paper end - optimise explosions
float f = this.radius * (0.7F + this.level.random.nextFloat() * 0.6F);
double d4 = this.x;
double d5 = this.y;
double d6 = this.z;
for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) {
- BlockPos blockposition = BlockPos.containing(d4, d5, d6);
- BlockState iblockdata = this.level.getBlockState(blockposition);
- if (!iblockdata.isDestroyable()) continue; // Paper - Protect Bedrock and End Portal/Frames from being destroyed
- FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: Optimize call to getFluid for explosions
+ // Paper start - optimise explosions
+ final int blockX = Mth.floor(d4);
+ final int blockY = Mth.floor(d5);
+ final int blockZ = Mth.floor(d6);
+
+ final long key = BlockPos.asLong(blockX, blockY, blockZ);
+
+ if (cachedBlock.key != key) {
+ final int cacheKey =
+ (blockX & BLOCK_EXPLOSION_CACHE_MASK) |
+ (blockY & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT) |
+ (blockZ & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT + BLOCK_EXPLOSION_CACHE_SHIFT);
+ cachedBlock = blockCache[cacheKey];
+ if (cachedBlock == null || cachedBlock.key != key) {
+ blockCache[cacheKey] = cachedBlock = this.getOrCacheExplosionBlock(blockX, blockY, blockZ, key, true);
+ }
+ }
- if (!this.level.isInWorldBounds(blockposition)) {
+ if (cachedBlock.outOfWorld) {
break;
}
- Optional<Float> optional = this.damageCalculator.getBlockExplosionResistance(this, this.level, blockposition, iblockdata, fluid);
+ BlockPos blockposition = cachedBlock.immutablePos;
+ BlockState iblockdata = cachedBlock.blockState;
+ // Paper end - optimise explosions
- if (optional.isPresent()) {
- f -= ((Float) optional.get() + 0.3F) * 0.3F;
- }
+ if (!iblockdata.isDestroyable()) continue; // Paper
+ // Paper - optimise explosions
- if (f > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockposition, iblockdata, f)) {
+ f -= cachedBlock.resistance; // Paper - optimise explosions
+
+ if (f > 0.0F && cachedBlock.shouldExplode == null) { // Paper - optimise explosions
+ // Paper start - optimise explosions
+ // note: we expect shouldBlockExplode to be pure with respect to power, as Vanilla currently is.
+ // basically, it is unused, which allows us to cache the result
+ final boolean shouldExplode = this.damageCalculator.shouldBlockExplode(this, this.level, cachedBlock.immutablePos, cachedBlock.blockState, f);
+ cachedBlock.shouldExplode = shouldExplode ? Boolean.TRUE : Boolean.FALSE;
+ if (shouldExplode && (this.fire || !cachedBlock.blockState.isAir())) {
+ // Paper end - optimise explosions
set.add(blockposition);
// Paper start - prevent headless pistons from forming
if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && iblockdata.getBlock() == Blocks.MOVING_PISTON) {
@@ -216,11 +529,12 @@ public class Explosion {
}
}
// Paper end - prevent headless pistons from forming
+ } // Paper - optimise explosions
}
- d4 += d0 * 0.30000001192092896D;
- d5 += d1 * 0.30000001192092896D;
- d6 += d2 * 0.30000001192092896D;
+ d4 += d0; // Paper - optimise explosions
+ d5 += d1; // Paper - optimise explosions
+ d6 += d2; // Paper - optimise explosions
}
}
}
@@ -240,6 +554,8 @@ public class Explosion {
Vec3 vec3d = new Vec3(this.x, this.y, this.z);
Iterator iterator = list.iterator();
+ final BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos(); // Paper - optimise explosions
+
while (iterator.hasNext()) {
Entity entity = (Entity) iterator.next();
@@ -275,11 +591,11 @@ public class Explosion {
for (EnderDragonPart entityComplexPart : ((EnderDragon) entity).subEntities) {
// Calculate damage separately for each EntityComplexPart
if (list.contains(entityComplexPart)) {
- entityComplexPart.hurt(this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity));
+ entityComplexPart.hurt(this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entityComplexPart, getSeenFraction(vec3d, entityComplexPart, blockCache, blockPos))); // Paper - actually optimise explosions and use the right entity to calculate the damage
}
}
} else {
- entity.hurt(this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity));
+ entity.hurt(this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, getSeenFraction(vec3d, entity, blockCache, blockPos))); // Paper - actually optimise explosions
}
if (entity.lastDamageCancelled) { // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Skip entity if damage event was cancelled
@@ -288,7 +604,7 @@ public class Explosion {
// CraftBukkit end
}
- double d12 = (1.0D - d7) * this.getBlockDensity(vec3d, entity) * (double) this.damageCalculator.getKnockbackMultiplier(entity); // Paper - Optimize explosions
+ double d12 = (1.0D - d7) * this.getBlockDensity(vec3d, entity, blockCache, blockPos) * (double) this.damageCalculator.getKnockbackMultiplier(entity); // Paper - Optimize explosions
double d13;
if (entity instanceof LivingEntity) {
@@ -337,6 +653,9 @@ public class Explosion {
}
}
+ this.blockCache = null; // Paper - optimise explosions
+ this.chunkPosCache = null; // Paper - optimise explosions
+ this.chunkCache = null; // Paper - optimise explosions
}
public void finalizeExplosion(boolean particles) {
@@ -550,14 +869,14 @@ public class Explosion {
private BlockInteraction() {}
}
// Paper start - Optimize explosions
- private float getBlockDensity(Vec3 vec3d, Entity entity) {
+ private float getBlockDensity(Vec3 vec3d, Entity entity, ExplosionBlockCache[] blockCache, BlockPos.MutableBlockPos blockPos) { // Paper - optimise explosions
if (!this.level.paperConfig().environment.optimizeExplosions) {
- return getSeenPercent(vec3d, entity);
+ return this.getSeenFraction(vec3d, entity, blockCache, blockPos); // Paper - optimise explosions
}
CacheKey key = new CacheKey(this, entity.getBoundingBox());
Float blockDensity = this.level.explosionDensityCache.get(key);
if (blockDensity == null) {
- blockDensity = getSeenPercent(vec3d, entity);
+ blockDensity = this.getSeenFraction(vec3d, entity, blockCache, blockPos); // Paper - optimise explosions;
this.level.explosionDensityCache.put(key, blockDensity);
}
diff --git a/src/main/java/net/minecraft/world/level/ExplosionDamageCalculator.java b/src/main/java/net/minecraft/world/level/ExplosionDamageCalculator.java
index 0ef9b402d129b072134688c06719a56328581158..1eb259b48bcab6172c15546744eea410c6a3e1fe 100644
--- a/src/main/java/net/minecraft/world/level/ExplosionDamageCalculator.java
+++ b/src/main/java/net/minecraft/world/level/ExplosionDamageCalculator.java
@@ -26,11 +26,17 @@ public class ExplosionDamageCalculator {
return 1.0F;
}
+ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper
public float getEntityDamageAmount(Explosion explosion, Entity entity) {
+ // Paper start - actually optimise explosions
+ return this.getEntityDamageAmount(explosion, entity, Explosion.getSeenPercent(explosion.center(), entity));
+ }
+ public float getEntityDamageAmount(Explosion explosion, Entity entity, double seenPercent) {
+ // Paper end - actually optimise explosions
float f = explosion.radius() * 2.0F;
Vec3 vec3 = explosion.center();
double d = Math.sqrt(entity.distanceToSqr(vec3)) / (double)f;
- double e = (1.0 - d) * (double)Explosion.getSeenPercent(vec3, entity);
+ double e = (1.0 - d) * seenPercent; // Paper - actually optimise explosions
return (float)((e * e + e) / 2.0 * 7.0 * (double)f + 1.0);
}
}

View file

@ -0,0 +1,380 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sat, 23 Sep 2023 21:36:36 -0700
Subject: [PATCH] Optimise chunk tick iteration
When per-player mob spawning is enabled we do not need to randomly
shuffle the chunk list. Additionally, we can use the NearbyPlayers
class to quickly retrieve nearby players instead of possible
searching all players on the server.
diff --git a/src/main/java/io/papermc/paper/util/player/NearbyPlayers.java b/src/main/java/io/papermc/paper/util/player/NearbyPlayers.java
index c3ce8a42dddd76b7189ad5685b23f9d9f8ccadb3..f164256d59b761264876ca0c85f812d101bfd5de 100644
--- a/src/main/java/io/papermc/paper/util/player/NearbyPlayers.java
+++ b/src/main/java/io/papermc/paper/util/player/NearbyPlayers.java
@@ -17,7 +17,8 @@ public final class NearbyPlayers {
GENERAL_SMALL,
GENERAL_REALLY_SMALL,
TICK_VIEW_DISTANCE,
- VIEW_DISTANCE;
+ VIEW_DISTANCE, // Paper - optimise chunk iteration
+ SPAWN_RANGE, // Paper - optimise chunk iteration
}
private static final NearbyMapType[] MOB_TYPES = NearbyMapType.values();
@@ -26,10 +27,12 @@ public final class NearbyPlayers {
private static final int GENERAL_AREA_VIEW_DISTANCE = 33;
private static final int GENERAL_SMALL_VIEW_DISTANCE = 10;
private static final int GENERAL_REALLY_SMALL_VIEW_DISTANCE = 3;
+ private static final int SPAWN_RANGE_VIEW_DISTANCE = net.minecraft.server.level.DistanceManager.MOB_SPAWN_RANGE; // Paper - optimise chunk iteration
public static final int GENERAL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_AREA_VIEW_DISTANCE << 4);
public static final int GENERAL_SMALL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_SMALL_VIEW_DISTANCE << 4);
public static final int GENERAL_REALLY_SMALL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_REALLY_SMALL_VIEW_DISTANCE << 4);
+ public static final int SPAWN_RANGE_VIEW_DISTANCE_BLOCKS = (SPAWN_RANGE_VIEW_DISTANCE << 4); // Paper - optimise chunk iteration
private final ServerLevel world;
private final Reference2ReferenceOpenHashMap<ServerPlayer, TrackedPlayer[]> players = new Reference2ReferenceOpenHashMap<>();
@@ -80,6 +83,7 @@ public final class NearbyPlayers {
players[NearbyMapType.GENERAL_REALLY_SMALL.ordinal()].update(chunk.x, chunk.z, GENERAL_REALLY_SMALL_VIEW_DISTANCE);
players[NearbyMapType.TICK_VIEW_DISTANCE.ordinal()].update(chunk.x, chunk.z, ChunkSystem.getTickViewDistance(player));
players[NearbyMapType.VIEW_DISTANCE.ordinal()].update(chunk.x, chunk.z, ChunkSystem.getLoadViewDistance(player));
+ players[NearbyMapType.SPAWN_RANGE.ordinal()].update(chunk.x, chunk.z, SPAWN_RANGE_VIEW_DISTANCE); // Paper - optimise chunk iteration
}
public TrackedChunk getChunk(final ChunkPos pos) {
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
index 13d15a135dd0373bef4a5ac9ffb56dbbf53353a0..472b9494f8a34a8ba90d6a2936b0db7530a229ad 100644
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
@@ -77,11 +77,19 @@ public class ChunkHolder {
// Paper start
public void onChunkAdd() {
-
+ // Paper start - optimise chunk tick iteration
+ if (this.needsBroadcastChanges()) {
+ this.chunkMap.needsChangeBroadcasting.add(this);
+ }
+ // Paper end - optimise chunk tick iteration
}
public void onChunkRemove() {
-
+ // Paper start - optimise chunk tick iteration
+ if (this.needsBroadcastChanges()) {
+ this.chunkMap.needsChangeBroadcasting.remove(this);
+ }
+ // Paper end - optimise chunk tick iteration
}
// Paper end
@@ -216,7 +224,7 @@ public class ChunkHolder {
if (i < 0 || i >= this.changedBlocksPerSection.length) return; // CraftBukkit - SPIGOT-6086, SPIGOT-6296
if (this.changedBlocksPerSection[i] == null) {
- this.hasChangedSections = true;
+ this.hasChangedSections = true; this.addToBroadcastMap(); // Paper - optimise chunk tick iteration
this.changedBlocksPerSection[i] = new ShortOpenHashSet();
}
@@ -236,6 +244,7 @@ public class ChunkHolder {
int k = this.lightEngine.getMaxLightSection();
if (y >= j && y <= k) {
+ this.addToBroadcastMap(); // Paper - optimise chunk tick iteration
int l = y - j;
if (lightType == LightLayer.SKY) {
@@ -254,9 +263,19 @@ public class ChunkHolder {
this.broadcast(this.getPlayers(onChunkViewEdge), packet); // Paper - rewrite chunk system
}
// Paper end - starlight
+ // Paper start - optimise chunk tick iteration
+ public final boolean needsBroadcastChanges() {
+ return this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty();
+ }
+
+ private void addToBroadcastMap() {
+ io.papermc.paper.util.TickThread.ensureTickThread(this.chunkMap.level, this.pos, "Asynchronous ChunkHolder update is not allowed");
+ this.chunkMap.needsChangeBroadcasting.add(this);
+ }
+ // Paper end - optimise chunk tick iteration
public void broadcastChanges(LevelChunk chunk) {
- if (this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty()) {
+ if (this.needsBroadcastChanges()) { // Paper - optimise chunk tick iteration; moved into above, other logic needs to call
Level world = chunk.getLevel();
List list;
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 9bb56d235a7614cefcd0551a455f9c4ddadb1db0..39cf2abd9aacb5584467f6cde3d7b6e5b1912e96 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -194,6 +194,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.playerEntityTrackerTrackMaps[i].remove(player);
}
// Paper end - use distance map to optimise tracker
+ this.playerMobSpawnMap.remove(player); // Paper - optimise chunk tick iteration
}
void updateMaps(ServerPlayer player) {
@@ -243,6 +244,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
public final io.papermc.paper.util.player.NearbyPlayers nearbyPlayers;
// Paper end
+ // Paper start - optimise chunk tick iteration
+ public final it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<ChunkHolder> needsChangeBroadcasting = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>();
+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets);
+ // Paper end - optimise chunk tick iteration
public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop<Runnable> mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory, int viewDistance, boolean dsync) {
super(new RegionStorageInfo(session.getLevelId(), world.dimension(), "chunk"), session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync);
@@ -411,7 +416,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
// Paper end - Optional per player mob spawns
- private static double euclideanDistanceSquared(ChunkPos pos, Entity entity) {
+ public static double euclideanDistanceSquared(ChunkPos pos, Entity entity) { // Paper - optimise chunk iteration; public
double d0 = (double) SectionPos.sectionToBlockCoord(pos.x, 8);
double d1 = (double) SectionPos.sectionToBlockCoord(pos.z, 8);
double d2 = d0 - entity.getX();
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
index ed5154e41ca858f4d6b4d1c276c66831c038d2a6..cdb3c2cde5d9133ef60cf96d91762e6a7c8aeb4a 100644
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
@@ -49,7 +49,7 @@ public abstract class DistanceManager {
private static final int INITIAL_TICKET_LIST_CAPACITY = 4;
final Long2ObjectMap<ObjectSet<ServerPlayer>> playersPerChunk = new Long2ObjectOpenHashMap();
// Paper - rewrite chunk system
- private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8);
+ public static final int MOB_SPAWN_RANGE = 8; //private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8); // Paper - optimise chunk tick iteration
// Paper - rewrite chunk system
private final ChunkMap chunkMap; // Paper
@@ -135,7 +135,7 @@ public abstract class DistanceManager {
long i = chunkcoordintpair.toLong();
// Paper - no longer used
- this.naturalSpawnChunkCounter.update(i, 0, true);
+ //this.naturalSpawnChunkCounter.update(i, 0, true); // Paper - optimise chunk tick iteration
//this.playerTicketManager.update(i, 0, true); // Paper - no longer used
//this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used
}
@@ -149,7 +149,7 @@ public abstract class DistanceManager {
if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully
if (objectset == null || objectset.isEmpty()) { // Paper
this.playersPerChunk.remove(i);
- this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false);
+ //this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); // Paper - optimise chunk tick iteration
//this.playerTicketManager.update(i, Integer.MAX_VALUE, false); // Paper - no longer used
//this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used
}
@@ -191,13 +191,11 @@ public abstract class DistanceManager {
}
public int getNaturalSpawnChunkCount() {
- this.naturalSpawnChunkCounter.runAllUpdates();
- return this.naturalSpawnChunkCounter.chunks.size();
+ return this.chunkMap.playerMobSpawnMap.size(); // Paper - optimise chunk tick iteration
}
public boolean hasPlayersNearby(long chunkPos) {
- this.naturalSpawnChunkCounter.runAllUpdates();
- return this.naturalSpawnChunkCounter.chunks.containsKey(chunkPos);
+ return this.chunkMap.playerMobSpawnMap.getObjectsInRange(chunkPos) != null; // Paper - optimise chunk tick iteration
}
public String getDebugStatus() {
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index 18640c8681f6a3b2276123d19e3e8f0a8c630b41..b99f50604bafecbc68835974c9ed0caa91911a40 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -506,18 +506,10 @@ public class ServerChunkCache extends ChunkSource {
gameprofilerfiller.push("pollingChunks");
gameprofilerfiller.push("filteringLoadedChunks");
- List<ServerChunkCache.ChunkAndHolder> list = Lists.newArrayListWithCapacity(this.chunkMap.size());
- Iterator iterator = this.chunkMap.getChunks().iterator();
+ // Paper - optimise chunk tick iteration
if (this.level.getServer().tickRateManager().runsNormally()) this.level.timings.chunkTicks.startTiming(); // Paper
- while (iterator.hasNext()) {
- ChunkHolder playerchunk = (ChunkHolder) iterator.next();
- LevelChunk chunk = playerchunk.getTickingChunk();
-
- if (chunk != null) {
- list.add(new ServerChunkCache.ChunkAndHolder(chunk, playerchunk));
- }
- }
+ // Paper - optimise chunk tick iteration
if (this.level.tickRateManager().runsNormally()) {
gameprofilerfiller.popPush("naturalSpawnCount");
@@ -552,38 +544,109 @@ public class ServerChunkCache extends ChunkSource {
gameprofilerfiller.popPush("spawnAndTick");
boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit
- Util.shuffle(list, this.level.random);
- // Paper start - PlayerNaturallySpawnCreaturesEvent
- int chunkRange = level.spigotConfig.mobSpawnRange;
- chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange;
- chunkRange = Math.min(chunkRange, 8);
- for (ServerPlayer entityPlayer : this.level.players()) {
- entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange);
- entityPlayer.playerNaturallySpawnedEvent.callEvent();
+ // Paper start - optimise chunk tick iteration
+ ChunkMap playerChunkMap = this.chunkMap;
+ for (ServerPlayer player : this.level.players) {
+ if (!player.affectsSpawning || player.isSpectator()) {
+ playerChunkMap.playerMobSpawnMap.remove(player);
+ player.playerNaturallySpawnedEvent = null;
+ player.lastEntitySpawnRadiusSquared = -1.0;
+ continue;
+ }
+
+ int viewDistance = io.papermc.paper.chunk.system.ChunkSystem.getTickViewDistance(player);
+
+ // copied and modified from isOutisdeRange
+ int chunkRange = (int)level.spigotConfig.mobSpawnRange;
+ chunkRange = (chunkRange > viewDistance) ? viewDistance : chunkRange;
+ chunkRange = (chunkRange > DistanceManager.MOB_SPAWN_RANGE) ? DistanceManager.MOB_SPAWN_RANGE : chunkRange;
+
+ com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(player.getBukkitEntity(), (byte)chunkRange);
+ event.callEvent();
+ if (event.isCancelled() || event.getSpawnRadius() < 0) {
+ playerChunkMap.playerMobSpawnMap.remove(player);
+ player.playerNaturallySpawnedEvent = null;
+ player.lastEntitySpawnRadiusSquared = -1.0;
+ continue;
+ }
+
+ int range = Math.min(event.getSpawnRadius(), DistanceManager.MOB_SPAWN_RANGE); // limit to max spawn range
+ int chunkX = io.papermc.paper.util.CoordinateUtils.getChunkCoordinate(player.getX());
+ int chunkZ = io.papermc.paper.util.CoordinateUtils.getChunkCoordinate(player.getZ());
+
+ playerChunkMap.playerMobSpawnMap.addOrUpdate(player, chunkX, chunkZ, range);
+ player.lastEntitySpawnRadiusSquared = (double)((range << 4) * (range << 4)); // used in anyPlayerCloseEnoughForSpawning
+ player.playerNaturallySpawnedEvent = event;
}
- // Paper end - PlayerNaturallySpawnCreaturesEvent
+ // Paper end - optimise chunk tick iteration
int l = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
boolean flag1 = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit
- Iterator iterator1 = list.iterator();
+ // Paper - optimise chunk tick iteration
int chunksTicked = 0; // Paper
- while (iterator1.hasNext()) {
- ServerChunkCache.ChunkAndHolder chunkproviderserver_a = (ServerChunkCache.ChunkAndHolder) iterator1.next();
- LevelChunk chunk1 = chunkproviderserver_a.chunk;
+ // Paper start - optimise chunk tick iteration
+ io.papermc.paper.util.player.NearbyPlayers nearbyPlayers = this.chunkMap.getNearbyPlayers(); // Paper - optimise chunk tick iteration
+ Iterator<LevelChunk> chunkIterator;
+ if (this.level.paperConfig().entities.spawning.perPlayerMobSpawns) {
+ chunkIterator = this.tickingChunks.iterator();
+ } else {
+ chunkIterator = this.tickingChunks.unsafeIterator();
+ List<LevelChunk> shuffled = Lists.newArrayListWithCapacity(this.tickingChunks.size());
+ while (chunkIterator.hasNext()) {
+ shuffled.add(chunkIterator.next());
+ }
+ Util.shuffle(shuffled, this.level.random);
+ chunkIterator = shuffled.iterator();
+ }
+ try {
+ // Paper end - optimise chunk tick iteration
+ while (chunkIterator.hasNext()) {
+ LevelChunk chunk1 = chunkIterator.next();
+ // Paper end - optimise chunk tick iteration
ChunkPos chunkcoordintpair = chunk1.getPos();
- if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair)) {
+ // Paper start - optimise chunk tick iteration
+ com.destroystokyo.paper.util.maplist.ReferenceList<ServerPlayer> playersNearby
+ = nearbyPlayers.getPlayers(chunkcoordintpair, io.papermc.paper.util.player.NearbyPlayers.NearbyMapType.SPAWN_RANGE);
+ if (playersNearby == null) {
+ continue;
+ }
+ Object[] rawData = playersNearby.getRawData();
+ boolean spawn = false;
+ boolean tick = false;
+ for (int itr = 0, len = playersNearby.size(); itr < len; ++itr) {
+ ServerPlayer player = (ServerPlayer)rawData[itr];
+ if (player.isSpectator()) {
+ continue;
+ }
+
+ double distance = ChunkMap.euclideanDistanceSquared(chunkcoordintpair, player);
+ spawn |= player.lastEntitySpawnRadiusSquared >= distance;
+ tick |= ((double)io.papermc.paper.util.player.NearbyPlayers.SPAWN_RANGE_VIEW_DISTANCE_BLOCKS) * ((double)io.papermc.paper.util.player.NearbyPlayers.SPAWN_RANGE_VIEW_DISTANCE_BLOCKS) >= distance;
+ if (spawn & tick) {
+ break;
+ }
+ }
+ if (tick && chunk1.chunkStatus.isOrAfter(net.minecraft.server.level.FullChunkStatus.ENTITY_TICKING)) {
+ // Paper end - optimise chunk tick iteration
chunk1.incrementInhabitedTime(j);
- if (flag && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair, true)) { // Spigot
+ if (spawn && flag && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair)) { // Spigot // Paper - optimise chunk tick iteration
NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1);
}
- if (this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) {
+ if (true || this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { // Paper - optimise chunk tick iteration
this.level.tickChunk(chunk1, l);
if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // Paper
}
}
}
+ // Paper start - optimise chunk tick iteration
+ } finally {
+ if (chunkIterator instanceof io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator safeIterator) {
+ safeIterator.finishedIterating();
+ }
+ }
+ // Paper end - optimise chunk tick iteration
this.level.timings.chunkTicks.stopTiming(); // Paper
gameprofilerfiller.popPush("customSpawners");
@@ -595,11 +658,23 @@ public class ServerChunkCache extends ChunkSource {
}
gameprofilerfiller.popPush("broadcast");
- list.forEach((chunkproviderserver_a1) -> {
+ // Paper - optimise chunk tick iteration
this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing
- chunkproviderserver_a1.holder.broadcastChanges(chunkproviderserver_a1.chunk);
+ // Paper start - optimise chunk tick iteration
+ if (!this.chunkMap.needsChangeBroadcasting.isEmpty()) {
+ it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<ChunkHolder> copy = this.chunkMap.needsChangeBroadcasting.clone();
+ this.chunkMap.needsChangeBroadcasting.clear();
+ for (ChunkHolder holder : copy) {
+ holder.broadcastChanges(holder.getFullChunkNowUnchecked()); // LevelChunks are NEVER unloaded
+ if (holder.needsBroadcastChanges()) {
+ // I DON'T want to KNOW what DUMB plugins might be doing.
+ this.chunkMap.needsChangeBroadcasting.add(holder);
+ }
+ }
+ }
+ // Paper end - optimise chunk tick iteration
this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing
- });
+ // Paper - optimise chunk tick iteration
gameprofilerfiller.pop();
gameprofilerfiller.pop();
}
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
index ed42a78aebeda86b77f27cba4fcefbc6c17f86fc..49d612c1b84fe4f2335c9cf91cce5f60b6779ff6 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
@@ -341,6 +341,9 @@ public class ServerPlayer extends Player {
});
}
// Paper end - replace player chunk loader
+ // Paper start - optimise chunk tick iteration
+ public double lastEntitySpawnRadiusSquared = -1.0;
+ // Paper end - optimise chunk tick iteration
public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) {
super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile);

View file

@ -0,0 +1,129 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sat, 23 Sep 2023 22:05:35 -0700
Subject: [PATCH] Lag compensation ticks
Areas affected by lag comepnsation:
- Block breaking and destroying
- Eating food items
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 1adcbfb16ea1fe3378cde7c53e8868840eade963..5f3b35f0dbd9e78ad18ef5cf7be1a807beffeaf1 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -318,6 +318,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
public volatile Thread shutdownThread; // Paper
public volatile boolean abnormalExit = false; // Paper
+ public static final long SERVER_INIT = System.nanoTime(); // Paper - Lag compensation
public static <S extends MinecraftServer> S spin(Function<Thread, S> serverFactory) {
AtomicReference<S> atomicreference = new AtomicReference();
@@ -1757,6 +1758,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers
+ worldserver.updateLagCompensationTick(); // Paper - lag compensation
this.profiler.push(() -> {
String s = String.valueOf(worldserver);
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index a2a5a43c5e73ba2cccbec4e1ac563f692ae901b5..14e37f4720f35c5aca1f31f1de5f60f7d9ef06cd 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -570,6 +570,17 @@ public class ServerLevel extends Level implements WorldGenLevel {
return player != null && player.level() == this ? player : null;
}
// Paper end - optimise getPlayerByUUID
+ // Paper start - lag compensation
+ private long lagCompensationTick = net.minecraft.server.MinecraftServer.SERVER_INIT;
+
+ public long getLagCompensationTick() {
+ return this.lagCompensationTick;
+ }
+
+ public void updateLagCompensationTick() {
+ this.lagCompensationTick = (System.nanoTime() - net.minecraft.server.MinecraftServer.SERVER_INIT) / (java.util.concurrent.TimeUnit.MILLISECONDS.toNanos(50L));
+ }
+ // Paper end - lag compensation
// Add env and gen to constructor, IWorldDataServer -> WorldDataServer
public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey<Level> resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List<CustomSpawner> list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) {
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
index 3b6ebe8f9575783a1607eb6667554ca66de94271..969e64364cdc9ef419328ba3a3a9444cc83af813 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
@@ -127,7 +127,7 @@ public class ServerPlayerGameMode {
}
public void tick() {
- this.gameTicks = MinecraftServer.currentTick; // CraftBukkit;
+ this.gameTicks = (int)this.level.getLagCompensationTick(); // CraftBukkit; // Paper - lag compensation
BlockState iblockdata;
if (this.hasDelayedDestroy) {
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
index 9ff43ff4076c658b8561c5a7abd067423830f964..1ba83df3c2717dbd027b02d4d69e50091977d35f 100644
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
@@ -3833,6 +3833,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
this.getEntityData().resendPossiblyDesyncedDataValues(java.util.List.of(DATA_LIVING_ENTITY_FLAGS), serverPlayer);
}
// Paper end - Properly cancel usable items
+ // Paper start - lag compensate eating
+ protected long eatStartTime;
+ protected int totalEatTimeTicks;
+ // Paper end - lag compensate eating
private void updatingUsingItem() {
if (this.isUsingItem()) {
if (ItemStack.isSameItem(this.getItemInHand(this.getUsedItemHand()), this.useItem)) {
@@ -3851,7 +3855,12 @@ public abstract class LivingEntity extends Entity implements Attackable {
this.triggerItemUseEffects(stack, 5);
}
- if (--this.useItemRemaining == 0 && !this.level().isClientSide && !stack.useOnRelease()) {
+ // Paper start - lag compensate eating
+ // we add 1 to the expected time to avoid lag compensating when we should not
+ boolean shouldLagCompensate = this.useItem.getItem().isEdible() && this.eatStartTime != -1 && (System.nanoTime() - this.eatStartTime) > ((1 + this.totalEatTimeTicks) * 50 * (1000 * 1000));
+ if ((--this.useItemRemaining == 0 || shouldLagCompensate) && !this.level().isClientSide && !stack.useOnRelease()) {
+ this.useItemRemaining = 0;
+ // Paper end - lag compensate eating
this.completeUsingItem();
}
@@ -3897,7 +3906,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
if (!itemstack.isEmpty() && !this.isUsingItem() || forceUpdate) { // Paper - Prevent consuming the wrong itemstack
this.useItem = itemstack;
- this.useItemRemaining = itemstack.getUseDuration();
+ // Paper start - lag compensate eating
+ this.useItemRemaining = this.totalEatTimeTicks = itemstack.getUseDuration();
+ this.eatStartTime = System.nanoTime();
+ // Paper end - lag compensate eating
if (!this.level().isClientSide) {
this.setLivingEntityFlag(1, true);
this.setLivingEntityFlag(2, hand == InteractionHand.OFF_HAND);
@@ -3922,7 +3934,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
}
} else if (!this.isUsingItem() && !this.useItem.isEmpty()) {
this.useItem = ItemStack.EMPTY;
- this.useItemRemaining = 0;
+ // Paper start - lag compensate eating
+ this.useItemRemaining = this.totalEatTimeTicks = 0;
+ this.eatStartTime = -1L;
+ // Paper end - lag compensate eating
}
}
@@ -4057,7 +4072,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
}
this.useItem = ItemStack.EMPTY;
- this.useItemRemaining = 0;
+ // Paper start - lag compensate eating
+ this.useItemRemaining = this.totalEatTimeTicks = 0;
+ this.eatStartTime = -1L;
+ // Paper end - lag compensate eating
}
public boolean isBlocking() {

View file

@ -0,0 +1,228 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sat, 23 Sep 2023 23:15:52 -0700
Subject: [PATCH] Optimise nearby player retrieval
Instead of searching/testing every player online on the server,
we can instead use the nearby player tracking system to reduce
the number of tests per search.
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index 14e37f4720f35c5aca1f31f1de5f60f7d9ef06cd..16387f864b24c649927d5883d8158b0f7e6d0854 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -581,6 +581,115 @@ public class ServerLevel extends Level implements WorldGenLevel {
this.lagCompensationTick = (System.nanoTime() - net.minecraft.server.MinecraftServer.SERVER_INIT) / (java.util.concurrent.TimeUnit.MILLISECONDS.toNanos(50L));
}
// Paper end - lag compensation
+ // Paper start - optimise nearby player retrieval
+ @Override
+ public java.util.List<net.minecraft.world.entity.player.Player> getNearbyPlayers(net.minecraft.world.entity.ai.targeting.TargetingConditions targetPredicate,
+ net.minecraft.world.entity.LivingEntity entity,
+ net.minecraft.world.phys.AABB box) {
+ return this.getNearbyEntities(Player.class, targetPredicate, entity, box);
+ }
+
+ @Override
+ public Player getNearestPlayer(double x, double y, double z, double maxDistance, @Nullable Predicate<Entity> targetPredicate) {
+ if (maxDistance > 0.0D) {
+ io.papermc.paper.util.player.NearbyPlayers players = this.chunkSource.chunkMap.getNearbyPlayers();
+
+ com.destroystokyo.paper.util.maplist.ReferenceList<ServerPlayer> nearby = players.getPlayersByBlock(
+ io.papermc.paper.util.CoordinateUtils.getBlockCoordinate(x),
+ io.papermc.paper.util.CoordinateUtils.getBlockCoordinate(z),
+ io.papermc.paper.util.player.NearbyPlayers.NearbyMapType.GENERAL
+ );
+
+ if (nearby == null) {
+ return null;
+ }
+
+ ServerPlayer nearest = null;
+ double nearestDist = maxDistance * maxDistance;
+ Object[] rawData = nearby.getRawData();
+ for (int i = 0, len = nearby.size(); i < len; ++i) {
+ ServerPlayer player = (ServerPlayer)rawData[i];
+ double dist = player.distanceToSqr(x, y, z);
+ if (dist >= nearestDist) {
+ continue;
+ }
+
+ if (targetPredicate == null || targetPredicate.test(player)) {
+ nearest = player;
+ nearestDist = dist;
+ }
+ }
+
+ return nearest;
+ } else {
+ ServerPlayer nearest = null;
+ double nearestDist = Double.MAX_VALUE;
+
+ for (ServerPlayer player : this.players()) {
+ double dist = player.distanceToSqr(x, y, z);
+ if (dist >= nearestDist) {
+ continue;
+ }
+
+ if (targetPredicate == null || targetPredicate.test(player)) {
+ nearest = player;
+ nearestDist = dist;
+ }
+ }
+
+ return nearest;
+ }
+ }
+
+ @Override
+ public Player getNearestPlayer(net.minecraft.world.entity.ai.targeting.TargetingConditions targetPredicate, LivingEntity entity) {
+ return this.getNearestPlayer(targetPredicate, entity, entity.getX(), entity.getY(), entity.getZ());
+ }
+
+ @Override
+ public Player getNearestPlayer(net.minecraft.world.entity.ai.targeting.TargetingConditions targetPredicate, LivingEntity entity,
+ double x, double y, double z) {
+ double range = targetPredicate.range;
+ if (range > 0.0D) {
+ io.papermc.paper.util.player.NearbyPlayers players = this.chunkSource.chunkMap.getNearbyPlayers();
+
+ com.destroystokyo.paper.util.maplist.ReferenceList<ServerPlayer> nearby = players.getPlayersByBlock(
+ io.papermc.paper.util.CoordinateUtils.getBlockCoordinate(x),
+ io.papermc.paper.util.CoordinateUtils.getBlockCoordinate(z),
+ io.papermc.paper.util.player.NearbyPlayers.NearbyMapType.GENERAL
+ );
+
+ if (nearby == null) {
+ return null;
+ }
+
+ ServerPlayer nearest = null;
+ double nearestDist = Double.MAX_VALUE;
+ Object[] rawData = nearby.getRawData();
+ for (int i = 0, len = nearby.size(); i < len; ++i) {
+ ServerPlayer player = (ServerPlayer)rawData[i];
+ double dist = player.distanceToSqr(x, y, z);
+ if (dist >= nearestDist) {
+ continue;
+ }
+
+ if (targetPredicate.test(entity, player)) {
+ nearest = player;
+ nearestDist = dist;
+ }
+ }
+
+ return nearest;
+ } else {
+ return this.getNearestEntity(this.players(), targetPredicate, entity, x, y, z);
+ }
+ }
+
+ @Nullable
+ public Player getNearestPlayer(net.minecraft.world.entity.ai.targeting.TargetingConditions targetPredicate, double x, double y, double z) {
+ return this.getNearestPlayer(targetPredicate, null, x, y, z);
+ }
+ // Paper end - optimise nearby player retrieval
// Add env and gen to constructor, IWorldDataServer -> WorldDataServer
public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey<Level> resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List<CustomSpawner> list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) {
diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java
index 2e887e426dcd79e2dda401f127d0e01ca537e80e..65cd42ce9f553e0aa5bf248bdbf902f9d1f55460 100644
--- a/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java
+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java
@@ -21,17 +21,50 @@ public class PlayerSensor extends Sensor<LivingEntity> {
@Override
protected void doTick(ServerLevel world, LivingEntity entity) {
- List<Player> list = world.players()
- .stream()
- .filter(EntitySelector.NO_SPECTATORS)
- .filter(player -> entity.closerThan(player, 16.0))
- .sorted(Comparator.comparingDouble(entity::distanceToSqr))
- .collect(Collectors.toList());
+ // Paper start - Perf: optimise nearby player retrieval & remove streams from hot code
+ io.papermc.paper.util.player.NearbyPlayers nearbyPlayers = world.chunkSource.chunkMap.getNearbyPlayers();
+ net.minecraft.world.phys.Vec3 entityPos = entity.position();
+ com.destroystokyo.paper.util.maplist.ReferenceList<net.minecraft.server.level.ServerPlayer> nearby = nearbyPlayers.getPlayersByChunk(
+ entity.chunkPosition().x,
+ entity.chunkPosition().z,
+ io.papermc.paper.util.player.NearbyPlayers.NearbyMapType.GENERAL_REALLY_SMALL
+ );
+
+ List<Player> players = new java.util.ArrayList<>(nearby == null ? 0 : nearby.size());
+ if (nearby != null) {
+ Object[] rawData = nearby.getRawData();
+ for (int index = 0, len = nearby.size(); index < len; ++index) {
+ net.minecraft.server.level.ServerPlayer player = (net.minecraft.server.level.ServerPlayer) rawData[index];
+ if (player.isSpectator()) {
+ continue;
+ }
+ if (player.distanceToSqr(entityPos.x, entityPos.y, entityPos.z) >= (16.0 * 16.0)) {
+ continue;
+ }
+ players.add(player);
+ }
+ }
+ players.sort(Comparator.comparingDouble(entity::distanceToSqr));
Brain<?> brain = entity.getBrain();
- brain.setMemory(MemoryModuleType.NEAREST_PLAYERS, list);
- List<Player> list2 = list.stream().filter(player -> isEntityTargetable(entity, player)).collect(Collectors.toList());
- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, list2.isEmpty() ? null : list2.get(0));
- Optional<Player> optional = list2.stream().filter(player -> isEntityAttackable(entity, player)).findFirst();
- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, optional);
+
+ brain.setMemory(MemoryModuleType.NEAREST_PLAYERS, players);
+
+ Player firstTargetable = null;
+ Player firstAttackable = null;
+ for (Player player : players) {
+ if (firstTargetable == null && Sensor.isEntityTargetable(entity, player)) {
+ firstTargetable = player;
+ }
+ if (firstAttackable == null && Sensor.isEntityAttackable(entity, player)) {
+ firstAttackable = player;
+ }
+
+ if (firstAttackable != null && firstTargetable != null) {
+ break;
+ }
+ }
+ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, firstTargetable);
+ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, Optional.ofNullable(firstAttackable));
+ // Paper end - Perf: optimise nearby player retrieval & remove streams from hot code
}
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
index aecb0ad814586bfc5e56755ee14379a69388b38c..d2f0c3b26d4beedb49d86e0242d843590d469d02 100644
--- a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
+++ b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
@@ -10,7 +10,7 @@ public class TargetingConditions {
public static final TargetingConditions DEFAULT = forCombat();
private static final double MIN_VISIBILITY_DISTANCE_FOR_INVISIBLE_TARGET = 2.0;
private final boolean isCombat;
- private double range = -1.0;
+ public double range = -1.0; // Paper - public
private boolean checkLineOfSight = true;
private boolean testInvisible = true;
@Nullable
diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java
index 21843501355a0c0c8d594e3e5312e97861c9a777..ea0aee88c7d901034427db201c1b2430f8a1d522 100644
--- a/src/main/java/net/minecraft/world/level/EntityGetter.java
+++ b/src/main/java/net/minecraft/world/level/EntityGetter.java
@@ -233,9 +233,13 @@ public interface EntityGetter {
T livingEntity = null;
for (T livingEntity2 : entityList) {
+ // Paper start - optimise nearby player retrieval; move up
+ // don't check entities outside closest range
+ double e = livingEntity2.distanceToSqr(x, y, z);
+ if (d == -1.0 || e < d) {
+ // Paper end - move up
if (targetPredicate.test(entity, livingEntity2)) {
- double e = livingEntity2.distanceToSqr(x, y, z);
- if (d == -1.0 || e < d) {
+ // Paper - optimise nearby player retrieval; move up
d = e;
livingEntity = livingEntity2;
}

View file

@ -0,0 +1,34 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sat, 18 Jul 2020 16:03:57 -0700
Subject: [PATCH] Distance manager tick timings
Recently this has been taking up more time, so add a timings to
really figure out how much.
diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java
index 46449728f69ee7d4f78470f8da23c055acd53a3b..4b467f1af93452d13829f756d55dee18b8889d40 100644
--- a/src/main/java/co/aikar/timings/MinecraftTimings.java
+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java
@@ -47,6 +47,7 @@ public final class MinecraftTimings {
public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update");
public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate");
public static final Timing scoreboardScoreSearch = Timings.ofSafe("Scoreboard score search"); // Paper - add timings for scoreboard search
+ public static final Timing distanceManagerTick = Timings.ofSafe("Distance Manager Tick"); // Paper - add timings for distance manager
public static final Timing midTickChunkTasks = Timings.ofSafe("Mid Tick Chunk Tasks");
diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java
index 5b446e6ac151f99f64f0c442d0b40b5e251bc4c4..6bc7c6f16a1649fc9e24e7cf90fca401e5bd4875 100644
--- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java
+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java
@@ -1316,7 +1316,9 @@ public final class ChunkHolderManager {
}
public boolean processTicketUpdates() {
+ co.aikar.timings.MinecraftTimings.distanceManagerTick.startTiming(); try { // Paper - add timings for distance manager
return this.processTicketUpdates(true, true, null);
+ } finally { co.aikar.timings.MinecraftTimings.distanceManagerTick.stopTiming(); } // Paper - add timings for distance manager
}
private static final ThreadLocal<List<ChunkProgressionTask>> CURRENT_TICKET_UPDATE_SCHEDULING = new ThreadLocal<>();

View file

@ -0,0 +1,64 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Wed, 6 May 2020 05:00:57 -0400
Subject: [PATCH] Handle Oversized block entities in chunks
Splits out Extra Packets if too many TE's are encountered to prevent
creating too large of a packet to sed.
Co-authored-by: Spottedleaf <Spottedleaf@users.noreply.github.com>
diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
index 05e29a703f022b5047262bae8caef33d9dfb9035..2501fbaf497d226051800c53d60a39bbc80db91c 100644
--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
@@ -27,6 +27,14 @@ public class ClientboundLevelChunkPacketData {
private final CompoundTag heightmaps;
private final byte[] buffer;
private final List<ClientboundLevelChunkPacketData.BlockEntityInfo> blockEntitiesData;
+ // Paper start - Handle oversized block entities in chunks
+ private final java.util.List<net.minecraft.network.protocol.Packet<?>> extraPackets = new java.util.ArrayList<>();
+ private static final int TE_LIMIT = Integer.getInteger("Paper.excessiveTELimit", 750);
+
+ public List<net.minecraft.network.protocol.Packet<?>> getExtraPackets() {
+ return this.extraPackets;
+ }
+ // Paper end - Handle oversized block entities in chunks
// Paper start - Anti-Xray - Add chunk packet info
@Deprecated @io.papermc.paper.annotation.DoNotUse public ClientboundLevelChunkPacketData(LevelChunk chunk) { this(chunk, null); }
@@ -50,8 +58,18 @@ public class ClientboundLevelChunkPacketData {
extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), chunk, chunkPacketInfo);
// Paper end
this.blockEntitiesData = Lists.newArrayList();
+ int totalTileEntities = 0; // Paper - Handle oversized block entities in chunks
for (Entry<BlockPos, BlockEntity> entry2 : chunk.getBlockEntities().entrySet()) {
+ // Paper start - Handle oversized block entities in chunks
+ if (++totalTileEntities > TE_LIMIT) {
+ var packet = entry2.getValue().getUpdatePacket();
+ if (packet != null) {
+ this.extraPackets.add(packet);
+ continue;
+ }
+ }
+ // Paper end - Handle oversized block entities in chunks
this.blockEntitiesData.add(ClientboundLevelChunkPacketData.BlockEntityInfo.create(entry2.getValue()));
}
}
diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
index 8ead66c134688b11dca15f6509147e726f182e6a..cfcac0fdc130120cb1f8d97c6353d93db7ddf81b 100644
--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
@@ -83,4 +83,11 @@ public class ClientboundLevelChunkWithLightPacket implements Packet<ClientGamePa
public ClientboundLightUpdatePacketData getLightData() {
return this.lightData;
}
+
+ // Paper start - Handle oversized block entities in chunks
+ @Override
+ public java.util.List<Packet<?>> getExtraPackets() {
+ return this.chunkData.getExtraPackets();
+ }
+ // Paper end - Handle oversized block entities in chunks
}

View file

@ -0,0 +1,23 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Tue, 16 Feb 2021 00:16:56 -0800
Subject: [PATCH] Send full pos packets for hard colliding entities
Prevent collision problems due to desync (i.e boats)
Configurable under
`send-full-pos-for-hard-colliding-entities`
diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
index 22eec853588ded2d255ab69d408f8e987832abe2..4f103f731623a8570238a6867fda1c5f83fca4e4 100644
--- a/src/main/java/net/minecraft/server/level/ServerEntity.java
+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
@@ -183,7 +183,7 @@ public class ServerEntity {
long i1 = this.positionCodec.encodeZ(vec3d);
boolean flag6 = k < -32768L || k > 32767L || l < -32768L || l > 32767L || i1 < -32768L || i1 > 32767L;
- if (!flag6 && this.teleportDelay <= 400 && !this.wasRiding && this.wasOnGround == this.entity.onGround()) {
+ if (!flag6 && this.teleportDelay <= 400 && !this.wasRiding && this.wasOnGround == this.entity.onGround()&& !(io.papermc.paper.configuration.GlobalConfiguration.get().collisions.sendFullPosForHardCollidingEntities && this.entity.hardCollides())) { // Paper - send full pos for hard colliding entities to prevent collision problems due to desync
if ((!flag2 || !flag3) && !(this.entity instanceof AbstractArrow)) {
if (flag2) {
packet1 = new ClientboundMoveEntityPacket.Pos(this.entity.getId(), (short) ((int) k), (short) ((int) l), (short) ((int) i1), this.entity.onGround());