1.18 misc performance dev branch (#7368)

- Port player chunk loader patch
Makes the chunk system act as it did in 1.17, no additional tickets (and thus logic) to make a chunk ticking.
Adds simulation distance API, deprecates old no-tick method.
- More collision optimisations
Ancient patch from tuinity that never could be pushed to master.
- Fix Optimise ArraySetSorted#removeIf patch
- Execute chunk tasks fairly for worlds while waiting for next tick
- Port Replace ticket level propagator
This commit is contained in:
Spottedleaf 2022-02-18 09:44:46 -08:00
parent 9425b30b18
commit 722983fbc7
14 changed files with 3258 additions and 97 deletions

View file

@ -23,13 +23,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ void setViewDistance(int viewDistance);
+
+ /**
+ * Sets the simulation distance for this world.
+ * @param simulationDistance simulation distance in [2, 32]
+ */
+ void setSimulationDistance(int simulationDistance);
+
+ /**
+ * Returns the no-tick view distance for this world.
+ * <p>
+ * No-tick view distance is the view distance where chunks will load, however the chunks and their entities will not
+ * be set to tick.
+ * </p>
+ * @return The no-tick view distance for this world.
+ * @deprecated Use {@link #getViewDistance()}
+ */
+ @Deprecated
+ int getNoTickViewDistance();
+
+ /**
@ -39,7 +47,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ * be set to tick.
+ * </p>
+ * @param viewDistance view distance in [2, 32]
+ * @deprecated Use {@link #setViewDistance(int)}
+ */
+ @Deprecated
+ void setNoTickViewDistance(int viewDistance);
+
+ /**
@ -49,7 +59,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ * </p>
+ * @return The sending view distance for this world.
+ */
+ public int getSendViewDistance();
+ int getSendViewDistance();
+
+ /**
+ * Sets the sending view distance for this world.
@ -58,7 +68,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ * </p>
+ * @param viewDistance view distance in [2, 32] or -1
+ */
+ public void setSendViewDistance(int viewDistance);
+ void setSendViewDistance(int viewDistance);
+ // Paper end - view distance api
+
// Spigot start
@ -78,7 +88,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ *
+ * @return the player's view distance
+ * @see org.bukkit.World#getViewDistance()
+ * @see org.bukkit.World#getNoTickViewDistance()
+ */
+ public int getViewDistance();
+
@ -87,9 +96,22 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ *
+ * @param viewDistance the player's view distance
+ * @see org.bukkit.World#setViewDistance(int)
+ * @see org.bukkit.World#setNoTickViewDistance(int)
+ */
+ public void setViewDistance(int viewDistance);
+
+ /**
+ * Gets the simulation distance for this player
+ *
+ * @return the player's simulation distance
+ */
+ public int getSimulationDistance();
+
+ /**
+ * Sets the simulation distance for this player
+ *
+ * @param simulationDistance the player's new simulation distance
+ */
+ public void setSimulationDistance(int simulationDistance);
+
+ /**
+ * Gets the no-ticking view distance for this player.
@ -98,7 +120,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ * be set to tick.
+ * </p>
+ * @return The no-tick view distance for this player.
+ * @deprecated Use {@link #getViewDistance()}
+ */
+ @Deprecated
+ public int getNoTickViewDistance();
+
+ /**
@ -108,7 +132,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ * be set to tick.
+ * </p>
+ * @param viewDistance view distance in [2, 32] or -1
+ * @deprecated Use {@link #setViewDistance(int)}
+ */
+ @Deprecated
+ public void setNoTickViewDistance(int viewDistance);
+
+ /**

View file

@ -84,8 +84,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
commands.put("paper", new PaperCommand("paper"));
+ commands.put("mspt", new MSPTCommand("mspt"));
version = getInt("config-version", 24);
set("config-version", 24);
version = getInt("config-version", 25);
set("config-version", 25);
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java

View file

@ -26,9 +26,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
// Paper - no, iterating just ONCE is expensive enough! Don't do it TWICE! Code moved up
this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing
gameprofilerfiller.pop();
// Paper end - use set of chunks requiring updates, rather than iterating every single one loaded
+ // Paper start - controlled flush for entity tracker packets
+ List<net.minecraft.network.Connection> disabledFlushes = new java.util.ArrayList<>(this.level.players.size());
+ for (ServerPlayer player : this.level.players) {

View file

@ -116,7 +116,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ ChunkHolder playerchunk = this.updatingChunks.queueRemove(j); // Paper - Don't copy
if (playerchunk != null) {
this.pendingUnloads.put(j, playerchunk);
playerchunk.onChunkRemove(); // Paper
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
if (!this.modified) {
return false;

View file

@ -0,0 +1,37 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Tue, 28 Dec 2021 07:19:01 -0800
Subject: [PATCH] Execute chunk tasks fairly for worlds while waiting for next
tick
Currently, only the first world would have had tasks executed.
This might result in chunks loading far slower in the nether,
for example.
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.executeMidTickTasks(); // Paper - execute chunk tasks mid tick
return true;
} else {
+ boolean ret = false; // Paper - force execution of all worlds, do not just bias the first
if (this.haveTime()) {
Iterator iterator = this.getAllLevels().iterator();
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
ServerLevel worldserver = (ServerLevel) iterator.next();
if (worldserver.getChunkSource().pollTask()) {
- return true;
+ ret = true; // Paper - force execution of all worlds, do not just bias the first
}
}
}
- return false;
+ return ret; // Paper - force execution of all worlds, do not just bias the first
}
}

View file

@ -91,6 +91,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+import net.minecraft.world.level.border.WorldBorder;
+import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.level.chunk.LevelChunkSection;
+import net.minecraft.world.level.chunk.PalettedContainer;
+import net.minecraft.world.level.material.FlowingFluid;
+import net.minecraft.world.level.material.FluidState;
+import net.minecraft.world.phys.AABB;
@ -101,7 +102,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+import net.minecraft.world.phys.shapes.Shapes;
+import net.minecraft.world.phys.shapes.VoxelShape;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
+
@ -109,6 +109,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+
+ public static final double COLLISION_EPSILON = 1.0E-7;
+
+ public static final long KNOWN_EMPTY_BLOCK = 0b00; // known to always have voxelshape of empty
+ public static final long KNOWN_FULL_BLOCK = 0b01; // known to always have voxelshape of full cube
+ public static final long KNOWN_UNKNOWN_BLOCK = 0b10; // must read the actual block state for info
+ public static final long KNOWN_SPECIAL_BLOCK = 0b11; // caller must check this block for special collisions
+
+ public static boolean isSpecialCollidingBlock(final net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase block) {
+ return block.shapeExceedsCube() || block.getBlock() == Blocks.MOVING_PISTON;
+ }
+
+ public static boolean isEmpty(final AABB aabb) {
+ return (aabb.maxX - aabb.minX) < COLLISION_EPSILON && (aabb.maxY - aabb.minY) < COLLISION_EPSILON && (aabb.maxZ - aabb.minZ) < COLLISION_EPSILON;
+ }
@ -471,8 +480,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ }
+
+ public static boolean getCollisionsForBlocksOrWorldBorder(final CollisionGetter getter, final Entity entity, final AABB aabb,
+ final List<AABB> into, final boolean loadChunks, final boolean collidesWithUnloaded,
+ final boolean checkBorder, final boolean checkOnly, final BiPredicate<BlockState, BlockPos> predicate) {
+ final List<AABB> into, final boolean loadChunks, final boolean collidesWithUnloaded,
+ final boolean checkBorder, final boolean checkOnly, final BiPredicate<BlockState, BlockPos> predicate) {
+ boolean ret = false;
+
+ if (checkBorder) {
@ -486,21 +495,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ }
+ }
+
+ int minBlockX = Mth.floor(aabb.minX - COLLISION_EPSILON) - 1;
+ int maxBlockX = Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1;
+ final int minBlockX = Mth.floor(aabb.minX - COLLISION_EPSILON) - 1;
+ final int maxBlockX = Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1;
+
+ int minBlockY = Mth.floor(aabb.minY - COLLISION_EPSILON) - 1;
+ int maxBlockY = Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1;
+ final int minBlockY = Mth.floor(aabb.minY - COLLISION_EPSILON) - 1;
+ final int maxBlockY = Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1;
+
+ int minBlockZ = Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1;
+ int maxBlockZ = Mth.floor(aabb.maxZ + COLLISION_EPSILON) + 1;
+ final int minBlockZ = Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1;
+ final int maxBlockZ = Mth.floor(aabb.maxZ + COLLISION_EPSILON) + 1;
+
+ final int minSection = WorldUtil.getMinSection(getter);
+ final int maxSection = WorldUtil.getMaxSection(getter);
+ final int minBlock = minSection << 4;
+ final int maxBlock = (maxSection << 4) | 15;
+
+ BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
+ final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
+ CollisionContext collisionShape = null;
+
+ // special cases:
@ -509,16 +518,22 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ return ret;
+ }
+
+ int minYIterate = Math.max(minBlock, minBlockY);
+ int maxYIterate = Math.min(maxBlock, maxBlockY);
+ final int minYIterate = Math.max(minBlock, minBlockY);
+ final int maxYIterate = Math.min(maxBlock, maxBlockY);
+
+ int minChunkX = minBlockX >> 4;
+ int maxChunkX = maxBlockX >> 4;
+ final int minChunkX = minBlockX >> 4;
+ final int maxChunkX = maxBlockX >> 4;
+
+ int minChunkZ = minBlockZ >> 4;
+ int maxChunkZ = maxBlockZ >> 4;
+ final int minChunkY = minBlockY >> 4;
+ final int maxChunkY = maxBlockY >> 4;
+
+ ServerChunkCache chunkProvider;
+ final int minChunkYIterate = minYIterate >> 4;
+ final int maxChunkYIterate = maxYIterate >> 4;
+
+ final int minChunkZ = minBlockZ >> 4;
+ final int maxChunkZ = maxBlockZ >> 4;
+
+ final ServerChunkCache chunkProvider;
+ if (getter instanceof WorldGenRegion) {
+ chunkProvider = null;
+ } else if (getter instanceof ServerLevel) {
@ -526,26 +541,24 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ } else {
+ chunkProvider = null;
+ }
+ // TODO special case single chunk?
+
+ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
+ int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk
+ int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk
+ final int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk
+ final int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk
+
+ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
+ int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk
+ int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk
+ final int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk
+ final int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk
+
+ int chunkXGlobalPos = currChunkX << 4;
+ int chunkZGlobalPos = currChunkZ << 4;
+ ChunkAccess chunk;
+ final int chunkXGlobalPos = currChunkX << 4;
+ final int chunkZGlobalPos = currChunkZ << 4;
+ final ChunkAccess chunk;
+ if (chunkProvider == null) {
+ chunk = (ChunkAccess)getter.getChunkForCollisions(currChunkX, currChunkZ);
+ } else {
+ chunk = loadChunks ? chunkProvider.getChunk(currChunkX, currChunkZ, true) : chunkProvider.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ);
+ }
+
+
+ if (chunk == null) {
+ if (collidesWithUnloaded) {
+ if (checkOnly) {
@ -558,59 +571,306 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ continue;
+ }
+
+ LevelChunkSection[] sections = chunk.getSections();
+ final LevelChunkSection[] sections = chunk.getSections();
+
+ // bound y
+
+ for (int currY = minYIterate; currY <= maxYIterate; ++currY) {
+ LevelChunkSection section = sections[(currY >> 4) - minSection];
+ if (section.hasOnlyAir()) {
+ for (int currChunkY = minChunkYIterate; currChunkY <= maxChunkYIterate; ++currChunkY) {
+ final LevelChunkSection section = sections[currChunkY - minSection];
+ if (section == null || section.hasOnlyAir()) {
+ // empty
+ // skip to next section
+ currY = (currY & ~(15)) + 15; // increment by 15: iterator loop increments by the extra one
+ continue;
+ }
+ final PalettedContainer<BlockState> blocks = section.states;
+
+ net.minecraft.world.level.chunk.PalettedContainer<BlockState> blocks = section.states;
+ final int minY = currChunkY == minChunkYIterate ? minYIterate & 15 : 0; // coordinate in chunk
+ final int maxY = currChunkY == maxChunkYIterate ? maxYIterate & 15 : 15; // coordinate in chunk
+ final int chunkYGlobalPos = currChunkY << 4;
+
+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
+ for (int currX = minX; currX <= maxX; ++currX) {
+ int localBlockIndex = (currX) | (currZ << 4) | ((currY & 15) << 8);
+ int blockX = currX | chunkXGlobalPos;
+ int blockY = currY;
+ int blockZ = currZ | chunkZGlobalPos;
+ final boolean sectionHasSpecial = section.hasSpecialCollidingBlocks();
+
+ int edgeCount = ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) +
+ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) +
+ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0);
+ if (edgeCount == 3) {
+ final int minXIterate;
+ final int maxXIterate;
+ final int minZIterate;
+ final int maxZIterate;
+ final int minYIterateLocal;
+ final int maxYIterateLocal;
+
+ if (!sectionHasSpecial) {
+ minXIterate = currChunkX == minChunkX ? minX + 1 : minX;
+ maxXIterate = currChunkX == maxChunkX ? maxX - 1 : maxX;
+ minZIterate = currChunkZ == minChunkZ ? minZ + 1 : minZ;
+ maxZIterate = currChunkZ == maxChunkZ ? maxZ - 1 : maxZ;
+ minYIterateLocal = currChunkY == minChunkY ? minY + 1 : minY;
+ maxYIterateLocal = currChunkY == maxChunkY ? maxY - 1 : maxY;
+ if (minXIterate > maxXIterate || minZIterate > maxZIterate) {
+ continue;
+ }
+ } else {
+ minXIterate = minX;
+ maxXIterate = maxX;
+ minZIterate = minZ;
+ maxZIterate = maxZ;
+ minYIterateLocal = minY;
+ maxYIterateLocal = maxY;
+ }
+
+ for (int currY = minYIterateLocal; currY <= maxYIterateLocal; ++currY) {
+ long collisionForHorizontal = section.getKnownBlockInfoHorizontalRaw(currY, minZIterate & 15);
+ for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ,
+ collisionForHorizontal = (currZ & 1) == 0 ? section.getKnownBlockInfoHorizontalRaw(currY, currZ & 15) : collisionForHorizontal) {
+ // From getKnownBlockInfoHorizontalRaw:
+ // important detail: this returns 32 values, one for localZ = localZ & (~1) and one for localZ = localZ | 1
+ // the even localZ is the lower 32 bits, the odd is the upper 32 bits
+ // We want to use a bitset to only iterate over non-empty blocks.
+ // We need to build a bitset mask to and out the other collisions we just don't care at all about
+ // First, we need to build a bitset from 0..n*2 where n is the number of blocks on the x axis
+ // It's important to note that the iterate values can be outside [0, 15], but if they are,
+ // then none of the x or z loops would meet their conditions. So we can assume they are never
+ // out of bounds here
+ final int xAxisBits = (maxXIterate - minXIterate + 1) << 1; // << 1 -> * 2 // Never > 32
+ long bitset = (1L << xAxisBits) - 1;
+ // Now we need to offset it by 32 bits if current Z is odd (lower 32 bits is 16 block infos for even z, upper is for odd)
+ int shift = (currZ & 1) << 5; // this will be a LEFT shift
+ // Now we need to offset shift so that the bitset first position is at minXIterate
+ shift += (minXIterate << 1); // 0th pos -> 0th bit, 1st pos -> 2nd bit, ...
+
+ // all done
+ bitset = bitset << shift;
+ if ((collisionForHorizontal & bitset) == 0L) {
+ // All empty
+ continue;
+ }
+ for (int currX = minXIterate; currX <= maxXIterate; ++currX) {
+ final int localBlockIndex = (currX) | (currZ << 4) | (currY << 8);
+
+ BlockState blockData = blocks.get(localBlockIndex);
+ if (blockData.isAir()) {
+ continue;
+ }
+ final int blockInfo = (int) LevelChunkSection.getKnownBlockInfo(localBlockIndex, collisionForHorizontal);
+
+ if ((edgeCount != 1 || blockData.shapeExceedsCube()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) {
+ mutablePos.set(blockX, blockY, blockZ);
+ if (collisionShape == null) {
+ collisionShape = new LazyEntityCollisionContext(entity);
+ }
+ VoxelShape voxelshape2 = blockData.getCollisionShape(getter, mutablePos, collisionShape);
+ if (voxelshape2 != Shapes.empty()) {
+ VoxelShape voxelshape3 = voxelshape2.move((double)blockX, (double)blockY, (double)blockZ);
+
+ if (predicate != null && !predicate.test(blockData, mutablePos)) {
+ switch (blockInfo) {
+ case (int) CollisionUtil.KNOWN_EMPTY_BLOCK: {
+ continue;
+ }
+
+ if (checkOnly) {
+ if (voxelshape3.intersects(aabb)) {
+ return true;
+ case (int) CollisionUtil.KNOWN_FULL_BLOCK: {
+ double blockX = (double)(currX | chunkXGlobalPos);
+ double blockY = (double)(currY | chunkYGlobalPos);
+ double blockZ = (double)(currZ | chunkZGlobalPos);
+ final AABB blockBox = new AABB(
+ blockX, blockY, blockZ,
+ blockX + 1.0, blockY + 1.0, blockZ + 1.0,
+ true
+ );
+ if (predicate != null) {
+ if (!voxelShapeIntersect(aabb, blockBox)) {
+ continue;
+ }
+ // fall through to get the block for the predicate
+ } else {
+ if (voxelShapeIntersect(aabb, blockBox)) {
+ if (checkOnly) {
+ return true;
+ } else {
+ into.add(blockBox);
+ ret = true;
+ }
+ }
+ continue;
+ }
+ }
+ // default: fall through to standard logic
+ }
+
+ int blockX = currX | chunkXGlobalPos;
+ int blockY = currY | chunkYGlobalPos;
+ int blockZ = currZ | chunkZGlobalPos;
+
+ int edgeCount = ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) +
+ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) +
+ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0);
+ if (edgeCount == 3) {
+ continue;
+ }
+
+ BlockState blockData = blocks.get(localBlockIndex);
+
+ if ((edgeCount != 1 || blockData.shapeExceedsCube()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) {
+ mutablePos.set(blockX, blockY, blockZ);
+ if (collisionShape == null) {
+ collisionShape = new LazyEntityCollisionContext(entity);
+ }
+ VoxelShape voxelshape2 = blockData.getCollisionShape(getter, mutablePos, collisionShape);
+ if (voxelshape2 != Shapes.empty()) {
+ VoxelShape voxelshape3 = voxelshape2.move((double)blockX, (double)blockY, (double)blockZ);
+
+ if (predicate != null && !predicate.test(blockData, mutablePos)) {
+ continue;
+ }
+
+ if (checkOnly) {
+ if (voxelshape3.intersects(aabb)) {
+ return true;
+ }
+ } else {
+ ret |= addBoxesToIfIntersects(voxelshape3, aabb, into);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+ }
+
+ public static boolean getCollisionsForBlocksOrWorldBorderReference(final CollisionGetter getter, final Entity entity, final AABB aabb,
+ final List<AABB> into, final boolean loadChunks, final boolean collidesWithUnloaded,
+ final boolean checkBorder, final boolean checkOnly, final BiPredicate<BlockState, BlockPos> predicate) {
+ boolean ret = false;
+
+ if (checkBorder) {
+ if (CollisionUtil.isAlmostCollidingOnBorder(getter.getWorldBorder(), aabb)) {
+ if (checkOnly) {
+ return true;
+ } else {
+ CollisionUtil.addBoxesTo(getter.getWorldBorder().getCollisionShape(), into);
+ ret = true;
+ }
+ }
+ }
+
+ final int minBlockX = Mth.floor(aabb.minX - COLLISION_EPSILON) - 1;
+ final int maxBlockX = Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1;
+
+ final int minBlockY = Mth.floor(aabb.minY - COLLISION_EPSILON) - 1;
+ final int maxBlockY = Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1;
+
+ final int minBlockZ = Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1;
+ final int maxBlockZ = Mth.floor(aabb.maxZ + COLLISION_EPSILON) + 1;
+
+ final int minSection = WorldUtil.getMinSection(getter);
+ final int maxSection = WorldUtil.getMaxSection(getter);
+ final int minBlock = minSection << 4;
+ final int maxBlock = (maxSection << 4) | 15;
+
+ final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
+ CollisionContext collisionShape = null;
+
+ // special cases:
+ if (minBlockY > maxBlock || maxBlockY < minBlock) {
+ // no point in checking
+ return ret;
+ }
+
+ final int minYIterate = Math.max(minBlock, minBlockY);
+ final int maxYIterate = Math.min(maxBlock, maxBlockY);
+
+ final int minChunkX = minBlockX >> 4;
+ final int maxChunkX = maxBlockX >> 4;
+
+ final int minChunkY = minBlockY >> 4;
+ final int maxChunkY = maxBlockY >> 4;
+
+ final int minChunkYIterate = minYIterate >> 4;
+ final int maxChunkYIterate = maxYIterate >> 4;
+
+ final int minChunkZ = minBlockZ >> 4;
+ final int maxChunkZ = maxBlockZ >> 4;
+
+ final ServerChunkCache chunkProvider;
+ if (getter instanceof WorldGenRegion) {
+ chunkProvider = null;
+ } else if (getter instanceof ServerLevel) {
+ chunkProvider = ((ServerLevel)getter).getChunkSource();
+ } else {
+ chunkProvider = null;
+ }
+
+ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
+ final int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk
+ final int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk
+
+ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
+ final int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk
+ final int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk
+
+ final int chunkXGlobalPos = currChunkX << 4;
+ final int chunkZGlobalPos = currChunkZ << 4;
+ final ChunkAccess chunk;
+ if (chunkProvider == null) {
+ chunk = (ChunkAccess)getter.getChunkForCollisions(currChunkX, currChunkZ);
+ } else {
+ chunk = loadChunks ? chunkProvider.getChunk(currChunkX, currChunkZ, true) : chunkProvider.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ);
+ }
+
+ if (chunk == null) {
+ if (collidesWithUnloaded) {
+ if (checkOnly) {
+ return true;
+ } else {
+ into.add(getBoxForChunk(currChunkX, currChunkZ));
+ ret = true;
+ }
+ }
+ continue;
+ }
+
+ final LevelChunkSection[] sections = chunk.getSections();
+
+ // bound y
+ for (int currChunkY = minChunkYIterate; currChunkY <= maxChunkYIterate; ++currChunkY) {
+ final LevelChunkSection section = sections[currChunkY - minSection];
+ if (section == null || section.hasOnlyAir()) {
+ // empty
+ continue;
+ }
+ final PalettedContainer<BlockState> blocks = section.states;
+
+ final int minY = currChunkY == minChunkYIterate ? minYIterate & 15 : 0; // coordinate in chunk
+ final int maxY = currChunkY == maxChunkYIterate ? maxYIterate & 15 : 15; // coordinate in chunk
+ final int chunkYGlobalPos = currChunkY << 4;
+
+ for (int currY = minY; currY <= maxY; ++currY) {
+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
+ for (int currX = minX; currX <= maxX; ++currX) {
+ int localBlockIndex = (currX) | (currZ << 4) | ((currY) << 8);
+ int blockX = currX | chunkXGlobalPos;
+ int blockY = currY | chunkYGlobalPos;
+ int blockZ = currZ | chunkZGlobalPos;
+
+ int edgeCount = ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) +
+ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) +
+ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0);
+ if (edgeCount == 3) {
+ continue;
+ }
+
+ BlockState blockData = blocks.get(localBlockIndex);
+ if (blockData.getBlockCollisionBehavior() == CollisionUtil.KNOWN_EMPTY_BLOCK) {
+ continue;
+ }
+
+ if ((edgeCount != 1 || blockData.shapeExceedsCube()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) {
+ mutablePos.set(blockX, blockY, blockZ);
+ if (collisionShape == null) {
+ collisionShape = new LazyEntityCollisionContext(entity);
+ }
+ VoxelShape voxelshape2 = blockData.getCollisionShape(getter, mutablePos, collisionShape);
+ if (voxelshape2 != Shapes.empty()) {
+ VoxelShape voxelshape3 = voxelshape2.move((double)blockX, (double)blockY, (double)blockZ);
+
+ if (predicate != null && !predicate.test(blockData, mutablePos)) {
+ continue;
+ }
+
+ if (checkOnly) {
+ if (voxelshape3.intersects(aabb)) {
+ return true;
+ }
+ } else {
+ ret |= addBoxesToIfIntersects(voxelshape3, aabb, into);
+ }
+ } else {
+ ret |= addBoxesToIfIntersects(voxelshape3, aabb, into);
+ }
+ }
+ }
@ -1219,15 +1479,199 @@ diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
@@ -0,0 +0,0 @@ public abstract class BlockBehaviour {
return this.conditionallyFullOpaque;
}
// Paper end
+ // Paper start
+ private long blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_SPECIAL_BLOCK;
+
+ public final long getBlockCollisionBehavior() {
+ return this.blockCollisionBehavior;
+ }
+ // Paper end
public void initCache() {
this.fluid = this.getBlock().getFluidState(this.asState()); // Paper - moved from getFluid()
@@ -0,0 +0,0 @@ public abstract class BlockBehaviour {
}
this.shapeExceedsCube = this.cache == null || this.cache.largeCollisionShape; // Paper - moved from actual method to here
this.opacityIfCached = this.cache == null || this.isConditionallyFullOpaque() ? -1 : this.cache.lightBlock; // Paper - cache opacity for light
-
+ // TODO optimise light
+ // Paper start
+ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(this)) {
+ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_SPECIAL_BLOCK;
+ } else {
+ try {
+ // There is NOTHING HACKY ABOUT THIS AT ALLLLLLLLLLLLLLL
+ VoxelShape constantShape = this.getCollisionShape(null, null, null);
+ if (constantShape == null) {
+ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_UNKNOWN_BLOCK;
+ } else {
+ constantShape = constantShape.optimize();
+ if (constantShape.isEmpty()) {
+ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_EMPTY_BLOCK;
+ } else {
+ final List<net.minecraft.world.phys.AABB> boxes = constantShape.toAabbs();
+ if (constantShape == net.minecraft.world.phys.shapes.Shapes.getFullUnoptimisedCube() || (boxes.size() == 1 && boxes.get(0).equals(net.minecraft.world.phys.shapes.Shapes.BLOCK_OPTIMISED.aabb))) {
+ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_FULL_BLOCK;
+ } else {
+ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_UNKNOWN_BLOCK;
+ }
+ }
+ }
+ } catch (final Error error) {
+ throw error;
+ } catch (final Throwable throwable) {
+ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_UNKNOWN_BLOCK;
+ }
+ }
+ // Paper end
}
public Block getBlock() {
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
@@ -0,0 +0,0 @@ public class LevelChunkSection {
this.biomes = new PalettedContainer<>(biomeRegistry, (Biome) biomeRegistry.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); // Paper - Anti-Xray - Add preset biomes
}
+ // Paper start
+ protected int specialCollidingBlocks;
+ // blockIndex = x | (z << 4) | (y << 8)
+ private long[] knownBlockCollisionData;
+
+ private long[] initKnownDataField() {
+ return this.knownBlockCollisionData = new long[16 * 16 * 16 * 2 / Long.SIZE];
+ }
+
+ public final boolean hasSpecialCollidingBlocks() {
+ return this.specialCollidingBlocks != 0;
+ }
+
+ public static long getKnownBlockInfo(final int blockIndex, final long value) {
+ final int valueShift = (blockIndex & (Long.SIZE / 2 - 1));
+
+ return (value >>> (valueShift << 1)) & 0b11L;
+ }
+
+ public final long getKnownBlockInfo(final int blockIndex) {
+ if (this.knownBlockCollisionData == null) {
+ return 0L;
+ }
+
+ final int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2)
+ final int valueShift = (blockIndex & (Long.SIZE / 2 - 1));
+
+ final long value = this.knownBlockCollisionData[arrayIndex];
+
+ return (value >>> (valueShift << 1)) & 0b11L;
+ }
+
+ // important detail: this returns 32 values, one for localZ = localZ & (~1) and one for localZ = localZ | 1
+ // the even localZ is the lower 32 bits, the odd is the upper 32 bits
+ public final long getKnownBlockInfoHorizontalRaw(final int localY, final int localZ) {
+ if (this.knownBlockCollisionData == null) {
+ return 0L;
+ }
+
+ final int horizontalIndex = (localZ << 4) | (localY << 8);
+ return this.knownBlockCollisionData[horizontalIndex >>> (6 - 1)];
+ }
+
+ private void initBlockCollisionData() {
+ this.specialCollidingBlocks = 0;
+ // In 1.18 all sections will be initialised, whether or not they have blocks (fucking stupid btw)
+ // This means we can't aggressively initialise the backing long[], or else memory usage will just skyrocket.
+ // So only init if we contain non-empty blocks.
+ if (this.nonEmptyBlockCount == 0) {
+ this.knownBlockCollisionData = null;
+ return;
+ }
+ this.initKnownDataField();
+ for (int index = 0; index < (16 * 16 * 16); ++index) {
+ final BlockState state = this.states.get(index);
+ this.setKnownBlockInfo(index, state);
+ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(state)) {
+ ++this.specialCollidingBlocks;
+ }
+ }
+ }
+
+ // only use for initBlockCollisionData
+ private void setKnownBlockInfo(final int blockIndex, final BlockState blockState) {
+ final int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2)
+ final int valueShift = (blockIndex & (Long.SIZE / 2 - 1)) << 1;
+
+ long value = this.knownBlockCollisionData[arrayIndex];
+
+ value &= ~(0b11L << valueShift);
+ value |= blockState.getBlockCollisionBehavior() << valueShift;
+
+ this.knownBlockCollisionData[arrayIndex] = value;
+ }
+
+ public void updateKnownBlockInfo(final int blockIndex, final BlockState from, final BlockState to) {
+ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(from)) {
+ --this.specialCollidingBlocks;
+ }
+ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(to)) {
+ ++this.specialCollidingBlocks;
+ }
+
+ if (this.nonEmptyBlockCount == 0) {
+ this.knownBlockCollisionData = null;
+ return;
+ }
+
+ if (this.knownBlockCollisionData == null) {
+ this.initKnownDataField();
+ }
+
+ final int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2)
+ final int valueShift = (blockIndex & (Long.SIZE / 2 - 1)) << 1;
+
+ long value = this.knownBlockCollisionData[arrayIndex];
+
+ value &= ~(0b11L << valueShift);
+ value |= to.getBlockCollisionBehavior() << valueShift;
+
+ this.knownBlockCollisionData[arrayIndex] = value;
+ }
+ // Paper end
+
public static int getBottomBlockY(int chunkPos) {
return chunkPos << 4;
}
@@ -0,0 +0,0 @@ public class LevelChunkSection {
return this.setBlockState(x, y, z, state, true);
}
- public BlockState setBlockState(int x, int y, int z, BlockState state, boolean lock) {
- BlockState iblockdata1;
+ public BlockState setBlockState(int x, int y, int z, BlockState state, boolean lock) { // Paper - state -> new state
+ BlockState iblockdata1; // Paper - iblockdata1 -> oldState
if (lock) {
iblockdata1 = (BlockState) this.states.getAndSet(x, y, z, state);
@@ -0,0 +0,0 @@ public class LevelChunkSection {
++this.tickingFluidCount;
}
+ this.updateKnownBlockInfo(x | (z << 4) | (y << 8), iblockdata1, state); // Paper
return iblockdata1;
}
@@ -0,0 +0,0 @@ public class LevelChunkSection {
}
});
+ this.initBlockCollisionData(); // Paper
}
public PalettedContainer<BlockState> getStates() {
diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/phys/AABB.java

View file

@ -5,6 +5,41 @@ Subject: [PATCH] Optimise ArraySetSorted#removeIf
Remove iterator allocation and ensure the call is always O(n)
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
@@ -0,0 +0,0 @@ public abstract class DistanceManager {
protected void purgeStaleTickets() {
++this.ticketTickCounter;
ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator();
+ // Paper start - use optimised removeIf
+ long[] currChunk = new long[1];
+ long ticketCounter = DistanceManager.this.ticketTickCounter;
+ java.util.function.Predicate<Ticket<?>> removeIf = (ticket) -> {
+ final boolean ret = ticket.timedOut(ticketCounter);
+ if (ret) {
+ this.tickingTicketsTracker.removeTicket(currChunk[0], ticket);
+ }
+ return ret;
+ };
+ // Paper end - use optimised removeIf
while (objectiterator.hasNext()) {
Entry<SortedArraySet<Ticket<?>>> entry = (Entry) objectiterator.next();
- Iterator<Ticket<?>> iterator = ((SortedArraySet) entry.getValue()).iterator();
- boolean flag = false;
+ // Paper start - use optimised removeIf
+ Iterator<Ticket<?>> iterator = null;
+ currChunk[0] = entry.getLongKey();
+ boolean flag = entry.getValue().removeIf(removeIf);
- while (iterator.hasNext()) {
+ while (false && iterator.hasNext()) {
+ // Paper end - use optimised removeIf
Ticket<?> ticket = (Ticket) iterator.next();
if (ticket.timedOut(this.ticketTickCounter)) {
diff --git a/src/main/java/net/minecraft/util/SortedArraySet.java b/src/main/java/net/minecraft/util/SortedArraySet.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/util/SortedArraySet.java

View file

@ -5,10 +5,94 @@ Subject: [PATCH] Optimise chunk tick iteration
Use a dedicated list of entity ticking chunks to reduce the cost
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
@@ -0,0 +0,0 @@ public class ChunkHolder {
long key = net.minecraft.server.MCUtil.getCoordinateKey(this.pos);
this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key);
this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key);
+ // Paper start - optimise chunk tick iteration
+ if (this.needsBroadcastChanges()) {
+ this.chunkMap.needsChangeBroadcasting.add(this);
+ }
+ // Paper end - optimise chunk tick iteration
}
void onChunkRemove() {
this.playersInMobSpawnRange = null;
this.playersInChunkTickRange = null;
+ // Paper start - optimise chunk tick iteration
+ if (this.needsBroadcastChanges()) {
+ this.chunkMap.needsChangeBroadcasting.remove(this);
+ }
+ // Paper end - optimise chunk tick iteration
}
// Paper end - optimise anyPlayerCloseEnoughForSpawning
long lastAutoSaveTime; // Paper - incremental autosave
@@ -0,0 +0,0 @@ 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();
}
@@ -0,0 +0,0 @@ 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) {
@@ -0,0 +0,0 @@ public class ChunkHolder {
}
}
+ // Paper start - optimise chunk tick iteration
+ public final boolean needsBroadcastChanges() {
+ return this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty();
+ }
+
+ private void addToBroadcastMap() {
+ org.spigotmc.AsyncCatcher.catchOp("ChunkHolder update");
+ 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 - moved into above, other logic needs to call
Level world = chunk.getLevel();
int i = 0;
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
private final Queue<Runnable> unloadQueue;
int viewDistance;
public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobDistanceMap; // Paper
+ public final ReferenceOpenHashSet<ChunkHolder> needsChangeBroadcasting = new ReferenceOpenHashSet<>();
// CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback()
public final CallbackExecutor callbackExecutor = new CallbackExecutor();
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -0,0 +0,0 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; // Paper
import java.util.function.Function; // Paper
+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; // Paper
public class ServerChunkCache extends ChunkSource {
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
this.lastSpawnState = spawnercreature_d;
@ -56,16 +140,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ LevelChunk chunk1 = iterator1.next();
+ ChunkHolder holder = chunk1.playerChunk;
+ if (holder != null) {
+ gameprofilerfiller.popPush("broadcast");
+ this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing
+ holder.broadcastChanges(chunk1);
+ this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing
+ gameprofilerfiller.pop();
+ // Paper - move down
+ // Paper end - optimise chunk tick iteration
ChunkPos chunkcoordintpair = chunk1.getPos();
- if (this.level.isPositionEntityTicking(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning
+ if ((true || this.level.isPositionEntityTicking(chunkcoordintpair)) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning & optimise chunk tick iteration
+ if (this.level.isPositionEntityTicking(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning
chunk1.incrementInhabitedTime(j);
- if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning
+ if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning & optimise chunk tick iteration
@ -90,17 +170,34 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
gameprofilerfiller.popPush("customSpawners");
if (flag2) {
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies);
} // Paper - timings
}
-
- gameprofilerfiller.popPush("broadcast");
- list.forEach((chunkproviderserver_a1) -> {
- this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing
- chunkproviderserver_a1.holder.broadcastChanges(chunkproviderserver_a1.chunk);
- this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing
- });
- gameprofilerfiller.pop();
+ // Paper - no, iterating just ONCE is expensive enough! Don't do it TWICE! Code moved up
gameprofilerfiller.pop();
+ // Paper start - use set of chunks requiring updates, rather than iterating every single one loaded
+ gameprofilerfiller.popPush("broadcast");
+ this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing
+ if (!this.chunkMap.needsChangeBroadcasting.isEmpty()) {
+ ReferenceOpenHashSet<ChunkHolder> copy = this.chunkMap.needsChangeBroadcasting.clone();
+ this.chunkMap.needsChangeBroadcasting.clear();
+ for (ChunkHolder holder : copy) {
+ holder.broadcastChanges(holder.getFullChunkUnchecked()); // LevelChunks are NEVER unloaded
+ if (holder.needsBroadcastChanges()) {
+ // I DON'T want to KNOW what DUMB plugins might be doing.
+ this.chunkMap.needsChangeBroadcasting.add(holder);
+ }
+ }
+ }
+ this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing
gameprofilerfiller.pop();
+ // Paper end - use set of chunks requiring updates, rather than iterating every single one loaded
this.chunkMap.tick();
}
}

View file

@ -13,14 +13,27 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
@@ -0,0 +0,0 @@ public class ChunkHolder {
long key = net.minecraft.server.MCUtil.getCoordinateKey(this.pos);
this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key);
this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key);
this.chunkMap.needsChangeBroadcasting.add(this);
}
// Paper end - optimise chunk tick iteration
+ // Paper start - optimise checkDespawn
+ LevelChunk chunk = this.getFullChunkUnchecked();
+ if (chunk != null) {
+ chunk.updateGeneralAreaCache();
+ }
+ // Paper end - optimise checkDespawn
}
void onChunkRemove() {
@@ -0,0 +0,0 @@ public class ChunkHolder {
this.chunkMap.needsChangeBroadcasting.remove(this);
}
// Paper end - optimise chunk tick iteration
+ // Paper start - optimise checkDespawn
+ LevelChunk chunk = this.getFullChunkUnchecked();
+ if (chunk != null) {
+ chunk.removeGeneralAreaCache();
+ }
+ // Paper end - optimise checkDespawn
}
// Paper end - optimise anyPlayerCloseEnoughForSpawning
@ -30,8 +43,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
int viewDistance;
public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobDistanceMap; // Paper
public final ReferenceOpenHashSet<ChunkHolder> needsChangeBroadcasting = new ReferenceOpenHashSet<>();
+ // Paper start - optimise checkDespawn
+ public static final int GENERAL_AREA_MAP_SQUARE_RADIUS = 40;
@ -331,6 +344,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ this.updateGeneralAreaCache(((ServerLevel)this.level).getChunkSource().chunkMap.playerGeneralAreaMap.getObjectsInRange(this.coordinateKey));
+ }
+
+ public void removeGeneralAreaCache() {
+ this.playerGeneralAreaCacheSet = false;
+ this.playerGeneralAreaCache = null;
+ }
+
+ public void updateGeneralAreaCache(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<net.minecraft.server.level.ServerPlayer> value) {
+ this.playerGeneralAreaCacheSet = true;
+ this.playerGeneralAreaCache = value;

View file

@ -18,11 +18,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInMobSpawnRange;
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInChunkTickRange;
+
+ void updateRanges() {
+ void onChunkAdd() {
+ long key = net.minecraft.server.MCUtil.getCoordinateKey(this.pos);
+ this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key);
+ this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key);
+ }
+
+ void onChunkRemove() {
+ this.playersInMobSpawnRange = null;
+ this.playersInChunkTickRange = null;
+ }
+ // Paper end - optimise anyPlayerCloseEnoughForSpawning
+
public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) {
@ -32,7 +37,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
this.setTicketLevel(level);
this.changedBlocksPerSection = new ShortSet[world.getSectionsCount()];
this.chunkMap = (ChunkMap)playersWatchingChunkProvider; // Paper
+ this.updateRanges(); // Paper - optimise anyPlayerCloseEnoughForSpawning
+ this.onChunkAdd(); // Paper - optimise anyPlayerCloseEnoughForSpawning
}
// CraftBukkit start
@ -123,13 +128,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
protected ChunkGenerator generator() {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
} else {
if (holder != null) {
holder.setTicketLevel(level);
+ holder.updateRanges(); // Paper - optimise anyPlayerCloseEnoughForSpawning
}
holder = (ChunkHolder) this.pendingUnloads.remove(pos);
if (holder != null) {
holder.setTicketLevel(level);
+ holder.onChunkAdd(); // Paper - optimise anyPlayerCloseEnoughForSpawning - PUT HERE AFTER RE-ADDING ONLY
} else {
holder = new ChunkHolder(new ChunkPos(pos), level, this.level, this.lightEngine, this.queueSorter, this);
// Paper start
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
ChunkHolder playerchunk = (ChunkHolder) this.updatingChunkMap.remove(j);
if (holder != null) {
if (playerchunk != null) {
+ playerchunk.onChunkRemove(); // Paper
this.pendingUnloads.put(j, playerchunk);
this.modified = true;
this.scheduleUnload(j, playerchunk); // Paper - Move up - don't leak chunks
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
return this.anyPlayerCloseEnoughForSpawning(pos, false);
}

View file

@ -364,8 +364,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ commands = new HashMap<String, Command>();
+ commands.put("paper", new PaperCommand("paper"));
+
+ version = getInt("config-version", 24);
+ set("config-version", 24);
+ version = getInt("config-version", 25);
+ set("config-version", 25);
+ readConfig(PaperConfig.class, null);
+ }
+

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,249 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sun, 21 Mar 2021 16:25:42 -0700
Subject: [PATCH] Replace ticket level propagator
Mojang's propagator is slow, and this isn't surprising
given it's built on the same utilities the vanilla light engine
is built on. The simple propagator I wrote is approximately 4x
faster when simulating player movement. For a long time timing
reports have shown this function take up significant tick, (
approx 10% or more), and async sampling data shows the level
propagation alone takes up a significant amount. So this
should help with that. A big side effect is that mid-tick
will be more effective, since more time will be allocated
to actually processing chunk tasks vs the ticket level updates.
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
@@ -0,0 +0,0 @@ import net.minecraft.world.level.chunk.LevelChunk;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import it.unimi.dsi.fastutil.longs.Long2IntLinkedOpenHashMap; // Paper
public abstract class DistanceManager {
static final Logger LOGGER = LogManager.getLogger();
@@ -0,0 +0,0 @@ public abstract class DistanceManager {
private static final int BLOCK_TICKING_LEVEL_THRESHOLD = 33;
final Long2ObjectMap<ObjectSet<ServerPlayer>> playersPerChunk = new Long2ObjectOpenHashMap();
public final Long2ObjectOpenHashMap<SortedArraySet<Ticket<?>>> tickets = new Long2ObjectOpenHashMap();
- private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker();
+ //private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); // Paper - replace ticket level propagator
public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used
//private final TickingTracker tickingTicketsTracker = new TickingTracker(); // Paper - no longer used
//private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33); // Paper - no longer used
@@ -0,0 +0,0 @@ public abstract class DistanceManager {
this.chunkMap = chunkMap; // Paper
}
+ // Paper start - replace ticket level propagator
+ protected final Long2IntLinkedOpenHashMap ticketLevelUpdates = new Long2IntLinkedOpenHashMap() {
+ @Override
+ protected void rehash(int newN) {
+ // no downsizing allowed
+ if (newN < this.n) {
+ return;
+ }
+ super.rehash(newN);
+ }
+ };
+ protected final io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D ticketLevelPropagator = new io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D(
+ (long coordinate, byte oldLevel, byte newLevel) -> {
+ DistanceManager.this.ticketLevelUpdates.putAndMoveToLast(coordinate, convertBetweenTicketLevels(newLevel));
+ }
+ );
+ // function for converting between ticket levels and propagator levels and vice versa
+ // the problem is the ticket level propagator will propagate from a set source down to zero, whereas mojang expects
+ // levels to propagate from a set value up to a maximum value. so we need to convert the levels we put into the propagator
+ // and the levels we get out of the propagator
+
+ // this maps so that GOLDEN_TICKET + 1 will be 0 in the propagator, GOLDEN_TICKET will be 1, and so on
+ // we need GOLDEN_TICKET+1 as 0 because anything >= GOLDEN_TICKET+1 should be unloaded
+ public static int convertBetweenTicketLevels(final int level) {
+ return ChunkMap.MAX_CHUNK_DISTANCE - level + 1;
+ }
+
+ protected final int getPropagatedTicketLevel(final long coordinate) {
+ return convertBetweenTicketLevels(this.ticketLevelPropagator.getLevel(coordinate));
+ }
+
+ protected final void updateTicketLevel(final long coordinate, final int ticketLevel) {
+ if (ticketLevel > ChunkMap.MAX_CHUNK_DISTANCE) {
+ this.ticketLevelPropagator.removeSource(coordinate);
+ } else {
+ this.ticketLevelPropagator.setSource(coordinate, convertBetweenTicketLevels(ticketLevel));
+ }
+ }
+ // Paper end - replace ticket level propagator
+
protected void purgeStaleTickets() {
++this.ticketTickCounter;
ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator();
@@ -0,0 +0,0 @@ public abstract class DistanceManager {
}
if (flag) {
- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue()), false);
+ this.updateTicketLevel(entry.getLongKey(), getTicketLevelAt(entry.getValue())); // Paper - replace ticket level propagator
}
if (((SortedArraySet) entry.getValue()).isEmpty()) {
@@ -0,0 +0,0 @@ public abstract class DistanceManager {
@Nullable
protected abstract ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k);
+ protected long ticketLevelUpdateCount; // Paper - replace ticket level propagator
public boolean runAllUpdates(ChunkMap chunkStorage) {
//this.f.a(); // Paper - no longer used
//this.tickingTicketsTracker.runAllUpdates(); // Paper - no longer used
org.spigotmc.AsyncCatcher.catchOp("DistanceManagerTick"); // Paper
//this.playerTicketManager.runAllUpdates(); // Paper - no longer used
- int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE);
- boolean flag = i != 0;
+ boolean flag = this.ticketLevelPropagator.propagateUpdates(); // Paper - replace ticket level propagator
if (flag) {
;
}
- // Paper start
- if (!this.pendingChunkUpdates.isEmpty()) {
- this.pollingPendingChunkUpdates = true; try { // Paper - Chunk priority
- while(!this.pendingChunkUpdates.isEmpty()) {
- ChunkHolder remove = this.pendingChunkUpdates.remove();
- remove.isUpdateQueued = false;
- remove.updateFutures(chunkStorage, this.mainThreadExecutor);
- }
- } finally { this.pollingPendingChunkUpdates = false; } // Paper - Chunk priority
- // Paper end
- return true;
- } else {
- if (!this.ticketsToRelease.isEmpty()) {
- LongIterator longiterator = this.ticketsToRelease.iterator();
+ // Paper start - replace level propagator
+ ticket_update_loop:
+ while (!this.ticketLevelUpdates.isEmpty()) {
+ flag = true;
- while (longiterator.hasNext()) {
- long j = longiterator.nextLong();
+ boolean oldPolling = this.pollingPendingChunkUpdates;
+ this.pollingPendingChunkUpdates = true;
+ try {
+ for (java.util.Iterator<Long2IntMap.Entry> iterator = this.ticketLevelUpdates.long2IntEntrySet().fastIterator(); iterator.hasNext();) {
+ Long2IntMap.Entry entry = iterator.next();
+ long key = entry.getLongKey();
+ int newLevel = entry.getIntValue();
+ ChunkHolder chunk = this.getChunk(key);
+
+ if (chunk == null && newLevel > ChunkMap.MAX_CHUNK_DISTANCE) {
+ // not loaded and it shouldn't be loaded!
+ continue;
+ }
- if (this.getTickets(j).stream().anyMatch((ticket) -> {
- return ticket.getType() == TicketType.PLAYER;
- })) {
- ChunkHolder playerchunk = chunkStorage.getUpdatingChunkIfPresent(j);
+ int currentLevel = chunk == null ? ChunkMap.MAX_CHUNK_DISTANCE + 1 : chunk.getTicketLevel();
- if (playerchunk == null) {
- throw new IllegalStateException();
+ if (currentLevel == newLevel) {
+ // nothing to do
+ continue;
+ }
+
+ this.updateChunkScheduling(key, newLevel, chunk, currentLevel);
+ }
+
+ long recursiveCheck = ++this.ticketLevelUpdateCount;
+ while (!this.ticketLevelUpdates.isEmpty()) {
+ long key = this.ticketLevelUpdates.firstLongKey();
+ int newLevel = this.ticketLevelUpdates.removeFirstInt();
+ ChunkHolder chunk = this.getChunk(key);
+
+ if (chunk == null) {
+ if (newLevel <= ChunkMap.MAX_CHUNK_DISTANCE) {
+ throw new IllegalStateException("Expected chunk holder to be created");
}
+ // not loaded and it shouldn't be loaded!
+ continue;
+ }
- CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> completablefuture = playerchunk.getEntityTickingChunkFuture();
+ int currentLevel = chunk.oldTicketLevel;
- completablefuture.thenAccept((either) -> {
- this.mainThreadExecutor.execute(() -> {
- this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> {
- }, j, false));
- });
- });
+ if (currentLevel == newLevel) {
+ // nothing to do
+ continue;
+ }
+
+ chunk.updateFutures(chunkStorage, this.mainThreadExecutor);
+ if (recursiveCheck != this.ticketLevelUpdateCount) {
+ // back to the start, we must create player chunks and update the ticket level fields before
+ // processing the actual level updates
+ continue ticket_update_loop;
}
}
- this.ticketsToRelease.clear();
- }
+ for (;;) {
+ if (recursiveCheck != this.ticketLevelUpdateCount) {
+ continue ticket_update_loop;
+ }
+ ChunkHolder pendingUpdate = this.pendingChunkUpdates.poll();
+ if (pendingUpdate == null) {
+ break;
+ }
- return flag;
+ pendingUpdate.updateFutures(chunkStorage, this.mainThreadExecutor);
+ }
+ } finally {
+ this.pollingPendingChunkUpdates = oldPolling;
+ }
}
+
+ return flag;
+ // Paper end - replace level propagator
}
boolean pollingPendingChunkUpdates = false; // Paper - Chunk priority
@@ -0,0 +0,0 @@ public abstract class DistanceManager {
ticket1.setCreatedTick(this.ticketTickCounter);
if (ticket.getTicketLevel() < j) {
- this.ticketTracker.update(i, ticket.getTicketLevel(), true);
+ this.updateTicketLevel(i, ticket.getTicketLevel()); // Paper - replace ticket level propagator
}
return ticket == ticket1; // CraftBukkit
@@ -0,0 +0,0 @@ public abstract class DistanceManager {
// Paper start - Chunk priority
int newLevel = getTicketLevelAt(arraysetsorted);
if (newLevel > oldLevel) {
- this.ticketTracker.update(i, newLevel, false);
+ this.updateTicketLevel(i, newLevel); // Paper // Paper - replace ticket level propagator
}
// Paper end
return removed; // CraftBukkit
@@ -0,0 +0,0 @@ public abstract class DistanceManager {
SortedArraySet<Ticket<?>> tickets = entry.getValue();
if (tickets.remove(target)) {
// copied from removeTicket
- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt(tickets), false);
+ this.updateTicketLevel(entry.getLongKey(), getTicketLevelAt(tickets)); // Paper - replace ticket level propagator
// can't use entry after it's removed
if (tickets.isEmpty()) {

View file

@ -102,7 +102,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
@@ -0,0 +0,0 @@ public class ChunkHolder {
this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key);
this.playersInChunkTickRange = null;
}
// Paper end - optimise anyPlayerCloseEnoughForSpawning
+ long lastAutoSaveTime; // Paper - incremental autosave