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..c637fada --- /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..0bf765334f20fa5a999400076797d5b1f82c7469 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(this.simpleRandom, 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..082b272556ec87a605931c6cbcc19604596930d1 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 int leaf$countTickingBlocks; ++ private int leaf$countTickingSections; ++ ++ public final long leaf$randomTickChance() { ++ return leaf$randomTickChance; ++ } ++ public final void leaf$setRandomTickChance(long chance) { ++ leaf$randomTickChance = chance; ++ } ++ public final int leaf$countTickingBlocks() { ++ return leaf$countTickingBlocks; ++ } ++ public final int leaf$countTickingSections() { ++ return leaf$countTickingSections; ++ } ++ public final void leaf$recompute() { ++ int total1 = 0; ++ int total2 = 0; ++ for (LevelChunkSection section : sections) { ++ if (section.isRandomlyTickingBlocks()) { ++ total1 += section.moonrise$getTickingBlockList().size(); ++ 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 (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; ++ } ++ 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..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..9fcf5194 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/world/RandomTickSystem.java @@ -0,0 +1,97 @@ +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 final LongArrayList tickPos = new LongArrayList(); + private static final long SCALE = 0x100000L; + private static final long CHUNK_BLOCKS = 4096L; + private static final int MASK = 0xfffff; + private static final int MASK_ONE_FOURTH = 0x300000; + + 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()); + 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 chance = (tickSpeed * tickingCount * SCALE) / CHUNK_BLOCKS; + + chunk.leaf$setRandomTickChance(chance); + return chance; + } + + public void randomTickChunk( + RandomSource randomSource, + LevelChunk chunk, + long tickSpeed + ) { + int a = randomSource.nextInt(); + if ((a & MASK_ONE_FOURTH) != 0) { + return; + } + tickSpeed = tickSpeed * 4; + + long chance = chunk.leaf$randomTickChance(); + if (chance == 0L && (chance = recompute(chunk, tickSpeed)) == 0L) { + return; + } + if (chance >= (long) (a & MASK) || (chance = recompute(chunk, tickSpeed)) == 0L) { + return; + } + int tickingCount = chunk.leaf$countTickingBlocks(); + OptionalLong pos = chunk.leaf$tickingPos(randomSource.nextInt(tickingCount)); + if (pos.isPresent()) { + tickPos.add(pos.getAsLong()); + } + + if (chance < SCALE) { + return; + } + chance -= SCALE; + long last = randomSource.nextInt() & MASK; + while (last <= chance) { + pos = chunk.leaf$tickingPos(randomSource.nextInt(tickingCount)); + if (pos.isPresent()) { + tickPos.add(pos.getAsLong()); + } + chance -= SCALE; + } + } +}