Merge branch '1.20' into 1.21.1

This commit is contained in:
embeddedt 2026-05-23 14:44:44 -04:00
commit 5d862d0de3
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
8 changed files with 170 additions and 4 deletions

View File

@ -0,0 +1,9 @@
package org.embeddedt.modernfix.annotation;
public enum FeatureLevel {
GA, BETA;
public boolean isAtLeast(FeatureLevel required) {
return this.ordinal() >= required.ordinal();
}
}

View File

@ -0,0 +1,12 @@
package org.embeddedt.modernfix.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface RequiresFeatureLevel {
FeatureLevel value() default FeatureLevel.GA;
}

View File

@ -116,6 +116,7 @@ dependencies {
compileOnly("curse.maven:cofhcore-69162:5374122")
compileOnly("curse.maven:resourcefullib-570073:5659871")
compileOnly("curse.maven:kubejs-238086:5853326")
compileOnly("curse.maven:terrablender-neoforge-940057:6054947")
}
tasks.named<Jar>("jar") {

View File

@ -0,0 +1,97 @@
package org.embeddedt.modernfix.common.mixin.perf.optimize_surface_rules;
import com.google.common.collect.ImmutableList;
import com.llamalad7.mixinextras.sugar.Share;
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
import it.unimi.dsi.fastutil.objects.ObjectArraySet;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.levelgen.SurfaceRules;
import org.embeddedt.modernfix.annotation.FeatureLevel;
import org.embeddedt.modernfix.annotation.RequiresFeatureLevel;
import org.embeddedt.modernfix.annotation.RequiresMod;
import org.embeddedt.modernfix.world.gen.ExtendedSurfaceContext;
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.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import terrablender.worldgen.surface.NamespacedSurfaceRuleSource;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
@Mixin(NamespacedSurfaceRuleSource.class)
@RequiresMod("terrablender")
@RequiresFeatureLevel(FeatureLevel.BETA)
public class NamespacedSurfaceRuleSourceMixin {
@Shadow
@Final
private Map<String, SurfaceRules.RuleSource> sources;
@Shadow
@Final
private SurfaceRules.RuleSource base;
/**
* @author embeddedt
* @reason Avoid doing an expensive biome lookup per block in cases where we can prove all biomes will be from a
* single namespace. This achieves much of the benefit of TerraBlenderFix without the compatibility issues.
*/
@Inject(method = "apply(Lnet/minecraft/world/level/levelgen/SurfaceRules$Context;)Lnet/minecraft/world/level/levelgen/SurfaceRules$SurfaceRule;", at = @At("HEAD"), cancellable = true, remap = false)
private void modernfix$fastApply(SurfaceRules.Context context, CallbackInfoReturnable<SurfaceRules.SurfaceRule> cir,
@Share("possibleNamespaces") LocalRef<Set<String>> possibleNamespacesRef) {
var possibleBiomes = ((ExtendedSurfaceContext)(Object)context).mfix$getPossibleBiomes();
if (possibleBiomes == null) {
return;
}
Set<String> namespaces = mfix$findNamespaces(possibleBiomes);
possibleNamespacesRef.set(namespaces);
if (namespaces.size() != 1) {
return;
}
String singleNamespace = namespaces.iterator().next();
// In a single namespace scenario, we can bypass the biome lookup and directly construct a sequence rule
SurfaceRules.RuleSource namespacedSource = this.sources.get(singleNamespace);
if (namespacedSource == null) {
// Sequence rule wrapper not required
cir.setReturnValue(this.base.apply(context));
} else {
cir.setReturnValue(new SurfaceRules.SequenceRule(ImmutableList.of(namespacedSource.apply(context), this.base.apply(context))));
}
}
/**
* @author embeddedt
* @reason Even if we have to fall back to the namespaced source, avoid compiling surface rules for namespaces that
* will never be hit in the given chunk.
*/
@ModifyArg(method = "apply(Lnet/minecraft/world/level/levelgen/SurfaceRules$Context;)Lnet/minecraft/world/level/levelgen/SurfaceRules$SurfaceRule;", at = @At(value = "INVOKE", target = "Ljava/util/Set;forEach(Ljava/util/function/Consumer;)V"), remap = false)
private Consumer<Map.Entry<String, SurfaceRules.RuleSource>> mfix$filterConsumer(Consumer<Map.Entry<String, SurfaceRules.RuleSource>> originalConsumer,
@Share("possibleNamespaces") LocalRef<Set<String>> possibleNamespacesRef) {
var possibleNamespaces = possibleNamespacesRef.get();
if (possibleNamespaces == null) {
return originalConsumer;
}
return entry -> {
if(possibleNamespaces.contains(entry.getKey())) {
originalConsumer.accept(entry);
}
};
}
private static Set<String> mfix$findNamespaces(Set<ResourceKey<Biome>> possibleBiomes) {
if (possibleBiomes.size() == 1) {
return Set.of(possibleBiomes.iterator().next().location().getNamespace());
} else {
var namespaces = new ObjectArraySet<String>(4);
for (var key : possibleBiomes) {
namespaces.add(key.location().getNamespace());
}
return Set.copyOf(namespaces);
}
}
}

View File

@ -26,8 +26,15 @@ public class NoiseBasedChunkGeneratorMixin {
@SuppressWarnings("unchecked")
private static void mfix$accumulate(Set<ResourceKey<Biome>> chunkBiomes, LevelChunkSection section) {
var palette = ((ExtendedPalettedContainer<Holder<Biome>>)section.getBiomes()).mfix$getPalette();
for (int i = 0; i < palette.getSize(); i++) {
chunkBiomes.add(palette.valueFor(i).unwrapKey().orElseThrow());
if (palette.getSize() == 1) {
// No need to iterate the storage itself, as there can only be one value
chunkBiomes.add(palette.valueFor(0).unwrapKey().orElseThrow());
} else {
// Use getAll() rather than raw palette iteration. PalettedContainer.recreate() seeds the new
// palette with Biomes.PLAINS (the initial default), leaving a stale palette entry even after
// fillBiomesFromNoise replaces all cells with real biomes. getAll() only visits entries that
// are actually referenced in the backing storage, so stale entries are correctly excluded.
section.getBiomes().getAll(holder -> chunkBiomes.add(holder.unwrapKey().orElseThrow()));
}
}

View File

@ -3,6 +3,7 @@ package org.embeddedt.modernfix.core;
import com.google.common.collect.ImmutableSet;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.embeddedt.modernfix.annotation.FeatureLevel;
import org.embeddedt.modernfix.core.config.ModernFixEarlyConfig;
import org.embeddedt.modernfix.core.config.Option;
import org.embeddedt.modernfix.core.launchplugin.CoreLaunchPluginService;
@ -40,6 +41,11 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin {
this.logger.info("Loaded configuration file for ModernFix {}: {} options available, {} override(s) found",
ModernFixPlatformHooks.INSTANCE.getVersionString(), config.getOptionCount(), config.getOptionOverrideCount());
if(ModernFixEarlyConfig.ACTIVE_FEATURE_LEVEL != FeatureLevel.GA) {
this.logger.warn("ModernFix stability level is set to {}. Features at this level may be unstable or cause crashes.",
ModernFixEarlyConfig.ACTIVE_FEATURE_LEVEL);
}
config.getOptionMap().values().forEach(option -> {
if (option.isOverridden()) {
String source = "[unknown]";
@ -129,10 +135,17 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin {
}
String mixin = mixinClassName.substring(MIXIN_PACKAGE_ROOT.length());
if(!instance.isOptionEnabled(mixin))
if(!instance.isOptionEnabled(mixin)) {
this.logger.debug("Skipping mixin {}: disabled by configuration", mixin);
return false;
}
String disabledBecauseMod = instance.config.getPermanentlyDisabledMixins().get(mixin);
return disabledBecauseMod == null;
if(disabledBecauseMod != null) {
this.logger.debug("Skipping mixin {}: disabled for mod compat ({})", mixin, disabledBecauseMod);
return false;
}
this.logger.debug("Applying mixin {}", mixin);
return true;
}
public boolean isOptionEnabled(String mixin) {

View File

@ -9,7 +9,9 @@ import org.apache.commons.lang3.SystemUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.annotation.FeatureLevel;
import org.embeddedt.modernfix.annotation.IgnoreOutsideDev;
import org.embeddedt.modernfix.annotation.RequiresFeatureLevel;
import org.embeddedt.modernfix.annotation.RequiresMod;
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
@ -65,6 +67,18 @@ public class ModernFixEarlyConfig {
private static final String MIXIN_CLIENT_ONLY_DESC = Type.getDescriptor(ClientOnlyMixin.class);
private static final String MIXIN_REQUIRES_MOD_DESC = Type.getDescriptor(RequiresMod.class);
private static final String MIXIN_DEV_ONLY_DESC = Type.getDescriptor(IgnoreOutsideDev.class);
private static final String FEATURE_LEVEL_ANNOTATION_DESC = Type.getDescriptor(RequiresFeatureLevel.class);
public static final FeatureLevel ACTIVE_FEATURE_LEVEL = resolveFeatureLevel();
private static FeatureLevel resolveFeatureLevel() {
String prop = System.getProperty("modernfix.stabilityLevel", "ga").toUpperCase(Locale.ROOT);
try {
return FeatureLevel.valueOf(prop);
} catch (IllegalArgumentException e) {
return FeatureLevel.GA;
}
}
private static final Pattern PLATFORM_PREFIX = Pattern.compile("(neoforge|fabric|common)\\.");
@ -112,6 +126,7 @@ public class ModernFixEarlyConfig {
return;
boolean isMixin = false, isClientOnly = false, requiredModPresent = true, isDevOnly = false;
String requiredModId = "";
FeatureLevel requiredLevel = FeatureLevel.GA;
for(AnnotationNode annotation : node.invisibleAnnotations) {
if(Objects.equals(annotation.desc, MIXIN_DESC)) {
isMixin = true;
@ -130,6 +145,15 @@ public class ModernFixEarlyConfig {
}
} else if(Objects.equals(annotation.desc, MIXIN_DEV_ONLY_DESC)) {
isDevOnly = true;
} else if(Objects.equals(annotation.desc, FEATURE_LEVEL_ANNOTATION_DESC)) {
for(int i = 0; i < annotation.values.size(); i += 2) {
if(annotation.values.get(i).equals("value")) {
// ASM stores enum annotation values as String[]{typeDescriptor, constantName}
String[] enumVal = (String[]) annotation.values.get(i + 1);
requiredLevel = FeatureLevel.valueOf(enumVal[1]);
break;
}
}
}
}
if(isMixin && (!isDevOnly || ModernFixPlatformHooks.INSTANCE.isDevEnv())) {
@ -138,6 +162,8 @@ public class ModernFixEarlyConfig {
mixinsMissingMods.put(mixinClassName, requiredModId);
else if(isClientOnly && !ModernFixPlatformHooks.INSTANCE.isClient())
mixinsMissingMods.put(mixinClassName, "[not client]");
else if(!ACTIVE_FEATURE_LEVEL.isAtLeast(requiredLevel))
mixinsMissingMods.put(mixinClassName, "[feature level: requires " + requiredLevel + "]");
String mixinCategoryName = "mixin." + mixinClassName.substring(0, mixinClassName.lastIndexOf('.'));
mixinOptions.add(mixinCategoryName);
}

View File

@ -5,6 +5,7 @@ public net.minecraft.client.renderer.block.model.multipart.MultiPart definition
public net.minecraft.client.resources.model.ModelBakery$ModelBakerImpl
public net.minecraft.client.resources.model.ModelBakery$ModelBakerImpl <init>(Lnet/minecraft/client/resources/model/ModelBakery;Lnet/minecraft/client/resources/model/ModelBakery$TextureGetter;Lnet/minecraft/client/resources/model/ModelResourceLocation;)V
public net.minecraft.world.level.levelgen.SurfaceRules$SequenceRule
public net.minecraft.world.level.levelgen.SurfaceRules$SequenceRule <init>(Ljava/util/List;)V
public net.minecraft.world.level.levelgen.SurfaceRules$SequenceRuleSource
public net.minecraft.world.level.levelgen.SurfaceRules$SequenceRuleSource <init>(Ljava/util/List;)V
public net.minecraft.world.level.levelgen.SurfaceRules$TestRuleSource