diff --git a/leaf-server/minecraft-patches/features/0280-optimize-random-tick.patch b/leaf-server/minecraft-patches/features/0280-optimize-random-tick.patch new file mode 100644 index 00000000..108f600b --- /dev/null +++ b/leaf-server/minecraft-patches/features/0280-optimize-random-tick.patch @@ -0,0 +1,105 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: hayanesuru +Date: Fri, 6 Jun 2025 20:46:10 +0900 +Subject: [PATCH] optimize random tick + + +diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java +index fc86e900e41305287a6cc6d766184c6e28d6189b..9286312e11b98e4873f1ca13cf210feadcd6a2d7 100644 +--- a/net/minecraft/server/level/ServerChunkCache.java ++++ b/net/minecraft/server/level/ServerChunkCache.java +@@ -632,6 +632,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + } finally { + list.clear(); + } ++ this.level.randomTickSystem.tick(this.level); // Leaf - optimize random tick + + this.iterateTickingChunksFaster(); // Paper - chunk tick iteration optimisations + if (_boolean) { +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index 224a032e8992f104ad9380182ed67c316c93274e..07f6da45a88e1630ca4249bb134fce1f95d2c39c 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -1110,6 +1110,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + private int currentIceAndSnowTick = 0; protected void resetIceAndSnowTick() { this.currentIceAndSnowTick = this.simpleRandom.nextInt(16); } // Gale - Airplane - optimize random calls in chunk ticking + ++ public org.dreeam.leaf.world.RandomTickSystem randomTickSystem = new org.dreeam.leaf.world.RandomTickSystem(); // Leaf - optimize random tick + public void tickChunk(LevelChunk chunk, int randomTickSpeed) { + final net.minecraft.world.level.levelgen.BitRandomSource simpleRandom = this.simpleRandom; // Paper - optimise random ticking // Leaf - Faster random generator - upcasting + ChunkPos pos = chunk.getPos(); +@@ -1125,7 +1126,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } // Paper - Option to disable ice and snow + + if (randomTickSpeed > 0) { +- this.optimiseRandomTick(chunk, randomTickSpeed); // Paper - optimise random ticking ++ if (org.dreeam.leaf.config.modules.opt.OptimizeRandomTick.enabled) randomTickSystem.tickChunk(this.simpleRandom, chunk, randomTickSpeed); // Leaf - optimize random tick ++ else this.optimiseRandomTick(chunk, randomTickSpeed); // Paper - optimise random ticking // Leaf - optimize random tick + } + } + +diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java +index 31f19dfe16e270b55f3b44754c97ed8d9fa422cf..31c6c035aca5400a5c0a030bfe5334545e2a4bca 100644 +--- a/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/net/minecraft/world/level/chunk/LevelChunk.java +@@ -150,6 +150,48 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + } + // Gale end - Airplane - optimize random calls in chunk ticking - instead of using a random every time the chunk is ticked, define when lightning strikes preemptively + ++ // Leaf start - optimize random tick ++ private boolean leaf$tickingBlocksDirty = true; ++ private int leaf$tickingBlocksCount; ++ private int leaf$firstTickingSectionIndex = -1; ++ public final int leaf$tickingBlocksCount() { ++ if (!leaf$tickingBlocksDirty) { ++ return leaf$tickingBlocksCount; ++ } ++ leaf$tickingBlocksDirty = false; ++ int sum = 0; ++ leaf$firstTickingSectionIndex = -1; ++ for (int i = 0; i < sections.length; i++) { ++ LevelChunkSection section = sections[i]; ++ int size = section.moonrise$getTickingBlockList().size(); ++ if (size != 0 && leaf$firstTickingSectionIndex == -1) { ++ leaf$firstTickingSectionIndex = i; ++ } ++ sum += size; ++ } ++ leaf$tickingBlocksCount = sum; ++ return sum; ++ } ++ public final java.util.OptionalLong leaf$getTickingPos(int idx) { ++ if (leaf$firstTickingSectionIndex != -1) { ++ for (int i = leaf$firstTickingSectionIndex; i < sections.length; i++) { ++ LevelChunkSection section = sections[i]; ++ var l = section.moonrise$getTickingBlockList(); ++ int size = l.size(); ++ if (idx < size) { ++ short loc = l.getRaw(idx); ++ int x = (loc & 15) | (chunkPos.x << 4); ++ int y = (loc >>> 8) | ((getMinSectionY() + i) << 4); ++ int z = ((loc >>> 4) & 15) | (chunkPos.z << 4); ++ return java.util.OptionalLong.of(BlockPos.asLong(x, y, z)); ++ } ++ idx -= size; ++ } ++ } ++ leaf$tickingBlocksDirty = true; ++ return java.util.OptionalLong.empty(); ++ } ++ // Leaf end - optimize random tick + public LevelChunk(Level level, ChunkPos pos) { + this(level, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, null, null, null); + } +@@ -414,6 +456,11 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + if (blockState == state) { + return null; + } else { ++ // Leaf start - optimize random tick ++ if (blockState.isRandomlyTicking() != state.isRandomlyTicking()) { ++ leaf$tickingBlocksDirty = true; ++ } ++ // Leaf end - optimize random tick + Block block = state.getBlock(); + this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING).update(i, y, i2, state); + this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES).update(i, y, i2, state); diff --git a/leaf-server/minecraft-patches/features/0280-Paw-optimization.patch b/leaf-server/minecraft-patches/features/0281-Paw-optimization.patch similarity index 99% rename from leaf-server/minecraft-patches/features/0280-Paw-optimization.patch rename to leaf-server/minecraft-patches/features/0281-Paw-optimization.patch index 2fba7230..4e9029bb 100644 --- a/leaf-server/minecraft-patches/features/0280-Paw-optimization.patch +++ b/leaf-server/minecraft-patches/features/0281-Paw-optimization.patch @@ -100,7 +100,7 @@ index 4535858701b2bb232b9d2feb2af6551526232ddc..e65c62dbe4c1560ae153e4c4344e9194 - // Paper end - detailed watchdog information } diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java -index fc86e900e41305287a6cc6d766184c6e28d6189b..6fd7e881bcfc7df2e4ac7abb99504c8194470e96 100644 +index 9286312e11b98e4873f1ca13cf210feadcd6a2d7..014fb76ad0e57048d036659bd89e628fd81a0190 100644 --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java @@ -622,8 +622,10 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon @@ -117,10 +117,10 @@ index fc86e900e41305287a6cc6d766184c6e28d6189b..6fd7e881bcfc7df2e4ac7abb99504c81 for (LevelChunk levelChunk : list) { diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 224a032e8992f104ad9380182ed67c316c93274e..7d34862d15360940e12b9c0f131ecffdb2a0a146 100644 +index 07f6da45a88e1630ca4249bb134fce1f95d2c39c..9fbaafaf5df6e003742cdea55da732cc7e602866 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java -@@ -1508,13 +1508,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1510,13 +1510,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // Paper end - log detailed entity tick information public void tickNonPassenger(Entity entity) { @@ -134,7 +134,7 @@ index 224a032e8992f104ad9380182ed67c316c93274e..7d34862d15360940e12b9c0f131ecffd entity.setOldPosAndRot(); entity.tickCount++; entity.totalEntityAge++; // Paper - age-like counter for all entities -@@ -1527,13 +1521,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1529,13 +1523,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe for (Entity entity1 : entity.getPassengers()) { this.tickPassenger(entity, entity1, isActive); // Paper - EAR 2 } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizeRandomTick.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizeRandomTick.java new file mode 100644 index 00000000..525bcac7 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizeRandomTick.java @@ -0,0 +1,20 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; +import org.dreeam.leaf.config.annotations.Experimental; + +public class OptimizeRandomTick extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName() + ".optimise-random-tick"; + } + + @Experimental + public static boolean enabled = false; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath(), enabled); + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/world/RandomTickSystem.java b/leaf-server/src/main/java/org/dreeam/leaf/world/RandomTickSystem.java new file mode 100644 index 00000000..fdcaad27 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/world/RandomTickSystem.java @@ -0,0 +1,164 @@ +package org.dreeam.leaf.world; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.material.FluidState; + +import java.util.OptionalLong; + +public final class RandomTickSystem { + private static final long SCALE = 0x100000L; + private static final long CHUNK_BLOCKS = 4096L; + + /// reduce unnecessary sampling and block counting + private static final long TICK_MASK = 0b11L; + private static final long TICK_MUL = 4L; + private static final int BITS_STEP = 2; + private static final int BITS_MAX = 60; + + private final LongArrayList queue = new LongArrayList(); + private final LongArrayList samples = new LongArrayList(); + private final LongArrayList weights = new LongArrayList(); + private long weightsSum = 0L; + + private int bits = 60; + private long cacheRandom = 0L; + + public void tick(ServerLevel world) { + if (weights.isEmpty() || samples.isEmpty()) { + return; + } + + final var random = world.simpleRandom; + final long chosen; + if (((weightsSum % SCALE) >= boundedNextLong(random, SCALE))) { + chosen = weightsSum / SCALE + 1L; + } else { + chosen = weightsSum / SCALE; + } + if (chosen == 0L) { + return; + } + + final long spoke = weightsSum / chosen; + if (spoke == 0L) { + return; + } + + final long[] weightsRaw = weights.elements(); + final long[] samplesRaw = samples.elements(); + + long accumulated = weightsRaw[0]; + long current = boundedNextLong(random, spoke); + int i = 0; + while (current < weightsSum) { + while (accumulated < current) { + i += 1; + accumulated += weightsRaw[i]; + } + queue.add(samplesRaw[i]); + current += spoke; + } + while (queue.size() < chosen) { + queue.add(samplesRaw[i]); + } + + long[] queueRaw = queue.elements(); + int j = 0; + int k; + for (k = queue.size() - 3; j < k; j += 4) { + final long packed1 = queueRaw[j]; + final long packed2 = queueRaw[j + 1]; + final long packed3 = queueRaw[j + 2]; + final long packed4 = queueRaw[j + 3]; + final LevelChunk chunk1 = getChunk(world, packed1); + final LevelChunk chunk2 = packed1 != packed2 ? getChunk(world, packed2) : chunk1; + final LevelChunk chunk3 = packed2 != packed3 ? getChunk(world, packed3) : chunk2; + final LevelChunk chunk4 = packed3 != packed4 ? getChunk(world, packed4) : chunk3; + if (chunk1 != null) tickBlock(world, chunk1, random); + if (chunk2 != null) tickBlock(world, chunk2, random); + if (chunk3 != null) tickBlock(world, chunk3, random); + if (chunk4 != null) tickBlock(world, chunk4, random); + } + for (k = queue.size(); j < k; j++) { + LevelChunk chunk = getChunk(world, queueRaw[j]); + if (chunk != null) tickBlock(world, chunk, random); + } + + weightsSum = 0L; + queue.clear(); + weights.clear(); + samples.clear(); + } + + private static LevelChunk getChunk(ServerLevel world, long packed) { + return world.chunkSource.getChunkAtIfLoadedImmediately((int) packed, (int) (packed >> 32)); + } + + private static void tickBlock(ServerLevel world, LevelChunk chunk, RandomSource random) { + OptionalLong optionalPos = chunk.leaf$getTickingPos(random.nextInt(chunk.leaf$tickingBlocksCount())); + if (optionalPos.isEmpty()) { + return; + } + BlockPos pos = BlockPos.of(optionalPos.getAsLong()); + BlockState state = chunk.getBlockStateFinal(pos.getX(), pos.getY(), pos.getZ()); + state.randomTick(world, pos, random); + + final boolean doubleTickFluids = !ca.spottedleaf.moonrise.common.PlatformHooks.get().configFixMC224294(); + if (doubleTickFluids) { + final FluidState fluidState = state.getFluidState(); + if (fluidState.isRandomlyTicking()) { + fluidState.randomTick(world, pos, random); + } + } + } + + public void tickChunk( + RandomSource random, + LevelChunk chunk, + long tickSpeed + ) { + if (this.bits == BITS_MAX) { + this.bits = 0; + this.cacheRandom = random.nextLong(); + } else { + this.bits += BITS_STEP; + } + if ((this.cacheRandom & (TICK_MASK << bits)) == 0L) { + long count = chunk.leaf$tickingBlocksCount(); + if (count != 0L) { + long weight = (TICK_MUL * tickSpeed * count * SCALE) / CHUNK_BLOCKS; + samples.add(chunk.getPos().longKey); + weights.add(weight); + weightsSum += weight; + } + } + } + + /** + * @param rng a random number generator to be used as a + * source of pseudorandom {@code long} values + * @param bound the upper bound (exclusive); must be greater than zero + * + * @return a pseudorandomly chosen {@code long} value + * + * @see java.util.random.RandomGenerator#nextLong(long) nextLong(bound) + */ + public static long boundedNextLong(RandomSource rng, long bound) { + final long m = bound - 1; + long r = rng.nextLong(); + if ((bound & m) == 0L) { + r &= m; + } else { + for (long u = r >>> 1; + u + m - (r = u % bound) < 0L; + u = rng.nextLong() >>> 1) + ; + } + return r; + } +}