Add config

This commit is contained in:
embeddedt 2023-01-04 20:26:29 -05:00
parent 700b2ec265
commit 588dc1a86e
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
19 changed files with 405 additions and 49 deletions

View File

@ -3,3 +3,6 @@
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.
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.

View File

@ -0,0 +1,95 @@
package org.embeddedt.modernfix.core;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.embeddedt.modernfix.core.config.ModernFixConfig;
import org.embeddedt.modernfix.core.config.Option;
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import java.io.File;
import java.util.List;
import java.util.Set;
public class ModernFixMixinPlugin implements IMixinConfigPlugin {
private static final String MIXIN_PACKAGE_ROOT = "org.embeddedt.modernfix.mixin.";
private final Logger logger = LogManager.getLogger("ModernFix");
public static ModernFixConfig config = null;
@Override
public void onLoad(String mixinPackage) {
try {
config = ModernFixConfig.load(new File("./config/modernfix-mixins.properties"));
} catch (Exception e) {
throw new RuntimeException("Could not load configuration file for ModernFix", e);
}
this.logger.info("Loaded configuration file for ModernFix: {} options available, {} override(s) found",
config.getOptionCount(), config.getOptionOverrideCount());
}
@Override
public String getRefMapperConfig() {
return null;
}
@Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
if (!mixinClassName.startsWith(MIXIN_PACKAGE_ROOT)) {
this.logger.error("Expected mixin '{}' to start with package root '{}', treating as foreign and " +
"disabling!", mixinClassName, MIXIN_PACKAGE_ROOT);
return false;
}
String mixin = mixinClassName.substring(MIXIN_PACKAGE_ROOT.length());
Option option = config.getEffectiveOptionForMixin(mixin);
if (option == null) {
this.logger.error("No rules matched mixin '{}', treating as foreign and disabling!", mixin);
return false;
}
if (option.isOverridden()) {
String source = "[unknown]";
if (option.isUserDefined()) {
source = "user configuration";
} else if (option.isModDefined()) {
source = "mods [" + String.join(", ", option.getDefiningMods()) + "]";
}
if (option.isEnabled()) {
this.logger.warn("Force-enabling mixin '{}' as rule '{}' (added by {}) enables it", mixin,
option.getName(), source);
} else {
this.logger.warn("Force-disabling mixin '{}' as rule '{}' (added by {}) disables it and children", mixin,
option.getName(), source);
}
}
return option.isEnabled();
}
@Override
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {
}
@Override
public List<String> getMixins() {
return null;
}
@Override
public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
}
@Override
public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
}
}

View File

@ -0,0 +1,177 @@
package org.embeddedt.modernfix.core.config;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.minecraftforge.fml.loading.FMLLoader;
import java.io.*;
import java.util.*;
import java.util.stream.Collectors;
public class ModernFixConfig {
private static final Logger LOGGER = LogManager.getLogger("ModernFixConfig");
private final Map<String, Option> options = new HashMap<>();
private ModernFixConfig() {
// Defines the default rules which can be configured by the user or other mods.
// You must manually add a rule for any new mixins not covered by an existing package rule.
this.addMixinRule("core", true); // TODO: Don't actually allow the user to disable this
this.addMixinRule("feature.measure_time", true);
this.addMixinRule("perf.remove_biome_temperature_cache", true);
this.addMixinRule("perf.resourcepacks", true);
this.addMixinRule("perf.reduce_blockstate_cache_rebuilds", true);
this.addMixinRule("perf.boost_worker_count", true);
this.addMixinRule("perf.skip_first_datapack_reload", true);
this.addMixinRule("perf.parallelize_model_loading", true);
this.addMixinRule("perf.trim_model_caches", true);
this.addMixinRule("bugfix.concurrency", true);
}
/**
* Defines a Mixin rule which can be configured by users and other mods.
* @throws IllegalStateException If a rule with that name already exists
* @param mixin The name of the mixin package which will be controlled by this rule
* @param enabled True if the rule will be enabled by default, otherwise false
*/
private void addMixinRule(String mixin, boolean enabled) {
String name = getMixinRuleName(mixin);
if (this.options.putIfAbsent(name, new Option(name, enabled, false)) != null) {
throw new IllegalStateException("Mixin rule already defined: " + mixin);
}
}
private void readProperties(Properties props) {
for (Map.Entry<Object, Object> entry : props.entrySet()) {
String key = (String) entry.getKey();
String value = (String) entry.getValue();
Option option = this.options.get(key);
if (option == null) {
LOGGER.warn("No configuration key exists with name '{}', ignoring", key);
continue;
}
boolean enabled;
if (value.equalsIgnoreCase("true")) {
enabled = true;
} else if (value.equalsIgnoreCase("false")) {
enabled = false;
} else {
LOGGER.warn("Invalid value '{}' encountered for configuration key '{}', ignoring", value, key);
continue;
}
option.setEnabled(enabled, true);
}
}
/**
* Returns the effective option for the specified class name. This traverses the package path of the given mixin
* and checks each root for configuration rules. If a configuration rule disables a package, all mixins located in
* that package and its children will be disabled. The effective option is that of the highest-priority rule, either
* a enable rule at the end of the chain or a disable rule at the earliest point in the chain.
*
* @return Null if no options matched the given mixin name, otherwise the effective option for this Mixin
*/
public Option getEffectiveOptionForMixin(String mixinClassName) {
int lastSplit = 0;
int nextSplit;
Option rule = null;
while ((nextSplit = mixinClassName.indexOf('.', lastSplit)) != -1) {
String key = getMixinRuleName(mixinClassName.substring(0, nextSplit));
Option candidate = this.options.get(key);
if (candidate != null) {
rule = candidate;
if (!rule.isEnabled()) {
return rule;
}
}
lastSplit = nextSplit + 1;
}
return rule;
}
/**
* Loads the configuration file from the specified location. If it does not exist, a new configuration file will be
* created. The file on disk will then be updated to include any new options.
*/
public static ModernFixConfig load(File file) {
ModernFixConfig config = new ModernFixConfig();
Properties props = new Properties();
if(file.exists()) {
try (FileInputStream fin = new FileInputStream(file)){
props.load(fin);
} catch (IOException e) {
throw new RuntimeException("Could not load config file", e);
}
config.readProperties(props);
}
try {
config.writeConfig(file, props);
} catch (IOException e) {
LOGGER.warn("Could not write configuration file", e);
}
return config;
}
private void writeConfig(File file, Properties props) throws IOException {
File dir = file.getParentFile();
if (!dir.exists()) {
if (!dir.mkdirs()) {
throw new IOException("Could not create parent directories");
}
} else if (!dir.isDirectory()) {
throw new IOException("The parent file is not a directory");
}
try (Writer writer = new FileWriter(file)) {
writer.write("# This is the configuration file for ModernFix.\n");
writer.write("#\n");
writer.write("# The following options are enabled by default and should only be disabled if there is a.\n");
writer.write("# compatibility issue. Add a line mixin.example_name=false without the # sign to disable a rule.\n");
List<String> lines = this.options.keySet().stream()
.filter(key -> !key.equals("mixin.core"))
.sorted()
.map(key -> "# " + key + "\n")
.collect(Collectors.toList());
for(String line : lines) {
writer.write(line);
}
for (Map.Entry<Object, Object> entry : props.entrySet()) {
String key = (String) entry.getKey();
String value = (String) entry.getValue();
writer.write(key + "=" + value + "\n");
}
}
}
private static String getMixinRuleName(String name) {
return "mixin." + name;
}
public int getOptionCount() {
return this.options.size();
}
public int getOptionOverrideCount() {
return (int) this.options.values()
.stream()
.filter(Option::isOverridden)
.count();
}
}

View File

@ -0,0 +1,63 @@
package org.embeddedt.modernfix.core.config;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
public class Option {
private final String name;
private Set<String> modDefined = null;
private boolean enabled;
private boolean userDefined;
public Option(String name, boolean enabled, boolean userDefined) {
this.name = name;
this.enabled = enabled;
this.userDefined = userDefined;
}
public void setEnabled(boolean enabled, boolean userDefined) {
this.enabled = enabled;
this.userDefined = userDefined;
}
public void addModOverride(boolean enabled, String modId) {
this.enabled = enabled;
if (this.modDefined == null) {
this.modDefined = new LinkedHashSet<>();
}
this.modDefined.add(modId);
}
public boolean isEnabled() {
return this.enabled;
}
public boolean isOverridden() {
return this.isUserDefined() || this.isModDefined();
}
public boolean isUserDefined() {
return this.userDefined;
}
public boolean isModDefined() {
return this.modDefined != null;
}
public String getName() {
return this.name;
}
public void clearModsDefiningValue() {
this.modDefined = null;
}
public Collection<String> getDefiningMods() {
return this.modDefined != null ? Collections.unmodifiableCollection(this.modDefined) : Collections.emptyList();
}
}

View File

@ -1,4 +1,4 @@
package org.embeddedt.modernfix.mixin;
package org.embeddedt.modernfix.mixin.bugfix.concurrency;
import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet;
import net.minecraft.client.renderer.RenderType;

View File

@ -0,0 +1,39 @@
package org.embeddedt.modernfix.mixin.feature.measure_time;
import com.mojang.datafixers.util.Function4;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.IResourceManager;
import net.minecraft.util.datafix.codec.DatapackCodec;
import net.minecraft.util.registry.DynamicRegistries;
import net.minecraft.world.storage.IServerConfiguration;
import net.minecraft.world.storage.SaveFormat;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.ModernFixClient;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.function.Function;
@Mixin(Minecraft.class)
public class MinecraftMixin {
private long datapackReloadStartTime;
@Inject(method = "makeServerStem", at = @At(value = "HEAD"))
private void recordReloadStart(DynamicRegistries.Impl p_238189_1_, Function<SaveFormat.LevelSave, DatapackCodec> p_238189_2_, Function4<SaveFormat.LevelSave, DynamicRegistries.Impl, IResourceManager, DatapackCodec, IServerConfiguration> p_238189_3_, boolean p_238189_4_, SaveFormat.LevelSave p_238189_5_, CallbackInfoReturnable<Minecraft.PackManager> cir) {
datapackReloadStartTime = System.nanoTime();
}
@Inject(method = "makeServerStem", at = @At(value = "RETURN"))
private void recordReloadEnd(DynamicRegistries.Impl p_238189_1_, Function<SaveFormat.LevelSave, DatapackCodec> p_238189_2_, Function4<SaveFormat.LevelSave, DynamicRegistries.Impl, IResourceManager, DatapackCodec, IServerConfiguration> p_238189_3_, boolean p_238189_4_, SaveFormat.LevelSave p_238189_5_, CallbackInfoReturnable<Minecraft.PackManager> cir) {
float timeSpentReloading = ((float)(System.nanoTime() - datapackReloadStartTime) / 1000000000f);
ModernFix.LOGGER.warn("Datapack reload took " + timeSpentReloading + " seconds.");
}
@Inject(method = "loadWorld(Ljava/lang/String;Lnet/minecraft/util/registry/DynamicRegistries$Impl;Ljava/util/function/Function;Lcom/mojang/datafixers/util/Function4;ZLnet/minecraft/client/Minecraft$WorldSelectionType;Z)V", at = @At("HEAD"), remap = false)
private void recordWorldLoadStart(String worldName, DynamicRegistries.Impl dynamicRegistries, Function<SaveFormat.LevelSave, DatapackCodec> levelSaveToDatapackFunction, Function4<SaveFormat.LevelSave, DynamicRegistries.Impl, IResourceManager, DatapackCodec, IServerConfiguration> quadFunction, boolean vanillaOnly, Minecraft.WorldSelectionType selectionType, boolean creating, CallbackInfo ci) {
ModernFixClient.worldLoadStartTime = System.nanoTime();
}
}

View File

@ -1,4 +1,4 @@
package org.embeddedt.modernfix.mixin;
package org.embeddedt.modernfix.mixin.perf.boost_worker_count;
import net.minecraft.util.Util;
import org.embeddedt.modernfix.ModernFix;

View File

@ -1,4 +1,4 @@
package org.embeddedt.modernfix.mixin;
package org.embeddedt.modernfix.mixin.perf.parallelize_model_loading;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Sets;
@ -43,11 +43,6 @@ public abstract class ModelBakeryMixin {
@Shadow public abstract IUnbakedModel getModel(ResourceLocation modelLocation);
@Shadow @Final public static BlockModel GENERATION_MARKER;
@Shadow @Final private static ItemModelGenerator ITEM_MODEL_GENERATOR;
@Shadow @Nullable private SpriteMap atlasSet;
@Shadow @Final private Map<Triple<ResourceLocation, TransformationMatrix, Boolean>, IBakedModel> bakedCache;
@Shadow @Final private Map<ResourceLocation, IBakedModel> bakedTopLevelModels;
@Shadow @Final private Map<ResourceLocation, IUnbakedModel> topLevelModels;
private Map<ResourceLocation, BlockModel> deserializedModelCache = null;
private boolean useModelCache = false;

View File

@ -1,4 +1,4 @@
package org.embeddedt.modernfix.mixin;
package org.embeddedt.modernfix.mixin.perf.reduce_blockstate_cache_rebuilds;
import net.minecraft.block.AbstractBlock;
import org.embeddedt.modernfix.duck.IBlockState;

View File

@ -1,4 +1,4 @@
package org.embeddedt.modernfix.mixin;
package org.embeddedt.modernfix.mixin.perf.reduce_blockstate_cache_rebuilds;
import net.minecraft.block.BlockState;
import org.embeddedt.modernfix.ModernFix;

View File

@ -1,4 +1,4 @@
package org.embeddedt.modernfix.mixin;
package org.embeddedt.modernfix.mixin.perf.reduce_blockstate_cache_rebuilds;
import com.google.common.collect.Multimap;
import net.minecraft.util.ResourceLocation;

View File

@ -1,4 +1,4 @@
package org.embeddedt.modernfix.mixin;
package org.embeddedt.modernfix.mixin.perf.remove_biome_temperature_cache;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.biome.Biome;

View File

@ -1,4 +1,4 @@
package org.embeddedt.modernfix.mixin;
package org.embeddedt.modernfix.mixin.perf.resourcepacks;
import com.google.common.base.Joiner;
import net.minecraft.resources.ResourcePackType;

View File

@ -1,4 +1,4 @@
package org.embeddedt.modernfix.mixin;
package org.embeddedt.modernfix.mixin.perf.resourcepacks;
import com.google.common.base.Joiner;
import com.google.common.cache.CacheBuilder;

View File

@ -1,4 +1,4 @@
package org.embeddedt.modernfix.mixin;
package org.embeddedt.modernfix.mixin.perf.skip_first_datapack_reload;
import net.minecraft.client.Minecraft;
import net.minecraft.nbt.CompoundNBT;

View File

@ -1,4 +1,4 @@
package org.embeddedt.modernfix.mixin;
package org.embeddedt.modernfix.mixin.perf.skip_first_datapack_reload;
import com.mojang.datafixers.util.Function4;
import net.minecraft.client.Minecraft;
@ -25,24 +25,6 @@ import java.util.function.Function;
public abstract class MinecraftMixin {
@Shadow public abstract Minecraft.PackManager makeServerStem(DynamicRegistries.Impl dynamicRegistries, Function<SaveFormat.LevelSave, DatapackCodec> worldStorageToDatapackFunction, Function4<SaveFormat.LevelSave, DynamicRegistries.Impl, IResourceManager, DatapackCodec, IServerConfiguration> quadFunction, boolean vanillaOnly, SaveFormat.LevelSave worldStorage) throws InterruptedException, ExecutionException;
private long datapackReloadStartTime;
private int registryHash;
@Inject(method = "makeServerStem", at = @At(value = "HEAD"))
private void recordReloadStart(DynamicRegistries.Impl p_238189_1_, Function<SaveFormat.LevelSave, DatapackCodec> p_238189_2_, Function4<SaveFormat.LevelSave, DynamicRegistries.Impl, IResourceManager, DatapackCodec, IServerConfiguration> p_238189_3_, boolean p_238189_4_, SaveFormat.LevelSave p_238189_5_, CallbackInfoReturnable<Minecraft.PackManager> cir) {
datapackReloadStartTime = System.nanoTime();
}
@Inject(method = "makeServerStem", at = @At(value = "RETURN"))
private void recordReloadEnd(DynamicRegistries.Impl p_238189_1_, Function<SaveFormat.LevelSave, DatapackCodec> p_238189_2_, Function4<SaveFormat.LevelSave, DynamicRegistries.Impl, IResourceManager, DatapackCodec, IServerConfiguration> p_238189_3_, boolean p_238189_4_, SaveFormat.LevelSave p_238189_5_, CallbackInfoReturnable<Minecraft.PackManager> cir) {
float timeSpentReloading = ((float)(System.nanoTime() - datapackReloadStartTime) / 1000000000f);
ModernFix.LOGGER.warn("Datapack reload took " + timeSpentReloading + " seconds.");
}
@Inject(method = "loadWorld(Ljava/lang/String;Lnet/minecraft/util/registry/DynamicRegistries$Impl;Ljava/util/function/Function;Lcom/mojang/datafixers/util/Function4;ZLnet/minecraft/client/Minecraft$WorldSelectionType;Z)V", at = @At("HEAD"), remap = false)
private void recordWorldLoadStart(String worldName, DynamicRegistries.Impl dynamicRegistries, Function<SaveFormat.LevelSave, DatapackCodec> levelSaveToDatapackFunction, Function4<SaveFormat.LevelSave, DynamicRegistries.Impl, IResourceManager, DatapackCodec, IServerConfiguration> quadFunction, boolean vanillaOnly, Minecraft.WorldSelectionType selectionType, boolean creating, CallbackInfo ci) {
ModernFixClient.worldLoadStartTime = System.nanoTime();
}
@Redirect(method = "loadLevel(Ljava/lang/String;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/registry/DynamicRegistries;builtin()Lnet/minecraft/util/registry/DynamicRegistries$Impl;"))
private DynamicRegistries.Impl useNullRegistry() {
return null;

View File

@ -1,4 +1,4 @@
package org.embeddedt.modernfix.mixin;
package org.embeddedt.modernfix.mixin.perf.skip_first_datapack_reload;
import com.mojang.datafixers.DataFixer;
import net.minecraft.world.storage.SaveFormat;

View File

@ -1,4 +1,4 @@
package org.embeddedt.modernfix.mixin;
package org.embeddedt.modernfix.mixin.perf.trim_model_caches;
import net.minecraft.client.renderer.model.ModelBakery;
import net.minecraft.client.renderer.model.ModelManager;

View File

@ -2,24 +2,26 @@
"required": true,
"minVersion": "0.8",
"package": "org.embeddedt.modernfix.mixin",
"plugin": "org.embeddedt.modernfix.core.ModernFixMixinPlugin",
"compatibilityLevel": "JAVA_8",
"refmap": "modernfix.refmap.json",
"mixins": [
"BiomeMixin",
"ModFileResourcePackMixin",
"VanillaPackMixin",
"LevelSaveMixin",
"SaveFormatAccessor",
"AbstractBlockStateMixin",
"GameDataMixin",
"BlockCallbacksMixin",
"UtilMixin"
"feature.measure_time.MinecraftMixin",
"perf.remove_biome_temperature_cache.BiomeMixin",
"perf.resourcepacks.ModFileResourcePackMixin",
"perf.resourcepacks.VanillaPackMixin",
"perf.skip_first_datapack_reload.LevelSaveMixin",
"perf.skip_first_datapack_reload.SaveFormatAccessor",
"perf.reduce_blockstate_cache_rebuilds.AbstractBlockStateMixin",
"perf.reduce_blockstate_cache_rebuilds.GameDataMixin",
"perf.reduce_blockstate_cache_rebuilds.BlockCallbacksMixin",
"perf.boost_worker_count.UtilMixin"
],
"client": [
"MinecraftMixin",
"RenderTypeMixin",
"ModelBakeryMixin",
"ModelManagerMixin"
"perf.skip_first_datapack_reload.MinecraftMixin",
"bugfix.concurrency.RenderTypeMixin",
"perf.parallelize_model_loading.ModelBakeryMixin",
"perf.trim_model_caches.ModelManagerMixin"
],
"injectors": {
"defaultRequire": 1