Files
Shiroha/shiroha-server/minecraft-patches/features/0086-Lithium-optimized-non-poi-block-searching.patch
T
NanaChiyo0721 1bb895c165
Shiroha CI / build (push) Canceled after 0s
Shiroha CI / Event File (push) Canceled after 0s
Fix incorrect ticket lock size computing
Yeah this would fully solve the issue of chunk unloading race condition

The same fix was already initially added at "Force clamp grid-exponent to 1~6"
2026-08-09 14:57:32 +08:00

280 lines
17 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: NanaChiyo0721 <nanachiyo0721@163.com>
Date: Sat, 25 Jul 2026 21:16:19 +0800
Subject: [PATCH] Lithium optimized non poi block searching
diff --git a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
index c6e9b155c2341c13d569f28f54f0c1d2d7313676..5b186bad86ae1465f2b2f9124ded943ad83fabc2 100644
--- a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
+++ b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
@@ -5,7 +5,7 @@ import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.PathfinderMob;
import net.minecraft.world.level.LevelReader;
-public abstract class MoveToBlockGoal extends Goal {
+public abstract class MoveToBlockGoal extends Goal implements net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.LithiumMoveToBlockGoal { // Shiroha - Lithium Optimizes move to block goal
private static final int GIVE_UP_TICKS = 1200;
private static final int STAY_TICKS = 1200;
private static final int INTERVAL_TICKS = 200;
@@ -133,4 +133,167 @@ public abstract class MoveToBlockGoal extends Goal {
}
protected abstract boolean isValidTarget(LevelReader level, BlockPos pos);
+ // Shiroha start - Lithium Optimizes move to block goal
+ /**
+ * Finds the nearest block matching the predicates.
+ * <p>
+ * Side effect: The matching block position is stored in the blockPos field.
+ *
+ * @return Whether a matching block was found.
+ */
+ @Override
+ public boolean lithium$findNearestBlock(java.util.function.Predicate<net.minecraft.world.level.block.state.BlockState> requiredBlock, java.util.function.BiPredicate<net.minecraft.world.level.chunk.ChunkAccess,
+ BlockPos.MutableBlockPos> lithium$isValidTarget, final boolean shouldChunkLoad) {
+ //Center of the search starts 1 block below the mob's block position
+ BlockPos center = this.mob.blockPosition().offset(0,-1,0);
+
+ //Range is +-(searchRange - 1), +-verticalSearchRange, +-(searchRange - 1)
+ //Cache ChunkAccesses - getting them is surprisingly expensive - and track whether subchunks have the block
+ final LevelReader levelReader = this.mob.level();
+ net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.CheckAndCacheBlockChecker checker = new net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.CheckAndCacheBlockChecker(center,
+ this.searchRange-1, this.verticalSearchRange,
+ levelReader, requiredBlock, shouldChunkLoad);
+ it.unimi.dsi.fastutil.longs.LongArrayList sortedChunksMaybeWithBlock = new it.unimi.dsi.fastutil.longs.LongArrayList(checker.getChunkSize());
+ checker.initializeChunks(sortedChunksMaybeWithBlock::addLast);
+
+ if (checker.shouldStop()) {
+ return false; //No chunks with the target block - return early
+ }
+
+ final int minY = net.caffeinemc.mods.lithium.common.util.Pos.BlockCoord.getMinY(levelReader);
+ final int maxY = net.caffeinemc.mods.lithium.common.util.Pos.BlockCoord.getMaxYInclusive(levelReader);
+
+ // Prefer chunk aware search because it also cuts iterations inside "empty" chunk sections
+ if(!checker.hasUnloadedPossibleChunks()){
+ return this.lithium$chunkAwareSearch(center, lithium$isValidTarget, checker, sortedChunksMaybeWithBlock, minY, maxY);
+ }
+
+ // Use vanilla search because unordered search may observably alter chunk-loading behavior
+ return this.lithium$vanillaOrderSearch(center, lithium$isValidTarget, checker, minY, maxY);
+ }
+
+ private boolean lithium$vanillaOrderSearch(BlockPos center,
+ java.util.function.BiPredicate<net.minecraft.world.level.chunk.ChunkAccess, BlockPos.MutableBlockPos> lithium$isValidTarget,
+ net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.CheckAndCacheBlockChecker checker, final int minY, final int maxY) {
+ BlockPos.MutableBlockPos currentPos = new BlockPos.MutableBlockPos();
+ final int centerY = center.getY();
+
+ for (int layer = this.verticalSearchStart; layer <= this.verticalSearchRange; layer = layer > 0 ? -layer : 1 - layer) {
+ final int y = centerY + layer;
+
+ // Layer outside of build limit - skip
+ // Note: this is likely to be hit because farms where this lags tend to be built at world floor
+ if (y < minY || y > maxY) {
+ continue;
+ }
+
+ for (int ring = 0; ring < this.searchRange; ring++) {
+ for (int dX = 0; dX <= ring; dX = dX > 0 ? -dX : 1 - dX) {
+ for (int dZ = dX < ring && dX > -ring ? ring : 0; dZ <= ring; dZ = dZ > 0 ? -dZ : 1 - dZ) {
+ currentPos.setWithOffset(center, dX, layer, dZ);
+ if (this.mob.isWithinHome(currentPos) && checker.checkPosition(currentPos)) {
+ // ChunkAccess is always loaded at this point
+ net.minecraft.world.level.chunk.ChunkAccess chunkAccess = checker.getCachedChunkAccess(currentPos);
+ if (lithium$isValidTarget.test(chunkAccess, currentPos)){
+ this.blockPos = currentPos;
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private boolean lithium$chunkAwareSearch(BlockPos center,
+ java.util.function.BiPredicate<net.minecraft.world.level.chunk.ChunkAccess, BlockPos.MutableBlockPos> lithium$isValidTarget,
+ net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.CheckAndCacheBlockChecker checker, it.unimi.dsi.fastutil.longs.LongArrayList sortedChunksMaybeWithBlock,
+ final int minY, final int maxY) {
+ // Sort chunks by lowest sort order - has the earliest searched position
+ // Note: In this search order, the closest point normally is also the closest point in the search
+ sortedChunksMaybeWithBlock.sort((chunkLong0, chunkLong1) ->
+ net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.NonPOISearchDistances.MoveToBlockGoalDistances.getMinimumSortOrderOfChunk(center, chunkLong0)
+ - net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.NonPOISearchDistances.MoveToBlockGoalDistances.getMinimumSortOrderOfChunk(center, chunkLong1)
+ );
+
+ java.util.function.Predicate<net.minecraft.world.level.block.state.BlockState> requiredBlock = checker.blockStatePredicate;
+ final int minSectionY = checker.minSectionY;
+
+ BlockPos.MutableBlockPos foundPos = new BlockPos.MutableBlockPos();
+ BlockPos.MutableBlockPos currentPos = new BlockPos.MutableBlockPos();
+
+ // Same layer order as vanilla - saves iterations if targets are found in the first layer
+ for (int layer = this.verticalSearchStart; layer <= this.verticalSearchRange; layer = layer > 0 ? -layer : 1 - layer) {
+ final int y = center.getY() + layer;
+
+ // Layer outside of build limit - skip
+ // Note: this is likely to be hit because farms where this lags tend to be built at world floor
+ if (y < minY || y > maxY) {
+ continue;
+ }
+
+ final int chunkY = net.minecraft.core.SectionPos.blockToSectionCoord(y);
+ final int ySectionIndex = chunkY - minSectionY;
+
+ int closestFound = Integer.MAX_VALUE;
+ int ringMax = this.searchRange - 1;
+
+ // Iterate through slices of chunks that may have the target blockState
+ for (long chunkPos: sortedChunksMaybeWithBlock) {
+ final int chunkX = net.minecraft.world.level.ChunkPos.getX(chunkPos);
+ final int chunkZ = net.minecraft.world.level.ChunkPos.getZ(chunkPos);
+
+ // Break since no subsequent chunks can be closer
+ if (closestFound < net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.NonPOISearchDistances.MoveToBlockGoalDistances.getMinimumSortOrderOfChunk(center, chunkX, chunkZ)) {
+ break;
+ }
+
+ // Skip if the current subchunk does not have the block
+ if (!checker.checkCachedSection(chunkX, chunkY, chunkZ)) {
+ continue;
+ }
+
+ net.minecraft.world.level.chunk.ChunkAccess chunkAccess = checker.getCachedChunkAccess(chunkPos);
+ // If ChunkSection may have close enough targets, iterate layer in Paletted Container (x then z) order
+ final int chunkBlockX = net.minecraft.core.SectionPos.sectionToBlockCoord(chunkX);
+ int xMin = Math.max(center.getX() - ringMax, chunkBlockX);
+ int xMax = Math.min(center.getX() + ringMax, chunkBlockX + 15);
+ final int chunkBlockZ = net.minecraft.core.SectionPos.sectionToBlockCoord(chunkZ);
+ int zMin = Math.max(center.getZ() - ringMax, chunkBlockZ);
+ int zMax = Math.min(center.getZ() + ringMax, chunkBlockZ + 15);
+ net.minecraft.world.level.chunk.LevelChunkSection levelChunkSection = chunkAccess.getSections()[ySectionIndex];
+ for (int z = zMin; z <= zMax; z++) {
+ for (int x = xMin; x <= xMax; x++) {
+ int dX = x - center.getX();
+ int dZ = z - center.getZ();
+ int ring = net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.NonPOISearchDistances.MoveToBlockGoalDistances.getRing(dX, dZ);
+ int currentDistance = net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.NonPOISearchDistances.MoveToBlockGoalDistances.getVanillaSortOrderInt(ring, dX, dZ);
+ if (currentDistance < closestFound
+ && this.mob.isWithinHome(currentPos.set(x, y, z))
+ && requiredBlock.test(levelChunkSection.getBlockState(x & 15, y & 15, z & 15))
+ && lithium$isValidTarget.test(chunkAccess, currentPos)) {
+ // Constrain search size when we find a valid target
+ ringMax = ring;
+ xMin = Math.max(center.getX() - ringMax, chunkBlockX);
+ xMax = Math.min(center.getX() + ringMax, chunkBlockX + 15);
+ zMax = Math.min(center.getZ() + ringMax, chunkBlockZ + 15);
+ foundPos.set(x, y, z);
+ closestFound = currentDistance;
+ }
+ }
+ }
+ }
+
+ if (closestFound < Integer.MAX_VALUE) {
+ // Vanilla uses the mutable pos, no need to create immutable copy
+ this.blockPos = foundPos;
+ return true;
+ }
+ }
+
+ return false;
+ }
+ // Shiroha end - Lithium Optimizes move to block goal
}
diff --git a/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java b/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java
index ff0f99d4b560a1c0721885b0eba5bd5ab2087bd7..90beeee7b74324f81690776546713b4f04887f75 100644
--- a/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java
+++ b/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java
@@ -25,6 +25,19 @@ public class RemoveBlockGoal extends MoveToBlockGoal {
private final Mob removerMob;
private int ticksSinceReachedGoal;
private static final int WAIT_AFTER_BLOCK_FOUND = 20;
+ // Shiroha start - Lithium Optimize remove block goal
+ private static final java.util.function.BiPredicate<ChunkAccess, BlockPos.MutableBlockPos> IS_VALID_TARGET_ABOVE_BIPREDICATE =
+ RemoveBlockGoal::lithium$isValidTargetAbove;
+ //Split check condition in order to use maybeHas
+ private boolean lithium$isValidTargetBlock(net.minecraft.world.level.block.state.BlockState blockState){
+ return blockState.is(this.blockToRemove);
+ }
+
+ private static boolean lithium$isValidTargetAbove(ChunkAccess chunkAccess, BlockPos.MutableBlockPos mutable) {
+ return chunkAccess.getBlockState(mutable.move(0, 1, 0)).isAir()
+ && chunkAccess.getBlockState(mutable.move(0, 1, 0)).isAir();
+ }
+ // Shiroha end - Lithium Optimize remove block goal
public RemoveBlockGoal(final Block blockToRemove, final PathfinderMob mob, final double speedModifier, final int verticalSearchRange) {
super(mob, speedModifier, 24, verticalSearchRange);
@@ -39,7 +52,7 @@ public class RemoveBlockGoal extends MoveToBlockGoal {
} else if (this.nextStartTick > 0) {
this.nextStartTick--;
return false;
- } else if (this.findNearestBlock()) {
+ } else if (((net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.LithiumMoveToBlockGoal) this).lithium$findNearestBlock(this::lithium$isValidTargetBlock, IS_VALID_TARGET_ABOVE_BIPREDICATE, false)) { // Shiroha - Lithium Optimize remove block goal
this.nextStartTick = reducedTickDelay(20);
return true;
} else {
diff --git a/net/minecraft/world/entity/ai/sensing/HoglinSpecificSensor.java b/net/minecraft/world/entity/ai/sensing/HoglinSpecificSensor.java
index 7022679c84e184999b416e06d1af2cc2500cb12b..ac8f4acc2aaee8ca9f7598d842ecf29f2ad73be3 100644
--- a/net/minecraft/world/entity/ai/sensing/HoglinSpecificSensor.java
+++ b/net/minecraft/world/entity/ai/sensing/HoglinSpecificSensor.java
@@ -16,6 +16,14 @@ import net.minecraft.world.entity.monster.hoglin.Hoglin;
import net.minecraft.world.entity.monster.piglin.Piglin;
public class HoglinSpecificSensor extends Sensor<Hoglin> {
+ // Shiroha start - Lithium Optimizes Hoglin repellent search.
+ private static final java.util.function.Predicate<net.minecraft.world.level.block.state.BlockState> IS_VALID_REPELLENT_PREDICATE =
+ HoglinSpecificSensor::lithium$isValidRepellent;
+
+ private static boolean lithium$isValidRepellent(net.minecraft.world.level.block.state.BlockState blockState) {
+ return blockState.is(BlockTags.HOGLIN_REPELLENTS);
+ }
+ // Shiroha end - Lithium Optimizes Hoglin repellent search.
@Override
public Set<MemoryModuleType<?>> requires() {
return ImmutableSet.of(
@@ -30,8 +38,8 @@ public class HoglinSpecificSensor extends Sensor<Hoglin> {
@Override
protected void doTick(final ServerLevel level, final Hoglin body) {
- Brain<?> brain = body.getBrain();
- brain.setMemory(MemoryModuleType.NEAREST_REPELLENT, this.findNearestRepellent(level, body));
+ Brain<?> brain = body.getBrain();;
+ brain.setMemory(MemoryModuleType.NEAREST_REPELLENT, net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.CommonBlockSearchesCheckAndCache.blockPosFindClosestMatch(level, body, 8, 4, IS_VALID_REPELLENT_PREDICATE, true)); // Shiroha - Lithium Optimizes Hoglin repellent search.
Optional<Piglin> adultPiglin = Optional.empty();
int adultPiglinCount = 0;
List<Hoglin> adultHoglins = Lists.newArrayList();
diff --git a/net/minecraft/world/entity/ai/sensing/PiglinSpecificSensor.java b/net/minecraft/world/entity/ai/sensing/PiglinSpecificSensor.java
index 060a53da799a3036a343cc6f1d73e3e5ca4b7af9..7e5accce5a9183165e7604ca5e144e600b6f503d 100644
--- a/net/minecraft/world/entity/ai/sensing/PiglinSpecificSensor.java
+++ b/net/minecraft/world/entity/ai/sensing/PiglinSpecificSensor.java
@@ -26,6 +26,15 @@ import net.minecraft.world.level.block.CampfireBlock;
import net.minecraft.world.level.block.state.BlockState;
public class PiglinSpecificSensor extends Sensor<LivingEntity> {
+ // Shiroha start - Lithium Optimizes Piglin repellent search.
+ private static final java.util.function.Predicate<BlockState> IS_VALID_REPELLENT_PREDICATE =
+ PiglinSpecificSensor::lithium$isValidRepellent;
+ private static boolean lithium$isValidRepellent(BlockState blockState){
+ final boolean isPiglinRepellent = blockState.is(BlockTags.PIGLIN_REPELLENTS);
+ return isPiglinRepellent && blockState.is(Blocks.SOUL_CAMPFIRE) ?
+ CampfireBlock.isLitCampfire(blockState) : isPiglinRepellent;
+ }
+ // Shiroha end - Lithium Optimizes Piglin repellent search.
@Override
public Set<MemoryModuleType<?>> requires() {
return ImmutableSet.of(
@@ -47,7 +56,7 @@ public class PiglinSpecificSensor extends Sensor<LivingEntity> {
@Override
protected void doTick(final ServerLevel level, final LivingEntity body) {
Brain<?> brain = body.getBrain();
- brain.setMemory(MemoryModuleType.NEAREST_REPELLENT, findNearestRepellent(level, body));
+ brain.setMemory(MemoryModuleType.NEAREST_REPELLENT, net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.CommonBlockSearchesCheckAndCache.blockPosFindClosestMatch(level, body, 8, 4, IS_VALID_REPELLENT_PREDICATE, true)); // Shiroha - Lithium Optimizes Piglin repellent search.
Optional<Mob> nemesis = Optional.empty();
Optional<Hoglin> huntableHoglin = Optional.empty();
Optional<Hoglin> babyHoglin = Optional.empty();