mirror of
https://github.com/PaperMC/Paper.git
synced 2024-12-23 17:37:45 +01:00
1061 lines
58 KiB
Diff
1061 lines
58 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
|
Date: Sun, 31 Jan 2021 02:29:24 -0800
|
|
Subject: [PATCH] Optimise general POI access
|
|
|
|
There are a couple of problems with mojang's POI code.
|
|
Firstly, it's all streams. Unsurprisingly, stacking
|
|
streams on top of each other is horrible for performance
|
|
and ultimately took up half of a villager's tick!
|
|
|
|
Secondly, sometime's the search radius is large and there are
|
|
a significant number of poi entries per chunk section. Even
|
|
removing streams at this point doesn't help much. The only solution
|
|
is to start at the search point and iterate outwards. This
|
|
type of approach shows massive gains for portals, simply because
|
|
we can avoid sync loading a large area of chunks. I also tested
|
|
a massive farm I found in JellySquid's discord, which showed
|
|
to benefit significantly simply because the farm had so many
|
|
portal blocks that searching through them all was very slow.
|
|
|
|
Great care has been taken so that behavior remains identical to
|
|
vanilla, however I cannot account for oddball Stream API
|
|
implementations, if they even exist (streams can technically
|
|
be loose with iteration order in a sorted stream given its
|
|
source stream is not tagged with ordered, and mojang does not
|
|
tag the source stream as ordered). However in my testing on openjdk
|
|
there showed no difference, as expected.
|
|
|
|
This patch also specifically optimises other areas of code to
|
|
use PoiAccess. For example, some villager AI and portaling code
|
|
had to be specifically modified.
|
|
|
|
diff --git a/src/main/java/io/papermc/paper/util/PoiAccess.java b/src/main/java/io/papermc/paper/util/PoiAccess.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..69be1761b3b5ba7b496c1c10a4db897e6212d671
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/util/PoiAccess.java
|
|
@@ -0,0 +1,804 @@
|
|
+package io.papermc.paper.util;
|
|
+
|
|
+import com.mojang.datafixers.util.Pair;
|
|
+import it.unimi.dsi.fastutil.doubles.Double2ObjectMap;
|
|
+import it.unimi.dsi.fastutil.doubles.Double2ObjectRBTreeMap;
|
|
+import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
|
|
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
|
+import java.util.function.BiPredicate;
|
|
+import net.minecraft.core.BlockPos;
|
|
+import net.minecraft.core.Holder;
|
|
+import net.minecraft.util.Mth;
|
|
+import net.minecraft.world.entity.ai.village.poi.PoiManager;
|
|
+import net.minecraft.world.entity.ai.village.poi.PoiRecord;
|
|
+import net.minecraft.world.entity.ai.village.poi.PoiSection;
|
|
+import net.minecraft.world.entity.ai.village.poi.PoiType;
|
|
+import java.util.ArrayList;
|
|
+import java.util.HashSet;
|
|
+import java.util.Iterator;
|
|
+import java.util.List;
|
|
+import java.util.Map;
|
|
+import java.util.Optional;
|
|
+import java.util.Set;
|
|
+import java.util.function.Predicate;
|
|
+
|
|
+/**
|
|
+ * Provides optimised access to POI data. All returned values will be identical to vanilla.
|
|
+ */
|
|
+public final class PoiAccess {
|
|
+
|
|
+ protected static double clamp(final double val, final double min, final double max) {
|
|
+ return (val < min ? min : (val > max ? max : val));
|
|
+ }
|
|
+
|
|
+ protected static double getSmallestDistanceSquared(final double boxMinX, final double boxMinY, final double boxMinZ,
|
|
+ final double boxMaxX, final double boxMaxY, final double boxMaxZ,
|
|
+
|
|
+ final double circleX, final double circleY, final double circleZ) {
|
|
+ // is the circle center inside the box?
|
|
+ if (circleX >= boxMinX && circleX <= boxMaxX && circleY >= boxMinY && circleY <= boxMaxY && circleZ >= boxMinZ && circleZ <= boxMaxZ) {
|
|
+ return 0.0;
|
|
+ }
|
|
+
|
|
+ final double boxWidthX = (boxMaxX - boxMinX) / 2.0;
|
|
+ final double boxWidthY = (boxMaxY - boxMinY) / 2.0;
|
|
+ final double boxWidthZ = (boxMaxZ - boxMinZ) / 2.0;
|
|
+
|
|
+ final double boxCenterX = (boxMinX + boxMaxX) / 2.0;
|
|
+ final double boxCenterY = (boxMinY + boxMaxY) / 2.0;
|
|
+ final double boxCenterZ = (boxMinZ + boxMaxZ) / 2.0;
|
|
+
|
|
+ double centerDiffX = circleX - boxCenterX;
|
|
+ double centerDiffY = circleY - boxCenterY;
|
|
+ double centerDiffZ = circleZ - boxCenterZ;
|
|
+
|
|
+ centerDiffX = circleX - (clamp(centerDiffX, -boxWidthX, boxWidthX) + boxCenterX);
|
|
+ centerDiffY = circleY - (clamp(centerDiffY, -boxWidthY, boxWidthY) + boxCenterY);
|
|
+ centerDiffZ = circleZ - (clamp(centerDiffZ, -boxWidthZ, boxWidthZ) + boxCenterZ);
|
|
+
|
|
+ return (centerDiffX * centerDiffX) + (centerDiffY * centerDiffY) + (centerDiffZ * centerDiffZ);
|
|
+ }
|
|
+
|
|
+
|
|
+ // key is:
|
|
+ // upper 32 bits:
|
|
+ // upper 16 bits: max y section
|
|
+ // lower 16 bits: min y section
|
|
+ // lower 32 bits:
|
|
+ // upper 16 bits: section
|
|
+ // lower 16 bits: radius
|
|
+ protected static long getKey(final int minSection, final int maxSection, final int section, final int radius) {
|
|
+ return (
|
|
+ (maxSection & 0xFFFFL) << (64 - 16)
|
|
+ | (minSection & 0xFFFFL) << (64 - 32)
|
|
+ | (section & 0xFFFFL) << (64 - 48)
|
|
+ | (radius & 0xFFFFL) << (64 - 64)
|
|
+ );
|
|
+ }
|
|
+
|
|
+ // only includes x/z axis
|
|
+ // finds the closest poi data by distance.
|
|
+ public static BlockPos findClosestPoiDataPosition(final PoiManager poiStorage,
|
|
+ final Predicate<Holder<PoiType>> villagePlaceType,
|
|
+ // position predicate must not modify chunk POI
|
|
+ final Predicate<BlockPos> positionPredicate,
|
|
+ final BlockPos sourcePosition,
|
|
+ final int range, // distance on x y z axis
|
|
+ final double maxDistanceSquared,
|
|
+ final PoiManager.Occupancy occupancy,
|
|
+ final boolean load) {
|
|
+ final PoiRecord ret = findClosestPoiDataRecord(
|
|
+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistanceSquared, occupancy, load
|
|
+ );
|
|
+
|
|
+ return ret == null ? null : ret.getPos();
|
|
+ }
|
|
+
|
|
+ // only includes x/z axis
|
|
+ // finds the closest poi data by distance.
|
|
+ public static Pair<Holder<PoiType>, BlockPos> findClosestPoiDataTypeAndPosition(final PoiManager poiStorage,
|
|
+ final Predicate<Holder<PoiType>> villagePlaceType,
|
|
+ // position predicate must not modify chunk POI
|
|
+ final Predicate<BlockPos> positionPredicate,
|
|
+ final BlockPos sourcePosition,
|
|
+ final int range, // distance on x y z axis
|
|
+ final double maxDistanceSquared,
|
|
+ final PoiManager.Occupancy occupancy,
|
|
+ final boolean load) {
|
|
+ final PoiRecord ret = findClosestPoiDataRecord(
|
|
+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistanceSquared, occupancy, load
|
|
+ );
|
|
+
|
|
+ return ret == null ? null : Pair.of(ret.getPoiType(), ret.getPos());
|
|
+ }
|
|
+
|
|
+ // only includes x/z axis
|
|
+ // finds the closest poi data by distance. if multiple match the same distance, then they all are returned.
|
|
+ public static void findClosestPoiDataPositions(final PoiManager poiStorage,
|
|
+ final Predicate<Holder<PoiType>> villagePlaceType,
|
|
+ // position predicate must not modify chunk POI
|
|
+ final Predicate<BlockPos> positionPredicate,
|
|
+ final BlockPos sourcePosition,
|
|
+ final int range, // distance on x y z axis
|
|
+ final double maxDistanceSquared,
|
|
+ final PoiManager.Occupancy occupancy,
|
|
+ final boolean load,
|
|
+ final Set<BlockPos> ret) {
|
|
+ final Set<BlockPos> positions = new HashSet<>();
|
|
+ // pos predicate is last thing that runs before adding to ret.
|
|
+ final Predicate<BlockPos> newPredicate = (final BlockPos pos) -> {
|
|
+ if (positionPredicate != null && !positionPredicate.test(pos)) {
|
|
+ return false;
|
|
+ }
|
|
+ return positions.add(pos.immutable());
|
|
+ };
|
|
+
|
|
+ final List<PoiRecord> toConvert = new ArrayList<>();
|
|
+ findClosestPoiDataRecords(
|
|
+ poiStorage, villagePlaceType, newPredicate, sourcePosition, range, maxDistanceSquared, occupancy, load, toConvert
|
|
+ );
|
|
+
|
|
+ for (final PoiRecord record : toConvert) {
|
|
+ ret.add(record.getPos());
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // only includes x/z axis
|
|
+ // finds the closest poi data by distance.
|
|
+ public static PoiRecord findClosestPoiDataRecord(final PoiManager poiStorage,
|
|
+ final Predicate<Holder<PoiType>> villagePlaceType,
|
|
+ // position predicate must not modify chunk POI
|
|
+ final Predicate<BlockPos> positionPredicate,
|
|
+ final BlockPos sourcePosition,
|
|
+ final int range, // distance on x y z axis
|
|
+ final double maxDistanceSquared,
|
|
+ final PoiManager.Occupancy occupancy,
|
|
+ final boolean load) {
|
|
+ final List<PoiRecord> ret = new ArrayList<>();
|
|
+ findClosestPoiDataRecords(
|
|
+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistanceSquared, occupancy, load, ret
|
|
+ );
|
|
+ return ret.isEmpty() ? null : ret.get(0);
|
|
+ }
|
|
+
|
|
+ // only includes x/z axis
|
|
+ // finds the closest poi data by distance.
|
|
+ public static PoiRecord findClosestPoiDataRecord(final PoiManager poiStorage,
|
|
+ final Predicate<Holder<PoiType>> villagePlaceType,
|
|
+ // position predicate must not modify chunk POI
|
|
+ final BiPredicate<Holder<PoiType>, BlockPos> predicate,
|
|
+ final BlockPos sourcePosition,
|
|
+ final int range, // distance on x y z axis
|
|
+ final double maxDistanceSquared,
|
|
+ final PoiManager.Occupancy occupancy,
|
|
+ final boolean load) {
|
|
+ final List<PoiRecord> ret = new ArrayList<>();
|
|
+ findClosestPoiDataRecords(
|
|
+ poiStorage, villagePlaceType, predicate, sourcePosition, range, maxDistanceSquared, occupancy, load, ret
|
|
+ );
|
|
+ return ret.isEmpty() ? null : ret.get(0);
|
|
+ }
|
|
+
|
|
+ // only includes x/z axis
|
|
+ // finds the closest poi data by distance. if multiple match the same distance, then they all are returned.
|
|
+ public static void findClosestPoiDataRecords(final PoiManager poiStorage,
|
|
+ final Predicate<Holder<PoiType>> villagePlaceType,
|
|
+ // position predicate must not modify chunk POI
|
|
+ final Predicate<BlockPos> positionPredicate,
|
|
+ final BlockPos sourcePosition,
|
|
+ final int range, // distance on x y z axis
|
|
+ final double maxDistanceSquared,
|
|
+ final PoiManager.Occupancy occupancy,
|
|
+ final boolean load,
|
|
+ final List<PoiRecord> ret) {
|
|
+ final BiPredicate<Holder<PoiType>, BlockPos> predicate = positionPredicate != null ? (type, pos) -> positionPredicate.test(pos) : null;
|
|
+ findClosestPoiDataRecords(poiStorage, villagePlaceType, predicate, sourcePosition, range, maxDistanceSquared, occupancy, load, ret);
|
|
+ }
|
|
+
|
|
+ public static void findClosestPoiDataRecords(final PoiManager poiStorage,
|
|
+ final Predicate<Holder<PoiType>> villagePlaceType,
|
|
+ // position predicate must not modify chunk POI
|
|
+ final BiPredicate<Holder<PoiType>, BlockPos> predicate,
|
|
+ final BlockPos sourcePosition,
|
|
+ final int range, // distance on x y z axis
|
|
+ final double maxDistanceSquared,
|
|
+ final PoiManager.Occupancy occupancy,
|
|
+ final boolean load,
|
|
+ final List<PoiRecord> ret) {
|
|
+ final Predicate<? super PoiRecord> occupancyFilter = occupancy.getTest();
|
|
+
|
|
+ final List<PoiRecord> closestRecords = new ArrayList<>();
|
|
+ double closestDistanceSquared = maxDistanceSquared;
|
|
+
|
|
+ final int lowerX = Mth.floor(sourcePosition.getX() - range) >> 4;
|
|
+ final int lowerY = WorldUtil.getMinSection(poiStorage.world);
|
|
+ final int lowerZ = Mth.floor(sourcePosition.getZ() - range) >> 4;
|
|
+ final int upperX = Mth.floor(sourcePosition.getX() + range) >> 4;
|
|
+ final int upperY = WorldUtil.getMaxSection(poiStorage.world);
|
|
+ final int upperZ = Mth.floor(sourcePosition.getZ() + range) >> 4;
|
|
+
|
|
+ final int centerX = sourcePosition.getX() >> 4;
|
|
+ final int centerY = Mth.clamp(sourcePosition.getY() >> 4, lowerY, upperY);
|
|
+ final int centerZ = sourcePosition.getZ() >> 4;
|
|
+ final long centerKey = CoordinateUtils.getChunkSectionKey(centerX, centerY, centerZ);
|
|
+
|
|
+ final LongArrayFIFOQueue queue = new LongArrayFIFOQueue();
|
|
+ final LongOpenHashSet seen = new LongOpenHashSet();
|
|
+ seen.add(centerKey);
|
|
+ queue.enqueue(centerKey);
|
|
+
|
|
+ while (!queue.isEmpty()) {
|
|
+ final long key = queue.dequeueLong();
|
|
+ final int sectionX = CoordinateUtils.getChunkSectionX(key);
|
|
+ final int sectionY = CoordinateUtils.getChunkSectionY(key);
|
|
+ final int sectionZ = CoordinateUtils.getChunkSectionZ(key);
|
|
+
|
|
+ if (sectionX < lowerX || sectionX > upperX || sectionY < lowerY || sectionY > upperY || sectionZ < lowerZ || sectionZ > upperZ) {
|
|
+ // out of bound chunk
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final double sectionDistanceSquared = getSmallestDistanceSquared(
|
|
+ (sectionX << 4) + 0.5,
|
|
+ (sectionY << 4) + 0.5,
|
|
+ (sectionZ << 4) + 0.5,
|
|
+ (sectionX << 4) + 15.5,
|
|
+ (sectionY << 4) + 15.5,
|
|
+ (sectionZ << 4) + 15.5,
|
|
+ (double)sourcePosition.getX(), (double)sourcePosition.getY(), (double)sourcePosition.getZ()
|
|
+ );
|
|
+ if (sectionDistanceSquared > closestDistanceSquared) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // queue all neighbours
|
|
+ for (int dz = -1; dz <= 1; ++dz) {
|
|
+ for (int dx = -1; dx <= 1; ++dx) {
|
|
+ for (int dy = -1; dy <= 1; ++dy) {
|
|
+ // -1 and 1 have the 1st bit set. so just add up the first bits, and it will tell us how many
|
|
+ // values are set. we only care about cardinal neighbours, so, we only care if one value is set
|
|
+ if ((dx & 1) + (dy & 1) + (dz & 1) != 1) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final int neighbourX = sectionX + dx;
|
|
+ final int neighbourY = sectionY + dy;
|
|
+ final int neighbourZ = sectionZ + dz;
|
|
+
|
|
+ final long neighbourKey = CoordinateUtils.getChunkSectionKey(neighbourX, neighbourY, neighbourZ);
|
|
+ if (seen.add(neighbourKey)) {
|
|
+ queue.enqueue(neighbourKey);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ final Optional<PoiSection> poiSectionOptional = load ? poiStorage.getOrLoad(key) : poiStorage.get(key);
|
|
+
|
|
+ if (poiSectionOptional == null || !poiSectionOptional.isPresent()) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final PoiSection poiSection = poiSectionOptional.get();
|
|
+
|
|
+ final Map<Holder<PoiType>, Set<PoiRecord>> sectionData = poiSection.getData();
|
|
+ if (sectionData.isEmpty()) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // now we search the section data
|
|
+ for (final Map.Entry<Holder<PoiType>, Set<PoiRecord>> entry : sectionData.entrySet()) {
|
|
+ if (!villagePlaceType.test(entry.getKey())) {
|
|
+ // filter out by poi type
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // now we can look at the poi data
|
|
+ for (final PoiRecord poiData : entry.getValue()) {
|
|
+ if (!occupancyFilter.test(poiData)) {
|
|
+ // filter by occupancy
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final BlockPos poiPosition = poiData.getPos();
|
|
+
|
|
+ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range
|
|
+ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) {
|
|
+ // out of range for square radius
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // it's important that it's poiPosition.distSqr(source) : the value actually is different IF the values are swapped!
|
|
+ final double dataRange = poiPosition.distSqr(sourcePosition);
|
|
+
|
|
+ if (dataRange > closestDistanceSquared) {
|
|
+ // out of range for distance check
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (predicate != null && !predicate.test(poiData.getPoiType(), poiPosition)) {
|
|
+ // filter by position
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (dataRange < closestDistanceSquared) {
|
|
+ closestRecords.clear();
|
|
+ closestDistanceSquared = dataRange;
|
|
+ }
|
|
+ closestRecords.add(poiData);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // uh oh! we might have multiple records that match the distance sorting!
|
|
+ // we need to re-order our results by the way vanilla would have iterated over them.
|
|
+ closestRecords.sort((record1, record2) -> {
|
|
+ // vanilla iterates the same way we do for data inside sections, so we know the ordering inside a section
|
|
+ // is fine and should be preserved (this sort is stable so we're good there)
|
|
+ // but they iterate sections by x then by z (like the following)
|
|
+ // for (int x = -dx; x <= dx; ++x)
|
|
+ // for (int z = -dz; z <= dz; ++z)
|
|
+ // ....
|
|
+ // so we need to reorder such that records with lower chunk z, then lower chunk x come first
|
|
+ final BlockPos pos1 = record1.getPos();
|
|
+ final BlockPos pos2 = record2.getPos();
|
|
+
|
|
+ final int cx1 = pos1.getX() >> 4;
|
|
+ final int cz1 = pos1.getZ() >> 4;
|
|
+
|
|
+ final int cx2 = pos2.getX() >> 4;
|
|
+ final int cz2 = pos2.getZ() >> 4;
|
|
+
|
|
+ if (cz2 != cz1) {
|
|
+ // want smaller z
|
|
+ return Integer.compare(cz1, cz2);
|
|
+ }
|
|
+
|
|
+ if (cx2 != cx1) {
|
|
+ // want smaller x
|
|
+ return Integer.compare(cx1, cx2);
|
|
+ }
|
|
+
|
|
+ // same chunk
|
|
+ // once vanilla has the chunk, it will iterate from all of the chunk sections starting from smaller y
|
|
+ // so now we just compare section y, wanting smaller y
|
|
+
|
|
+ return Integer.compare(pos1.getY() >> 4, pos2.getY() >> 4);
|
|
+ });
|
|
+
|
|
+ // now we match perfectly what vanilla would have outputted, without having to search the whole radius (hopefully).
|
|
+ ret.addAll(closestRecords);
|
|
+ }
|
|
+
|
|
+ // finds the closest poi entry pos.
|
|
+ public static BlockPos findNearestPoiPosition(final PoiManager poiStorage,
|
|
+ final Predicate<Holder<PoiType>> villagePlaceType,
|
|
+ // position predicate must not modify chunk POI
|
|
+ final Predicate<BlockPos> positionPredicate,
|
|
+ final BlockPos sourcePosition,
|
|
+ final int range, // distance on x y z axis
|
|
+ final double maxDistanceSquared,
|
|
+ final PoiManager.Occupancy occupancy,
|
|
+ final boolean load) {
|
|
+ final PoiRecord ret = findNearestPoiRecord(
|
|
+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistanceSquared, occupancy, load
|
|
+ );
|
|
+ return ret == null ? null : ret.getPos();
|
|
+ }
|
|
+
|
|
+ // finds the closest `max` poi entry positions.
|
|
+ public static void findNearestPoiPositions(final PoiManager poiStorage,
|
|
+ final Predicate<Holder<PoiType>> villagePlaceType,
|
|
+ // position predicate must not modify chunk POI
|
|
+ final Predicate<BlockPos> positionPredicate,
|
|
+ final BlockPos sourcePosition,
|
|
+ final int range, // distance on x y z axis
|
|
+ final double maxDistanceSquared,
|
|
+ final PoiManager.Occupancy occupancy,
|
|
+ final boolean load,
|
|
+ final int max,
|
|
+ final List<Pair<Holder<PoiType>, BlockPos>> ret) {
|
|
+ final Set<BlockPos> positions = new HashSet<>();
|
|
+ // pos predicate is last thing that runs before adding to ret.
|
|
+ final Predicate<BlockPos> newPredicate = (final BlockPos pos) -> {
|
|
+ if (positionPredicate != null && !positionPredicate.test(pos)) {
|
|
+ return false;
|
|
+ }
|
|
+ return positions.add(pos.immutable());
|
|
+ };
|
|
+
|
|
+ final List<PoiRecord> toConvert = new ArrayList<>();
|
|
+ findNearestPoiRecords(
|
|
+ poiStorage, villagePlaceType, newPredicate, sourcePosition, range, maxDistanceSquared, occupancy, load, max, toConvert
|
|
+ );
|
|
+
|
|
+ for (final PoiRecord record : toConvert) {
|
|
+ ret.add(Pair.of(record.getPoiType(), record.getPos()));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // finds the closest poi entry.
|
|
+ public static PoiRecord findNearestPoiRecord(final PoiManager poiStorage,
|
|
+ final Predicate<Holder<PoiType>> villagePlaceType,
|
|
+ // position predicate must not modify chunk POI
|
|
+ final Predicate<BlockPos> positionPredicate,
|
|
+ final BlockPos sourcePosition,
|
|
+ final int range, // distance on x y z axis
|
|
+ final double maxDistanceSquared,
|
|
+ final PoiManager.Occupancy occupancy,
|
|
+ final boolean load) {
|
|
+ final List<PoiRecord> ret = new ArrayList<>();
|
|
+ findNearestPoiRecords(
|
|
+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistanceSquared, occupancy, load,
|
|
+ 1, ret
|
|
+ );
|
|
+ return ret.isEmpty() ? null : ret.get(0);
|
|
+ }
|
|
+
|
|
+ // finds the closest `max` poi entries.
|
|
+ public static void findNearestPoiRecords(final PoiManager poiStorage,
|
|
+ final Predicate<Holder<PoiType>> villagePlaceType,
|
|
+ // position predicate must not modify chunk POI
|
|
+ final Predicate<BlockPos> positionPredicate,
|
|
+ final BlockPos sourcePosition,
|
|
+ final int range, // distance on x y z axis
|
|
+ final double maxDistanceSquared,
|
|
+ final PoiManager.Occupancy occupancy,
|
|
+ final boolean load,
|
|
+ final int max,
|
|
+ final List<PoiRecord> ret) {
|
|
+ final Predicate<? super PoiRecord> occupancyFilter = occupancy.getTest();
|
|
+
|
|
+ final Double2ObjectRBTreeMap<List<PoiRecord>> closestRecords = new Double2ObjectRBTreeMap<>();
|
|
+ int totalRecords = 0;
|
|
+ double furthestDistanceSquared = maxDistanceSquared;
|
|
+
|
|
+ final int lowerX = Mth.floor(sourcePosition.getX() - range) >> 4;
|
|
+ final int lowerY = WorldUtil.getMinSection(poiStorage.world);
|
|
+ final int lowerZ = Mth.floor(sourcePosition.getZ() - range) >> 4;
|
|
+ final int upperX = Mth.floor(sourcePosition.getX() + range) >> 4;
|
|
+ final int upperY = WorldUtil.getMaxSection(poiStorage.world);
|
|
+ final int upperZ = Mth.floor(sourcePosition.getZ() + range) >> 4;
|
|
+
|
|
+ final int centerX = sourcePosition.getX() >> 4;
|
|
+ final int centerY = Mth.clamp(sourcePosition.getY() >> 4, lowerY, upperY);
|
|
+ final int centerZ = sourcePosition.getZ() >> 4;
|
|
+ final long centerKey = CoordinateUtils.getChunkSectionKey(centerX, centerY, centerZ);
|
|
+
|
|
+ final LongArrayFIFOQueue queue = new LongArrayFIFOQueue();
|
|
+ final LongOpenHashSet seen = new LongOpenHashSet();
|
|
+ seen.add(centerKey);
|
|
+ queue.enqueue(centerKey);
|
|
+
|
|
+ while (!queue.isEmpty()) {
|
|
+ final long key = queue.dequeueLong();
|
|
+ final int sectionX = CoordinateUtils.getChunkSectionX(key);
|
|
+ final int sectionY = CoordinateUtils.getChunkSectionY(key);
|
|
+ final int sectionZ = CoordinateUtils.getChunkSectionZ(key);
|
|
+
|
|
+ if (sectionX < lowerX || sectionX > upperX || sectionY < lowerY || sectionY > upperY || sectionZ < lowerZ || sectionZ > upperZ) {
|
|
+ // out of bound chunk
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final double sectionDistanceSquared = getSmallestDistanceSquared(
|
|
+ (sectionX << 4) + 0.5,
|
|
+ (sectionY << 4) + 0.5,
|
|
+ (sectionZ << 4) + 0.5,
|
|
+ (sectionX << 4) + 15.5,
|
|
+ (sectionY << 4) + 15.5,
|
|
+ (sectionZ << 4) + 15.5,
|
|
+ (double) sourcePosition.getX(), (double) sourcePosition.getY(), (double) sourcePosition.getZ()
|
|
+ );
|
|
+
|
|
+ if (sectionDistanceSquared > (totalRecords >= max ? furthestDistanceSquared : maxDistanceSquared)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // queue all neighbours
|
|
+ for (int dz = -1; dz <= 1; ++dz) {
|
|
+ for (int dx = -1; dx <= 1; ++dx) {
|
|
+ for (int dy = -1; dy <= 1; ++dy) {
|
|
+ // -1 and 1 have the 1st bit set. so just add up the first bits, and it will tell us how many
|
|
+ // values are set. we only care about cardinal neighbours, so, we only care if one value is set
|
|
+ if ((dx & 1) + (dy & 1) + (dz & 1) != 1) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final int neighbourX = sectionX + dx;
|
|
+ final int neighbourY = sectionY + dy;
|
|
+ final int neighbourZ = sectionZ + dz;
|
|
+
|
|
+ final long neighbourKey = CoordinateUtils.getChunkSectionKey(neighbourX, neighbourY, neighbourZ);
|
|
+ if (seen.add(neighbourKey)) {
|
|
+ queue.enqueue(neighbourKey);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ final Optional<PoiSection> poiSectionOptional = load ? poiStorage.getOrLoad(key) : poiStorage.get(key);
|
|
+
|
|
+ if (poiSectionOptional == null || !poiSectionOptional.isPresent()) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final PoiSection poiSection = poiSectionOptional.get();
|
|
+
|
|
+ final Map<Holder<PoiType>, Set<PoiRecord>> sectionData = poiSection.getData();
|
|
+ if (sectionData.isEmpty()) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // now we search the section data
|
|
+ for (final Map.Entry<Holder<PoiType>, Set<PoiRecord>> entry : sectionData.entrySet()) {
|
|
+ if (!villagePlaceType.test(entry.getKey())) {
|
|
+ // filter out by poi type
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // now we can look at the poi data
|
|
+ for (final PoiRecord poiData : entry.getValue()) {
|
|
+ if (!occupancyFilter.test(poiData)) {
|
|
+ // filter by occupancy
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final BlockPos poiPosition = poiData.getPos();
|
|
+
|
|
+ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range
|
|
+ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) {
|
|
+ // out of range for square radius
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // it's important that it's poiPosition.distSqr(source) : the value actually is different IF the values are swapped!
|
|
+ final double dataRange = poiPosition.distSqr(sourcePosition);
|
|
+
|
|
+ if (dataRange > maxDistanceSquared) {
|
|
+ // out of range for distance check
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (dataRange > furthestDistanceSquared && totalRecords >= max) {
|
|
+ // out of range for distance check
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (positionPredicate != null && !positionPredicate.test(poiPosition)) {
|
|
+ // filter by position
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (dataRange > furthestDistanceSquared) {
|
|
+ // we know totalRecords < max, so this entry is now our furthest
|
|
+ furthestDistanceSquared = dataRange;
|
|
+ }
|
|
+
|
|
+ closestRecords.computeIfAbsent(dataRange, (final double unused) -> {
|
|
+ return new ArrayList<>();
|
|
+ }).add(poiData);
|
|
+
|
|
+ if (++totalRecords >= max) {
|
|
+ if (closestRecords.size() >= 2) {
|
|
+ int entriesInClosest = 0;
|
|
+ final Iterator<Double2ObjectMap.Entry<List<PoiRecord>>> iterator = closestRecords.double2ObjectEntrySet().iterator();
|
|
+ double nextFurthestDistanceSquared = 0.0;
|
|
+
|
|
+ for (int i = 0, len = closestRecords.size() - 1; i < len; ++i) {
|
|
+ final Double2ObjectMap.Entry<List<PoiRecord>> recordEntry = iterator.next();
|
|
+ entriesInClosest += recordEntry.getValue().size();
|
|
+ nextFurthestDistanceSquared = recordEntry.getDoubleKey();
|
|
+ }
|
|
+
|
|
+ if (entriesInClosest >= max) {
|
|
+ // the last set of entries at range wont even be considered for sure... nuke em
|
|
+ final Double2ObjectMap.Entry<List<PoiRecord>> recordEntry = iterator.next();
|
|
+ totalRecords -= recordEntry.getValue().size();
|
|
+ iterator.remove();
|
|
+
|
|
+ furthestDistanceSquared = nextFurthestDistanceSquared;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ final List<PoiRecord> closestRecordsUnsorted = new ArrayList<>();
|
|
+
|
|
+ // we're done here, so now just flatten the map and sort it.
|
|
+
|
|
+ for (final List<PoiRecord> records : closestRecords.values()) {
|
|
+ closestRecordsUnsorted.addAll(records);
|
|
+ }
|
|
+
|
|
+ // uh oh! we might have multiple records that match the distance sorting!
|
|
+ // we need to re-order our results by the way vanilla would have iterated over them.
|
|
+ closestRecordsUnsorted.sort((record1, record2) -> {
|
|
+ // vanilla iterates the same way we do for data inside sections, so we know the ordering inside a section
|
|
+ // is fine and should be preserved (this sort is stable so we're good there)
|
|
+ // but they iterate sections by x then by z (like the following)
|
|
+ // for (int x = -dx; x <= dx; ++x)
|
|
+ // for (int z = -dz; z <= dz; ++z)
|
|
+ // ....
|
|
+ // so we need to reorder such that records with lower chunk z, then lower chunk x come first
|
|
+ final BlockPos pos1 = record1.getPos();
|
|
+ final BlockPos pos2 = record2.getPos();
|
|
+
|
|
+ final int cx1 = pos1.getX() >> 4;
|
|
+ final int cz1 = pos1.getZ() >> 4;
|
|
+
|
|
+ final int cx2 = pos2.getX() >> 4;
|
|
+ final int cz2 = pos2.getZ() >> 4;
|
|
+
|
|
+ if (cz2 != cz1) {
|
|
+ // want smaller z
|
|
+ return Integer.compare(cz1, cz2);
|
|
+ }
|
|
+
|
|
+ if (cx2 != cx1) {
|
|
+ // want smaller x
|
|
+ return Integer.compare(cx1, cx2);
|
|
+ }
|
|
+
|
|
+ // same chunk
|
|
+ // once vanilla has the chunk, it will iterate from all of the chunk sections starting from smaller y
|
|
+ // so now we just compare section y, wanting smaller section y
|
|
+
|
|
+ return Integer.compare(pos1.getY() >> 4, pos2.getY() >> 4);
|
|
+ });
|
|
+
|
|
+ // trim out any entries exceeding our maximum
|
|
+ for (int i = closestRecordsUnsorted.size() - 1; i >= max; --i) {
|
|
+ closestRecordsUnsorted.remove(i);
|
|
+ }
|
|
+
|
|
+ // now we match perfectly what vanilla would have outputted, without having to search the whole radius (hopefully).
|
|
+ ret.addAll(closestRecordsUnsorted);
|
|
+ }
|
|
+
|
|
+ public static BlockPos findAnyPoiPosition(final PoiManager poiStorage,
|
|
+ final Predicate<Holder<PoiType>> villagePlaceType,
|
|
+ final Predicate<BlockPos> positionPredicate,
|
|
+ final BlockPos sourcePosition,
|
|
+ final int range, // distance on x y z axis
|
|
+ final PoiManager.Occupancy occupancy,
|
|
+ final boolean load) {
|
|
+ final PoiRecord ret = findAnyPoiRecord(
|
|
+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, occupancy, load
|
|
+ );
|
|
+
|
|
+ return ret == null ? null : ret.getPos();
|
|
+ }
|
|
+
|
|
+ public static void findAnyPoiPositions(final PoiManager poiStorage,
|
|
+ final Predicate<Holder<PoiType>> villagePlaceType,
|
|
+ final Predicate<BlockPos> positionPredicate,
|
|
+ final BlockPos sourcePosition,
|
|
+ final int range, // distance on x y z axis
|
|
+ final PoiManager.Occupancy occupancy,
|
|
+ final boolean load,
|
|
+ final int max,
|
|
+ final List<Pair<Holder<PoiType>, BlockPos>> ret) {
|
|
+ final Set<BlockPos> positions = new HashSet<>();
|
|
+ // pos predicate is last thing that runs before adding to ret.
|
|
+ final Predicate<BlockPos> newPredicate = (final BlockPos pos) -> {
|
|
+ if (positionPredicate != null && !positionPredicate.test(pos)) {
|
|
+ return false;
|
|
+ }
|
|
+ return positions.add(pos.immutable());
|
|
+ };
|
|
+
|
|
+ final List<PoiRecord> toConvert = new ArrayList<>();
|
|
+ findAnyPoiRecords(
|
|
+ poiStorage, villagePlaceType, newPredicate, sourcePosition, range, occupancy, load, max, toConvert
|
|
+ );
|
|
+
|
|
+ for (final PoiRecord record : toConvert) {
|
|
+ ret.add(Pair.of(record.getPoiType(), record.getPos()));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static PoiRecord findAnyPoiRecord(final PoiManager poiStorage,
|
|
+ final Predicate<Holder<PoiType>> villagePlaceType,
|
|
+ final Predicate<BlockPos> positionPredicate,
|
|
+ final BlockPos sourcePosition,
|
|
+ final int range, // distance on x y z axis
|
|
+ final PoiManager.Occupancy occupancy,
|
|
+ final boolean load) {
|
|
+ final List<PoiRecord> ret = new ArrayList<>();
|
|
+ findAnyPoiRecords(poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, occupancy, load, 1, ret);
|
|
+ return ret.isEmpty() ? null : ret.get(0);
|
|
+ }
|
|
+
|
|
+ public static void findAnyPoiRecords(final PoiManager poiStorage,
|
|
+ final Predicate<Holder<PoiType>> villagePlaceType,
|
|
+ final Predicate<BlockPos> positionPredicate,
|
|
+ final BlockPos sourcePosition,
|
|
+ final int range, // distance on x y z axis
|
|
+ final PoiManager.Occupancy occupancy,
|
|
+ final boolean load,
|
|
+ final int max,
|
|
+ final List<PoiRecord> ret) {
|
|
+ // the biggest issue with the original mojang implementation is that they chain so many streams together
|
|
+ // the amount of streams chained just rolls performance, even if nothing is iterated over
|
|
+ final Predicate<? super PoiRecord> occupancyFilter = occupancy.getTest();
|
|
+ final double rangeSquared = range * range;
|
|
+
|
|
+ int added = 0;
|
|
+
|
|
+ // First up, we need to iterate the chunks
|
|
+ // all the values here are in chunk sections
|
|
+ final int lowerX = Mth.floor(sourcePosition.getX() - range) >> 4;
|
|
+ final int lowerY = Math.max(WorldUtil.getMinSection(poiStorage.world), Mth.floor(sourcePosition.getY() - range) >> 4);
|
|
+ final int lowerZ = Mth.floor(sourcePosition.getZ() - range) >> 4;
|
|
+ final int upperX = Mth.floor(sourcePosition.getX() + range) >> 4;
|
|
+ final int upperY = Math.min(WorldUtil.getMaxSection(poiStorage.world), Mth.floor(sourcePosition.getY() + range) >> 4);
|
|
+ final int upperZ = Mth.floor(sourcePosition.getZ() + range) >> 4;
|
|
+
|
|
+ // Vanilla iterates by x until max is reached then increases z
|
|
+ // vanilla also searches by increasing Y section value
|
|
+ for (int currZ = lowerZ; currZ <= upperZ; ++currZ) {
|
|
+ for (int currX = lowerX; currX <= upperX; ++currX) {
|
|
+ for (int currY = lowerY; currY <= upperY; ++currY) { // vanilla searches the entire chunk because they're actually stupid. just search the sections we need
|
|
+ final Optional<PoiSection> poiSectionOptional = load ? poiStorage.getOrLoad(CoordinateUtils.getChunkSectionKey(currX, currY, currZ)) :
|
|
+ poiStorage.get(CoordinateUtils.getChunkSectionKey(currX, currY, currZ));
|
|
+ final PoiSection poiSection = poiSectionOptional == null ? null : poiSectionOptional.orElse(null);
|
|
+ if (poiSection == null) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final Map<Holder<PoiType>, Set<PoiRecord>> sectionData = poiSection.getData();
|
|
+ if (sectionData.isEmpty()) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // now we search the section data
|
|
+ for (final Map.Entry<Holder<PoiType>, Set<PoiRecord>> entry : sectionData.entrySet()) {
|
|
+ if (!villagePlaceType.test(entry.getKey())) {
|
|
+ // filter out by poi type
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // now we can look at the poi data
|
|
+ for (final PoiRecord poiData : entry.getValue()) {
|
|
+ if (!occupancyFilter.test(poiData)) {
|
|
+ // filter by occupancy
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final BlockPos poiPosition = poiData.getPos();
|
|
+
|
|
+ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range
|
|
+ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) {
|
|
+ // out of range for square radius
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (poiPosition.distSqr(sourcePosition) > rangeSquared) {
|
|
+ // out of range for distance check
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (positionPredicate != null && !positionPredicate.test(poiPosition)) {
|
|
+ // filter by position
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // found one!
|
|
+ ret.add(poiData);
|
|
+ if (++added >= max) {
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private PoiAccess() {
|
|
+ throw new RuntimeException();
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
|
|
index e8aa27547e3fa1a42720889c7038d4fb0273e7b5..e1b6fe9ecda25f86431baf414f1bfd3a26a8b2bd 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
|
|
@@ -71,11 +71,11 @@ public class AcquirePoi {
|
|
return true;
|
|
}
|
|
};
|
|
- Set<Pair<Holder<PoiType>, BlockPos>> set = poiManager.findAllClosestFirstWithType(
|
|
- poiPredicate, predicate2, entity.blockPosition(), 48, PoiManager.Occupancy.HAS_SPACE
|
|
- )
|
|
- .limit(5L)
|
|
- .collect(Collectors.toSet());
|
|
+ // Paper start - optimise POI access
|
|
+ java.util.List<Pair<Holder<PoiType>, BlockPos>> poiposes = new java.util.ArrayList<>();
|
|
+ io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, poiPredicate, predicate2, entity.blockPosition(), 48, 48*48, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes);
|
|
+ Set<Pair<Holder<PoiType>, BlockPos>> set = new java.util.HashSet<>(poiposes);
|
|
+ // Paper end - optimise POI access
|
|
Path path = findPathToPois(entity, set);
|
|
if (path != null && path.canReach()) {
|
|
BlockPos blockPos = path.getTarget();
|
|
diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
|
|
index d5a549f08b98c80a5cf0eef02cb8a389c32dfecb..92731b6b593289e9f583c9b705b219e81fcd8e73 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
|
|
@@ -53,11 +53,12 @@ public class NearestBedSensor extends Sensor<Mob> {
|
|
return true;
|
|
}
|
|
};
|
|
- Set<Pair<Holder<PoiType>, BlockPos>> set = poiManager.findAllWithType(
|
|
- holder -> holder.is(PoiTypes.HOME), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY
|
|
- )
|
|
- .collect(Collectors.toSet());
|
|
- Path path = AcquirePoi.findPathToPois(entity, set);
|
|
+ // Paper start - optimise POI access
|
|
+ java.util.List<Pair<Holder<PoiType>, BlockPos>> poiposes = new java.util.ArrayList<>();
|
|
+ // don't ask me why it's unbounded. ask mojang.
|
|
+ io.papermc.paper.util.PoiAccess.findAnyPoiPositions(poiManager, type -> type.is(PoiTypes.HOME), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY, false, Integer.MAX_VALUE, poiposes);
|
|
+ Path path = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes));
|
|
+ // Paper end - optimise POI access
|
|
if (path != null && path.canReach()) {
|
|
BlockPos blockPos = path.getTarget();
|
|
Optional<Holder<PoiType>> optional = poiManager.getType(blockPos);
|
|
diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
|
|
index 7b52b0507cbda76aee1db954641f397bef51f94d..c6f193339fdcbcc938d4eafdcad0b112cf1698d5 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
|
|
@@ -138,36 +138,45 @@ public class PoiManager extends SectionStorage<PoiSection> {
|
|
public Optional<BlockPos> find(
|
|
Predicate<Holder<PoiType>> typePredicate, Predicate<BlockPos> posPredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus
|
|
) {
|
|
- return this.findAll(typePredicate, posPredicate, pos, radius, occupationStatus).findFirst();
|
|
+ // Paper start - re-route to faster logic
|
|
+ BlockPos ret = io.papermc.paper.util.PoiAccess.findAnyPoiPosition(this, typePredicate, posPredicate, pos, radius, occupationStatus, false);
|
|
+ return Optional.ofNullable(ret);
|
|
+ // Paper end
|
|
}
|
|
|
|
public Optional<BlockPos> findClosest(Predicate<Holder<PoiType>> typePredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus) {
|
|
- return this.getInRange(typePredicate, pos, radius, occupationStatus)
|
|
- .map(PoiRecord::getPos)
|
|
- .min(Comparator.comparingDouble(blockPos2 -> blockPos2.distSqr(pos)));
|
|
+ // Paper start - re-route to faster logic
|
|
+ BlockPos ret = io.papermc.paper.util.PoiAccess.findClosestPoiDataPosition(this, typePredicate, null, pos, radius, radius * radius, occupationStatus, false);
|
|
+ return Optional.ofNullable(ret);
|
|
+ // Paper end - re-route to faster logic
|
|
}
|
|
|
|
public Optional<Pair<Holder<PoiType>, BlockPos>> findClosestWithType(
|
|
Predicate<Holder<PoiType>> typePredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus
|
|
) {
|
|
- return this.getInRange(typePredicate, pos, radius, occupationStatus)
|
|
- .min(Comparator.comparingDouble(poi -> poi.getPos().distSqr(pos)))
|
|
- .map(poi -> Pair.of(poi.getPoiType(), poi.getPos()));
|
|
+ // Paper start - re-route to faster logic
|
|
+ return Optional.ofNullable(io.papermc.paper.util.PoiAccess.findClosestPoiDataTypeAndPosition(
|
|
+ this, typePredicate, null, pos, radius, radius * radius, occupationStatus, false
|
|
+ ));
|
|
+ // Paper end - re-route to faster logic
|
|
}
|
|
|
|
public Optional<BlockPos> findClosest(
|
|
Predicate<Holder<PoiType>> typePredicate, Predicate<BlockPos> posPredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus
|
|
) {
|
|
- return this.getInRange(typePredicate, pos, radius, occupationStatus)
|
|
- .map(PoiRecord::getPos)
|
|
- .filter(posPredicate)
|
|
- .min(Comparator.comparingDouble(blockPos2 -> blockPos2.distSqr(pos)));
|
|
+ // Paper start - re-route to faster logic
|
|
+ BlockPos ret = io.papermc.paper.util.PoiAccess.findClosestPoiDataPosition(this, typePredicate, posPredicate, pos, radius, radius * radius, occupationStatus, false);
|
|
+ return Optional.ofNullable(ret);
|
|
+ // Paper end - re-route to faster logic
|
|
}
|
|
|
|
public Optional<BlockPos> take(Predicate<Holder<PoiType>> typePredicate, BiPredicate<Holder<PoiType>, BlockPos> biPredicate, BlockPos pos, int radius) {
|
|
- return this.getInRange(typePredicate, pos, radius, PoiManager.Occupancy.HAS_SPACE)
|
|
- .filter(poi -> biPredicate.test(poi.getPoiType(), poi.getPos()))
|
|
- .findFirst()
|
|
+ // Paper start - re-route to faster logic
|
|
+ final @javax.annotation.Nullable PoiRecord closest = io.papermc.paper.util.PoiAccess.findClosestPoiDataRecord(
|
|
+ this, typePredicate, biPredicate, pos, radius, radius * radius, Occupancy.HAS_SPACE, false
|
|
+ );
|
|
+ return Optional.ofNullable(closest)
|
|
+ // Paper end - re-route to faster logic
|
|
.map(poi -> {
|
|
poi.acquireTicket();
|
|
return poi.getPos();
|
|
@@ -182,8 +191,21 @@ public class PoiManager extends SectionStorage<PoiSection> {
|
|
int radius,
|
|
RandomSource random
|
|
) {
|
|
- List<PoiRecord> list = Util.toShuffledList(this.getInRange(typePredicate, pos, radius, occupationStatus), random);
|
|
- return list.stream().filter(poi -> positionPredicate.test(poi.getPos())).findFirst().map(PoiRecord::getPos);
|
|
+ // Paper start - re-route to faster logic
|
|
+ List<PoiRecord> list = new java.util.ArrayList<>();
|
|
+ io.papermc.paper.util.PoiAccess.findAnyPoiRecords(
|
|
+ this, typePredicate, positionPredicate, pos, radius, occupationStatus, false, Integer.MAX_VALUE, list
|
|
+ );
|
|
+
|
|
+ // the old method shuffled the list and then tried to find the first element in it that
|
|
+ // matched positionPredicate, however we moved positionPredicate into the poi search. This means we can avoid a
|
|
+ // shuffle entirely, and just pick a random element from list
|
|
+ if (list.isEmpty()) {
|
|
+ return Optional.empty();
|
|
+ }
|
|
+
|
|
+ return Optional.of(list.get(random.nextInt(list.size())).getPos());
|
|
+ // Paper end - re-route to faster logic
|
|
}
|
|
|
|
public boolean release(BlockPos pos) {
|
|
diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java
|
|
index 5b7deae326228e482b218aeebd857a59b7434eaf..4ee7d75c56d9f9ff3607276857dde84410ba3f2a 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java
|
|
@@ -26,7 +26,7 @@ import org.slf4j.Logger;
|
|
public class PoiSection {
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
private final Short2ObjectMap<PoiRecord> records = new Short2ObjectOpenHashMap<>();
|
|
- private final Map<Holder<PoiType>, Set<PoiRecord>> byType = Maps.newHashMap();
|
|
+ private final Map<Holder<PoiType>, Set<PoiRecord>> byType = Maps.newHashMap(); public final Map<Holder<PoiType>, Set<PoiRecord>> getData() { return this.byType; } // Paper - public accessor
|
|
private final Runnable setDirty;
|
|
private boolean isValid;
|
|
public final Optional<PoiSection> noAllocateOptional = Optional.of(this); // Paper - rewrite chunk system
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
|
|
index 883fbe5c81e3be27007a1a0489f80ba1863e5a04..a4a919d8373f1535e336de7e648d41a07efb1cba 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
|
|
@@ -74,11 +74,11 @@ public class SectionStorage<R> extends RegionFileStorage implements AutoCloseabl
|
|
}
|
|
|
|
@Nullable
|
|
- protected Optional<R> get(long pos) {
|
|
+ public Optional<R> get(long pos) { // Paper - public
|
|
return this.storage.get(pos);
|
|
}
|
|
|
|
- protected Optional<R> getOrLoad(long pos) {
|
|
+ public Optional<R> getOrLoad(long pos) { // Paper - public
|
|
if (this.outsideStoredRange(pos)) {
|
|
return Optional.empty();
|
|
} else {
|
|
diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java
|
|
index a61959700d5e00739a79eaa617ac383160335f26..2407d93c11b806701fc7d192f39d535128281e80 100644
|
|
--- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java
|
|
+++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java
|
|
@@ -51,18 +51,39 @@ public class PortalForcer {
|
|
// int i = flag ? 16 : 128;
|
|
// CraftBukkit end
|
|
|
|
- villageplace.ensureLoadedAndValid(this.level, blockposition, i);
|
|
- Optional<PoiRecord> optional = villageplace.getInSquare((holder) -> {
|
|
- return holder.is(PoiTypes.NETHER_PORTAL);
|
|
- }, blockposition, i, PoiManager.Occupancy.ANY).filter((villageplacerecord) -> {
|
|
- return worldborder.isWithinBounds(villageplacerecord.getPos()) && !(this.level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> villageplacerecord.getPos().getY() >= v)); // Paper - Configurable nether ceiling damage
|
|
- }).sorted(Comparator.comparingDouble((PoiRecord villageplacerecord) -> { // CraftBukkit - decompile error
|
|
- return villageplacerecord.getPos().distSqr(blockposition);
|
|
- }).thenComparingInt((villageplacerecord) -> {
|
|
- return villageplacerecord.getPos().getY();
|
|
- })).filter((villageplacerecord) -> {
|
|
- return this.level.getBlockState(villageplacerecord.getPos()).hasProperty(BlockStateProperties.HORIZONTAL_AXIS);
|
|
- }).findFirst();
|
|
+ // Paper start - optimise portals
|
|
+ Optional<PoiRecord> optional;
|
|
+ java.util.List<PoiRecord> records = new java.util.ArrayList<>();
|
|
+ io.papermc.paper.util.PoiAccess.findClosestPoiDataRecords(
|
|
+ villageplace,
|
|
+ type -> type.is(PoiTypes.NETHER_PORTAL),
|
|
+ (BlockPos pos) -> {
|
|
+ net.minecraft.world.level.chunk.ChunkAccess lowest = this.level.getChunk(pos.getX() >> 4, pos.getZ() >> 4, net.minecraft.world.level.chunk.status.ChunkStatus.EMPTY);
|
|
+ if (!lowest.getStatus().isOrAfter(net.minecraft.world.level.chunk.status.ChunkStatus.FULL)
|
|
+ && (lowest.getBelowZeroRetrogen() == null || !lowest.getBelowZeroRetrogen().targetStatus().isOrAfter(net.minecraft.world.level.chunk.status.ChunkStatus.SPAWN))) {
|
|
+ // why would we generate the chunk?
|
|
+ return false;
|
|
+ }
|
|
+ if (!worldborder.isWithinBounds(pos) || (this.level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> pos.getY() >= v))) { // Paper - Configurable nether ceiling damage
|
|
+ return false;
|
|
+ }
|
|
+ return lowest.getBlockState(pos).hasProperty(BlockStateProperties.HORIZONTAL_AXIS);
|
|
+ },
|
|
+ blockposition, i, Double.MAX_VALUE, PoiManager.Occupancy.ANY, true, records
|
|
+ );
|
|
+
|
|
+ // this gets us most of the way there, but we bias towards lower y values.
|
|
+ PoiRecord lowestYRecord = null;
|
|
+ for (PoiRecord record : records) {
|
|
+ if (lowestYRecord == null) {
|
|
+ lowestYRecord = record;
|
|
+ } else if (lowestYRecord.getPos().getY() > record.getPos().getY()) {
|
|
+ lowestYRecord = record;
|
|
+ }
|
|
+ }
|
|
+ // now we're done
|
|
+ optional = Optional.ofNullable(lowestYRecord);
|
|
+ // Paper end - optimise portals
|
|
|
|
return optional.map((villageplacerecord) -> {
|
|
BlockPos blockposition1 = villageplacerecord.getPos();
|