mirror of
https://github.com/PaperMC/Paper.git
synced 2024-11-30 06:24:06 +01:00
0faaa7da92
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: 19b7b7bd #561: Add clear weather World API 5929c808 #552: Add the ability to retrieve hit, step, fall, and other sounds from blocks. CraftBukkit Changes: e1ebdd92 #771: Add clear weather World API 424598d2 #752: Add the ability to retrieve hit, step, fall, and other sounds from blocks.
408 lines
19 KiB
Diff
408 lines
19 KiB
Diff
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/server/BlockPosition.java b/src/main/java/net/minecraft/server/BlockPosition.java
|
|
index f068436960c96b0df427bcdf337a4dcc6a9f66f5..253423acc3c3697a5e47136f34da92617e719753 100644
|
|
--- a/src/main/java/net/minecraft/server/BlockPosition.java
|
|
+++ b/src/main/java/net/minecraft/server/BlockPosition.java
|
|
@@ -462,6 +462,7 @@ public class BlockPosition extends BaseBlockPosition {
|
|
return this.d(MathHelper.floor(d0), MathHelper.floor(d1), MathHelper.floor(d2));
|
|
}
|
|
|
|
+ public final BlockPosition.MutableBlockPosition setValues(final BaseBlockPosition baseblockposition) { return this.g(baseblockposition); } // Paper - OBFHELPER
|
|
public BlockPosition.MutableBlockPosition g(BaseBlockPosition baseblockposition) {
|
|
return this.d(baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ());
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
|
|
index 211e4353e88ad703c7f6aa8e416e6a50909bb3d7..98401f36f768f36f98007c52c998252e25b07d47 100644
|
|
--- a/src/main/java/net/minecraft/server/Chunk.java
|
|
+++ b/src/main/java/net/minecraft/server/Chunk.java
|
|
@@ -596,8 +596,8 @@ public class Chunk implements IChunkAccess {
|
|
this.entities.remove(entity); // Paper
|
|
}
|
|
|
|
- @Override
|
|
- public int getHighestBlock(HeightMap.Type heightmap_type, int i, int j) {
|
|
+ public final int getHighestBlockY(HeightMap.Type heightmap_type, int i, int j) { return this.getHighestBlock(heightmap_type, i, j) + 1; } // Paper - sort of an obfhelper, but without -1
|
|
+ @Override public int getHighestBlock(HeightMap.Type heightmap_type, int i, int j) { // Paper
|
|
return ((HeightMap) this.heightMap.get(heightmap_type)).a(i & 15, j & 15) - 1;
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java
|
|
index 90bb2f7ad9f82818d58de9be918f1299c6725070..668942c629732b8d46b7f2646df8e7827baae240 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkSection.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkSection.java
|
|
@@ -7,12 +7,14 @@ import javax.annotation.Nullable;
|
|
public class ChunkSection {
|
|
|
|
public static final DataPalette<IBlockData> GLOBAL_PALETTE = new DataPaletteGlobal<>(Block.REGISTRY_ID, Blocks.AIR.getBlockData());
|
|
- private final int yPos;
|
|
+ final int yPos; // Paper - private -> package-private
|
|
short nonEmptyBlockCount; // Paper - package-private
|
|
- private short tickingBlockCount;
|
|
+ short tickingBlockCount; // Paper - private -> package-private
|
|
private short e;
|
|
final DataPaletteBlock<IBlockData> blockIds; // Paper - package-private
|
|
|
|
+ final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper
|
|
+
|
|
// Paper start - Anti-Xray - Add parameters
|
|
@Deprecated public ChunkSection(int i) { this(i, null, null, true); } // Notice for updates: Please make sure this constructor isn't used anywhere
|
|
public ChunkSection(int i, IChunkAccess chunk, World world, boolean initializeBlocks) {
|
|
@@ -67,6 +69,9 @@ public class ChunkSection {
|
|
--this.nonEmptyBlockCount;
|
|
if (iblockdata1.isTicking()) {
|
|
--this.tickingBlockCount;
|
|
+ // Paper start
|
|
+ this.tickingList.remove(i, j, k);
|
|
+ // Paper end
|
|
}
|
|
}
|
|
|
|
@@ -78,6 +83,9 @@ public class ChunkSection {
|
|
++this.nonEmptyBlockCount;
|
|
if (iblockdata.isTicking()) {
|
|
++this.tickingBlockCount;
|
|
+ // Paper start
|
|
+ this.tickingList.add(i, j, k, iblockdata);
|
|
+ // Paper end
|
|
}
|
|
}
|
|
|
|
@@ -113,23 +121,29 @@ public class ChunkSection {
|
|
}
|
|
|
|
public void recalcBlockCounts() {
|
|
+ // Paper start
|
|
+ this.tickingList.clear();
|
|
+ // Paper end
|
|
this.nonEmptyBlockCount = 0;
|
|
this.tickingBlockCount = 0;
|
|
this.e = 0;
|
|
- this.blockIds.a((iblockdata, i) -> {
|
|
+ this.blockIds.forEachLocation((iblockdata, location) -> { // Paper
|
|
Fluid fluid = iblockdata.getFluid();
|
|
|
|
if (!iblockdata.isAir()) {
|
|
- this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + i);
|
|
+ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1);
|
|
if (iblockdata.isTicking()) {
|
|
- 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.f()) {
|
|
- this.e = (short) (this.e + i);
|
|
+ this.e = (short) (this.e + 1);
|
|
}
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/DataBits.java b/src/main/java/net/minecraft/server/DataBits.java
|
|
index 2901700dc0ff5dce4fb6f6df9f6686c786293d28..26b48b5ffa4ce3fbe50810dc1a8070d333a2684f 100644
|
|
--- a/src/main/java/net/minecraft/server/DataBits.java
|
|
+++ b/src/main/java/net/minecraft/server/DataBits.java
|
|
@@ -111,4 +111,32 @@ public class DataBits {
|
|
}
|
|
|
|
}
|
|
+
|
|
+ // Paper start
|
|
+ public final void forEach(DataBitConsumer consumer) {
|
|
+ int i = 0;
|
|
+ long[] along = this.b;
|
|
+ int j = along.length;
|
|
+
|
|
+ for (int k = 0; k < j; ++k) {
|
|
+ long l = along[k];
|
|
+
|
|
+ for (int i1 = 0; i1 < this.f; ++i1) {
|
|
+ consumer.accept(i, (int) (l & this.d));
|
|
+ l >>= this.c;
|
|
+ ++i;
|
|
+ if (i >= this.e) {
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @FunctionalInterface
|
|
+ static interface DataBitConsumer {
|
|
+
|
|
+ void accept(int location, int data);
|
|
+
|
|
+ }
|
|
+ // Paper end
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/DataPaletteBlock.java b/src/main/java/net/minecraft/server/DataPaletteBlock.java
|
|
index ed77117630d54b7ad81f633110c7d2a7c59288e9..95ef96286855624590b72d69514b0fc0e08fddba 100644
|
|
--- a/src/main/java/net/minecraft/server/DataPaletteBlock.java
|
|
+++ b/src/main/java/net/minecraft/server/DataPaletteBlock.java
|
|
@@ -277,6 +277,14 @@ public class DataPaletteBlock<T> implements DataPaletteExpandable<T> {
|
|
});
|
|
}
|
|
|
|
+ // Paper start
|
|
+ public void forEachLocation(DataPaletteBlock.a<T> datapaletteblock_a) {
|
|
+ this.getDataBits().forEach((int location, int data) -> {
|
|
+ datapaletteblock_a.accept(this.getDataPalette().getObject(data), location);
|
|
+ });
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
@FunctionalInterface
|
|
public interface a<T> {
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/EntityTurtle.java b/src/main/java/net/minecraft/server/EntityTurtle.java
|
|
index fa8eab20bb839c53bf7989d02d2274f835002609..4ad393bc99881d813e2b349fb929fc8e69631723 100644
|
|
--- a/src/main/java/net/minecraft/server/EntityTurtle.java
|
|
+++ b/src/main/java/net/minecraft/server/EntityTurtle.java
|
|
@@ -28,7 +28,7 @@ public class EntityTurtle extends EntityAnimal {
|
|
}
|
|
|
|
public void setHomePos(BlockPosition blockposition) {
|
|
- this.datawatcher.set(EntityTurtle.bp, blockposition);
|
|
+ this.datawatcher.set(EntityTurtle.bp, blockposition.immutableCopy()); // Paper - called with mutablepos...
|
|
}
|
|
|
|
public BlockPosition getHomePos() { // Paper - public
|
|
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
|
|
index 3b0b86bd33601f741850047e3fe0b6022bc1095b..8c58581417ca3e0336359e7db65568a08e025d6d 100644
|
|
--- a/src/main/java/net/minecraft/server/World.java
|
|
+++ b/src/main/java/net/minecraft/server/World.java
|
|
@@ -1412,10 +1412,18 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
|
public abstract ITagRegistry p();
|
|
|
|
public BlockPosition a(int i, int j, int k, int l) {
|
|
+ // Paper start - allow use of mutable pos
|
|
+ BlockPosition.MutableBlockPosition ret = new BlockPosition.MutableBlockPosition();
|
|
+ this.getRandomBlockPosition(i, j, k, l, ret);
|
|
+ return ret.immutableCopy();
|
|
+ }
|
|
+ public final BlockPosition.MutableBlockPosition getRandomBlockPosition(int i, int j, int k, int l, BlockPosition.MutableBlockPosition out) {
|
|
+ // Paper end
|
|
this.n = this.n * 3 + 1013904223;
|
|
int i1 = this.n >> 2;
|
|
|
|
- return new BlockPosition(i + (i1 & 15), j + (i1 >> 16 & l), k + (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 isSavingDisabled() {
|
|
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
|
|
index c345729675c76c5cd02a469283ed668a2df52816..52b1f4afc47d91603e1829275daecf8e9dd4cb1f 100644
|
|
--- a/src/main/java/net/minecraft/server/WorldServer.java
|
|
+++ b/src/main/java/net/minecraft/server/WorldServer.java
|
|
@@ -561,7 +561,12 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
});
|
|
}
|
|
|
|
- public void a(Chunk chunk, int i) {
|
|
+ // Paper start - optimise random block ticking
|
|
+ private final BlockPosition.MutableBlockPosition chunkTickMutablePosition = new BlockPosition.MutableBlockPosition();
|
|
+ private final com.destroystokyo.paper.util.math.ThreadUnsafeRandom randomTickRandom = new com.destroystokyo.paper.util.math.ThreadUnsafeRandom();
|
|
+ // Paper end
|
|
+
|
|
+ public void a(Chunk chunk, int i) { final int randomTickSpeed = i; // Paper
|
|
ChunkCoordIntPair chunkcoordintpair = chunk.getPos();
|
|
boolean flag = this.isRaining();
|
|
int j = chunkcoordintpair.d();
|
|
@@ -569,10 +574,10 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
GameProfilerFiller gameprofilerfiller = this.getMethodProfiler();
|
|
|
|
gameprofilerfiller.enter("thunder");
|
|
- BlockPosition blockposition;
|
|
+ final BlockPosition.MutableBlockPosition blockposition = this.chunkTickMutablePosition; // Paper - use mutable to reduce allocation rate, final to force compile fail on change
|
|
|
|
if (!this.paperConfig.disableThunder && flag && this.W() && this.random.nextInt(100000) == 0) { // Paper - Disable thunder
|
|
- blockposition = this.a(this.a(j, 0, k, 15));
|
|
+ blockposition.setValues(this.a(this.a(j, 0, k, 15))); // Paper
|
|
if (this.isRainingAt(blockposition)) {
|
|
DifficultyDamageScaler difficultydamagescaler = this.getDamageScaler(blockposition);
|
|
boolean flag1 = this.getGameRules().getBoolean(GameRules.DO_MOB_SPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.b() * paperConfig.skeleHorseSpawnChance; // Paper
|
|
@@ -595,59 +600,77 @@ public class WorldServer extends World implements GeneratorAccessSeed {
|
|
}
|
|
|
|
gameprofilerfiller.exitEnter("iceandsnow");
|
|
- if (!this.paperConfig.disableIceAndSnow && this.random.nextInt(16) == 0) { // Paper - Disable ice and snow
|
|
- blockposition = this.getHighestBlockYAt(HeightMap.Type.MOTION_BLOCKING, this.a(j, 0, k, 15));
|
|
- BlockPosition blockposition1 = blockposition.down();
|
|
+ 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.Type.MOTION_BLOCKING, blockposition.getX() & 15, blockposition.getZ() & 15);
|
|
+ int downY = normalY - 1;
|
|
+ blockposition.setY(normalY);
|
|
+ // Paper end
|
|
BiomeBase biomebase = this.getBiome(blockposition);
|
|
|
|
- if (biomebase.a(this, blockposition1)) {
|
|
- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, Blocks.ICE.getBlockData(), null); // CraftBukkit
|
|
+ // Paper start - optimise chunk ticking
|
|
+ blockposition.setY(downY);
|
|
+ if (biomebase.a(this, blockposition)) {
|
|
+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition, Blocks.ICE.getBlockData(), null); // CraftBukkit
|
|
+ // Paper end
|
|
}
|
|
|
|
+ blockposition.setY(normalY); // Paper
|
|
if (flag && biomebase.b(this, blockposition)) {
|
|
org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition, Blocks.SNOW.getBlockData(), null); // CraftBukkit
|
|
}
|
|
|
|
- if (flag && this.getBiome(blockposition1).c() == BiomeBase.Precipitation.RAIN) {
|
|
- this.getType(blockposition1).getBlock().c((World) this, blockposition1);
|
|
+ // Paper start - optimise chunk ticking
|
|
+ blockposition.setY(downY);
|
|
+ if (flag && this.getBiome(blockposition).c() == BiomeBase.Precipitation.RAIN) {
|
|
+ chunk.getType(blockposition).getBlock().c((World) this, blockposition);
|
|
+ // Paper end
|
|
}
|
|
}
|
|
|
|
- gameprofilerfiller.exitEnter("tickBlocks");
|
|
- timings.chunkTicksBlocks.startTiming(); // Paper
|
|
+ // Paper start - optimise random block ticking
|
|
+ gameprofilerfiller.exit();
|
|
if (i > 0) {
|
|
- ChunkSection[] achunksection = chunk.getSections();
|
|
- int l = achunksection.length;
|
|
+ gameprofilerfiller.enter("randomTick");
|
|
+ timings.chunkTicksBlocks.startTiming(); // Paper
|
|
|
|
- for (int i1 = 0; i1 < l; ++i1) {
|
|
- ChunkSection chunksection = achunksection[i1];
|
|
+ ChunkSection[] sections = chunk.getSections();
|
|
|
|
- if (chunksection != Chunk.a && chunksection.d()) {
|
|
- int j1 = chunksection.getYPosition();
|
|
+ for (int sectionIndex = 0; sectionIndex < 16; ++sectionIndex) {
|
|
+ ChunkSection section = sections[sectionIndex];
|
|
+ if (section == null || section.tickingList.size() == 0) {
|
|
+ continue;
|
|
+ }
|
|
|
|
- for (int k1 = 0; k1 < i; ++k1) {
|
|
- BlockPosition blockposition2 = this.a(j, j1, k, 15);
|
|
+ int yPos = sectionIndex << 4;
|
|
|
|
- gameprofilerfiller.enter("randomTick");
|
|
- IBlockData iblockdata = chunksection.getType(blockposition2.getX() - j, blockposition2.getY() - j1, blockposition2.getZ() - k);
|
|
+ for (int a = 0; a < randomTickSpeed; ++a) {
|
|
+ int tickingBlocks = section.tickingList.size();
|
|
+ int index = this.randomTickRandom.nextInt(16 * 16 * 16);
|
|
+ if (index >= tickingBlocks) {
|
|
+ continue;
|
|
+ }
|
|
|
|
- if (iblockdata.isTicking()) {
|
|
- iblockdata.b(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;
|
|
|
|
- Fluid fluid = iblockdata.getFluid();
|
|
+ BlockPosition blockposition2 = blockposition.setValues(j + randomX, randomY, k + randomZ);
|
|
+ IBlockData iblockdata = com.destroystokyo.paper.util.maplist.IBlockDataList.getBlockDataFromRaw(raw);
|
|
|
|
- if (fluid.f()) {
|
|
- fluid.b(this, blockposition2, this.random);
|
|
- }
|
|
+ iblockdata.b(this, blockposition2, this.randomTickRandom);
|
|
|
|
- gameprofilerfiller.exit();
|
|
- }
|
|
+ // We drop the fluid tick since LAVA is ALREADY TICKED by the above method.
|
|
+ // TODO CHECK ON UPDATE
|
|
}
|
|
}
|
|
+ gameprofilerfiller.exit();
|
|
+ timings.chunkTicksBlocks.stopTiming(); // Paper
|
|
+ // Paper end
|
|
}
|
|
- timings.chunkTicksBlocks.stopTiming(); // Paper
|
|
- gameprofilerfiller.exit();
|
|
}
|
|
|
|
protected BlockPosition a(BlockPosition blockposition) {
|