diff --git a/leaf-server/minecraft-patches/features/0193-optimize-random-tick.patch b/leaf-server/minecraft-patches/features/0193-optimize-random-tick.patch index c637fada..c338221d 100644 --- a/leaf-server/minecraft-patches/features/0193-optimize-random-tick.patch +++ b/leaf-server/minecraft-patches/features/0193-optimize-random-tick.patch @@ -39,43 +39,27 @@ index eb849c57992658005e0f514c6f7923f8ca43bebf..0bf765334f20fa5a999400076797d5b1 } diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java -index 624a177695580510c0a49d4503dee72da7fd7114..082b272556ec87a605931c6cbcc19604596930d1 100644 +index 624a177695580510c0a49d4503dee72da7fd7114..007fca408eb638726a44000b60464838c3d4a5a4 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 +@@ -151,6 +151,36 @@ 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() { ++ public final int leaf$lastTickingBlocksCount() { + return leaf$countTickingBlocks; + } -+ public final int leaf$countTickingSections() { -+ return leaf$countTickingSections; -+ } -+ public final void leaf$recompute() { -+ int total1 = 0; -+ int total2 = 0; ++ public final int leaf$tickingBlocksCount() { ++ int sum = 0; + for (LevelChunkSection section : sections) { -+ if (section.isRandomlyTickingBlocks()) { -+ total1 += section.moonrise$getTickingBlockList().size(); -+ total2++; -+ } ++ sum += section.moonrise$getTickingBlockList().size(); + } -+ leaf$countTickingBlocks = total1; -+ leaf$countTickingSections = total2; ++ leaf$countTickingBlocks = sum; ++ return sum; + } -+ public final java.util.OptionalLong leaf$tickingPos(int idx) { ++ public final java.util.OptionalLong leaf$getTickingPos(int idx) { + for (int i = 0; i < sections.length; i++) { + LevelChunkSection section = sections[i]; + var l = section.moonrise$getTickingBlockList(); 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 index 4fd417ca..8640d8f1 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/world/RandomTickSystem.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/world/RandomTickSystem.java @@ -6,92 +6,161 @@ 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.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 SCALE_HALF = 0x80000L; + private static final int BITS = 20; private static final long CHUNK_BLOCKS = 4096L; - private static final int MASK = 0xfffff; - private static final int MASK_ONE_FOURTH = 0x300000; + + private final LongArrayList queue = new LongArrayList(); + private final LongArrayList samples = new LongArrayList(); + private final LongArrayList weights = new LongArrayList(); + private long weightsSum = 0; + + private int bits = 60; + private long cacheRandom = 0L; 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) { + if (weights.isEmpty() || samples.isEmpty()) { return; } + + var random = world.simpleRandom; + int spin = random.next(BITS); + + int chosen = (weightsSum >= SCALE_HALF ? random.nextInt((int) (weightsSum / SCALE_HALF)) : 0) + + (((int) (weightsSum % SCALE_HALF) >= random.next(BITS - 1)) ? 1 : 0); + if (chosen == 0) { + return; + } + long spoke = weightsSum / chosen; + long[] weightsRaw = weights.elements(); + long[] samplesRaw = samples.elements(); + long accumulated = weightsRaw[0]; + long current = spin; + 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.push(samplesRaw[i]); + } + weightsSum = 0; + weights.clear(); + samples.clear(); + + var simpleRandom = world.simpleRandom; + int j = queue.size(); + long[] queueRaw = queue.elements(); + int k = j - 3; + // optimize getChunk for large ticking block or tick speed + for (i = 0; i < k; i += 4) { + long packed1 = queueRaw[i]; + long packed2 = queueRaw[i + 1]; + long packed3 = queueRaw[i + 2]; + long packed4 = queueRaw[i + 3]; + int x; + int z; + LevelChunk chunk1; + LevelChunk chunk2; + LevelChunk chunk3; + LevelChunk chunk4; + + x = (int) packed1; + z = (int) (packed1 >> 32); + chunk1 = world.chunkSource.getChunkAtIfLoadedImmediately(x, z); + + if (packed1 != packed2) { + x = (int) packed2; + z = (int) (packed2 >> 32); + chunk2 = world.chunkSource.getChunkAtIfLoadedImmediately(x, z); + } else { + chunk2 = chunk1; + } + if (packed2 != packed3) { + x = (int) packed3; + z = (int) (packed3 >> 32); + chunk3 = world.chunkSource.getChunkAtIfLoadedImmediately(x, z); + } else { + chunk3 = chunk2; + } + if (packed3 != packed4) { + x = (int) packed4; + z = (int) (packed4 >> 32); + chunk4 = world.chunkSource.getChunkAtIfLoadedImmediately(x, z); + } else { + chunk4 = chunk3; + } + if (chunk1 != null) { + tickBlock(world, chunk1, simpleRandom); + } + if (chunk2 != null) { + tickBlock(world, chunk2, simpleRandom); + } + if (chunk3 != null) { + tickBlock(world, chunk3, simpleRandom); + } + if (chunk4 != null) { + tickBlock(world, chunk4, simpleRandom); + } + } + for (; i < j; i++) { + long packed = queueRaw[i]; + int x = (int) packed; + int z = (int) (packed >> 32); + LevelChunk chunk = world.chunkSource.getChunkAtIfLoadedImmediately(x, z); + if (chunk != null) { + tickBlock(world, chunk, simpleRandom); + } + } + + queue.clear(); + } + + private static void tickBlock(ServerLevel world, LevelChunk chunk, RandomSource random) { + OptionalLong optionalPos = chunk.leaf$getTickingPos(random.nextInt(chunk.leaf$lastTickingBlocksCount())); + if (optionalPos.isEmpty()) { + return; + } + BlockPos pos = BlockPos.of(optionalPos.getAsLong()); BlockState state = chunk.getBlockStateFinal(pos.getX(), pos.getY(), pos.getZ()); - state.randomTick(world, pos, tickRand); + 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, tickRand); + fluidState.randomTick(world, pos, random); } } } - 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, + BitRandomSource random, LevelChunk chunk, long tickSpeed ) { - int a = randomSource.nextInt(); - if ((a & MASK_ONE_FOURTH) != 0) { - return; + if (this.bits == 60) { + this.bits = 0; + this.cacheRandom = random.nextLong(); + } else { + this.bits += 2; } - 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 (chance > last) { - pos = chunk.leaf$tickingPos(randomSource.nextInt(tickingCount)); - if (pos.isPresent()) { - tickPos.add(pos.getAsLong()); - } - chance -= SCALE; + if ((this.cacheRandom & (0b11L << bits)) == 0L && chunk.leaf$tickingBlocksCount() != 0L) { + long chance = (4 * tickSpeed * chunk.leaf$lastTickingBlocksCount() * SCALE) / CHUNK_BLOCKS; + samples.add(chunk.getPos().longKey); + weights.add(chance); + weightsSum += chance; } } }