diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 9292434d..5b621513 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -44,6 +44,8 @@ jobs: run: ./scripts/tagcleaner.sh - name: Build ModernFix using Gradle run: ./gradlew build + - name: Run mixin audit + run: timeout 60 xvfb-run ./gradlew runAuditClient - name: Publish mod to CurseForge & Modrinth if: steps.check_branch.outputs.is_release == 'true' run: ./gradlew publishMods copyJarToBin diff --git a/build.gradle.kts b/build.gradle.kts index 5ec76443..c1cab591 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -41,6 +41,10 @@ neoForge { create("server") { server() } + create("auditClient") { + client() + jvmArguments.addAll("-Dmodernfix.auditAndExit=true", "-Djava.awt.headless=true") + } } mods { diff --git a/src/main/java/org/embeddedt/modernfix/ModernFix.java b/src/main/java/org/embeddedt/modernfix/ModernFix.java index 09deaaa9..a1fdf9b6 100644 --- a/src/main/java/org/embeddedt/modernfix/ModernFix.java +++ b/src/main/java/org/embeddedt/modernfix/ModernFix.java @@ -13,6 +13,7 @@ import org.embeddedt.modernfix.core.ModernFixMixinPlugin; import org.embeddedt.modernfix.platform.ModernFixPlatformHooks; import org.embeddedt.modernfix.resources.ReloadExecutor; import org.embeddedt.modernfix.util.ClassInfoManager; +import org.spongepowered.asm.mixin.MixinEnvironment; import java.lang.management.ManagementFactory; @@ -45,6 +46,15 @@ public class ModernFix { return resourceReloadService; } + public static void runAuditIfRequested() { + boolean auditAndExit = Boolean.getBoolean("modernfix.auditAndExit"); + if (auditAndExit || Boolean.getBoolean("modernfix.auditMixinsAtStart")) { + MixinEnvironment.getCurrentEnvironment().audit(); + if (auditAndExit) { + System.exit(0); + } + } + } public ModernFix() { INSTANCE = this; diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_languages/ClientLanguageMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_languages/ClientLanguageMixin.java index 01595bdc..0d62a734 100644 --- a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_languages/ClientLanguageMixin.java +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_languages/ClientLanguageMixin.java @@ -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.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.server.packs.resources.Resource; 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.ModifyArg; +import java.util.ArrayList; import java.util.List; 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. */ -@Mixin(ClientLanguage.class) +@Mixin(value = ClientLanguage.class, priority = 2000) @ClientOnlyMixin public class ClientLanguageMixin { - private static final ThreadLocal MFIX_MODIFY_APPEND_SEMANTICS = ThreadLocal.withInitial(() -> Boolean.FALSE); - /** * @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) - private static BiConsumer changeSemanticsOfConsumer(BiConsumer consumer, @Local(ordinal = 0, argsOnly = true) Map destinationMap, @Local(ordinal = 0) Resource resource) { - return MFIX_MODIFY_APPEND_SEMANTICS.get() ? ((k, v) -> destinationMap.put(k, resource)) : consumer; - } - - /** - * @author embeddedt - * @reason collect resources that own keys with a prepass - */ - @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 resources, Map destinationMap, Operation original) { - MFIX_MODIFY_APPEND_SEMANTICS.set(true); - try { - original.call(languageName, resources, destinationMap); - } finally { - MFIX_MODIFY_APPEND_SEMANTICS.remove(); + @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 collectResources(String languageName, List resources, + Map destinationMap, Operation original, + @Share("usedResources") LocalRef> usedResources) { + List collected = usedResources.get(); + if (collected == null) { + collected = new ArrayList<>(); + usedResources.set(collected); } + 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 */ @ModifyArg(method = "loadFrom", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/language/ClientLanguage;(Ljava/util/Map;Z)V"), index = 0) - private static Map modifyLanguageMap(Map storage) { - return DynamicLanguageMap.forStorage(Map.copyOf(storage)); + private static Map modifyLanguageMap(Map storage, @Share("usedResources") LocalRef> usedResources) { + List collected = Objects.requireNonNullElse(usedResources.get(), List.of()); + return DynamicLanguageMap.forVanillaData(storage, collected); } } diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/SurfaceSystemMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/SurfaceSystemMixin.java index aa26d345..c80b375e 100644 --- a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/SurfaceSystemMixin.java +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/SurfaceSystemMixin.java @@ -26,7 +26,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.util.function.Function; -@Mixin(SurfaceSystem.class) +@Mixin(value = SurfaceSystem.class, priority = 2000) public class SurfaceSystemMixin { private static final ThreadLocal MFIX_LOOKUP_CACHE = ThreadLocal.withInitial(ChunkBiomeLookup::new); private static final ThreadLocal MFIX_BLOCK_COLUMN = new ThreadLocal<>(); @@ -43,7 +43,7 @@ public class SurfaceSystemMixin { return lookup; } - @Inject(method = "buildSurface", at = @At("RETURN")) + @Inject(method = "buildSurface", at = @At("TAIL")) private void finishAndDisposeLookups(RandomState randomState, BiomeManager biomeManager, Registry biomes, boolean p_224652_, WorldGenerationContext context, ChunkAccess chunk, NoiseChunk noiseChunk, SurfaceRules.RuleSource ruleSource, CallbackInfo ci) { MFIX_LOOKUP_CACHE.get().dispose(); var column = MFIX_BLOCK_COLUMN.get(); diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/suspend_integrated_server_during_load/IntegratedServerMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/suspend_integrated_server_during_load/IntegratedServerMixin.java index 9d8ac304..36aa2cc2 100644 --- a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/suspend_integrated_server_during_load/IntegratedServerMixin.java +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/suspend_integrated_server_during_load/IntegratedServerMixin.java @@ -18,6 +18,8 @@ import org.embeddedt.modernfix.duck.suspend_integrated_server_during_load.IDefer 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.callback.CallbackInfo; import java.net.Proxy; import java.util.Optional; @@ -30,6 +32,7 @@ public abstract class IntegratedServerMixin extends MinecraftServer implements I @Shadow private boolean paused; + private int mfix$numTickServerCalls = 0; private final AtomicBoolean mfix$hasPrimaryClientJoined = new AtomicBoolean(false); public IntegratedServerMixin(Thread serverThread, LevelStorageSource.LevelStorageAccess storageSource, PackRepository packRepository, WorldStem worldStem, Optional gameRules, Proxy proxy, DataFixer fixerUpper, Services services, LevelLoadListener levelLoadListener, boolean propagatesCrashes) { @@ -46,14 +49,26 @@ public abstract class IntegratedServerMixin extends MinecraftServer implements I return !mfix$hasPrimaryClientJoined.get() || original.call(instance); } + /** + * @author embeddedt + * @reason Keep our own tick count for the integrated server specifically, rather than relying on super + * to increment. + */ + @Inject(method = "tickServer", at = @At("HEAD")) + private void mfix$countTicks(CallbackInfo ci) { + this.mfix$numTickServerCalls++; + } + /** * @author embeddedt * @reason If waiting for a client connection to exist, we only need to tick the server connection, - * not the whole server as vanilla does. + * not the whole server as vanilla does. However, we must tick the whole server once to accommodate mods + * that rely on the first tick to initialize state as a side effect. Not doing this causes issues like + * #639. */ @WrapWithCondition(method = "tickServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;tickServer(Ljava/util/function/BooleanSupplier;)V", ordinal = 0)) private boolean preventRunningFullServerTick(MinecraftServer server, BooleanSupplier hasTimeLeft) { - if (this.paused && !mfix$hasPrimaryClientJoined.get()) { + if (this.mfix$numTickServerCalls >= 2 && this.paused && !mfix$hasPrimaryClientJoined.get()) { var conn = this.getConnection(); if (conn != null) { conn.tick(); diff --git a/src/main/java/org/embeddedt/modernfix/dynamiclanguages/DynamicLanguageMap.java b/src/main/java/org/embeddedt/modernfix/dynamiclanguages/DynamicLanguageMap.java index a3471627..9e6110dc 100644 --- a/src/main/java/org/embeddedt/modernfix/dynamiclanguages/DynamicLanguageMap.java +++ b/src/main/java/org/embeddedt/modernfix/dynamiclanguages/DynamicLanguageMap.java @@ -10,10 +10,28 @@ import net.minecraft.server.packs.resources.Resource; import org.embeddedt.modernfix.ModernFix; import java.io.IOException; +import java.util.HashMap; +import java.util.List; import java.util.Map; public class DynamicLanguageMap { - public static Map forStorage(Map storage) { + private static Map createStorage(Map rawLanguageContents, List languageResources) { + Map 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 forVanillaData(Map rawLanguageContents, List languageResources) { + Map storage = createStorage(rawLanguageContents, languageResources); LoadingCache> languageFileContents = CacheBuilder.newBuilder() .softValues() .build(new CacheLoader<>() { diff --git a/src/main/java/org/embeddedt/modernfix/neoforge/init/ModernFixForge.java b/src/main/java/org/embeddedt/modernfix/neoforge/init/ModernFixForge.java index e0695d1f..c61ed8b2 100644 --- a/src/main/java/org/embeddedt/modernfix/neoforge/init/ModernFixForge.java +++ b/src/main/java/org/embeddedt/modernfix/neoforge/init/ModernFixForge.java @@ -77,6 +77,7 @@ public class ModernFixForge { ModLoader.addLoadingIssue(ModLoadingIssue.warning("modernfix.perf_mod_warning")); }); } + event.enqueueWork(ModernFix::runAuditIfRequested); } private void registerNetworkChannel(final RegisterPayloadHandlersEvent event) {