From f89aaecf2c5a72a44a8be664a9e8e2d686939a31 Mon Sep 17 00:00:00 2001 From: MC_XiaoHei Date: Sat, 17 Aug 2024 21:59:51 +0800 Subject: [PATCH] [ci skip] feat: Rewrite Nether Portal find logic(WIP) --- ...058-Rewrite-Nether-Portal-find-logic.patch | 234 +++++++++++++++--- 1 file changed, 195 insertions(+), 39 deletions(-) diff --git a/patches/server/0058-Rewrite-Nether-Portal-find-logic.patch b/patches/server/0058-Rewrite-Nether-Portal-find-logic.patch index 5413bb6..b11d29f 100644 --- a/patches/server/0058-Rewrite-Nether-Portal-find-logic.patch +++ b/patches/server/0058-Rewrite-Nether-Portal-find-logic.patch @@ -5,9 +5,18 @@ Subject: [PATCH] Rewrite Nether Portal find logic diff --git a/src/main/java/io/papermc/paper/util/PoiAccess.java b/src/main/java/io/papermc/paper/util/PoiAccess.java -index 69be1761b3b5ba7b496c1c10a4db897e6212d671..b65ea7f1ffb16b750dc4a33334b4dc71d8452d4c 100644 +index 69be1761b3b5ba7b496c1c10a4db897e6212d671..8a2000e2e27cd539f728238bf96e88894cd4d230 100644 --- a/src/main/java/io/papermc/paper/util/PoiAccess.java +++ b/src/main/java/io/papermc/paper/util/PoiAccess.java +@@ -181,7 +181,7 @@ public final class PoiAccess { + + // 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, ++ public static void findClosestPoiDataRecords(final net.minecraft.world.level.portal.PortalForcer.IPoiManager poiStorage, // Lumina - rewrite nether portal find logic + final Predicate> villagePlaceType, + // position predicate must not modify chunk POI + final Predicate positionPredicate, @@ -195,7 +195,7 @@ public final class PoiAccess { findClosestPoiDataRecords(poiStorage, villagePlaceType, predicate, sourcePosition, range, maxDistanceSquared, occupancy, load, ret); } @@ -30,15 +39,29 @@ index 69be1761b3b5ba7b496c1c10a4db897e6212d671..b65ea7f1ffb16b750dc4a33334b4dc71 final int upperZ = Mth.floor(sourcePosition.getZ() + range) >> 4; final int centerX = sourcePosition.getX() >> 4; -@@ -273,7 +273,7 @@ public final class PoiAccess { +@@ -273,13 +273,19 @@ public final class PoiAccess { } } - final Optional poiSectionOptional = load ? poiStorage.getOrLoad(key) : poiStorage.get(key); -+ final Optional poiSectionOptional = load ? poiStorage._getOrLoad(key) : poiStorage._get(key); // Lumina - rewrite nether portal find logic ++ // Lumina start - rewrite nether portal find logic ++ final Optional poiSectionOptional = load ? poiStorage._getOrLoad(key) : poiStorage._get(key); - if (poiSectionOptional == null || !poiSectionOptional.isPresent()) { +- if (poiSectionOptional == null || !poiSectionOptional.isPresent()) { ++ if(poiSectionOptional == null) { ++ throw new net.minecraft.world.level.portal.PortalForcer.UncachedSectionException(); ++ } ++ ++ if (poiSectionOptional.isEmpty()) { continue; + } + +- final PoiSection poiSection = poiSectionOptional.get(); ++ final net.minecraft.world.level.portal.PortalForcer.IPoiSection poiSection = poiSectionOptional.get(); ++ // Lumina end - rewrite nether portal find logic + + final Map, Set> sectionData = poiSection.getData(); + if (sectionData.isEmpty()) { 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 16e2e3edc62f9774518b8e783f8eb1ca35d413c8..615b167f461ab9f3346311fd0d06264b7b381a73 100644 --- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java @@ -84,27 +107,43 @@ index 16e2e3edc62f9774518b8e783f8eb1ca35d413c8..615b167f461ab9f3346311fd0d06264b private final Long2ByteMap levels = new Long2ByteOpenHashMap(); 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 4ee7d75c56d9f9ff3607276857dde84410ba3f2a..8731cebf72febd84822284914bbf76075e6b4b0b 100644 +index 4ee7d75c56d9f9ff3607276857dde84410ba3f2a..76408fe55a8c7d27e7c4f609c059db5da8bfb5f8 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 -@@ -78,6 +78,7 @@ public class PoiSection { - private boolean add(PoiRecord poi) { - BlockPos blockPos = poi.getPos(); - Holder holder = poi.getPoiType(); -+ if (holder.is(PoiTypes.NETHER_PORTAL)) System.out.println("Adding nether portal at " + blockPos); // Lumina - rewrite nether portal logic - short s = SectionPos.sectionRelativePos(blockPos); - PoiRecord poiRecord = this.records.get(s); - if (poiRecord != null) { -@@ -95,6 +96,7 @@ public class PoiSection { - - public void remove(BlockPos pos) { - PoiRecord poiRecord = this.records.remove(SectionPos.sectionRelativePos(pos)); -+ if (poiRecord.getPoiType().is(PoiTypes.NETHER_PORTAL)) System.out.println("Adding nether portal at " + pos); // Lumina - rewrite nether portal logic - if (poiRecord == null) { - LOGGER.error("POI data mismatch: never registered at {}", pos); - } else { +@@ -23,10 +23,10 @@ import net.minecraft.core.SectionPos; + import net.minecraft.util.VisibleForDebug; + import org.slf4j.Logger; + +-public class PoiSection { ++public class PoiSection implements net.minecraft.world.level.portal.PortalForcer.IPoiSection { // Lumina - rewrite nether portal find logic + private static final Logger LOGGER = LogUtils.getLogger(); + private final Short2ObjectMap records = new Short2ObjectOpenHashMap<>(); +- private final Map, Set> byType = Maps.newHashMap(); public final Map, Set> getData() { return this.byType; } // Paper - public accessor ++ private final Map, Set> byType = Maps.newHashMap(); @Override public final Map, Set> getData() { return this.byType; } // Paper - public accessor // Lumina - rewrite nether portal find logic + private final Runnable setDirty; + private boolean isValid; + public final Optional noAllocateOptional = Optional.of(this); // Paper - rewrite chunk system +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 7fb23c02dc91ed3af27eb4420ce920ab22bdb359..3e050da7117788c7aef8838e5f251f72730f2649 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -1021,6 +1021,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public boolean setBlock(BlockPos pos, BlockState state, int flags, int maxUpdateDepth) { + io.papermc.paper.util.TickThread.ensureTickThread((ServerLevel)this, pos, "Updating block asynchronously"); // Folia - region threading + io.papermc.paper.threadedregions.RegionizedWorldData worldData = this.getCurrentWorldData(); // Folia - region threading ++ // Lumina start - rewrite nether portal find logic ++ BlockState oldType = getBlockState(pos); ++ if(oldType.is(Blocks.NETHER_PORTAL)) { ++ ((ServerLevel)this).getPortalForcer().cacheManager.getOrCreateIfAbsent().remove(pos); ++ } else if (state.is(Blocks.NETHER_PORTAL)) { ++ ((ServerLevel)this).getPortalForcer().cacheManager.getOrCreateIfAbsent().add(pos); ++ } ++ // Lumina end - rewrite nether portal find logic + // CraftBukkit start - tree generation + if (worldData.captureTreeGeneration) { // Folia - region threading + // Paper start - Protect Bedrock and End Portal/Frames from being destroyed 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 e251935b89799046e82228f49ea7a7737078892b..44fac6e555685887d7389ef0ae3b33e65f314447 100644 +index e251935b89799046e82228f49ea7a7737078892b..09565f18cae25c857e5b5e90f6e6ce5d7ff67113 100644 --- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java +++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java @@ -11,6 +11,7 @@ import net.minecraft.server.level.TicketType; @@ -115,7 +154,21 @@ index e251935b89799046e82228f49ea7a7737078892b..44fac6e555685887d7389ef0ae3b33e6 import net.minecraft.world.entity.ai.village.poi.PoiTypes; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.block.Blocks; -@@ -39,6 +40,7 @@ public class PortalForcer { +@@ -19,6 +20,13 @@ import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.block.state.properties.BlockStateProperties; + import net.minecraft.world.level.border.WorldBorder; + import net.minecraft.world.level.levelgen.Heightmap; ++// Lumina start - rewrite nether portal find logic ++import net.minecraft.core.Holder; ++import net.minecraft.world.entity.ai.village.poi.*; ++import org.jetbrains.annotations.Nullable; ++import java.util.*; ++import java.util.concurrent.ConcurrentHashMap; ++// Lumina end - rewrite nether portal find logic + + public class PortalForcer { + +@@ -39,6 +47,7 @@ public class PortalForcer { public PortalForcer(ServerLevel world) { this.level = world; @@ -123,15 +176,39 @@ index e251935b89799046e82228f49ea7a7737078892b..44fac6e555685887d7389ef0ae3b33e6 } public Optional findPortalAround(BlockPos pos, boolean destIsNether, WorldBorder worldBorder) { -@@ -46,7 +48,50 @@ public class PortalForcer { +@@ -46,32 +55,129 @@ public class PortalForcer { return this.findPortalAround(pos, worldBorder, destIsNether ? level.paperConfig().environment.portalCreateRadius : level.paperConfig().environment.portalSearchRadius); // Search Radius // Paper - Configurable portal search radius } +- public Optional findPortalAround(BlockPos blockposition, WorldBorder worldborder, int i) { +- PoiManager villageplace = this.level.getPoiManager(); +- // int i = flag ? 16 : 128; +- // CraftBukkit end + // Lumina start - rewrite nether portal find logic -+ private static class CacheManager implements IPoiManager { -+ private final java.util.concurrent.ConcurrentHashMap portalCache = new java.util.concurrent.ConcurrentHashMap<>(); ++ public static class CacheManager implements IPoiManager { ++ private final java.util.concurrent.ConcurrentHashMap portalCache = new java.util.concurrent.ConcurrentHashMap<>(); + private final PoiManager poiManager; -+ + +- // Paper start - optimise portals +- Optional optional; +- java.util.List records = new java.util.ArrayList<>(); +- io.papermc.paper.util.PoiAccess.findClosestPoiDataRecords( +- villageplace, +- type -> type.is(PoiTypes.NETHER_PORTAL), +- (BlockPos pos) -> { +- net.minecraft.world.level.chunk.ChunkAccess lowest = this.level.getChunk(pos.getX() >> 4, pos.getZ() >> 4, net.minecraft.world.level.chunk.status.ChunkStatus.EMPTY); +- if (!lowest.getStatus().isOrAfter(net.minecraft.world.level.chunk.status.ChunkStatus.FULL) +- && (lowest.getBelowZeroRetrogen() == null || !lowest.getBelowZeroRetrogen().targetStatus().isOrAfter(net.minecraft.world.level.chunk.status.ChunkStatus.SPAWN))) { +- // why would we generate the chunk? +- return false; +- } +- if (!worldborder.isWithinBounds(pos) || (this.level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> pos.getY() >= v))) { // Paper - Configurable nether ceiling damage +- return false; +- } +- return lowest.getBlockState(pos).hasProperty(BlockStateProperties.HORIZONTAL_AXIS); +- }, +- blockposition, i, Double.MAX_VALUE, PoiManager.Occupancy.ANY, true, records +- ); + public CacheManager(ServerLevel level) { + this.poiManager = level.getPoiManager(); + } @@ -147,30 +224,109 @@ index e251935b89799046e82228f49ea7a7737078892b..44fac6e555685887d7389ef0ae3b33e6 + } + + @Override -+ public Optional _getOrLoad(long pos) { -+ return Optional.ofNullable(portalCache.get(pos)); ++ public java.util.Optional _getOrLoad(long pos) { ++ IPoiSection res = portalCache.get(pos); ++ return res == null ? null : java.util.Optional.ofNullable(portalCache.get(pos)); + } + + @Override -+ public Optional _get(long pos) { ++ public java.util.Optional _get(long pos) { + return this.poiManager._get(pos); + } + -+ public void update(BlockPos pos) { ++ public IPoiSection getOrCreateIfAbsent(long pos) { ++ IPoiSection res = portalCache.get(pos); ++ if (res == null) { ++ res = new NetherPortalBlockConcurrentPoiSection(); ++ portalCache.put(pos, res); ++ } ++ return res; ++ } ++ } ++ ++ public static class NetherPortalBlockConcurrentPoiSection implements IPoiSection { ++ private final Map, Set> data = new java.util.concurrent.ConcurrentHashMap<>(); ++ private final Holder netherPortalType; + ++ public NetherPortalBlockConcurrentPoiSection() { ++ this.netherPortalType = PoiTypes.forState(NetherPortalBlock.stateById(0)).get(); ++ } ++ ++ @Override ++ public Map, Set> getData() { ++ return data; ++ } ++ ++ @Override ++ public void add(BlockPos pos) { ++ PoiRecord record = new PoiRecord(pos, netherPortalType, () -> {}); ++ Set set = data.computeIfAbsent(netherPortalType, (key) -> ConcurrentHashMap.newKeySet()); ++ set.add(record); ++ data.put(netherPortalType, set); ++ } ++ ++ @Override ++ public void remove(BlockPos pos) { ++ PoiRecord record = new PoiRecord(pos, netherPortalType, () -> {}); ++ Set set = data.get(netherPortalType); ++ if (set != null && set.contains(record)) { ++ set.remove(record); ++ data.put(netherPortalType, set); ++ } + } + } + -+ private CacheManager cacheManager; ++ public final CacheManager cacheManager; + + public interface IPoiManager { + int getMinSection(); + int getMaxSection(); -+ Optional _getOrLoad(long pos); -+ Optional _get(long pos); ++ @Nullable ++ Optional _getOrLoad(long pos); ++ Optional _get(long pos); + } + - public Optional findPortalAround(BlockPos blockposition, WorldBorder worldborder, int i) { -+ // Lumina end - rewrite nether portal find logic - PoiManager villageplace = this.level.getPoiManager(); - // int i = flag ? 16 : 128; - // CraftBukkit end ++ public interface IPoiSection { ++ Map, Set> getData(); ++ default void add(BlockPos pos) {} ++ default void remove(BlockPos pos) {} ++ } ++ ++ public static class UncachedSectionException extends RuntimeException { } ++ ++ public Optional findPortalAround(BlockPos blockposition, WorldBorder worldborder, int i) { ++ java.util.List records = new java.util.ArrayList<>(); ++ Optional optional; ++ try { ++ io.papermc.paper.util.PoiAccess.findClosestPoiDataRecords( ++ cacheManager, ++ type -> type.is(PoiTypes.NETHER_PORTAL), ++ (BlockPos ignored) -> true, ++ blockposition, i, Double.MAX_VALUE, PoiManager.Occupancy.ANY, true, records); ++ } catch (UncachedSectionException ignored) { ++ PoiManager villageplace = this.level.getPoiManager(); ++ // int i = flag ? 16 : 128; ++ // CraftBukkit end ++ ++ // Paper start - optimise portals ++ io.papermc.paper.util.PoiAccess.findClosestPoiDataRecords( ++ villageplace, ++ type -> type.is(PoiTypes.NETHER_PORTAL), ++ (BlockPos pos) -> { ++ net.minecraft.world.level.chunk.ChunkAccess lowest = this.level.getChunk(pos.getX() >> 4, pos.getZ() >> 4, net.minecraft.world.level.chunk.status.ChunkStatus.EMPTY); ++ if (!lowest.getStatus().isOrAfter(net.minecraft.world.level.chunk.status.ChunkStatus.FULL) ++ && (lowest.getBelowZeroRetrogen() == null || !lowest.getBelowZeroRetrogen().targetStatus().isOrAfter(net.minecraft.world.level.chunk.status.ChunkStatus.SPAWN))) { ++ // why would we generate the chunk? ++ return false; ++ } ++ if (!worldborder.isWithinBounds(pos) || (this.level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> pos.getY() >= v))) { // Paper - Configurable nether ceiling damage ++ return false; ++ } ++ return lowest.getBlockState(pos).hasProperty(BlockStateProperties.HORIZONTAL_AXIS); ++ }, ++ blockposition, i, Double.MAX_VALUE, PoiManager.Occupancy.ANY, true, records ++ ); ++ } // Lumina end - rewrite nether portal find logic + // this gets us most of the way there, but we bias towards lower y values. + PoiRecord lowestYRecord = null; + for (PoiRecord record : records) {