Skip to content

Commit

Permalink
optimize explosion entity raycasts
Browse files Browse the repository at this point in the history
  • Loading branch information
RacoonDog committed Feb 23, 2025
1 parent b7d66a1 commit bfc85e6
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<Float> cir, @Share("context") LocalRef<MutableExplosionClipContext> contextRef, @Share("blockHitFactory") LocalRef<BiFunction<MutableExplosionClipContext, BlockPos, BlockHitResult>> 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<MutableExplosionClipContext> 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<MutableExplosionClipContext> contextRef, @Share("blockHitFactory") LocalRef<BiFunction<MutableExplosionClipContext, BlockPos, BlockHitResult>> hitFactoryRef) {
MutableExplosionClipContext context = contextRef.get();
BiFunction<MutableExplosionClipContext, BlockPos, BlockHitResult> 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<MutableExplosionClipContext, BlockPos, BlockHitResult> 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();
}
};
}
}
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions common/src/main/resources/lithium.mixins.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions lithium-fabric-mixin-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions lithium-neoforge-mixin-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit bfc85e6

Please # to comment.