Skip to content

Commit

Permalink
Avoid creating and dispatching to empty game event dispatchers
Browse files Browse the repository at this point in the history
  • Loading branch information
2No2Name committed Jun 11, 2024
1 parent 68c5799 commit fc7f950
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 0 deletions.
8 changes: 8 additions & 0 deletions lithium-mixin-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,14 @@ Various improvements to explosion block damage, e.g. not accessing blocks along
(default: `true`)
Caches entity explosion exposure to avoid duplicate calculations.

### `mixin.world.game_events`
(default: `true`)
Various improvements to game events (vibrations).

### `mixin.world.game_events.dispatch_to_empty`
(default: `true`)
Create game event dispatchers for chunk sections only when needed, i.e. when a listener is added to a section. This reduces memory usage for chunks that do not have any listeners. This speeds up attempting to dispatch events when there are no nearby listeners, which could be sculk sensors, allays, wardens or sculk shriekers.

### `mixin.world.inline_block_access`
(default: `true`)
Faster block and fluid access due to inlining and reduced method size
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package me.jellysquid.mods.lithium.common.world.chunk;

import net.minecraft.world.event.listener.GameEventDispatcher;
import org.jetbrains.annotations.Nullable;

public interface ChunkWithEmptyGameEventDispatcher {

@Nullable GameEventDispatcher lithium$getExistingGameEventDispatcher(int ySectionCoord);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package me.jellysquid.mods.lithium.mixin.world.game_events.dispatch_to_empty;

import me.jellysquid.mods.lithium.common.world.chunk.ChunkWithEmptyGameEventDispatcher;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.event.GameEvent;
import net.minecraft.world.event.listener.GameEventDispatchManager;
import net.minecraft.world.event.listener.GameEventDispatcher;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;

@Mixin(GameEventDispatchManager.class)
public class GameEventDispatchManagerMixin {

@Redirect(
method = "dispatch",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/chunk/Chunk;getGameEventDispatcher(I)Lnet/minecraft/world/event/listener/GameEventDispatcher;"
)
)
private GameEventDispatcher existingGameEventDispatcherOrNull(Chunk chunk, int ySectionCoord) {
if (chunk instanceof ChunkWithEmptyGameEventDispatcher)
return ((ChunkWithEmptyGameEventDispatcher) chunk).lithium$getExistingGameEventDispatcher(ySectionCoord);
return chunk.getGameEventDispatcher(ySectionCoord);
}
@Redirect(
method = "dispatch",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/event/listener/GameEventDispatcher;dispatch(Lnet/minecraft/registry/entry/RegistryEntry;Lnet/minecraft/util/math/Vec3d;Lnet/minecraft/world/event/GameEvent$Emitter;Lnet/minecraft/world/event/listener/GameEventDispatcher$DispatchCallback;)Z"
)
)
private boolean handleNullDispatcher(@Nullable GameEventDispatcher dispatcher, RegistryEntry<GameEvent> gameEventRegistryEntry, Vec3d vec3d, GameEvent.Emitter emitter, GameEventDispatcher.DispatchCallback dispatchCallback) {
if (dispatcher == null) {
return false;
}
return dispatcher.dispatch(gameEventRegistryEntry, vec3d, emitter, dispatchCallback);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package me.jellysquid.mods.lithium.mixin.world.game_events.dispatch_to_empty;

import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import me.jellysquid.mods.lithium.common.world.chunk.ChunkWithEmptyGameEventDispatcher;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.World;
import net.minecraft.world.chunk.ChunkSection;
import net.minecraft.world.chunk.UpgradeData;
import net.minecraft.world.chunk.WorldChunk;
import net.minecraft.world.event.listener.GameEventDispatcher;
import net.minecraft.world.gen.chunk.BlendingData;
import net.minecraft.world.tick.ChunkTickScheduler;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

@Mixin(WorldChunk.class)
public class WorldChunkMixin implements ChunkWithEmptyGameEventDispatcher {

private static final Int2ObjectOpenHashMap<?> EMPTY_MAP = new Int2ObjectOpenHashMap<>(0);

@Shadow
@Final
@Mutable
private Int2ObjectMap<GameEventDispatcher> gameEventDispatchers;

@Redirect(
method = "<init>(Lnet/minecraft/world/World;Lnet/minecraft/util/math/ChunkPos;Lnet/minecraft/world/chunk/UpgradeData;Lnet/minecraft/world/tick/ChunkTickScheduler;Lnet/minecraft/world/tick/ChunkTickScheduler;J[Lnet/minecraft/world/chunk/ChunkSection;Lnet/minecraft/world/chunk/WorldChunk$EntityLoader;Lnet/minecraft/world/gen/chunk/BlendingData;)V",
at = @At(value = "NEW", target = "it/unimi/dsi/fastutil/ints/Int2ObjectOpenHashMap"),
require = 1, allow = 1
)
private Int2ObjectOpenHashMap<?> initGameEventDispatchers() {
return EMPTY_MAP;
}
@Inject(
method = "<init>(Lnet/minecraft/world/World;Lnet/minecraft/util/math/ChunkPos;Lnet/minecraft/world/chunk/UpgradeData;Lnet/minecraft/world/tick/ChunkTickScheduler;Lnet/minecraft/world/tick/ChunkTickScheduler;J[Lnet/minecraft/world/chunk/ChunkSection;Lnet/minecraft/world/chunk/WorldChunk$EntityLoader;Lnet/minecraft/world/gen/chunk/BlendingData;)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/Heightmap$Type;values()[Lnet/minecraft/world/Heightmap$Type;"),
require = 1, allow = 1
)
private void replaceWithNullMap(World world, ChunkPos pos, UpgradeData upgradeData, ChunkTickScheduler<?> blockTickScheduler, ChunkTickScheduler<?> fluidTickScheduler, long inhabitedTime, ChunkSection[] sectionArrayInitializer, WorldChunk.EntityLoader entityLoader, BlendingData blendingData, CallbackInfo ci) {
if (this.gameEventDispatchers == EMPTY_MAP) {
this.gameEventDispatchers = null;
}
}

@Override
public @Nullable GameEventDispatcher lithium$getExistingGameEventDispatcher(int ySectionCoord) {
if (this.gameEventDispatchers != null) {
return this.gameEventDispatchers.get(ySectionCoord);
}
return null;
}

@Inject(
method = "getGameEventDispatcher(I)Lnet/minecraft/world/event/listener/GameEventDispatcher;",
at = @At(value = "FIELD", shift = At.Shift.BEFORE, target = "Lnet/minecraft/world/chunk/WorldChunk;gameEventDispatchers:Lit/unimi/dsi/fastutil/ints/Int2ObjectMap;")
)
private void initializeCollection(int ySectionCoord, CallbackInfoReturnable<GameEventDispatcher> cir) {
if (this.gameEventDispatchers == null) {
this.gameEventDispatchers = new Int2ObjectOpenHashMap<>(4);
}
}

@Inject(
method = "removeGameEventDispatcher(I)V",
at = @At("RETURN")
)
private void removeGameEventDispatcher(int ySectionCoord, CallbackInfo ci) {
if (this.gameEventDispatchers != null && this.gameEventDispatchers.isEmpty()) {
this.gameEventDispatchers = null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@MixinConfigOption(
description = "Create game event dispatchers for chunk sections only when needed, i.e. when a" +
" listener is added to a section. This reduces memory usage for chunks that do not have any listeners." +
" This speeds up attempting to dispatch events when there are no nearby listeners, which could be sculk" +
" sensors, allays, wardens or sculk shriekers."
)
package me.jellysquid.mods.lithium.mixin.world.game_events.dispatch_to_empty;

import net.caffeinemc.gradle.MixinConfigOption;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@MixinConfigOption(description = "Various improvements to game events (vibrations).")
package me.jellysquid.mods.lithium.mixin.world.game_events;

import net.caffeinemc.gradle.MixinConfigOption;
2 changes: 2 additions & 0 deletions src/main/resources/lithium.mixins.json
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@
"world.explosions.block_raycast.ExplosionMixin",
"world.explosions.cache_exposure.ExplosionBehaviorMixin",
"world.explosions.cache_exposure.ExplosionMixin",
"world.game_events.dispatch_to_empty.GameEventDispatchManagerMixin",
"world.game_events.dispatch_to_empty.WorldChunkMixin",
"world.inline_block_access.WorldChunkMixin",
"world.inline_block_access.WorldMixin",
"world.inline_height.WorldChunkMixin",
Expand Down

0 comments on commit fc7f950

Please sign in to comment.