diff --git a/leaf-server/minecraft-patches/features/0275-optimize-random-tick.patch b/leaf-server/minecraft-patches/features/0275-optimize-random-tick.patch index 4dd1a9af..53be9ce4 100644 --- a/leaf-server/minecraft-patches/features/0275-optimize-random-tick.patch +++ b/leaf-server/minecraft-patches/features/0275-optimize-random-tick.patch @@ -5,19 +5,26 @@ Subject: [PATCH] optimize random tick diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java -index 46e171ca454253c32e22c0c18587e9a7ba19f331..6f4d6e588b24490f3f543ed90cfe5a0b8eb15386 100644 +index 46e171ca454253c32e22c0c18587e9a7ba19f331..b0f4ef60832d2e301a9a31716d888638d9250c14 100644 --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java -@@ -635,6 +635,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon +@@ -634,7 +634,13 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + list.clear(); } - this.iterateTickingChunksFaster(); // Paper - chunk tick iteration optimisations -+ this.level.randomTickSystem.tick(this.level); // Leaf - optimize random tick +- this.iterateTickingChunksFaster(); // Paper - chunk tick iteration optimisations ++ // Leaf start - optimize random tick ++ if (org.dreeam.leaf.config.modules.opt.OptimizeRandomTick.enabled) { ++ this.level.randomTickSystem.tick(this.level); ++ } else { ++ this.iterateTickingChunksFaster(); // Paper - chunk tick iteration optimisations ++ } ++ // Leaf end - optimize random tick if (_boolean) { this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies); } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 27da552e2542153a58d6177f592cf30d858c41a9..a180612fea46ec9f7dcfe1781e985dd2e98ed513 100644 +index 27da552e2542153a58d6177f592cf30d858c41a9..35fb0770eb385e3837cb29711905c41b899bac8f 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -1114,6 +1114,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @@ -28,28 +35,19 @@ index 27da552e2542153a58d6177f592cf30d858c41a9..a180612fea46ec9f7dcfe1781e985dd2 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(); -@@ -1129,7 +1130,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 a3674ddb883eecb255279375a5e2eece7e016c0f..634bf0c15eb63aeb5ff06dd9b43ecbab627b0128 100644 +index a3674ddb883eecb255279375a5e2eece7e016c0f..193009af4e477660ce36edda2658f09983941592 100644 --- a/net/minecraft/world/level/chunk/LevelChunk.java +++ b/net/minecraft/world/level/chunk/LevelChunk.java -@@ -152,6 +152,48 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p +@@ -152,6 +152,61 @@ 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$tickingBlocksCount = 0; + private int leaf$firstTickingSectionIndex = -1; ++ private int leaf$mostTickingSectionIndex = -1; + public final int leaf$tickingBlocksCount() { + if (!leaf$tickingBlocksDirty) { + return leaf$tickingBlocksCount; @@ -57,12 +55,17 @@ index a3674ddb883eecb255279375a5e2eece7e016c0f..634bf0c15eb63aeb5ff06dd9b43ecbab + leaf$tickingBlocksDirty = false; + int sum = 0; + leaf$firstTickingSectionIndex = -1; ++ leaf$mostTickingSectionIndex = -1; ++ int most = 0; + for (int i = 0; i < sections.length; i++) { -+ LevelChunkSection section = sections[i]; -+ int size = section.moonrise$getTickingBlockList().size(); ++ var 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; @@ -70,16 +73,23 @@ index a3674ddb883eecb255279375a5e2eece7e016c0f..634bf0c15eb63aeb5ff06dd9b43ecbab + } + 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++) { -+ LevelChunkSection section = sections[i]; -+ var l = section.moonrise$getTickingBlockList(); -+ int size = l.size(); ++ if (i == most) { ++ continue; ++ } ++ var list = sections[i].moonrise$getTickingBlockList(); ++ int size = list.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)); ++ 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; + } @@ -91,7 +101,7 @@ index a3674ddb883eecb255279375a5e2eece7e016c0f..634bf0c15eb63aeb5ff06dd9b43ecbab public LevelChunk(Level level, ChunkPos pos) { this(level, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, null, null, null); } -@@ -416,6 +458,11 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p +@@ -416,6 +471,11 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p if (blockState == state) { return null; } else { diff --git a/leaf-server/minecraft-patches/features/0278-Paw-optimization.patch b/leaf-server/minecraft-patches/features/0278-Paw-optimization.patch index 2aa176bc..a55755fe 100644 --- a/leaf-server/minecraft-patches/features/0278-Paw-optimization.patch +++ b/leaf-server/minecraft-patches/features/0278-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 36e592ff42eba050829f9c4d055c3d49a78ba813..b32cb1c85a9f7a7a96a257c4546ee7e21cd91a6d 100644 +index b0f4ef60832d2e301a9a31716d888638d9250c14..bfce7501918a120f0fa84699ab6f70bacfe92ec9 100644 --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java @@ -623,8 +623,10 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon @@ -117,10 +117,10 @@ index 36e592ff42eba050829f9c4d055c3d49a78ba813..b32cb1c85a9f7a7a96a257c4546ee7e2 for (LevelChunk levelChunk : list) { diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index a180612fea46ec9f7dcfe1781e985dd2e98ed513..7cb8329fc787aa6a9f877afb43dc9cd655cb0e90 100644 +index 35fb0770eb385e3837cb29711905c41b899bac8f..fd6fe51ccac5163e70569484239bebeb79348501 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java -@@ -1518,13 +1518,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1517,13 +1517,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 a180612fea46ec9f7dcfe1781e985dd2e98ed513..7cb8329fc787aa6a9f877afb43dc9cd6 entity.setOldPosAndRot(); entity.tickCount++; entity.totalEntityAge++; // Paper - age-like counter for all entities -@@ -1537,13 +1531,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1536,13 +1530,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe for (Entity entity1 : entity.getPassengers()) { this.tickPassenger(entity, entity1, isActive); // Paper - EAR 2 } @@ -149,7 +149,7 @@ index a180612fea46ec9f7dcfe1781e985dd2e98ed513..7cb8329fc787aa6a9f877afb43dc9cd6 private void tickPassenger(Entity ridingEntity, Entity passengerEntity, final boolean isActive) { // Paper - EAR 2 diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index fc39af434b59054260d763b994fb50bcf92b4ff3..ddc2a7fbca414b8e3c044cfe7076d2cac182b63e 100644 +index 4dce68aafbccfeda82d0bee127a59f43502c6f70..145c5f7a69710fe636bb5bca97f5a43fb778203d 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -1166,16 +1166,6 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess 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 23967287..19e9f161 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,39 +1,70 @@ package org.dreeam.leaf.world; +import ca.spottedleaf.moonrise.common.list.ReferenceList; 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.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.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 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 long TICK_FILTER_MASK = 0b11L; + private static final long CHUNK_BLOCKS = 4096L / 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()) { + final var random = world.simpleRandom; + + final ReferenceList entityTickingChunks = world.moonrise$getEntityTickingChunks(); + final int randomTickSpeed = world.getGameRules().getInt(GameRules.RULE_RANDOMTICKING); + final LevelChunk[] raw = entityTickingChunks.getRawDataUnchecked(); + final int size = entityTickingChunks.size(); + final boolean disableIceAndSnow = world.paperConfig().environment.disableIceAndSnow; + if (randomTickSpeed <= 0) { return; } + if (!disableIceAndSnow) { + iceSnow(world, size, randomTickSpeed, random, raw); + } + final long weightsSum = fillWeight(size, random, raw, randomTickSpeed); + if (weights.isEmpty() || samples.isEmpty() || weightsSum == 0L) { + return; + } + sus(random, weightsSum); + weights.clear(); + samples.clear(); - final var random = world.simpleRandom; + final var 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; + } + if (a != null) { + tickBlock(world, a, random); + } + } + queue.clear(); + } + + private void sus(BitRandomSource random, long weightsSum) { final long chosen; if (((weightsSum % SCALE) >= boundedNextLong(random, SCALE))) { chosen = weightsSum / SCALE + 1L; @@ -44,62 +75,67 @@ public final class RandomTickSystem { return; } - final long spoke = weightsSum / chosen; - if (spoke == 0L) { - return; - } - final long[] weightsRaw = weights.elements(); final long[] samplesRaw = samples.elements(); long accumulated = weightsRaw[0]; + final long spoke = weightsSum / chosen; + if (spoke == 0L) return; long current = boundedNextLong(random, spoke); int i = 0; while (current < weightsSum) { while (accumulated < current) { - i += 1; + i++; 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++) { - final 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 long fillWeight(int size, BitRandomSource random, LevelChunk[] raw, long randomTickSpeed) { + int bits = 0; + long cacheRandom = random.nextLong(); + long weightsSum = 0L; + + for (int i = 0; i < size; i++) { + if (bits != BITS_MAX) { + bits += BITS_STEP; + } else { + bits = 0; + cacheRandom = random.nextLong(); + } + if ((cacheRandom & (TICK_FILTER_MASK << bits)) != 0L) { + continue; + } + final var 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); + weightsSum += weight; + } + } + return weightsSum; } - private static void tickBlock(ServerLevel world, LevelChunk chunk, RandomSource random) { + 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++) { + currentIceAndSnowTick -= randomTickSpeed; + if (currentIceAndSnowTick <= 0) { + currentIceAndSnowTick = random.nextInt(48 * 16); + LevelChunk chunk = raw[i]; + ChunkPos pos = chunk.getPos(); + int minBlockX = pos.getMinBlockX(); + int minBlockZ = pos.getMinBlockZ(); + world.tickPrecipitation(world.getBlockRandomPos(minBlockX, 0, minBlockZ, 15)); + } + } + } + + private static void tickBlock(ServerLevel world, LevelChunk chunk, BitRandomSource random) { int count = chunk.leaf$tickingBlocksCount(); if (count == 0) { return; @@ -121,43 +157,13 @@ public final class RandomTickSystem { } } - 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; + private static long boundedNextLong(BitRandomSource rng, long bound) { + final long m = bound - 1L; long r = rng.nextLong(); if ((bound & m) == 0L) { r &= m; } else { + //noinspection StatementWithEmptyBody for (long u = r >>> 1; u + m - (r = u % bound) < 0L; u = rng.nextLong() >>> 1)