mirror of
https://github.com/PaperMC/Paper.git
synced 2024-08-30 16:49:42 +02:00
998 lines
52 KiB
Diff
998 lines
52 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..0a88c60161b04a733151c15046358f4b3b8b3280
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/util/PoiAccess.java
|
|
@@ -0,0 +1,748 @@
|
|
+package io.papermc.paper.util;
|
|
+
|
|
+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 net.minecraft.core.BlockPos;
|
|
+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<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 maxDistance,
|
|
+ final PoiManager.Occupancy occupancy,
|
|
+ final boolean load) {
|
|
+ final PoiRecord ret = findClosestPoiDataRecord(
|
|
+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load
|
|
+ );
|
|
+
|
|
+ return ret == null ? null : 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<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 maxDistance,
|
|
+ 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, maxDistance, 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<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 maxDistance,
|
|
+ final PoiManager.Occupancy occupancy,
|
|
+ final boolean load) {
|
|
+ final List<PoiRecord> ret = new ArrayList<>();
|
|
+ findClosestPoiDataRecords(
|
|
+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, 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<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 maxDistance,
|
|
+ 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 = maxDistance * maxDistance;
|
|
+
|
|
+ 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 LongArrayFIFOQueue queue = new LongArrayFIFOQueue();
|
|
+ queue.enqueue(CoordinateUtils.getChunkSectionKey(centerX, centerY, centerZ));
|
|
+ final LongOpenHashSet seen = new LongOpenHashSet();
|
|
+
|
|
+ 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.orElse(null);
|
|
+
|
|
+ final Map<PoiType, Set<PoiRecord>> sectionData = poiSection.getData();
|
|
+ if (sectionData.isEmpty()) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // now we search the section data
|
|
+ for (final Map.Entry<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 (positionPredicate != null && !positionPredicate.test(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<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 maxDistance,
|
|
+ final PoiManager.Occupancy occupancy,
|
|
+ final boolean load) {
|
|
+ final PoiRecord ret = findNearestPoiRecord(
|
|
+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load
|
|
+ );
|
|
+ return ret == null ? null : ret.getPos();
|
|
+ }
|
|
+
|
|
+ // finds the closest `max` poi entry positions.
|
|
+ public static void findNearestPoiPositions(final PoiManager poiStorage,
|
|
+ final Predicate<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 maxDistance,
|
|
+ final PoiManager.Occupancy occupancy,
|
|
+ final boolean load,
|
|
+ final int max,
|
|
+ final List<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, maxDistance, occupancy, load, max, toConvert
|
|
+ );
|
|
+
|
|
+ for (final PoiRecord record : toConvert) {
|
|
+ ret.add(record.getPos());
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // finds the closest poi entry.
|
|
+ public static PoiRecord findNearestPoiRecord(final PoiManager poiStorage,
|
|
+ final Predicate<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 maxDistance,
|
|
+ final PoiManager.Occupancy occupancy,
|
|
+ final boolean load) {
|
|
+ final List<PoiRecord> ret = new ArrayList<>();
|
|
+ findNearestPoiRecords(
|
|
+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, 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<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 maxDistance,
|
|
+ final PoiManager.Occupancy occupancy,
|
|
+ final boolean load,
|
|
+ final int max,
|
|
+ final List<PoiRecord> ret) {
|
|
+ final Predicate<? super PoiRecord> occupancyFilter = occupancy.getTest();
|
|
+
|
|
+ final double maxDistanceSquared = maxDistance * maxDistance;
|
|
+ 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 LongArrayFIFOQueue queue = new LongArrayFIFOQueue();
|
|
+ queue.enqueue(CoordinateUtils.getChunkSectionKey(centerX, centerY, centerZ));
|
|
+ final LongOpenHashSet seen = new LongOpenHashSet();
|
|
+
|
|
+ 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.orElse(null);
|
|
+
|
|
+ final Map<PoiType, Set<PoiRecord>> sectionData = poiSection.getData();
|
|
+ if (sectionData.isEmpty()) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // now we search the section data
|
|
+ for (final Map.Entry<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<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<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<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(record.getPos());
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static PoiRecord findAnyPoiRecord(final PoiManager poiStorage,
|
|
+ final Predicate<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<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<PoiType, Set<PoiRecord>> sectionData = poiSection.getData();
|
|
+ if (sectionData.isEmpty()) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // now we search the section data
|
|
+ for (final Map.Entry<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 84a0ee595bebcc1947c602c4c06e7437706ce37c..afbb2acd27416c801af3d718850b82a170734cd3 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
|
|
@@ -83,7 +83,11 @@ public class AcquirePoi extends Behavior<PathfinderMob> {
|
|
return true;
|
|
}
|
|
};
|
|
- Set<BlockPos> set = poiManager.findAllClosestFirst(this.poiType.getPredicate(), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.HAS_SPACE).limit(5L).collect(Collectors.toSet());
|
|
+ // Paper start - optimise POI access
|
|
+ java.util.List<BlockPos> poiposes = new java.util.ArrayList<>();
|
|
+ io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, this.poiType.getPredicate(), predicate, entity.blockPosition(), 48, 48*48, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes);
|
|
+ Set<BlockPos> set = new java.util.HashSet<>(poiposes);
|
|
+ // Paper end - optimise POI access
|
|
Path path = entity.getNavigation().createPath(set, this.poiType.getValidRange());
|
|
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 0eea3e39616e40e15d1662b973c097cda3b2cee7..3ccc1421f4a5a08dadb9fe3c9fa3ac3131e6ba1e 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
|
|
@@ -49,8 +49,12 @@ public class NearestBedSensor extends Sensor<Mob> {
|
|
return true;
|
|
}
|
|
};
|
|
- Stream<BlockPos> stream = poiManager.findAll(PoiType.HOME.getPredicate(), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY);
|
|
- Path path = entity.getNavigation().createPath(stream, PoiType.HOME.getValidRange());
|
|
+ // Paper start - optimise POI access
|
|
+ java.util.List<BlockPos> poiposes = new java.util.ArrayList<>();
|
|
+ // don't ask me why it's unbounded. ask mojang.
|
|
+ io.papermc.paper.util.PoiAccess.findAnyPoiPositions(poiManager, PoiType.HOME.getPredicate(), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY, false, Integer.MAX_VALUE, poiposes);
|
|
+ Path path = entity.getNavigation().createPath(new java.util.HashSet<>(poiposes), PoiType.HOME.getValidRange());
|
|
+ // Paper end - optimise POI access
|
|
if (path != null && path.canReach()) {
|
|
BlockPos blockPos = path.getTarget();
|
|
Optional<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 4a972b26242cf4c9d7e8f655cb1264cddad5f143..8a569e3300543cb171c3befae59969628adc424c 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
|
|
@@ -37,7 +37,7 @@ public class PoiManager extends SectionStorage<PoiSection> {
|
|
public static final int VILLAGE_SECTION_SIZE = 1;
|
|
private final PoiManager.DistanceTracker distanceTracker;
|
|
private final LongSet loadedChunks = new LongOpenHashSet();
|
|
- private final net.minecraft.server.level.ServerLevel world; // Paper
|
|
+ public final net.minecraft.server.level.ServerLevel world; // Paper // Paper public
|
|
|
|
public PoiManager(Path path, DataFixer dataFixer, boolean dsync, LevelHeightAccessor world) {
|
|
super(path, PoiSection::codec, PoiSection::new, dataFixer, DataFixTypes.POI_CHUNK, dsync, world);
|
|
@@ -100,36 +100,55 @@ public class PoiManager extends SectionStorage<PoiSection> {
|
|
}
|
|
|
|
public Optional<BlockPos> find(Predicate<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 - re-route to faster logic
|
|
}
|
|
|
|
public Optional<BlockPos> findClosest(Predicate<PoiType> typePredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus) {
|
|
- return this.getInRange(typePredicate, pos, radius, occupationStatus).map(PoiRecord::getPos).min(Comparator.comparingDouble((blockPos2) -> {
|
|
- return 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<BlockPos> findClosest(Predicate<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) -> {
|
|
- return 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<PoiType> typePredicate, Predicate<BlockPos> positionPredicate, BlockPos pos, int radius) {
|
|
- return this.getInRange(typePredicate, pos, radius, PoiManager.Occupancy.HAS_SPACE).filter((poi) -> {
|
|
- return positionPredicate.test(poi.getPos());
|
|
- }).findFirst().map((poi) -> {
|
|
- poi.acquireTicket();
|
|
- return poi.getPos();
|
|
- });
|
|
+ // Paper start - re-route to faster logic
|
|
+ PoiRecord ret = io.papermc.paper.util.PoiAccess.findAnyPoiRecord(
|
|
+ this, typePredicate, positionPredicate, pos, radius, PoiManager.Occupancy.HAS_SPACE, false
|
|
+ );
|
|
+ if (ret == null) {
|
|
+ return Optional.empty();
|
|
+ }
|
|
+ ret.acquireTicket();
|
|
+ return Optional.of(ret.getPos());
|
|
+ // Paper end - re-route to faster logic
|
|
}
|
|
|
|
public Optional<BlockPos> getRandom(Predicate<PoiType> typePredicate, Predicate<BlockPos> positionPredicate, PoiManager.Occupancy occupationStatus, BlockPos pos, int radius, Random random) {
|
|
- List<PoiRecord> list = this.getInRange(typePredicate, pos, radius, occupationStatus).collect(Collectors.toList());
|
|
- Collections.shuffle(list, random);
|
|
- return list.stream().filter((poi) -> {
|
|
- return 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 63f283f32bdad02299d4a16c305a28c3bfbce9a8..de94f25792261c6c89986ad3dee3255c2a89357b 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
|
|
@@ -25,7 +25,7 @@ import org.apache.logging.log4j.Logger;
|
|
public class PoiSection {
|
|
private static final Logger LOGGER = LogManager.getLogger();
|
|
private final Short2ObjectMap<PoiRecord> records = new Short2ObjectOpenHashMap<>();
|
|
- private final Map<PoiType, Set<PoiRecord>> byType = Maps.newHashMap();
|
|
+ private final Map<PoiType, Set<PoiRecord>> byType = Maps.newHashMap(); public final Map<PoiType, Set<PoiRecord>> getData() { return this.byType; } // Paper - public accessor
|
|
private final Runnable setDirty;
|
|
private boolean isValid;
|
|
|
|
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 ff6cadec530dedf9efc5d6226e48a096a1073ad6..d73b99d7fde724da4503b5176c3ad7b013197c6a 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
|
|
@@ -61,11 +61,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 ed79058696eb26a89b9d4116821840dbad9ea449..d990d1652b71205816d678618bf360a60f309ad2 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,40 @@ public class PortalForcer {
|
|
// int i = flag ? 16 : 128;
|
|
// CraftBukkit end
|
|
|
|
- villageplace.ensureLoadedAndValid(this.level, blockposition, i);
|
|
- Optional<PoiRecord> optional = villageplace.getInSquare((villageplacetype) -> {
|
|
- return villageplacetype == PoiType.NETHER_PORTAL;
|
|
- }, blockposition, i, PoiManager.Occupancy.ANY).filter((villageplacerecord) -> {
|
|
- return worldborder.isWithinBounds(villageplacerecord.getPos());
|
|
- }).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,
|
|
+ (PoiType type) -> {
|
|
+ return type == PoiType.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.ChunkStatus.EMPTY);
|
|
+ if (!lowest.getStatus().isOrAfter(net.minecraft.world.level.chunk.ChunkStatus.FULL)) {
|
|
+ // why would we generate the chunk?
|
|
+ return false;
|
|
+ }
|
|
+ if (!worldborder.isWithinBounds(pos)) {
|
|
+ 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();
|