From 04f3e928226b1867101f850f25577925badf394c Mon Sep 17 00:00:00 2001 From: gniftygnome <50962022+gniftygnome@users.noreply.github.com> Date: Thu, 13 Jun 2024 17:49:03 -0700 Subject: [PATCH] Improve various Ashy Shoals performance issues. (#163) - Allow Ashy Shoals in dimensions besides vanilla Nether - Add config option to limit ash particle probability --- .../mixin/MixinBiomeParticleConfig.java | 31 ++++++++ .../assets/cinderscapes/lang/en_us.json | 2 + client/src/main/resources/fabric.mod.json | 1 + .../resources/mixins.cinderscapes-client.json | 11 +++ .../config/CinderscapesConfig.java | 2 + .../cinderscapes/mixin/MixinServerWorld.java | 76 +++++++++++++------ 6 files changed, 99 insertions(+), 24 deletions(-) create mode 100644 client/src/main/java/com/terraformersmc/cinderscapes/mixin/MixinBiomeParticleConfig.java create mode 100644 client/src/main/resources/mixins.cinderscapes-client.json diff --git a/client/src/main/java/com/terraformersmc/cinderscapes/mixin/MixinBiomeParticleConfig.java b/client/src/main/java/com/terraformersmc/cinderscapes/mixin/MixinBiomeParticleConfig.java new file mode 100644 index 00000000..071c6051 --- /dev/null +++ b/client/src/main/java/com/terraformersmc/cinderscapes/mixin/MixinBiomeParticleConfig.java @@ -0,0 +1,31 @@ +package com.terraformersmc.cinderscapes.mixin; + +import com.terraformersmc.cinderscapes.Cinderscapes; +import com.terraformersmc.cinderscapes.config.CinderscapesConfig; +import net.minecraft.particle.ParticleEffect; +import net.minecraft.world.biome.BiomeParticleConfig; +import org.spongepowered.asm.mixin.*; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(BiomeParticleConfig.class) +public class MixinBiomeParticleConfig { + @Shadow + @Mutable + @Final + private float probability; + + @Unique + private static final float ASH_PARTICLE_LIMIT = 0.125f; + + @Inject(method = "", at = @At("TAIL"), locals = LocalCapture.NO_CAPTURE) + private void cinderscapes$configurableAshParticle(ParticleEffect particle, float probability, CallbackInfo ci) { + if (CinderscapesConfig.INSTANCE.limitAshParticles && probability > ASH_PARTICLE_LIMIT) { + Cinderscapes.LOGGER.info("Limiting ash particle probability from {} to {}", probability, ASH_PARTICLE_LIMIT); + + this.probability = ASH_PARTICLE_LIMIT; + } + } +} diff --git a/client/src/main/resources/assets/cinderscapes/lang/en_us.json b/client/src/main/resources/assets/cinderscapes/lang/en_us.json index 6e92b2c9..5deaa73d 100644 --- a/client/src/main/resources/assets/cinderscapes/lang/en_us.json +++ b/client/src/main/resources/assets/cinderscapes/lang/en_us.json @@ -127,6 +127,8 @@ "text.autoconfig.cinderscapes.title": "Cinderscapes Config", "text.autoconfig.cinderscapes.option.easterEggs": "Enable Easter Eggs", "text.autoconfig.cinderscapes.option.enableAshFall": "Enable Ash Fall", + "text.autoconfig.cinderscapes.option.limitAshParticles": "(Client) Limit ash particles", + "text.autoconfig.cinderscapes.option.limitAshParticles.@Tooltip": "Ash particle rate limit roughly that of Basalt Delta", "text.autoconfig.cinderscapes.option.polypiteLuminance": "Polypite Quartz Brightness", "text.autoconfig.cinderscapes.option.polypiteLuminance.@Tooltip": "Only affects newly generated chunks (lighting cache)", "text.autoconfig.cinderscapes.option.biomes": "Modify Biome Generation", diff --git a/client/src/main/resources/fabric.mod.json b/client/src/main/resources/fabric.mod.json index 70db62bf..0e2be1e6 100644 --- a/client/src/main/resources/fabric.mod.json +++ b/client/src/main/resources/fabric.mod.json @@ -12,6 +12,7 @@ ] }, "mixins": [ + "mixins.cinderscapes-client.json" ], "custom": { "modmenu": { diff --git a/client/src/main/resources/mixins.cinderscapes-client.json b/client/src/main/resources/mixins.cinderscapes-client.json new file mode 100644 index 00000000..fdd43d8e --- /dev/null +++ b/client/src/main/resources/mixins.cinderscapes-client.json @@ -0,0 +1,11 @@ +{ + "required": true, + "package": "com.terraformersmc.cinderscapes.mixin", + "compatibilityLevel": "JAVA_17", + "client": [ + "MixinBiomeParticleConfig" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/common/src/main/java/com/terraformersmc/cinderscapes/config/CinderscapesConfig.java b/common/src/main/java/com/terraformersmc/cinderscapes/config/CinderscapesConfig.java index 5db5e011..81bca29c 100644 --- a/common/src/main/java/com/terraformersmc/cinderscapes/config/CinderscapesConfig.java +++ b/common/src/main/java/com/terraformersmc/cinderscapes/config/CinderscapesConfig.java @@ -19,6 +19,8 @@ public final class CinderscapesConfig implements ConfigData { public boolean easterEggs = false; + public boolean limitAshParticles = false; + @ConfigEntry.Gui.Tooltip @ConfigEntry.BoundedDiscrete(max = 15) public int polypiteLuminance = 4; diff --git a/common/src/main/java/com/terraformersmc/cinderscapes/mixin/MixinServerWorld.java b/common/src/main/java/com/terraformersmc/cinderscapes/mixin/MixinServerWorld.java index a0a6fedb..e38ccb6a 100644 --- a/common/src/main/java/com/terraformersmc/cinderscapes/mixin/MixinServerWorld.java +++ b/common/src/main/java/com/terraformersmc/cinderscapes/mixin/MixinServerWorld.java @@ -7,16 +7,25 @@ import net.minecraft.block.BlockState; import net.minecraft.registry.DynamicRegistryManager; import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.WorldGenerationProgressListener; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Direction; +import net.minecraft.util.math.ChunkSectionPos; +import net.minecraft.util.math.random.RandomSequencesState; import net.minecraft.util.profiler.Profiler; import net.minecraft.world.MutableWorldProperties; import net.minecraft.world.World; import net.minecraft.world.biome.Biome; +import net.minecraft.world.chunk.ChunkSection; +import net.minecraft.world.dimension.DimensionOptions; import net.minecraft.world.dimension.DimensionType; -import net.minecraft.world.dimension.DimensionTypes; +import net.minecraft.world.level.ServerWorldProperties; +import net.minecraft.world.level.storage.LevelStorage; +import net.minecraft.world.spawner.SpecialSpawner; +import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; @@ -24,53 +33,72 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.LocalCapture; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.function.Predicate; import java.util.function.Supplier; @Mixin(ServerWorld.class) -abstract class MixinServerWorld extends World { - private MixinServerWorld(MutableWorldProperties properties, RegistryKey registryRef, DynamicRegistryManager registryManager, RegistryEntry dimensionEntry, Supplier profiler, boolean isClient, boolean debugWorld, long biomeAccess, int maxChainedNeighborUpdates) { +public abstract class MixinServerWorld extends World { + @Unique + private static Predicate> ASHY_SHOALS_PREDICATE; + + protected MixinServerWorld(MutableWorldProperties properties, RegistryKey registryRef, DynamicRegistryManager registryManager, RegistryEntry dimensionEntry, Supplier profiler, boolean isClient, boolean debugWorld, long biomeAccess, int maxChainedNeighborUpdates) { super(properties, registryRef, registryManager, dimensionEntry, profiler, isClient, debugWorld, biomeAccess, maxChainedNeighborUpdates); } + @Inject(method = "", at = @At("TAIL")) + private void cinderscapes$cacheAshyShoalsEntry(MinecraftServer server, Executor workerExecutor, LevelStorage.Session session, ServerWorldProperties properties, RegistryKey worldKey, DimensionOptions dimensionOptions, WorldGenerationProgressListener worldGenerationProgressListener, boolean debugWorld, long seed, List spawners, boolean shouldTickTime, @Nullable RandomSequencesState randomSequencesState, CallbackInfo ci) { + ASHY_SHOALS_PREDICATE = Predicate.isEqual(getRegistryManager().get(RegistryKeys.BIOME).entryOf(CinderscapesBiomes.ASHY_SHOALS)); + } + /* * NOTE: Unlike this mixin, vanilla evaluates the tick only at the surface Y level. * This means there is no utility for us in reusing vanilla's calculations. */ - @Inject(method="tickIceAndSnow", at = @At(value = "HEAD"), locals = LocalCapture.NO_CAPTURE) + @Inject(method = "tickIceAndSnow", at = @At("HEAD"), locals = LocalCapture.NO_CAPTURE) private void cinderscapes$tickAsh(BlockPos tickPos, CallbackInfo ci) { - // Ashy shoals only exists in the nether, why do this iteration on any other dimension - if (!getDimensionEntry().matchesKey(DimensionTypes.THE_NETHER)) return; - if (CinderscapesConfig.INSTANCE.enableAshFall) { - BlockPos.Mutable pos = tickPos.mutableCopy(); + if (!CinderscapesConfig.INSTANCE.enableAshFall) { + return; + } - for (; pos.getY() < 127; pos.setY(pos.getY() + 1)) { - BlockPos up = pos.up(); - if (!canPlace(up)) { - continue; - } else if (!this.getBiome(up).matchesKey(CinderscapesBiomes.ASHY_SHOALS)) { - continue; - } - this.setBlockState(up, CinderscapesBlocks.ASH.getDefaultState()); + BlockPos.Mutable pos = tickPos.mutableCopy(); + ChunkSection[] sections = getChunk(tickPos).getSectionArray(); + int sectionIndex; + + for (pos.setY(pos.getY() + 1); pos.getY() < 127; pos.setY(pos.getY() + 1)) { + sectionIndex = ChunkSectionPos.getSectionCoord(pos.getY()); + + if (sections[sectionIndex].isEmpty() || !sections[sectionIndex].getBiomeContainer().hasAny(ASHY_SHOALS_PREDICATE)) { + pos.setY(ChunkSectionPos.getBlockCoord(sectionIndex + 1) - 1); + continue; + } + + if (canPlaceAshAt(pos) && getBiome(pos).matchesKey(CinderscapesBiomes.ASHY_SHOALS)) { + setBlockState(pos, CinderscapesBlocks.ASH.getDefaultState()); break; } } } @Unique - private boolean canPlace(BlockPos pos){ - return this.getBlockState(pos).isAir() && + private boolean canPlaceAshAt(BlockPos.Mutable pos) { + return getBlockState(pos).isAir() && blockAbove(pos).isIn(CinderscapesBlockTags.ASH_PERMEABLE) && CinderscapesBlocks.ASH.getDefaultState().canPlaceAt(this, pos); } @Unique - private BlockState blockAbove(BlockPos pos) { - BlockPos iPos = pos.mutableCopy(); + private BlockState blockAbove(BlockPos.Mutable pos) { + int originalY = pos.getY(); + BlockState stateAbove; - //up() makes new immutable blockpos per call. This uses the Mutable as a Mutable. //noinspection StatementWithEmptyBody - for (; isAir(iPos) && iPos.getY() < 127; iPos.setY(iPos.getY() + 1)); + for (; isAir(pos) && pos.getY() < 127; pos.setY(pos.getY() + 1)); + + stateAbove = getBlockState(pos); + pos.setY(originalY); - return getBlockState(iPos); + return stateAbove; } } \ No newline at end of file