mirror of
https://github.com/PaperMC/Paper.git
synced 2024-12-01 06:53:44 +01:00
dcc290167f
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: dea9ce0a SPIGOT-7198: Add Sittable interface to Camel CraftBukkit Changes: eecb4c0dc SPIGOT-7196: Exception loading alternate worlds 0ff61e8fa SPIGOT-7198: Add Sittable interface to Camel 676441aac PR-1121: Handle additional missing SpawnEggs in MetaSpawnEgg e85280e02 Handle missing SpawnEggs in MetaSpawnEgg Spigot Changes: d90018e0 SPIGOT-7199: NPE loading or creating world with custom chunk generator
457 lines
21 KiB
Diff
457 lines
21 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..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 4dec1619002cd922c25f27cc771cf8672092a341..46464111ce908eaa44def90167c50a442e0ae640 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
@@ -706,6 +706,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();
|
|
@@ -715,10 +719,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().environment.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() * this.paperConfig().entities.spawning.skeletonHorseThunderSpawnChance.or(0.01D) && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD); // Paper
|
|
@@ -748,18 +752,26 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
int l;
|
|
|
|
if (!this.paperConfig().environment.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();
|
|
+ // 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 = (Biome) this.getBiome(blockposition).value();
|
|
|
|
- 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) {
|
|
int i1 = this.getGameRules().getInt(GameRules.RULE_SNOW_ACCUMULATION_HEIGHT);
|
|
BlockState iblockdata;
|
|
|
|
+ blockposition.setY(normalY); // Paper
|
|
if (i1 > 0 && biomebase.shouldSnow(this, blockposition)) {
|
|
iblockdata = this.getBlockState(blockposition);
|
|
if (iblockdata.is(Blocks.SNOW)) {
|
|
@@ -774,52 +786,55 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition, Blocks.SNOW.defaultBlockState(), null); // CraftBukkit
|
|
}
|
|
}
|
|
+ blockposition.setY(downY); // Paper
|
|
|
|
- iblockdata = this.getBlockState(blockposition1);
|
|
+ iblockdata = this.getBlockState(blockposition); // Paper
|
|
Biome.Precipitation biomebase_precipitation = biomebase.getPrecipitation();
|
|
|
|
- if (biomebase_precipitation == Biome.Precipitation.RAIN && biomebase.coldEnoughToSnow(blockposition1)) {
|
|
+ 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 j1 = achunksection.length;
|
|
-
|
|
- for (int k1 = 0; k1 < j1; ++k1) {
|
|
- LevelChunkSection chunksection = achunksection[k1];
|
|
-
|
|
- if (chunksection.isRandomlyTicking()) {
|
|
- int l1 = chunksection.bottomBlockY();
|
|
-
|
|
- for (l = 0; l < randomTickSpeed; ++l) {
|
|
- BlockPos blockposition2 = this.getBlockRandomPos(j, l1, k, 15);
|
|
-
|
|
- gameprofilerfiller.push("randomTick");
|
|
- BlockState iblockdata2 = chunksection.getBlockState(blockposition2.getX() - j, blockposition2.getY() - l1, 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 (iblockdata2.isRandomlyTicking()) {
|
|
- iblockdata2.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 = iblockdata2.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 (ping the Canadian)
|
|
}
|
|
}
|
|
}
|
|
-
|
|
+ // 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 9b81ce9d85cba07e9752c29fb5a842c4b00aa873..36e33923bf48e56c743ed043bcbc66bc32f0422f 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 9686ce7536c9924b1b2aced4f013f46759cbc72e..5d8e9bdf5538b19681f21949368d862fab8a89ad 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 30663713e198bfe40b95c48524b71ea65f39965e..25503678e7d049a8b3172cfad8a5606958c32302 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java
|
|
@@ -83,7 +83,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 f2868adace339d923012efc9aba0d882909eecb5..7c86a130afb536ece5726caf7f2b32f68fa5fa0a 100644
|
|
--- a/src/main/java/net/minecraft/world/level/Level.java
|
|
+++ b/src/main/java/net/minecraft/world/level/Level.java
|
|
@@ -1368,10 +1368,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 066874d27495dcaa3dea254b7328257e46920357..c3f1334b2bb97f0633f3ea43b97ee49adfd8bc0d 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
|
|
@@ -27,6 +27,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(int i, PalettedContainer<BlockState> datapaletteblock, PalettedContainer<Holder<Biome>> palettedcontainerro) {
|
|
// CraftBukkit end
|
|
@@ -85,6 +86,9 @@ public class LevelChunkSection {
|
|
--this.nonEmptyBlockCount;
|
|
if (iblockdata1.isRandomlyTicking()) {
|
|
--this.tickingBlockCount;
|
|
+ // Paper start
|
|
+ this.tickingList.remove(x, y, z);
|
|
+ // Paper end
|
|
}
|
|
}
|
|
|
|
@@ -96,6 +100,9 @@ public class LevelChunkSection {
|
|
++this.nonEmptyBlockCount;
|
|
if (state.isRandomlyTicking()) {
|
|
++this.tickingBlockCount;
|
|
+ // Paper start
|
|
+ this.tickingList.add(x, y, z, state);
|
|
+ // Paper end
|
|
}
|
|
}
|
|
|
|
@@ -127,40 +134,31 @@ public class LevelChunkSection {
|
|
}
|
|
|
|
public void recalcBlockCounts() {
|
|
- class a implements PalettedContainer.CountConsumer<BlockState> {
|
|
-
|
|
- public int nonEmptyBlockCount;
|
|
- public int tickingBlockCount;
|
|
- public int tickingFluidCount;
|
|
-
|
|
- a() {}
|
|
-
|
|
- public void accept(BlockState iblockdata, int i) {
|
|
- FluidState fluid = iblockdata.getFluidState();
|
|
-
|
|
- if (!iblockdata.isAir()) {
|
|
- this.nonEmptyBlockCount += i;
|
|
- if (iblockdata.isRandomlyTicking()) {
|
|
- this.tickingBlockCount += i;
|
|
- }
|
|
+ // Paper start - unfuck this
|
|
+ this.tickingList.clear();
|
|
+ this.nonEmptyBlockCount = 0;
|
|
+ this.tickingBlockCount = 0;
|
|
+ this.tickingFluidCount = 0;
|
|
+ this.states.forEachLocation((BlockState iblockdata, int i) -> {
|
|
+ FluidState fluid = iblockdata.getFluidState();
|
|
+
|
|
+ if (!iblockdata.isAir()) {
|
|
+ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1);
|
|
+ if (iblockdata.isRandomlyTicking()) {
|
|
+ this.tickingBlockCount = (short)(this.tickingBlockCount + 1);
|
|
+ this.tickingList.add(i, iblockdata);
|
|
}
|
|
+ }
|
|
|
|
- if (!fluid.isEmpty()) {
|
|
- this.nonEmptyBlockCount += i;
|
|
- if (fluid.isRandomlyTicking()) {
|
|
- this.tickingFluidCount += i;
|
|
- }
|
|
+ if (!fluid.isEmpty()) {
|
|
+ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1);
|
|
+ if (fluid.isRandomlyTicking()) {
|
|
+ this.tickingFluidCount = (short) (this.tickingFluidCount + 1);
|
|
}
|
|
-
|
|
}
|
|
- }
|
|
|
|
- a a0 = new a();
|
|
-
|
|
- this.states.count(a0);
|
|
- this.nonEmptyBlockCount = (short) a0.nonEmptyBlockCount;
|
|
- this.tickingBlockCount = (short) a0.tickingBlockCount;
|
|
- this.tickingFluidCount = (short) a0.tickingFluidCount;
|
|
+ });
|
|
+ // Paper end
|
|
}
|
|
|
|
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 715830d27490572bbe963515f046205c6d7e834d..3c7ef1e8e338a84eee34f39ce73e64876632ea87 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
|
|
@@ -383,6 +383,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);
|