mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-19 15:09:25 +00:00
Optimize random tick (#359)
* optimize random tick
* rebase
* fix random tick chance check
* add back zero check fix always tick on first tick
* optimize random check
* cleanup
* remove 0 to 2 random on amount chosen
* cleanup
* add queue and reuse block count
* reduce LevelChunkSection#tickingBlocks memory overhead
* Revert "reduce LevelChunkSection#tickingBlocks memory overhead"
This reverts commit 942376481b.
* fix overflow
* [ci skip] rebuild patches
---------
Co-authored-by: hayanesuru <hayanesuru@outlook.jp>
This commit is contained in:
@@ -0,0 +1,105 @@
|
|||||||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||||
|
From: hayanesuru <hayanesuru@outlook.jp>
|
||||||
|
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 c80e6669013d3cf87b51d0cb0b62fcf5466d702c..9d132c88629976cc00d470980308571ac3222284 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 - optimize 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 7cbcc2dd8533973108841d3989265a392a6f6214..bc0c80ed78bbb594cf7281e8d87f27dd06a1fcbb 100644
|
||||||
|
--- a/net/minecraft/server/level/ServerLevel.java
|
||||||
|
+++ b/net/minecraft/server/level/ServerLevel.java
|
||||||
|
@@ -1120,6 +1120,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 - optimize random tick
|
||||||
|
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();
|
||||||
|
@@ -1169,7 +1170,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 - 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 a90bf0d80ae4dac9b19b8e467b402917cc19a271..44b672671e0bea6a5f91e9b8573f9a8225a20f6a 100644
|
||||||
|
--- a/net/minecraft/world/level/chunk/LevelChunk.java
|
||||||
|
+++ b/net/minecraft/world/level/chunk/LevelChunk.java
|
||||||
|
@@ -149,6 +149,48 @@ 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$firstTickingSectionIndex = -1;
|
||||||
|
+ public final int leaf$tickingBlocksCount() {
|
||||||
|
+ if (!leaf$tickingBlocksDirty) {
|
||||||
|
+ return leaf$tickingBlocksCount;
|
||||||
|
+ }
|
||||||
|
+ leaf$tickingBlocksDirty = false;
|
||||||
|
+ int sum = 0;
|
||||||
|
+ leaf$firstTickingSectionIndex = -1;
|
||||||
|
+ for (int i = 0; i < sections.length; i++) {
|
||||||
|
+ LevelChunkSection section = sections[i];
|
||||||
|
+ int size = section.moonrise$getTickingBlockList().size();
|
||||||
|
+ if (size != 0 && leaf$firstTickingSectionIndex == -1) {
|
||||||
|
+ leaf$firstTickingSectionIndex = i;
|
||||||
|
+ }
|
||||||
|
+ sum += size;
|
||||||
|
+ }
|
||||||
|
+ leaf$tickingBlocksCount = sum;
|
||||||
|
+ return sum;
|
||||||
|
+ }
|
||||||
|
+ public final java.util.OptionalLong leaf$getTickingPos(int idx) {
|
||||||
|
+ if (leaf$firstTickingSectionIndex != -1) {
|
||||||
|
+ for (int i = leaf$firstTickingSectionIndex; 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;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ leaf$tickingBlocksDirty = true;
|
||||||
|
+ return java.util.OptionalLong.empty();
|
||||||
|
+ }
|
||||||
|
+ // Leaf end - optimize random tick
|
||||||
|
public LevelChunk(Level level, ChunkPos pos) {
|
||||||
|
this(level, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, null, null, null);
|
||||||
|
}
|
||||||
|
@@ -417,6 +459,11 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
|
||||||
|
if (blockState == state) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
+ // Leaf start - optimize random tick
|
||||||
|
+ if (blockState.isRandomlyTicking() != state.isRandomlyTicking()) {
|
||||||
|
+ leaf$tickingBlocksDirty = true;
|
||||||
|
+ }
|
||||||
|
+ // Leaf end - optimize random tick
|
||||||
|
Block block = state.getBlock();
|
||||||
|
this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING).update(i, y, i2, state);
|
||||||
|
this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES).update(i, y, i2, state);
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
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 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 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()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final var random = world.simpleRandom;
|
||||||
|
final long chosen;
|
||||||
|
if (((weightsSum % SCALE) >= boundedNextLong(random, SCALE))) {
|
||||||
|
chosen = weightsSum / SCALE + 1L;
|
||||||
|
} else {
|
||||||
|
chosen = weightsSum / SCALE;
|
||||||
|
}
|
||||||
|
if (chosen == 0L) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final long spoke = weightsSum / chosen;
|
||||||
|
if (spoke == 0L) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final long[] weightsRaw = weights.elements();
|
||||||
|
final long[] samplesRaw = samples.elements();
|
||||||
|
|
||||||
|
long accumulated = weightsRaw[0];
|
||||||
|
long current = boundedNextLong(random, spoke);
|
||||||
|
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.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++) {
|
||||||
|
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 static void tickBlock(ServerLevel world, LevelChunk chunk, RandomSource random) {
|
||||||
|
OptionalLong optionalPos = chunk.leaf$getTickingPos(random.nextInt(chunk.leaf$tickingBlocksCount()));
|
||||||
|
if (optionalPos.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BlockPos pos = BlockPos.of(optionalPos.getAsLong());
|
||||||
|
BlockState state = chunk.getBlockStateFinal(pos.getX(), pos.getY(), pos.getZ());
|
||||||
|
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, random);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
long r = rng.nextLong();
|
||||||
|
if ((bound & m) == 0L) {
|
||||||
|
r &= m;
|
||||||
|
} else {
|
||||||
|
for (long u = r >>> 1;
|
||||||
|
u + m - (r = u % bound) < 0L;
|
||||||
|
u = rng.nextLong() >>> 1)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user