mirror of
https://github.com/PaperMC/Paper.git
synced 2024-12-30 16:19:03 +01:00
bc0dd0df3d
Upstream has released updates that appear to apply and compile correctly. This update has not been tested by PaperMC and as with ANY update, please do your own testing Bukkit Changes: 716b4fce Revert SnakeYAML upgrade ca6f8942 Update to Minecraft 1.18-rc3 57e7e952 #683: Add Player#showDemoScreen CraftBukkit Changes: c98abfb0 Update to Minecraft 1.18-rc3 9b258501 #960: Add Player#showDemoScreen d9542247 Produce remapped jars after bootstrap jar 99f3ddde SPIGOT-6808: Fix RegionAccessor#getBiome Spigot Changes: b7a4222e Update to Minecraft 1.18-rc3
408 lines
20 KiB
Diff
408 lines
20 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
|
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..e8b4053babe46999980b92643125405064af1c04
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java
|
|
@@ -0,0 +1,46 @@
|
|
+package io.papermc.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/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
index f31d84f0eb65c2754b8622997f404b8cd5ee845d..94ba82b4c7d4a308eec0475d3fe790f0498dbe29 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
@@ -639,6 +639,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();
|
|
+ // Paper end
|
|
|
|
public void tickChunk(LevelChunk chunk, int randomTickSpeed) {
|
|
ChunkPos chunkcoordintpair = chunk.getPos();
|
|
@@ -648,10 +652,10 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
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.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - disable thunder
|
|
- 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);
|
|
boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * paperConfig.skeleHorseSpawnChance && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD); // Paper
|
|
@@ -674,65 +678,78 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
}
|
|
|
|
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.getHeight(Heightmap.Types.MOTION_BLOCKING, blockposition.getX() & 15, blockposition.getZ() & 15) + 1;
|
|
+ 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
|
|
}
|
|
|
|
if (flag) {
|
|
+ blockposition.setY(normalY); // Paper
|
|
if (biomebase.shouldSnow(this, blockposition)) {
|
|
org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition, Blocks.SNOW.defaultBlockState(), null); // CraftBukkit
|
|
}
|
|
|
|
- BlockState iblockdata = this.getBlockState(blockposition1);
|
|
+ blockposition.setY(downY); // Paper
|
|
+ BlockState iblockdata = this.getBlockState(blockposition); // Paper
|
|
+ blockposition.setY(normalY); // Paper
|
|
Biome.Precipitation biomebase_precipitation = this.getBiome(blockposition).getPrecipitation();
|
|
|
|
- if (biomebase_precipitation == Biome.Precipitation.RAIN && biomebase.coldEnoughToSnow(blockposition1)) {
|
|
+ blockposition.setY(downY); // Paper
|
|
+ if (biomebase_precipitation == Biome.Precipitation.RAIN && biomebase.coldEnoughToSnow(blockposition)) { // Paper
|
|
biomebase_precipitation = Biome.Precipitation.SNOW;
|
|
}
|
|
|
|
- iblockdata.getBlock().handlePrecipitation(iblockdata, this, blockposition1, biomebase_precipitation);
|
|
+ iblockdata.getBlock().handlePrecipitation(iblockdata, this, blockposition, biomebase_precipitation); // Paper
|
|
}
|
|
}
|
|
|
|
- gameprofilerfiller.popPush("tickBlocks");
|
|
+ // Paper start - optimise random block ticking
|
|
+ gameprofilerfiller.popPush("randomTick");
|
|
timings.chunkTicksBlocks.startTiming(); // Paper
|
|
if (randomTickSpeed > 0) {
|
|
- LevelChunkSection[] achunksection = chunk.getSections();
|
|
- int l = achunksection.length;
|
|
-
|
|
- for (int i1 = 0; i1 < l; ++i1) {
|
|
- LevelChunkSection chunksection = achunksection[i1];
|
|
-
|
|
- if (chunksection.isRandomlyTicking()) {
|
|
- int j1 = chunksection.bottomBlockY();
|
|
-
|
|
- for (int k1 = 0; k1 < randomTickSpeed; ++k1) {
|
|
- BlockPos blockposition2 = this.getBlockRandomPos(j, j1, k, 15);
|
|
-
|
|
- gameprofilerfiller.push("randomTick");
|
|
- BlockState iblockdata1 = chunksection.getBlockState(blockposition2.getX() - j, blockposition2.getY() - j1, blockposition2.getZ() - k);
|
|
+ LevelChunkSection[] sections = chunk.getSections();
|
|
+ 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;
|
|
+ }
|
|
|
|
- if (iblockdata1.isRandomlyTicking()) {
|
|
- iblockdata1.randomTick(this, blockposition2, this.random);
|
|
- }
|
|
+ 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 = iblockdata1.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, blockposition2, 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
|
|
}
|
|
}
|
|
}
|
|
-
|
|
+ // Paper end - optimise random block ticking
|
|
timings.chunkTicksBlocks.stopTiming(); // Paper
|
|
gameprofilerfiller.pop();
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/util/BitStorage.java b/src/main/java/net/minecraft/util/BitStorage.java
|
|
index 62251727788d48a461ea6f7945771d7d6bdc7282..106610ccc74b70b557b01c61262d56c4f1147acf 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[] is);
|
|
|
|
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 6426d6c2c31ead49444fe56e2230266290fa79dd..881a2318aac72526e0451688af58c620e4f525d1 100644
|
|
--- a/src/main/java/net/minecraft/util/SimpleBitStorage.java
|
|
+++ b/src/main/java/net/minecraft/util/SimpleBitStorage.java
|
|
@@ -124,6 +124,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 172e7a0761724cc802387e613258830a0defb04a..55c486349ddb295d4d4c6fa9214cdae90f851948 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 60e1111f3c2c43398f21c541248f38524f41f4fb..56e9c0d15249562ebea8eb451d4bcc9ff5e7d594 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java
|
|
@@ -87,7 +87,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 367c932ae6abfbc73a42afaa143cefcedea52ffb..6be462975523dae9ff436ba1643b2042ec66e554 100644
|
|
--- a/src/main/java/net/minecraft/world/level/Level.java
|
|
+++ b/src/main/java/net/minecraft/world/level/Level.java
|
|
@@ -1352,10 +1352,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 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 f9e871cff9318e6fea92d5b7a7035b3bfa259bca..c579be24944b310151738d375c53e2d52b32edb7 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 {
|
|
private short tickingFluidCount;
|
|
public final PalettedContainer<BlockState> states;
|
|
private final PalettedContainer<Biome> biomes;
|
|
+ public final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper
|
|
|
|
public LevelChunkSection(int chunkPos, PalettedContainer<BlockState> blockStateContainer, PalettedContainer<Biome> biomeContainer) {
|
|
this.bottomBlockY = LevelChunkSection.getBottomBlockY(chunkPos);
|
|
@@ -79,6 +80,9 @@ public class LevelChunkSection {
|
|
--this.nonEmptyBlockCount;
|
|
if (iblockdata1.isRandomlyTicking()) {
|
|
--this.tickingBlockCount;
|
|
+ // Paper start
|
|
+ this.tickingList.remove(x, y, z);
|
|
+ // Paper end
|
|
}
|
|
}
|
|
|
|
@@ -90,6 +94,9 @@ public class LevelChunkSection {
|
|
++this.nonEmptyBlockCount;
|
|
if (state.isRandomlyTicking()) {
|
|
++this.tickingBlockCount;
|
|
+ // Paper start
|
|
+ this.tickingList.add(x, y, z, state);
|
|
+ // Paper end
|
|
}
|
|
}
|
|
|
|
@@ -121,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, i) -> { // Paper
|
|
FluidState fluid = iblockdata.getFluidState();
|
|
|
|
if (!iblockdata.isAir()) {
|
|
- this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + i);
|
|
+ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1); // Paper
|
|
if (iblockdata.isRandomlyTicking()) {
|
|
- this.tickingBlockCount = (short) (this.tickingBlockCount + i);
|
|
+ // Paper start
|
|
+ this.tickingBlockCount = (short)(this.tickingBlockCount + 1);
|
|
+ this.tickingList.add(i, iblockdata);
|
|
+ // Paper end
|
|
}
|
|
}
|
|
|
|
if (!fluid.isEmpty()) {
|
|
- this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + i);
|
|
+ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1); // Paper
|
|
if (fluid.isRandomlyTicking()) {
|
|
- this.tickingFluidCount = (short) (this.tickingFluidCount + i);
|
|
+ this.tickingFluidCount = (short) (this.tickingFluidCount + 1); // Paper
|
|
}
|
|
}
|
|
|
|
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 ec285516c2c403b65b2446f20b187628539d96a6..dea80ddba894453a0229888adfa235638cc9f659 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
|
|
@@ -286,6 +286,14 @@ public class PalettedContainer<T> implements PaletteResize<T> {
|
|
}
|
|
}
|
|
|
|
+ // 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);
|