Merge remote-tracking branch 'origin/1.20' into 1.21.1

This commit is contained in:
embeddedt 2026-04-10 19:18:13 -04:00
commit c45f063bfb
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
10 changed files with 276 additions and 64 deletions

View File

@ -1,7 +1,7 @@
package org.embeddedt.modernfix.common.mixin.feature.cause_lag_by_disabling_threads;
import net.minecraft.Util;
import org.embeddedt.modernfix.util.DirectExecutorService;
import org.embeddedt.modernfix.util.SingleThreadedWorkerService;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
@ -12,5 +12,5 @@ import java.util.concurrent.ExecutorService;
@Mixin(Util.class)
public class UtilMixin {
@Shadow @Final @Mutable
private static final ExecutorService BACKGROUND_EXECUTOR = new DirectExecutorService();
private static final ExecutorService BACKGROUND_EXECUTOR = new SingleThreadedWorkerService();
}

View File

@ -0,0 +1,40 @@
package org.embeddedt.modernfix.common.mixin.perf.compact_entity_models;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.model.geom.builders.CubeDefinition;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@Mixin(CubeDefinition.class)
@ClientOnlyMixin
public class CubeDefinitionMixin {
@Unique
private static final ConcurrentHashMap<List<Object>, ModelPart.Cube> MFIX_CUBE_CACHE = new ConcurrentHashMap<>();
/**
* @author embeddedt
* @reason deduplicate creation of Cube objects
*/
@WrapOperation(method = "bake", at = @At(value = "NEW", target = "(IIFFFFFFFFFZFFLjava/util/Set;)Lnet/minecraft/client/model/geom/ModelPart$Cube;"))
private ModelPart.Cube modernfix$deduplicateCube(int texCoordU, int texCoordV, float originX, float originY, float originZ,
float dimensionX, float dimensionY, float dimensionZ, float gtowX,
float growY, float growZ, boolean mirror, float texScaleU,
float texScaleV, Set visibleFaces,
Operation<ModelPart.Cube> original) {
List<Object> cacheKey = List.of(texCoordU, texCoordV, originX, originY, originZ, dimensionX, dimensionY, dimensionZ, gtowX, growY, growZ, mirror, texScaleU, texScaleV, visibleFaces);
var cube = MFIX_CUBE_CACHE.get(cacheKey);
if (cube == null) {
cube = original.call((Object[])cacheKey.toArray());
MFIX_CUBE_CACHE.put(cacheKey, cube);
}
return cube;
}
}

View File

@ -0,0 +1,57 @@
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 net.minecraft.client.resources.language.ClientLanguage;
import net.minecraft.server.packs.resources.Resource;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.dynamiclanguages.DynamicLanguageMap;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
/**
* Modifies the language system to load/unload the contents of language entries based on GC pressure.
*/
@Mixin(ClientLanguage.class)
@ClientOnlyMixin
public class ClientLanguageMixin {
private static final ThreadLocal<Boolean> 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
*/
@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<String, ?> changeSemanticsOfConsumer(BiConsumer<String, ?> consumer, @Local(ordinal = 0, argsOnly = true) Map<String, Object> 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<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();
}
}
/**
* @author embeddedt
* @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)
private static Map<String, String> modifyLanguageMap(Map<String, ?> storage) {
return DynamicLanguageMap.forStorage(Map.copyOf(storage));
}
}

View File

@ -0,0 +1,38 @@
package org.embeddedt.modernfix.common.mixin.perf.forge_registry_alloc;
import net.minecraft.world.level.levelgen.DebugLevelSource;
import net.neoforged.neoforge.registries.GameData;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.AbstractList;
import java.util.stream.Collector;
import java.util.stream.Stream;
@Mixin(DebugLevelSource.class)
public class DebugLevelSourceMixin {
/**
* @author embeddedt
* @reason Reuse the existing blockstate list held by Forge instead of making a new one
*/
@Redirect(method = "initValidStates", at = @At(value = "INVOKE", target = "Ljava/util/stream/Stream;collect(Ljava/util/stream/Collector;)Ljava/lang/Object;", ordinal = 0), remap = false)
private static Object getStateList(Stream<?> instance, Collector<?, ?, ?> arCollector) {
var idMapper = GameData.getBlockStateIDMap();
return new AbstractList<>() {
@Override
public int size() {
return idMapper.size();
}
@Override
public Object get(int index) {
var o = idMapper.byId(index);
if (o == null) {
throw new IndexOutOfBoundsException();
}
return o;
}
};
}
}

View File

@ -1,28 +1,34 @@
package org.embeddedt.modernfix.common.mixin.safety;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import net.minecraft.client.Minecraft;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.client.renderer.entity.layers.RenderLayer;
import net.minecraft.world.entity.Entity;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
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.callback.CallbackInfo;
import java.util.Collections;
import java.util.List;
@Mixin(LivingEntityRenderer.class)
@ClientOnlyMixin
public class LivingEntityRendererMixin {
@Shadow @Final @Mutable
protected List<RenderLayer<?, ?>> layers;
public abstract class LivingEntityRendererMixin<T extends Entity, M extends EntityModel<T>> {
@Shadow
public abstract boolean addLayer(RenderLayer<T, M> layer);
@Inject(method = "<init>", at = @At("RETURN"))
private void synchronizeLayerList(CallbackInfo ci) {
/* allows buggy mods to call addLayer concurrently, order is not deterministic but can't fix that */
this.layers = Collections.synchronizedList(layers);
/**
* @author embeddedt
* @reason avoid CMEs from buggy mods calling addLayer on wrong thread
*/
@WrapMethod(method = "addLayer")
private boolean handleOffThreadLayerAdd(RenderLayer<T, M> layer, Operation<Boolean> original) {
if (!Minecraft.getInstance().isSameThread()) {
ModernFix.LOGGER.error("LivingEntityRenderer.addLayer called on wrong thread", new Exception());
Minecraft.getInstance().tell(() -> this.addLayer(layer));
return true;
}
return original.call(layer);
}
}

View File

@ -173,15 +173,12 @@ public class ModernFixEarlyConfig {
.put("mixin.feature.blockentity_incorrect_thread", false)
.put("mixin.perf.clear_mixin_classinfo", false)
.put("mixin.perf.deduplicate_climate_parameters", false)
.put("mixin.perf.faster_capabilities.bytecode_analysis", false)
.put("mixin.bugfix.packet_leak", false)
.put("mixin.perf.deduplicate_location", false)
.put("mixin.perf.dynamic_entity_renderers", false)
.put("mixin.feature.integrated_server_watchdog", true)
.put("mixin.perf.faster_item_rendering", false)
.put("mixin.perf.ingredient_item_deduplication", false)
.put("mixin.feature.spam_thread_dump", false)
.put("mixin.feature.disable_unihex_font", false)
.put("mixin.feature.remove_chat_signing", false)
.put("mixin.bugfix.skip_redundant_saves", false)
.put("mixin.feature.snapshot_easter_egg", true)
@ -193,6 +190,12 @@ public class ModernFixEarlyConfig {
.putConditionally(() -> !isFabric, "mixin.bugfix.fix_config_crashes", true)
.putConditionally(() -> !isFabric, "mixin.feature.registry_event_progress", true)
.putConditionally(() -> isFabric, "mixin.perf.clear_fabric_mapping_tables", false)
// Beta (promote on next release)
.put("mixin.perf.compact_entity_models", false)
.put("mixin.perf.dynamic_languages", false)
.put("mixin.perf.faster_capabilities.bytecode_analysis", false)
.put("mixin.perf.ingredient_item_deduplication", false)
// END
.build();
private ModernFixEarlyConfig(File file) {
@ -236,7 +239,7 @@ public class ModernFixEarlyConfig {
disableIfModPresent("mixin.bugfix.item_cache_flag", "lithium", "canary", "radium");
// DimThread makes changes to the server chunk manager (understandably), C2ME probably does the same
disableIfModPresent("mixin.bugfix.chunk_deadlock", "c2me", "dimthread");
disableIfModPresent("mixin.perf.reuse_datapacks", "tac");
disableIfModPresent("mixin.perf.release_protochunks", "c2me");
disableIfModPresent("mixin.launch.class_search_cache", "optifine");
disableIfModPresent("mixin.perf.faster_texture_stitching", "optifine");
disableIfModPresent("mixin.bugfix.entity_pose_stack", "optifine");

View File

@ -0,0 +1,42 @@
package org.embeddedt.modernfix.dynamiclanguages;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Maps;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.locale.Language;
import net.minecraft.server.packs.resources.Resource;
import org.embeddedt.modernfix.ModernFix;
import java.io.IOException;
import java.util.Map;
public class DynamicLanguageMap {
public static Map<String, String> forStorage(Map<String, ?> storage) {
LoadingCache<Resource, Map<String, String>> languageFileContents = CacheBuilder.newBuilder()
.softValues()
.build(new CacheLoader<>() {
@Override
public Map<String, String> load(Resource resource) throws Exception {
Map<String, String> data = new Object2ObjectOpenHashMap<>();
try (var stream = resource.open()) {
Language.loadFromJson(stream, data::put);
} catch (IOException e) {
ModernFix.LOGGER.error("Error loading language data from {}", resource.sourcePackId(), e);
}
return data;
}
});
return Maps.asMap(storage.keySet(), k -> {
var value = storage.get(k);
if (value instanceof Resource r) {
return languageFileContents.getUnchecked(r).getOrDefault(k, "");
} else if (value instanceof String s) {
return s;
} else {
return null;
}
});
}
}

View File

@ -1,43 +0,0 @@
package org.embeddedt.modernfix.util;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.TimeUnit;
public class DirectExecutorService extends AbstractExecutorService {
private boolean isShutdown;
@Override
public void shutdown() {
isShutdown = true;
}
@NotNull
@Override
public List<Runnable> shutdownNow() {
isShutdown = true;
return List.of();
}
@Override
public boolean isShutdown() {
return isShutdown;
}
@Override
public boolean isTerminated() {
return isShutdown;
}
@Override
public boolean awaitTermination(long timeout, @NotNull TimeUnit unit) throws InterruptedException {
return true;
}
@Override
public void execute(@NotNull Runnable command) {
command.run();
}
}

View File

@ -0,0 +1,68 @@
package org.embeddedt.modernfix.util;
import net.minecraft.util.thread.ProcessorMailbox;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* Like {@link Executors#newSingleThreadExecutor()}, but handles the case where the background executor schedules
* a task to itself and waits for it the way a direct executor would.
*/
public class SingleThreadedWorkerService extends AbstractExecutorService {
private final AtomicReference<Thread> thread = new AtomicReference<>();
private final ExecutorService executorService;
public SingleThreadedWorkerService() {
this.executorService = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r, "Worker-Main");
t.setPriority(Thread.MIN_PRIORITY);
thread.set(t);
return t;
});
}
@Override
public void shutdown() {
executorService.shutdown();
}
@NotNull
@Override
public List<Runnable> shutdownNow() {
return executorService.shutdownNow();
}
@Override
public boolean isShutdown() {
return executorService.isShutdown();
}
@Override
public boolean isTerminated() {
return executorService.isTerminated();
}
@Override
public boolean awaitTermination(long timeout, @NotNull TimeUnit unit) throws InterruptedException {
return executorService.awaitTermination(timeout, unit);
}
private static boolean isForcedAsyncCommand(Runnable command) {
return command instanceof ProcessorMailbox<?>;
}
@Override
public void execute(@NotNull Runnable command) {
if (!isForcedAsyncCommand(command) && Thread.currentThread() == thread.get()) {
command.run();
} else {
executorService.execute(command);
}
}
}

View File

@ -99,6 +99,7 @@ public class ChunkBiomeLookup implements Function<BlockPos, Holder<Biome>> {
public void dispose() {
// Make sure we do not retain strong references to the biome holders
Arrays.fill(biomes, null);
this.fallbackManager = null;
}
private boolean fetchBiomes(BiomeManager.NoiseBiomeSource source) {