diff --git a/README.md b/README.md index d9266842..e1543d82 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # ModernFix -A Forge 1.16 mod that uses mixins to make the game slightly more performant (and hopefully less buggy too). - -In the words of asiekierka, questionable "performance improvements" that are not in Forge for probably very good reasons. +A performance mod for modern Minecraft that significantly improves launch times, world load times, memory usage, etc. Some fixes are based on prior work in various Forge PRs (check commit history and/or code comments). The config system is directly derived from Sodium and used under the terms of the LGPL-3.0 license. diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/BlockModelShaperMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/BlockModelShaperMixin.java index 6e4f35ba..129157b6 100644 --- a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/BlockModelShaperMixin.java +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/BlockModelShaperMixin.java @@ -6,10 +6,13 @@ import net.minecraft.client.resources.model.ModelManager; import net.minecraft.world.level.block.state.BlockState; import org.embeddedt.modernfix.annotation.ClientOnlyMixin; import org.embeddedt.modernfix.dynamicresources.ModelLocationCache; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Overwrite; -import org.spongepowered.asm.mixin.Shadow; +import org.embeddedt.modernfix.util.DynamicMap; +import org.spongepowered.asm.mixin.*; +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.Map; @Mixin(BlockModelShaper.class) @@ -17,6 +20,15 @@ import org.spongepowered.asm.mixin.Shadow; public class BlockModelShaperMixin { @Shadow @Final private ModelManager modelManager; + @Shadow @Final @Mutable + private Map modelByStateCache; + + @Inject(method = "", at = @At("RETURN")) + private void replaceModelMap(CallbackInfo ci) { + // replace the backing map for mods which will access it + this.modelByStateCache = new DynamicMap<>(state -> modelManager.getModel(ModelLocationCache.get(state))); + } + /** * @author embeddedt * @reason no need to rebuild model cache, and location cache is done elsewhere diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/ItemModelShaperMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/ItemModelShaperMixin.java index 7472760d..66a183f0 100644 --- a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/ItemModelShaperMixin.java +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/ItemModelShaperMixin.java @@ -1,11 +1,13 @@ package org.embeddedt.modernfix.common.mixin.perf.dynamic_resources; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import net.minecraft.client.renderer.ItemModelShaper; import net.minecraft.client.resources.model.BakedModel; import net.minecraft.client.resources.model.ModelManager; import net.minecraft.client.resources.model.ModelResourceLocation; import net.minecraft.world.item.Item; import org.embeddedt.modernfix.dynamicresources.ModelLocationCache; +import org.embeddedt.modernfix.util.DynamicInt2ObjectMap; import org.spongepowered.asm.mixin.*; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -19,6 +21,8 @@ public abstract class ItemModelShaperMixin { @Shadow public abstract ModelManager getModelManager(); + @Shadow @Final @Mutable private Int2ObjectMap shapesCache; + private Map overrideLocationsVanilla; public ItemModelShaperMixin() { @@ -30,6 +34,7 @@ public abstract class ItemModelShaperMixin { @Inject(method = "", at = @At("RETURN")) private void replaceLocationMap(CallbackInfo ci) { overrideLocationsVanilla = new HashMap<>(); + this.shapesCache = new DynamicInt2ObjectMap<>(index -> getModelManager().getModel(ModelLocationCache.get(Item.byId(index)))); } @Unique diff --git a/common/src/main/java/org/embeddedt/modernfix/structure/CachingStructureManager.java b/common/src/main/java/org/embeddedt/modernfix/structure/CachingStructureManager.java index 73345285..b5ed3b88 100644 --- a/common/src/main/java/org/embeddedt/modernfix/structure/CachingStructureManager.java +++ b/common/src/main/java/org/embeddedt/modernfix/structure/CachingStructureManager.java @@ -1,6 +1,7 @@ package org.embeddedt.modernfix.structure; import com.mojang.datafixers.DataFixer; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import net.minecraft.SharedConstants; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtIo; @@ -15,6 +16,7 @@ import org.embeddedt.modernfix.util.FileUtil; import java.io.*; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Set; public class CachingStructureManager { private static ThreadLocal digestThreadLocal = ThreadLocal.withInitial(() -> { @@ -45,6 +47,8 @@ public class CachingStructureManager { return sb.toString(); } + private static final Set laggyStructureMods = new ObjectOpenHashSet<>(); + public static CompoundTag readStructureTag(ResourceLocation location, DataFixer datafixer, InputStream stream) throws IOException { byte[] structureBytes = toBytes(stream); CompoundTag currentTag = NbtIo.readCompressed(new ByteArrayInputStream(structureBytes)); @@ -53,6 +57,11 @@ public class CachingStructureManager { } int currentDataVersion = currentTag.getInt("DataVersion"); if(currentDataVersion < SharedConstants.getCurrentVersion().getWorldVersion()) { + synchronized (laggyStructureMods) { + if(laggyStructureMods.add(location.getNamespace())) { + ModernFix.LOGGER.warn("Mod {} is shipping outdated structure files, which can cause worldgen lag; please report this to them.", location.getNamespace()); + } + } /* Needs upgrade, try looking up from cache */ MessageDigest hasher = digestThreadLocal.get(); hasher.reset(); diff --git a/common/src/main/java/org/embeddedt/modernfix/util/DynamicInt2ObjectMap.java b/common/src/main/java/org/embeddedt/modernfix/util/DynamicInt2ObjectMap.java new file mode 100644 index 00000000..5c44208e --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/util/DynamicInt2ObjectMap.java @@ -0,0 +1,61 @@ +package org.embeddedt.modernfix.util; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.ObjectCollection; +import it.unimi.dsi.fastutil.objects.ObjectSet; + +import java.util.Map; +import java.util.function.Function; + +public class DynamicInt2ObjectMap extends DynamicMap implements Int2ObjectMap { + public DynamicInt2ObjectMap(Function function) { + super(function); + } + + @Override + public IntSet keySet() { + throw new UnsupportedOperationException(); + } + + @Override + public ObjectCollection values() { + throw new UnsupportedOperationException(); + } + + @Override + public ObjectSet> entrySet() { + throw new UnsupportedOperationException(); + } + + @Override + public void defaultReturnValue(V rv) { + throw new UnsupportedOperationException(); + } + + @Override + public V defaultReturnValue() { + throw new UnsupportedOperationException(); + } + + @Override + public ObjectSet> int2ObjectEntrySet() { + throw new UnsupportedOperationException(); + } + + @Override + public V getOrDefault(int key, V defaultValue) { + V value = get(key); + return value == null ? defaultValue : value; + } + + @Override + public V get(int key) { + return function.apply(key); + } + + @Override + public boolean containsKey(int key) { + return true; + } +} diff --git a/common/src/main/java/org/embeddedt/modernfix/util/DynamicMap.java b/common/src/main/java/org/embeddedt/modernfix/util/DynamicMap.java new file mode 100644 index 00000000..e8803770 --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/util/DynamicMap.java @@ -0,0 +1,81 @@ +package org.embeddedt.modernfix.util; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +public class DynamicMap implements Map { + protected final Function function; + + public DynamicMap(Function function) { + this.function = function; + } + + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean containsKey(Object o) { + return true; + } + + @Override + public boolean containsValue(Object o) { + return true; + } + + @Override + public V get(Object o) { + return function.apply((K)o); + } + + @Nullable + @Override + public V put(K k, V v) { + throw new UnsupportedOperationException(); + } + + @Override + public V remove(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(@NotNull Map map) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @NotNull + @Override + public Set keySet() { + throw new UnsupportedOperationException(); + } + + @NotNull + @Override + public Collection values() { + throw new UnsupportedOperationException(); + } + + @NotNull + @Override + public Set> entrySet() { + throw new UnsupportedOperationException(); + } +} diff --git a/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/dynamic_resources/ModelBakeryMixin.java b/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/dynamic_resources/ModelBakeryMixin.java index 0026b064..e0955ee6 100644 --- a/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/dynamic_resources/ModelBakeryMixin.java +++ b/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/dynamic_resources/ModelBakeryMixin.java @@ -43,6 +43,7 @@ import java.util.*; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.function.Function; +import java.util.function.Predicate; /* high priority so that our injectors are added before other mods' */ @Mixin(value = ModelBakery.class, priority = 600) @@ -165,7 +166,13 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery { }; // discard unwrapped models int oldSize = this.unbakedCache.size(); - this.unbakedCache.entrySet().removeIf(entry -> entry.getValue() instanceof BlockModel || entry.getValue() instanceof MultiVariant || entry.getValue() instanceof MultiPart); + Predicate> isVanillaModel = entry -> entry.getValue() instanceof BlockModel || entry.getValue() instanceof MultiVariant || entry.getValue() instanceof MultiPart; + // bake indigo models + this.topLevelModels.entrySet().forEach((entry) -> { + if(!isVanillaModel.test(entry)) + this.bake(entry.getKey(), BlockModelRotation.X0_Y0); + }); + this.unbakedCache.entrySet().removeIf(isVanillaModel); ModernFix.LOGGER.info("{} models evicted, {} custom models loaded permanently", oldSize - this.unbakedCache.size(), this.unbakedCache.size()); this.unbakedCache = new LayeredForwardingMap<>(new Map[] { this.unbakedCache, mutableBackingMap }); this.bakedTopLevelModels = new DynamicBakedModelProvider((ModelBakery)(Object)this, bakedCache);