Lithium Optimize non poi block searching
This commit is contained in:
+159
@@ -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));
|
||||
}
|
||||
}
|
||||
+48
@@ -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);
|
||||
}
|
||||
}
|
||||
+30
@@ -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);
|
||||
}
|
||||
+68
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+189
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
+293
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user