From 2e822d3714957be62e7263f6a8413b0fb9b603b7 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Sun, 8 Jun 2025 13:21:54 +0900 Subject: [PATCH] optimize random tick (#357) * random tick * cleanup * [ci skip] cleanup --- .../features/0192-optimize-random-tick.patch | 97 ++++++++ .../modules/opt/OptimizeRandomTick.java | 20 ++ .../dreeam/leaf/world/RandomTickSystem.java | 222 ++++++++++++++++++ 3 files changed, 339 insertions(+) create mode 100644 leaf-server/minecraft-patches/features/0192-optimize-random-tick.patch create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizeRandomTick.java create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/world/RandomTickSystem.java diff --git a/leaf-server/minecraft-patches/features/0192-optimize-random-tick.patch b/leaf-server/minecraft-patches/features/0192-optimize-random-tick.patch new file mode 100644 index 00000000..54a9bf0d --- /dev/null +++ b/leaf-server/minecraft-patches/features/0192-optimize-random-tick.patch @@ -0,0 +1,97 @@ +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 2f927b422c2c4f2f65d822befe3cbfd9e3bb3708..d0fcfeaf093b718c8acd6e057176d569651299f2 100644 +--- a/net/minecraft/server/level/ServerChunkCache.java ++++ b/net/minecraft/server/level/ServerChunkCache.java +@@ -693,6 +693,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + this.level.tickChunk(levelChunk, _int); + } + } ++ this.level.randomTickSystem.tick(this.level); // Leaf - random tick + + if (flagAndHasNaturalSpawn) { // Gale - MultiPaper - skip unnecessary mob spawning computations + this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies); +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index eb849c57992658005e0f514c6f7923f8ca43bebf..2efcdb9bc91b9106b4aef9e24cc20596be4a5661 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -1128,6 +1128,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 + 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(); +@@ -1177,7 +1178,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.randomTickChunk(chunk, randomTickSpeed); // Leaf - random tick ++ else this.optimiseRandomTick(chunk, randomTickSpeed); // Paper - optimise random ticking // Leaf - random tick + } + } + +diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java +index 624a177695580510c0a49d4503dee72da7fd7114..8be4f0978451179fca8b9603743e875817040a08 100644 +--- a/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/net/minecraft/world/level/chunk/LevelChunk.java +@@ -151,6 +151,52 @@ 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 - random tick ++ private long leaf$randomTickChance; ++ private long leaf$countTickingBlocks; ++ private long leaf$countTickingSections; ++ ++ public final long leaf$randomTickChance() { ++ return leaf$randomTickChance; ++ } ++ public final void leaf$setRandomTickChance(long chance) { ++ leaf$randomTickChance = chance; ++ } ++ public final long leaf$countTickingBlocks() { ++ return leaf$countTickingBlocks; ++ } ++ public final long leaf$countTickingSections() { ++ return leaf$countTickingSections; ++ } ++ public final void leaf$recompute() { ++ long total1 = 0L; ++ long total2 = 0L; ++ for (LevelChunkSection section : sections) { ++ total1 += section.moonrise$getTickingBlockList().size(); ++ if (section.isRandomlyTickingBlocks()) { ++ total2++; ++ } ++ } ++ leaf$countTickingBlocks = total1; ++ leaf$countTickingSections = total2; ++ } ++ public final java.util.OptionalLong leaf$tickingPos(int idx) { ++ for (int i = 0; i < sections.length; i++) { ++ LevelChunkSection section = sections[i]; ++ var l = section.moonrise$getTickingBlockList(); ++ int size = l.size(); ++ if (size > idx) { ++ short loc = l.getRaw(size - idx); ++ return java.util.OptionalLong.of(BlockPos.asLong( ++ (loc & 15) | (chunkPos.x << 4), ++ (loc >>> 8) | (((getMinSectionY() + i) << 4)), ++ ((loc >>> 4) & 15) | (chunkPos.z << 4))); ++ } ++ idx -= size; ++ } ++ return java.util.OptionalLong.empty(); ++ } ++ // Leaf end - random tick + public LevelChunk(Level level, ChunkPos pos) { + this(level, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, null, null, null); + } 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..5a265d8e --- /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 = true; + + @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..5907d2c6 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/world/RandomTickSystem.java @@ -0,0 +1,222 @@ +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.levelgen.BitRandomSource; +import net.minecraft.world.level.levelgen.PositionalRandomFactory; +import net.minecraft.world.level.levelgen.RandomSupport; +import net.minecraft.world.level.material.FluidState; +import org.jetbrains.annotations.NotNull; + +import java.util.OptionalLong; + +public final class RandomTickSystem { + private final LongArrayList tickPos = new LongArrayList(); + private final WyRand rand = new WyRand(RandomSupport.generateUniqueSeed()); + private static final long SCALE = 0x100000L; + private static final long MASK = 0xfffffL; + private long cache = rand.next(); + private int cacheIdx = 0; + + public void tick(ServerLevel world) { + var simpleRandom = world.simpleRandom; + int j = tickPos.size(); + for (int i = 0; i < j; i++) { + tickBlock(world, tickPos.getLong(i), simpleRandom); + } + tickPos.clear(); + } + + private static void tickBlock(ServerLevel world, long packed, RandomSource tickRand) { + final boolean doubleTickFluids = !ca.spottedleaf.moonrise.common.PlatformHooks.get().configFixMC224294(); + BlockPos pos = BlockPos.of(packed); + LevelChunk chunk = world.chunkSource.getChunkAtIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4); + if (chunk == null) { + return; + } + BlockState state = chunk.getBlockStateFinal(pos.getX(), pos.getY(), pos.getZ()); + if (state == null) { + return; + } + state.randomTick(world, pos, tickRand); + if (doubleTickFluids) { + final FluidState fluidState = state.getFluidState(); + if (fluidState.isRandomlyTicking()) { + fluidState.randomTick(world, pos, tickRand); + } + } + } + + private long recompute(LevelChunk chunk, long tickSpeed) { + chunk.leaf$recompute(); + long tickingCount = chunk.leaf$countTickingBlocks(); + long numSections = chunk.leaf$countTickingSections(); + if (tickingCount == 0L || numSections == 0L) { + chunk.leaf$setRandomTickChance(0L); + return 0L; + } + long product = tickSpeed * tickingCount; + long chance = ((product + 2048L) * SCALE) / 4096L; + chunk.leaf$setRandomTickChance(chance); + return chance; + } +/* + public void randomTickChunkOrigin( + ServerLevel level, + LevelChunk chunk, + long tickSpeed + ) { + final LevelChunkSection[] sections = chunk.getSections(); + final int minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(level); // Leaf - Micro optimizations for random tick - no redundant cast + final net.minecraft.world.level.levelgen.BitRandomSource simpleRandom = level.simpleRandom; // Leaf - Faster random generator - upcasting + final boolean doubleTickFluids = !ca.spottedleaf.moonrise.common.PlatformHooks.get().configFixMC224294(); + + final ChunkPos cpos = chunk.getPos(); + final int offsetX = cpos.x << 4; + final int offsetZ = cpos.z << 4; + + for (int sectionIndex = 0, sectionsLen = sections.length; sectionIndex < sectionsLen; sectionIndex++) { + // Leaf start - Micro optimizations for random tick + final LevelChunkSection section = sections[sectionIndex]; + if (!section.isRandomlyTickingBlocks()) { + continue; + } + final int offsetY = (sectionIndex + minSection) << 4; + final net.minecraft.world.level.chunk.PalettedContainer states = section.states; + // Leaf end - Micro optimizations for random tick + + final ca.spottedleaf.moonrise.common.list.ShortList tickList = section.moonrise$getTickingBlockList(); // Leaf - Micro optimizations for random tick - no redundant cast + + for (int i = 0; i < tickSpeed; ++i) { + final int index = simpleRandom.nextInt() & ((16 * 16 * 16) - 1); + + if (index >= tickList.size()) { // Leaf - Micro optimizations for random tick - inline one-time value + // most of the time we fall here + continue; + } + + final int location = tickList.getRaw(index); // Leaf - Micro optimizations for random tick - no unnecessary operations + final BlockState state = states.get(location); + + // do not use a mutable pos, as some random tick implementations store the input without calling immutable()! + final BlockPos pos = new BlockPos((location & 15) | offsetX, (location >>> (4 + 4)) | offsetY, ((location >>> 4) & 15) | offsetZ); // Leaf - Micro optimizations for random tick - no redundant mask + + state.randomTick(level, pos, simpleRandom); // Leaf - Micro optimizations for random tick - no redundant cast + if (doubleTickFluids) { + final FluidState fluidState = state.getFluidState(); + if (fluidState.isRandomlyTicking()) { + fluidState.randomTick(level, pos, simpleRandom); // Leaf - Micro optimizations for random tick - no redundant cast + } + } + } + } + } +*/ + + public void randomTickChunk( + LevelChunk chunk, + long tickSpeed + ) { + long a; + if (cacheIdx == 0) { + a = cache; + cacheIdx = 1; + } else if (cacheIdx == 1) { + a = cache >>> 32; + cacheIdx = 2; + } else { + a = cache = rand.next(); + cacheIdx = 0; + } + if ((a & 0x300000L) != 0L) { + return; + } + tickSpeed = tickSpeed * 4; + long chance = chunk.leaf$randomTickChance(); + if (chance == 0L && (chance = recompute(chunk, tickSpeed)) == 0) { + return; + } + if (chance >= (a & MASK)) { + return; + } + if ((chance = recompute(chunk, tickSpeed)) == 0) { + return; + } + + long tickingCount = chunk.leaf$countTickingBlocks(); + long shouldTick = rand.next() & MASK; + int randPos = (int) ((rand.next() & Integer.MAX_VALUE) % tickingCount); + OptionalLong pos = chunk.leaf$tickingPos(randPos); + if (pos.isPresent()) { + tickPos.add(pos.getAsLong()); + } + while (shouldTick <= chance) { + randPos = (int) ((rand.next() & Integer.MAX_VALUE) % tickingCount); + pos = chunk.leaf$tickingPos(randPos); + if (pos.isPresent()) tickPos.add(pos.getAsLong()); + chance -= SCALE; + } + } + + private final static class WyRand implements BitRandomSource { + private long state; + + private static final long WY0 = 0x2d35_8dcc_aa6c_78a5L; + private static final long WY1 = 0x8bb8_4b93_962e_acc9L; + private static final int BITS = 64; + + public WyRand(long seed) { + this.state = seed; + } + + @Override + public int next(int bits) { + return (int)(this.next() >>> (BITS - bits)); + } + + @Override + public @NotNull RandomSource fork() { + return new WyRand(next()); + } + + @Override + public @NotNull PositionalRandomFactory forkPositional() { + throw new UnsupportedOperationException("forkPositional"); + } + + @Override + public void setSeed(long seed) { + this.state = seed; + } + + public int nextInt() { + return (int) (next() & Integer.MAX_VALUE); + } + + @Override + public double nextGaussian() { + throw new UnsupportedOperationException("nextGaussian"); + } + + public long next() { + long seed = this.state; + seed += WY0; + long aLow = seed & 0xFFFFFFFFL; + long aHigh = seed >>> 32; + long bLow = (seed ^ WY1) & 0xFFFFFFFFL; + long bHigh = (seed ^ WY1) >>> 32; + long loLo = aLow * bLow; + long hiLo = aHigh * bLow; + long loHi = aLow * bHigh; + long hiHi = aHigh * bHigh; + long mid1 = (loLo >>> 32) + (hiLo & 0xFFFFFFFFL) + (loHi & 0xFFFFFFFFL); + long mid2 = (hiLo >>> 32) + (loHi >>> 32) + (mid1 >>> 32); + this.state = seed; + return ((loLo & 0xFFFFFFFFL) | (mid1 << 32)) ^ hiHi + (mid2 & 0xFFFFFFFFL); + } + } +}