initial 1.18 port
This commit is contained in:
parent
360eea8b1c
commit
7e2aaabd19
13
build.gradle
13
build.gradle
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
package org.embeddedt.modernfix.jei.async;
|
||||
|
||||
public interface IAsyncJeiStarter {
|
||||
static void checkForLoadInterruption() {
|
||||
if(((JEIReloadThread)Thread.currentThread()).isStopRequested())
|
||||
throw new JEILoadingInterruptedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
package org.embeddedt.modernfix.jei.async;
|
||||
|
||||
public class JEILoadingInterruptedException extends Error {
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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)];
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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(() -> {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
package org.embeddedt.modernfix.structure.logic;
|
||||
|
||||
public interface EyeOfEnderData {
|
||||
void setLocateTaskOngoing(boolean locateTaskOngoing);
|
||||
}
|
||||
|
|
@ -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()
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.*;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user