papermc/patches/server-remapped/0408-Optimise-random-block-ticking.patch

408 lines
20 KiB
Diff
Raw Normal View History

2021-06-11 12:02:28 +00:00
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Mon, 27 Jan 2020 21:28:00 -0800
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/com/destroystokyo/paper/util/math/ThreadUnsafeRandom.java b/src/main/java/com/destroystokyo/paper/util/math/ThreadUnsafeRandom.java
new file mode 100644
index 0000000000000000000000000000000000000000..3edc8e52e06a62ce9f8cc734fd7458b37cfaad91
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/util/math/ThreadUnsafeRandom.java
@@ -0,0 +1,46 @@
+package com.destroystokyo.paper.util.math;
+
+import java.util.Random;
+
+public final class ThreadUnsafeRandom extends Random {
+
+ // 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;
+
+ @Override
+ public void setSeed(long seed) {
+ // note: called by Random constructor
+ this.seed = initialScramble(seed);
+ }
+
+ @Override
+ protected 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/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java
index b13e5d05d862ea8c6031b8071f525f00bc48f7e7..3db77d9eda98eacb099135643aff5e94751f4c7c 100644
--- a/src/main/java/net/minecraft/core/BlockPos.java
+++ b/src/main/java/net/minecraft/core/BlockPos.java
@@ -468,6 +468,7 @@ public class BlockPos extends Vec3i {
return this.set(Mth.floor(x), Mth.floor(y), Mth.floor(z));
}
+ public final BlockPos.MutableBlockPos setValues(final Vec3i baseblockposition) { return this.set(baseblockposition); } // Paper - OBFHELPER
public BlockPos.MutableBlockPos set(Vec3i pos) {
return this.set(pos.getX(), pos.getY(), pos.getZ());
}
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index bf1bb1530037ebcacc8d5a491789909bddb8b697..5d85895456b5d65954889cadf932027ea23b400b 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -669,7 +669,12 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
});
}
- public void tickChunk(LevelChunk chunk, int randomTickSpeed) {
+ // Paper start - optimise random block ticking
+ private final BlockPos.MutableBlockPos chunkTickMutablePosition = new BlockPos.MutableBlockPos();
+ private final com.destroystokyo.paper.util.math.ThreadUnsafeRandom randomTickRandom = new com.destroystokyo.paper.util.math.ThreadUnsafeRandom();
+ // Paper end
+
+ public void tickChunk(LevelChunk chunk, int randomTickSpeed) { final int randomTickSpeed1 = randomTickSpeed; // Paper
ChunkPos chunkcoordintpair = chunk.getPos();
boolean flag = this.isRaining();
int j = chunkcoordintpair.getMinBlockX();
@@ -677,10 +682,10 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
ProfilerFiller gameprofilerfiller = this.getProfiler();
gameprofilerfiller.push("thunder");
- BlockPos blockposition;
+ final BlockPos.MutableBlockPos blockposition = this.chunkTickMutablePosition; // Paper - use mutable to reduce allocation rate, final to force compile fail on change
if (!this.paperConfig.disableThunder && flag && this.isThundering() && this.random.nextInt(100000) == 0) { // Paper - Disable thunder
- blockposition = this.findLightingTargetAround(this.getBlockRandomPos(j, 0, k, 15));
+ blockposition.setValues(this.findLightingTargetAround(this.getBlockRandomPos(j, 0, k, 15))); // Paper
if (this.isRainingAt(blockposition)) {
DifficultyInstance difficultydamagescaler = this.getCurrentDifficultyAt(blockposition);
boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * paperConfig.skeleHorseSpawnChance; // Paper
@@ -703,59 +708,77 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
}
gameprofilerfiller.popPush("iceandsnow");
- if (!this.paperConfig.disableIceAndSnow && this.random.nextInt(16) == 0) { // Paper - Disable ice and snow
- blockposition = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, this.getBlockRandomPos(j, 0, k, 15));
- BlockPos blockposition1 = blockposition.below();
+ if (!this.paperConfig.disableIceAndSnow && this.randomTickRandom.nextInt(16) == 0) { // Paper - Disable ice and snow // Paper - optimise random ticking
+ // Paper start - optimise chunk ticking
+ this.getRandomBlockPosition(j, 0, k, 15, blockposition);
+ int normalY = chunk.getHighestBlockY(Heightmap.Types.MOTION_BLOCKING, blockposition.getX() & 15, blockposition.getZ() & 15);
+ int downY = normalY - 1;
+ blockposition.setY(normalY);
+ // Paper end
Biome biomebase = this.getBiome(blockposition);
- if (biomebase.shouldFreeze(this, blockposition1)) {
- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, Blocks.ICE.defaultBlockState(), null); // CraftBukkit
+ // Paper start - optimise chunk ticking
+ blockposition.setY(downY);
+ if (biomebase.shouldFreeze(this, blockposition)) {
+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition, Blocks.ICE.defaultBlockState(), null); // CraftBukkit
+ // Paper end
}
+ blockposition.setY(normalY); // Paper
if (flag && biomebase.shouldSnow(this, blockposition)) {
org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition, Blocks.SNOW.defaultBlockState(), null); // CraftBukkit
}
- if (flag && this.getBiome(blockposition1).getPrecipitation() == Biome.Precipitation.RAIN) {
- this.getBlockState(blockposition1).getBlock().handleRain((net.minecraft.world.level.Level) this, blockposition1);
+ // Paper start - optimise chunk ticking
+ blockposition.setY(downY);
+ if (flag && this.getBiome(blockposition).getPrecipitation() == Biome.Precipitation.RAIN) {
+ chunk.getBlockState(blockposition).getBlock().handleRain((net.minecraft.world.level.Level) this, blockposition);
+ // Paper end
}
}
- gameprofilerfiller.popPush("tickBlocks");
- timings.chunkTicksBlocks.startTiming(); // Paper
+ // Paper start - optimise random block ticking
+ gameprofilerfiller.pop();
if (randomTickSpeed > 0) {
- LevelChunkSection[] achunksection = chunk.getSections();
- int l = achunksection.length;
+ gameprofilerfiller.push("randomTick");
+ timings.chunkTicksBlocks.startTiming(); // Paper
- for (int i1 = 0; i1 < l; ++i1) {
- LevelChunkSection chunksection = achunksection[i1];
+ LevelChunkSection[] sections = chunk.getSections();
- if (chunksection != LevelChunk.EMPTY_SECTION && chunksection.isRandomlyTicking()) {
- int j1 = chunksection.bottomBlockY();
+ for (int sectionIndex = 0; sectionIndex < 16; ++sectionIndex) {
+ LevelChunkSection section = sections[sectionIndex];
+ if (section == null || section.tickingList.size() == 0) {
+ continue;
+ }
- for (int k1 = 0; k1 < randomTickSpeed; ++k1) {
- BlockPos blockposition2 = this.getBlockRandomPos(j, j1, k, 15);
+ int yPos = sectionIndex << 4;
- gameprofilerfiller.push("randomTick");
- BlockState iblockdata = chunksection.getBlockState(blockposition2.getX() - j, blockposition2.getY() - j1, blockposition2.getZ() - k);
+ for (int a = 0; a < randomTickSpeed1; ++a) {
+ int tickingBlocks = section.tickingList.size();
+ int index = this.randomTickRandom.nextInt(16 * 16 * 16);
+ if (index >= tickingBlocks) {
+ continue;
+ }
- if (iblockdata.isRandomlyTicking()) {
- iblockdata.randomTick(this, blockposition2, this.random);
- }
+ 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;
- FluidState fluid = iblockdata.getFluidState();
+ BlockPos blockposition2 = blockposition.setValues(j + randomX, randomY, k + randomZ);
+ BlockState iblockdata = com.destroystokyo.paper.util.maplist.IBlockDataList.getBlockDataFromRaw(raw);
- if (fluid.isRandomlyTicking()) {
- fluid.randomTick(this, blockposition2, this.random);
- }
+ iblockdata.randomTick(this, blockposition2, this.randomTickRandom);
- gameprofilerfiller.pop();
- }
+ // We drop the fluid tick since LAVA is ALREADY TICKED by the above method.
+ // TODO CHECK ON UPDATE
}
}
+ gameprofilerfiller.pop();
+ timings.chunkTicksBlocks.stopTiming(); // Paper
+ // Paper end
}
- timings.chunkTicksBlocks.stopTiming(); // Paper
- gameprofilerfiller.pop();
}
protected BlockPos findLightingTargetAround(BlockPos pos) {
diff --git a/src/main/java/net/minecraft/util/BitStorage.java b/src/main/java/net/minecraft/util/BitStorage.java
index dd84984f28484cf7129c294222696784e128221a..9ea72751354e893cd3820befaa5df3e5e503de6e 100644
--- a/src/main/java/net/minecraft/util/BitStorage.java
+++ b/src/main/java/net/minecraft/util/BitStorage.java
@@ -112,4 +112,32 @@ public class BitStorage {
}
}
+
+ // Paper start
+ 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;
+ }
+ }
+ }
+ }
+
+ @FunctionalInterface
+ public static interface DataBitConsumer {
+
+ void accept(int location, int data);
+
+ }
+ // Paper end
}
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 42b636c4ebb6eb83c8a9f3f5f9a766d37d065dc3..0e15ca2fb9cd1aeb4a075b8d50350dd7fd463c72 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java
@@ -91,7 +91,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() { // Paper - public
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index 1d536d77518a70bdc1a23924aea99df1042b3cd5..632f32405053fbcff2fd26fa99f98c6add9f9dc7 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -1472,10 +1472,18 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
public abstract TagContainer getTagManager();
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 i, int j, int k, 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.setValues(i + (i1 & 15), j + (i1 >> 16 & l), k + (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/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
index 4fef3abe4b416cbebe1b456468b5c3e162de18f1..87d7a87a2925f2c062658e960bb5128738828d9f 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
@@ -639,8 +639,8 @@ public class LevelChunk implements ChunkAccess {
this.entities.remove(entity); // Paper
}
- @Override
- public int getHeight(Heightmap.Types type, int x, int z) {
+ public final int getHighestBlockY(Heightmap.Types heightmap_type, int i, int j) { return this.getHeight(heightmap_type, i, j) + 1; } // Paper - sort of an obfhelper, but without -1
+ @Override public int getHeight(Heightmap.Types type, int x, int z) { // Paper
return ((Heightmap) this.heightmaps.get(type)).getFirstAvailable(x & 15, z & 15) - 1;
}
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 38c7c5f18fc84d4a1de2da1ddc6d3ac37c25f341..c44d32f966c61497b4a8892eb51da3a71ad031d9 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
@@ -14,12 +14,14 @@ import net.minecraft.world.level.material.FluidState;
public class LevelChunkSection {
public static final Palette<BlockState> GLOBAL_BLOCKSTATE_PALETTE = new GlobalPalette<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState());
- private final int bottomBlockY;
+ final int bottomBlockY; // Paper - private -> package-private
short nonEmptyBlockCount; // Paper - package-private
- private short tickingBlockCount;
+ short tickingBlockCount; // Paper - private -> package-private
private short tickingFluidCount;
final PalettedContainer<BlockState> states; // Paper - package-private
+ public final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper
+
// Paper start - Anti-Xray - Add parameters
@Deprecated public LevelChunkSection(int yOffset) { this(yOffset, null, null, true); } // Notice for updates: Please make sure this constructor isn't used anywhere
public LevelChunkSection(int i, ChunkAccess chunk, Level world, boolean initializeBlocks) {
@@ -74,6 +76,9 @@ public class LevelChunkSection {
--this.nonEmptyBlockCount;
if (iblockdata1.isRandomlyTicking()) {
--this.tickingBlockCount;
+ // Paper start
+ this.tickingList.remove(x, y, z);
+ // Paper end
}
}
@@ -85,6 +90,9 @@ public class LevelChunkSection {
++this.nonEmptyBlockCount;
if (state.isRandomlyTicking()) {
++this.tickingBlockCount;
+ // Paper start
+ this.tickingList.add(x, y, z, state);
+ // Paper end
}
}
@@ -120,23 +128,29 @@ public class LevelChunkSection {
}
public void recalcBlockCounts() {
+ // Paper start
+ this.tickingList.clear();
+ // Paper end
this.nonEmptyBlockCount = 0;
this.tickingBlockCount = 0;
this.tickingFluidCount = 0;
- this.states.count((iblockdata, i) -> {
+ this.states.forEachLocation((iblockdata, location) -> { // Paper
FluidState fluid = iblockdata.getFluidState();
if (!iblockdata.isAir()) {
- this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + i);
+ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1);
if (iblockdata.isRandomlyTicking()) {
- this.tickingBlockCount = (short) (this.tickingBlockCount + i);
+ this.tickingBlockCount = (short) (this.tickingBlockCount + 1);
+ // Paper start
+ this.tickingList.add(location, iblockdata);
+ // Paper end
}
}
if (!fluid.isEmpty()) {
- this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + i);
+ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1);
if (fluid.isRandomlyTicking()) {
- this.tickingFluidCount = (short) (this.tickingFluidCount + i);
+ this.tickingFluidCount = (short) (this.tickingFluidCount + 1);
}
}
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 dd252372e1e380674b1191e9ea265cbb10de437b..f93316b3ae5cd5fb960fa24f8c921b5b9276d9f3 100644
--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
@@ -285,6 +285,14 @@ public class PalettedContainer<T> implements PaletteResize<T> {
});
}
+ // Paper start
+ public void forEachLocation(PalettedContainer.CountConsumer<T> datapaletteblock_a) {
+ this.getDataBits().forEach((int location, int data) -> {
+ datapaletteblock_a.accept(this.getDataPalette().getObject(data), location);
+ });
+ }
+ // Paper end
+
@FunctionalInterface
public interface CountConsumer<T> {