update some optimizations from upstream

This commit is contained in:
Helvetica Volubi
2026-07-28 15:37:41 +08:00
parent f517cc0b8a
commit eea830188f
13 changed files with 1365 additions and 0 deletions
@@ -0,0 +1,23 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Tue, 28 Jul 2026 15:08:10 +0800
Subject: [PATCH] Anonymous Object: Fix wrong variable passing in
RegionizedWorldData
As requested by the original author, this patch will be anonymous and hide the original commit address.
This patch is used with permission from the original author.
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/io/papermc/paper/threadedregions/RegionizedWorldData.java b/io/papermc/paper/threadedregions/RegionizedWorldData.java
index 3a50e625160d0dad6bd8fa534b719087dae7881d..20439c632044ae9b4358bfba309b378b3de98601 100644
--- a/io/papermc/paper/threadedregions/RegionizedWorldData.java
+++ b/io/papermc/paper/threadedregions/RegionizedWorldData.java
@@ -841,7 +841,7 @@ public final class RegionizedWorldData {
}
public void setTickingBlockEntities(final boolean to) {
- this.tickingBlockEntities = true;
+ this.tickingBlockEntities = to; // Anonymous - Fix wrong variable passing in RegionizedWorldData
}
public List<TickingBlockEntity> getBlockEntityTickers() {
@@ -0,0 +1,25 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Tue, 28 Jul 2026 15:08:28 +0800
Subject: [PATCH] Anonymous Object: Fix wrong variable passing in
ThreadedRegionizer
As requested by the original author, this patch will be anonymous and hide the original commit address.
This patch is used with permission from the original author.
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/io/papermc/paper/threadedregions/ThreadedRegionizer.java b/io/papermc/paper/threadedregions/ThreadedRegionizer.java
index a346b96500ae859e9469c80e2ff6b39c4d3d0b52..73e0aeb5a5e9bdd1f275dd64d1fffa32c389052d 100644
--- a/io/papermc/paper/threadedregions/ThreadedRegionizer.java
+++ b/io/papermc/paper/threadedregions/ThreadedRegionizer.java
@@ -755,8 +755,8 @@ public final class ThreadedRegionizer<R extends ThreadedRegionizer.ThreadedRegio
final int x1 = CoordinateUtils.getChunkX(k1);
final int x2 = CoordinateUtils.getChunkX(k2);
- final int z1 = CoordinateUtils.getChunkZ(x1);
- final int z2 = CoordinateUtils.getChunkZ(x2);
+ final int z1 = CoordinateUtils.getChunkZ(k1); // Anonymous - Fix wrong variable passing in ThreadedRegionizer
+ final int z2 = CoordinateUtils.getChunkZ(k2); // Anonymous - Fix wrong variable passing in ThreadedRegionizer
final int zCompare = Integer.compare(z1, z2);
if (zCompare != 0) {
@@ -0,0 +1,33 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Tue, 28 Jul 2026 15:08:42 +0800
Subject: [PATCH] Anonymous Object: Fix wrong ticket update determination in
RegionizedTaskQueue
As requested by the original author, this patch will be anonymous and hide the original commit address.
This patch is used with permission from the original author.
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/io/papermc/paper/threadedregions/RegionizedTaskQueue.java b/io/papermc/paper/threadedregions/RegionizedTaskQueue.java
index b264dbd13a392cde3f24ff75c0034985fda68ccc..fe0a6bd90cba4869a0ee11723832f2dee5039f62 100644
--- a/io/papermc/paper/threadedregions/RegionizedTaskQueue.java
+++ b/io/papermc/paper/threadedregions/RegionizedTaskQueue.java
@@ -289,14 +289,17 @@ public final class RegionizedTaskQueue {
boolean executeTickTasks = allowedTickTasks > 0;
boolean executeChunkTasks = allowedChunkTasks > 0;
boolean executeGlobalTasks = true;
+ boolean processedChunkTask = false; // Anonymous - Fix wrong ticket update determination in RegionizedTaskQueue
do {
executeTickTasks = executeTickTasks && allowedTickTasks-- > 0 && tickTaskQueue.executeTask();
executeChunkTasks = executeChunkTasks && allowedChunkTasks-- > 0 && chunkTaskQueue.executeTask();
executeGlobalTasks = executeGlobalTasks && this.worldRegionTaskData.executeGlobalChunkTask();
+
+ processedChunkTask |= (executeChunkTasks | executeGlobalTasks); // Anonymous - Fix wrong ticket update determination in RegionizedTaskQueue
} while (executeTickTasks | executeChunkTasks | executeGlobalTasks);
- if (allowedChunkTasks > 0) {
+ if (processedChunkTask) { // Anonymous - Fix wrong ticket update determination in RegionizedTaskQueue
// if we executed chunk tasks, we should try to process ticket updates for full status changes
this.worldRegionTaskData.world.moonrise$getChunkTaskScheduler().chunkHolderManager.processTicketUpdates();
}
@@ -0,0 +1,23 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Tue, 28 Jul 2026 15:09:06 +0800
Subject: [PATCH] Anonymous Object: Lithium optimized collections for
WeightedList
As requested by the original author, this patch will be anonymous and hide the original commit address.
This patch is used with permission from the original author.
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/util/random/WeightedList.java b/net/minecraft/util/random/WeightedList.java
index a8481259e42aa116e29bccd1e30fdfd91f51a8cc..49a558d6021db2d57eb2596206ea459fd33485e0 100644
--- a/net/minecraft/util/random/WeightedList.java
+++ b/net/minecraft/util/random/WeightedList.java
@@ -23,7 +23,7 @@ public class WeightedList<E> { // Paper - non-final
private final WeightedList.@Nullable Selector<E> selector;
protected WeightedList(final List<? extends Weighted<E>> items) { // Paper - protected
- this.items = List.copyOf(items);
+ this.items = items.size() > 4 ? new net.caffeinemc.mods.lithium.common.util.collections.HashedReferenceList(items) : new it.unimi.dsi.fastutil.objects.ReferenceArrayList<>(items); // Anonymous - Lithium optimized collections for WeightedList
this.totalWeight = WeightedRandom.getTotalWeight(items, Weighted::weight);
if (this.totalWeight == 0) {
this.selector = null;
@@ -0,0 +1,282 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Tue, 28 Jul 2026 15:09:19 +0800
Subject: [PATCH] Anonymous Object: Lithium optimized non poi block searching
As requested by the original author, this patch will be anonymous and hide the original commit address.
This patch is used with permission from the original author.
Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html)
diff --git a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
index c6e9b155c2341c13d569f28f54f0c1d2d7313676..927635518c51375315390877d582b32fc38748df 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 { // Anonymous - 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);
+ // Anonymous 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;
+ }
+ // Anonymous 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..99c076ee2fe0d8294398c5ef1f24a95da5eee89c 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;
+ // Anonymous 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();
+ }
+ // Anonymous 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)) { // Anonymous - 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..78743515ff2afd1ea1f06432f33f41a03fc7988b 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> {
+ // Anonymous 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);
+ }
+ // Anonymous 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)); // Anonymous - 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..8594f559a5c8f5957348f7178464d5e9c5abc5ff 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> {
+ // Anonymous 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;
+ }
+ // Anonymous 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)); // Anonymous - Lithium Optimizes Piglin repellent search.
Optional<Mob> nemesis = Optional.empty();
Optional<Hoglin> huntableHoglin = Optional.empty();
Optional<Hoglin> babyHoglin = Optional.empty();
@@ -0,0 +1,159 @@
/*
* This file is part of Lithium
*
* Lithium is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Lithium is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Lithium. If not, see <https://www.gnu.org/licenses/>.
*/
package net.caffeinemc.mods.lithium.common.ai.non_poi_block_search;
import net.caffeinemc.mods.lithium.common.util.collections.FixedChunkAccessSectionBitBuffer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* This is intended to be used to optimize Non-POI block searches by pre-checking the ChunkSections, getting the
* ChunkAccesses which then allows for returning early or only calling getBlockState in possible ChunkSections.
* <p>
* Note: Please correctly specify shouldChunkLoad based on whether the getBlockState in vanilla can chunk load or not.
* The default in game is that it can. Setting it to true will mean that the search will assume that unloaded chunks
* may have the target and will chunk load then search them if and when it reaches it.
*
* @author jcw780
*/
public class CheckAndCacheBlockChecker {
private final FixedChunkAccessSectionBitBuffer chunkSections2MaybeContainsMatchingBlock;
private final LevelReader levelReader;
public final boolean shouldChunkLoad;
public final Predicate<BlockState> blockStatePredicate;
private int unloadedPossibleChunkSections = 0;
public final int minSectionY;
public CheckAndCacheBlockChecker(BlockPos origin, int horizontalRangeInclusive, int verticalRangeInclusive, LevelReader levelReader,
Predicate<BlockState> blockStatePredicate, boolean shouldChunkLoad) {
this.chunkSections2MaybeContainsMatchingBlock = new FixedChunkAccessSectionBitBuffer(origin, horizontalRangeInclusive, verticalRangeInclusive);
this.levelReader = levelReader;
this.shouldChunkLoad = shouldChunkLoad;
this.blockStatePredicate = blockStatePredicate;
this.minSectionY = levelReader.getMinSectionY();
}
public void initializeChunks() {
this.initializeChunks(null);
}
public void initializeChunks(Consumer<Long> chunkCollector) {
final boolean nullChunkCollector = chunkCollector == null;
for (long chunkPos : this.chunkSections2MaybeContainsMatchingBlock.getChunkPosInRange()) {
int x = ChunkPos.getX(chunkPos);
int z = ChunkPos.getZ(chunkPos);
boolean chunkMaybeHas = false;
//Never load chunks in the first pass to avoid observably altering chunk loading behavior
//Otherwise full region will be loaded vs partial region if the search finds the block early.
ChunkAccess chunkAccess = levelReader.getChunk(x, z, ChunkStatus.FULL, false);
if (chunkAccess != null) {
this.chunkSections2MaybeContainsMatchingBlock.setChunkAccess(chunkPos, chunkAccess);
for (int y : this.chunkSections2MaybeContainsMatchingBlock.getSectionYInRange()) {
chunkMaybeHas = this.checkChunkSection(chunkAccess, x, y, z) || chunkMaybeHas;
}
} else if (this.shouldChunkLoad) {
/* If the search may chunk load then it is possible that target blocks may be revealed when the search
* reaches it. Since we cannot load the chunks and check now, we cannot definitively exclude subchunks
* inside the chunk. This means that we must flag subchunks that are within build limit - otherwise air
* anyway - for the search.
*/
for (int y : this.chunkSections2MaybeContainsMatchingBlock.getSectionYInRange()) {
this.chunkSections2MaybeContainsMatchingBlock.setChunkSectionStatus(SectionPos.asLong(x, y, z),
!levelReader.isOutsideBuildHeight(SectionPos.sectionToBlockCoord(y)));
++this.unloadedPossibleChunkSections;
}
chunkMaybeHas = true;
}
if (!nullChunkCollector && chunkMaybeHas) {
chunkCollector.accept(chunkPos);
}
}
}
public int getChunkSize(){
return this.chunkSections2MaybeContainsMatchingBlock.numChunks;
}
public boolean hasUnloadedPossibleChunks(){
return this.unloadedPossibleChunkSections > 0;
}
private boolean checkChunkSection(ChunkAccess chunkAccess, int chunkX, int chunkY, int chunkZ) {
final int chunkSectionYIndex = chunkY - this.minSectionY;
LevelChunkSection[] chunkSections = chunkAccess.getSections();
if (chunkSectionYIndex >= 0
&& chunkSectionYIndex < chunkSections.length
&& chunkSections[chunkSectionYIndex].maybeHas(blockStatePredicate)) {
this.chunkSections2MaybeContainsMatchingBlock.setChunkSectionStatus(
SectionPos.asLong(chunkX, chunkY, chunkZ), true);
return true;
}
return false;
}
public boolean checkCachedSection(int chunkX, int chunkY, int chunkZ) {
return this.chunkSections2MaybeContainsMatchingBlock.getChunkSectionBit(chunkX, chunkY, chunkZ);
}
public ChunkAccess getCachedChunkAccess(long chunkPos) {
return this.chunkSections2MaybeContainsMatchingBlock.getChunkAccess(chunkPos);
}
public ChunkAccess getCachedChunkAccess(BlockPos blockPos) {
return this.chunkSections2MaybeContainsMatchingBlock.getChunkAccess(blockPos);
}
public boolean shouldStop(){
return this.chunkSections2MaybeContainsMatchingBlock.hasNoTrueChunkSections();
}
public boolean checkPosition(BlockPos blockPos) {
if(!this.chunkSections2MaybeContainsMatchingBlock.getChunkSectionBit(blockPos)) return false;
ChunkAccess chunkAccess = this.chunkSections2MaybeContainsMatchingBlock.getChunkAccess(blockPos);
if(chunkAccess == null) {
if (!this.shouldChunkLoad) {
return false;
}
final int chunkX = SectionPos.blockToSectionCoord(blockPos.getX());
final int chunkY = SectionPos.blockToSectionCoord(blockPos.getY());
final int chunkZ = SectionPos.blockToSectionCoord(blockPos.getZ());
chunkAccess = levelReader.getChunk(chunkX, chunkZ, ChunkStatus.FULL, true);
//this chunkAccess cannot be null and reach here because it should throw earlier
assert chunkAccess != null;
this.chunkSections2MaybeContainsMatchingBlock.setChunkAccess(blockPos, chunkAccess);
if (!checkChunkSection(chunkAccess, chunkX, chunkY, chunkZ)) {
--this.unloadedPossibleChunkSections;
return false;
}
}
return blockStatePredicate.test(chunkAccess.getBlockState(blockPos));
}
}
@@ -0,0 +1,48 @@
/*
* This file is part of Lithium
*
* Lithium is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Lithium is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Lithium. If not, see <https://www.gnu.org/licenses/>.
*/
package net.caffeinemc.mods.lithium.common.ai.non_poi_block_search;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.state.BlockState;
import java.util.Optional;
import java.util.function.Predicate;
/**
* Uses CheckAndCacheBlockChecker to improve common block searches
*/
public class CommonBlockSearchesCheckAndCache {
/**
* Optimizes BlockPos::findClosestMatch
* [Vanilla Copy] search order and chunk-loading - even though the latter is unlikely to be observable in vanilla.
*/
public static Optional<BlockPos> blockPosFindClosestMatch(LevelReader levelReader, LivingEntity livingEntity,
int horizontalRange, int verticalRange,
Predicate<BlockState> blockStatePredicate,
boolean shouldChunkLoad){
BlockPos mobPos = livingEntity.blockPosition();
CheckAndCacheBlockChecker checker = new CheckAndCacheBlockChecker(
mobPos, horizontalRange, verticalRange, levelReader, blockStatePredicate, shouldChunkLoad);
checker.initializeChunks();
if(checker.shouldStop()) {
return Optional.empty();
}
return BlockPos.findClosestMatch(mobPos, horizontalRange, verticalRange, checker::checkPosition);
}
}
@@ -0,0 +1,30 @@
/*
* This file is part of Lithium
*
* Lithium is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Lithium is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Lithium. If not, see <https://www.gnu.org/licenses/>.
*/
package net.caffeinemc.mods.lithium.common.ai.non_poi_block_search;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
public interface LithiumMoveToBlockGoal {
boolean lithium$findNearestBlock(Predicate<BlockState> requiredBlock,
BiPredicate<ChunkAccess, BlockPos.MutableBlockPos> lithium$isValidTarget,
final boolean shouldChunkLoad);
}
@@ -0,0 +1,68 @@
/*
* This file is part of Lithium
*
* Lithium is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Lithium is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Lithium. If not, see <https://www.gnu.org/licenses/>.
*/
package net.caffeinemc.mods.lithium.common.ai.non_poi_block_search;
import net.caffeinemc.mods.lithium.common.util.Distances;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.ChunkPos;
public class NonPOISearchDistances {
public static class MoveToBlockGoalDistances {
public static int getMinimumSortOrderOfChunk(BlockPos center, final long chunkPos) {
return getMinimumSortOrderOfChunk(center, ChunkPos.getX(chunkPos), ChunkPos.getZ(chunkPos));
}
public static int getMinimumSortOrderOfChunk(BlockPos center, final int chunkX, final int chunkZ) {
final int dX = Distances.getClosestBlockCoordInSection(center.getX(), chunkX) - center.getX();
final int dZ = Distances.getClosestBlockCoordInSection(center.getZ(), chunkZ) - center.getZ();
//This will always get the closest one due to the nature of the search
return getVanillaSortOrderInt(getRing(dX, dZ), dX, dZ);
}
public static int getRing(final int dX, final int dZ){
return Math.max(Math.abs(dX), Math.abs(dZ));
}
/**
* Sort order function for 1 layer of MoveToBlockGoal findNearestBlock
* This is equivalent to:
* int withinRingX = Math.abs(dX) * 2 - (dX > 0 ? 1 : 0);
* int withinRingZ = Math.abs(dZ) * 2 - (dZ > 0 ? 1 : 0);
* return ring << 16 | withinRingX << 8 | withinRingZ;
* <p>
* This works because the search prioritizes in order of:
* 1. The distance of y from the center - Not used
* 2. Whether y is - or + (+ is closer) - Not used
* 3. The square ring that the block is in (outer is further)
* 4. The distance of x from the center
* 5. Whether x is - or + (+ is closer)
* 6. The distance of z from the center
* 7. Whether z is - or + (+ is closer)
* <p>
* Note: The bit-packing only works for horizontal search ranges of <=128.
* You can convert to longs if you somehow exceed that, but also seriously consider POIs instead.
*
* @param ring Which square ring the block is at relative to the center
* @param dX Relative x position of the block to the center
* @param dZ Relative z position of the block to the center
*/
public static int getVanillaSortOrderInt(final int ring, final int dX, final int dZ) {
return (ring << 16 | Math.abs(dX) << 9 | Math.abs(dZ) << 1) - ((dX > 0 ? 1 : 0) << 8 | (dZ > 0 ? 1 : 0));
}
}
}
@@ -0,0 +1,83 @@
/*
* This file is part of Lithium
*
* Lithium is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Lithium is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Lithium. If not, see <https://www.gnu.org/licenses/>.
*/
package net.caffeinemc.mods.lithium.common.util;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
public class Distances {
public static long getMinChunkToBlockDistanceL2Sq(BlockPos origin, int chunkX, int chunkZ) {
int chunkMinX = SectionPos.sectionToBlockCoord(chunkX);
int chunkMinZ = SectionPos.sectionToBlockCoord(chunkZ);
int xDistance = origin.getX() - chunkMinX;
if (xDistance > 0) {
xDistance = Math.max(0, xDistance - 15);
}
int zDistance = origin.getZ() - chunkMinZ;
if (zDistance > 0) {
zDistance = Math.max(0, zDistance - 15);
}
return (long) xDistance * (long) xDistance + (long) zDistance * (long) zDistance;
}
public static BlockPos getClosestPosInChunk(BlockPos origin, int chunkX, int chunkZ) {
int closestX = getClosestBlockCoordInSection(origin.getX(), chunkX);
int closestZ = getClosestBlockCoordInSection(origin.getZ(), chunkZ);
return new BlockPos(closestX, origin.getY(), closestZ);
}
public static boolean isWithinCubeRadius(BlockPos origin, int radius, BlockPos pos) {
return Math.abs(pos.getX() - origin.getX()) <= radius &&
Math.abs(pos.getZ() - origin.getZ()) <= radius;
}
public static boolean isWithinSphereRadius(BlockPos origin, long radiusSq, BlockPos pos) {
return distanceSq(origin, pos) <= radiusSq;
}
public static int getClosestBlockCoordInSection(int blockCoord, int sectionCoord) {
final int minBlockInSection = SectionPos.sectionToBlockCoord(sectionCoord);
return Math.min(Math.max(blockCoord, minBlockInSection), minBlockInSection + 15);
}
public static long getMinSectionDistanceSq(BlockPos origin, int chunkX, int chunkY, int chunkZ) {
int originX = origin.getX(), originY = origin.getY(), originZ = origin.getZ();
long distX = getClosestBlockCoordInSection(originX, chunkX) - originX;
long distY = getClosestBlockCoordInSection(originY, chunkY) - originY;
long distZ = getClosestBlockCoordInSection(originZ, chunkZ) - originZ;
return distX * distX + distY * distY + distZ * distZ;
}
public static long distanceSq(BlockPos a, BlockPos b) {
long dx = a.getX() - b.getX();
long dy = a.getY() - b.getY();
long dz = a.getZ() - b.getZ();
return dx * dx + dy * dy + dz * dz;
}
public static int distanceSqInt(BlockPos a, BlockPos b) {
int dx = a.getX() - b.getX();
int dy = a.getY() - b.getY();
int dz = a.getZ() - b.getZ();
// Check overflows to avoid silent incorrect results
return Math.addExact(Math.addExact(Math.multiplyExact(dx, dx), Math.multiplyExact(dy, dy)), Math.multiplyExact(dz, dz));
}
}
@@ -0,0 +1,109 @@
/*
* This file is part of Lithium
*
* Lithium is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Lithium is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Lithium. If not, see <https://www.gnu.org/licenses/>.
*/
package net.caffeinemc.mods.lithium.common.util;
import net.minecraft.core.SectionPos;
import net.minecraft.world.level.LevelHeightAccessor;
public class Pos {
public static class BlockCoord {
public static int getYSize(LevelHeightAccessor view) {
return view.getHeight();
}
public static int getMinY(LevelHeightAccessor view) {
return view.getMinY();
}
public static int getMaxYInclusive(LevelHeightAccessor view) {
return view.getMaxY();
}
public static int getMaxYExclusive(LevelHeightAccessor view) {
return view.getMaxY() + 1;
}
public static int getMaxInSectionCoord(int sectionCoord) {
return 15 + getMinInSectionCoord(sectionCoord);
}
public static int getMaxYInSectionIndex(LevelHeightAccessor view, int sectionIndex){
return getMaxInSectionCoord(SectionYCoord.fromSectionIndex(view, sectionIndex));
}
public static int getMinInSectionCoord(int sectionCoord) {
return SectionPos.sectionToBlockCoord(sectionCoord);
}
public static int getMinYInSectionIndex(LevelHeightAccessor view, int sectionIndex) {
return getMinInSectionCoord(SectionYCoord.fromSectionIndex(view, sectionIndex));
}
}
public static class ChunkCoord {
public static int fromBlockCoord(int blockCoord) {
return SectionPos.blockToSectionCoord(blockCoord);
}
public static int fromBlockSize(int i) {
return i >> 4; //same method as fromBlockCoord, just be clear about coord/size semantic difference
}
}
public static class SectionYCoord {
public static int getNumYSections(LevelHeightAccessor view) {
return view.getSectionsCount();
}
public static int getMinYSection(LevelHeightAccessor view) {
return view.getMinSectionY();
}
public static int getMaxYSectionInclusive(LevelHeightAccessor view) {
return view.getMaxSectionY();
}
public static int getMaxYSectionExclusive(LevelHeightAccessor view) {
return view.getMaxSectionY() + 1;
}
public static int fromSectionIndex(LevelHeightAccessor view, int sectionCoord) {
return sectionCoord + SectionYCoord.getMinYSection(view);
}
public static int fromBlockCoord(int blockCoord) {
return SectionPos.blockToSectionCoord(blockCoord);
}
}
public static class SectionYIndex {
public static int getNumYSections(LevelHeightAccessor view) {
return view.getSectionsCount();
}
public static int getMinYSectionIndex(LevelHeightAccessor view) {
return 0;
}
public static int getMaxYSectionIndexInclusive(LevelHeightAccessor view) {
return view.getSectionsCount() - 1;
}
public static int getMaxYSectionIndexExclusive(LevelHeightAccessor view) {
return view.getSectionsCount();
}
public static int fromSectionCoord(LevelHeightAccessor view, int sectionCoord) {
return sectionCoord - SectionYCoord.getMinYSection(view);
}
public static int fromBlockCoord(LevelHeightAccessor view, int blockCoord) {
return fromSectionCoord(view, SectionPos.blockToSectionCoord(blockCoord));
}
}
}
@@ -0,0 +1,189 @@
/*
* This file is part of Lithium
*
* Lithium is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Lithium is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Lithium. If not, see <https://www.gnu.org/licenses/>.
*/
package net.caffeinemc.mods.lithium.common.util.collections;
import it.unimi.dsi.fastutil.ints.IntIterable;
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.longs.LongIterable;
import it.unimi.dsi.fastutil.longs.LongIterator;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkAccess;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
public class FixedChunkAccessSectionBitBuffer {
public final int xMin, yMin, zMin;
public final int xLength, yLength, zLength, numChunks, numSections;
public final BitSet chunkSectionBits;
public final ArrayList<ChunkAccess> chunkAccesses;
public FixedChunkAccessSectionBitBuffer(int x0, int x1, int y0, int y1, int z0, int z1) {
this.xMin = Math.min(x0, x1);
this.yMin = Math.min(y0, y1);
this.zMin = Math.min(z0, z1);
this.xLength = Math.max(x0, x1) - this.xMin + 1;
this.yLength = Math.max(y0, y1) - this.yMin + 1;
this.zLength = Math.max(z0, z1) - this.zMin + 1;
this.numChunks = xLength * zLength;
this.numSections = yLength * xLength * zLength;
this.chunkSectionBits = new BitSet(numSections);
this.chunkAccesses = new ArrayList<>(Collections.nCopies(xLength * zLength,null));
}
public FixedChunkAccessSectionBitBuffer(BlockPos center, int horizontalRangeInclusive, int verticalRangeInclusive) {
this(SectionPos.blockToSectionCoord(center.getX() - horizontalRangeInclusive),
SectionPos.blockToSectionCoord(center.getX() + horizontalRangeInclusive),
SectionPos.blockToSectionCoord(center.getY() - verticalRangeInclusive),
SectionPos.blockToSectionCoord(center.getY() + verticalRangeInclusive),
SectionPos.blockToSectionCoord(center.getZ() - horizontalRangeInclusive),
SectionPos.blockToSectionCoord(center.getZ() + horizontalRangeInclusive)
);
}
public int getSectionIndex(int x, int y, int z) {
int dx = x - this.xMin;
int dy = y - this.yMin;
int dz = z - this.zMin;
return (dx * this.zLength + dz) * this.yLength + dy;
}
public int getSectionIndex(long sectionPos) {
return this.getSectionIndex(
SectionPos.x(sectionPos),
SectionPos.y(sectionPos),
SectionPos.z(sectionPos)
);
}
public boolean getChunkSectionBit(BlockPos blockPos) {
return this.getChunkSectionBit(SectionPos.blockToSectionCoord(blockPos.getX()), SectionPos.blockToSectionCoord(blockPos.getY()), SectionPos.blockToSectionCoord(blockPos.getZ()));
}
public boolean getChunkSectionBit(int chunkX, int chunkY, int chunkZ) {
return this.chunkSectionBits.get(this.getSectionIndex(chunkX, chunkY, chunkZ));
}
public void setChunkSectionStatus(long sectionPos, boolean value) {
this.chunkSectionBits.set(this.getSectionIndex(sectionPos), value);
}
public int getChunkIndex(int x, int z) {
int dx = x - this.xMin;
int dz = z - this.zMin;
return dx * this.zLength + dz;
}
public int getChunkIndex(long chunkPos) {
return this.getChunkIndex(ChunkPos.getX(chunkPos), ChunkPos.getZ(chunkPos));
}
public ChunkAccess getChunkAccess(long chunkPos){
return this.chunkAccesses.get(this.getChunkIndex(chunkPos));
}
public ChunkAccess getChunkAccess(BlockPos blockPos){
return this.getChunkAccess(ChunkPos.pack(blockPos));
}
public void setChunkAccess(long chunkPos, ChunkAccess chunkAccess) {
this.chunkAccesses.set(this.getChunkIndex(chunkPos), chunkAccess);
}
public void setChunkAccess(BlockPos blockPos, ChunkAccess chunkAccess) {
this.setChunkAccess(ChunkPos.pack(blockPos), chunkAccess);
}
public boolean hasNoTrueChunkSections(){
return this.chunkSectionBits.nextSetBit(0) == -1;
}
public LongIterable getChunkPosInRange() {
return new LongIterable() {
@Override
public @NotNull LongIterator iterator(){
return getChunkPosInRangeIterator();
}
};
}
public LongIterator getChunkPosInRangeIterator() {
final int xMin = this.xMin;
final int xMax = this.xMin + this.xLength - 1;
final int zMin = this.zMin;
final int zMax = this.zMin + this.zLength - 1;
return new LongIterator() {
int x = xMin;
int z = zMin;
@Override
public long nextLong () {
long result = ChunkPos.pack(x, z);
if (z < zMax) {
z++;
} else {
z = zMin;
x++;
}
return result;
}
@Override
public boolean hasNext(){
return x <= xMax;
}
};
}
public IntIterable getSectionYInRange() {
return new IntIterable() {
@Override
public @NotNull IntIterator iterator(){
return getSectionYInRangeIterator();
}
};
}
public IntIterator getSectionYInRangeIterator() {
final int yMin = this.yMin;
final int yLimit = yMin + this.yLength;
return new IntIterator() {
int y = yMin;
@Override
public int nextInt(){
return y++;
}
@Override
public boolean hasNext(){
return y < yLimit;
}
};
}
}
@@ -0,0 +1,293 @@
/*
* This file is part of Lithium
*
* Lithium is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Lithium is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Lithium. If not, see <https://www.gnu.org/licenses/>.
*/
package net.caffeinemc.mods.lithium.common.util.collections;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ReferenceArrayList;
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* Wraps a {@link List} with a hash table which provides O(1) lookups for {@link Collection#contains(Object)}. The type
* contained by this list must use reference-equality semantics.
*/
@SuppressWarnings("SuspiciousMethodCalls")
public class HashedReferenceList<T> implements List<T> {
private final ReferenceArrayList<T> list;
private final Reference2IntOpenHashMap<T> counter;
public HashedReferenceList(Collection<T> list) {
this.list = new ReferenceArrayList<>();
this.list.addAll(list);
this.counter = new Reference2IntOpenHashMap<>();
this.counter.defaultReturnValue(0);
for (T obj : this.list) {
this.counter.addTo(obj, 1);
}
}
@Override
public int size() {
return this.list.size();
}
@Override
public boolean isEmpty() {
return this.list.isEmpty();
}
@Override
public boolean contains(Object o) {
return this.counter.containsKey(o);
}
@Override
public Iterator<T> iterator() {
return this.listIterator();
}
@Override
public Object[] toArray() {
return this.list.toArray();
}
@Override
public <T1> T1[] toArray(T1 @NotNull [] a) {
return this.list.toArray(a);
}
@Override
public boolean add(T t) {
this.trackReferenceAdded(t);
return this.list.add(t);
}
@Override
public boolean remove(Object o) {
this.trackReferenceRemoved(o);
return this.list.remove(o);
}
@Override
public boolean containsAll(Collection<?> c) {
for (Object obj : c) {
if (!this.counter.containsKey(obj)) {
return false;
}
}
return true;
}
@Override
public boolean addAll(Collection<? extends T> c) {
for (T obj : c) {
this.trackReferenceAdded(obj);
}
return this.list.addAll(c);
}
@Override
public boolean addAll(int index, Collection<? extends T> c) {
for (T obj : c) {
this.trackReferenceAdded(obj);
}
return this.list.addAll(index, c);
}
@Override
public boolean removeAll(@NotNull Collection<?> c) {
if (this.size() >= 2 && c.size() > 4 && c instanceof List) {
//HashReferenceList uses reference equality, so using ReferenceOpenHashSet is fine
c = new ReferenceOpenHashSet<>(c);
}
this.counter.keySet().removeAll(c);
return this.list.removeAll(c);
}
@Override
public boolean retainAll(@NotNull Collection<?> c) {
this.counter.keySet().retainAll(c);
return this.list.retainAll(c);
}
@Override
public void clear() {
this.counter.clear();
this.list.clear();
}
@Override
public T get(int index) {
return this.list.get(index);
}
@Override
public T set(int index, T element) {
T prev = this.list.set(index, element);
if (prev != element) {
if (prev != null) {
this.trackReferenceRemoved(prev);
}
this.trackReferenceAdded(element);
}
return prev;
}
@Override
public void add(int index, T element) {
this.trackReferenceAdded(element);
this.list.add(index, element);
}
@Override
public T remove(int index) {
T prev = this.list.remove(index);
if (prev != null) {
this.trackReferenceRemoved(prev);
}
return prev;
}
@Override
public int indexOf(Object o) {
return this.list.indexOf(o);
}
@Override
public int lastIndexOf(Object o) {
return this.list.lastIndexOf(o);
}
@Override
public ListIterator<T> listIterator() {
return this.listIterator(0);
}
@Override
public ListIterator<T> listIterator(int index) {
return new ListIterator<>() {
private final ListIterator<T> inner = HashedReferenceList.this.list.listIterator(index);
@Override
public boolean hasNext() {
return this.inner.hasNext();
}
@Override
public T next() {
return this.inner.next();
}
@Override
public boolean hasPrevious() {
return this.inner.hasPrevious();
}
@Override
public T previous() {
return this.inner.previous();
}
@Override
public int nextIndex() {
return this.inner.nextIndex();
}
@Override
public int previousIndex() {
return this.inner.previousIndex();
}
@Override
public void remove() {
int last = this.previousIndex();
if (last == -1) {
throw new NoSuchElementException();
}
T prev = HashedReferenceList.this.get(last);
if (prev != null) {
HashedReferenceList.this.trackReferenceRemoved(prev);
}
this.inner.remove();
}
@Override
public void set(T t) {
int last = this.previousIndex();
if (last == -1) {
throw new NoSuchElementException();
}
T prev = HashedReferenceList.this.get(last);
if (prev != t) {
if (prev != null) {
HashedReferenceList.this.trackReferenceRemoved(prev);
}
HashedReferenceList.this.trackReferenceAdded(t);
}
this.inner.remove();
}
@Override
public void add(T t) {
HashedReferenceList.this.trackReferenceAdded(t);
this.inner.add(t);
}
};
}
@Override
public List<T> subList(int fromIndex, int toIndex) {
return this.list.subList(fromIndex, toIndex);
}
private void trackReferenceAdded(T t) {
this.counter.addTo(t, 1);
}
@SuppressWarnings("unchecked")
private void trackReferenceRemoved(Object o) {
if (this.counter.addTo((T) o, -1) <= 1) {
this.counter.removeInt(o);
}
}
}