initial 1.18 port

This commit is contained in:
embeddedt 2023-02-18 12:42:32 -05:00
parent 360eea8b1c
commit 7e2aaabd19
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
70 changed files with 74 additions and 2886 deletions

View File

@ -4,7 +4,7 @@ plugins {
id 'com.matthewprenger.cursegradle' version '1.4.0'
}
sourceCompatibility = targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = targetCompatibility = JavaVersion.VERSION_17
group = 'org.embeddedt'
version = '1.6.0-beta3'
@ -66,10 +66,10 @@ dependencies {
forge "net.minecraftforge:forge:${project.forge_version}"
modRuntimeOnly "curse.maven:lazydfu-460819:${lazydfu_version}"
modCompileOnly("mezz.jei:jei-${minecraft_version}:${jei_version}")
modRuntimeOnly("mezz.jei:jei-${minecraft_version}:${jei_version}")
modCompileOnly("curse.maven:refinedstorage-243076:${refined_storage_version}")
// compile against the JEI API but do not include it at runtime
modCompileOnly("mezz.jei:jei-${minecraft_version}-forge:${jei_version}")
}
tasks.withType(JavaCompile) {
@ -79,10 +79,7 @@ tasks.withType(JavaCompile) {
// If Javadoc is generated, this must be specified in that task too.
options.encoding = "UTF-8"
// The Minecraft launcher currently installs Java 8 for users, so your mod probably wants to target Java 8 too
// JDK 9 introduced a new way of specifying this that will make sure no newer classes or methods are used.
// We'll use that if it's available, but otherwise we'll use the older option.
def targetVersion = 8
def targetVersion = 17
if (JavaVersion.current().isJava9Compatible()) {
options.release = targetVersion
}

View File

@ -8,10 +8,9 @@ org.gradle.jvmargs=-Xmx1G
loom.platform=forge
mod_id=modernfix
minecraft_version=1.16.5
forge_version=1.16.5-36.2.39
lazydfu_version=3249059
mekanism_version=1.16.5-10.1.2.457
parchment_version=2022.03.06
jei_version=7.7.1.153
refined_storage_version=3807951
minecraft_version=1.18.2
forge_version=1.18.2-40.2.1
lazydfu_version=3544496
parchment_version=2022.11.06
refined_storage_version=4392829
jei_version=10.2.1.283

View File

@ -1,213 +0,0 @@
package cpw.mods.modlauncher;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import cpw.mods.modlauncher.api.ITransformer;
import cpw.mods.modlauncher.api.ITransformerActivity;
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
import net.minecraftforge.coremod.transformer.CoreModBaseTransformer;
import net.minecraftforge.fml.loading.FMLPaths;
import net.minecraftforge.fml.loading.LoadingModList;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.classloading.api.IHashableTransformer;
import org.embeddedt.modernfix.classloading.hashers.CoreModTransformerHasher;
import org.embeddedt.modernfix.classloading.hashers.MixinTransformerHasher;
import org.embeddedt.modernfix.util.FileUtil;
import org.objectweb.asm.Type;
import org.spongepowered.asm.launch.MixinLaunchPluginLegacy;
import javax.lang.model.SourceVersion;
public class ModernFixCachingClassTransformer extends ClassTransformer {
public static final Logger LOGGER = LogManager.getLogger("ModernFixCachingTransformer");
public static File CLASS_CACHE_FOLDER = null;
private final LaunchPluginHandler pluginHandler;
private final Map<String, ILaunchPluginService> plugins;
private final TransformStore transformStore;
private final TransformerAuditTrail auditTrail;
private final TransformingClassLoader transformingClassLoader;
private final HashMap<String, List<ITransformer<?>>> transformersByClass;
private ConcurrentHashMap<String, Pair<List<byte[]>, byte[]>> transformationCache;
private ForkJoinPool classSaverPool = ForkJoinPool.commonPool();
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
public static ThreadLocal<MessageDigest> systemHasher = ThreadLocal.withInitial(() -> {
try {
return MessageDigest.getInstance("SHA-256");
} catch(NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
});
public ModernFixCachingClassTransformer(TransformStore transformStore, LaunchPluginHandler pluginHandler, TransformingClassLoader transformingClassLoader, TransformerAuditTrail trail) {
super(transformStore, pluginHandler, transformingClassLoader, trail);
CLASS_CACHE_FOLDER = FileUtil.childFile(FMLPaths.GAMEDIR.get().resolve("modernfix").resolve("classCacheV1").toFile());
this.transformStore = transformStore;
this.pluginHandler = pluginHandler;
this.transformingClassLoader = transformingClassLoader;
this.auditTrail = trail;
/* Build a lookup table of all transformers for a given class */
this.transformersByClass = new HashMap<>();
try {
Field pluginsField = LaunchPluginHandler.class.getDeclaredField("plugins");
pluginsField.setAccessible(true);
this.plugins = (Map<String, ILaunchPluginService>)pluginsField.get(this.pluginHandler);
Field transformersByTypeField = TransformStore.class.getDeclaredField("transformers");
transformersByTypeField.setAccessible(true);
Field transformersMapField = TransformList.class.getDeclaredField("transformers");
transformersMapField.setAccessible(true);
EnumMap<TransformTargetLabel.LabelType, TransformList<?>> transformersByType = (EnumMap<TransformTargetLabel.LabelType, TransformList<?>>)transformersByTypeField.get(this.transformStore);
for(TransformList<?> transformList : transformersByType.values()) {
Map<TransformTargetLabel, List<ITransformer<?>>> transformers = (Map<TransformTargetLabel, List<ITransformer<?>>>)transformersMapField.get(transformList);
for(Map.Entry<TransformTargetLabel, List<ITransformer<?>>> entry : transformers.entrySet()) {
String className = entry.getKey().getClassName().getClassName();
List<ITransformer<?>> transformerList = this.transformersByClass.computeIfAbsent(className, k -> new ArrayList<>());
transformerList.addAll(entry.getValue());
}
}
for(List<ITransformer<?>> transformerList : this.transformersByClass.values()) {
transformerList.sort((t1, t2) -> Comparator.<String>naturalOrder().compare(StringUtils.join(t1.labels(), " "), StringUtils.join(t2.labels(), " ")));
}
} catch(ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
private ArrayList<byte[]> computeHash(String className, byte[] inputClass, String reason) {
final String internalName = className.replace('.', '/');
final Type classDesc = Type.getObjectType(internalName);
ArrayList<ILaunchPluginService> pluginList = new ArrayList<>();
for(ILaunchPluginService plugin : plugins.values()) {
if(!plugin.handlesClass(classDesc, inputClass.length == 0, reason).isEmpty()) {
pluginList.add(plugin);
}
}
final boolean needsTransforming = transformStore.needsTransforming(internalName);
if (!needsTransforming && pluginList.isEmpty()) {
return null;
}
/* Now compute the hash list for the required transformers */
ArrayList<byte[]> hashList = new ArrayList<>();
pluginList.sort((service1, service2) -> Comparator.<String>naturalOrder().compare(service1.name(), service2.name()));
for(ILaunchPluginService service : pluginList) {
byte[] hash = obtainHash(service, className);
if(hash == null) {
return null;
}
hashList.add(hash);
}
if(needsTransforming) {
List<ITransformer<?>> transformers = this.transformersByClass.get(internalName);
if(transformers != null) {
for(ITransformer<?> transformer : transformers) {
byte[] hash = obtainHash(transformer, className);
if(hash == null) {
return null;
}
hashList.add(hash);
}
}
}
/* Hash the class itself last, so that we bail out early if plugins can't hash */
MessageDigest hasher = systemHasher.get();
hasher.reset();
hashList.add(hasher.digest(inputClass));
return hashList;
}
/**
* Check the hashed list of transformers and use a cached version of the class if possible. This code needs
* to be very fast as the entire point is to spend very little time doing transformation work that was done before.
* @param inputClass The bytecode to be transformed
* @param className Name of the class
* @param reason Reason for the class being loaded
* @return The transformed version of the class
*/
@Override
public byte[] transform(byte[] inputClass, String className, String reason) {
/* We only want to cache actual transformations */
if(ITransformerActivity.CLASSLOADING_REASON.equals(reason) || "mixin".equals(reason)) {
final byte[] classToHash = inputClass;
ArrayList<byte[]> hashList = computeHash(className, classToHash, reason);
if(hashList == null)
return super.transform(inputClass, className, reason);
/* Check if the cache contains a transformed class matching these hashes */
/* TODO maybe sanitize the class name? */
File cacheLocation = new File(CLASS_CACHE_FOLDER, className.replace('.', '/') + "." + reason);
boolean hashesMatch = true;
try(ObjectInputStream stream = new ObjectInputStream(new FileInputStream(cacheLocation))) {
ArrayList<byte[]> savedHash = (ArrayList<byte[]>)stream.readObject();
byte[] savedInputClass = (byte[])stream.readObject();
if(hashList != null) {
for(int i = 0; i < savedHash.size(); i++) {
if(!Arrays.equals(savedHash.get(i), hashList.get(i))) {
hashesMatch = false;
break;
}
}
} else
hashesMatch = false;
if(hashesMatch)
inputClass = savedInputClass;
} catch(IOException | ClassNotFoundException | ClassCastException e) {
if(!(e instanceof FileNotFoundException))
e.printStackTrace();
hashesMatch = false;
}
if(!hashesMatch) {
inputClass = super.transform(inputClass, className, reason);
if(hashList != null) {
final byte[] classToSave = inputClass;
final ArrayList<byte[]> hashListToSave = hashList;
classSaverPool.submit(() -> {
cacheLocation.getParentFile().mkdirs();
try(ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream(cacheLocation))) {
stream.writeObject(hashListToSave);
stream.writeObject(classToSave);
} catch(IOException e) {
e.printStackTrace();
}
});
}
}
return inputClass;
}
return super.transform(inputClass, className, reason);
}
private final byte[] FORGE_HASH = LoadingModList.get().getModFileById("forge").getMods().get(0).getVersion().toString().getBytes(StandardCharsets.UTF_8);
private byte[] obtainHash(Object o, String className) {
if(o instanceof CoreModBaseTransformer) {
return CoreModTransformerHasher.obtainHash((CoreModBaseTransformer<?>)o);
} else if(o instanceof MixinLaunchPluginLegacy) {
return MixinTransformerHasher.obtainHash((MixinLaunchPluginLegacy)o, className);
} else if(o instanceof IHashableTransformer) {
return ((IHashableTransformer)o).getHashForClass(className);
} else if(o.getClass().getName().startsWith("net.minecraftforge.")) {
return FORGE_HASH;
} else {
LOGGER.warn("No hash implementation found for: " + o.getClass().getName());
return null;
}
}
}

View File

@ -2,21 +2,19 @@ package org.embeddedt.modernfix;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.server.ServerStartedEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.fml.ExtensionPoint;
import net.minecraftforge.fml.IExtensionPoint;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.event.server.FMLServerStartedEvent;
import net.minecraftforge.fml.event.server.FMLServerStartingEvent;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.network.FMLNetworkConstants;
import net.minecraftforge.network.NetworkConstants;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.embeddedt.modernfix.core.config.ModernFixConfig;
import org.embeddedt.modernfix.structure.AsyncLocator;
import java.lang.management.ManagementFactory;
@ -40,12 +38,12 @@ public class ModernFix {
// Register ourselves for server and other game events we are interested in
MinecraftForge.EVENT_BUS.register(this);
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> MinecraftForge.EVENT_BUS.register(new ModernFixClient()));
ModLoadingContext.get().registerExtensionPoint(ExtensionPoint.DISPLAYTEST, () -> Pair.of(() -> FMLNetworkConstants.IGNORESERVERONLY, (a, b) -> true));
ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(() -> NetworkConstants.IGNORESERVERONLY, (a, b) -> true));
ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, ModernFixConfig.COMMON_CONFIG);
}
@SubscribeEvent
public void onServerStarted(FMLServerStartedEvent event) {
public void onServerStarted(ServerStartedEvent event) {
if(FMLLoader.getDist() == Dist.DEDICATED_SERVER) {
float gameStartTime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000f;
ModernFix.LOGGER.warn("Dedicated server took " + gameStartTime + " seconds to load");

View File

@ -3,7 +3,7 @@ package org.embeddedt.modernfix;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.ConnectScreen;
import net.minecraft.client.gui.screens.TitleScreen;
import net.minecraftforge.client.event.GuiOpenEvent;
import net.minecraftforge.client.event.ScreenOpenEvent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.SubscribeEvent;
@ -23,10 +23,10 @@ public class ModernFixClient {
}
@SubscribeEvent(priority = EventPriority.LOWEST)
public void onMultiplayerConnect(GuiOpenEvent event) {
if(event.getGui() instanceof ConnectScreen && !event.isCanceled()) {
public void onMultiplayerConnect(ScreenOpenEvent event) {
if(event.getScreen() instanceof ConnectScreen && !event.isCanceled()) {
worldLoadStartTime = System.nanoTime();
} else if (event.getGui() instanceof TitleScreen && gameStartTimeSeconds < 0) {
} else if (event.getScreen() instanceof TitleScreen && gameStartTimeSeconds < 0) {
gameStartTimeSeconds = ManagementFactory.getRuntimeMXBean().getUptime() / 1000f;
ModernFix.LOGGER.warn("Game took " + gameStartTimeSeconds + " seconds to start");
}

View File

@ -1,114 +0,0 @@
package org.embeddedt.modernfix.classloading;
import com.google.common.collect.Lists;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.LoadingModList;
import net.minecraftforge.fml.loading.moddiscovery.*;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.IModLocator;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Stream;
public class ModernFixResourceFinder {
private static HashMap<String, List<URL>> urlsForClass = null;
private static final Class<? extends IModLocator> MINECRAFT_LOCATOR;
private static Field explodedDirModsField = null;
private static final Logger LOGGER = LogManager.getLogger("ModernFixResourceFinder");
static {
try {
MINECRAFT_LOCATOR = (Class<? extends IModLocator>)Class.forName("net.minecraftforge.fml.loading.moddiscovery.ModDiscoverer$MinecraftLocator");
} catch(ClassNotFoundException e) {
/* that shouldn't happen */
throw new RuntimeException(e);
}
}
public static synchronized void init() throws ReflectiveOperationException {
urlsForClass = new HashMap<>();
LOGGER.info("Start building list of class locations...");
for(ModFileInfo fileInfo : LoadingModList.get().getModFiles()) {
ModFile file = fileInfo.getFile();
IModLocator locator = file.getLocator();
Iterable<Path> rootPath = getRootPathForLocator(locator, file);
for(Path root : rootPath) {
try(Stream<Path> stream = Files.walk(root)) {
stream
.map(root::relativize)
.forEach(path -> {
String strPath = path.toString();
URL url = (URL)LamdbaExceptionUtils.uncheck(() -> {
return new URL("modjar://" + fileInfo.getMods().get(0).getModId() + "/" + strPath);
});
List<URL> urlList = urlsForClass.get(strPath);
if(urlList != null) {
if(urlList.size() > 1)
urlList.add(url);
else {
/* Convert singleton to real list */
ArrayList<URL> newList = new ArrayList<>(urlList);
newList.add(url);
urlsForClass.put(strPath, newList);
}
} else {
/* Use a singleton list initially to keep memory usage down */
urlsForClass.put(strPath, Collections.singletonList(url));
}
});
} catch(IOException e) {
throw new RuntimeException(e);
}
}
}
for(List<URL> list : urlsForClass.values()) {
if(list instanceof ArrayList)
((ArrayList<URL>)list).trimToSize();
}
LOGGER.info("Finish building");
}
private static Iterable<Path> getRootPathForLocator(IModLocator locator, ModFile file) throws ReflectiveOperationException {
if(locator instanceof AbstractJarFileLocator) {
FileSystem modFs = locator.findPath(file, ".").getFileSystem();
return modFs.getRootDirectories();
} else if (locator instanceof ExplodedDirectoryLocator) {
if(explodedDirModsField == null) {
explodedDirModsField = ExplodedDirectoryLocator.class.getDeclaredField("mods");
explodedDirModsField.setAccessible(true);
}
Map<IModFile, Pair<Path, List<Path>>> mods = (Map<IModFile, Pair<Path, List<Path>>>)explodedDirModsField.get(locator);
return mods.get(file).getRight();
} else if(MINECRAFT_LOCATOR.isAssignableFrom(locator.getClass())) {
Path mcJar = FMLLoader.getMCPaths()[0];
if(Files.isDirectory(mcJar)) {
return mcJar;
} else {
return locator.findPath(file, ".").getFileSystem().getRootDirectories();
}
} else
throw new UnsupportedOperationException("Unknown ModLocator type: " + locator.getClass().getName());
}
private static final Pattern SLASH_REPLACER = Pattern.compile("/+");
public static Enumeration<URL> findAllURLsForResource(String input) {
input = SLASH_REPLACER.matcher(input).replaceAll("/");
List<URL> urlList = urlsForClass.get(input);
if(urlList != null)
return Collections.enumeration(urlList);
else {
return Collections.emptyEnumeration();
}
}
}

View File

@ -1,11 +0,0 @@
package org.embeddedt.modernfix.classloading.api;
public interface IHashableTransformer {
/**
* Called on an ILaunchPluginService or ITransformer to obtain a unique hash of the transformations that will be applied.
* Used to invalidate the transformation cache when needed.
* @param className Name of class being transformed
* @return A unique hash of the transformations that will be applied
*/
byte[] getHashForClass(String className);
}

View File

@ -1,49 +0,0 @@
package org.embeddedt.modernfix.classloading.hashers;
import cpw.mods.modlauncher.ModernFixCachingClassTransformer;
import net.minecraftforge.coremod.CoreMod;
import net.minecraftforge.coremod.transformer.CoreModBaseTransformer;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.ConcurrentHashMap;
public class CoreModTransformerHasher {
private static final ConcurrentHashMap<CoreMod, byte[]> hashForCoremod;
private static Field coremodField;
static {
hashForCoremod = new ConcurrentHashMap<>();
try {
coremodField = CoreModBaseTransformer.class.getDeclaredField("coreMod");
coremodField.setAccessible(true);
} catch(ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
private static byte[] hashCoreMod(CoreMod coreMod) {
byte[] coreModContents;
try {
coreModContents = Files.readAllBytes(coreMod.getPath());
} catch(IOException e) {
throw new RuntimeException(e);
}
MessageDigest hasher = ModernFixCachingClassTransformer.systemHasher.get();
hasher.reset();
return hasher.digest(coreModContents);
}
public static byte[] obtainHash(CoreModBaseTransformer<?> transformer) {
CoreMod coremod;
try {
coremod = (CoreMod)coremodField.get(transformer);
} catch(ReflectiveOperationException e) {
throw new RuntimeException(e);
}
return hashForCoremod.computeIfAbsent(coremod, CoreModTransformerHasher::hashCoreMod);
}
}

View File

@ -1,154 +0,0 @@
package org.embeddedt.modernfix.classloading.hashers;
import com.google.common.io.Resources;
import cpw.mods.modlauncher.ModernFixCachingClassTransformer;
import org.spongepowered.asm.launch.IClassProcessor;
import org.spongepowered.asm.launch.MixinLaunchPluginLegacy;
import org.spongepowered.asm.mixin.MixinEnvironment;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import org.spongepowered.asm.mixin.injection.invoke.arg.ArgsClassGenerator;
import org.spongepowered.asm.mixin.transformer.ext.Extensions;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.stream.Stream;
public class MixinTransformerHasher {
private static HashMap<String, byte[]> hashesByClass = null;
private final static MessageDigest hasher;
private static Field processorsListField, transformerField, processorField, environmentField;
private static boolean fixedArgsClassCount = false;
private static final byte[] NO_MIXINS = new byte[] {(byte)0xde, (byte)0xad, (byte)0xbe, (byte)0xef};
static {
try {
hasher = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public static byte[] obtainHash(MixinLaunchPluginLegacy plugin, String className) {
/* FIXME runs too early right now, and therefore doesn't pick up the list of mixins correctly */
synchronized (MixinTransformerHasher.class) {
if(hashesByClass == null) {
try {
if(processorsListField == null) {
processorsListField = MixinLaunchPluginLegacy.class.getDeclaredField("processors");
processorsListField.setAccessible(true);
}
List<IClassProcessor> processors = (List<IClassProcessor>) processorsListField.get(plugin);
Object transformHandler = null;
for(IClassProcessor processor : processors) {
if(processor.getClass().getName().equals("org.spongepowered.asm.service.modlauncher.MixinTransformationHandler")) {
transformHandler = processor;
break;
}
}
if(transformHandler == null)
throw new IllegalStateException("Mixin transform handler not found");
if(transformerField == null) {
transformerField = transformHandler.getClass().getDeclaredField("transformer");
transformerField.setAccessible(true);
}
Object transformer = transformerField.get(transformHandler);
if(!fixedArgsClassCount) {
Path syntheticFolderPath = ModernFixCachingClassTransformer.CLASS_CACHE_FOLDER.toPath().resolve("org").resolve("spongepowered").resolve("asm").resolve("synthetic");
if(Files.exists(syntheticFolderPath)) {
Field extensionsField = transformer.getClass().getDeclaredField("extensions");
extensionsField.setAccessible(true);
Extensions extensions = (Extensions)extensionsField.get(transformer);
ArgsClassGenerator argsGen = extensions.getGenerator(ArgsClassGenerator.class);
Field nextIndexField = ArgsClassGenerator.class.getDeclaredField("nextIndex");
try(Stream<Path> argsStream = Files.find(syntheticFolderPath, 1, (path, attr) -> path.getFileName().toString().startsWith("Args$"))) {
int[] startIndex = new int[1];
startIndex[0] = 1;
argsStream.forEach(path -> {
String fileName = path.getFileName().toString();
try {
int idx = Integer.parseInt(fileName.replace("Args$", ""));
startIndex[0] = Math.max(startIndex[0], idx + 1);
} catch(NumberFormatException e) {
ModernFixCachingClassTransformer.LOGGER.warn("Unexpected classname: " + fileName);
}
});
nextIndexField.setAccessible(true);
nextIndexField.set(argsGen, startIndex[0]);
ModernFixCachingClassTransformer.LOGGER.debug("Patched ArgsClassGenerator to start at index " + startIndex[0]);
} catch(IOException e) {
ModernFixCachingClassTransformer.LOGGER.error("Failed to adjust Mixin synthetic args");
}
}
fixedArgsClassCount = true;
}
if(processorField == null) {
processorField = transformer.getClass().getDeclaredField("processor");
processorField.setAccessible(true);
}
Object processor = processorField.get(transformer);
if(environmentField == null) {
environmentField = processor.getClass().getDeclaredField("currentEnvironment");
environmentField.setAccessible(true);
}
MixinEnvironment currentEnv = (MixinEnvironment)environmentField.get(processor);
if(currentEnv == null || currentEnv.getPhase() != MixinEnvironment.Phase.DEFAULT) {
return null; /* no hash obtained until mixin is ready */
}
Field configsField = processor.getClass().getDeclaredField("configs");
configsField.setAccessible(true);
List<?> configs = (List<?>)configsField.get(processor);
Field mixinsField = Class.forName("org.spongepowered.asm.mixin.transformer.MixinConfig").getDeclaredField("mixins");
mixinsField.setAccessible(true);
/* getTargetClasses can't be used because it's package-private */
Field classNamesField = Class.forName("org.spongepowered.asm.mixin.transformer.MixinInfo").getDeclaredField("targetClassNames");
classNamesField.setAccessible(true);
HashMap<String, ArrayList<IMixinInfo>> mixinsByClass = new HashMap<>();
for(Object config : configs) {
List<? extends IMixinInfo> mixins = (List<? extends IMixinInfo>)mixinsField.get(config);
for(IMixinInfo mixin : mixins) {
List<String> targetClassNames = (List<String>)classNamesField.get(mixin);
for(String s : targetClassNames) {
mixinsByClass.computeIfAbsent(s, k -> new ArrayList<>()).add(mixin);
}
}
}
for(ArrayList<IMixinInfo> infos : mixinsByClass.values()) {
infos.sort((info1, info2) -> Comparator.<String>naturalOrder().compare(info1.getClassName(), info2.getClassName()));
}
/* Now go through each class name and hash it */
HashMap<String, byte[]> hashesByClassInit = new HashMap<>();
for(Map.Entry<String, ArrayList<IMixinInfo>> mixinsForClass : mixinsByClass.entrySet()) {
hasher.reset();
for(IMixinInfo mixin : mixinsForClass.getValue()) {
URL url = Thread.currentThread().getContextClassLoader().getResource(mixin.getClassName().replace('.', '/') + ".class");
if(url == null)
throw new IllegalStateException("Can't find " + mixin.getClassName());
byte[] bytecode;
try {
bytecode = Resources.asByteSource(url).read();
} catch(IOException e) {
throw new RuntimeException(e);
}
hasher.update(bytecode);
}
hashesByClassInit.put(mixinsForClass.getKey().replace('/', '.'), hasher.digest());
}
hashesByClass = hashesByClassInit;
} catch(ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
return hashesByClass.getOrDefault(className, NO_MIXINS);
}
}

View File

@ -1,12 +1,7 @@
package org.embeddedt.modernfix.core;
import com.google.common.io.Resources;
import cpw.mods.modlauncher.*;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.embeddedt.modernfix.classloading.ModernFixResourceFinder;
import org.embeddedt.modernfix.core.config.ModernFixEarlyConfig;
import org.embeddedt.modernfix.core.config.Option;
import org.objectweb.asm.tree.ClassNode;
@ -14,14 +9,7 @@ import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.function.Function;
public class ModernFixMixinPlugin implements IMixinConfigPlugin {
private static final String MIXIN_PACKAGE_ROOT = "org.embeddedt.modernfix.mixin.";
@ -38,88 +26,11 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin {
this.logger.info("Loaded configuration file for ModernFix: {} options available, {} override(s) found",
config.getOptionCount(), config.getOptionOverrideCount());
/* We abuse the constructor of a mixin plugin as a safe location to start modifying the classloader */
/* Swap the transformer for ours */
ClassLoader loader = Thread.currentThread().getContextClassLoader();
if(!(loader instanceof TransformingClassLoader)) {
throw new IllegalStateException("Expected a TransformingClassLoader");
}
try {
if(isOptionEnabled("launch.class_search_cache.ModernFixResourceFinder")) {
Field resourceFinderField = TransformingClassLoader.class.getDeclaredField("resourceFinder");
/* Construct a new list of resource finders, using similar logic to ML */
resourceFinderField.setAccessible(true);
Function<String, Enumeration<URL>> resourceFinder = constructResourceFinder();
/* Merge with the findResources implementation provided by the DelegatedClassLoader */
Field dclField = TransformingClassLoader.class.getDeclaredField("delegatedClassLoader");
dclField.setAccessible(true);
URLClassLoader dcl = (URLClassLoader)dclField.get(loader);
resourceFinder = EnumerationHelper.mergeFunctors(resourceFinder, LamdbaExceptionUtils.rethrowFunction(dcl::findResources));
resourceFinderField.set(loader, resourceFinder);
}
} catch(RuntimeException | ReflectiveOperationException e) {
logger.error("Failed to make classloading changes", e);
}
}
private Method defineClassMethod = null;
private Class<?> injectClassIntoSystemLoader(String className) throws ReflectiveOperationException, IOException {
ClassLoader systemLoader = ClassTransformer.class.getClassLoader();
if(defineClassMethod == null) {
defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClassMethod.setAccessible(true);
}
byte[] newTransformerBytes = Resources.toByteArray(ModernFixMixinPlugin.class.getResource("/" + className.replace('.', '/') + ".class"));
return (Class<?>)defineClassMethod.invoke(systemLoader, className, newTransformerBytes, 0, newTransformerBytes.length);
}
private Function<String, Enumeration<URL>> constructResourceFinder() throws ReflectiveOperationException {
ModernFixResourceFinder.init();
Field servicesHandlerField = Launcher.class.getDeclaredField("transformationServicesHandler");
servicesHandlerField.setAccessible(true);
Object servicesHandler = servicesHandlerField.get(Launcher.INSTANCE);
Field serviceLookupField = servicesHandler.getClass().getDeclaredField("serviceLookup");
serviceLookupField.setAccessible(true);
Map<String, TransformationServiceDecorator> serviceLookup = (Map<String, TransformationServiceDecorator>)serviceLookupField.get(servicesHandler);
Method getClassLoaderMethod = TransformationServiceDecorator.class.getDeclaredMethod("getClassLoader");
getClassLoaderMethod.setAccessible(true);
Function<String, Enumeration<URL>> resourceEnumeratorLocator = ModernFixResourceFinder::findAllURLsForResource;
for(TransformationServiceDecorator decorator : serviceLookup.values()) {
Function<String, Optional<URL>> func = (Function<String, Optional<URL>>)getClassLoaderMethod.invoke(decorator);
if(func != null) {
resourceEnumeratorLocator = EnumerationHelper.mergeFunctors(resourceEnumeratorLocator, EnumerationHelper.fromOptional(func));
}
}
return resourceEnumeratorLocator;
}
@Override
public void onLoad(String mixinPackage) {
try {
if(isOptionEnabled("launch.transformer_cache.ModernFixClassTransformer")) {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Field classTransformerField = TransformingClassLoader.class.getDeclaredField("classTransformer");
classTransformerField.setAccessible(true);
ClassTransformer t = (ClassTransformer)classTransformerField.get(loader);
TransformStore store = ObfuscationReflectionHelper.getPrivateValue(ClassTransformer.class, t, "transformers");
LaunchPluginHandler pluginHandler = ObfuscationReflectionHelper.getPrivateValue(ClassTransformer.class, t, "pluginHandler");
TransformerAuditTrail trail = ObfuscationReflectionHelper.getPrivateValue(ClassTransformer.class, t, "auditTrail");
injectClassIntoSystemLoader("org.embeddedt.modernfix.util.FileUtil");
injectClassIntoSystemLoader("org.embeddedt.modernfix.classloading.api.IHashableTransformer");
injectClassIntoSystemLoader("org.embeddedt.modernfix.classloading.hashers.CoreModTransformerHasher");
injectClassIntoSystemLoader("org.embeddedt.modernfix.classloading.hashers.MixinTransformerHasher");
Class<?> newTransformerClass = injectClassIntoSystemLoader("cpw.mods.modlauncher.ModernFixCachingClassTransformer");
Constructor<?> constructor = newTransformerClass.getConstructor(TransformStore.class, LaunchPluginHandler.class, TransformingClassLoader.class, TransformerAuditTrail.class);
ClassTransformer newTransformer = (ClassTransformer)constructor.newInstance(store, pluginHandler, loader, trail);
classTransformerField.set(loader, newTransformer);
public void onLoad(String s) {
logger.info("Successfully injected caching transformer");
}
} catch(RuntimeException | ReflectiveOperationException | IOException e) {
logger.error("Failed to make classloading changes", e);
}
}
@Override

View File

@ -7,6 +7,7 @@ import net.minecraftforge.common.ForgeConfigSpec;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.event.config.ModConfigEvent;
import org.embeddedt.modernfix.ModernFix;
import java.util.Collections;
@ -52,7 +53,7 @@ public class ModernFixConfig {
}
@SubscribeEvent
public static void onModConfigEvent(final ModConfig.ModConfigEvent configEvent) {
public static void onModConfigEvent(final ModConfigEvent configEvent) {
if (configEvent.getConfig().getSpec() == COMMON_CONFIG) {
bakeConfig();
}

View File

@ -20,30 +20,20 @@ public class ModernFixEarlyConfig {
this.addMixinRule("feature.measure_time", true);
this.addMixinRule("feature.reduce_loading_screen_freezes", false);
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.parallelize_model_loading.multipart", false);
this.addMixinRule("bugfix.concurrency", true);
this.addMixinRule("bugfix.edge_chunk_not_saved", true);
this.addMixinRule("bugfix.packet_leak", false);
this.addMixinRule("bugfix.structure_manager_crash", true);
this.addMixinRule("perf.async_jei", true);
this.addMixinRule("perf.thread_priorities", true);
this.addMixinRule("perf.preload_block_classes", false);
this.addMixinRule("perf.sync_executor_sleep", true);
this.addMixinRule("perf.scan_cache", true);
this.addMixinRule("perf.compress_biome_container", true);
this.addMixinRule("perf.nuke_empty_chunk_sections", true);
this.addMixinRule("perf.flatten_model_predicates", true);
this.addMixinRule("perf.deduplicate_location", false);
this.addMixinRule("perf.cache_blockstate_cache_arrays", true);
this.addMixinRule("perf.faster_baking", true);
this.addMixinRule("perf.cache_model_materials", true);
this.addMixinRule("perf.datapack_reload_exceptions", true);
this.addMixinRule("perf.async_locator", true);
/* Keep this off if JEI isn't installed to prevent breaking vanilla gameplay */
this.addMixinRule("perf.blast_search_trees", FMLLoader.getLoadingModList().getModFileById("jei") != null);
this.addMixinRule("safety", true);

View File

@ -1,8 +0,0 @@
package org.embeddedt.modernfix.jei.async;
public interface IAsyncJeiStarter {
static void checkForLoadInterruption() {
if(((JEIReloadThread)Thread.currentThread()).isStopRequested())
throw new JEILoadingInterruptedException();
}
}

View File

@ -1,4 +0,0 @@
package org.embeddedt.modernfix.jei.async;
public class JEILoadingInterruptedException extends Error {
}

View File

@ -1,18 +0,0 @@
package org.embeddedt.modernfix.jei.async;
public class JEIReloadThread extends Thread {
private volatile boolean stopRequested;
public JEIReloadThread(Runnable runnable, String s) {
super(runnable, s);
this.stopRequested = false;
}
public void requestStop() {
stopRequested = true;
}
public boolean isStopRequested() {
return stopRequested;
}
}

View File

@ -1,25 +0,0 @@
package org.embeddedt.modernfix.mixin.bugfix.concurrency;
import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet;
import net.minecraft.client.renderer.RenderType;
import com.mojang.blaze3d.vertex.VertexFormat;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(RenderType.CompositeRenderType.class)
public class RenderTypeMixin {
@Shadow @Final private static ObjectOpenCustomHashSet<RenderType.CompositeRenderType> INSTANCES;
/**
* @author embeddedt
* @reason synchronize, can be accessed by multiple mods during modloading
*/
@Overwrite
private static RenderType.CompositeRenderType memoize(String name, VertexFormat format, int drawMode, int bufferSize, boolean useDelegate, boolean needsSorting, RenderType.CompositeState renderState) {
synchronized (INSTANCES){
return INSTANCES.addOrGet(new RenderType.CompositeRenderType(name, format, drawMode, bufferSize, useDelegate, needsSorting, renderState));
}
}
}

View File

@ -1,31 +0,0 @@
package org.embeddedt.modernfix.mixin.bugfix.packet_leak;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket;
import org.embeddedt.modernfix.duck.IClientNetHandler;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(ClientPacketListener.class)
public class ClientPlayNetHandlerMixin implements IClientNetHandler {
private FriendlyByteBuf savedCopy = null;
/**
* @author embeddedt
* @reason Release the packet buffer at the end. Needed in f
*/
@Redirect(method = "handleCustomPayload", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/protocol/game/ClientboundCustomPayloadPacket;getData()Lnet/minecraft/network/FriendlyByteBuf;"))
private FriendlyByteBuf saveCopyForRelease(ClientboundCustomPayloadPacket instance) {
FriendlyByteBuf copy = instance.getData();
savedCopy = copy;
return copy;
}
@Override
public FriendlyByteBuf getCopiedCustomBuffer() {
FriendlyByteBuf copy = savedCopy;
savedCopy = null;
return copy;
}
}

View File

@ -1,43 +0,0 @@
package org.embeddedt.modernfix.mixin.bugfix.packet_leak;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket;
import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.duck.IClientNetHandler;
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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ClientboundCustomPayloadPacket.class)
public class SCustomPayloadPlayPacketMixin {
@Shadow private FriendlyByteBuf data;
private boolean needsRelease;
@Inject(method = "<init>(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/network/FriendlyByteBuf;)V", at = @At("RETURN"))
private void markNotOwned(ResourceLocation pIdentifier, FriendlyByteBuf pData, CallbackInfo ci) {
this.needsRelease = false;
}
@Inject(method = "read", at = @At("RETURN"))
private void markOwned(FriendlyByteBuf p_148837_1_, CallbackInfo ci) {
this.needsRelease = true;
}
@Redirect(method = "handle(Lnet/minecraft/network/protocol/game/ClientGamePacketListener;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/protocol/game/ClientGamePacketListener;handleCustomPayload(Lnet/minecraft/network/protocol/game/ClientboundCustomPayloadPacket;)V"))
private void handleAndFree(ClientGamePacketListener instance, ClientboundCustomPayloadPacket sCustomPayloadPlayPacket) {
try {
instance.handleCustomPayload(sCustomPayloadPlayPacket);
} finally {
FriendlyByteBuf copied = ((IClientNetHandler)instance).getCopiedCustomBuffer();
if(copied != null)
copied.release();
}
if(this.needsRelease)
this.data.release();
}
}

View File

@ -1,27 +0,0 @@
package org.embeddedt.modernfix.mixin.bugfix.structure_manager_crash;
import com.mojang.datafixers.DataFixer;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.level.storage.LevelStorageSource;
import org.spongepowered.asm.mixin.Final;
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.util.Collections;
import java.util.Map;
@Mixin(StructureManager.class)
public class StructureManagerMixin {
@Shadow @Final private Map<ResourceLocation, StructureTemplate> structureRepository;
@Inject(method = "<init>", at = @At("RETURN"))
private void makeStructuresSafe(ResourceManager arg, LevelStorageSource.LevelStorageAccess arg2, DataFixer dataFixer, CallbackInfo ci) {
this.structureRepository = Collections.synchronizedMap(this.structureRepository);
}
}

View File

@ -2,8 +2,8 @@ package org.embeddedt.modernfix.mixin.feature.measure_time;
import com.google.common.base.Stopwatch;
import net.minecraft.server.Bootstrap;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.Opcodes;
import org.slf4j.Logger;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;

View File

@ -2,6 +2,7 @@ package org.embeddedt.modernfix.mixin.feature.measure_time;
import com.mojang.datafixers.util.Function4;
import net.minecraft.client.Minecraft;
import net.minecraft.server.WorldStem;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.world.level.DataPackConfig;
import net.minecraft.core.RegistryAccess;
@ -21,19 +22,19 @@ import java.util.function.Function;
public class MinecraftMixin {
private long datapackReloadStartTime;
@Inject(method = "makeServerStem", at = @At(value = "HEAD"))
private void recordReloadStart(RegistryAccess.RegistryHolder p_238189_1_, Function<LevelStorageSource.LevelStorageAccess, DataPackConfig> p_238189_2_, Function4<LevelStorageSource.LevelStorageAccess, RegistryAccess.RegistryHolder, ResourceManager, DataPackConfig, WorldData> p_238189_3_, boolean p_238189_4_, LevelStorageSource.LevelStorageAccess p_238189_5_, CallbackInfoReturnable<Minecraft.ServerStem> cir) {
@Inject(method = "makeWorldStem", at = @At(value = "HEAD"))
private void recordReloadStart(CallbackInfoReturnable<WorldStem> cir) {
datapackReloadStartTime = System.nanoTime();
}
@Inject(method = "makeServerStem", at = @At(value = "RETURN"))
private void recordReloadEnd(RegistryAccess.RegistryHolder p_238189_1_, Function<LevelStorageSource.LevelStorageAccess, DataPackConfig> p_238189_2_, Function4<LevelStorageSource.LevelStorageAccess, RegistryAccess.RegistryHolder, ResourceManager, DataPackConfig, WorldData> p_238189_3_, boolean p_238189_4_, LevelStorageSource.LevelStorageAccess p_238189_5_, CallbackInfoReturnable<Minecraft.ServerStem> cir) {
@Inject(method = "makeWorldStem", at = @At(value = "RETURN"))
private void recordReloadEnd(CallbackInfoReturnable<WorldStem> cir) {
float timeSpentReloading = ((float)(System.nanoTime() - datapackReloadStartTime) / 1000000000f);
ModernFix.LOGGER.warn("Datapack reload took " + timeSpentReloading + " seconds.");
}
@Inject(method = "loadWorld", at = @At("HEAD"), remap = false)
private void recordWorldLoadStart(String worldName, RegistryAccess.RegistryHolder dynamicRegistries, Function<LevelStorageSource.LevelStorageAccess, DataPackConfig> levelSaveToDatapackFunction, Function4<LevelStorageSource.LevelStorageAccess, RegistryAccess.RegistryHolder, ResourceManager, DataPackConfig, WorldData> quadFunction, boolean vanillaOnly, Minecraft.ExperimentalDialogType selectionType, boolean creating, CallbackInfo ci) {
@Inject(method = "doLoadLevel", at = @At("HEAD"), remap = false)
private void recordWorldLoadStart(CallbackInfo ci) {
ModernFixClient.worldLoadStartTime = System.nanoTime();
}
}

View File

@ -1,105 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.async_jei;
import mezz.jei.api.IModPlugin;
import mezz.jei.api.helpers.IModIdHelper;
import mezz.jei.config.*;
import mezz.jei.config.sorting.RecipeCategorySortingConfig;
import mezz.jei.events.EventBusHelper;
import mezz.jei.events.PlayerJoinedWorldEvent;
import mezz.jei.gui.textures.Textures;
import mezz.jei.ingredients.IIngredientSorter;
import mezz.jei.startup.ClientLifecycleHandler;
import mezz.jei.startup.JeiStarter;
import mezz.jei.startup.NetworkHandler;
import net.minecraft.client.Minecraft;
import net.minecraftforge.client.event.ClientPlayerNetworkEvent;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.jei.async.JEILoadingInterruptedException;
import org.embeddedt.modernfix.jei.async.JEIReloadThread;
import org.embeddedt.modernfix.util.JEIUtil;
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.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.List;
@Mixin(ClientLifecycleHandler.class)
public class ClientLifecycleHandlerMixin {
@Shadow(remap = false) @Final private JeiStarter starter;
@Shadow(remap = false) @Final private List<IModPlugin> plugins;
@Shadow(remap = false) @Final private Textures textures;
@Shadow(remap = false) @Final private IClientConfig clientConfig;
@Shadow(remap = false) @Final private IEditModeConfig editModeConfig;
@Shadow(remap = false) @Final private IngredientFilterConfig ingredientFilterConfig;
@Shadow(remap = false) @Final private WorldConfig worldConfig;
@Shadow(remap = false) @Final private BookmarkConfig bookmarkConfig;
@Shadow(remap = false) @Final private IModIdHelper modIdHelper;
@Shadow(remap = false) @Final private RecipeCategorySortingConfig recipeCategorySortingConfig;
@Shadow(remap = false) @Final private IIngredientSorter ingredientSorter;
private volatile JEIReloadThread reloadThread = null;
@Inject(method = "setupJEI", at = @At(value = "INVOKE", target = "Lmezz/jei/startup/ClientLifecycleHandler;startJEI()V"), cancellable = true, remap = false)
private void startAsync(CallbackInfo ci) {
ci.cancel();
startJEIAsync(() -> Minecraft.getInstance().execute(() -> EventBusHelper.post(new PlayerJoinedWorldEvent())));
}
/**
* @author embeddedt
* @reason force JEI starts to be asynchronous
*/
@Overwrite(remap = false)
public void startJEI() {
startJEIAsync(() -> {});
}
@Inject(method = "<init>", at = @At("TAIL"))
private void setupCancellationHandler(NetworkHandler networkHandler, Textures textures, CallbackInfo ci) {
EventBusHelper.addListener(this, ClientPlayerNetworkEvent.LoggedOutEvent.class, event -> cancelPreviousStart());
JEIUtil.registerLoadingRenderer(() -> reloadThread != null);
}
private void cancelPreviousStart() {
JEIReloadThread currentReloadThread = reloadThread;
if(currentReloadThread != null) {
currentReloadThread.requestStop();
Minecraft.getInstance().managedBlock(currentReloadThread::isAlive);
reloadThread = null;
}
}
private static int numReloads = 1;
private void startJEIAsync(Runnable whenFinishedCb) {
ModernFix.LOGGER.info("JEI restart triggered. Waiting for previous thread to die.");
cancelPreviousStart();
ModernFix.LOGGER.info("Starting new JEI thread.");
JEIReloadThread newThread = new JEIReloadThread(() -> {
try {
starter.start(
plugins,
textures,
clientConfig,
editModeConfig,
ingredientFilterConfig,
worldConfig,
bookmarkConfig,
modIdHelper,
recipeCategorySortingConfig,
ingredientSorter);
} catch(JEILoadingInterruptedException e) {
ModernFix.LOGGER.warn("JEI loading interrupted prematurely (this is normal)");
}
whenFinishedCb.run();
reloadThread = null;
}, "ModernFix JEI Reload Thread " + numReloads++);
newThread.setPriority(Thread.MIN_PRIORITY);
reloadThread = newThread;
newThread.start();
}
}

View File

@ -1,22 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.async_jei;
import mezz.jei.api.ingredients.IIngredientType;
import mezz.jei.api.runtime.IIngredientManager;
import mezz.jei.gui.ingredients.IIngredientListElement;
import mezz.jei.ingredients.IngredientListElementFactory;
import net.minecraft.core.NonNullList;
import org.embeddedt.modernfix.jei.async.IAsyncJeiStarter;
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;
@Mixin(IngredientListElementFactory.class)
public class IngredientListElementFactoryMixin {
private static int ingredientNum = 0;
@Inject(method = "addToBaseList", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/NonNullList;add(Ljava/lang/Object;)Z"))
private static void checkForInterrupt(NonNullList<IIngredientListElement<?>> baseList, IIngredientManager ingredientManager, IIngredientType ingredientType, CallbackInfo ci) {
if((ingredientNum++ % 100) == 0)
IAsyncJeiStarter.checkForLoadInterruption();
}
}

View File

@ -1,47 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.async_jei;
import mezz.jei.api.IModPlugin;
import mezz.jei.api.helpers.IModIdHelper;
import mezz.jei.config.*;
import mezz.jei.config.sorting.RecipeCategorySortingConfig;
import mezz.jei.gui.textures.Textures;
import mezz.jei.ingredients.IIngredientSorter;
import mezz.jei.load.PluginCaller;
import mezz.jei.startup.JeiStarter;
import net.minecraft.client.Minecraft;
import org.embeddedt.modernfix.jei.async.IAsyncJeiStarter;
import org.embeddedt.modernfix.jei.async.JEILoadingInterruptedException;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CancellationException;
import java.util.List;
import java.util.concurrent.CompletionException;
import java.util.function.Consumer;
@Mixin(JeiStarter.class)
public class JeiStarterMixin {
@Shadow(remap = false) private boolean started;
@Inject(method = "start", at = @At(value = "INVOKE", target = "Lmezz/jei/util/ErrorUtil;checkNotEmpty(Ljava/util/Collection;Ljava/lang/String;)V", ordinal = 0, shift = At.Shift.AFTER), remap = false)
private void setStartedFlag(List<IModPlugin> plugins, Textures textures, IClientConfig clientConfig, IEditModeConfig editModeConfig, IIngredientFilterConfig ingredientFilterConfig, IWorldConfig worldConfig, BookmarkConfig bookmarkConfig, IModIdHelper modIdHelper, RecipeCategorySortingConfig recipeCategorySortingConfig, IIngredientSorter ingredientSorter, CallbackInfo ci) {
/* We need to set this ASAP so the reload system will restart the async load if needed */
started = true;
}
@Redirect(method = "start", at = @At(value = "INVOKE", target = "Lmezz/jei/load/PluginCaller;callOnPlugins(Ljava/lang/String;Ljava/util/List;Ljava/util/function/Consumer;)V"), remap = false)
private void callOnPluginsViaMainThread(String title, List<IModPlugin> plugins, Consumer<IModPlugin> func) {
PluginCaller.callOnPlugins(title, plugins, plugin -> {
try {
Minecraft.getInstance().executeBlocking(() -> func.accept(plugin));
} catch(CancellationException | CompletionException e) {
throw new RuntimeException(e);
}
});
}
}

View File

@ -1,38 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.async_jei;
import mezz.jei.api.IModPlugin;
import mezz.jei.load.PluginCaller;
import net.minecraft.client.Minecraft;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.core.config.ModernFixConfig;
import org.embeddedt.modernfix.jei.async.IAsyncJeiStarter;
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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletionException;
import java.util.function.Consumer;
@Mixin(PluginCaller.class)
public class PluginCallerMixin {
@Inject(method = "callOnPlugins", at = @At(value = "INVOKE", target = "Ljava/util/Iterator;hasNext()Z"), remap = false)
private static void checkForInterrupt(String title, List<IModPlugin> plugins, Consumer<IModPlugin> func, CallbackInfo ci) {
IAsyncJeiStarter.checkForLoadInterruption();
}
@SuppressWarnings({"unchecked","rawtypes"})
@Redirect(method = "callOnPlugins", at = @At(value = "INVOKE", target = "Ljava/util/function/Consumer;accept(Ljava/lang/Object;)V"), remap = false)
private static void runOnMainThreadIfNeeded(Consumer instance, Object pluginObj) {
IModPlugin plugin = (IModPlugin)pluginObj;
if(ModernFixConfig.jeiPluginBlacklist.contains(plugin.getPluginUid())) {
ModernFix.LOGGER.warn("Going to main thread for " + plugin.getPluginUid());
Minecraft.getInstance().executeBlocking(() -> instance.accept(plugin));
} else {
instance.accept(plugin);
}
}
}

View File

@ -1,18 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.async_jei;
import com.google.common.collect.ImmutableListMultimap;
import mezz.jei.recipes.RecipeManagerInternal;
import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.jei.async.IAsyncJeiStarter;
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;
@Mixin(RecipeManagerInternal.class)
public class RecipeManagerInternalMixin {
@Inject(method = "addRecipes", at = @At(value = "INVOKE", target = "Lmezz/jei/recipes/RecipeManagerInternal;addRecipeTyped(Ljava/lang/Object;Lnet/minecraft/resources/ResourceLocation;)V"))
private void checkForInterrupt(ImmutableListMultimap<ResourceLocation, Object> recipes, CallbackInfo ci) {
IAsyncJeiStarter.checkForLoadInterruption();
}
}

View File

@ -1,12 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.async_locator;
import net.minecraft.commands.CommandSource;
import net.minecraft.commands.CommandSourceStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(CommandSourceStack.class)
public interface CommandSourceStackAccess {
@Accessor
CommandSource getSource();
}

View File

@ -1,109 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.async_locator;
import com.google.common.collect.ImmutableSet;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.animal.Dolphin;
import net.minecraft.world.level.levelgen.feature.StructureFeature;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.structure.AsyncLocator;
import org.spongepowered.asm.mixin.Final;
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 org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
@Mixin(targets = "net.minecraft.world.entity.animal.Dolphin$DolphinSwimToTreasureGoal")
public class DolphinSwimToTreasureGoalMixin {
@Final
@Shadow
private Dolphin dolphin;
@Shadow
private boolean stuck;
private AsyncLocator.LocateTask<BlockPos> locateTask = null;
/*
Intercept DolphinSwimToTreasureGoal#start call right before it calls ServerLevel#findNearestMapFeature to pass
the logic over to an async task.
*/
@Inject(
method = "start",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/server/level/ServerLevel;findNearestMapFeature(Lnet/minecraft/world/level/levelgen/feature/StructureFeature;Lnet/minecraft/core/BlockPos;IZ)Lnet/minecraft/core/BlockPos;"
),
cancellable = true,
locals = LocalCapture.CAPTURE_FAILSOFT
)
public void findTreasureAsync(CallbackInfo ci, ServerLevel level, BlockPos blockpos) {
ModernFix.LOGGER.debug("Intercepted DolphinSwimToTreasureGoal#start call");
handleFindTreasureAsync(level, blockpos);
ci.cancel();
}
/*
Intercept DolphinSwimToTreasureGoal#canContinueToUse to return true if an async locating task is ongoing so that
the goal isn't ended early due to no treasure pos being set yet.
*/
@Inject(
method = "canContinueToUse",
at = @At(value = "HEAD"),
cancellable = true
)
public void continueToUseIfLocatingTreasure(CallbackInfoReturnable<Boolean> cir) {
if (locateTask != null) {
ModernFix.LOGGER.debug("Locating task ongoing - returning true for continueToUse()");
cir.setReturnValue(true);
}
}
@Inject(
method = "stop",
at = @At(value = "HEAD")
)
public void stopLocatingTreasure(CallbackInfo ci) {
if (locateTask != null) {
ModernFix.LOGGER.debug("Locating task ongoing - cancelling during stop()");
locateTask.cancel();
locateTask = null;
}
}
/*
Intercept DolphinSwimToTreasureGoal#tick to return early if an async locating task is ongoing so that the
dolphin doesn't try to go towards an old treasure position.
*/
@Inject(
method = "tick",
at = @At(value = "HEAD"),
cancellable = true
)
public void skipTickingIfLocatingTreasure(CallbackInfo ci) {
if (locateTask != null) {
ModernFix.LOGGER.debug("Locating task ongoing - skipping tick()");
ci.cancel();
}
}
private void handleFindTreasureAsync(ServerLevel level, BlockPos blockPos) {
locateTask = AsyncLocator.locateLevel(level, ImmutableSet.of(StructureFeature.OCEAN_RUIN, StructureFeature.SHIPWRECK), blockPos, 50, false)
.thenOnServerThread(pos -> handleLocationFound(level, pos));
}
private void handleLocationFound(ServerLevel level, BlockPos pos) {
locateTask = null;
if (pos != null) {
ModernFix.LOGGER.debug("Location found - updating dolphin treasure pos");
dolphin.setTreasurePos(pos);
level.broadcastEntityEvent(dolphin, (byte) 38);
} else {
ModernFix.LOGGER.debug("No location found - marking dolphin as stuck");
stuck = true;
}
}
}

View File

@ -1,104 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.async_locator;
import net.minecraft.advancements.critereon.UsedEnderEyeTrigger;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.stats.Stat;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.EyeOfEnder;
import net.minecraft.world.item.EnderEyeItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.feature.StructureFeature;
import net.minecraft.world.phys.HitResult;
import org.embeddedt.modernfix.structure.logic.EnderEyeItemLogic;
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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
@Mixin(EnderEyeItem.class)
public class EnderEyeItemMixin {
/*
Intercept EnderEyeItem#use call and return BlockPos.ZERO instead. It won't be used in the EyeOfEnder entity
created later either, as we need to set the actual location ourselves.
*/
@Redirect(
method = "use",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/chunk/ChunkGenerator;findNearestMapFeature(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/level/levelgen/feature/StructureFeature;Lnet/minecraft/core/BlockPos;IZ)Lnet/minecraft/core/BlockPos;"
)
)
public BlockPos levelFindNearestMapFeature(
ChunkGenerator generator,
ServerLevel level,
StructureFeature<?> structureFeature,
BlockPos pPos,
int pRadius,
boolean pSkipExistingChunks
) {
return BlockPos.ZERO;
}
// Start the async locate task here so we have the eye of ender entity for context
@Inject(
method = "use",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/entity/projectile/EyeOfEnder;setItem(Lnet/minecraft/world/item/ItemStack;)V"
),
locals = LocalCapture.CAPTURE_FAILEXCEPTION
)
public void startAsyncLocateTask(
Level pLevel,
Player pPlayer,
InteractionHand pHand,
CallbackInfoReturnable<InteractionResultHolder<ItemStack>> cir,
ItemStack itemstack,
HitResult hitresult,
BlockPos blockpos,
EyeOfEnder eyeofender
) {
EnderEyeItemLogic.locateAsync((ServerLevel)pLevel, pPlayer, eyeofender, (EnderEyeItem) (Object) this);
}
@Redirect(
method = "use",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/entity/projectile/EyeOfEnder;signalTo(Lnet/minecraft/core/BlockPos;)V"
)
)
public void eyeOfEnderSignalTo(EyeOfEnder eyeOfEnder, BlockPos blockpos) {
// Do nothing - we'll do this later if a location is found
}
@Redirect(
method = "use",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/advancements/critereon/UsedEnderEyeTrigger;trigger(Lnet/minecraft/server/level/ServerPlayer;Lnet/minecraft/core/BlockPos;)V"
)
)
public void triggerUsedEnderEyeCriteria(UsedEnderEyeTrigger trigger, ServerPlayer player, BlockPos pos) {
// Do nothing - we'll do this later if a location is found
}
@Redirect(
method = "use",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/entity/player/Player;awardStat(Lnet/minecraft/stats/Stat;)V"
)
)
public void playerAwardStat(Player instance, Stat<?> pStat) {
// Do nothing - we'll do this later if a location is found
}
}

View File

@ -1,63 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.async_locator;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.levelgen.feature.StructureFeature;
import net.minecraft.world.level.saveddata.maps.MapDecoration;
import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.functions.ExplorationMapFunction;
import net.minecraft.world.phys.Vec3;
import org.embeddedt.modernfix.structure.logic.ExplorationMapFunctionLogic;
import org.spongepowered.asm.mixin.Final;
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.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
@Mixin(ExplorationMapFunction.class)
public class ExplorationMapFunctionMixin {
@Shadow
@Final
StructureFeature<?> destination;
@Shadow
@Final
MapDecoration.Type mapDecoration;
@Shadow
@Final
byte zoom;
@Shadow
@Final
int searchRadius;
@Shadow
@Final
boolean skipKnownStructures;
@Inject(
method = "run",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/server/level/ServerLevel;findNearestMapFeature(Lnet/minecraft/world/level/levelgen/feature/StructureFeature;Lnet/minecraft/core/BlockPos;IZ)Lnet/minecraft/core/BlockPos;"
),
locals = LocalCapture.CAPTURE_FAILSOFT,
cancellable = true
)
public void updateMapAsync(
ItemStack pStack,
LootContext pContext,
CallbackInfoReturnable<ItemStack> cir,
Vec3 vec3,
ServerLevel serverlevel
) {
ItemStack mapStack = ExplorationMapFunctionLogic.updateMapAsync(
serverlevel, new BlockPos(vec3), zoom, searchRadius, skipKnownStructures, mapDecoration, destination
);
cir.setReturnValue(mapStack);
}
}

View File

@ -1,11 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.async_locator;
import net.minecraft.world.entity.projectile.EyeOfEnder;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(EyeOfEnder.class)
public interface EyeOfEnderAccess {
@Accessor
void setLife(int life);
}

View File

@ -1,37 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.async_locator;
import net.minecraft.world.entity.projectile.EyeOfEnder;
import org.embeddedt.modernfix.structure.logic.EyeOfEnderData;
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;
@Mixin(EyeOfEnder.class)
public class EyeOfEnderMixin implements EyeOfEnderData {
private boolean locateTaskOngoing = false;
@Override
public void setLocateTaskOngoing(boolean locateTaskOngoing) {
this.locateTaskOngoing = locateTaskOngoing;
}
/*
Intercept EyeOfEnder#tick call and return after the super call if there's an ongoing locate task. This is to
prevent the entity from moving or dying until we have a location result.
*/
@Inject(
method = "tick",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/entity/Entity;tick()V",
shift = At.Shift.AFTER
),
cancellable = true
)
public void skipTick(CallbackInfo ci) {
if (locateTaskOngoing) {
ci.cancel();
}
}
}

View File

@ -1,15 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.async_locator;
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import net.minecraft.server.commands.LocateCommand;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(LocateCommand.class)
public interface LocateCommandAccess {
@Accessor("ERROR_FAILED")
static SimpleCommandExceptionType getErrorFailed() {
throw new UnsupportedOperationException();
}
}

View File

@ -1,34 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.async_locator;
import net.minecraft.commands.CommandSource;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.commands.LocateCommand;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.levelgen.feature.StructureFeature;
import org.embeddedt.modernfix.structure.logic.LocateCommandLogic;
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.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
@Mixin(LocateCommand.class)
public class LocateCommandMixin {
@Inject(
method = "locate",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/server/level/ServerLevel;findNearestMapFeature(Lnet/minecraft/world/level/levelgen/feature/StructureFeature;Lnet/minecraft/core/BlockPos;IZ)Lnet/minecraft/core/BlockPos;"
),
cancellable = true,
locals = LocalCapture.CAPTURE_FAILSOFT
)
private static void findLocationAsync(CommandSourceStack sourceStack, StructureFeature<?> feature, CallbackInfoReturnable<Integer> cir) {
CommandSource source = ((CommandSourceStackAccess) sourceStack).getSource();
if (source instanceof ServerPlayer || source instanceof MinecraftServer) {
LocateCommandLogic.locateAsync(sourceStack, feature);
cir.setReturnValue(0);
}
}
}

View File

@ -1,17 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.async_locator;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.MapItem;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(MapItem.class)
public interface MapItemAccess {
@Invoker
static MapItemSavedData callCreateAndStoreSavedData(ItemStack pStack, Level pLevel, int pX, int pZ, int pScale, boolean pTrackingPosition, boolean pUnlimitedTracking, ResourceKey<Level> pDimension) {
throw new UnsupportedOperationException();
}
}

View File

@ -1,13 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.async_locator;
import net.minecraft.world.item.trading.MerchantOffer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(MerchantOffer.class)
public interface MerchantOfferAccess {
@Mutable
@Accessor
void setMaxUses(int maxUses);
}

View File

@ -1,62 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.async_locator;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.trading.MerchantOffer;
import net.minecraft.world.level.levelgen.feature.StructureFeature;
import net.minecraft.world.level.saveddata.maps.MapDecoration;
import org.embeddedt.modernfix.structure.logic.MerchantLogic;
import org.spongepowered.asm.mixin.Final;
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.CallbackInfoReturnable;
import java.util.Locale;
import java.util.Random;
@Mixin(targets = "net.minecraft.world.entity.npc.VillagerTrades$TreasureMapForEmeralds")
public class TreasureMapForEmeraldsMixin {
@Shadow
@Final
private int emeraldCost;
@Shadow
@Final
private MapDecoration.Type destinationType;
@Shadow
@Final
private int maxUses;
@Shadow
@Final
private int villagerXp;
@Shadow
@Final
private StructureFeature<?> destination;
/*
Intercept TreasureMapForEmeralds#getOffer call right before it calls ServerLevel#findNearestMapFeature to pass
the logic over to an async task. Instead of returning the complete map or null, we'll have to always return an
incomplete filled map and later update it with the details when we have them.
*/
@Inject(
method = "getOffer",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/server/level/ServerLevel;findNearestMapFeature(Lnet/minecraft/world/level/levelgen/feature/StructureFeature;Lnet/minecraft/core/BlockPos;IZ)Lnet/minecraft/core/BlockPos;"
),
cancellable = true
)
public void updateMapAsync(Entity pTrader, Random pRand, CallbackInfoReturnable<MerchantOffer> callbackInfo) {
String displayName = "filled_map." + this.destination.getFeatureName().toLowerCase(Locale.ROOT);
MerchantOffer offer = MerchantLogic.updateMapAsync(
pTrader, emeraldCost, displayName, destinationType, maxUses, villagerXp, destination
);
if (offer != null) {
callbackInfo.setReturnValue(offer);
}
}
}

View File

@ -1,7 +1,7 @@
package org.embeddedt.modernfix.mixin.perf.blast_search_trees;
import mezz.jei.ingredients.IIngredientListElementInfo;
import mezz.jei.ingredients.IngredientFilter;
import mezz.jei.api.ingredients.ITypedIngredient;
import mezz.jei.common.ingredients.IngredientFilter;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
@ -10,5 +10,5 @@ import java.util.List;
@Mixin(IngredientFilter.class)
public interface IngredientFilterInvoker {
@Invoker(remap = false)
List<IIngredientListElementInfo<?>> invokeGetIngredientListUncached(String filterText);
List<ITypedIngredient<?>> invokeGetIngredientListUncached(String filterText);
}

View File

@ -1,25 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.boost_worker_count;
import net.minecraft.Util;
import org.embeddedt.modernfix.ModernFix;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.Constant;
import org.spongepowered.asm.mixin.injection.ModifyConstant;
@Mixin(Util.class)
public class UtilMixin {
@ModifyConstant(method = "makeExecutor", constant = @Constant(intValue = 7))
private static int useHigherThreadCount(int old) {
String requestedMax = System.getProperty("max.bg.threads");
if(requestedMax != null) {
try {
int newMax = Integer.parseInt(requestedMax);
if(newMax >= 1 && newMax <= 255)
return newMax;
} catch(NumberFormatException e) {
ModernFix.LOGGER.error("max.bg.threads is not a number");
}
}
return 255;
}
}

View File

@ -1,148 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.compress_biome_container;
import it.unimi.dsi.fastutil.objects.Reference2ShortMap;
import it.unimi.dsi.fastutil.objects.Reference2ShortOpenHashMap;
import net.minecraft.util.BitStorage;
import net.minecraft.core.IdMap;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.util.Mth;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.chunk.ChunkBiomeContainer;
import net.minecraft.world.level.biome.BiomeSource;
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;
@Mixin(ChunkBiomeContainer.class)
public class MixinBiomeContainer {
@Mutable
@Shadow
@Final
private Biome[] biomes;
@Shadow
@Final
private IdMap<Biome> biomeRegistry;
@Shadow
@Final
private static int WIDTH_BITS;
private Biome[] palette;
private BitStorage intArray;
@Inject(method = "<init>(Lnet/minecraft/core/IdMap;[I)V", at = @At("RETURN"))
private void reinit1(IdMap p_i241970_1_, int[] p_i241970_2_, CallbackInfo ci) {
this.createCompact();
}
@Inject(method = "<init>(Lnet/minecraft/core/IdMap;[Lnet/minecraft/world/level/biome/Biome;)V", at = @At("RETURN"))
private void reinit2(IdMap p_i241971_1_, Biome[] p_i241971_2_, CallbackInfo ci) {
this.createCompact();
}
@Inject(method = "<init>(Lnet/minecraft/core/IdMap;Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/biome/BiomeSource;)V", at = @At("RETURN"))
private void reinit3(IdMap p_i241968_1_, ChunkPos p_i241968_2_, BiomeSource p_i241968_3_, CallbackInfo ci) {
this.createCompact();
}
@Inject(method = "<init>(Lnet/minecraft/core/IdMap;Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/biome/BiomeSource;[I)V", at = @At("RETURN"))
private void reinit4(IdMap p_i241969_1_, ChunkPos p_i241969_2_, BiomeSource p_i241969_3_, int[] p_i241969_4_, CallbackInfo ci) {
this.createCompact();
}
private void createCompact() {
if (this.intArray != null || this.biomes[0] == null) {
return;
}
Reference2ShortOpenHashMap<Biome> paletteTable = this.createPalette();
Biome[] paletteIndexed = new Biome[paletteTable.size()];
for (Reference2ShortMap.Entry<Biome> entry : paletteTable.reference2ShortEntrySet()) {
paletteIndexed[entry.getShortValue()] = entry.getKey();
}
int packedIntSize = Math.max(2, Mth.ceillog2(paletteTable.size()));
BitStorage integerArray = new BitStorage(packedIntSize, ChunkBiomeContainer.BIOMES_SIZE);
Biome prevBiome = null;
short prevId = -1;
for (int i = 0; i < this.biomes.length; i++) {
Biome biome = this.biomes[i];
short id;
if (prevBiome == biome) {
id = prevId;
} else {
id = paletteTable.getShort(biome);
if (id < 0) {
throw new IllegalStateException("Palette is missing entry: " + biome);
}
prevId = id;
prevBiome = biome;
}
integerArray.set(i, id);
}
this.palette = paletteIndexed;
this.intArray = integerArray;
this.biomes = null;
}
private Reference2ShortOpenHashMap<Biome> createPalette() {
Reference2ShortOpenHashMap<Biome> map = new Reference2ShortOpenHashMap<>();
map.defaultReturnValue(Short.MIN_VALUE);
Biome prevObj = null;
short id = 0;
for (Biome obj : this.biomes) {
if (obj == prevObj) {
continue;
}
if (map.getShort(obj) < 0) {
map.put(obj, id++);
}
prevObj = obj;
}
return map;
}
/**
* @author JellySquid
* @reason Use paletted lookup
*/
@Overwrite
public int[] writeBiomes() {
int size = this.intArray.getSize();
int[] array = new int[size];
for(int i = 0; i < size; ++i) {
array[i] = this.biomeRegistry.getId(this.palette[this.intArray.get(i)]);
}
return array;
}
/**
* @author JellySquid
* @reason Use paletted lookup
*/
@Overwrite
public Biome getNoiseBiome(int biomeX, int biomeY, int biomeZ) {
int x = biomeX & ChunkBiomeContainer.HORIZONTAL_MASK;
int y = Mth.clamp(biomeY, 0, ChunkBiomeContainer.VERTICAL_MASK);
int z = biomeZ & ChunkBiomeContainer.HORIZONTAL_MASK;
return this.palette[this.intArray.get(y << WIDTH_BITS + WIDTH_BITS | z << WIDTH_BITS | x)];
}
}

View File

@ -1,15 +1,15 @@
package org.embeddedt.modernfix.mixin.perf.datapack_reload_exceptions;
import net.minecraft.world.level.storage.loot.LootTables;
import org.apache.logging.log4j.Logger;
import org.slf4j.Logger;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(LootTables.class)
public class LootTableManagerMixin {
@Redirect(method = "*(Lnet/minecraft/resources/IResourceManager;Lcom/google/common/collect/ImmutableMap$Builder;Lnet/minecraft/util/ResourceLocation;Lcom/google/gson/JsonElement;)V",
at = @At(value = "INVOKE", target = "Lorg/apache/logging/log4j/Logger;error(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;)V", remap = false))
@Redirect(method = "*(Lnet/minecraft/server/packs/resources/ResourceManager;Lcom/google/common/collect/ImmutableMap$Builder;Lnet/minecraft/resources/ResourceLocation;Lcom/google/gson/JsonElement;)V",
at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;error(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;)V", remap = false))
private void logWithoutStacktrace(Logger instance, String s, Object location, Object exc) {
instance.error(s + ": {}", location, exc.toString());
}

View File

@ -1,14 +1,14 @@
package org.embeddedt.modernfix.mixin.perf.datapack_reload_exceptions;
import net.minecraft.world.item.crafting.RecipeManager;
import org.apache.logging.log4j.Logger;
import org.slf4j.Logger;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(RecipeManager.class)
public class RecipeManagerMixin {
@Redirect(method = "apply(Ljava/util/Map;Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/util/profiling/ProfilerFiller;)V", at = @At(value = "INVOKE", target = "Lorg/apache/logging/log4j/Logger;error(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;)V", remap = false))
@Redirect(method = "apply(Ljava/util/Map;Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/util/profiling/ProfilerFiller;)V", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;error(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;)V", remap = false))
private void silenceException(Logger instance, String s, Object location, Object exc) {
instance.error(s + ": {}", location, exc.toString());
}

View File

@ -52,8 +52,6 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery {
@Shadow @Nullable private AtlasSet atlasSet;
@Shadow @Nullable public abstract BakedModel getBakedModel(ResourceLocation pLocation, ModelState pTransform, Function<Material, TextureAtlasSprite> textureGetter);
@Shadow @Final public static ModelResourceLocation MISSING_MODEL_LOCATION;
@Shadow @Final private Map<Triple<ResourceLocation, Transformation, Boolean>, BakedModel> bakedCache;
@ -136,7 +134,7 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery {
TextureAtlas atlastexture = pair.getFirst();
TextureAtlas.Preparations atlastexture$sheetdata = pair.getSecond();
pResourceManager.register(atlastexture.location(), atlastexture);
pResourceManager.bind(atlastexture.location());
pResourceManager.bindForSetup(atlastexture.location());
atlastexture.updateFilter(atlastexture$sheetdata);
}
pProfiler.pop();

View File

@ -12,8 +12,7 @@ import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
import net.minecraftforge.client.model.ForgeModelBakery;
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
import org.embeddedt.modernfix.models.LazyBakedModel;
import org.spongepowered.asm.mixin.Final;
@ -22,7 +21,6 @@ import org.spongepowered.asm.mixin.Overwrite;
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 org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
@ -44,7 +42,7 @@ public class ModelManagerMixin {
@Shadow @Final private BlockModelShaper blockModelShaper;
@Inject(method = "prepare(Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/util/profiling/ProfilerFiller;)Lnet/minecraft/client/resources/model/ModelBakery;", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/profiling/ProfilerFiller;endTick()V"), locals = LocalCapture.CAPTURE_FAILHARD)
private void fireModelBakeEvent(ResourceManager pResourceManager, ProfilerFiller pProfiler, CallbackInfoReturnable<ModelBakery> cir, ModelLoader pObject) {
private void fireModelBakeEvent(ResourceManager pResourceManager, ProfilerFiller pProfiler, CallbackInfoReturnable<ModelBakery> cir, ForgeModelBakery pObject) {
pProfiler.push("modelevent");
if (this.atlases != null) {
Minecraft.getInstance().executeBlocking(() -> {

View File

@ -1,35 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.nuke_empty_chunk_sections;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.UpgradeData;
import net.minecraft.world.level.TickList;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.chunk.ChunkBiomeContainer;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LevelChunkSection;
import org.spongepowered.asm.mixin.Final;
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.util.function.Consumer;
@Mixin(LevelChunk.class)
public class MixinChunk {
@Shadow @Final private LevelChunkSection[] sections;
@Inject(method = "<init>(Lnet/minecraft/world/level/Level;Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/chunk/ChunkBiomeContainer;Lnet/minecraft/world/level/chunk/UpgradeData;Lnet/minecraft/world/level/TickList;Lnet/minecraft/world/level/TickList;J[Lnet/minecraft/world/level/chunk/LevelChunkSection;Ljava/util/function/Consumer;)V",
at = @At("RETURN"))
private void reinit(Level world, ChunkPos pos, ChunkBiomeContainer container, UpgradeData data,
TickList list1, TickList list2, long inhabited,
LevelChunkSection[] oldSections, Consumer consumer, CallbackInfo ci) {
/* taken from Hydrogen */
for(int i = 0; i < this.sections.length; i++) {
if(LevelChunkSection.isEmpty(this.sections[i])) {
this.sections[i] = null;
}
}
}
}

View File

@ -12,7 +12,7 @@ public class BooleanPropertyMixin {
* There is no point comparing the immutable sets in any two instances of this class, as they will always be
* the same.
*/
@Redirect(method = "equals", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/ImmutableSet;equals(Ljava/lang/Object;)Z", remap = false))
@Redirect(method = "equals", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/ImmutableSet;equals(Ljava/lang/Object;)Z", remap = false), remap = false)
private boolean skipEqualityCheck(ImmutableSet instance, Object object) {
return true;
}

View File

@ -25,7 +25,7 @@ public class PropertyMixin {
* @author embeddedt
* @reason compare hashcodes if generated, use reference equality for speed
*/
@Overwrite
@Overwrite(remap = false)
public boolean equals(Object p_equals_1_) {
if (this == p_equals_1_) {
return true;

View File

@ -17,7 +17,7 @@ public class TransformationMatrixMixin {
* @author embeddedt
* @reason use cached hashcode if exists
*/
@Overwrite
@Overwrite(remap = false)
public int hashCode() {
int hash;
if(cachedHashCode != null) {

View File

@ -1,20 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.preload_block_classes;
import net.minecraftforge.fml.ModLoadingStage;
import net.minecraftforge.registries.GameData;
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.CallbackInfoReturnable;
import java.util.stream.Stream;
import org.embeddedt.modernfix.util.BlockClassPreloader;
@Mixin(GameData.class)
public class GameDataMixin {
@Inject(method = "generateRegistryEvents", at = @At("RETURN"), remap = false)
private static void preloadBlockClasses(CallbackInfoReturnable<Stream<ModLoadingStage.EventGenerator<?>>> cir) {
BlockClassPreloader.preloadClasses();
}
}

View File

@ -1,120 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.resourcepacks;
import com.google.common.base.Joiner;
import net.minecraft.server.packs.PackType;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.packs.ModFileResourcePack;
import org.embeddedt.modernfix.ModernFix;
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.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Mixin(ModFileResourcePack.class)
public abstract class ModFileResourcePackMixin {
@Shadow public abstract Set<String> getNamespaces(PackType type);
@Shadow(remap = false) @Final private ModFile modFile;
private EnumMap<PackType, Set<String>> namespacesByType;
private EnumMap<PackType, HashMap<String, List<Path>>> rootListingByNamespaceAndType;
private Set<String> containedPaths;
private boolean useNamespaceCaches;
private FileSystem resourcePackFS;
private static Joiner slashJoiner = Joiner.on('/');
@Inject(method = "<init>", at = @At("TAIL"))
private void cacheResources(ModFile modFile, CallbackInfo ci) {
this.resourcePackFS = modFile.getLocator().findPath(modFile, "").getFileSystem();
this.useNamespaceCaches = false;
this.namespacesByType = new EnumMap<>(PackType.class);
for(PackType type : PackType.values()) {
this.namespacesByType.put(type, this.getNamespaces(type));
}
this.useNamespaceCaches = true;
this.rootListingByNamespaceAndType = new EnumMap<>(PackType.class);
this.containedPaths = new HashSet<>();
for(PackType type : PackType.values()) {
Set<String> namespaces = this.namespacesByType.get(type);
HashMap<String, List<Path>> rootListingForNamespaces = new HashMap<>();
for(String namespace : namespaces) {
try {
Path root = modFile.getLocator().findPath(modFile, type.getDirectory(), namespace).toAbsolutePath();
try (Stream<Path> stream = Files.walk(root)) {
ArrayList<Path> rootListingPaths = new ArrayList<>();
stream
.map(path -> root.relativize(path.toAbsolutePath()))
.filter(this::isValidCachedResourcePath)
.forEach(path -> {
if(!path.toString().endsWith(".mcmeta"))
rootListingPaths.add(path);
String mergedPath = slashJoiner.join(type.getDirectory(), namespace, path);
this.containedPaths.add(mergedPath);
});
rootListingPaths.trimToSize();
rootListingForNamespaces.put(namespace, rootListingPaths);
}
} catch(IOException e) {
rootListingForNamespaces.put(namespace, Collections.emptyList());
}
}
this.rootListingByNamespaceAndType.put(type, rootListingForNamespaces);
}
}
private boolean isValidCachedResourcePath(Path path) {
String str = path.toString();
for(int i = 0; i < str.length(); i++) {
if(!ResourceLocation.validPathChar(str.charAt(i))) {
return false;
}
}
return true;
}
@Inject(method = "getNamespaces", at = @At("HEAD"), cancellable = true)
private void useCacheForNamespaces(PackType type, CallbackInfoReturnable<Set<String>> cir) {
if(useNamespaceCaches) {
cir.setReturnValue(this.namespacesByType.get(type));
}
}
@Inject(method = "hasResource(Ljava/lang/String;)Z", at = @At(value = "HEAD"), cancellable = true)
private void useCacheForExistence(String path, CallbackInfoReturnable<Boolean> cir) {
cir.setReturnValue(this.containedPaths.contains(path));
}
/**
* @author embeddedt
* @reason Use cached listing of mod resources
*/
@Overwrite
public Collection<ResourceLocation> getResources(PackType type, String resourceNamespace, String pathIn, int maxDepth, Predicate<String> filter)
{
Path inputPath = this.resourcePackFS.getPath(pathIn);
return this.rootListingByNamespaceAndType.get(type).getOrDefault(resourceNamespace, Collections.emptyList()).stream().
filter(path -> path.getNameCount() <= maxDepth). // Make sure the depth is within bounds
filter(path -> path.startsWith(inputPath)). // Make sure the target path is inside this one
filter(path -> filter.test(path.getFileName().toString())). // Test the file name against the predicate
// Finally we need to form the RL, so use the first name as the domain, and the rest as the path
// It is VERY IMPORTANT that we do not rely on Path.toString as this is inconsistent between operating systems
// Join the path names ourselves to force forward slashes
map(path -> new ResourceLocation(resourceNamespace, slashJoiner.join(path))).
collect(Collectors.toList());
}
}

View File

@ -1,76 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.resourcepacks;
import com.google.common.base.Joiner;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.VanillaPackResources;
import net.minecraft.resources.ResourceLocation;
import org.apache.commons.lang3.tuple.Pair;
import org.embeddedt.modernfix.FileWalker;
import org.embeddedt.modernfix.ModernFix;
import org.spongepowered.asm.mixin.Final;
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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import java.io.IOException;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.stream.Stream;
@Mixin(VanillaPackResources.class)
public class VanillaPackMixin {
@Shadow @Final private static Map<PackType, FileSystem> JAR_FILESYSTEM_BY_TYPE;
private static LoadingCache<Pair<Path, Integer>, List<Path>> pathStreamLoadingCache = CacheBuilder.newBuilder()
.build(FileWalker.INSTANCE);
private static Set<String> containedPaths = null;
@Inject(method = "<init>", at = @At("TAIL"))
private void cacheContainedPaths(String[] p_i47912_1_, CallbackInfo ci) {
if(containedPaths != null)
return;
containedPaths = new HashSet<>();
Joiner slashJoiner = Joiner.on('/');
for(PackType type : PackType.values()) {
FileSystem fs = JAR_FILESYSTEM_BY_TYPE.get(type);
if(fs == null)
throw new IllegalStateException("No filesystem for vanilla " + type.name() + " assets");
try {
Path root = fs.getPath(type.getDirectory()).toAbsolutePath();
try(Stream<Path> stream = Files.walk(root)) {
stream
.map(path -> root.relativize(path.toAbsolutePath()))
.forEach(path -> containedPaths.add(slashJoiner.join(type.getDirectory(), path)));
}
} catch(IOException e) {
e.printStackTrace();
}
}
}
@Redirect(method = "getResources(Ljava/util/Collection;ILjava/lang/String;Ljava/nio/file/Path;Ljava/lang/String;Ljava/util/function/Predicate;)V", at = @At(value = "INVOKE", target = "Ljava/nio/file/Files;walk(Ljava/nio/file/Path;I[Ljava/nio/file/FileVisitOption;)Ljava/util/stream/Stream;"))
private static Stream<Path> useCacheForLoading(Path path, int maxDepth, FileVisitOption[] fileVisitOptions) throws IOException {
try {
return pathStreamLoadingCache.get(Pair.of(path, maxDepth)).stream();
} catch (ExecutionException e) {
if(e.getCause() instanceof IOException) /* generally always should be */
throw (IOException)e.getCause();
else
throw new IOException(e);
}
}
@Inject(method = "hasResource", at = @At(value = "INVOKE", target = "Ljava/lang/Class;getResource(Ljava/lang/String;)Ljava/net/URL;"), cancellable = true)
private void useCacheForExistence(PackType type, ResourceLocation location, CallbackInfoReturnable<Boolean> cir) {
cir.setReturnValue(containedPaths.contains(type.getDirectory() + "/" + location.getNamespace() + "/" + location.getPath()));
}
}

View File

@ -1,32 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.skip_first_datapack_reload;
import net.minecraft.client.Minecraft;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.duck.ILevelSave;
import org.embeddedt.modernfix.util.DummyServerConfiguration;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import java.nio.file.Path;
@Mixin(LevelStorageSource.LevelStorageAccess.class)
public class LevelSaveMixin implements ILevelSave {
@Shadow @Final private Path levelPath;
public void runWorldPersistenceHooks(LevelStorageSource format) {
((SaveFormatAccessor)format).invokeReadLevelData(this.levelPath.toFile(), (file, dataFixer) -> {
try {
CompoundTag compoundTag = NbtIo.readCompressed(file);
net.minecraftforge.fml.WorldPersistenceHooks.handleWorldDataLoad((LevelStorageSource.LevelStorageAccess)(Object)this, new DummyServerConfiguration(), compoundTag);
} catch (Exception e) {
ModernFix.LOGGER.error("Exception reading {}", file, e);
}
return null;
});
}
}

View File

@ -1,49 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.skip_first_datapack_reload;
import com.mojang.datafixers.util.Function4;
import net.minecraft.client.Minecraft;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.world.level.DataPackConfig;
import net.minecraft.core.RegistryAccess;
import net.minecraft.world.level.storage.WorldData;
import net.minecraft.world.level.storage.LevelStorageSource;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.ModernFixClient;
import org.embeddedt.modernfix.duck.ILevelSave;
import org.spongepowered.asm.mixin.Final;
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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
@Mixin(Minecraft.class)
public abstract class MinecraftMixin {
@Shadow public abstract Minecraft.ServerStem makeServerStem(RegistryAccess.RegistryHolder dynamicRegistries, Function<LevelStorageSource.LevelStorageAccess, DataPackConfig> worldStorageToDatapackFunction, Function4<LevelStorageSource.LevelStorageAccess, RegistryAccess.RegistryHolder, ResourceManager, DataPackConfig, WorldData> quadFunction, boolean vanillaOnly, LevelStorageSource.LevelStorageAccess worldStorage) throws InterruptedException, ExecutionException;
@Shadow @Final private LevelStorageSource levelSource;
@Redirect(method = "loadLevel(Ljava/lang/String;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/RegistryAccess;builtin()Lnet/minecraft/core/RegistryAccess$RegistryHolder;"))
private RegistryAccess.RegistryHolder useNullRegistry() {
return null;
}
@Redirect(method = "loadWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;makeServerStem(Lnet/minecraft/core/RegistryAccess$RegistryHolder;Ljava/util/function/Function;Lcom/mojang/datafixers/util/Function4;ZLnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;)Lnet/minecraft/client/Minecraft$ServerStem;", ordinal = 0))
private Minecraft.ServerStem skipFirstReload(Minecraft client, RegistryAccess.RegistryHolder dynamicRegistries, Function<LevelStorageSource.LevelStorageAccess, DataPackConfig> worldStorageToDatapackFunction, Function4<LevelStorageSource.LevelStorageAccess, RegistryAccess.RegistryHolder, ResourceManager, DataPackConfig, WorldData> quadFunction, boolean vanillaOnly, LevelStorageSource.LevelStorageAccess levelSave, String worldName, RegistryAccess.RegistryHolder originalRegistries, Function<LevelStorageSource.LevelStorageAccess, DataPackConfig> levelSaveToDatapackFunction, Function4<LevelStorageSource.LevelStorageAccess, RegistryAccess.RegistryHolder, ResourceManager, DataPackConfig, WorldData> quadFunction2, boolean vanillaOnly2, Minecraft.ExperimentalDialogType selectionType, boolean creating) throws InterruptedException, ExecutionException {
if(!creating) {
ModernFix.LOGGER.warn("Skipping first reload, this is still experimental");
ModernFix.runningFirstInjection = true;
((ILevelSave)levelSave).runWorldPersistenceHooks(levelSource);
ModernFix.runningFirstInjection = false;
return null;
} else {
/* allow reload */
return makeServerStem(dynamicRegistries, worldStorageToDatapackFunction, quadFunction, vanillaOnly, levelSave);
}
}
}

View File

@ -1,15 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.skip_first_datapack_reload;
import com.mojang.datafixers.DataFixer;
import net.minecraft.world.level.storage.LevelStorageSource;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
import java.io.File;
import java.util.function.BiFunction;
@Mixin(LevelStorageSource.class)
public interface SaveFormatAccessor {
@Invoker
<T> T invokeReadLevelData(File saveDir, BiFunction<File, DataFixer, T> levelDatReader);
}

View File

@ -3,7 +3,7 @@ package org.embeddedt.modernfix.mixin.perf.thread_priorities;
import com.mojang.authlib.GameProfileRepository;
import com.mojang.authlib.minecraft.MinecraftSessionService;
import net.minecraft.client.Minecraft;
import net.minecraft.server.ServerResources;
import net.minecraft.server.WorldStem;
import net.minecraft.server.packs.repository.PackRepository;
import net.minecraft.client.server.IntegratedServer;
import net.minecraft.server.players.GameProfileCache;
@ -21,9 +21,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(IntegratedServer.class)
public class IntegratedServerMixin {
@Inject(method = "<init>", at = @At("RETURN"))
private void adjustServerPriority(Thread pServerThread, Minecraft pMinecraft, RegistryAccess.RegistryHolder pRegistryHolder, LevelStorageSource.LevelStorageAccess pStorageSource, PackRepository pPackRepository, ServerResources pResources, WorldData pWorldData, MinecraftSessionService pSessionService, GameProfileRepository pProfileRepository, GameProfileCache pProfileCache, ChunkProgressListenerFactory pProgressListenerfactory, CallbackInfo ci) {
private void adjustServerPriority(Thread thread, Minecraft arg, LevelStorageSource.LevelStorageAccess arg2, PackRepository arg3, WorldStem arg4, MinecraftSessionService minecraftSessionService, GameProfileRepository gameProfileRepository, GameProfileCache arg5, ChunkProgressListenerFactory arg6, CallbackInfo ci) {
int pri = ModernFixConfig.INTEGRATED_SERVER_PRIORITY.get();
ModernFix.LOGGER.info("Changing server thread priority to " + pri);
pServerThread.setPriority(pri);
thread.setPriority(pri);
}
}

View File

@ -89,11 +89,6 @@ public class LazyBakedModel implements BakedModel {
return computeDelegate().getOverrides();
}
@Override
public BakedModel getBakedModel() {
return computeDelegate().getBakedModel();
}
@Nonnull
@Override
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, @Nonnull Random rand, @Nonnull IModelData extraData) {
@ -101,8 +96,8 @@ public class LazyBakedModel implements BakedModel {
}
@Override
public boolean isAmbientOcclusion(BlockState state) {
return computeDelegate().isAmbientOcclusion(state);
public boolean useAmbientOcclusion(BlockState state) {
return computeDelegate().useAmbientOcclusion(state);
}
@Override
@ -122,8 +117,8 @@ public class LazyBakedModel implements BakedModel {
}
@Override
public TextureAtlasSprite getParticleTexture(@Nonnull IModelData data) {
return computeDelegate().getParticleTexture(data);
public TextureAtlasSprite getParticleIcon(@Nonnull IModelData data) {
return computeDelegate().getParticleIcon(data);
}
@Override

View File

@ -1,14 +1,15 @@
package org.embeddedt.modernfix.searchtree;
import mezz.jei.Internal;
import mezz.jei.ingredients.IIngredientListElementInfo;
import mezz.jei.ingredients.IngredientFilter;
import mezz.jei.runtime.JeiRuntime;
import mezz.jei.api.ingredients.ITypedIngredient;
import mezz.jei.common.Internal;
import mezz.jei.common.ingredients.IngredientFilter;
import mezz.jei.common.runtime.JeiRuntime;
import net.minecraft.world.item.ItemStack;
import org.embeddedt.modernfix.mixin.perf.blast_search_trees.IngredientFilterInvoker;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* Uses JEI to handle search tree lookups.
@ -23,9 +24,9 @@ public class JEIBackedSearchTree extends DummySearchTree<ItemStack> {
}
@Override
public List<ItemStack> search(String pSearchText) {
JeiRuntime runtime = Internal.getRuntime();
if(runtime != null) {
return this.searchJEI(Internal.getIngredientFilter(), pSearchText);
Optional<JeiRuntime> runtime = Internal.getRuntime();
if(runtime.isPresent()) {
return this.searchJEI((IngredientFilter)runtime.get().getIngredientFilter(), pSearchText);
} else {
/* Use the default, dummy implementation */
return super.search(pSearchText);
@ -35,10 +36,10 @@ public class JEIBackedSearchTree extends DummySearchTree<ItemStack> {
private List<ItemStack> searchJEI(IngredientFilter filter, String pSearchText) {
if(!pSearchText.equals(lastSearchText)) {
listCache.clear();
List<IIngredientListElementInfo<?>> ingredients = ((IngredientFilterInvoker)filter).invokeGetIngredientListUncached(filteringByTag ? ("$" + pSearchText) : pSearchText);
for(IIngredientListElementInfo<?> ingredient : ingredients) {
if(ingredient.getElement().getIngredient() instanceof ItemStack) {
listCache.add((ItemStack)ingredient.getElement().getIngredient());
List<ITypedIngredient<?>> ingredients = ((IngredientFilterInvoker)filter).invokeGetIngredientListUncached(filteringByTag ? ("$" + pSearchText) : pSearchText);
for(ITypedIngredient<?> ingredient : ingredients) {
if(ingredient.getIngredient() instanceof ItemStack) {
listCache.add((ItemStack)ingredient.getIngredient());
}
}
lastSearchText = pSearchText;

View File

@ -1,215 +0,0 @@
package org.embeddedt.modernfix.structure;
import com.mojang.datafixers.util.Pair;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.feature.StructureFeature;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.thread.SidedThreadGroups;
import net.minecraftforge.fml.event.server.FMLServerAboutToStartEvent;
import net.minecraftforge.fml.event.server.FMLServerStoppingEvent;
import org.embeddedt.modernfix.ModernFix;
import org.jetbrains.annotations.NotNull;
import java.sql.Struct;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@Mod.EventBusSubscriber(modid = ModernFix.MODID)
public class AsyncLocator {
private static ExecutorService LOCATING_EXECUTOR_SERVICE = null;
private static final AtomicInteger poolNum = new AtomicInteger(1);
private AsyncLocator() {}
private static void setupExecutorService() {
shutdownExecutorService();
int threads = 1; // very unlikely we need more than one
ModernFix.LOGGER.info("Starting locating executor service with thread pool size of {}", threads);
LOCATING_EXECUTOR_SERVICE = Executors.newFixedThreadPool(
threads,
new ThreadFactory() {
private final AtomicInteger threadNum = new AtomicInteger(1);
private final String namePrefix = "asynclocator-" + poolNum.getAndIncrement() + "-thread-";
@Override
public Thread newThread(@NotNull Runnable r) {
return new Thread(SidedThreadGroups.SERVER, r, namePrefix + threadNum.getAndIncrement());
}
}
);
}
private static void shutdownExecutorService() {
if (LOCATING_EXECUTOR_SERVICE != null) {
ModernFix.LOGGER.info("Shutting down locating executor service");
LOCATING_EXECUTOR_SERVICE.shutdown();
}
}
@SubscribeEvent
public static void handleServerAboutToStartEvent(FMLServerAboutToStartEvent ignoredEvent) {
setupExecutorService();
}
@SubscribeEvent
public static void handleServerStoppingEvent(FMLServerStoppingEvent ignoredEvent) {
shutdownExecutorService();
}
/**
* Queues a task to locate a feature using {@link ServerLevel#findNearestMapFeature(TagKey, BlockPos, int, boolean)}
* and returns a {@link LocateTask} with the futures for it.
*/
public static LocateTask<BlockPos> locateLevel(
ServerLevel level,
Collection<StructureFeature<?>> structure,
BlockPos pos,
int searchRadius,
boolean skipKnownStructures
) {
ModernFix.LOGGER.debug(
"Creating locate task for {} in {} around {} within {} chunks",
structure, level, pos, searchRadius
);
CompletableFuture<BlockPos> completableFuture = new CompletableFuture<>();
Future<?> future = LOCATING_EXECUTOR_SERVICE.submit(
() -> doLocateLevel(completableFuture, level, structure, pos, searchRadius, skipKnownStructures)
);
return new LocateTask<>(level.getServer(), completableFuture, future);
}
/**
* Queues a task to locate a feature using
* {@link ChunkGenerator#findNearestMapFeature(ServerLevel, HolderSet, BlockPos, int, boolean)} and returns a
* {@link LocateTask} with the futures for it.
*/
public static LocateTask<Pair<BlockPos, StructureFeature<?>>> locateChunkGen(
ServerLevel level,
Collection<StructureFeature<?>> structureSet,
BlockPos pos,
int searchRadius,
boolean skipKnownStructures
) {
ModernFix.LOGGER.debug(
"Creating locate task for {} in {} around {} within {} chunks",
structureSet, level, pos, searchRadius
);
CompletableFuture<Pair<BlockPos, StructureFeature<?>>> completableFuture = new CompletableFuture<>();
Future<?> future = LOCATING_EXECUTOR_SERVICE.submit(
() -> doLocateChunkGenerator(completableFuture, level, structureSet, pos, searchRadius, skipKnownStructures)
);
return new LocateTask<>(level.getServer(), completableFuture, future);
}
private static String structureSetToString(Collection<StructureFeature<?>> collection) {
return "[" + collection.stream().map(StructureFeature::getRegistryName).map(ResourceLocation::toString).collect(Collectors.joining(", ")) + "]";
}
private static void doLocateLevel(
CompletableFuture<BlockPos> completableFuture,
ServerLevel level,
Collection<StructureFeature<?>> structureTag,
BlockPos pos,
int searchRadius,
boolean skipExistingChunks
) {
String structures = structureSetToString(structureTag);
ModernFix.LOGGER.debug(
"Trying to locate {} in {} around {} within {} chunks",
structures, level, pos, searchRadius
);
Optional<BlockPos> thePosition = structureTag.stream()
.map(tag -> level.findNearestMapFeature(tag, pos, searchRadius, skipExistingChunks))
.filter(Objects::nonNull)
.findFirst();
if (!thePosition.isPresent())
ModernFix.LOGGER.debug("No {} found", structures);
else
ModernFix.LOGGER.debug("Found {} at {}", structures, thePosition.get());
completableFuture.complete(thePosition.orElse(null));
}
@SuppressWarnings({"rawtypes", "unchecked" })
private static void doLocateChunkGenerator(
CompletableFuture<Pair<BlockPos, StructureFeature<?>>> completableFuture,
ServerLevel level,
Collection<StructureFeature<?>> structureSet,
BlockPos pos,
int searchRadius,
boolean skipExistingChunks
) {
String structures = structureSetToString(structureSet);
ModernFix.LOGGER.debug(
"Trying to locate {} in {} around {} within {} chunks",
structures, level, pos, searchRadius
);
Optional<Pair<BlockPos, StructureFeature>> foundStructure = structureSet.stream()
.map(feature -> Pair.of(level.getChunkSource().getGenerator()
.findNearestMapFeature(level, feature, pos, searchRadius, skipExistingChunks), (StructureFeature)feature))
.filter(pair -> pair.getFirst() != null)
.findFirst();
if (!foundStructure.isPresent())
ModernFix.LOGGER.debug("No {} found", structures);
else
ModernFix.LOGGER.debug("Found {} at {}", structures, foundStructure.get().getFirst());
completableFuture.complete((Pair<BlockPos, StructureFeature<?>>)(Object)foundStructure.orElse(null));
}
/**
* Holder of the futures for an async locate task as well as providing some helper functions.
* The completableFuture will be completed once the call to
* {@link ServerLevel#findNearestMapFeature(TagKey, BlockPos, int, boolean)} has completed, and will hold the
* result of it.
* The taskFuture is the future for the {@link Runnable} itself in the executor service.
*/
public static class LocateTask<T> {
private final MinecraftServer server;
private final CompletableFuture<T> completableFuture;
private final Future<?> taskFuture;
public LocateTask(MinecraftServer server, CompletableFuture<T> completableFuture, Future<?> taskFuture) {
this.server = server;
this.completableFuture = completableFuture;
this.taskFuture = taskFuture;
}
/**
* Helper function that calls {@link CompletableFuture#thenAccept(Consumer)} with the given action.
* Bear in mind that the action will be executed from the task's thread. If you intend to change any game data,
* it's strongly advised you use {@link #thenOnServerThread(Consumer)} instead so that it's queued and executed
* on the main server thread instead.
*/
public LocateTask<T> then(Consumer<T> action) {
completableFuture.thenAccept(action);
return this;
}
/**
* Helper function that calls {@link CompletableFuture#thenAccept(Consumer)} with the given action on the server
* thread.
*/
public LocateTask<T> thenOnServerThread(Consumer<T> action) {
completableFuture.thenAccept(pos -> server.submit(() -> action.accept(pos)));
return this;
}
/**
* Helper function that cancels both completableFuture and taskFuture.
*/
public void cancel() {
taskFuture.cancel(true);
completableFuture.cancel(false);
}
}
}

View File

@ -1,92 +0,0 @@
package org.embeddedt.modernfix.structure.logic;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ChestMenu;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.MapItem;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.ChestBlockEntity;
import net.minecraft.world.level.saveddata.maps.MapDecoration;
import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
import org.embeddedt.modernfix.mixin.perf.async_locator.MapItemAccess;
public class CommonLogic {
private CommonLogic() {}
/**
* Creates an empty "Filled Map", with a hover tooltip name stating that it's locating a feature.
*
* @return The ItemStack
*/
public static ItemStack createEmptyMap() {
ItemStack stack = new ItemStack(Items.FILLED_MAP);
stack.setHoverName(new TranslatableComponent("asynclocator.map.locating"));
return stack;
}
/**
* Updates the map stack with all the given data.
*
* @param mapStack The map ItemStack to update
* @param level The ServerLevel
* @param pos The feature position
* @param scale The map scale
* @param destinationType The map feature type
*/
public static void updateMap(
ItemStack mapStack,
ServerLevel level,
BlockPos pos,
int scale,
MapDecoration.Type destinationType
) {
updateMap(mapStack, level, pos, scale, destinationType, null);
}
/**
* Updates the map stack with all the given data.
*
* @param mapStack The map ItemStack to update
* @param level The ServerLevel
* @param pos The feature position
* @param scale The map scale
* @param destinationType The map feature type
* @param displayName The hover tooltip display name of the ItemStack
*/
public static void updateMap(
ItemStack mapStack,
ServerLevel level,
BlockPos pos,
int scale,
MapDecoration.Type destinationType,
String displayName
) {
MapItemAccess.callCreateAndStoreSavedData(
mapStack, level, pos.getX(), pos.getZ(), scale, true, true, level.dimension()
);
MapItem.renderBiomePreviewMap(level, mapStack);
MapItemSavedData.addTargetDecoration(mapStack, pos, "+", destinationType);
if (displayName != null)
mapStack.setHoverName(new TranslatableComponent(displayName));
}
/**
* Broadcasts slot changes to all players that have the chest container open.
* Won't do anything if the BlockEntity isn't an instance of {@link ChestBlockEntity}.
*/
public static void broadcastChestChanges(ServerLevel level, BlockEntity be) {
if (!(be instanceof ChestBlockEntity))
return;
level.players().forEach(player -> {
AbstractContainerMenu container = player.containerMenu;
if (container instanceof ChestMenu && ((ChestMenu)container).getContainer() == be) {
container.broadcastChanges();
}
});
}
}

View File

@ -1,38 +0,0 @@
package org.embeddedt.modernfix.structure.logic;
import com.google.common.collect.ImmutableSet;
import net.minecraft.advancements.CriteriaTriggers;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.stats.Stats;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.EyeOfEnder;
import net.minecraft.world.item.EnderEyeItem;
import net.minecraft.world.level.levelgen.feature.StructureFeature;
import org.embeddedt.modernfix.mixin.perf.async_locator.EyeOfEnderAccess;
import org.embeddedt.modernfix.structure.AsyncLocator;
public class EnderEyeItemLogic {
private EnderEyeItemLogic() {}
public static void locateAsync(ServerLevel level, Player player, EyeOfEnder eyeOfEnder, EnderEyeItem enderEyeItem) {
AsyncLocator.locateChunkGen(
level,
ImmutableSet.of(StructureFeature.STRONGHOLD),
player.blockPosition(),
100,
false
).thenOnServerThread(pos -> {
((EyeOfEnderData) eyeOfEnder).setLocateTaskOngoing(false);
if (pos != null) {
eyeOfEnder.signalTo(pos.getFirst());
CriteriaTriggers.USED_ENDER_EYE.trigger((ServerPlayer) player, pos.getFirst());
player.awardStat(Stats.ITEM_USED.get(enderEyeItem));
} else {
// Set the entity's life to long enough that it dies
((EyeOfEnderAccess) eyeOfEnder).setLife(Integer.MAX_VALUE - 100);
}
});
((EyeOfEnderData) eyeOfEnder).setLocateTaskOngoing(true);
}
}

View File

@ -1,111 +0,0 @@
package org.embeddedt.modernfix.structure.logic;
import com.google.common.collect.ImmutableSet;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.levelgen.feature.StructureFeature;
import net.minecraft.world.level.saveddata.maps.MapDecoration;
import net.minecraftforge.fml.loading.Java9BackportUtils;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.IItemHandlerModifiable;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.structure.AsyncLocator;
import java.util.function.BiConsumer;
// TODO: Need to test this
public class ExplorationMapFunctionLogic {
private static final int MAX_STACK_SIZE = 64;
private ExplorationMapFunctionLogic() {}
public static void invalidateMap(ItemStack mapStack, ServerLevel level, BlockPos pos) {
handleUpdateMapInChest(mapStack, level, pos, (handler, slot) -> {
if (handler instanceof IItemHandlerModifiable) {
((IItemHandlerModifiable)handler).setStackInSlot(slot, new ItemStack(Items.MAP));
} else {
handler.extractItem(slot, MAX_STACK_SIZE, false);
handler.insertItem(slot, new ItemStack(Items.MAP), false);
}
});
}
public static void updateMap(
ItemStack mapStack,
ServerLevel level,
BlockPos pos,
int scale,
MapDecoration.Type destinationType,
BlockPos invPos
) {
CommonLogic.updateMap(mapStack, level, pos, scale, destinationType);
// Shouldn't need to set the stack in its slot again, as we're modifying the same instance
handleUpdateMapInChest(mapStack, level, invPos, (handler, slot) -> {});
}
public static void handleUpdateMapInChest(
ItemStack mapStack,
ServerLevel level,
BlockPos invPos,
BiConsumer<IItemHandler, Integer> handleSlotFound
) {
BlockEntity be = level.getBlockEntity(invPos);
if (be != null) {
Java9BackportUtils.ifPresentOrElse(be.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY).resolve(),
itemHandler -> {
for (int i = 0; i < itemHandler.getSlots(); i++) {
ItemStack slotStack = itemHandler.getStackInSlot(i);
if (slotStack == mapStack) {
handleSlotFound.accept(itemHandler, i);
CommonLogic.broadcastChestChanges(level, be);
return;
}
}
},
() -> ModernFix.LOGGER.warn(
"Couldn't find item handler capability on chest {} at {}",
be.getClass().getSimpleName(), invPos
)
);
} else {
ModernFix.LOGGER.warn(
"Couldn't find block entity on chest {} at {}",
level.getBlockState(invPos), invPos
);
}
}
public static void handleLocationFound(
ItemStack mapStack,
ServerLevel level,
BlockPos pos,
int scale,
MapDecoration.Type destinationType,
BlockPos invPos
) {
if (pos == null) {
invalidateMap(mapStack, level, invPos);
} else {
updateMap(mapStack, level, pos, scale, destinationType, invPos);
}
}
public static ItemStack updateMapAsync(
ServerLevel level,
BlockPos blockPos,
int scale,
int searchRadius,
boolean skipKnownStructures,
MapDecoration.Type destinationType,
StructureFeature<?> destination
) {
ItemStack mapStack = CommonLogic.createEmptyMap();
AsyncLocator.locateLevel(level, ImmutableSet.of(destination), blockPos, searchRadius, skipKnownStructures)
.thenOnServerThread(pos -> handleLocationFound(mapStack, level, pos, scale, destinationType, blockPos));
return mapStack;
}
}

View File

@ -1,5 +0,0 @@
package org.embeddedt.modernfix.structure.logic;
public interface EyeOfEnderData {
void setLocateTaskOngoing(boolean locateTaskOngoing);
}

View File

@ -1,30 +0,0 @@
package org.embeddedt.modernfix.structure.logic;
import com.google.common.collect.ImmutableSet;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.server.commands.LocateCommand;
import net.minecraft.world.level.levelgen.feature.StructureFeature;
import org.embeddedt.modernfix.mixin.perf.async_locator.LocateCommandAccess;
import org.embeddedt.modernfix.structure.AsyncLocator;
public class LocateCommandLogic {
private LocateCommandLogic() {}
public static void locateAsync(CommandSourceStack sourceStack, StructureFeature<?> feature) {
BlockPos originPos = new BlockPos(sourceStack.getPosition());
AsyncLocator.locateLevel(sourceStack.getLevel(), ImmutableSet.of(feature), originPos, 100, false)
.thenOnServerThread(pair -> {
if (pair != null) {
LocateCommand.showLocateResult(sourceStack, feature.getFeatureName(), originPos, pair, "commands.locate.success");
} else {
sourceStack.sendFailure(
new TextComponent(
LocateCommandAccess.getErrorFailed().create().getMessage()
)
);
}
});
}
}

View File

@ -1,125 +0,0 @@
package org.embeddedt.modernfix.structure.logic;
import com.google.common.collect.ImmutableSet;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.npc.AbstractVillager;
import net.minecraft.world.entity.npc.Villager;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.trading.MerchantOffer;
import net.minecraft.world.level.levelgen.feature.StructureFeature;
import net.minecraft.world.level.saveddata.maps.MapDecoration;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.mixin.perf.async_locator.MerchantOfferAccess;
import org.embeddedt.modernfix.structure.AsyncLocator;
import java.util.Optional;
public class MerchantLogic {
private static final boolean REMOVE_OFFER = false;
private MerchantLogic() {}
public static void invalidateMap(AbstractVillager merchant, ItemStack mapStack) {
mapStack.setHoverName(new TranslatableComponent("asynclocator.map.none"));
Optional<MerchantOffer> offerOptional = merchant.getOffers()
.stream()
.filter(offer -> offer.getResult() == mapStack)
.findFirst();
if(offerOptional.isPresent()) {
removeOffer(merchant, offerOptional.get());
} else {
ModernFix.LOGGER.warn("Failed to find merchant offer for map");
}
}
public static void removeOffer(AbstractVillager merchant, MerchantOffer offer) {
if (REMOVE_OFFER) {
merchant.getOffers().remove(offer);
} else {
((MerchantOfferAccess) offer).setMaxUses(0);
offer.setToOutOfStock();
}
}
public static void handleLocationFound(
ServerLevel level,
AbstractVillager merchant,
ItemStack mapStack,
String displayName,
MapDecoration.Type destinationType,
BlockPos pos
) {
if (pos == null) {
invalidateMap(merchant, mapStack);
} else {
CommonLogic.updateMap(mapStack, level, pos, 2, destinationType, displayName);
}
if (merchant.getTradingPlayer() instanceof ServerPlayer) {
ServerPlayer tradingPlayer = (ServerPlayer)merchant.getTradingPlayer();
tradingPlayer.sendMerchantOffers(
tradingPlayer.containerMenu.containerId,
merchant.getOffers(),
merchant instanceof Villager ? ((Villager)merchant).getVillagerData().getLevel() : 1,
merchant.getVillagerXp(),
merchant.showProgressBar(),
merchant.canRestock()
);
}
}
public static MerchantOffer updateMapAsync(
Entity pTrader,
int emeraldCost,
String displayName,
MapDecoration.Type destinationType,
int maxUses,
int villagerXp,
StructureFeature<?> destination
) {
return updateMapAsyncInternal(
pTrader,
emeraldCost,
maxUses,
villagerXp,
(level, merchant, mapStack) -> AsyncLocator.locateLevel(level, ImmutableSet.of(destination), merchant.blockPosition(), 100, true)
.thenOnServerThread(pos -> handleLocationFound(
level,
merchant,
mapStack,
displayName,
destinationType,
pos
))
);
}
private static MerchantOffer updateMapAsyncInternal(
Entity trader, int emeraldCost, int maxUses, int villagerXp, MapUpdateTask task
) {
if (trader instanceof AbstractVillager) {
AbstractVillager merchant = (AbstractVillager)trader;
ItemStack mapStack = CommonLogic.createEmptyMap();
task.apply((ServerLevel) trader.level, merchant, mapStack);
return new MerchantOffer(
new ItemStack(Items.EMERALD, emeraldCost),
new ItemStack(Items.COMPASS),
mapStack,
maxUses,
villagerXp,
0.2F
);
} else {
return null;
}
}
public interface MapUpdateTask {
void apply(ServerLevel level, AbstractVillager merchant, ItemStack mapStack);
}
}

View File

@ -1,90 +0,0 @@
package org.embeddedt.modernfix.util;
import com.google.common.base.Stopwatch;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.Block;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.ModWorkManager;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.forgespi.language.ModFileScanData;
import org.embeddedt.modernfix.ModernFix;
import org.objectweb.asm.Type;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public class BlockClassPreloader {
public static void preloadClasses() {
Stopwatch stopwatch = Stopwatch.createStarted();
ModernFix.LOGGER.warn("Preparing to preload classes...");
HashMap<Type, Boolean> isABlockClass = new HashMap<>();
isABlockClass.put(Type.getType(BlockBehaviour.class), true);
isABlockClass.put(Type.getType(Block.class), true);
Field selfField, parentField;
List<CompletableFuture> futures = new ArrayList<>();
try {
selfField = ModFileScanData.ClassData.class.getDeclaredField("clazz");
selfField.setAccessible(true);
parentField = ModFileScanData.ClassData.class.getDeclaredField("parent");
parentField.setAccessible(true);
List<ModFileScanData.ClassData> currentCandidates = ModList.get().getModFiles().stream()
.map(ModFileInfo::getFile)
.map(ModFile::getScanResult)
.flatMap(data -> data.getClasses().stream())
.collect(Collectors.toList());
HashSet<Type> blockClasses = new HashSet<>();
blockClasses.add(Type.getType(BlockBehaviour.class));
HashSet<Type> nonBlockClasses = new HashSet<>();
int previousSize = -1;
nonBlockClasses.add(Type.getType(Object.class));
currentCandidates.removeIf(clz -> {
Type self;
try {
self = (Type)selfField.get(clz);
} catch(ReflectiveOperationException e) {
throw new RuntimeException(e);
}
return (nonBlockClasses.contains(self) || blockClasses.contains(self));
});
while(blockClasses.size() > previousSize && currentCandidates.size() > 0) {
previousSize = blockClasses.size();
currentCandidates.removeIf(clz -> {
Type parent, self;
try {
parent = (Type)parentField.get(clz);
self = (Type)selfField.get(clz);
} catch(ReflectiveOperationException e) {
throw new RuntimeException(e);
}
if(nonBlockClasses.contains(parent)) {
nonBlockClasses.add(self);
return true;
} else if(blockClasses.contains(parent)) {
blockClasses.add(self);
futures.add(CompletableFuture.runAsync(() -> {
if(self.getClassName().toLowerCase(Locale.ROOT).contains("mixin"))
return;
try {
Class.forName(self.getClassName());
} catch(Throwable e) {
ModernFix.LOGGER.warn("Couldn't load " + self.getClassName(), e);
}
}, ModWorkManager.parallelExecutor()));
return true;
} else
return false;
});
}
futures.forEach(CompletableFuture::join);
} catch(ReflectiveOperationException e) {
throw new RuntimeException(e);
} finally {
ModernFix.LOGGER.warn("Preloading classes took " + stopwatch.elapsed(TimeUnit.MILLISECONDS)/1000f + " seconds");
stopwatch.stop();
}
}
}

View File

@ -1,30 +0,0 @@
package org.embeddedt.modernfix.util;
import com.mojang.blaze3d.vertex.PoseStack;
import mezz.jei.Internal;
import mezz.jei.api.runtime.IIngredientListOverlay;
import mezz.jei.runtime.JeiRuntime;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Gui;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraftforge.client.event.GuiScreenEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import java.util.function.Supplier;
public class JEIUtil {
private static Supplier<Boolean> isLoading = null;
public static void registerLoadingRenderer(Supplier<Boolean> isLoading) {
JEIUtil.isLoading = isLoading;
MinecraftForge.EVENT_BUS.register(JEIUtil.class);
}
@SubscribeEvent
public static void renderLoad(GuiScreenEvent.DrawScreenEvent.Post event) {
if(isLoading.get()) {
Gui.drawString(new PoseStack(), Minecraft.getInstance().font, new TranslatableComponent("modernfix.jei_load"), 0, 0, 0xffffff);
}
}
}

View File

@ -1,16 +1,11 @@
package org.embeddedt.modernfix.util;
import net.minecraftforge.client.event.ModelBakeEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.EventBus;
import net.minecraftforge.eventbus.api.EventListenerHelper;
import net.minecraftforge.eventbus.api.IEventListener;
import net.minecraftforge.fml.ModContainer;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.ModLoader;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
import org.embeddedt.modernfix.ModernFix;
import java.util.*;

View File

@ -1,16 +1,12 @@
package org.embeddedt.modernfix.util;
import com.google.common.base.Preconditions;
import com.google.common.base.Stopwatch;
import net.minecraftforge.fml.ModContainer;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.ModWorkManager;
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
import net.minecraftforge.forgespi.language.IModInfo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import org.embeddedt.modernfix.ModernFix;
@ -19,8 +15,6 @@ import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@ -36,7 +30,7 @@ public class OrderedParallelModDispatcher {
Set<String> finishedMods = Collections.synchronizedSet(new HashSet<>(modIDsToFilter));
HashMap<String, CompletableFuture<?>> submittedFutures = new HashMap<>();
Semaphore jobWaitingSemaphore = new Semaphore(0);
ArrayList<ModInfo> remainingModList = new ArrayList<>(ModList.get().getMods());
ArrayList<IModInfo> remainingModList = new ArrayList<>(ModList.get().getMods());
while(remainingModList.size() > 0) {
remainingModList.removeIf(modInfo -> {
if(finishedMods.contains(modInfo.getModId()))
@ -56,8 +50,7 @@ public class OrderedParallelModDispatcher {
ModContainer container = modContainerOpt.get();
ModernFix.LOGGER.debug(DISPATCHER, "Submitting job for " + modInfo.getModId());
submittedFutures.put(modInfo.getModId(), CompletableFuture.runAsync(() -> {
Supplier<?> contextExtension = ObfuscationReflectionHelper.getPrivateValue(ModContainer.class, container, "contextExtension");
ModLoadingContext.get().setActiveContainer(container, contextExtension.get());
ModLoadingContext.get().setActiveContainer(container);
try {
task.accept(modInfo.getModId());
} catch(RuntimeException e) {

View File

@ -6,7 +6,7 @@
# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml
modLoader = "javafml" #mandatory
# A version range to match for said mod loader - for regular FML @Mod it will be the forge version
loaderVersion = "[36,)" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions.
loaderVersion = "[40,)" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions.
# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties.
# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here.
license = "GNU LGPL 3.0"
@ -43,7 +43,7 @@ modId = "forge" #mandatory
# Does this dependency have to exist - if not, ordering below must be specified
mandatory = true #mandatory
# The version range of the dependency
versionRange = "[36,)" #mandatory
versionRange = "[40,)" #mandatory
# An ordering relationship for the dependency - BEFORE or AFTER required if the relationship is not mandatory
ordering = "NONE"
# Side this dependency is applied on - BOTH, CLIENT or SERVER
@ -53,13 +53,13 @@ side = "BOTH"
modId = "minecraft"
mandatory = true
# This version range declares a minimum of the current minecraft version up to but not including the next major version
versionRange = "[1.16.5,1.17)"
versionRange = "[1.18.2,1.19)"
ordering = "NONE"
side = "BOTH"
[[dependencies.modernfix]]
modId = "jei"
mandatory = false
# This version range declares a minimum of the current minecraft version up to but not including the next major version
versionRange = "[7.7.1.153,)"
versionRange = "[10,)"
ordering = "BEFORE"
side = "CLIENT"

View File

@ -3,49 +3,27 @@
"minVersion": "0.8",
"package": "org.embeddedt.modernfix.mixin",
"plugin": "org.embeddedt.modernfix.core.ModernFixMixinPlugin",
"compatibilityLevel": "JAVA_8",
"compatibilityLevel": "JAVA_17",
"refmap": "modernfix.refmap.json",
"mixins": [
"bugfix.edge_chunk_not_saved.ChunkManagerMixin",
"bugfix.structure_manager_crash.StructureManagerMixin",
"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.GameDataMixin",
"perf.reduce_blockstate_cache_rebuilds.BlockCallbacksMixin",
"perf.boost_worker_count.UtilMixin",
"perf.thread_priorities.UtilMixin",
"perf.preload_block_classes.GameDataMixin",
"perf.reduce_blockstate_cache_rebuilds.BlocksMixin",
"perf.reduce_blockstate_cache_rebuilds.BlockCallbacksMixin",
"perf.reduce_blockstate_cache_rebuilds.ShapeCacheMixin",
"perf.deduplicate_location.MixinResourceLocation",
"perf.sync_executor_sleep.SyncExecutorMixin",
"perf.compress_biome_container.MixinBiomeContainer",
"perf.nuke_empty_chunk_sections.MixinChunk",
"perf.cache_blockstate_cache_arrays.AbstractBlockStateCacheMixin",
"perf.datapack_reload_exceptions.LootTableManagerMixin",
"perf.datapack_reload_exceptions.RecipeManagerMixin",
"perf.async_locator.CommandSourceStackAccess",
"perf.async_locator.DolphinSwimToTreasureGoalMixin",
"perf.async_locator.EnderEyeItemMixin",
"perf.async_locator.ExplorationMapFunctionMixin",
"perf.async_locator.EyeOfEnderAccess",
"perf.async_locator.EyeOfEnderMixin",
"perf.async_locator.LocateCommandAccess",
"perf.async_locator.LocateCommandMixin",
"perf.async_locator.MapItemAccess",
"perf.async_locator.MerchantOfferAccess",
"perf.async_locator.TreasureMapForEmeraldsMixin",
"feature.measure_time.BootstrapMixin"
],
"client": [
"feature.measure_time.MinecraftMixin",
"feature.reduce_loading_screen_freezes.ModelBakeryMixin",
"perf.skip_first_datapack_reload.MinecraftMixin",
"bugfix.concurrency.RenderTypeMixin",
"bugfix.concurrency.MinecraftMixin",
"perf.parallelize_model_loading.ModelBakeryMixin",
"perf.parallelize_model_loading.OBJLoaderMixin",
@ -55,11 +33,6 @@
"perf.parallelize_model_loading.TransformationMatrixMixin",
"perf.parallelize_model_loading.BooleanPropertyMixin",
"perf.parallelize_model_loading.PropertyMixin",
"perf.async_jei.IngredientListElementFactoryMixin",
"perf.async_jei.ClientLifecycleHandlerMixin",
"perf.async_jei.JeiStarterMixin",
"perf.async_jei.PluginCallerMixin",
"perf.async_jei.RecipeManagerInternalMixin",
"perf.thread_priorities.IntegratedServerMixin",
"safety.BlockColorsMixin",
"perf.flatten_model_predicates.AndConditionMixin",
@ -71,9 +44,7 @@
"perf.faster_baking.BlockModelShapesMixin",
"perf.faster_baking.ModelManagerMixin",
"perf.cache_model_materials.VanillaModelMixin",
"perf.cache_model_materials.MultipartMixin",
"bugfix.packet_leak.ClientPlayNetHandlerMixin",
"bugfix.packet_leak.SCustomPayloadPlayPacketMixin"
"perf.cache_model_materials.MultipartMixin"
],
"injectors": {
"defaultRequire": 1