Merge remote-tracking branch 'origin/1.19.2' into 1.19.4

This commit is contained in:
embeddedt 2023-04-27 13:37:50 -04:00
commit 6c7cd80bb3
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
44 changed files with 696 additions and 948 deletions

3
.gitignore vendored
View File

@ -4,6 +4,9 @@ libs
media
classes/
# Changelog
CHANGELOG.md
# Created by https://www.gitignore.io/api/gradle,intellij,eclipse,windows,osx,linux
### Gradle ###

View File

@ -3,6 +3,8 @@ plugins {
id "maven-publish"
id 'com.matthewprenger.cursegradle' version '1.4.0'
id 'com.palantir.git-version' version '1.0.0'
id 'se.bjurr.gitchangelog.git-changelog-gradle-plugin' version '1.79.0'
id "com.modrinth.minotaur" version "2.+"
}
sourceCompatibility = targetCompatibility = JavaVersion.VERSION_17
@ -63,6 +65,7 @@ repositories {
url "https://maven.tterrag.com/"
}
maven { url "https://maven.shedaniel.me" }
maven { url 'https://maven.blamejared.com' }
}
dependencies {
@ -98,6 +101,8 @@ dependencies {
modRuntimeOnly("curse.maven:ferritecore-429235:4441949")
modCompileOnly("team.chisel.ctm:CTM:${ctm_version}")
modCompileOnly("curse.maven:supermartijncore-454372:4484241")
modCompileOnly("appeng:appliedenergistics2-forge:12.9.4")
modCompileOnly("vazkii.patchouli:Patchouli:1.19.2-77")
}
tasks.withType(JavaCompile) {
@ -115,6 +120,22 @@ tasks.withType(JavaCompile) {
*/
}
task generateChangelog(type: se.bjurr.gitchangelog.plugin.gradle.GitChangelogTask) {
def details = versionDetails();
if(details.commitDistance > 0) {
fromRef = details.lastTag;
} else {
def secondLastTagCmd = "git describe --abbrev=0 " + details.lastTag + "^"
def secondLastTag = secondLastTagCmd.execute().text.trim()
fromRef = secondLastTag;
}
file = new File("CHANGELOG.md");
def otherTemplateContent = new File('gradle/changelog.mustache').getText('UTF-8');
templateContent = "## Changes since " + fromRef + "\n" + otherTemplateContent;
toCommit = "HEAD";
}
java {
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
// if it is present.
@ -160,12 +181,41 @@ curseforge {
apiKey = System.getenv("CURSEFORGE_TOKEN")
project {
id = "790626"
changelog = '[Changelog is not currently available]'
changelog = file('./CHANGELOG.md')
changelogType = "markdown"
releaseType = "beta"
releaseType = "release"
addGameVersion "Forge"
addGameVersion minecraft_version
mainArtifact remapJar
}
}
}
modrinth {
token = System.getenv("MODRINTH_TOKEN")
projectId = "modernfix" // This can be the project ID or the slug. Either will work!
versionType = "release" // This is the default -- can also be `beta` or `alpha`
uploadFile = remapJar
gameVersions = [minecraft_version]
loaders = ["forge"]
changelog.set(provider { file("./CHANGELOG.md").getText('UTF-8') })
}
tasks.register('checkCleanTag') {
doLast {
def details = versionDetails()
if (!details.isCleanTag || versionDetails().commitDistance != 0) {
throw new GradleException('Not a clean tree.')
}
}
}
tasks.curseforge.dependsOn(":checkCleanTag")
tasks.curseforge.dependsOn(":generateChangelog")
tasks.modrinth.dependsOn(":checkCleanTag")
tasks.modrinth.dependsOn(":generateChangelog")
tasks.register('publishToModSites') {
publishToModSites.dependsOn(tasks.modrinth)
publishToModSites.dependsOn(tasks.curseforge)
}

View File

@ -0,0 +1,5 @@
{{#commits}}
{{#ifMatches messageTitle "^(?!Merge).*"}}
* [{{{messageTitle}}}](https://github.com/embeddedt/ModernFix/commit/{{hashFull}}) - {{{authorName}}}
{{/ifMatches}}
{{/commits}}

9
release.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
echo -n "Currently on: "
git describe
echo -n "New version: "
read newtag
git tag -a $newtag -m "$newtag"
git push
git push --tags
./gradlew publishToModSites

View File

@ -27,14 +27,11 @@ import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
import net.minecraftforge.network.NetworkEvent;
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
import org.embeddedt.modernfix.core.config.ModernFixConfig;
import org.embeddedt.modernfix.load.LoadEvents;
import org.embeddedt.modernfix.packet.EntityIDSyncPacket;
import org.embeddedt.modernfix.screen.DeferredLevelLoadingScreen;
import org.embeddedt.modernfix.world.IntegratedWatchdog;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Field;
import java.sql.Ref;
import java.util.*;
import java.util.function.Supplier;
@ -51,9 +48,6 @@ public class ModernFixClient {
public ModernFixClient() {
// clear reserve as it's not needed
MemoryReserve.release();
if(ModernFixMixinPlugin.instance.isOptionEnabled("perf.faster_singleplayer_load.ClientEvents")) {
MinecraftForge.EVENT_BUS.register(new LoadEvents());
}
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.branding.F3Screen")) {
Optional<? extends ModContainer> mfContainer = ModList.get().getModContainerById("modernfix");
if(mfContainer.isPresent())
@ -93,7 +87,6 @@ public class ModernFixClient {
if(event.phase == TickEvent.Phase.END
&& recipesUpdated
&& tagsUpdated
&& !(Minecraft.getInstance().screen instanceof DeferredLevelLoadingScreen)
&& worldLoadStartTime != -1
&& Minecraft.getInstance().player != null
&& numRenderTicks++ >= 10) {

View File

@ -1,59 +0,0 @@
package org.embeddedt.modernfix.agent;
import com.google.common.collect.ImmutableMap;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.*;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.function.Function;
public class Agent {
public static void agentmain(String args, Instrumentation instrumentation) {
instrumentation.addTransformer(new EarlyTransformer());
}
private static class EarlyTransformer implements ClassFileTransformer {
private static final ImmutableMap<String, Function<ClassNode, ClassNode>> TRANSFORMERS = ImmutableMap.<String, Function<ClassNode, ClassNode>>builder()
.put("net/minecraftforge/fml/loading/moddiscovery/Scanner", EarlyTransformer::transformScanner)
.build();
private static ClassNode transformScanner(ClassNode input) {
for(MethodNode method : input.methods) {
if(method.name.equals("fileVisitor")) {
for(int i = 0; i < method.instructions.size(); i++) {
AbstractInsnNode ainsn = method.instructions.get(i);
if(ainsn.getOpcode() == Opcodes.INVOKEVIRTUAL) {
MethodInsnNode minsn = (MethodInsnNode)ainsn;
if(minsn.name.equals("accept") && minsn.owner.equals("org/objectweb/asm/ClassReader")) {
method.instructions.set(minsn.getPrevious(), new LdcInsnNode(ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES));
return input;
}
}
}
}
}
return input;
}
@Override
public byte[] transform(ClassLoader classLoader, String s, Class<?> aClass, ProtectionDomain protectionDomain, byte[] bytes) throws IllegalClassFormatException {
Function<ClassNode, ClassNode> func = TRANSFORMERS.get(s);
if(func != null) {
ClassReader reader = new ClassReader(bytes);
ClassNode node = new ClassNode(Opcodes.ASM9);
reader.accept(node, 0);
node = func.apply(node);
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
node.accept(writer);
return writer.toByteArray();
} else
return bytes;
}
}
}

View File

@ -41,6 +41,9 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin {
this.logger.info("Loaded configuration file for ModernFix: {} options available, {} override(s) found",
config.getOptionCount(), config.getOptionOverrideCount());
if(ModernFixEarlyConfig.OPTIFINE_PRESENT)
this.logger.fatal("OptiFine detected. Use of ModernFix with OptiFine is not supported due to its impact on launch time and breakage of Forge features.");
FastAccessTransformerList.attemptReplace();
DFUBlaster.blastMaps();

View File

@ -15,8 +15,23 @@ public class ModernFixEarlyConfig {
private final Map<String, Option> options = new HashMap<>();
public static final boolean OPTIFINE_PRESENT;
static {
boolean hasOfClass = false;
try {
Class.forName("optifine.OptiFineTransformationService");
hasOfClass = true;
} catch(Throwable e) {
}
OPTIFINE_PRESENT = hasOfClass;
}
private static boolean modPresent(String modId) {
return FMLLoader.getLoadingModList().getModFileById(modId) != null;
if(modId.equals("optifine"))
return OPTIFINE_PRESENT;
else
return FMLLoader.getLoadingModList().getModFileById(modId) != null;
}
private ModernFixEarlyConfig() {
@ -44,14 +59,19 @@ public class ModernFixEarlyConfig {
this.addMixinRule("perf.compress_blockstate", false);
this.addMixinRule("bugfix.concurrency", true);
this.addMixinRule("bugfix.edge_chunk_not_saved", true);
this.addMixinRule("perf.fast_forge_dummies", true);
this.addMixinRule("perf.dynamic_structure_manager", true);
this.addMixinRule("bugfix.chunk_deadlock", true);
this.addMixinRule("perf.thread_priorities", true);
this.addMixinRule("perf.scan_cache", true);
this.addMixinRule("perf.kubejs", modPresent("kubejs"));
this.addMixinRule("perf.flatten_model_predicates", true);
this.addMixinRule("perf.tag_id_caching", true);
this.addMixinRule("perf.deduplicate_location", false);
this.addMixinRule("perf.cache_blockstate_cache_arrays", true);
this.addMixinRule("perf.cache_model_materials", true);
this.addMixinRule("perf.nbt_memory_usage", true);
this.addMixinRule("perf.patchouli_deduplicate_books", modPresent("patchouli"));
this.addMixinRule("perf.datapack_reload_exceptions", true);
this.addMixinRule("perf.faster_texture_stitching", true);
/* off by default in 1.18 because it doesn't work as well */
@ -59,7 +79,6 @@ public class ModernFixEarlyConfig {
/* Keep this off if JEI/REI isn't installed to prevent breaking vanilla gameplay */
this.addMixinRule("perf.blast_search_trees", FMLLoader.getLoadingModList().getModFileById("jei") != null || FMLLoader.getLoadingModList().getModFileById("roughlyenoughitems") != null);
this.addMixinRule("safety", true);
this.addMixinRule("launch.transformer_cache", false);
this.addMixinRule("launch.class_search_cache", true);
boolean isDevEnv = !FMLLoader.isProduction() && FMLLoader.getLoadingModList().getModFileById("modernfix").getFile().getProvider() instanceof ExplodedDirectoryLocator;
this.addMixinRule("devenv", isDevEnv);
@ -71,11 +90,12 @@ public class ModernFixEarlyConfig {
disableIfModPresent("mixin.perf.compress_biome_container", "chocolate", "betterendforge");
disableIfModPresent("mixin.bugfix.mc218112", "performant");
disableIfModPresent("mixin.perf.reuse_datapacks", "tac");
disableIfModPresent("mixin.launch.class_search_cache", "optifine");
}
private void disableIfModPresent(String configName, String... ids) {
for(String id : ids) {
if(FMLLoader.getLoadingModList().getModFileById(id) != null) {
if(modPresent(id)) {
Option option = this.options.get(configName);
if(option != null)
option.addModOverride(false, id);

View File

@ -3,6 +3,7 @@ package org.embeddedt.modernfix.duck;
import com.google.common.collect.ImmutableList;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
@ -11,4 +12,7 @@ import net.minecraft.world.level.block.state.StateDefinition;
public interface IExtendedModelBakery {
ImmutableList<BlockState> getBlockStatesForMRL(StateDefinition<Block, BlockState> stateDefinition, ModelResourceLocation location);
BakedModel bakeDefault(ResourceLocation modelLocation);
BakedModel getBakedMissingModel();
void setBakedMissingModel(BakedModel m);
UnbakedModel mfix$getUnbakedMissingModel();
}

View File

@ -4,6 +4,7 @@ import com.mojang.math.Transformation;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.BlockModelRotation;
import net.minecraft.client.resources.model.BuiltInModel;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.resources.ResourceLocation;
import org.apache.commons.lang3.tuple.Triple;
@ -22,12 +23,19 @@ public class DynamicBakedModelProvider implements Map<ResourceLocation, BakedMod
private final ModelBakery bakery;
private final Map<ModelBakery.BakedCacheKey, BakedModel> bakedCache;
private final Map<ResourceLocation, BakedModel> permanentOverrides;
private BakedModel missingModel;
private static final BakedModel SENTINEL = new BuiltInModel(null, null, null, false);
public DynamicBakedModelProvider(ModelBakery bakery, Map<ModelBakery.BakedCacheKey, BakedModel> cache) {
this.bakery = bakery;
this.bakedCache = cache;
this.permanentOverrides = new Object2ObjectOpenHashMap<>();
}
public void setMissingModel(BakedModel model) {
this.missingModel = model;
}
private static Triple<ResourceLocation, Transformation, Boolean> vanillaKey(Object o) {
return Triple.of((ResourceLocation)o, BlockModelRotation.X0_Y0.getRotation(), false);
}
@ -43,7 +51,7 @@ public class DynamicBakedModelProvider implements Map<ResourceLocation, BakedMod
@Override
public boolean containsKey(Object o) {
return true; //permanentOverrides.containsKey(o) || bakedCache.containsKey(vanillaKey(o));
return permanentOverrides.getOrDefault(o, SENTINEL) != null;
}
@Override
@ -53,8 +61,18 @@ public class DynamicBakedModelProvider implements Map<ResourceLocation, BakedMod
@Override
public BakedModel get(Object o) {
BakedModel model = permanentOverrides.get(o);
return model != null ? model : ((IExtendedModelBakery)bakery).bakeDefault((ResourceLocation)o);
BakedModel model = permanentOverrides.getOrDefault(o, SENTINEL);
if(model != SENTINEL)
return model;
else {
model = ((IExtendedModelBakery)bakery).bakeDefault((ResourceLocation)o);
if(model == missingModel) {
// to correctly emulate the original map, we return null for missing models
permanentOverrides.put((ResourceLocation) o, null);
return null;
} else
return model;
}
}
@Nullable

View File

@ -17,11 +17,13 @@ public class DynamicModelBakeEvent extends Event {
private BakedModel model;
private final UnbakedModel unbakedModel;
private final ModelBaker modelLoader;
public DynamicModelBakeEvent(ResourceLocation location, UnbakedModel unbakedModel, BakedModel model, ModelBaker loader) {
private final ModelBakery modelBakery;
public DynamicModelBakeEvent(ResourceLocation location, UnbakedModel unbakedModel, BakedModel model, ModelBaker loader, ModelBakery bakery) {
this.location = location;
this.model = model;
this.unbakedModel = unbakedModel;
this.modelLoader = loader;
this.modelBakery = bakery;
}
public ResourceLocation getLocation() {
@ -40,6 +42,10 @@ public class DynamicModelBakeEvent extends Event {
return this.modelLoader;
}
public ModelBakery getModelBakery() {
return this.modelBakery;
}
public void setModel(BakedModel model) {
this.model = model;
}

View File

@ -1,113 +0,0 @@
package org.embeddedt.modernfix.load;
import it.unimi.dsi.fastutil.longs.LongIterator;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.LevelLoadingScreen;
import net.minecraft.client.gui.screens.ProgressScreen;
import net.minecraft.client.server.IntegratedServer;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.TicketType;
import net.minecraft.server.level.progress.ChunkProgressListener;
import net.minecraft.util.Unit;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.ForcedChunksSavedData;
import net.minecraftforge.client.event.ScreenEvent;
import net.minecraftforge.common.world.ForgeChunkManager;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.event.server.ServerAboutToStartEvent;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.server.ServerLifecycleHooks;
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
import org.embeddedt.modernfix.screen.DeferredLevelLoadingScreen;
import java.util.concurrent.locks.LockSupport;
import java.util.function.BooleanSupplier;
/**
* Handles deferring the world load screen.
* <p></p>
* TODO: The vanilla check that at least 441 chunks have been loaded does not check whether they are spawn chunks
* or chunks loaded by the player. Consequently it is possible for loading to finish before every spawn chunk has
* been loaded. However the chunk system has at least been warmed up by this point so the remaining chunks load
* reasonably quickly.
*/
public class LoadEvents {
private boolean hasFirstPlayerJoined = false;
@SubscribeEvent
public void serverWillStart(ServerAboutToStartEvent event) {
hasFirstPlayerJoined = false;
}
@SubscribeEvent(priority = EventPriority.LOWEST)
public void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) {
if(!hasFirstPlayerJoined && ModernFixMixinPlugin.instance.isOptionEnabled("perf.faster_singleplayer_load.ClientEvents")) {
hasFirstPlayerJoined = true;
MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
if(server instanceof IntegratedServer) {
handleInitialChunkLoad();
}
}
}
@SubscribeEvent(priority = EventPriority.LOWEST)
public void onWorldShow(ScreenEvent.Opening event) {
if(ServerLifecycleHooks.getCurrentServer() instanceof IntegratedServer) {
if(event.getNewScreen() == null && Minecraft.getInstance().level != null && integratedWorldLoadListener != null) {
/* this means the world is about to be displayed, check if 441 initialized */
ServerChunkCache provider = ServerLifecycleHooks.getCurrentServer().overworld().getChunkSource();
BooleanSupplier worldLoadDone = () -> provider.getTickingGenerated() >= 441;
if(!worldLoadDone.getAsBoolean()) {
DeferredLevelLoadingScreen newScreen = new DeferredLevelLoadingScreen(Minecraft.getInstance().progressListener.get(), worldLoadDone);
event.setNewScreen(newScreen);
}
} else if(event.getNewScreen() instanceof LevelLoadingScreen && Minecraft.getInstance().level == null && ModernFixMixinPlugin.instance.isOptionEnabled("perf.faster_singleplayer_load.ClientEvents")) {
ProgressScreen loadscreen = new ProgressScreen(false);
loadscreen.progressStartNoAbort(Component.translatable("connect.joining"));
event.setNewScreen(loadscreen);
}
}
}
public static ChunkProgressListener integratedWorldLoadListener;
private void handleInitialChunkLoad() {
MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
ServerLevel overworld = server.overworld();
ServerChunkCache provider = overworld.getChunkSource();
provider.getLightEngine().setTaskPerBatch(500);
provider.addRegionTicket(TicketType.START, new ChunkPos(overworld.getSharedSpawnPos()), 11, Unit.INSTANCE);
while(provider.getTickingGenerated() < 441) {
server.runAllTasks();
Thread.yield();
LockSupport.parkNanos("waiting for world load", 100000L);
server.nextTickTime = Util.getMillis() + 10;
}
for(ServerLevel serverworld1 : server.getAllLevels()) {
ForcedChunksSavedData forcedchunkssavedata = serverworld1.getDataStorage().get(ForcedChunksSavedData::load, "chunks");
if (forcedchunkssavedata != null) {
LongIterator longiterator = forcedchunkssavedata.getChunks().iterator();
while(longiterator.hasNext()) {
long i = longiterator.nextLong();
ChunkPos chunkpos = new ChunkPos(i);
serverworld1.getChunkSource().updateChunkForced(chunkpos, true);
}
ForgeChunkManager.reinstatePersistentChunks(serverworld1, forcedchunkssavedata);
}
}
server.runAllTasks();
server.nextTickTime = Util.getMillis() + 10;
provider.getLightEngine().setTaskPerBatch(5);
if(integratedWorldLoadListener != null) {
integratedWorldLoadListener.stop();
integratedWorldLoadListener = null;
}
}
}

View File

@ -1,7 +1,6 @@
package org.embeddedt.modernfix.mixin.devenv;
import com.mojang.text2speech.Narrator;
import com.mojang.text2speech.NarratorDummy;
import net.minecraft.client.GameNarrator;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@ -11,6 +10,6 @@ import org.spongepowered.asm.mixin.injection.Redirect;
public class NarratorMixin {
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lcom/mojang/text2speech/Narrator;getNarrator()Lcom/mojang/text2speech/Narrator;", remap = false))
private Narrator useDummyNarrator() {
return new NarratorDummy();
return Narrator.EMPTY;
}
}

View File

@ -8,6 +8,8 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.common.MinecraftForge;
import org.apache.commons.lang3.tuple.Triple;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
import org.embeddedt.modernfix.dynamicresources.DynamicBakedModelProvider;
import org.embeddedt.modernfix.dynamicresources.DynamicModelBakeEvent;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@ -46,9 +48,18 @@ public abstract class ModelBakerImplMixin {
}
}
if(ibakedmodel == null) {
ibakedmodel = iunbakedmodel.bake((ModelBaker)this, textureGetter, arg2, arg);
IExtendedModelBakery extendedBakery = (IExtendedModelBakery)this.field_40571;
if(iunbakedmodel == extendedBakery.mfix$getUnbakedMissingModel()) {
// use a shared baked missing model
if(extendedBakery.getBakedMissingModel() == null) {
extendedBakery.setBakedMissingModel(iunbakedmodel.bake((ModelBaker)this, textureGetter, arg2, arg));
((DynamicBakedModelProvider)this.field_40571.getBakedTopLevelModels()).setMissingModel(extendedBakery.getBakedMissingModel());
}
ibakedmodel = extendedBakery.getBakedMissingModel();
} else
ibakedmodel = iunbakedmodel.bake((ModelBaker)this, textureGetter, arg2, arg);
}
DynamicModelBakeEvent event = new DynamicModelBakeEvent(arg, iunbakedmodel, ibakedmodel, (ModelBaker)this);
DynamicModelBakeEvent event = new DynamicModelBakeEvent(arg, iunbakedmodel, ibakedmodel, (ModelBaker)this, this.field_40571);
MinecraftForge.EVENT_BUS.post(event);
this.field_40571.bakedCache.put(key, event.getModel());
cir.setReturnValue(event.getModel());

View File

@ -6,6 +6,7 @@ import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalNotification;
import com.google.common.collect.ImmutableList;
import com.mojang.math.Transformation;
import net.minecraft.client.color.block.BlockColors;
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.*;
@ -19,6 +20,7 @@ import org.apache.commons.lang3.tuple.Triple;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.dynamicresources.DynamicBakedModelProvider;
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
import org.objectweb.asm.Opcodes;
import org.slf4j.Logger;
import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.injection.At;
@ -63,14 +65,17 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery {
@Shadow @Final @Mutable private Map<ModelBakery.BakedCacheKey, BakedModel> bakedCache;
@Shadow @Final @Mutable private BlockColors blockColors;
private Cache<ModelBakery.BakedCacheKey, BakedModel> loadedBakedModels;
private Cache<ResourceLocation, UnbakedModel> loadedModels;
private HashMap<ResourceLocation, UnbakedModel> smallLoadingCache = new HashMap<>();
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/profiling/ProfilerFiller;push(Ljava/lang/String;)V", ordinal = 0))
private void replaceTopLevelBakedModels(ProfilerFiller profiler, String arg) {
@Redirect(method = "<init>", at = @At(value = "FIELD", opcode = Opcodes.PUTFIELD, target = "Lnet/minecraft/client/resources/model/ModelBakery;blockColors:Lnet/minecraft/client/color/block/BlockColors;"))
private void replaceTopLevelBakedModels(ModelBakery bakery, BlockColors val) {
this.blockColors = val;
this.loadedBakedModels = CacheBuilder.newBuilder()
.expireAfterAccess(3, TimeUnit.MINUTES)
.maximumSize(1000)
@ -88,7 +93,6 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery {
this.bakedCache = loadedBakedModels.asMap();
this.unbakedCache = loadedModels.asMap();
this.bakedTopLevelModels = new DynamicBakedModelProvider((ModelBakery)(Object)this, bakedCache);
profiler.push(arg);
}
private <K, V> void onModelRemoved(RemovalNotification<K, V> notification) {
@ -276,4 +280,18 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery {
public ImmutableList<BlockState> getBlockStatesForMRL(StateDefinition<Block, BlockState> stateDefinition, ModelResourceLocation location) {
return loadOnlyRelevantBlockState(stateDefinition, location);
}
private BakedModel bakedMissingModel = null;
public void setBakedMissingModel(BakedModel m) {
bakedMissingModel = m;
}
public BakedModel getBakedMissingModel() {
return bakedMissingModel;
}
public UnbakedModel mfix$getUnbakedMissingModel() {
return missingModel;
}
}

View File

@ -0,0 +1,38 @@
package org.embeddedt.modernfix.mixin.perf.dynamic_resources.ae2;
import appeng.core.AppEng;
import appeng.init.client.InitAutoRotatingModel;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraftforge.common.MinecraftForge;
import org.embeddedt.modernfix.dynamicresources.DynamicModelBakeEvent;
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.Map;
import java.util.function.Function;
@Mixin(InitAutoRotatingModel.class)
public class RegistrationMixin {
@Shadow @Final private static Map<String, Function<BakedModel, BakedModel>> CUSTOMIZERS;
@Inject(method = "init", at = @At("TAIL"), remap = false)
private static void doRegisterDynBake(CallbackInfo ci) {
MinecraftForge.EVENT_BUS.addListener(RegistrationMixin::onDynamicModelBake);
}
private static void onDynamicModelBake(DynamicModelBakeEvent event) {
if (!event.getLocation().getNamespace().equals(AppEng.MOD_ID)) {
return;
}
BakedModel missing = event.getModelBakery().getBakedTopLevelModels().get(ModelBakery.MISSING_MODEL_LOCATION);
if(event.getModel() == missing)
return;
Function<BakedModel, BakedModel> customizerFn = CUSTOMIZERS.get(event.getLocation().getPath());
if(customizerFn != null)
event.setModel(customizerFn.apply(event.getModel()));
}
}

View File

@ -0,0 +1,46 @@
package org.embeddedt.modernfix.mixin.perf.fast_forge_dummies;
import com.mojang.serialization.Lifecycle;
import net.minecraft.core.Holder;
import net.minecraft.core.MappedRegistry;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.Opcodes;
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.Map;
import java.util.function.Function;
@Mixin(targets = { "net/minecraftforge/registries/NamespacedWrapper" })
public abstract class NamespacedHolderHelperMixin<T> extends MappedRegistry<T> {
@Shadow private Map<ResourceLocation, Holder.Reference<T>> holdersByName;
public NamespacedHolderHelperMixin(ResourceKey<? extends Registry<T>> arg, Lifecycle lifecycle) {
super(arg, lifecycle);
}
@Inject(method = "freeze", at = @At(value = "FIELD", opcode = Opcodes.GETFIELD, target = "Lnet/minecraftforge/registries/NamespacedWrapper;holdersByName:Ljava/util/Map;"), cancellable = true, remap = false)
private void fastDummyCheck(CallbackInfoReturnable<Registry<T>> cir) {
// Quickly iterate without making any streams, etc. to see if everything is fine
// Use the slow path (by returning without cancelling) when there is an error
for(Holder.Reference<T> ref : this.holdersByName.values()) {
if(!ref.isBound())
return;
}
if (this.unregisteredIntrusiveHolders != null) {
for(Holder.Reference<T> ref : this.unregisteredIntrusiveHolders.values()) {
if(ref.getType() == Holder.Reference.Type.INTRUSIVE && !ref.isBound())
return;
}
}
// Skip the creation of streams
cir.setReturnValue(this);
}
}

View File

@ -1,41 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.faster_singleplayer_load;
import net.minecraft.Util;
import net.minecraft.client.server.IntegratedServer;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.progress.ChunkProgressListener;
import org.embeddedt.modernfix.ModernFixClient;
import org.embeddedt.modernfix.load.LoadEvents;
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(MinecraftServer.class)
public abstract class MinecraftServerMixin {
@Shadow protected long nextTickTime;
@Shadow public abstract ServerLevel overworld();
@Shadow protected abstract void updateMobSpawningFlags();
/**
* @author embeddedt
* @reason defer the 441 chunk load until *after* join game packets are sent to the client, in order to allow
* mods that process advancements, etc. to work on that at the same time
*/
@Inject(method = "prepareLevels", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerLevel;getChunkSource()Lnet/minecraft/server/level/ServerChunkCache;", ordinal = 0), cancellable = true)
private void skipInitialChunkLoad(ChunkProgressListener arg, CallbackInfo ci) {
if(((Object)this) instanceof IntegratedServer) {
ci.cancel();
LoadEvents.integratedWorldLoadListener = arg;
this.nextTickTime = Util.getMillis();
this.overworld().getChunkSource().getLightEngine().setTaskPerBatch(5);
this.updateMobSpawningFlags();
}
}
}

View File

@ -1,32 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.flatten_model_predicates;
import com.google.common.collect.Streams;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.client.renderer.block.model.multipart.AndCondition;
import net.minecraft.client.renderer.block.model.multipart.Condition;
import net.minecraft.world.level.block.state.StateDefinition;
import org.embeddedt.modernfix.predicate.StatePropertyPredicateHelper;
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 java.util.function.Predicate;
import java.util.stream.Collectors;
@Mixin(AndCondition.class)
public class AndConditionMixin {
@Shadow @Final private Iterable<? extends Condition> conditions;
/**
* @author JellySquid
* @reason Flatten predicates
*/
@Overwrite
public Predicate<BlockState> getPredicate(StateDefinition<Block, BlockState> stateManager) {
return StatePropertyPredicateHelper.allMatch(Streams.stream(this.conditions).map((multipartModelSelector) -> {
return multipartModelSelector.getPredicate(stateManager);
}).collect(Collectors.toList()));
}
}

View File

@ -1,32 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.flatten_model_predicates;
import com.google.common.collect.Streams;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.client.renderer.block.model.multipart.Condition;
import net.minecraft.client.renderer.block.model.multipart.OrCondition;
import net.minecraft.world.level.block.state.StateDefinition;
import org.embeddedt.modernfix.predicate.StatePropertyPredicateHelper;
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 java.util.function.Predicate;
import java.util.stream.Collectors;
@Mixin(OrCondition.class)
public class OrConditionMixin {
@Shadow @Final private Iterable<? extends Condition> conditions;
/**
* @author JellySquid
* @reason Flatten predicates
*/
@Overwrite
public Predicate<BlockState> getPredicate(StateDefinition<Block, BlockState> stateManager) {
return StatePropertyPredicateHelper.anyMatch(Streams.stream(this.conditions).map((multipartModelSelector) -> {
return multipartModelSelector.getPredicate(stateManager);
}).collect(Collectors.toList()));
}
}

View File

@ -1,77 +0,0 @@
package org.embeddedt.modernfix.mixin.perf.flatten_model_predicates;
import com.google.common.base.Splitter;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.client.renderer.block.model.multipart.KeyValueCondition;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.block.state.StateDefinition;
import org.embeddedt.modernfix.predicate.single.SingleMatchAny;
import org.embeddedt.modernfix.predicate.single.SingleMatchOne;
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 java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@Mixin(KeyValueCondition.class)
public class PropertyValueConditionMixin {
@Shadow @Final private String key;
@Shadow @Final private String value;
@Shadow @Final private static Splitter PIPE_SPLITTER;
/**
* @author JellySquid
* @reason De-duplication
*/
@Overwrite
public Predicate<BlockState> getPredicate(StateDefinition<Block, BlockState> stateManager) {
Property<?> property = stateManager.getProperty(this.key);
if (property == null) {
throw new RuntimeException(String.format("Unknown property '%s' on '%s'", this.key, stateManager.getOwner().toString()));
}
String valueString = this.value;
boolean negate = !valueString.isEmpty() && valueString.charAt(0) == '!';
if (negate) {
valueString = valueString.substring(1);
}
List<String> split = PIPE_SPLITTER.splitToList(valueString);
if (split.isEmpty()) {
throw new RuntimeException(String.format("Empty value '%s' for property '%s' on '%s'", this.value, this.key, stateManager.getOwner().toString()));
}
Predicate<BlockState> predicate;
if (split.size() == 1) {
predicate = new SingleMatchOne(property, this.getPropertyValue(stateManager, property, valueString));
} else {
predicate = SingleMatchAny.create(property, split.stream()
.map(str -> this.getPropertyValue(stateManager, property, str))
.collect(Collectors.toList()));
}
return negate ? predicate.negate() : predicate;
}
private Object getPropertyValue(StateDefinition<Block, BlockState> stateFactory, Property<?> property, String valueString) {
Object value = property.getValue(valueString)
.orElse(null);
if (value == null) {
throw new RuntimeException(String.format("Unknown value '%s' for property '%s' on '%s' in '%s'",
valueString, this.key, stateFactory.getOwner().toString(), this.value));
}
return value;
}
}

View File

@ -0,0 +1,50 @@
package org.embeddedt.modernfix.mixin.perf.kubejs;
import dev.latvian.mods.kubejs.recipe.RecipesEventJS;
import org.embeddedt.modernfix.ModernFix;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Map;
@Mixin(RecipesEventJS.class)
public class RecipeEventJSMixin {
/**
* The recipe event object can be leaked in scripts and this wastes 40MB of memory.
*/
@Inject(method = "post", at = @At("RETURN"), remap = false)
private void clearRecipeLists(CallbackInfo ci) {
ModernFix.LOGGER.info("Clearing KubeJS recipe lists...");
// Even though we are a mixin class, use reflection so this works across a variety of versions
Field[] fields = RecipesEventJS.class.getDeclaredFields();
for(Field f : fields) {
try {
if(!Modifier.isStatic(f.getModifiers())
&& (Collection.class.isAssignableFrom(f.getType())
|| Map.class.isAssignableFrom(f.getType()))
) {
f.setAccessible(true);
Object collection = f.get(this);
int size;
if(collection instanceof Map) {
size = ((Map<?, ?>)collection).size();
((Map<?, ?>)collection).clear();
} else if(collection instanceof Collection) {
size = ((Collection<?>)collection).size();
((Collection<?>)collection).clear();
} else
throw new IllegalStateException();
ModernFix.LOGGER.debug("Cleared {} with {} entries", f.getName(), size);
}
} catch(RuntimeException | ReflectiveOperationException e) {
ModernFix.LOGGER.debug("Uh oh, couldn't clear field", e);
}
}
}
}

View File

@ -1,22 +1,84 @@
package org.embeddedt.modernfix.mixin.perf.modern_resourcepacks;
import net.minecraft.resources.ResourceLocation;
import com.google.common.base.Joiner;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.LoadingCache;
import net.minecraft.server.packs.PackType;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.VanillaPackResources;
import net.minecraft.server.packs.metadata.pack.PackMetadataSection;
import org.apache.commons.lang3.tuple.Pair;
import org.embeddedt.modernfix.FileWalker;
import org.embeddedt.modernfix.util.FileUtil;
import org.embeddedt.modernfix.util.PackTypeHelper;
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.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.stream.Stream;
@Mixin(VanillaPackResources.class)
public class VanillaPackResourcesMixin {
@Shadow @Final private static Map<PackType, Path> ROOT_DIR_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(PackMetadataSection arg, String[] p_i47912_1_, CallbackInfo ci) {
if(containedPaths != null)
return;
containedPaths = new HashSet<>();
Joiner slashJoiner = Joiner.on('/');
for(PackType type : PackType.values()) {
if(!PackTypeHelper.isVanillaPackType(type))
continue;
Path root = ROOT_DIR_BY_TYPE.get(type);
if(root == null)
throw new IllegalStateException("No filesystem for vanilla " + type.name() + " assets");
try {
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;Ljava/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;[Ljava/nio/file/FileVisitOption;)Ljava/util/stream/Stream;"))
private static Stream<Path> useCacheForLoading(Path path, FileVisitOption[] fileVisitOptions) throws IOException {
try {
return pathStreamLoadingCache.get(Pair.of(path, Integer.MAX_VALUE)).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) {
if(!PackTypeHelper.isVanillaPackType(type))
return;
cir.setReturnValue(containedPaths.contains(type.getDirectory() + "/" + location.getNamespace() + "/" + FileUtil.normalize(location.getPath())));
}
/**
* @author embeddedt

View File

@ -0,0 +1,45 @@
package org.embeddedt.modernfix.mixin.perf.nbt_memory_usage;
import com.google.common.collect.Maps;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import org.embeddedt.modernfix.util.CanonizingStringMap;
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.ModifyArg;
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.Map;
@Mixin(CompoundTag.class)
public class CompoundTagMixin {
@Shadow @Final @Mutable
private Map<String, Tag> tags;
/**
* Ensure that the backing map is always a CanonizingStringMap.
*/
@Redirect(method = "<init>(Ljava/util/Map;)V", at = @At(value = "FIELD", target = "Lnet/minecraft/nbt/CompoundTag;tags:Ljava/util/Map;", ordinal = 0))
private void replaceTagMap(CompoundTag tag, Map<String, Tag> incomingMap) {
if(incomingMap instanceof CanonizingStringMap)
this.tags = incomingMap;
else {
this.tags = new CanonizingStringMap<>();
this.tags.putAll(incomingMap);
}
}
/**
* @author embeddedt
* @reason use more efficient method when copying canonizing string map
*/
@Inject(method = "copy()Lnet/minecraft/nbt/CompoundTag;", at = @At("HEAD"), cancellable = true)
public void copyEfficient(CallbackInfoReturnable<Tag> cir) {
if(this.tags instanceof CanonizingStringMap) {
cir.setReturnValue(new CompoundTag(CanonizingStringMap.deepCopy((CanonizingStringMap<Tag>)this.tags, Tag::copy)));
}
}
}

View File

@ -0,0 +1,70 @@
package org.embeddedt.modernfix.mixin.perf.patchouli_deduplicate_books;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
import org.embeddedt.modernfix.ModernFix;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import vazkii.patchouli.client.book.BookContents;
import vazkii.patchouli.client.book.BookEntry;
import vazkii.patchouli.client.book.BookPage;
import vazkii.patchouli.client.book.ClientBookRegistry;
import vazkii.patchouli.client.book.page.PageTemplate;
import vazkii.patchouli.client.book.template.BookTemplate;
import vazkii.patchouli.client.book.template.TemplateComponent;
import vazkii.patchouli.client.book.template.component.ComponentItemStack;
import vazkii.patchouli.common.book.Book;
import vazkii.patchouli.common.book.BookRegistry;
import java.lang.reflect.Field;
import java.util.List;
@Mixin(ClientBookRegistry.class)
public class ClientBookRegistryMixin {
@Inject(method = "reload", at = @At("RETURN"), remap = false)
private void performDeduplication(CallbackInfo ci) {
Field templateField = ObfuscationReflectionHelper.findField(PageTemplate.class, "template");
Field contentsField = ObfuscationReflectionHelper.findField(Book.class, "contents");
Field componentsField = ObfuscationReflectionHelper.findField(BookTemplate.class, "components");
Field itemsField = ObfuscationReflectionHelper.findField(ComponentItemStack.class, "items");
int numItemsCleared = 0;
for(Book book : BookRegistry.INSTANCE.books.values()) {
try {
BookContents contents = (BookContents)contentsField.get(book);
if(contents == null || contents.entries == null)
continue;
for(BookEntry entry : contents.entries.values()) {
for(BookPage page : entry.getPages()) {
if(page instanceof PageTemplate) {
List<TemplateComponent> components;
BookTemplate template = (BookTemplate) templateField.get(page);
if(template == null)
continue;
components = (List<TemplateComponent>) componentsField.get(template);
if(components == null)
continue;
for (TemplateComponent component : components) {
if (component instanceof ComponentItemStack) {
ItemStack[] items = (ItemStack[]) itemsField.get(component);
if(items == null)
continue;
for (int i = 0; i < items.length; i++) {
if (items[i] != null && items[i].getItem() == Items.AIR) {
numItemsCleared++;
items[i] = ItemStack.EMPTY;
}
}
}
}
}
}
}
} catch(ReflectiveOperationException ignored) {
}
}
ModernFix.LOGGER.info("Cleared {} unneeded book NBT tags", numItemsCleared);
}
}

View File

@ -0,0 +1,30 @@
package org.embeddedt.modernfix.mixin.perf.tag_id_caching;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagEntry;
import net.minecraft.util.ExtraCodecs;
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(TagEntry.class)
public class TagEntryMixin {
@Shadow @Final private boolean tag;
@Shadow @Final private ResourceLocation id;
private ExtraCodecs.TagOrElementLocation cachedLoc;
/**
* @author embeddedt
* @reason use cached location, overwrite rather than inject to avoid allocs
*/
@Overwrite
public ExtraCodecs.TagOrElementLocation elementOrTag() {
ExtraCodecs.TagOrElementLocation loc = cachedLoc;
if(loc == null) {
loc = new ExtraCodecs.TagOrElementLocation(this.id, this.tag);
cachedLoc = loc;
}
return loc;
}
}

View File

@ -0,0 +1,29 @@
package org.embeddedt.modernfix.mixin.perf.tag_id_caching;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.ExtraCodecs;
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(ExtraCodecs.TagOrElementLocation.class)
public class TagOrElementLocationMixin {
@Shadow @Final private boolean tag;
@Shadow @Final private ResourceLocation id;
private String cachedDecoratedId;
/**
* @author embeddedt
* @reason use cached ID, overwrite rather than inject to avoid allocs
*/
@Overwrite
public String decoratedId() {
String id = cachedDecoratedId;
if(id == null) {
id = this.tag ? "#" + this.id : this.id.toString();
cachedDecoratedId = id;
}
return id;
}
}

View File

@ -1,22 +0,0 @@
package org.embeddedt.modernfix.predicate;
import java.util.function.Predicate;
public class AllPredicate<T> implements Predicate<T> {
private final Predicate<T>[] predicates;
public AllPredicate(Predicate<T>[] predicates) {
this.predicates = predicates;
}
@Override
public boolean test(T t) {
for (Predicate<T> predicate : this.predicates) {
if (!predicate.test(t)) {
return false;
}
}
return true;
}
}

View File

@ -1,22 +0,0 @@
package org.embeddedt.modernfix.predicate;
import java.util.function.Predicate;
public class AnyPredicate<T> implements Predicate<T> {
private final Predicate<T>[] predicates;
public AnyPredicate(Predicate<T>[] predicates) {
this.predicates = predicates;
}
@Override
public boolean test(T t) {
for (Predicate<T> predicate : this.predicates) {
if (predicate.test(t)) {
return true;
}
}
return false;
}
}

View File

@ -1,7 +0,0 @@
package org.embeddedt.modernfix.predicate;
/**
* Calculates the
*/
public class CachedModelPredicate {
}

View File

@ -1,33 +0,0 @@
package org.embeddedt.modernfix.predicate;
import net.minecraft.world.level.block.state.BlockState;
import org.embeddedt.modernfix.predicate.all.AllMatchOneBoolean;
import org.embeddedt.modernfix.predicate.all.AllMatchOneObject;
import org.embeddedt.modernfix.predicate.any.AllMatchAnyObject;
import org.embeddedt.modernfix.predicate.single.SingleMatchAny;
import org.embeddedt.modernfix.predicate.single.SingleMatchOne;
import java.util.List;
import java.util.function.Predicate;
public class StatePropertyPredicateHelper {
@SuppressWarnings("unchecked")
public static Predicate<BlockState> allMatch(List<Predicate<BlockState>> predicates) {
if (SingleMatchOne.areOfType(predicates)) {
if (SingleMatchOne.valuesMatchType(predicates, Boolean.class)) {
return new AllMatchOneBoolean(predicates);
}
return new AllMatchOneObject(predicates);
} else if (SingleMatchAny.areOfType(predicates)) {
return new AllMatchAnyObject(predicates);
}
return new AllPredicate<>(predicates.toArray(new Predicate[0]));
}
@SuppressWarnings("unchecked")
public static Predicate<BlockState> anyMatch(List<Predicate<BlockState>> predicates) {
return new AnyPredicate<>(predicates.toArray(new Predicate[0]));
}
}

View File

@ -1,47 +0,0 @@
package org.embeddedt.modernfix.predicate.all;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.Property;
import org.embeddedt.modernfix.predicate.single.SingleMatchOne;
import java.util.List;
import java.util.function.Predicate;
public class AllMatchOneBoolean implements Predicate<BlockState> {
private final Property<?>[] properties;
private final boolean[] values;
public AllMatchOneBoolean(List<Predicate<BlockState>> list) {
int size = list.size();
this.properties = new Property[size];
this.values = new boolean[size];
for (int i = 0; i < size; i++) {
SingleMatchOne predicate = (SingleMatchOne) list.get(i);
this.properties[i] = predicate.property;
this.values[i] = (boolean) predicate.value;
}
}
public static boolean canReplace(List<Predicate<BlockState>> list) {
return list.stream()
.allMatch(p -> {
return p instanceof SingleMatchOne && ((SingleMatchOne) p).value instanceof Boolean;
});
}
@Override
public boolean test(BlockState blockState) {
for (int i = 0; i < this.properties.length; i++) {
Boolean value = (Boolean) blockState.getValue(this.properties[i]);
if (value != this.values[i]) {
return false;
}
}
return true;
}
}

View File

@ -1,38 +0,0 @@
package org.embeddedt.modernfix.predicate.all;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.Property;
import org.embeddedt.modernfix.predicate.single.SingleMatchOne;
import java.util.List;
import java.util.function.Predicate;
public class AllMatchOneObject implements Predicate<BlockState> {
private final Property<?>[] properties;
private final Object[] values;
public AllMatchOneObject(List<Predicate<BlockState>> list) {
int size = list.size();
this.properties = new Property[size];
this.values = new Object[size];
for (int i = 0; i < size; i++) {
SingleMatchOne predicate = (SingleMatchOne) list.get(i);
this.properties[i] = predicate.property;
this.values[i] = predicate.value;
}
}
@Override
public boolean test(BlockState blockState) {
for (int i = 0; i < this.properties.length; i++) {
if (blockState.getValue(this.properties[i]) != this.values[i]) {
return false;
}
}
return true;
}
}

View File

@ -1,39 +0,0 @@
package org.embeddedt.modernfix.predicate.any;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.Property;
import org.apache.commons.lang3.ArrayUtils;
import org.embeddedt.modernfix.predicate.single.SingleMatchAny;
import java.util.List;
import java.util.function.Predicate;
public class AllMatchAnyObject implements Predicate<BlockState> {
private final Property<?>[] properties;
private final Object[][] values;
public AllMatchAnyObject(List<Predicate<BlockState>> list) {
int size = list.size();
this.properties = new Property[size];
this.values = new Object[size][];
for (int i = 0; i < size; i++) {
SingleMatchAny predicate = (SingleMatchAny) list.get(i);
this.properties[i] = predicate.property;
this.values[i] = predicate.values;
}
}
@Override
public boolean test(BlockState blockState) {
for (int i = 0; i < this.properties.length; i++) {
if (!ArrayUtils.contains(this.values[i], blockState.getValue(this.properties[i]))) {
return false;
}
}
return true;
}
}

View File

@ -1,63 +0,0 @@
package org.embeddedt.modernfix.predicate.single;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.Property;
import org.apache.commons.lang3.ArrayUtils;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
public class SingleMatchAny implements Predicate<BlockState> {
public static final ObjectOpenHashSet<SingleMatchAny> PREDICATES = new ObjectOpenHashSet<>();
public final Property<?> property;
public final Object[] values;
private SingleMatchAny(Property<?> property, List<Object> values) {
this.property = property;
this.values = values.toArray();
}
public static SingleMatchAny create(Property<?> property, List<Object> values) {
return PREDICATES.addOrGet(new SingleMatchAny(property, values));
}
public static boolean areOfType(List<Predicate<BlockState>> predicates) {
return predicates.stream()
.allMatch(p -> {
return p instanceof SingleMatchAny;
});
}
public static boolean valuesMatchType(List<Predicate<BlockState>> predicates, Class<?> type) {
return predicates.stream()
.allMatch(p -> {
return p instanceof SingleMatchAny &&
Arrays.stream(((SingleMatchAny) p).values).allMatch(t -> type.isInstance(p));
});
}
@Override
public boolean test(BlockState blockState) {
return ArrayUtils.contains(this.values, blockState.getValue(this.property));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SingleMatchAny that = (SingleMatchAny) o;
return Objects.equals(property, that.property) &&
Arrays.equals(values, that.values);
}
@Override
public int hashCode() {
int result = Objects.hash(property);
result = 31 * result + Arrays.hashCode(values);
return result;
}
}

View File

@ -1,36 +0,0 @@
package org.embeddedt.modernfix.predicate.single;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.Property;
import java.util.List;
import java.util.function.Predicate;
public class SingleMatchOne implements Predicate<BlockState> {
public final Property<?> property;
public final Object value;
public SingleMatchOne(Property<?> property, Object value) {
this.property = property;
this.value = value;
}
public static boolean areOfType(List<Predicate<BlockState>> predicates) {
return predicates.stream()
.allMatch(p -> {
return p instanceof SingleMatchOne;
});
}
public static boolean valuesMatchType(List<Predicate<BlockState>> predicates, Class<?> type) {
return predicates.stream()
.allMatch(p -> {
return p instanceof SingleMatchOne && type.isInstance(((SingleMatchOne) p).value);
});
}
@Override
public boolean test(BlockState blockState) {
return blockState.getValue(this.property) == this.value;
}
}

View File

@ -1,59 +0,0 @@
package org.embeddedt.modernfix.registry;
import com.google.common.base.Stopwatch;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.fml.ModWorkManager;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.util.AsyncStopwatch;
import org.embeddedt.modernfix.util.CachedSupplier;
import org.embeddedt.modernfix.util.OrderedParallelModDispatcher;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
public class DeferredRegisterBaker {
private static final HashMap<ResourceLocation, HashMap<String, List<CachedSupplier<?>>>> supplierMap = new HashMap<>();
public static <T> Supplier<T> cacheForComputationLater(ResourceLocation registry, String modid, Supplier<T> supplier) {
synchronized (supplierMap) {
HashMap<String, List<CachedSupplier<?>>> registrySupplierMap = supplierMap.computeIfAbsent(registry, reg -> new HashMap<>());
List<CachedSupplier<?>> modSupplierList = registrySupplierMap.computeIfAbsent(modid, id -> new ArrayList<>());
CachedSupplier<T> cacher = new CachedSupplier<>(supplier);
modSupplierList.add(cacher);
return cacher;
}
}
public static void bakeSuppliers(ResourceLocation registry) {
synchronized (supplierMap) {
Set<String> modErrors = Collections.synchronizedSet(new HashSet<>());
HashMap<String, List<CachedSupplier<?>>> registrySupplierMap = supplierMap.get(registry);
if(registrySupplierMap == null)
return;
ModernFix.LOGGER.info("Caching suppliers for " + registry);
Stopwatch realtimeStopwatch = Stopwatch.createStarted();
AsyncStopwatch cpuStopwatch = new AsyncStopwatch();
OrderedParallelModDispatcher.dispatchBlocking(ModWorkManager.parallelExecutor(), modId -> {
List<CachedSupplier<?>> suppliersToCompute = registrySupplierMap.get(modId);
if (suppliersToCompute == null || suppliersToCompute.size() == 0) {
return;
}
cpuStopwatch.startMeasuringAsync();
for (CachedSupplier<?> supplier : suppliersToCompute) {
try {
supplier.compute();
} catch(RuntimeException e) {
ModernFix.LOGGER.debug("Exception encountered while caching supplier", e);
modErrors.add(modId);
}
}
cpuStopwatch.stopMeasuringAsync();
});
realtimeStopwatch.stop();
if(modErrors.size() > 0)
ModernFix.LOGGER.warn("The following mods had errors while caching " + registry + " suppliers (this is likely safe): [" + String.join(", ", modErrors) + "]");
ModernFix.LOGGER.info("CPU time spent constructing " + registry + " suppliers: " + cpuStopwatch.getCpuTime()/1000f + " seconds");
ModernFix.LOGGER.info("Real time spent constructing " + registry + " suppliers: " + realtimeStopwatch.elapsed(TimeUnit.MILLISECONDS)/1000f + " seconds");
}
}
}

View File

@ -1,28 +0,0 @@
package org.embeddedt.modernfix.screen;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.gui.screens.LevelLoadingScreen;
import net.minecraft.server.level.progress.StoringChunkProgressListener;
import org.jetbrains.annotations.NotNull;
import java.util.function.BooleanSupplier;
public class DeferredLevelLoadingScreen extends LevelLoadingScreen {
private final BooleanSupplier worldLoadFinished;
public DeferredLevelLoadingScreen(StoringChunkProgressListener arg, BooleanSupplier worldLoadFinished) {
super(arg);
this.worldLoadFinished = worldLoadFinished;
}
@Override
public void tick() {
super.tick();
if(this.worldLoadFinished.getAsBoolean())
this.onClose();
}
@Override
public void renderBackground(@NotNull PoseStack arg) {
renderDirtBackground(arg);
}
}

View File

@ -1,45 +0,0 @@
package org.embeddedt.modernfix.util;
import com.google.common.base.Stopwatch;
import org.embeddedt.modernfix.ModernFix;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
public class AsyncStopwatch {
private final AtomicLong cpuTimeMs = new AtomicLong(0);
private final ThreadLocal<Stopwatch> threadStopwatch = ThreadLocal.withInitial(Stopwatch::createUnstarted);
public void startMeasuringAsync() {
threadStopwatch.get().start();
}
public void stopMeasuringAsync() {
Stopwatch watch = threadStopwatch.get();
watch.stop();
long elapsed = watch.elapsed(TimeUnit.MILLISECONDS);
cpuTimeMs.addAndGet(elapsed);
watch.reset();
}
public void ensureStoppedAsync() {
Stopwatch watch = threadStopwatch.get();
if(watch.isRunning())
stopMeasuringAsync();
}
public long getCpuTime() {
return cpuTimeMs.get();
}
public static void measureAndLogSerialRunningTime(String label, Runnable runnable) {
ModernFix.LOGGER.info(label + "...");
Stopwatch stopwatch = Stopwatch.createStarted();
try {
runnable.run();
ModernFix.LOGGER.info(label + " took " + stopwatch.elapsed(TimeUnit.MILLISECONDS)/1000f + " seconds");
} finally {
stopwatch.stop();
}
}
}

View File

@ -1,33 +0,0 @@
package org.embeddedt.modernfix.util;
import java.util.function.Supplier;
/**
* An implementation of Supplier that allows separating the time at which the value is computed from when it is
* retrieved.
*/
public class CachedSupplier<T> implements Supplier<T> {
private T value = null;
private boolean hasBeenComputed;
private final Supplier<T> delegate;
public CachedSupplier(Supplier<T> delegate) {
this.delegate = delegate;
}
public synchronized void compute() {
this.value = this.delegate.get();
this.hasBeenComputed = true;
}
@Override
public synchronized T get() {
if(this.hasBeenComputed) {
this.hasBeenComputed = false;
return this.value;
} else {
return this.delegate.get();
}
}
}

View File

@ -0,0 +1,150 @@
package org.embeddedt.modernfix.util;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
import it.unimi.dsi.fastutil.objects.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.Function;
/**
* Replacement backing map for CompoundTags. Uses an array map for tags with 4 or less entries,
* and a hash map for larger tags.
*/
public class CanonizingStringMap<T> implements Map<String, T> {
private Object2ObjectMap<String, T> backingMap;
private static final int GROWTH_THRESHOLD = 4;
private static final Interner<String> KEY_INTERNER = Interners.newStrongInterner();
public CanonizingStringMap() {
this(new Object2ObjectArrayMap<>());
}
protected CanonizingStringMap(Object2ObjectMap<String, T> newMap) {
this.backingMap = newMap;
}
@Override
public int size() {
return backingMap.size();
}
@Override
public boolean isEmpty() {
return backingMap.isEmpty();
}
@Override
public boolean containsKey(Object o) {
return backingMap.containsKey(o);
}
@Override
public boolean containsValue(Object o) {
return backingMap.containsValue(o);
}
@Override
public T get(Object o) {
return backingMap.get(o);
}
@Nullable
@Override
public T put(String s, T t) {
if(backingMap.size() >= GROWTH_THRESHOLD && !(backingMap instanceof Object2ObjectOpenHashMap) && !backingMap.containsKey(s)) {
// map will grow to GROWTH_THRESHOLD + 1 entries, change to hashmap
backingMap = new Object2ObjectOpenHashMap<>(backingMap);
}
s = KEY_INTERNER.intern(s);
return backingMap.put(s, t);
}
@Override
public T remove(Object o) {
T value = backingMap.remove(o);
// need to shrink to be consistent with new maps
if(backingMap.size() <= GROWTH_THRESHOLD && backingMap instanceof Object2ObjectOpenHashMap) {
backingMap = new Object2ObjectArrayMap<>(backingMap);
}
return value;
}
@Override
public void putAll(@NotNull Map<? extends String, ? extends T> map) {
if(map.size() == 0)
return;
map.forEach((String key, T val) -> {
key = KEY_INTERNER.intern(key);
backingMap.put(key, val);
});
// if it's too big to be an array, grow it
if(backingMap.size() > GROWTH_THRESHOLD && !(backingMap instanceof Object2ObjectOpenHashMap)) {
backingMap = new Object2ObjectOpenHashMap<>(backingMap);
}
}
@Override
public void clear() {
if(!(this.backingMap instanceof Object2ObjectArrayMap))
this.backingMap = new Object2ObjectArrayMap<>();
else
this.backingMap.clear();
}
@NotNull
@Override
public Set<String> keySet() {
return Collections.unmodifiableSet(this.backingMap.keySet());
}
@NotNull
@Override
public Collection<T> values() {
return Collections.unmodifiableCollection(this.backingMap.values());
}
@NotNull
@Override
public Set<Entry<String, T>> entrySet() {
return Collections.unmodifiableSet(this.backingMap.entrySet());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CanonizingStringMap<?> that = (CanonizingStringMap<?>)o;
if(that.backingMap.size() != backingMap.size())
return false;
return backingMap.object2ObjectEntrySet().containsAll(that.backingMap.object2ObjectEntrySet());
}
/**
* We deliberately use a hashcode that will be consistent regardless of underlying map type.
*/
@Override
public int hashCode() {
final ObjectIterator<Object2ObjectMap.Entry<String, T>> i = Object2ObjectMaps.fastIterator(backingMap);
int h = 0, n = backingMap.size();
while (n-- != 0)
h += i.next().hashCode();
return h;
}
public static <T> CanonizingStringMap<T> deepCopy(CanonizingStringMap<T> inputMap, Function<T, T> deepCopier) {
Object2ObjectMap<String, T> copiedBackingMap;
int size = inputMap.backingMap.size();
if(size > GROWTH_THRESHOLD) {
copiedBackingMap = new Object2ObjectOpenHashMap<>(size);
} else
copiedBackingMap = new Object2ObjectArrayMap<>(size);
inputMap.backingMap.object2ObjectEntrySet().forEach(entry -> {
copiedBackingMap.put(entry.getKey(), deepCopier.apply(entry.getValue()));
});
return new CanonizingStringMap<>(copiedBackingMap);
}
}

View File

@ -1,90 +0,0 @@
package org.embeddedt.modernfix.util;
import com.google.common.base.Preconditions;
import net.minecraftforge.fml.ModContainer;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
import net.minecraftforge.forgespi.language.IModInfo;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import org.embeddedt.modernfix.ModernFix;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* Iterates over all mods in the game, parallelizing where possible while preserving dependency ordering.
*
* Can also be given a list of mods to skip.
*/
public class OrderedParallelModDispatcher {
private static final Marker DISPATCHER = MarkerManager.getMarker("OrderedParallelModDispatcher");
public static void dispatchBlocking(Executor executor, Consumer<String> task, Collection<String> modIDsToFilter) {
Set<String> finishedMods = Collections.synchronizedSet(new HashSet<>(modIDsToFilter));
HashMap<String, CompletableFuture<?>> submittedFutures = new HashMap<>();
Semaphore jobWaitingSemaphore = new Semaphore(0);
ArrayList<IModInfo> remainingModList = new ArrayList<>(ModList.get().getMods());
while(remainingModList.size() > 0) {
remainingModList.removeIf(modInfo -> {
if(finishedMods.contains(modInfo.getModId()))
return true;
List<String> missingDependencies = modInfo.getDependencies().stream()
.filter(IModInfo.ModVersion::isMandatory)
.map(IModInfo.ModVersion::getModId)
.filter(modId -> !finishedMods.contains(modId))
.collect(Collectors.toList());
if(missingDependencies.size() > 0) {
ModernFix.LOGGER.debug(DISPATCHER, "Cannot process " + modInfo.getModId() + ", as it is waiting on mods: [" + String.join(", ", missingDependencies) + "]");
return false;
}
Optional<? extends ModContainer> modContainerOpt = ModList.get().getModContainerById(modInfo.getModId());
if(!modContainerOpt.isPresent())
throw new IllegalStateException("Can't find mod container");
ModContainer container = modContainerOpt.get();
ModernFix.LOGGER.debug(DISPATCHER, "Submitting job for " + modInfo.getModId());
submittedFutures.put(modInfo.getModId(), CompletableFuture.runAsync(() -> {
ModLoadingContext.get().setActiveContainer(container);
try {
task.accept(modInfo.getModId());
} catch(RuntimeException e) {
e.printStackTrace();
}
/*
* We cannot rely on the main thread to correctly mark us as done, as it might start running
* before the future is marked as complete. So we add the mod to the finished set ourselves.
*/
finishedMods.add(modInfo.getModId());
jobWaitingSemaphore.release();
//ModLoadingContext.get().setActiveContainer(null, null);
}, executor));
return true;
});
Preconditions.checkState(submittedFutures.size() > 0, "The semaphore will block forever!");
ModernFix.LOGGER.debug(DISPATCHER, "Waiting for one of [" + String.join(", ", submittedFutures.keySet()) + "] to finish...");
try {
jobWaitingSemaphore.acquire();
} catch(InterruptedException e) {
throw new RuntimeException("Unexpected interruption", e);
}
submittedFutures.entrySet().removeIf(entry -> {
if(entry.getValue().isDone()) {
ModernFix.LOGGER.debug(DISPATCHER, "Job finished for " + entry.getKey());
return true;
}
return false;
});
}
submittedFutures.values().forEach(CompletableFuture::join);
}
public static void dispatchBlocking(Executor executor, Consumer<String> task) {
dispatchBlocking(executor, task, Collections.emptyList());
}
}

View File

@ -30,4 +30,6 @@ public net.minecraft.client.resources.model.ModelBakery$BakedCacheKey
public net.minecraft.client.resources.model.ModelBakery$BakedCacheKey <init>(Lnet/minecraft/resources/ResourceLocation;Lcom/mojang/math/Transformation;Z)V # <init>
public net.minecraft.client.resources.model.ModelBakery f_119241_ # ITEM_MODEL_GENERATOR
public net.minecraft.client.resources.model.ModelBakery$ModelBakerImpl
public net.minecraft.client.resources.model.ModelBakery$ModelBakerImpl <init>(Lnet/minecraft/client/resources/model/ModelBakery;Ljava/util/function/BiFunction;Lnet/minecraft/resources/ResourceLocation;)V # <init>
public net.minecraft.client.resources.model.ModelBakery$ModelBakerImpl <init>(Lnet/minecraft/client/resources/model/ModelBakery;Ljava/util/function/BiFunction;Lnet/minecraft/resources/ResourceLocation;)V # <init>
public net.minecraft.nbt.CompoundTag <init>(Ljava/util/Map;)V # <init>
public net.minecraft.client.renderer.texture.TextureAtlasSprite <init>(Lnet/minecraft/client/renderer/texture/TextureAtlas;Lnet/minecraft/client/renderer/texture/TextureAtlasSprite$Info;IIIIILcom/mojang/blaze3d/platform/NativeImage;)V # <init>

View File

@ -11,6 +11,7 @@
"perf.dynamic_structure_manager.StructureManagerMixin",
"bugfix.chunk_deadlock.ServerChunkCacheMixin",
"perf.dedicated_reload_executor.MinecraftServerMixin",
"perf.fast_forge_dummies.NamespacedHolderHelperMixin",
"perf.remove_biome_temperature_cache.BiomeMixin",
"perf.reduce_blockstate_cache_rebuilds.GameDataMixin",
"perf.reduce_blockstate_cache_rebuilds.BlockCallbacksMixin",
@ -27,7 +28,11 @@
"feature.measure_time.ReloadableServerResourcesMixin",
"feature.branding.BrandingControlMixin",
"feature.direct_stack_trace.CrashReportMixin",
"perf.nbt_memory_usage.CompoundTagMixin",
"perf.fast_registry_validation.ForgeRegistryMixin",
"perf.kubejs.RecipeEventJSMixin",
"perf.tag_id_caching.TagEntryMixin",
"perf.tag_id_caching.TagOrElementLocationMixin",
"perf.cache_strongholds.ChunkGeneratorMixin",
"perf.cache_upgraded_structures.StructureManagerMixin",
"perf.cache_strongholds.ServerLevelMixin",
@ -52,6 +57,7 @@
"perf.dynamic_resources.ModelBakerImplMixin",
"perf.dynamic_resources.ModelBakeryMixin",
"perf.dynamic_resources.ModelManagerMixin",
"perf.dynamic_resources.ae2.RegistrationMixin",
"perf.dynamic_resources.ctm.TextureMetadataHandlerMixin",
"perf.dynamic_resources.ctm.CTMPackReloadListenerMixin",
"perf.dynamic_resources.supermartijncore.ClientRegistrationHandlerMixin",
@ -60,18 +66,15 @@
"perf.model_optimizations.TransformationMatrixMixin",
"perf.model_optimizations.BooleanPropertyMixin",
"perf.model_optimizations.PropertyMixin",
"perf.patchouli_deduplicate_books.ClientBookRegistryMixin",
"perf.thread_priorities.IntegratedServerMixin",
"safety.BlockColorsMixin",
"safety.ItemColorsMixin",
"perf.flatten_model_predicates.AndConditionMixin",
"perf.flatten_model_predicates.OrConditionMixin",
"perf.flatten_model_predicates.PropertyValueConditionMixin",
"perf.blast_search_trees.MinecraftMixin",
"perf.blast_search_trees.IngredientFilterInvoker",
"perf.cache_model_materials.MultipartMixin",
"perf.faster_texture_stitching.StitcherMixin",
"perf.skip_first_datapack_reload.CreateWorldScreenMixin",
"perf.faster_singleplayer_load.MinecraftServerMixin",
"devenv.MinecraftMixin",
"devenv.NarratorMixin"
],