diff --git a/patches/server/0001-Tuinity-Server-Changes.patch b/patches/server/0001-Tuinity-Server-Changes.patch index bea128fef..fcbf9cf73 100644 --- a/patches/server/0001-Tuinity-Server-Changes.patch +++ b/patches/server/0001-Tuinity-Server-Changes.patch @@ -6981,6 +6981,760 @@ index 0000000000000000000000000000000000000000..d2c7d2c7920324d7207225ed19484e80 + return tail >= head ? (tail - head) : (tail + (this.times.length - head)); + } +} +diff --git a/src/main/java/com/tuinity/tuinity/util/PoiAccess.java b/src/main/java/com/tuinity/tuinity/util/PoiAccess.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e99583529a2cbdf8b764be3dff4373ec0ffaecd7 +--- /dev/null ++++ b/src/main/java/com/tuinity/tuinity/util/PoiAccess.java +@@ -0,0 +1,748 @@ ++package com.tuinity.tuinity.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 villagePlaceType, ++ // position predicate must not modify chunk POI ++ final Predicate 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 villagePlaceType, ++ // position predicate must not modify chunk POI ++ final Predicate 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 ret) { ++ final Set positions = new HashSet<>(); ++ // pos predicate is last thing that runs before adding to ret. ++ final Predicate newPredicate = (final BlockPos pos) -> { ++ if (positionPredicate != null && !positionPredicate.test(pos)) { ++ return false; ++ } ++ return positions.add(pos.immutable()); ++ }; ++ ++ final List 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 villagePlaceType, ++ // position predicate must not modify chunk POI ++ final Predicate 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 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 villagePlaceType, ++ // position predicate must not modify chunk POI ++ final Predicate 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 ret) { ++ final Predicate occupancyFilter = occupancy.getTest(); ++ ++ final List 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 = sourcePosition.getY() >> 4; ++ 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 poiSectionOptional = load ? poiStorage.getOrLoad(key) : poiStorage.get(key); ++ ++ if (poiSectionOptional == null || !poiSectionOptional.isPresent()) { ++ continue; ++ } ++ ++ final PoiSection poiSection = poiSectionOptional.orElse(null); ++ ++ final Map> sectionData = poiSection.getData(); ++ if (sectionData.isEmpty()) { ++ continue; ++ } ++ ++ // now we search the section data ++ for (final Map.Entry> 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 villagePlaceType, ++ // position predicate must not modify chunk POI ++ final Predicate 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 villagePlaceType, ++ // position predicate must not modify chunk POI ++ final Predicate 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 ret) { ++ final Set positions = new HashSet<>(); ++ // pos predicate is last thing that runs before adding to ret. ++ final Predicate newPredicate = (final BlockPos pos) -> { ++ if (positionPredicate != null && !positionPredicate.test(pos)) { ++ return false; ++ } ++ return positions.add(pos.immutable()); ++ }; ++ ++ final List 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 villagePlaceType, ++ // position predicate must not modify chunk POI ++ final Predicate 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 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 villagePlaceType, ++ // position predicate must not modify chunk POI ++ final Predicate 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 ret) { ++ final Predicate occupancyFilter = occupancy.getTest(); ++ ++ final double maxDistanceSquared = maxDistance * maxDistance; ++ final Double2ObjectRBTreeMap> 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 = sourcePosition.getY() >> 4; ++ 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 poiSectionOptional = load ? poiStorage.getOrLoad(key) : poiStorage.get(key); ++ ++ if (poiSectionOptional == null || !poiSectionOptional.isPresent()) { ++ continue; ++ } ++ ++ final PoiSection poiSection = poiSectionOptional.orElse(null); ++ ++ final Map> sectionData = poiSection.getData(); ++ if (sectionData.isEmpty()) { ++ continue; ++ } ++ ++ // now we search the section data ++ for (final Map.Entry> 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>> iterator = closestRecords.double2ObjectEntrySet().iterator(); ++ double nextFurthestDistanceSquared = 0.0; ++ ++ for (int i = 0, len = closestRecords.size() - 1; i < len; ++i) { ++ final Double2ObjectMap.Entry> 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> recordEntry = iterator.next(); ++ totalRecords -= recordEntry.getValue().size(); ++ iterator.remove(); ++ ++ furthestDistanceSquared = nextFurthestDistanceSquared; ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ final List closestRecordsUnsorted = new ArrayList<>(); ++ ++ // we're done here, so now just flatten the map and sort it. ++ ++ for (final List 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 villagePlaceType, ++ final Predicate 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 villagePlaceType, ++ final Predicate 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 ret) { ++ final Set positions = new HashSet<>(); ++ // pos predicate is last thing that runs before adding to ret. ++ final Predicate newPredicate = (final BlockPos pos) -> { ++ if (positionPredicate != null && !positionPredicate.test(pos)) { ++ return false; ++ } ++ return positions.add(pos.immutable()); ++ }; ++ ++ final List 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 villagePlaceType, ++ final Predicate positionPredicate, ++ final BlockPos sourcePosition, ++ final int range, // distance on x y z axis ++ final PoiManager.Occupancy occupancy, ++ final boolean load) { ++ final List 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 villagePlaceType, ++ final Predicate 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 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 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 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> sectionData = poiSection.getData(); ++ if (sectionData.isEmpty()) { ++ continue; ++ } ++ ++ // now we search the section data ++ for (final Map.Entry> 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/com/tuinity/tuinity/util/TickThread.java b/src/main/java/com/tuinity/tuinity/util/TickThread.java new file mode 100644 index 0000000000000000000000000000000000000000..a08377e4b0d9c2d78cf851e2c72770cf623de51a @@ -10806,7 +11560,7 @@ index 9fe60d058ea1702930981dbd06093dc594e6bf8e..2dd5909a08a8d4bc250b36d297ef9f3f } }).exceptionally((throwable) -> { diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf8d0fea91 100644 +index 6ed95a0cff3e9c874f14bc90283f750e15765c67..509a4239dbda8d8d7edebfdc92bed84a13def369 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -103,6 +103,7 @@ import org.apache.logging.log4j.LogManager; @@ -10988,7 +11742,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf } void updateMaps(ServerPlayer player) { -@@ -316,27 +295,50 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -316,27 +295,125 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i]; int trackRange = this.entityTrackerTrackRanges[i]; @@ -11011,22 +11765,93 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf + // Tuinity start + public final List regionManagers = new java.util.ArrayList<>(); + public final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager dataRegionManager; -+ + +- if (!this.skipPlayer(player)) { +- this.playerViewDistanceTickMap.update(player, chunkX, chunkZ, effectiveTickViewDistance); +- this.playerViewDistanceNoTickMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 2); // clients need chunk 1 neighbour, and we need another 1 for sending those extra neighbours (as we require neighbours to send) + public static final class DataRegionData implements com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionData { -+ } ++ // Tuinity start - optimise notify() ++ private com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet navigators; ++ ++ public com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet getNavigators() { ++ return this.navigators; ++ } ++ ++ public boolean addToNavigators(final Mob navigator) { ++ if (this.navigators == null) { ++ this.navigators = new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(); ++ } ++ return this.navigators.add(navigator); + } + +- player.needsChunkCenterUpdate = true; +- this.playerViewDistanceBroadcastMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need an extra neighbour to render the full view distance configured +- player.needsChunkCenterUpdate = false; +- // Paper end - no-tick view distance ++ public boolean removeFromNavigators(final Mob navigator) { ++ if (this.navigators == null) { ++ return false; ++ } ++ return this.navigators.remove(navigator); ++ } ++ // Tuinity end - optimise notify() + } +- // Paper end + + public static final class DataRegionSectionData implements com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSectionData { + ++ // Tuinity start - optimise notify() ++ private com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet navigators; ++ ++ public com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet getNavigators() { ++ return this.navigators; ++ } ++ ++ public boolean addToNavigators(final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section, final Mob navigator) { ++ if (this.navigators == null) { ++ this.navigators = new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(); ++ } ++ final boolean ret = this.navigators.add(navigator); ++ if (ret) { ++ final DataRegionData data = (DataRegionData)section.getRegion().regionData; ++ if (!data.addToNavigators(navigator)) { ++ throw new IllegalStateException(); ++ } ++ } ++ return ret; ++ } ++ ++ public boolean removeFromNavigators(final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section, final Mob navigator) { ++ if (this.navigators == null) { ++ return false; ++ } ++ final boolean ret = this.navigators.remove(navigator); ++ if (ret) { ++ final DataRegionData data = (DataRegionData)section.getRegion().regionData; ++ if (!data.removeFromNavigators(navigator)) { ++ throw new IllegalStateException(); ++ } ++ } ++ return ret; ++ } ++ // Tuinity end - optimise notify() ++ + @Override + public void removeFromRegion(final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section, + final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.Region from) { + final DataRegionSectionData sectionData = (DataRegionSectionData)section.sectionData; + final DataRegionData fromData = (DataRegionData)from.regionData; ++ // Tuinity start - optimise notify() ++ if (sectionData.navigators != null) { ++ for (final Iterator iterator = sectionData.navigators.unsafeIterator(com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { ++ if (!fromData.removeFromNavigators(iterator.next())) { ++ throw new IllegalStateException(); ++ } ++ } ++ } ++ // Tuinity end - optimise notify() + } - -- if (!this.skipPlayer(player)) { -- this.playerViewDistanceTickMap.update(player, chunkX, chunkZ, effectiveTickViewDistance); -- this.playerViewDistanceNoTickMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 2); // clients need chunk 1 neighbour, and we need another 1 for sending those extra neighbours (as we require neighbours to send) ++ + @Override + public void addToRegion(final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section, + final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.Region oldRegion, @@ -11034,24 +11859,28 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf + final DataRegionSectionData sectionData = (DataRegionSectionData)section.sectionData; + final DataRegionData oldRegionData = oldRegion == null ? null : (DataRegionData)oldRegion.regionData; + final DataRegionData newRegionData = (DataRegionData)newRegion.regionData; - } ++ // Tuinity start - optimise notify() ++ if (sectionData.navigators != null) { ++ for (final Iterator iterator = sectionData.navigators.unsafeIterator(com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { ++ if (!newRegionData.addToNavigators(iterator.next())) { ++ throw new IllegalStateException(); ++ } ++ } ++ } ++ // Tuinity end - optimise notify() ++ } + } - -- player.needsChunkCenterUpdate = true; -- this.playerViewDistanceBroadcastMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need an extra neighbour to render the full view distance configured -- player.needsChunkCenterUpdate = false; -- // Paper end - no-tick view distance ++ + public final ChunkHolder getUnloadingChunkHolder(int chunkX, int chunkZ) { + return this.pendingUnloads.get(com.tuinity.tuinity.util.CoordinateUtils.getChunkKey(chunkX, chunkZ)); - } -- // Paper end ++ } + // Tuiniy end + + boolean unloadingPlayerChunk = false; // Tuinity - do not allow ticket level changes while unloading chunks public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureManager structureManager, Executor executor, BlockableEventLoop mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory, int viewDistance, boolean dsync) { super(new File(session.getDimensionPath(world.dimension()), "region"), dataFixer, dsync); -@@ -453,53 +455,28 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -453,53 +530,28 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } }); // Paper end - optimise PlayerChunkMap#isOutsideRange @@ -11070,14 +11899,14 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf - checkHighPriorityChunks(player); - if (newState.size() != 1) { - return; -- } -- LevelChunk chunk = ChunkMap.this.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(rangeX, rangeZ); -- if (chunk == null || !chunk.areNeighboursLoaded(2)) { -- return; + LevelChunk chunk = ChunkMap.this.level.getChunkSource().getChunkAtIfCachedImmediately(rangeX, rangeZ); + if (chunk != null) { + chunk.updateGeneralAreaCache(newState); } +- LevelChunk chunk = ChunkMap.this.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(rangeX, rangeZ); +- if (chunk == null || !chunk.areNeighboursLoaded(2)) { +- return; +- } - - ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ); - ChunkMap.this.level.getChunkSource().addTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update @@ -11119,7 +11948,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf } // Paper start - Chunk Prioritization -@@ -533,6 +510,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -533,6 +585,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public void checkHighPriorityChunks(ServerPlayer player) { @@ -11127,7 +11956,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf int currentTick = MinecraftServer.currentTick; if (currentTick - player.lastHighPriorityChecked < 20 || !player.isRealPlayer) { // weed out fake players return; -@@ -540,7 +518,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -540,7 +593,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider player.lastHighPriorityChecked = currentTick; it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap priorities = new it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap(); @@ -11136,7 +11965,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf net.minecraft.core.BlockPos.MutableBlockPos pos = new net.minecraft.core.BlockPos.MutableBlockPos(); // Prioritize circular near -@@ -606,7 +584,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -606,7 +659,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } private boolean shouldSkipPrioritization(ChunkPos coord) { @@ -11145,7 +11974,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf ChunkHolder chunk = getUpdatingChunkIfPresent(coord.toLong()); return chunk != null && (chunk.isFullChunkReady()); } -@@ -674,7 +652,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -674,7 +727,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @Nullable public ChunkHolder getUpdatingChunkIfPresent(long pos) { @@ -11154,7 +11983,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf } // Paper start - remove cloning of visible chunks unless accessed as a collection async -@@ -682,47 +660,25 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -682,47 +735,25 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider private boolean isIterating = false; private boolean hasPendingVisibleUpdate = false; public void forEachVisibleChunk(java.util.function.Consumer consumer) { @@ -11212,7 +12041,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf } protected IntSupplier getChunkQueueLevel(long pos) { -@@ -844,12 +800,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -844,12 +875,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @Nullable ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k) { @@ -11227,7 +12056,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf } if (holder != null) { -@@ -864,11 +821,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -864,11 +896,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider holder = (ChunkHolder) this.pendingUnloads.remove(pos); if (holder != null) { holder.setTicketLevel(level); @@ -11246,7 +12075,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf this.modified = true; } -@@ -1023,7 +986,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1023,7 +1061,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider while (longiterator.hasNext()) { // Spigot long j = longiterator.nextLong(); longiterator.remove(); // Spigot @@ -11255,7 +12084,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf if (playerchunk != null) { this.pendingUnloads.put(j, playerchunk); -@@ -1060,7 +1023,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1060,7 +1098,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.level, chunkPos.x, chunkPos.z, @@ -11264,7 +12093,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf if (!chunk.isUnsaved()) { return; -@@ -1081,7 +1044,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1081,7 +1119,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider asyncSaveData = ChunkSerializer.getAsyncSaveData(this.level, chunk); } @@ -11273,7 +12102,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf asyncSaveData, chunk); chunk.setUnsaved(false); -@@ -1097,7 +1060,19 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1097,7 +1135,19 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider if (completablefuture1 != completablefuture) { this.scheduleUnload(pos, holder); } else { @@ -11294,7 +12123,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf if (ichunkaccess instanceof LevelChunk) { ((LevelChunk) ichunkaccess).setLoaded(false); } -@@ -1122,7 +1097,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1122,7 +1172,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.lightEngine.updateChunkStatus(ichunkaccess.getPos()); this.lightEngine.tryScheduleUpdate(); this.progressListener.onStatusChange(ichunkaccess.getPos(), (ChunkStatus) null); @@ -11308,7 +12137,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf } }; -@@ -1141,19 +1122,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1141,19 +1197,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider if (!this.modified) { return false; } else { @@ -11332,7 +12161,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf this.modified = false; return true; -@@ -1166,11 +1139,20 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1166,11 +1214,20 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider if (requiredStatus == ChunkStatus.EMPTY) { return this.scheduleChunkLoad(chunkcoordintpair); } else { @@ -11355,7 +12184,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf if (optional.isPresent() && ((ChunkAccess) optional.get()).getStatus().isOrAfter(requiredStatus)) { CompletableFuture> completablefuture = requiredStatus.load(this.level, this.structureManager, this.lightEngine, (ichunkaccess) -> { -@@ -1182,6 +1164,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1182,6 +1239,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } else { return this.scheduleChunkGeneration(holder, requiredStatus); } @@ -11363,7 +12192,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf } } -@@ -1282,7 +1265,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1282,7 +1340,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider }); Executor executor = (runnable) -> { // Paper start - optimize chunk status progression without jumping through thread pool @@ -11372,7 +12201,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf this.mainInvokingExecutor.execute(runnable); return; } -@@ -1313,7 +1296,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1313,7 +1371,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.releaseLightTicket(chunkcoordintpair); return CompletableFuture.completedFuture(Either.right(playerchunk_failure)); }); @@ -11384,7 +12213,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf } protected void releaseLightTicket(ChunkPos pos) { -@@ -1472,9 +1458,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1472,9 +1533,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider chunk.unpackTicks(); return chunk; }); @@ -11395,7 +12224,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf } public int getTickingGenerated() { -@@ -1559,7 +1543,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1559,7 +1618,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider int k = this.viewDistance; this.viewDistance = j; @@ -11404,7 +12233,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf } } -@@ -1567,26 +1551,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1567,26 +1626,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider // Paper start - no-tick view distance public final void setNoTickViewDistance(int viewDistance) { viewDistance = viewDistance == -1 ? -1 : Mth.clamp(viewDistance, 2, 32); @@ -11433,7 +12262,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf if (player.level == this.level) { if (withinViewDistance && !withinMaxWatchDistance) { ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos.toLong()); -@@ -1610,7 +1579,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1610,7 +1654,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public int size() { @@ -11442,7 +12271,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf } protected DistanceManager getDistanceManager() { -@@ -1915,6 +1884,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1915,6 +1959,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider */ // Paper end - replaced by distance map this.updateMaps(player); // Paper - distance maps @@ -11450,7 +12279,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf } -@@ -1923,7 +1893,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1923,7 +1968,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider // Paper start - per player view distance // there can be potential desync with player's last mapped section and the view distance map, so use the // view distance map here. @@ -11459,7 +12288,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf if (inRange == null) { return Stream.empty(); -@@ -1939,8 +1909,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1939,8 +1984,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider continue; } ServerPlayer player = (ServerPlayer)temp; @@ -11471,7 +12300,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf int distX = Math.abs(MCUtil.getCoordinateX(lastPosition) - chunkPos.x); int distZ = Math.abs(MCUtil.getCoordinateZ(lastPosition) - chunkPos.z); -@@ -1955,6 +1926,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1955,6 +2001,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider continue; } ServerPlayer player = (ServerPlayer)temp; @@ -11479,7 +12308,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf players.add(player); } } -@@ -2265,7 +2237,7 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially +@@ -2265,7 +2312,7 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially final Entity entity; private final int range; SectionPos lastSectionPos; @@ -11488,7 +12317,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf public TrackedEntity(Entity entity, int i, int j, boolean flag) { this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit -@@ -2365,7 +2337,7 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially +@@ -2365,7 +2412,7 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially double vec3d_dy = player.getY() - this.entity.getY(); double vec3d_dz = player.getZ() - this.entity.getZ(); // Paper end - remove allocation of Vec3D here @@ -11497,7 +12326,7 @@ index 6ed95a0cff3e9c874f14bc90283f750e15765c67..c44faf838c0bc7da480afe0f5ccedcaf boolean flag = vec3d_dx >= (double) (-i) && vec3d_dx <= (double) i && vec3d_dz >= (double) (-i) && vec3d_dz <= (double) i && this.entity.broadcastToPlayer(player); // Paper - remove allocation of Vec3D here // CraftBukkit start - respect vanish API -@@ -2400,7 +2372,7 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially +@@ -2400,7 +2447,7 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially int j = entity.getType().clientTrackingRange() * 16; j = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, j); // Paper @@ -12218,7 +13047,7 @@ index 44aa0c4ec6f0e4df2541c74fa7de852dae59bda5..a00627e0fa38632449042f59c053b4da if (flag2) { packet1 = new ClientboundMoveEntityPacket.Pos(this.entity.getId(), (short) ((int) k), (short) ((int) l), (short) ((int) i1), this.entity.isOnGround()); diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 8154ca39ec7e2e8559cd125d73a59b8d2b00714c..a83b4e9b6b5bd6a57d4bc2f1cdc78ffb7e7d2fdd 100644 +index 8154ca39ec7e2e8559cd125d73a59b8d2b00714c..57163e3cb883ded5861e57c3ca03663c02ee7492 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -115,6 +115,7 @@ import net.minecraft.world.level.block.EntityBlock; @@ -12624,7 +13453,7 @@ index 8154ca39ec7e2e8559cd125d73a59b8d2b00714c..a83b4e9b6b5bd6a57d4bc2f1cdc78ffb timings.chunkTicksBlocks.stopTiming(); // Paper gameprofilerfiller.pop(); } -@@ -912,7 +1112,26 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl +@@ -912,7 +1112,27 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl } @@ -12643,6 +13472,7 @@ index 8154ca39ec7e2e8559cd125d73a59b8d2b00714c..a83b4e9b6b5bd6a57d4bc2f1cdc78ffb public void tickNonPassenger(Entity entity) { + // Tuinity start - log detailed entity tick information + com.tuinity.tuinity.util.TickThread.ensureTickThread("Cannot tick an entity off-main"); ++ this.entityManager.updateNavigatorsInRegion(entity); // Tuinity - optimise notify + try { + if (currentlyTickingEntity.get() == null) { + currentlyTickingEntity.lazySet(entity); @@ -12651,7 +13481,7 @@ index 8154ca39ec7e2e8559cd125d73a59b8d2b00714c..a83b4e9b6b5bd6a57d4bc2f1cdc78ffb ++TimingHistory.entityTicks; // Paper - timings // Spigot start co.aikar.timings.Timing timer; // Paper -@@ -953,7 +1172,13 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl +@@ -953,7 +1173,13 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl } // } finally { timer.stopTiming(); } // Paper - timings - move up @@ -12666,7 +13496,7 @@ index 8154ca39ec7e2e8559cd125d73a59b8d2b00714c..a83b4e9b6b5bd6a57d4bc2f1cdc78ffb } private void tickPassenger(Entity vehicle, Entity passenger) { -@@ -1245,9 +1470,13 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl +@@ -1245,9 +1471,13 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl // Spigot Start for (BlockEntity tileentity : chunk.getBlockEntities().values()) { if (tileentity instanceof net.minecraft.world.Container) { @@ -12681,6 +13511,53 @@ index 8154ca39ec7e2e8559cd125d73a59b8d2b00714c..a83b4e9b6b5bd6a57d4bc2f1cdc78ffb } } // Spigot End +@@ -1344,9 +1574,19 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + VoxelShape voxelshape1 = newState.getCollisionShape(this, pos); + + if (Shapes.joinIsNotEmpty(voxelshape, voxelshape1, BooleanOp.NOT_SAME)) { +- Iterator iterator = this.navigatingMobs.iterator(); ++ // Tuinity start - optimise notify() ++ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.Region region = this.getChunkSource().chunkMap.dataRegionManager.getRegion(pos.getX() >> 4, pos.getZ() >> 4); ++ if (region == null) { ++ return; ++ } ++ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet navigatorsFromRegion = ((ChunkMap.DataRegionData)region.regionData).getNavigators(); ++ if (navigatorsFromRegion == null) { ++ return; ++ } ++ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = navigatorsFromRegion.iterator(); + +- while (iterator.hasNext()) { ++ ++ try { while (iterator.hasNext()) { // Tuinity end - optimise notify() + // CraftBukkit start - fix SPIGOT-6362 + Mob entityinsentient; + try { +@@ -1365,6 +1605,11 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + navigationabstract.recomputePath(pos); + } + } ++ // Tuinity start - optimise notify() ++ } finally { ++ iterator.finishedIterating(); ++ } ++ // Tuinity end - optimise notify() + + } + } // Paper +@@ -2146,10 +2391,12 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + + public void onTickingStart(Entity entity) { + ServerLevel.this.entityTickList.add(entity); ++ ServerLevel.this.entityManager.addNavigatorsIfPathingToRegion(entity); // Tuinity - optimise notify + } + + public void onTickingEnd(Entity entity) { + ServerLevel.this.entityTickList.remove(entity); ++ ServerLevel.this.entityManager.removeNavigatorsFromData(entity); // Tuinity - optimise notify + } + + public void onTrackingStart(Entity entity) { diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java index a0acaac510aa2206a5c58f2b7aafdbc2bdf7a3dd..0da2dbeba93d428a035872e05177ed3fc29acf9b 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java @@ -14019,11 +14896,496 @@ index c4c5c35e37b793f3b74349ff03c0829f4913b91c..154b3c767d079f72643c826b962892c1 if (entityhuman != null) { double d0 = entityhuman.distanceToSqr((Entity) this); // CraftBukkit - decompile error +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..efe66264ad5717bf3aac0fbda07275fb5571acc1 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 { + return true; + } + }; +- Set set = poiManager.findAllClosestFirst(this.poiType.getPredicate(), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.HAS_SPACE).limit(5L).collect(Collectors.toSet()); ++ // Tuinity start - optimise POI access ++ java.util.List poiposes = new java.util.ArrayList<>(); ++ com.tuinity.tuinity.util.PoiAccess.findNearestPoiPositions(poiManager, this.poiType.getPredicate(), predicate, entity.blockPosition(), 48, 48*48, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes); ++ Set set = new java.util.HashSet<>(poiposes); ++ // Tuinity 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/behavior/GateBehavior.java b/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java +index 09998d160a6d79fdb5a5041a5d572649a1532e6a..3fe1f9bd4bb670d9a1ddabf2475f4d8f44d7e6fe 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java +@@ -30,11 +30,19 @@ public class GateBehavior extends Behavior { + + @Override + protected boolean canStillUse(ServerLevel world, E entity, long time) { +- return this.behaviors.stream().filter((behavior) -> { +- return behavior.getStatus() == Behavior.Status.RUNNING; +- }).anyMatch((behavior) -> { +- return behavior.canStillUse(world, entity, time); +- }); ++ // Tuinity start - remove streams ++ List>> entries = this.behaviors.entries; ++ for (int i = 0; i < entries.size(); i++) { ++ ShufflingList.WeightedEntry> entry = entries.get(i); ++ Behavior behavior = entry.getData(); ++ if (behavior.getStatus() == Status.RUNNING) { ++ if (behavior.canStillUse(world, entity, time)) { ++ return true; ++ } ++ } ++ } ++ return false; ++ // Tuinity end - remove streams + } + + @Override +@@ -45,25 +53,35 @@ public class GateBehavior extends Behavior { + @Override + protected void start(ServerLevel world, E entity, long time) { + this.orderPolicy.apply(this.behaviors); +- this.runningPolicy.apply(this.behaviors.stream(), world, entity, time); ++ this.runningPolicy.apply(this.behaviors.entries, world, entity, time); // Tuinity - remove streams + } + + @Override + protected void tick(ServerLevel world, E entity, long time) { +- this.behaviors.stream().filter((behavior) -> { +- return behavior.getStatus() == Behavior.Status.RUNNING; +- }).forEach((behavior) -> { +- behavior.tickOrStop(world, entity, time); +- }); ++ // Tuinity start - remove streams ++ List>> entries = this.behaviors.entries; ++ for (int i = 0; i < entries.size(); i++) { ++ ShufflingList.WeightedEntry> entry = entries.get(i); ++ Behavior behavior = entry.getData(); ++ if (behavior.getStatus() == Status.RUNNING) { ++ behavior.tickOrStop(world, entity, time); ++ } ++ } ++ // Tuinity end - remove streams + } + + @Override + protected void stop(ServerLevel world, E entity, long time) { +- this.behaviors.stream().filter((behavior) -> { +- return behavior.getStatus() == Behavior.Status.RUNNING; +- }).forEach((behavior) -> { +- behavior.doStop(world, entity, time); +- }); ++ // Tuinity start - remove streams ++ List>> entries = this.behaviors.entries; ++ for (int i = 0; i < entries.size(); i++) { ++ ShufflingList.WeightedEntry> entry = entries.get(i); ++ Behavior behavior = entry.getData(); ++ if (behavior.getStatus() == Status.RUNNING) { ++ behavior.doStop(world, entity, time); ++ } ++ } ++ // Tuinity end - remove streams + this.exitErasedMemories.forEach(entity.getBrain()::eraseMemory); + } + +@@ -94,25 +112,33 @@ public class GateBehavior extends Behavior { + public static enum RunningPolicy { + RUN_ONE { + @Override +- public void apply(Stream> tasks, ServerLevel world, E entity, long time) { +- tasks.filter((behavior) -> { +- return behavior.getStatus() == Behavior.Status.STOPPED; +- }).filter((behavior) -> { +- return behavior.tryStart(world, entity, time); +- }).findFirst(); ++ // Tuinity start - remove streams ++ public void apply(List>> tasks, ServerLevel world, E entity, long time) { ++ for (int i = 0; i < tasks.size(); i++) { ++ ShufflingList.WeightedEntry> task = tasks.get(i); ++ Behavior behavior = task.getData(); ++ if (behavior.getStatus() == Status.STOPPED && behavior.tryStart(world, entity, time)) { ++ break; ++ } ++ } ++ // Tuinity end - remove streams + } + }, + TRY_ALL { + @Override +- public void apply(Stream> tasks, ServerLevel world, E entity, long time) { +- tasks.filter((behavior) -> { +- return behavior.getStatus() == Behavior.Status.STOPPED; +- }).forEach((behavior) -> { +- behavior.tryStart(world, entity, time); +- }); ++ // Tuinity start - remove streams ++ public void apply(List>> tasks, ServerLevel world, E entity, long time) { ++ for (int i = 0; i < tasks.size(); i++) { ++ ShufflingList.WeightedEntry> task = tasks.get(i); ++ Behavior behavior = task.getData(); ++ if (behavior.getStatus() == Status.STOPPED) { ++ behavior.tryStart(world, entity, time); ++ } ++ } ++ // Tuinity end - remove streams + } + }; + +- public abstract void apply(Stream> tasks, ServerLevel world, E entity, long time); ++ public abstract void apply(List>> tasks, ServerLevel world, E entity, long time); // Tuinity - remove streams + } + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/SetLookAndInteract.java b/src/main/java/net/minecraft/world/entity/ai/behavior/SetLookAndInteract.java +index 1f59e790d62f0be8e505e339a6699ca3964aea0d..bb43e47d4b3989610a52c1941598865aee93ac04 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/SetLookAndInteract.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/SetLookAndInteract.java +@@ -34,21 +34,42 @@ public class SetLookAndInteract extends Behavior { + + @Override + public boolean checkExtraStartConditions(ServerLevel world, LivingEntity entity) { +- return this.selfFilter.test(entity) && this.getVisibleEntities(entity).stream().anyMatch(this::isMatchingTarget); ++ // Tuinity start - remove streams ++ if (!this.selfFilter.test(entity)) { ++ return false; ++ } ++ ++ List visibleEntities = this.getVisibleEntities(entity); ++ for (int i = 0; i < visibleEntities.size(); i++) { ++ LivingEntity livingEntity = visibleEntities.get(i); ++ if (this.isMatchingTarget(livingEntity)) { ++ return true; ++ } ++ } ++ return false; ++ // Tuinity end - remove streams + } + + @Override + public void start(ServerLevel world, LivingEntity entity, long time) { + super.start(world, entity, time); + Brain brain = entity.getBrain(); +- brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES).ifPresent((list) -> { +- list.stream().filter((livingEntity2) -> { +- return livingEntity2.distanceToSqr(entity) <= (double)this.interactionRangeSqr; +- }).filter(this::isMatchingTarget).findFirst().ifPresent((livingEntity) -> { +- brain.setMemory(MemoryModuleType.INTERACTION_TARGET, livingEntity); +- brain.setMemory(MemoryModuleType.LOOK_TARGET, new EntityTracker(livingEntity, true)); +- }); +- }); ++ // Tuinity start - remove streams ++ List list = brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES).orElse(null); ++ if (list != null) { ++ double maxRangeSquared = (double)this.interactionRangeSqr; ++ for (int i = 0; i < list.size(); i++) { ++ LivingEntity livingEntity2 = list.get(i); ++ if (livingEntity2.distanceToSqr(entity) <= maxRangeSquared) { ++ if (this.isMatchingTarget(livingEntity2)) { ++ brain.setMemory(MemoryModuleType.INTERACTION_TARGET, livingEntity2); ++ brain.setMemory(MemoryModuleType.LOOK_TARGET, new EntityTracker(livingEntity2, true)); ++ break; ++ } ++ } ++ } ++ } ++ // Tuinity end - remove streams + } + + private boolean isMatchingTarget(LivingEntity entity) { +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java b/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java +index 4fa64b1e2004810906bb0b174436c8e687a75ada..d5a3c6d239abbb31c52ec2dfb9b18b1b705cbc88 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java +@@ -12,7 +12,7 @@ import java.util.Random; + import java.util.stream.Stream; + + public class ShufflingList { +- protected final List> entries; ++ public final List> entries; // Tuinity - public + private final Random random = new Random(); + private final boolean isUnsafe; // Paper + +diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java +index e605daac0c90f5d0b9315d1499938feb0e478d0e..570316cf7831de70086fae35676006ee052851e0 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java ++++ b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java +@@ -27,7 +27,7 @@ import net.minecraft.world.phys.Vec3; + + public abstract class PathNavigation { + private static final int MAX_TIME_RECOMPUTE = 20; +- protected final Mob mob; ++ protected final Mob mob; public final Mob getEntity() { return this.mob; } // Tuinity - public accessor + protected final Level level; + @Nullable + protected Path path; +@@ -40,7 +40,7 @@ public abstract class PathNavigation { + protected long lastTimeoutCheck; + protected double timeoutLimit; + protected float maxDistanceToWaypoint = 0.5F; +- protected boolean hasDelayedRecomputation; ++ protected boolean hasDelayedRecomputation; protected final boolean needsPathRecalculation() { return this.hasDelayedRecomputation; } // Tuinity - public accessor + protected long timeLastRecompute; + protected NodeEvaluator nodeEvaluator; + private BlockPos targetPos; +@@ -49,6 +49,13 @@ public abstract class PathNavigation { + public final PathFinder pathFinder; + private boolean isStuck; + ++ // Tuinity start ++ public boolean isViableForPathRecalculationChecking() { ++ return !this.needsPathRecalculation() && ++ (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0); ++ } ++ // Tuinity end ++ + public PathNavigation(Mob mob, Level world) { + this.mob = mob; + this.level = world; +@@ -404,7 +411,7 @@ public abstract class PathNavigation { + } + + public void recomputePath(BlockPos pos) { +- if (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0) { ++ if (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0) { // Tuinity - diff on change - needed for isViableForPathRecalculationChecking() + Node node = this.path.getEndNode(); + Vec3 vec3 = new Vec3(((double)node.x + this.mob.getX()) / 2.0D, ((double)node.y + this.mob.getY()) / 2.0D, ((double)node.z + this.mob.getZ()) / 2.0D); + if (pos.closerThan(vec3, (double)(this.path.getNodeCount() - this.path.getNextNodeIndex()))) { +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 e41b2fa1db6fb77a26cdb498904021b430e35be0..f0ba454eea673bf02d1f6d7fe30c4f672643fe0c 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 { + return true; + } + }; +- Stream stream = poiManager.findAll(PoiType.HOME.getPredicate(), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY); +- Path path = entity.getNavigation().createPath(stream, PoiType.HOME.getValidRange()); ++ // Tuinity start - optimise POI access ++ java.util.List poiposes = new java.util.ArrayList<>(); ++ // don't ask me why it's unbounded. ask mojang. ++ com.tuinity.tuinity.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()); ++ // Tuinity end - optimise POI access + if (path != null && path.canReach()) { + BlockPos blockPos = path.getTarget(); + Optional optional = poiManager.getType(blockPos); +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java +index 49f3b25d28072b61f5cc97260df61df892a58714..de6b591eb865c6f5c23aaa4b9374bb9bbaaa85f6 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java +@@ -25,17 +25,20 @@ public class NearestItemSensor extends Sensor { + protected void doTick(ServerLevel world, Mob entity) { + Brain brain = entity.getBrain(); + List list = world.getEntitiesOfClass(ItemEntity.class, entity.getBoundingBox().inflate(8.0D, 4.0D, 8.0D), (itemEntity) -> { +- return true; ++ return itemEntity.closerThan(entity, 9.0D) && entity.wantsToPickUp(itemEntity.getItem()); // Tuinity - move predicate into getEntities + }); +- list.sort(Comparator.comparingDouble(entity::distanceToSqr)); ++ list.sort((e1, e2) -> Double.compare(entity.distanceToSqr(e1), entity.distanceToSqr(e2))); // better to take the sort perf hit than using line of sight more than we need to. ++ // Tuinity start - remove streams + // Paper start - remove streams in favour of lists + ItemEntity nearest = null; +- for (ItemEntity entityItem : list) { +- if (entity.wantsToPickUp(entityItem.getItem()) && entityItem.closerThan(entity, 9.0D) && entity.hasLineOfSight(entityItem)) { ++ for (int i = 0; i < list.size(); i++) { ++ ItemEntity entityItem = list.get(i); ++ if (entity.hasLineOfSight(entityItem)) { + nearest = entityItem; + break; + } + } ++ // Tuinity end - remove streams + brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, Optional.ofNullable(nearest)); + // Paper end + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java +index ffd83db0a419ab589e89feeddd3fb038d6ed5839..31ef567cf4f331d3329dd176392686db56aead66 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java +@@ -18,12 +18,19 @@ public class NearestLivingEntitySensor extends Sensor { + List list = world.getEntitiesOfClass(LivingEntity.class, aABB, (livingEntity2) -> { + return livingEntity2 != entity && livingEntity2.isAlive(); + }); +- list.sort(Comparator.comparingDouble(entity::distanceToSqr)); ++ // Tuinity start - remove streams ++ list.sort((e1, e2) -> Double.compare(entity.distanceToSqr(e1), entity.distanceToSqr(e2))); + Brain brain = entity.getBrain(); + brain.setMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES, list); + // Paper start - remove streams in favour of lists +- List visibleMobs = new java.util.ArrayList<>(list); +- visibleMobs.removeIf(otherEntityLiving -> !Sensor.isEntityTargetable(entity, otherEntityLiving)); ++ List visibleMobs = new java.util.ArrayList<>(); ++ for (int i = 0, len = list.size(); i < len; i++) { ++ LivingEntity nearby = list.get(i); ++ if (Sensor.isEntityTargetable(entity, nearby)) { ++ visibleMobs.add(nearby); ++ } ++ } ++ // Tuinity end - remove streams + brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, visibleMobs); + // Paper end + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java +index 457ea75137b8b02dc32bf1769ae8d57c470da470..3392a8d425d9f5e1417a665fb1514d013bf89337 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java +@@ -21,25 +21,31 @@ public class PlayerSensor extends Sensor { + + @Override + protected void doTick(ServerLevel world, LivingEntity entity) { +- // Paper start - remove streams in favour of lists +- List players = new java.util.ArrayList<>(world.players()); +- players.removeIf(player -> !EntitySelector.NO_SPECTATORS.test(player) || !entity.closerThan(player, 16.0D)); // Paper - removeIf only re-allocates once compared to iterator ++ // Tuinity start - remove streams ++ List players = (List)world.getNearbyPlayers(entity, entity.getX(), entity.getY(), entity.getZ(), 16.0D, EntitySelector.NO_SPECTATORS); ++ players.sort((e1, e2) -> Double.compare(entity.distanceToSqr(e1), entity.distanceToSqr(e2))); + Brain brain = entity.getBrain(); + + brain.setMemory(MemoryModuleType.NEAREST_PLAYERS, players); + +- Player nearest = null, nearestTargetable = null; +- for (Player player : players) { +- if (Sensor.isEntityTargetable(entity, player)) { +- if (nearest == null) nearest = player; +- if (Sensor.isEntityAttackable(entity, player)) { +- nearestTargetable = player; +- break; // Both variables are assigned, no reason to loop further +- } ++ Player firstTargetable = null; ++ Player firstAttackable = null; ++ for (int index = 0, len = players.size(); index < len; ++index) { ++ Player player = players.get(index); ++ if (firstTargetable == null && isEntityTargetable(entity, player)) { ++ firstTargetable = player; ++ } ++ if (firstAttackable == null && isEntityAttackable(entity, player)) { ++ firstAttackable = player; ++ } ++ ++ if (firstAttackable != null && firstTargetable != null) { ++ break; + } + } +- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, nearest); +- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, nearestTargetable); +- // Paper end ++ ++ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, firstTargetable); ++ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, Optional.ofNullable(firstAttackable)); ++ // Tuinity end - remove streams + } + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/VillagerBabiesSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/VillagerBabiesSensor.java +index 478010bc291fa3276aab0f66ce6283403af710ec..a198538fe4ae560adc66fad5a2f6b80bbd894e4b 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/VillagerBabiesSensor.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/VillagerBabiesSensor.java +@@ -22,7 +22,17 @@ public class VillagerBabiesSensor extends Sensor { + } + + private List getNearestVillagerBabies(LivingEntity entities) { +- return this.getVisibleEntities(entities).stream().filter(this::isVillagerBaby).collect(Collectors.toList()); ++ // Tuinity start - remove streams ++ List list = new java.util.ArrayList<>(); ++ List visibleEntities = this.getVisibleEntities(entities); ++ for (int i = 0; i < visibleEntities.size(); i++) { ++ LivingEntity livingEntity = visibleEntities.get(i); ++ if (this.isVillagerBaby(livingEntity)) { ++ list.add(livingEntity); ++ } ++ } ++ return list; ++ // Tuinity end - remove streams + } + + private boolean isVillagerBaby(LivingEntity entity) { 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 6c3455823f996e0421975b7f4a00f4e333e9f514..7db31cc24e68aab55a9ba735165da23059bdc626 100644 +index 6c3455823f996e0421975b7f4a00f4e333e9f514..3ba30a2e6f1e3eb82b2b6e8968fd2babbf220ded 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 -@@ -183,7 +183,7 @@ public class PoiManager extends SectionStorage { +@@ -37,7 +37,7 @@ public class PoiManager extends SectionStorage { + 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 // Tuinity public + + public PoiManager(File directory, DataFixer dataFixer, boolean dsync, LevelHeightAccessor world) { + super(directory, PoiSection::codec, PoiSection::new, dataFixer, DataFixTypes.POI_CHUNK, dsync, world); +@@ -100,36 +100,55 @@ public class PoiManager extends SectionStorage { + } + + public Optional find(Predicate typePredicate, Predicate posPredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus) { +- return this.findAll(typePredicate, posPredicate, pos, radius, occupationStatus).findFirst(); ++ // Tuinity start - re-route to faster logic ++ BlockPos ret = com.tuinity.tuinity.util.PoiAccess.findAnyPoiPosition(this, typePredicate, posPredicate, pos, radius, occupationStatus, false); ++ return Optional.ofNullable(ret); ++ // Tuinity end - re-route to faster logic + } + + public Optional findClosest(Predicate 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); +- })); ++ // Tuinity start - re-route to faster logic ++ BlockPos ret = com.tuinity.tuinity.util.PoiAccess.findClosestPoiDataPosition(this, typePredicate, null, pos, radius, radius*radius, occupationStatus, false); ++ return Optional.ofNullable(ret); ++ // Tuinity end - re-route to faster logic + } + + public Optional findClosest(Predicate predicate, Predicate predicate2, BlockPos blockPos, int i, PoiManager.Occupancy occupancy) { +- return this.getInRange(predicate, blockPos, i, occupancy).map(PoiRecord::getPos).filter(predicate2).min(Comparator.comparingDouble((blockPos2) -> { +- return blockPos2.distSqr(blockPos); +- })); ++ // Tuinity start - re-route to faster logic ++ BlockPos ret = com.tuinity.tuinity.util.PoiAccess.findClosestPoiDataPosition(this, predicate, predicate2, blockPos, i, i*i, occupancy, false); ++ return Optional.ofNullable(ret); ++ // Tuinity end - re-route to faster logic + } + + public Optional take(Predicate typePredicate, Predicate 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(); +- }); ++ // Tuinity start - re-route to faster logic ++ PoiRecord ret = com.tuinity.tuinity.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()); ++ // Tuinity end - re-route to faster logic + } + + public Optional getRandom(Predicate typePredicate, Predicate positionPredicate, PoiManager.Occupancy occupationStatus, BlockPos pos, int radius, Random random) { +- List list = this.getInRange(typePredicate, pos, radius, occupationStatus).collect(Collectors.toList()); +- Collections.shuffle(list, random); +- return list.stream().filter((poiRecord) -> { +- return positionPredicate.test(poiRecord.getPos()); +- }).findFirst().map(PoiRecord::getPos); ++ // Tuinity start - re-route to faster logic ++ List list = new java.util.ArrayList<>(); ++ com.tuinity.tuinity.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()); ++ // Tuinity end - re-route to faster logic + } + + public boolean release(BlockPos pos) { +@@ -183,7 +202,7 @@ public class PoiManager extends SectionStorage { data = this.getData(chunkcoordintpair); } com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.world, @@ -14032,6 +15394,25 @@ index 6c3455823f996e0421975b7f4a00f4e333e9f514..7db31cc24e68aab55a9ba735165da230 } // Paper end this.distanceTracker.runAllUpdates(); +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 75c1c4671fedb425dea20dc4fb0c6cb2304dee83..fc7b364adc2d0e5db22aa25e029c2e13c84d6096 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,12 +25,12 @@ import org.apache.logging.log4j.Logger; + public class PoiSection { + private static final Logger LOGGER = LogManager.getLogger(); + private final Short2ObjectMap records = new Short2ObjectOpenHashMap<>(); +- private final Map> byType = Maps.newHashMap(); ++ private final Map> byType = Maps.newHashMap(); public final Map> getData() { return this.byType; } // Tuinity - public accessor + private final Runnable setDirty; + private boolean isValid; + + public static Codec codec(Runnable updateListener) { +- return RecordCodecBuilder.create((instance) -> { ++ return RecordCodecBuilder.create((instance) -> { // Tuinity - decompile fix + return instance.group(RecordCodecBuilder.point(updateListener), Codec.BOOL.optionalFieldOf("Valid", Boolean.valueOf(false)).forGetter((poiSet) -> { + return poiSet.isValid; + }), PoiRecord.codec(updateListener).listOf().fieldOf("Records").forGetter((poiSet) -> { diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java index 925f16d5eb092518ef774f69a8d99689feb0f5d7..01d8af06f19427354cac95d691e65d31253fef94 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java @@ -16350,6 +17731,24 @@ index b7835b9b904e7d4bff64f7189049e334f5ab4d6f..ae638ac0a0557de204471fef4b03bdb0 public static final RegionFileVersion VERSION_GZIP = register(new RegionFileVersion(1, GZIPInputStream::new, GZIPOutputStream::new)); public static final RegionFileVersion VERSION_DEFLATE = register(new RegionFileVersion(2, InflaterInputStream::new, DeflaterOutputStream::new)); public static final RegionFileVersion VERSION_NONE = register(new RegionFileVersion(3, (inputStream) -> { +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 90f7b06bd2c558be35c4577044fa033e1fb5cc22..8f244db7e46ac1a3d2c8358f001d488900a76926 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 extends RegionFileStorage implements AutoCloseabl + } + + @Nullable +- protected Optional get(long pos) { ++ public Optional get(long pos) { // Tuinity - public + return this.storage.get(pos); + } + +- protected Optional getOrLoad(long pos) { ++ public Optional getOrLoad(long pos) { // Tuinity - public + if (this.outsideStoredRange(pos)) { + return Optional.empty(); + } else { diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java index f01182a0ac8a14bcd5b1deb778306e7bf1bf70ed..2cfc54a577d0a63a504e24bc54fd763fe51083e5 100644 --- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java @@ -16426,7 +17825,7 @@ index f01182a0ac8a14bcd5b1deb778306e7bf1bf70ed..2cfc54a577d0a63a504e24bc54fd763f } } diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -index 79e733b3ea2e6589d60f3b322244479d2b3b9f86..a99d0a00bbdb90588b87a3f85c62bdc1468b5e5a 100644 +index 79e733b3ea2e6589d60f3b322244479d2b3b9f86..0465235016d00a9db3694b920206b1cd3ae0824c 100644 --- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java @@ -41,8 +41,10 @@ public class PersistentEntitySectionManager implements A @@ -16441,7 +17840,73 @@ index 79e733b3ea2e6589d60f3b322244479d2b3b9f86..a99d0a00bbdb90588b87a3f85c62bdc1 this.visibleEntityStorage = new EntityLookup<>(); this.sectionStorage = new EntitySectionStorage<>(entityClass, this.chunkVisibility); this.chunkVisibility.defaultReturnValue(Visibility.HIDDEN); -@@ -93,6 +95,7 @@ public class PersistentEntitySectionManager implements A +@@ -52,6 +54,65 @@ public class PersistentEntitySectionManager implements A + this.entityGetter = new LevelEntityGetterAdapter<>(this.visibleEntityStorage, this.sectionStorage); + } + ++ // Tuinity start - optimise notify() ++ public final void removeNavigatorsFromData(Entity entity, final int chunkX, final int chunkZ) { ++ if (!(entity instanceof net.minecraft.world.entity.Mob)) { ++ return; ++ } ++ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section = ++ this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(chunkX, chunkZ); ++ if (section != null) { ++ net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData; ++ sectionData.removeFromNavigators(section, ((net.minecraft.world.entity.Mob)entity)); ++ } ++ } ++ ++ public final void removeNavigatorsFromData(Entity entity) { ++ if (!(entity instanceof net.minecraft.world.entity.Mob)) { ++ return; ++ } ++ BlockPos entityPos = entity.blockPosition(); ++ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section = ++ this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(entityPos.getX() >> 4, entityPos.getZ() >> 4); ++ if (section != null) { ++ net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData; ++ sectionData.removeFromNavigators(section, ((net.minecraft.world.entity.Mob)entity)); ++ } ++ } ++ ++ public final void addNavigatorsIfPathingToRegion(Entity entity) { ++ if (!(entity instanceof net.minecraft.world.entity.Mob)) { ++ return; ++ } ++ BlockPos entityPos = entity.blockPosition(); ++ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section = ++ this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(entityPos.getX() >> 4, entityPos.getZ() >> 4); ++ if (section != null) { ++ net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData; ++ if (((net.minecraft.world.entity.Mob)entity).getNavigation().isViableForPathRecalculationChecking()) { ++ sectionData.addToNavigators(section, ((net.minecraft.world.entity.Mob)entity)); ++ } ++ } ++ } ++ ++ public final void updateNavigatorsInRegion(Entity entity) { ++ if (!(entity instanceof net.minecraft.world.entity.Mob)) { ++ return; ++ } ++ BlockPos entityPos = entity.blockPosition(); ++ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section = ++ this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(entityPos.getX() >> 4, entityPos.getZ() >> 4); ++ if (section != null) { ++ net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData; ++ if (((net.minecraft.world.entity.Mob)entity).getNavigation().isViableForPathRecalculationChecking()) { ++ sectionData.addToNavigators(section, ((net.minecraft.world.entity.Mob)entity)); ++ } else { ++ sectionData.removeFromNavigators(section, ((net.minecraft.world.entity.Mob)entity)); ++ } ++ } ++ } ++ // Tuinity end - optimise notify() ++ + void removeSectionIfEmpty(long sectionPos, EntitySection section) { + if (section.isEmpty()) { + this.sectionStorage.remove(sectionPos); +@@ -93,6 +154,7 @@ public class PersistentEntitySectionManager implements A long l = SectionPos.asLong(entity.blockPosition()); EntitySection entitySection = this.sectionStorage.getOrCreateSection(l); entitySection.add(entity); @@ -16449,7 +17914,7 @@ index 79e733b3ea2e6589d60f3b322244479d2b3b9f86..a99d0a00bbdb90588b87a3f85c62bdc1 entity.setLevelCallback(new PersistentEntitySectionManager.Callback(entity, l, entitySection)); if (!existing) { this.callbacks.onCreated(entity); -@@ -147,6 +150,7 @@ public class PersistentEntitySectionManager implements A +@@ -147,6 +209,7 @@ public class PersistentEntitySectionManager implements A public void updateChunkStatus(ChunkPos chunkPos, ChunkHolder.FullChunkStatus levelType) { Visibility visibility = Visibility.fromFullChunkStatus(levelType); @@ -16457,15 +17922,49 @@ index 79e733b3ea2e6589d60f3b322244479d2b3b9f86..a99d0a00bbdb90588b87a3f85c62bdc1 this.updateChunkStatus(chunkPos, visibility); } -@@ -383,6 +387,7 @@ public class PersistentEntitySectionManager implements A +@@ -381,18 +444,38 @@ public class PersistentEntitySectionManager implements A + @Override + public void onMove() { BlockPos blockPos = this.entity.blockPosition(); - long l = SectionPos.asLong(blockPos); +- long l = SectionPos.asLong(blockPos); ++ long l = SectionPos.asLong(blockPos); // Tuinity - diff on change, new position section if (l != this.currentSectionKey) { +- Visibility visibility = this.currentSection.getStatus(); + PersistentEntitySectionManager.this.entitySliceManager.moveEntity((Entity)this.entity); // Tuinity - Visibility visibility = this.currentSection.getStatus(); ++ // Tuinity start ++ int shift = PersistentEntitySectionManager.this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.regionChunkShift; ++ int oldChunkX = com.tuinity.tuinity.util.CoordinateUtils.getChunkSectionX(this.currentSectionKey); ++ int oldChunkZ = com.tuinity.tuinity.util.CoordinateUtils.getChunkSectionZ(this.currentSectionKey); ++ int oldRegionX = oldChunkX >> shift; ++ int oldRegionZ = oldChunkZ >> shift; ++ ++ int newRegionX = com.tuinity.tuinity.util.CoordinateUtils.getChunkSectionX(l) >> shift; ++ int newRegionZ = com.tuinity.tuinity.util.CoordinateUtils.getChunkSectionZ(l) >> shift; ++ ++ if (oldRegionX != newRegionX || oldRegionZ != newRegionZ) { ++ PersistentEntitySectionManager.this.removeNavigatorsFromData((Entity)this.entity, oldChunkX, oldChunkZ); ++ } ++ // Tuinity end ++ Visibility visibility = this.currentSection.getStatus(); // Tuinity - diff on change - this should be OLD section visibility if (!this.currentSection.remove(this.entity)) { PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (moving to {})", this.entity, SectionPos.of(this.currentSectionKey), l); -@@ -426,6 +431,7 @@ public class PersistentEntitySectionManager implements A + } + + PersistentEntitySectionManager.this.removeSectionIfEmpty(this.currentSectionKey, this.currentSection); +- EntitySection entitySection = PersistentEntitySectionManager.this.sectionStorage.getOrCreateSection(l); ++ EntitySection entitySection = PersistentEntitySectionManager.this.sectionStorage.getOrCreateSection(l); // Tuinity - diff on change, this should be NEW section + entitySection.add(this.entity); + this.currentSection = entitySection; + this.currentSectionKey = l; ++ // Tuinity start ++ if ((oldRegionX != newRegionX || oldRegionZ != newRegionZ) && visibility.isTicking() && entitySection.getStatus().isTicking()) { ++ PersistentEntitySectionManager.this.addNavigatorsIfPathingToRegion((Entity)this.entity); ++ } ++ // Tuinity end + this.updateStatus(visibility, entitySection.getStatus()); + } + +@@ -426,6 +509,7 @@ public class PersistentEntitySectionManager implements A if (!this.currentSection.remove(this.entity)) { PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (destroying due to {})", this.entity, SectionPos.of(this.currentSectionKey), reason); } @@ -16530,6 +18029,58 @@ index 5da68897148192905c2747676c1ee2ee649f923f..b990099cf274f8cb0d96c139345cf0bf return treeConfiguration.dirtProvider; }), FeatureSize.CODEC.fieldOf("minimum_size").forGetter((treeConfiguration) -> { return treeConfiguration.minimumSize; +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 d5ba2e679ed1858ea18e18feffce50544ae036c2..78da12a3feb05a5504daf8379be3d568c389a458 100644 +--- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java ++++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java +@@ -52,16 +52,37 @@ public class PortalForcer { + // int i = flag ? 16 : 128; + // CraftBukkit end + +- villageplace.ensureLoadedAndValid(this.level, blockposition, i); +- Optional optional = villageplace.getInSquare((villageplacetype) -> { +- return villageplacetype == PoiType.NETHER_PORTAL; +- }, blockposition, i, PoiManager.Occupancy.ANY).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(); ++ // Tuinity start - optimise portals ++ Optional optional; ++ java.util.List records = new java.util.ArrayList<>(); ++ com.tuinity.tuinity.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; ++ } ++ 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); ++ // Tuinity end - optimise portals + + return optional.map((villageplacerecord) -> { + BlockPos blockposition1 = villageplacerecord.getPos(); diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java index 120498a39b7ca7aee9763084507508d4a1c425aa..6f7e6429c35eea346517cbf08cf223fc6d838a8c 100644 --- a/src/main/java/net/minecraft/world/phys/AABB.java diff --git a/patches/server/0073-Item-entity-immunities.patch b/patches/server/0073-Item-entity-immunities.patch index 546e210f9..6ed5fb8a1 100644 --- a/patches/server/0073-Item-entity-immunities.patch +++ b/patches/server/0073-Item-entity-immunities.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Item entity immunities diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 1430411a6f7ef3730d87c022774d7d623f2f415f..2d2da8f19c8845c5cfb4625ef7fed26212420eae 100644 +index 509a4239dbda8d8d7edebfdc92bed84a13def369..018fe34b41612645579308a4d237a416a88d9102 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -2233,7 +2233,7 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially +@@ -2308,7 +2308,7 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially public class TrackedEntity { @@ -94,7 +94,7 @@ index 158719d46c96bb733a00e08c8285f41a48406abf..5201e59c7ce9e92790c185279ba69d7f + // Purpur end } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 782f59ae094bac5c7c86b5c21418e9a7383d3226..1804be45445fee8ba4e30950792693ff47eca9a8 100644 +index e5b1175d724598009d1276afece4b18980fdf823..1b609e93e29c505f8fefcf1c5991499cd0383f55 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java @@ -108,6 +108,49 @@ public class PurpurWorldConfig {