From bfc85e656414369e7622cd660166a056c8c1f000 Mon Sep 17 00:00:00 2001 From: RacoonDog <32882447+racoondog@users.noreply.github.com> Date: Sun, 23 Feb 2025 03:33:43 -0500 Subject: [PATCH] optimize explosion entity raycasts --- .../MutableExplosionClipContext.java | 15 ++ .../entity_raycast/ServerExplosionMixin.java | 131 ++++++++++++++++++ .../entity_raycast/package-info.java | 4 + .../mixin/world/raycast/BlockGetterMixin.java | 3 + common/src/main/resources/lithium.mixins.json | 1 + lithium-fabric-mixin-config.md | 4 + lithium-neoforge-mixin-config.md | 4 + 7 files changed, 162 insertions(+) create mode 100644 common/src/main/java/net/caffeinemc/mods/lithium/common/world/explosions/MutableExplosionClipContext.java create mode 100644 common/src/main/java/net/caffeinemc/mods/lithium/mixin/world/explosions/entity_raycast/ServerExplosionMixin.java create mode 100644 common/src/main/java/net/caffeinemc/mods/lithium/mixin/world/explosions/entity_raycast/package-info.java diff --git a/common/src/main/java/net/caffeinemc/mods/lithium/common/world/explosions/MutableExplosionClipContext.java b/common/src/main/java/net/caffeinemc/mods/lithium/common/world/explosions/MutableExplosionClipContext.java new file mode 100644 index 000000000..62e5da6fe --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/lithium/common/world/explosions/MutableExplosionClipContext.java @@ -0,0 +1,15 @@ +package net.caffeinemc.mods.lithium.common.world.explosions; + +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; + +public class MutableExplosionClipContext { + public final Level level; + public final Vec3 to; + public Vec3 from = null; + + public MutableExplosionClipContext(Level level, Vec3 to) { + this.level = level; + this.to = to; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/lithium/mixin/world/explosions/entity_raycast/ServerExplosionMixin.java b/common/src/main/java/net/caffeinemc/mods/lithium/mixin/world/explosions/entity_raycast/ServerExplosionMixin.java new file mode 100644 index 000000000..c36bf189a --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/lithium/mixin/world/explosions/entity_raycast/ServerExplosionMixin.java @@ -0,0 +1,131 @@ +package net.caffeinemc.mods.lithium.mixin.world.explosions.entity_raycast; + +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalRef; +import net.caffeinemc.mods.lithium.common.util.Pos; +import net.caffeinemc.mods.lithium.common.world.explosions.MutableExplosionClipContext; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.*; +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 net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.CollisionContext; +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.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.function.BiFunction; + +/** + * @author Crosby + */ +@Mixin(ServerExplosion.class) +public class ServerExplosionMixin { + @SuppressWarnings("DataFlowIssue") + @Unique + private static final BlockHitResult MISS = BlockHitResult.miss(null, null, null); + @SuppressWarnings("DataFlowIssue") + @Unique + private static final ClipContext EMPTY = new ClipContext(null, null, null, null, (CollisionContext) null); + + /** + * Pre-allocate our {@link MutableExplosionClipContext}. + * @author Crosby + */ + @Inject( + method = "getSeenPercent", + at = @At("HEAD") + ) + private static void createMutableContext(Vec3 to, Entity entity, CallbackInfoReturnable cir, @Share("context") LocalRef contextRef, @Share("blockHitFactory") LocalRef> hitFactoryRef) { + contextRef.set(new MutableExplosionClipContext(entity.level(), to)); + hitFactoryRef.set(blockHitFactory()); + } + + /** + * Remove {@link ClipContext} allocation. + * @author Crosby + */ + @Redirect( + method = "getSeenPercent", + at = @At(value = "NEW", target = "net/minecraft/world/level/ClipContext") + ) + private static ClipContext removeUnusedObject(Vec3 from, Vec3 to, ClipContext.Block p_45690_, ClipContext.Fluid p_45691_, Entity entity, @Share("context") LocalRef contextRef) { + contextRef.get().from = from; + return EMPTY; + } + + /** + * Use specialized hit factory and remove miss allocation. + * @author Crosby + */ + @Redirect( + method = "getSeenPercent", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;clip(Lnet/minecraft/world/level/ClipContext;)Lnet/minecraft/world/phys/BlockHitResult;") + ) + private static BlockHitResult simplifyRaycast(Level level, ClipContext nullContext, @Share("context") LocalRef contextRef, @Share("blockHitFactory") LocalRef> hitFactoryRef) { + MutableExplosionClipContext context = contextRef.get(); + BiFunction blockHitFactory = hitFactoryRef.get(); + return BlockGetter.traverseBlocks(context.from, context.to, context, blockHitFactory, ctx -> MISS); + } + + /** + * Specialized version of {@link net.caffeinemc.mods.lithium.mixin.world.raycast.BlockGetterMixin#blockHitFactory(ClipContext)} + * where the inlined shape getter allows us to replace the {@link ClipContext} with our own {@link MutableExplosionClipContext}, + * eliminating extra allocations from the loop body of {@link ServerExplosion#getSeenPercent(Vec3, Entity)}. + * We also remove fluid handling and hit direction computation. + * @author Crosby + */ + @Unique + private static BiFunction blockHitFactory() { + return new BiFunction<>() { + int chunkX = Integer.MIN_VALUE, chunkZ = Integer.MIN_VALUE; + ChunkAccess chunk = null; + + @Override + public BlockHitResult apply(MutableExplosionClipContext context, BlockPos blockPos) { + BlockState state = getBlock(context.level, blockPos); + + return state.getCollisionShape(context.level, blockPos).clip(context.from, context.to, blockPos); + } + + private BlockState getBlock(LevelReader world, BlockPos blockPos) { + if (world.isOutsideBuildHeight(blockPos.getY())) { + return Blocks.VOID_AIR.defaultBlockState(); + } + int chunkX = Pos.ChunkCoord.fromBlockCoord(blockPos.getX()); + int chunkZ = Pos.ChunkCoord.fromBlockCoord(blockPos.getZ()); + + // Avoid calling into the chunk manager as much as possible through managing chunks locally + if (this.chunkX != chunkX || this.chunkZ != chunkZ) { + this.chunk = world.getChunk(chunkX, chunkZ); + + this.chunkX = chunkX; + this.chunkZ = chunkZ; + } + + final ChunkAccess chunk = this.chunk; + + // If the chunk is missing or out of bounds, assume that it is air + if (chunk != null) { + // We operate directly on chunk sections to avoid interacting with BlockPos and to squeeze out as much + // performance as possible here + LevelChunkSection section = chunk.getSections()[Pos.SectionYIndex.fromBlockCoord(chunk, blockPos.getY())]; + + // If the section doesn't exist or is empty, assume that the block is air + if (section != null && !section.hasOnlyAir()) { + return section.getBlockState(blockPos.getX() & 15, blockPos.getY() & 15, blockPos.getZ() & 15); + } + } + + return Blocks.AIR.defaultBlockState(); + } + }; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/lithium/mixin/world/explosions/entity_raycast/package-info.java b/common/src/main/java/net/caffeinemc/mods/lithium/mixin/world/explosions/entity_raycast/package-info.java new file mode 100644 index 000000000..e234edafa --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/lithium/mixin/world/explosions/entity_raycast/package-info.java @@ -0,0 +1,4 @@ +@MixinConfigOption(description = "Various improvements to explosion entity damage, e.g. simplifying the raycasts.") +package net.caffeinemc.mods.lithium.mixin.world.explosions.entity_raycast; + +import net.caffeinemc.gradle.MixinConfigOption; \ No newline at end of file diff --git a/common/src/main/java/net/caffeinemc/mods/lithium/mixin/world/raycast/BlockGetterMixin.java b/common/src/main/java/net/caffeinemc/mods/lithium/mixin/world/raycast/BlockGetterMixin.java index 02a5e9d70..4ca7f0aa1 100644 --- a/common/src/main/java/net/caffeinemc/mods/lithium/mixin/world/raycast/BlockGetterMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/lithium/mixin/world/raycast/BlockGetterMixin.java @@ -77,6 +77,9 @@ public BlockHitResult apply(ClipContext innerContext, BlockPos pos) { return d <= e ? blockHitResult : fluidHitResult; } + /** + * Modifications to this method should also be done in {@link net.caffeinemc.mods.lithium.mixin.world.explosions.entity_raycast.ServerExplosionMixin#blockHitFactory()} + */ private BlockState getBlock(LevelReader world, BlockPos blockPos) { if (world.isOutsideBuildHeight(blockPos.getY())) { return Blocks.VOID_AIR.defaultBlockState(); diff --git a/common/src/main/resources/lithium.mixins.json b/common/src/main/resources/lithium.mixins.json index 228d0bb90..87ab2cfbb 100644 --- a/common/src/main/resources/lithium.mixins.json +++ b/common/src/main/resources/lithium.mixins.json @@ -232,6 +232,7 @@ "world.combined_heightmap_update.HeightmapAccessor", "world.combined_heightmap_update.LevelChunkMixin", "world.explosions.block_raycast.ServerExplosionMixin", + "world.explosions.entity_raycast.ServerExplosionMixin", "world.game_events.dispatch.GameEventDispatcherMixin", "world.game_events.dispatch.LevelChunkMixin", "world.inline_block_access.LevelChunkMixin", diff --git a/lithium-fabric-mixin-config.md b/lithium-fabric-mixin-config.md index 41aee25a2..95eaad633 100644 --- a/lithium-fabric-mixin-config.md +++ b/lithium-fabric-mixin-config.md @@ -632,6 +632,10 @@ Various improvements to explosions. (default: `true`) Various improvements to explosion block damage, e.g. not accessing blocks along an explosion ray multiple times +### `mixin.world.explosions.entity_raycast` +(default: `true`) +Various improvements to explosion entity damage, e.g. simplifying the raycasts. + ### `mixin.world.game_events` (default: `true`) Various improvements to game events (vibrations) that are detected by allays, wardens and several sculk blocks. diff --git a/lithium-neoforge-mixin-config.md b/lithium-neoforge-mixin-config.md index 2e8400b2a..0c0efff6f 100644 --- a/lithium-neoforge-mixin-config.md +++ b/lithium-neoforge-mixin-config.md @@ -609,6 +609,10 @@ Various improvements to explosions. (default: `true`) Various improvements to explosion block damage, e.g. not accessing blocks along an explosion ray multiple times +### `mixin.world.explosions.entity_raycast` +(default: `true`) +Various improvements to explosion entity damage, e.g. simplifying the raycasts. + ### `mixin.world.game_events` (default: `true`) Various improvements to game events (vibrations) that are detected by allays, wardens and several sculk blocks.