From 9376fcaceae5cb3821a04e322161b1e122fa3745 Mon Sep 17 00:00:00 2001 From: 2No2Name <2No2Name@web.de> Date: Sat, 1 Feb 2025 07:15:50 +0100 Subject: [PATCH] Fast-path exit end portal search by counting bedrock blocks Useful during ender dragon respawning when placing the last end crystal https://github.com/CaffeineMC/lithium/issues/618 --- CHANGELOG.md | 2 +- .../BlockPatternExtended.java | 8 +++ .../block_pattern_matching/BlockSearch.java | 70 +++++++++++++++++++ .../BlockPatternMixin.java | 46 ++++++++++++ .../EndDragonFightMixin.java | 30 ++++++++ .../block_pattern_matching/package-info.java | 4 ++ common/src/main/resources/lithium.mixins.json | 2 + lithium-fabric-mixin-config.md | 4 ++ lithium-neoforge-mixin-config.md | 4 ++ 9 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 common/src/main/java/net/caffeinemc/mods/lithium/common/world/block_pattern_matching/BlockPatternExtended.java create mode 100644 common/src/main/java/net/caffeinemc/mods/lithium/common/world/block_pattern_matching/BlockSearch.java create mode 100644 common/src/main/java/net/caffeinemc/mods/lithium/mixin/block_pattern_matching/BlockPatternMixin.java create mode 100644 common/src/main/java/net/caffeinemc/mods/lithium/mixin/block_pattern_matching/EndDragonFightMixin.java create mode 100644 common/src/main/java/net/caffeinemc/mods/lithium/mixin/block_pattern_matching/package-info.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d87fcef5..6454abc7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ Lithium _LithiumVersion_ for Minecraft _MCVersion_ adds many new features. Make sure to take a backup of your world before using the mod and please report any bugs and mod compatibility issues at the [issue tracker](https://github.com/CaffeineMC/lithium-fabric/issues). You can check the [description of each optimization](https://github.com/CaffeineMC/lithium/blob/_ReleaseTag_/lithium-mixin-config.md) and how to disable it when encountering a problem. ## Additions -- Some new stuff +- Fast-path exit end portal search by counting nearby bedrock blocks. Reduces lag when placing the last end crystal when respawning the ender dragon. ## Changes - Many changes diff --git a/common/src/main/java/net/caffeinemc/mods/lithium/common/world/block_pattern_matching/BlockPatternExtended.java b/common/src/main/java/net/caffeinemc/mods/lithium/common/world/block_pattern_matching/BlockPatternExtended.java new file mode 100644 index 000000000..0769a5dc6 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/lithium/common/world/block_pattern_matching/BlockPatternExtended.java @@ -0,0 +1,8 @@ +package net.caffeinemc.mods.lithium.common.world.block_pattern_matching; + +import net.minecraft.world.level.block.Block; + +public interface BlockPatternExtended { + + void lithium$setRequiredBlock(Block block, int count); +} diff --git a/common/src/main/java/net/caffeinemc/mods/lithium/common/world/block_pattern_matching/BlockSearch.java b/common/src/main/java/net/caffeinemc/mods/lithium/common/world/block_pattern_matching/BlockSearch.java new file mode 100644 index 000000000..28bd6d1eb --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/lithium/common/world/block_pattern_matching/BlockSearch.java @@ -0,0 +1,70 @@ +package net.caffeinemc.mods.lithium.common.world.block_pattern_matching; + +import net.caffeinemc.mods.lithium.common.util.Pos; +import net.minecraft.core.BlockBox; +import net.minecraft.core.SectionPos; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunkSection; + +import java.util.function.Predicate; + +public class BlockSearch { + + public static boolean hasAtLeast(LevelReader levelReader, BlockBox searchBox, Block requiredBlock, int requiredBlockCount) { + Predicate predicate = blockState -> blockState.is(requiredBlock); + for (int chunkX = SectionPos.blockToSectionCoord(searchBox.min().getX()); chunkX <= SectionPos.blockToSectionCoord(searchBox.max().getX()); chunkX++) { + for (int chunkZ = SectionPos.blockToSectionCoord(searchBox.min().getZ()); chunkZ <= SectionPos.blockToSectionCoord(searchBox.max().getZ()); chunkZ++) { + + ChunkAccess chunk = levelReader.getChunk(chunkX, chunkZ); + int minSectionYIndex = Pos.SectionYIndex.fromBlockCoord(levelReader, searchBox.min().getY()); + int maxSectionYIndex = Pos.SectionYIndex.fromBlockCoord(levelReader, searchBox.max().getY()); + for (int sectionYIndex = minSectionYIndex; sectionYIndex <= maxSectionYIndex; sectionYIndex++) { + if (sectionYIndex >= 0 && sectionYIndex <= chunk.getSectionsCount()) { + LevelChunkSection section = chunk.getSection(sectionYIndex); + if (section.maybeHas(predicate)) { + int sectionYCoord = Pos.SectionYCoord.fromSectionIndex(levelReader, sectionYIndex); + requiredBlockCount -= countBlocksInBoxInSection( + section, + Math.max(searchBox.min().getX(), chunkX << 4), + Math.max(searchBox.min().getY(), sectionYCoord << 4), + Math.max(searchBox.min().getZ(), chunkZ << 4), + Math.min(searchBox.max().getX(), (chunkX << 4) + 15), + Math.min(searchBox.max().getY(), (sectionYCoord << 4) + 15), + Math.min(searchBox.max().getZ(), (chunkZ << 4) + 15), + requiredBlock, requiredBlockCount + ); + if (requiredBlockCount <= 0) { + return true; + } + } + } else if (requiredBlock == Blocks.VOID_AIR) { + return true; //Handle VOID_AIR somewhat correctly. We should count the volume, but noone uses void air anyway + } + } + } + } + return false; + } + + public static int countBlocksInBoxInSection(LevelChunkSection section, int minX, int minY, int minZ, int maxX, int maxY, int maxZ, Block requiredBlock, int findMax) { + int found = 0; + //Optimized iteration order xzy + for (int y = minY; y <= maxY; y++) { + for (int z = minZ; z <= maxZ; z++) { + for (int x = minX; x <= maxX; x++) { + if (section.getBlockState(x & 15, y & 15, z & 15).is(requiredBlock)) { + found++; + if (found >= findMax) { + return found; + } + } + } + } + } + return found; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/lithium/mixin/block_pattern_matching/BlockPatternMixin.java b/common/src/main/java/net/caffeinemc/mods/lithium/mixin/block_pattern_matching/BlockPatternMixin.java new file mode 100644 index 000000000..f8496cfa1 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/lithium/mixin/block_pattern_matching/BlockPatternMixin.java @@ -0,0 +1,46 @@ +package net.caffeinemc.mods.lithium.mixin.block_pattern_matching; + +import com.llamalad7.mixinextras.sugar.Local; +import net.caffeinemc.mods.lithium.common.world.block_pattern_matching.BlockPatternExtended; +import net.caffeinemc.mods.lithium.common.world.block_pattern_matching.BlockSearch; +import net.minecraft.core.BlockBox; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.pattern.BlockPattern; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(BlockPattern.class) +public class BlockPatternMixin implements BlockPatternExtended { + + + @Unique + private Block requiredBlock; + @Unique + private int requiredBlockCount; + + @Inject( + method = "find", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/BlockPos;offset(III)Lnet/minecraft/core/BlockPos;"), cancellable = true + ) + private void countRequiredBlocksBeforeExpensiveSearch(LevelReader levelReader, BlockPos blockPos, CallbackInfoReturnable cir, @Local int size) { + if (this.requiredBlock != null) { + BlockPos maxCorner = blockPos.offset(2 * size - 1, 2 * size - 1, 2 * size - 1); + BlockPos minCorner = blockPos.offset(-size, -size, -size); + + BlockBox searchBox = BlockBox.of(minCorner, maxCorner); + if (!BlockSearch.hasAtLeast(levelReader, searchBox, this.requiredBlock, this.requiredBlockCount)) { + cir.setReturnValue(null); + } + } + } + + @Override + public void lithium$setRequiredBlock(Block block, int count) { + this.requiredBlock = block; + this.requiredBlockCount = count; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/lithium/mixin/block_pattern_matching/EndDragonFightMixin.java b/common/src/main/java/net/caffeinemc/mods/lithium/mixin/block_pattern_matching/EndDragonFightMixin.java new file mode 100644 index 000000000..9d8a1515c --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/lithium/mixin/block_pattern_matching/EndDragonFightMixin.java @@ -0,0 +1,30 @@ +package net.caffeinemc.mods.lithium.mixin.block_pattern_matching; + +import net.caffeinemc.mods.lithium.common.world.block_pattern_matching.BlockPatternExtended; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.pattern.BlockPattern; +import net.minecraft.world.level.dimension.end.EndDragonFight; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(EndDragonFight.class) +public class EndDragonFightMixin { + + @Shadow + @Final + private BlockPattern exitPortalPattern; + + @Inject( + method = "(Lnet/minecraft/server/level/ServerLevel;JLnet/minecraft/world/level/dimension/end/EndDragonFight$Data;Lnet/minecraft/core/BlockPos;)V", at = @At("RETURN") + ) + private void setPatternToDragonPattern(ServerLevel serverLevel, long l, EndDragonFight.Data data, BlockPos blockPos, CallbackInfo ci) { + //Small todo: Find a way to not hardcode this, as this breaks mod compatibility when modifying the exit portal pattern + ((BlockPatternExtended) this.exitPortalPattern).lithium$setRequiredBlock(Blocks.BEDROCK, 41); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/lithium/mixin/block_pattern_matching/package-info.java b/common/src/main/java/net/caffeinemc/mods/lithium/mixin/block_pattern_matching/package-info.java new file mode 100644 index 000000000..5c3108d9e --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/lithium/mixin/block_pattern_matching/package-info.java @@ -0,0 +1,4 @@ +@MixinConfigOption(description = "Fast-path exit end portal search by counting nearby bedrock blocks. Reduces lag when placing the last end crystal when respawning the ender dragon.") +package net.caffeinemc.mods.lithium.mixin.block_pattern_matching; + +import net.caffeinemc.gradle.MixinConfigOption; \ No newline at end of file diff --git a/common/src/main/resources/lithium.mixins.json b/common/src/main/resources/lithium.mixins.json index ea86b660b..6e5361b56 100644 --- a/common/src/main/resources/lithium.mixins.json +++ b/common/src/main/resources/lithium.mixins.json @@ -81,6 +81,8 @@ "block.redstone_wire.DefaultRedstoneWireEvaluatorMixin", "block.redstone_wire.RedStoneWireBlockMixin", "block.redstone_wire.RedstoneWireEvaluatorMixin", + "block_pattern_matching.BlockPatternMixin", + "block_pattern_matching.EndDragonFightMixin", "cached_hashcode.FlowingFluid$BlockStatePairKeyMixin", "chunk.entity_class_groups.ClassInstanceMultiMapMixin", "chunk.no_locking.LevelChunkSectionMixin", diff --git a/lithium-fabric-mixin-config.md b/lithium-fabric-mixin-config.md index 970d45ded..8c9ed07f6 100644 --- a/lithium-fabric-mixin-config.md +++ b/lithium-fabric-mixin-config.md @@ -175,6 +175,10 @@ Moving blocks and retracting pistons avoid calculating their VoxelShapes by reus (default: `true`) Redstone wire power calculations avoid duplicate block accesses +### `mixin.block_pattern_matching` +(default: `true`) +Fast-path exit end portal search by counting nearby bedrock blocks. Reduces lag when placing the last end crystal when respawning the ender dragon. + ### `mixin.cached_hashcode` (default: `true`) BlockNeighborGroups used in fluid code cache their hashcode diff --git a/lithium-neoforge-mixin-config.md b/lithium-neoforge-mixin-config.md index 44e24c564..dbe5433bf 100644 --- a/lithium-neoforge-mixin-config.md +++ b/lithium-neoforge-mixin-config.md @@ -175,6 +175,10 @@ Moving blocks and retracting pistons avoid calculating their VoxelShapes by reus (default: `true`) Redstone wire power calculations avoid duplicate block accesses +### `mixin.block_pattern_matching` +(default: `true`) +Fast-path exit end portal search by counting nearby bedrock blocks. Reduces lag when placing the last end crystal when respawning the ender dragon. + ### `mixin.cached_hashcode` (default: `true`) BlockNeighborGroups used in fluid code cache their hashcode