From bf9486f0f096dc56f82943cf6d1c169d9d13cb54 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Fri, 27 Jun 2025 01:12:24 +0900 Subject: [PATCH] remove hash lookup in optimize random tick --- .../features/0270-optimize-random-tick.patch | 62 +------- .../dreeam/leaf/world/RandomTickSystem.java | 133 +++++++++++------- 2 files changed, 92 insertions(+), 103 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0270-optimize-random-tick.patch b/leaf-server/minecraft-patches/features/0270-optimize-random-tick.patch index 73b08be4..25bad716 100644 --- a/leaf-server/minecraft-patches/features/0270-optimize-random-tick.patch +++ b/leaf-server/minecraft-patches/features/0270-optimize-random-tick.patch @@ -36,72 +36,22 @@ index 27da552e2542153a58d6177f592cf30d858c41a9..35fb0770eb385e3837cb29711905c41b final net.minecraft.world.level.levelgen.BitRandomSource simpleRandom = this.simpleRandom; // Paper - optimise random ticking // Leaf - Faster random generator - upcasting ChunkPos pos = chunk.getPos(); diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java -index a3674ddb883eecb255279375a5e2eece7e016c0f..9249041fb249539b8dffbc0d4d338dc79941650f 100644 +index a3674ddb883eecb255279375a5e2eece7e016c0f..963a882af639f1f69698bafad1d70eda6d22b846 100644 --- a/net/minecraft/world/level/chunk/LevelChunk.java +++ b/net/minecraft/world/level/chunk/LevelChunk.java -@@ -152,6 +152,61 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p +@@ -152,6 +152,11 @@ 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 = 0; -+ private int leaf$firstTickingSectionIndex = -1; -+ private int leaf$mostTickingSectionIndex = -1; -+ public final int leaf$tickingBlocksCount() { -+ if (!leaf$tickingBlocksDirty) { -+ return leaf$tickingBlocksCount; -+ } -+ leaf$tickingBlocksDirty = false; -+ int sum = 0; -+ leaf$firstTickingSectionIndex = -1; -+ leaf$mostTickingSectionIndex = -1; -+ int most = 0; -+ for (int i = 0; i < sections.length; i++) { -+ ca.spottedleaf.moonrise.common.list.ShortList list = sections[i].moonrise$getTickingBlockList(); -+ int size = list.size(); -+ if (size != 0 && leaf$firstTickingSectionIndex == -1) { -+ leaf$firstTickingSectionIndex = i; -+ } -+ if (size > most) { -+ leaf$mostTickingSectionIndex = i; -+ } -+ sum += size; -+ } -+ leaf$tickingBlocksCount = sum; -+ return sum; -+ } -+ public final java.util.OptionalLong leaf$getTickingPos(int idx) { -+ if (leaf$firstTickingSectionIndex != -1) { -+ int most = leaf$mostTickingSectionIndex; -+ var mostList = sections[most].moonrise$getTickingBlockList(); -+ int mostSectionSize = mostList.size(); -+ if (idx < mostSectionSize) { -+ short loc = mostList.getRaw(idx); -+ return java.util.OptionalLong.of(BlockPos.asLong((loc & 15) | (locX << 4), (loc >>> 8) | ((getMinSectionY() + most) << 4), ((loc >>> 4) & 15) | (locZ << 4))); -+ } -+ idx -= mostSectionSize; -+ for (int i = leaf$firstTickingSectionIndex; i < sections.length; i++) { -+ if (i == most) { -+ continue; -+ } -+ var list = sections[i].moonrise$getTickingBlockList(); -+ int size = list.size(); -+ if (idx < size) { -+ short loc = list.getRaw(idx); -+ return java.util.OptionalLong.of(BlockPos.asLong((loc & 15) | (locX << 4), (loc >>> 8) | ((getMinSectionY() + i) << 4), ((loc >>> 4) & 15) | (locZ << 4))); -+ } -+ idx -= size; -+ } -+ } -+ leaf$tickingBlocksDirty = true; -+ return java.util.OptionalLong.empty(); -+ } ++ public boolean leaf$tickingBlocksDirty = true; ++ public short[] leaf$tickingBlocksCount = {}; ++ public short[] leaf$tickingIdx = {}; + // Leaf end - optimize random tick public LevelChunk(Level level, ChunkPos pos) { this(level, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, null, null, null); } -@@ -416,6 +471,11 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p +@@ -416,6 +421,11 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p if (blockState == state) { return null; } else { 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 b9a6420b..31a25405 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 @@ -1,19 +1,20 @@ package org.dreeam.leaf.world; -import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable; import ca.spottedleaf.moonrise.common.list.ReferenceList; +import ca.spottedleaf.moonrise.common.list.ShortList; +import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.shorts.ShortArrayList; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.levelgen.BitRandomSource; 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 TICK_FILTER_MASK = 0b11L; @@ -21,13 +22,18 @@ public final class RandomTickSystem { 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 final IntArrayList queue = new IntArrayList(); + private final ShortArrayList yList = new ShortArrayList(); + private final IntArrayList xzList = new IntArrayList(); + private final LongArrayList weightList = new LongArrayList(); public void tick(ServerLevel world) { - final BitRandomSource random = world.simpleRandom; + queue.clear(); + yList.clear(); + xzList.clear(); + weightList.clear(); + final BitRandomSource random = world.simpleRandom; final ReferenceList entityTickingChunks = world.moonrise$getEntityTickingChunks(); final int randomTickSpeed = world.getGameRules().getInt(GameRules.RULE_RANDOMTICKING); final LevelChunk[] raw = entityTickingChunks.getRawDataUnchecked(); @@ -39,33 +45,27 @@ public final class RandomTickSystem { if (!disableIceAndSnow) { iceSnow(world, size, randomTickSpeed, random, raw); } - final long weightsSum = fillWeight(size, random, raw, randomTickSpeed); - if (weights.isEmpty() || samples.isEmpty() || weightsSum == 0L) { + final long weightsSum = collectTickingChunks(size, random, raw, randomTickSpeed); + if (weightList.isEmpty() || yList.isEmpty() || weightsSum == 0L) { return; } - sus(random, weightsSum); - weights.clear(); - samples.clear(); + sampling(random, weightsSum); - final ConcurrentLong2ReferenceChainedHashTable fullChunks = world.chunkSource.fullChunks; - final long[] q = queue.elements(); - final int l = queue.size(); - LevelChunk a = null; - long b = 0L; - for (int k = 0; k < l; k++) { - final long pos = q[k]; - if (a == null || b != pos) { - a = fullChunks.get(pos); - b = pos; - } + final int[] q = queue.elements(); + final int qs = queue.size(); + final short[] yl = yList.elements(); + final int[] xzl = xzList.elements(); + final int minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(world); + for (int k = 0; k < qs; k++) { + final int index = q[k]; + final LevelChunk a = raw[xzl[index]]; if (a != null) { - tickBlock(world, a, random); + tickBlock(world, a, yl[index], random, minSection); } } - queue.clear(); } - private void sus(BitRandomSource random, long weightsSum) { + private void sampling(BitRandomSource random, long weightsSum) { final long chosen; if (((weightsSum % SCALE) >= boundedNextLong(random, SCALE))) { chosen = weightsSum / SCALE + 1L; @@ -76,9 +76,7 @@ public final class RandomTickSystem { return; } - final long[] weightsRaw = weights.elements(); - final long[] samplesRaw = samples.elements(); - + final long[] weightsRaw = weightList.elements(); long accumulated = weightsRaw[0]; final long spoke = weightsSum / chosen; if (spoke == 0L) return; @@ -89,12 +87,12 @@ public final class RandomTickSystem { i++; accumulated += weightsRaw[i]; } - queue.add(samplesRaw[i]); + queue.add(i); current += spoke; } } - private long fillWeight(int size, BitRandomSource random, LevelChunk[] raw, long randomTickSpeed) { + private long collectTickingChunks(int size, BitRandomSource random, LevelChunk[] raw, long randomTickSpeed) { int bits = 0; long cacheRandom = random.nextLong(); long weightsSum = 0L; @@ -110,17 +108,46 @@ public final class RandomTickSystem { continue; } final LevelChunk chunk = raw[i]; - final long count = chunk.leaf$tickingBlocksCount(); - if (count != 0L) { - long weight = (randomTickSpeed * count * SCALE) / CHUNK_BLOCKS; - samples.add(chunk.locX & 4294967295L | (chunk.locZ & 4294967295L) << 32); - weights.add(weight); + if (chunk.leaf$tickingBlocksDirty) { + populateChunkTickingCount(chunk); + } + short[] count = chunk.leaf$tickingBlocksCount; + short[] idx = chunk.leaf$tickingIdx; + for (int j = 0; j < count.length; j++) { + long weight = (randomTickSpeed * ((long) count[j]) * SCALE) / CHUNK_BLOCKS; + yList.add(idx[j]); + xzList.add(i); + weightList.add(weight); weightsSum += weight; } } return weightsSum; } + private static void populateChunkTickingCount(LevelChunk chunk) { + chunk.leaf$tickingBlocksDirty = false; + int sum = 0; + for (LevelChunkSection section : chunk.getSections()) { + sum += (section.moonrise$getTickingBlockList().size() == 0) ? 0 : 1; + } + if (chunk.leaf$tickingBlocksCount.length != sum) { + chunk.leaf$tickingBlocksCount = new short[sum]; + chunk.leaf$tickingIdx = new short[sum]; + } + short[] count = chunk.leaf$tickingBlocksCount; + short[] idx = chunk.leaf$tickingIdx; + int k = 0; + for (int j = 0, sectionsLength = chunk.getSections().length; j < sectionsLength; j++) { + LevelChunkSection section = chunk.getSections()[j]; + int n = (short) section.moonrise$getTickingBlockList().size(); + if (n != 0) { + count[k] = (short) n; + idx[k] = (short) j; + k++; + } + } + } + private static void iceSnow(ServerLevel world, int size, int randomTickSpeed, BitRandomSource random, LevelChunk[] raw) { int currentIceAndSnowTick = random.nextInt(48 * 16); for (int i = 0; i < size; i++) { @@ -136,17 +163,14 @@ public final class RandomTickSystem { } } - private static void tickBlock(ServerLevel world, LevelChunk chunk, BitRandomSource random) { - int count = chunk.leaf$tickingBlocksCount(); - if (count == 0) { - return; - } - OptionalLong optionalPos = chunk.leaf$getTickingPos(random.nextInt(count)); - if (optionalPos.isEmpty()) { - return; - } - BlockPos pos = BlockPos.of(optionalPos.getAsLong()); - BlockState state = chunk.getBlockStateFinal(pos.getX(), pos.getY(), pos.getZ()); + private static void tickBlock(ServerLevel world, LevelChunk chunk, int sectionIdx, BitRandomSource random, int minSection) { + LevelChunkSection section = chunk.getSection(sectionIdx); + ShortList list = section.moonrise$getTickingBlockList(); + int sz = list.size(); + if (sz == 0) return; + short location = list.getRaw(boundedNextInt(random, sz)); + BlockState state = section.states.get(location); + final BlockPos pos = new BlockPos((location & 15) | chunk.locX << 4, (location >>> (4 + 4)) | (sectionIdx + minSection) << 4, ((location >>> 4) & 15) | chunk.locZ << 4); state.randomTick(world, pos, random); final boolean doubleTickFluids = !ca.spottedleaf.moonrise.common.PlatformHooks.get().configFixMC224294(); @@ -172,4 +196,19 @@ public final class RandomTickSystem { } return r; } + + private static int boundedNextInt(BitRandomSource rng, int bound) { + final int m = bound - 1; + int r = rng.nextInt(); + if ((bound & m) == 0) { + r &= m; + } else { + //noinspection StatementWithEmptyBody + for (int u = r >>> 1; + u + m - (r = u % bound) < 0; + u = rng.nextInt() >>> 1) + ; + } + return r; + } }