Merge remote-tracking branch 'origin/1.21.1' into 26.1
This commit is contained in:
commit
be491e29ea
|
|
@ -2,7 +2,7 @@ package org.embeddedt.modernfix.common.mixin.feature.cause_lag_by_disabling_thre
|
|||
|
||||
import net.minecraft.TracingExecutor;
|
||||
import net.minecraft.util.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;
|
||||
|
|
@ -11,5 +11,5 @@ import org.spongepowered.asm.mixin.Shadow;
|
|||
@Mixin(Util.class)
|
||||
public class UtilMixin {
|
||||
@Shadow @Final @Mutable
|
||||
private static final TracingExecutor BACKGROUND_EXECUTOR = new TracingExecutor(new DirectExecutorService());
|
||||
private static final TracingExecutor BACKGROUND_EXECUTOR = new TracingExecutor(new SingleThreadedWorkerService());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -67,7 +67,9 @@ public abstract class ChunkHolderMixin extends GenerationChunkHolder implements
|
|||
// register for suspension check when chain completes
|
||||
var map = ((ISuspendedHolderTrackingChunkMap)this.playerProvider);
|
||||
this.saveSync.whenCompleteAsync((r, e) -> {
|
||||
map.mfix$markForSuspensionCheck(this.pos);
|
||||
if (this.getLatestChunk() != null) {
|
||||
map.mfix$markForSuspensionCheck(this.pos);
|
||||
}
|
||||
}, map.mfix$getMainThreadExecutor());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,35 @@
|
|||
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.client.renderer.entity.state.LivingEntityRenderState;
|
||||
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, S extends LivingEntityRenderState, M extends EntityModel<? super S>> {
|
||||
@Shadow
|
||||
public abstract boolean addLayer(RenderLayer<S, 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<S, M> layer, Operation<Boolean> original) {
|
||||
if (!Minecraft.getInstance().isSameThread()) {
|
||||
ModernFix.LOGGER.error("LivingEntityRenderer.addLayer called on wrong thread", new Exception());
|
||||
Minecraft.getInstance().schedule(() -> this.addLayer(layer));
|
||||
return true;
|
||||
}
|
||||
return original.call(layer);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package org.embeddedt.modernfix.util;
|
||||
|
||||
import net.minecraft.util.thread.AbstractConsecutiveExecutor;
|
||||
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 AbstractConsecutiveExecutor<?>;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(@NotNull Runnable command) {
|
||||
if (!isForcedAsyncCommand(command) && Thread.currentThread() == thread.get()) {
|
||||
command.run();
|
||||
} else {
|
||||
executorService.execute(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user