Adjust dynamic_languages for better mod compatibility

This commit is contained in:
embeddedt 2026-04-11 14:39:36 -04:00
parent 438ceb1984
commit d749205427
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
2 changed files with 39 additions and 25 deletions

View File

@ -2,7 +2,8 @@ package org.embeddedt.modernfix.common.mixin.perf.dynamic_languages;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local; import com.llamalad7.mixinextras.sugar.Share;
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
import net.minecraft.client.resources.language.ClientLanguage; import net.minecraft.client.resources.language.ClientLanguage;
import net.minecraft.server.packs.resources.Resource; import net.minecraft.server.packs.resources.Resource;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin; import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
@ -11,39 +12,33 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg; import org.spongepowered.asm.mixin.injection.ModifyArg;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.BiConsumer; import java.util.Objects;
/** /**
* Modifies the language system to load/unload the contents of language entries based on GC pressure. * Modifies the language system to load/unload the contents of language entries based on GC pressure.
*/ */
@Mixin(ClientLanguage.class) @Mixin(value = ClientLanguage.class, priority = 2000)
@ClientOnlyMixin @ClientOnlyMixin
public class ClientLanguageMixin { public class ClientLanguageMixin {
private static final ThreadLocal<Boolean> MFIX_MODIFY_APPEND_SEMANTICS = ThreadLocal.withInitial(() -> Boolean.FALSE);
/** /**
* @author embeddedt * @author embeddedt
* @reason modify the semantics of appendFrom so that it's used to do a prepass * @reason collect the list of all known language resources
*/ */
@ModifyArg(method = "appendFrom", at = @At(value = "INVOKE", target = "Lnet/minecraft/locale/Language;loadFromJson(Ljava/io/InputStream;Ljava/util/function/BiConsumer;)V"), index = 1) @WrapOperation(method = "loadFrom", at = @At(value = "INVOKE",
private static BiConsumer<String, ?> changeSemanticsOfConsumer(BiConsumer<String, ?> consumer, @Local(ordinal = 0, argsOnly = true) Map<String, Object> destinationMap, @Local(ordinal = 0) Resource resource) { target = "Lnet/minecraft/client/resources/language/ClientLanguage;appendFrom(Ljava/lang/String;Ljava/util/List;Ljava/util/Map;)V"))
return MFIX_MODIFY_APPEND_SEMANTICS.get() ? ((k, v) -> destinationMap.put(k, resource)) : consumer; private static void collectResources(String languageName, List<Resource> resources,
} Map<String, String> destinationMap, Operation<Void> original,
@Share("usedResources") LocalRef<List<Resource>> usedResources) {
/** List<Resource> collected = usedResources.get();
* @author embeddedt if (collected == null) {
* @reason collect resources that own keys with a prepass collected = new ArrayList<>();
*/ usedResources.set(collected);
@WrapOperation(method = "loadFrom", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/language/ClientLanguage;appendFrom(Ljava/lang/String;Ljava/util/List;Ljava/util/Map;)V"))
private static void trackEntrySource(String languageName, List<Resource> resources, Map<String, String> destinationMap, Operation<Void> original) {
MFIX_MODIFY_APPEND_SEMANTICS.set(true);
try {
original.call(languageName, resources, destinationMap);
} finally {
MFIX_MODIFY_APPEND_SEMANTICS.remove();
} }
collected.addAll(resources);
original.call(languageName, resources, destinationMap);
} }
/** /**
@ -51,7 +46,8 @@ public class ClientLanguageMixin {
* @reason figure out which keys are dynamically loaded and which are injected by mixins * @reason figure out which keys are dynamically loaded and which are injected by mixins
*/ */
@ModifyArg(method = "loadFrom", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/language/ClientLanguage;<init>(Ljava/util/Map;Z)V"), index = 0) @ModifyArg(method = "loadFrom", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/language/ClientLanguage;<init>(Ljava/util/Map;Z)V"), index = 0)
private static Map<String, String> modifyLanguageMap(Map<String, ?> storage) { private static Map<String, String> modifyLanguageMap(Map<String, String> storage, @Share("usedResources") LocalRef<List<Resource>> usedResources) {
return DynamicLanguageMap.forStorage(Map.copyOf(storage)); List<Resource> collected = Objects.requireNonNullElse(usedResources.get(), List.of());
return DynamicLanguageMap.forVanillaData(storage, collected);
} }
} }

View File

@ -10,10 +10,28 @@ import net.minecraft.server.packs.resources.Resource;
import org.embeddedt.modernfix.ModernFix; import org.embeddedt.modernfix.ModernFix;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
public class DynamicLanguageMap { public class DynamicLanguageMap {
public static Map<String, String> forStorage(Map<String, ?> storage) { private static Map<String, Object> createStorage(Map<String, String> rawLanguageContents, List<Resource> languageResources) {
Map<String, Object> storage = new HashMap<>(rawLanguageContents);
for (var resource : languageResources) {
try (var stream = resource.open()) {
Language.loadFromJson(stream, (key, value) -> {
if (value != null && value.equals(storage.get(key))) {
storage.put(key, resource);
}
});
} catch (Exception ignored) {
}
}
return Map.copyOf(storage);
}
public static Map<String, String> forVanillaData(Map<String, String> rawLanguageContents, List<Resource> languageResources) {
Map<String, Object> storage = createStorage(rawLanguageContents, languageResources);
LoadingCache<Resource, Map<String, String>> languageFileContents = CacheBuilder.newBuilder() LoadingCache<Resource, Map<String, String>> languageFileContents = CacheBuilder.newBuilder()
.softValues() .softValues()
.build(new CacheLoader<>() { .build(new CacheLoader<>() {