mirror of
https://github.com/PaperMC/Paper.git
synced 2024-12-27 15:00:13 +01:00
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:
parent
9425b30b18
commit
722983fbc7
14 changed files with 3258 additions and 97 deletions
|
@ -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);
|
||||
+
|
||||
+ /**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
+ }
|
||||
+
|
||||
|
|
2242
patches/server/Replace-player-chunk-loader-system.patch
Normal file
2242
patches/server/Replace-player-chunk-loader-system.patch
Normal file
File diff suppressed because it is too large
Load diff
249
patches/server/Replace-ticket-level-propagator.patch
Normal file
249
patches/server/Replace-ticket-level-propagator.patch
Normal 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()) {
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue