diff options
Diffstat (limited to 'patches/server/1004-Optimise-random-block-ticking.patch')
-rw-r--r-- | patches/server/1004-Optimise-random-block-ticking.patch | 456 |
1 files changed, 456 insertions, 0 deletions
diff --git a/patches/server/1004-Optimise-random-block-ticking.patch b/patches/server/1004-Optimise-random-block-ticking.patch new file mode 100644 index 0000000000..36af1b75bf --- /dev/null +++ b/patches/server/1004-Optimise-random-block-ticking.patch @@ -0,0 +1,456 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf <[email protected]> +Date: Sun, 20 Jun 2021 16:19:26 -0700 +Subject: [PATCH] Optimise random block ticking + +Massive performance improvement for random block ticking. +The performance increase comes from the fact that the vast +majority of attempted block ticks (~95% in my testing) fail +because the randomly selected block is not tickable. + +Now only tickable blocks are targeted, however this means that +the maximum number of block ticks occurs per chunk. However, +not all chunks are going to be targeted. The percent chance +of a chunk being targeted is based on how many tickable blocks +are in the chunk. +This means that while block ticks are spread out less, the +total number of blocks ticked per world tick remains the same. +Therefore, the chance of a random tickable block being ticked +remains the same. + +diff --git a/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java b/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7d93652c1abbb6aee6eb7c26cf35d4d032ef7b69 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java +@@ -0,0 +1,65 @@ ++package io.papermc.paper.util.math; ++ ++import net.minecraft.util.RandomSource; ++import net.minecraft.world.level.levelgen.LegacyRandomSource; ++import net.minecraft.world.level.levelgen.PositionalRandomFactory; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public final class ThreadUnsafeRandom extends LegacyRandomSource { ++ ++ // See javadoc and internal comments for java.util.Random where these values come from, how they are used, and the author for them. ++ private static final long multiplier = 0x5DEECE66DL; ++ private static final long addend = 0xBL; ++ private static final long mask = (1L << 48) - 1; ++ ++ private static long initialScramble(long seed) { ++ return (seed ^ multiplier) & mask; ++ } ++ ++ private long seed; ++ ++ public ThreadUnsafeRandom(long seed) { ++ super(seed); ++ } ++ ++ @Override ++ public RandomSource fork() { ++ return new ThreadUnsafeRandom(this.nextLong()); ++ } ++ ++ @Override ++ public PositionalRandomFactory forkPositional() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void setSeed(long seed) { ++ // note: called by Random constructor ++ this.seed = initialScramble(seed); ++ } ++ ++ @Override ++ public int next(int bits) { ++ // avoid the expensive CAS logic used by superclass ++ return (int) (((this.seed = this.seed * multiplier + addend) & mask) >>> (48 - bits)); ++ } ++ ++ // Taken from ++ // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ ++ // https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/blob/master/2016/06/25/fastrange.c ++ // Original license is public domain ++ public static int fastRandomBounded(final long randomInteger, final long limit) { ++ // randomInteger must be [0, pow(2, 32)) ++ // limit must be [0, pow(2, 32)) ++ return (int)((randomInteger * limit) >>> 32); ++ } ++ ++ @Override ++ public int nextInt(int bound) { ++ // yes this breaks random's spec ++ // however there's nothing that uses this class that relies on it ++ return fastRandomBounded(this.next(32) & 0xFFFFFFFFL, bound); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 3a49f8933bc6ca1862994b7e0b3006f5236dd94a..bdad61438e7fd89f5f0cac6632dd395fcf024361 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -861,6 +861,10 @@ public class ServerLevel extends Level implements WorldGenLevel { + entityplayer.stopSleepInBed(false, false); + }); + } ++ // Paper start - optimise random block ticking ++ private final BlockPos.MutableBlockPos chunkTickMutablePosition = new BlockPos.MutableBlockPos(); ++ private final io.papermc.paper.util.math.ThreadUnsafeRandom randomTickRandom = new io.papermc.paper.util.math.ThreadUnsafeRandom(this.random.nextLong()); ++ // Paper end + + public void tickChunk(LevelChunk chunk, int randomTickSpeed) { + ChunkPos chunkcoordintpair = chunk.getPos(); +@@ -870,8 +874,10 @@ public class ServerLevel extends Level implements WorldGenLevel { + ProfilerFiller gameprofilerfiller = this.getProfiler(); + + gameprofilerfiller.push("thunder"); ++ final BlockPos.MutableBlockPos blockposition = this.chunkTickMutablePosition; // Paper - use mutable to reduce allocation rate, final to force compile fail on change ++ + if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder +- BlockPos blockposition = this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15)); ++ blockposition.set(this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15))); // Paper + + if (this.isRainingAt(blockposition)) { + DifficultyInstance difficultydamagescaler = this.getCurrentDifficultyAt(blockposition); +@@ -903,7 +909,10 @@ public class ServerLevel extends Level implements WorldGenLevel { + if (!this.paperConfig().environment.disableIceAndSnow) { // Paper - Option to disable ice and snow + for (int l = 0; l < randomTickSpeed; ++l) { + if (this.random.nextInt(48) == 0) { +- this.tickPrecipitation(this.getBlockRandomPos(j, 0, k, 15)); ++ // Paper start ++ this.getRandomBlockPosition(j, 0, k, 15, blockposition); ++ this.tickPrecipitation(blockposition, chunk); ++ // Paper end + } + } + } // Paper - Option to disable ice and snow +@@ -911,36 +920,37 @@ public class ServerLevel extends Level implements WorldGenLevel { + gameprofilerfiller.popPush("tickBlocks"); + timings.chunkTicksBlocks.startTiming(); // Paper + if (randomTickSpeed > 0) { +- LevelChunkSection[] achunksection = chunk.getSections(); +- +- for (int i1 = 0; i1 < achunksection.length; ++i1) { +- LevelChunkSection chunksection = achunksection[i1]; +- +- if (chunksection.isRandomlyTicking()) { +- int j1 = chunk.getSectionYFromSectionIndex(i1); +- int k1 = SectionPos.sectionToBlockCoord(j1); +- +- for (int l1 = 0; l1 < randomTickSpeed; ++l1) { +- BlockPos blockposition1 = this.getBlockRandomPos(j, k1, k, 15); +- +- gameprofilerfiller.push("randomTick"); +- BlockState iblockdata = chunksection.getBlockState(blockposition1.getX() - j, blockposition1.getY() - k1, blockposition1.getZ() - k); +- +- if (iblockdata.isRandomlyTicking()) { +- iblockdata.randomTick(this, blockposition1, this.random); +- } ++ // Paper start - optimize random block ticking ++ LevelChunkSection[] sections = chunk.getSections(); ++ final int minSection = io.papermc.paper.util.WorldUtil.getMinSection(this); ++ for (int sectionIndex = 0; sectionIndex < sections.length; sectionIndex++) { ++ LevelChunkSection section = sections[sectionIndex]; ++ if (section == null || section.tickingList.size() == 0) continue; ++ ++ int yPos = (sectionIndex + minSection) << 4; ++ for (int a = 0; a < randomTickSpeed; ++a) { ++ int tickingBlocks = section.tickingList.size(); ++ int index = this.randomTickRandom.nextInt(16 * 16 * 16); ++ if (index >= tickingBlocks) { ++ continue; ++ } + +- FluidState fluid = iblockdata.getFluidState(); ++ long raw = section.tickingList.getRaw(index); ++ int location = com.destroystokyo.paper.util.maplist.IBlockDataList.getLocationFromRaw(raw); ++ int randomX = location & 15; ++ int randomY = ((location >>> (4 + 4)) & 255) | yPos; ++ int randomZ = (location >>> 4) & 15; + +- if (fluid.isRandomlyTicking()) { +- fluid.randomTick(this, blockposition1, this.random); +- } ++ BlockPos blockposition2 = blockposition.set(j + randomX, randomY, k + randomZ); ++ BlockState iblockdata = com.destroystokyo.paper.util.maplist.IBlockDataList.getBlockDataFromRaw(raw); + +- gameprofilerfiller.pop(); +- } ++ iblockdata.randomTick(this, blockposition2, this.randomTickRandom); + } ++ // We drop the fluid tick since LAVA is ALREADY TICKED by the above method (See LiquidBlock). ++ // TODO CHECK ON UPDATE (ping the Canadian) + } + } ++ // Paper end - optimise random block ticking + + timings.chunkTicksBlocks.stopTiming(); // Paper + gameprofilerfiller.pop(); +@@ -948,17 +958,25 @@ public class ServerLevel extends Level implements WorldGenLevel { + + @VisibleForTesting + public void tickPrecipitation(BlockPos pos) { +- BlockPos blockposition1 = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos); +- BlockPos blockposition2 = blockposition1.below(); ++ // Paper start - optimise chunk ticking ++ tickPrecipitation(pos.mutable(), this.getChunkAt(pos)); ++ } ++ public void tickPrecipitation(BlockPos.MutableBlockPos blockposition1, final LevelChunk chunk) { ++ int normalY = chunk.getHeight(Heightmap.Types.MOTION_BLOCKING, blockposition1.getX() & 15, blockposition1.getZ() & 15) + 1; ++ int downY = normalY - 1; ++ blockposition1.setY(normalY); ++ // Paper end - optimise chunk ticking + Biome biomebase = (Biome) this.getBiome(blockposition1).value(); + +- if (biomebase.shouldFreeze(this, blockposition2)) { +- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition2, Blocks.ICE.defaultBlockState(), null); // CraftBukkit ++ blockposition1.setY(downY); ++ if (biomebase.shouldFreeze(this, blockposition1)) { ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, Blocks.ICE.defaultBlockState(), null); // CraftBukkit + } + + if (this.isRaining()) { + int i = this.getGameRules().getInt(GameRules.RULE_SNOW_ACCUMULATION_HEIGHT); + ++ blockposition1.setY(normalY); // Paper - optimise chunk ticking + if (i > 0 && biomebase.shouldSnow(this, blockposition1)) { + BlockState iblockdata = this.getBlockState(blockposition1); + +@@ -976,12 +994,13 @@ public class ServerLevel extends Level implements WorldGenLevel { + } + } + +- Biome.Precipitation biomebase_precipitation = biomebase.getPrecipitationAt(blockposition2); ++ blockposition1.setY(downY); // Paper - optimise chunk ticking ++ Biome.Precipitation biomebase_precipitation = biomebase.getPrecipitationAt(blockposition1); // Paper - optimise chunk ticking + + if (biomebase_precipitation != Biome.Precipitation.NONE) { +- BlockState iblockdata2 = this.getBlockState(blockposition2); ++ BlockState iblockdata2 = this.getBlockState(blockposition1); // Paper - optimise chunk ticking + +- iblockdata2.getBlock().handlePrecipitation(iblockdata2, this, blockposition2, biomebase_precipitation); ++ iblockdata2.getBlock().handlePrecipitation(iblockdata2, this, blockposition1, biomebase_precipitation); // Paper - optimise chunk ticking + } + } + +diff --git a/src/main/java/net/minecraft/util/BitStorage.java b/src/main/java/net/minecraft/util/BitStorage.java +index 68648c5a5e3ff079f832092af0f2f801c42d1ede..8bafd5fd7499ba4a04bf706cfd1e156073716e21 100644 +--- a/src/main/java/net/minecraft/util/BitStorage.java ++++ b/src/main/java/net/minecraft/util/BitStorage.java +@@ -20,4 +20,15 @@ public interface BitStorage { + void unpack(int[] out); + + BitStorage copy(); ++ ++ // Paper start ++ void forEach(DataBitConsumer consumer); ++ ++ @FunctionalInterface ++ interface DataBitConsumer { ++ ++ void accept(int location, int data); ++ ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/util/SimpleBitStorage.java b/src/main/java/net/minecraft/util/SimpleBitStorage.java +index 9f438d9c6eb05e43d24e4af68188a3d4c46a938c..8d7d763bf51cac556057645e6169c9447993189b 100644 +--- a/src/main/java/net/minecraft/util/SimpleBitStorage.java ++++ b/src/main/java/net/minecraft/util/SimpleBitStorage.java +@@ -315,6 +315,28 @@ public class SimpleBitStorage implements BitStorage { + return this.bits; + } + ++ // Paper start ++ @Override ++ public final void forEach(DataBitConsumer consumer) { ++ int i = 0; ++ long[] along = this.data; ++ int j = along.length; ++ ++ for (int k = 0; k < j; ++k) { ++ long l = along[k]; ++ ++ for (int i1 = 0; i1 < this.valuesPerLong; ++i1) { ++ consumer.accept(i, (int) (l & this.mask)); ++ l >>= this.bits; ++ ++i; ++ if (i >= this.size) { ++ return; ++ } ++ } ++ } ++ } ++ // Paper end ++ + @Override + public void getAll(IntConsumer action) { + int i = 0; +diff --git a/src/main/java/net/minecraft/util/ZeroBitStorage.java b/src/main/java/net/minecraft/util/ZeroBitStorage.java +index 50040c497a819cd1229042ab3cb057d34a32cacc..01f5b946fabbe34f31110e75973dab9f39897346 100644 +--- a/src/main/java/net/minecraft/util/ZeroBitStorage.java ++++ b/src/main/java/net/minecraft/util/ZeroBitStorage.java +@@ -46,6 +46,15 @@ public class ZeroBitStorage implements BitStorage { + return 0; + } + ++ // Paper start ++ @Override ++ public void forEach(DataBitConsumer consumer) { ++ for(int i = 0; i < this.size; ++i) { ++ consumer.accept(i, 0); ++ } ++ } ++ // Paper end ++ + @Override + public void getAll(IntConsumer action) { + for (int i = 0; i < this.size; i++) { +diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java +index ffffa53fcaa6ec8681b283fa20bb5a3320ad9c11..30b87b5cb18c25cdd04eab64cfbe5acd6c1b6d84 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java +@@ -88,7 +88,7 @@ public class Turtle extends Animal { + } + + public void setHomePos(BlockPos pos) { +- this.entityData.set(Turtle.HOME_POS, pos); ++ this.entityData.set(Turtle.HOME_POS, pos.immutable()); // Paper - called with mutablepos... + } + + public BlockPos getHomePos() { +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index dab55ab08665f2b5ae0c899a4ab07c18460552ae..4001bec504b8fcda1eec03f49abd4e4d82cf6336 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -1387,10 +1387,18 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public abstract RecipeManager getRecipeManager(); + + public BlockPos getBlockRandomPos(int x, int y, int z, int l) { ++ // Paper start - allow use of mutable pos ++ BlockPos.MutableBlockPos ret = new BlockPos.MutableBlockPos(); ++ this.getRandomBlockPosition(x, y, z, l, ret); ++ return ret.immutable(); ++ } ++ public final BlockPos.MutableBlockPos getRandomBlockPosition(int x, int y, int z, int l, BlockPos.MutableBlockPos out) { ++ // Paper end + this.randValue = this.randValue * 3 + 1013904223; + int i1 = this.randValue >> 2; + +- return new BlockPos(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15)); ++ out.set(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15)); // Paper - change to setValues call ++ return out; // Paper + } + + public boolean noSave() { +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 f2e11bff414b521295bde721e01ae2166a6a3fd6..8cd6c1d838e0332125fde3fc36475034aa4effa0 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -25,6 +25,7 @@ public class LevelChunkSection { + public final PalettedContainer<BlockState> states; + // CraftBukkit start - read/write + private PalettedContainer<Holder<Biome>> biomes; ++ public final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper + + public LevelChunkSection(PalettedContainer<BlockState> datapaletteblock, PalettedContainer<Holder<Biome>> palettedcontainerro) { + // CraftBukkit end +@@ -77,6 +78,9 @@ public class LevelChunkSection { + --this.nonEmptyBlockCount; + if (iblockdata1.isRandomlyTicking()) { + --this.tickingBlockCount; ++ // Paper start ++ this.tickingList.remove(x, y, z); ++ // Paper end + } + } + +@@ -88,6 +92,9 @@ public class LevelChunkSection { + ++this.nonEmptyBlockCount; + if (state.isRandomlyTicking()) { + ++this.tickingBlockCount; ++ // Paper start ++ this.tickingList.add(x, y, z, state); ++ // Paper end + } + } + +@@ -115,40 +122,34 @@ public class LevelChunkSection { + } + + public void recalcBlockCounts() { +- class a implements PalettedContainer.CountConsumer<BlockState> { +- +- public int nonEmptyBlockCount; +- public int tickingBlockCount; +- public int tickingFluidCount; +- +- a(final LevelChunkSection chunksection) {} +- +- public void accept(BlockState iblockdata, int i) { ++ // Paper start - unfuck this ++ this.tickingList.clear(); ++ this.nonEmptyBlockCount = 0; ++ this.tickingBlockCount = 0; ++ this.tickingFluidCount = 0; ++ // Don't run this on clearly empty sections ++ if (this.maybeHas((BlockState state) -> !state.isAir() || !state.getFluidState().isEmpty())) { ++ this.states.forEachLocation((BlockState iblockdata, int i) -> { + FluidState fluid = iblockdata.getFluidState(); + + if (!iblockdata.isAir()) { +- this.nonEmptyBlockCount += i; ++ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1); + if (iblockdata.isRandomlyTicking()) { +- this.tickingBlockCount += i; ++ this.tickingBlockCount = (short)(this.tickingBlockCount + 1); ++ this.tickingList.add(i, iblockdata); + } + } + + if (!fluid.isEmpty()) { +- this.nonEmptyBlockCount += i; ++ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1); + if (fluid.isRandomlyTicking()) { +- this.tickingFluidCount += i; ++ this.tickingFluidCount = (short) (this.tickingFluidCount + 1); + } + } + +- } ++ }); + } +- +- a a0 = new a(this); +- +- this.states.count(a0); +- this.nonEmptyBlockCount = (short) a0.nonEmptyBlockCount; +- this.tickingBlockCount = (short) a0.tickingBlockCount; +- this.tickingFluidCount = (short) a0.tickingFluidCount; ++ // Paper end - unfuck this + } + + public PalettedContainer<BlockState> getStates() { +diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +index 926c81a25180d508d662eb3fa35f771636164694..81368bf186365878db2e1ed305bb7bf36c26f61f 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +@@ -381,6 +381,14 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer + } + } + ++ // Paper start ++ public void forEachLocation(PalettedContainer.CountConsumer<T> consumer) { ++ this.data.storage.forEach((int location, int data) -> { ++ consumer.accept(this.data.palette.valueFor(data), location); ++ }); ++ } ++ // Paper end ++ + @FunctionalInterface + public interface CountConsumer<T> { + void accept(T object, int count); |