diff --git a/.gitignore b/.gitignore index a3951e7f..c4d1b6c5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ libs media classes/ +# Changelog +CHANGELOG.md + # Created by https://www.gitignore.io/api/gradle,intellij,eclipse,windows,osx,linux ### Gradle ### diff --git a/build.gradle b/build.gradle index 69b647a7..93ee9737 100644 --- a/build.gradle +++ b/build.gradle @@ -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) +} diff --git a/gradle/changelog.mustache b/gradle/changelog.mustache new file mode 100644 index 00000000..b3175640 --- /dev/null +++ b/gradle/changelog.mustache @@ -0,0 +1,5 @@ +{{#commits}} +{{#ifMatches messageTitle "^(?!Merge).*"}} + * [{{{messageTitle}}}](https://github.com/embeddedt/ModernFix/commit/{{hashFull}}) - {{{authorName}}} +{{/ifMatches}} +{{/commits}} \ No newline at end of file diff --git a/release.sh b/release.sh new file mode 100755 index 00000000..092d47c8 --- /dev/null +++ b/release.sh @@ -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 diff --git a/src/main/java/org/embeddedt/modernfix/ModernFixClient.java b/src/main/java/org/embeddedt/modernfix/ModernFixClient.java index 4270f094..06fc2635 100644 --- a/src/main/java/org/embeddedt/modernfix/ModernFixClient.java +++ b/src/main/java/org/embeddedt/modernfix/ModernFixClient.java @@ -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 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) { diff --git a/src/main/java/org/embeddedt/modernfix/agent/Agent.java b/src/main/java/org/embeddedt/modernfix/agent/Agent.java deleted file mode 100644 index 5cf5298d..00000000 --- a/src/main/java/org/embeddedt/modernfix/agent/Agent.java +++ /dev/null @@ -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> TRANSFORMERS = ImmutableMap.>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 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; - } - } -} diff --git a/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java b/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java index f9df7610..df2538ac 100644 --- a/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java +++ b/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java @@ -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(); diff --git a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java index 7afb9339..30d63335 100644 --- a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java +++ b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java @@ -15,8 +15,23 @@ public class ModernFixEarlyConfig { private final Map 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); diff --git a/src/main/java/org/embeddedt/modernfix/duck/IExtendedModelBakery.java b/src/main/java/org/embeddedt/modernfix/duck/IExtendedModelBakery.java index ffe5a8f8..50a325be 100644 --- a/src/main/java/org/embeddedt/modernfix/duck/IExtendedModelBakery.java +++ b/src/main/java/org/embeddedt/modernfix/duck/IExtendedModelBakery.java @@ -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 getBlockStatesForMRL(StateDefinition stateDefinition, ModelResourceLocation location); BakedModel bakeDefault(ResourceLocation modelLocation); + BakedModel getBakedMissingModel(); + void setBakedMissingModel(BakedModel m); + UnbakedModel mfix$getUnbakedMissingModel(); } diff --git a/src/main/java/org/embeddedt/modernfix/dynamicresources/DynamicBakedModelProvider.java b/src/main/java/org/embeddedt/modernfix/dynamicresources/DynamicBakedModelProvider.java index d7db7461..d9bc92b4 100644 --- a/src/main/java/org/embeddedt/modernfix/dynamicresources/DynamicBakedModelProvider.java +++ b/src/main/java/org/embeddedt/modernfix/dynamicresources/DynamicBakedModelProvider.java @@ -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 bakedCache; private final Map permanentOverrides; + private BakedModel missingModel; + private static final BakedModel SENTINEL = new BuiltInModel(null, null, null, false); public DynamicBakedModelProvider(ModelBakery bakery, Map cache) { this.bakery = bakery; this.bakedCache = cache; this.permanentOverrides = new Object2ObjectOpenHashMap<>(); } + + public void setMissingModel(BakedModel model) { + this.missingModel = model; + } + private static Triple vanillaKey(Object o) { return Triple.of((ResourceLocation)o, BlockModelRotation.X0_Y0.getRotation(), false); } @@ -43,7 +51,7 @@ public class DynamicBakedModelProvider implements Map

- * 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; - } - } -} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/devenv/NarratorMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/devenv/NarratorMixin.java index 06e63d15..a4871a5b 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/devenv/NarratorMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/devenv/NarratorMixin.java @@ -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 = "", 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; } } diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ModelBakerImplMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ModelBakerImplMixin.java index 305f3ba2..994b2f2d 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ModelBakerImplMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ModelBakerImplMixin.java @@ -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()); diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ModelBakeryMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ModelBakeryMixin.java index b8138a75..5b20d4f1 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ModelBakeryMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ModelBakeryMixin.java @@ -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 bakedCache; + @Shadow @Final @Mutable private BlockColors blockColors; private Cache loadedBakedModels; + private Cache loadedModels; private HashMap smallLoadingCache = new HashMap<>(); - @Redirect(method = "", 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 = "", 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 void onModelRemoved(RemovalNotification notification) { @@ -276,4 +280,18 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery { public ImmutableList getBlockStatesForMRL(StateDefinition 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; + } } diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ae2/RegistrationMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ae2/RegistrationMixin.java new file mode 100644 index 00000000..7583b1c8 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ae2/RegistrationMixin.java @@ -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> 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 customizerFn = CUSTOMIZERS.get(event.getLocation().getPath()); + if(customizerFn != null) + event.setModel(customizerFn.apply(event.getModel())); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/fast_forge_dummies/NamespacedHolderHelperMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/fast_forge_dummies/NamespacedHolderHelperMixin.java new file mode 100644 index 00000000..49159495 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/fast_forge_dummies/NamespacedHolderHelperMixin.java @@ -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 extends MappedRegistry { + @Shadow private Map> holdersByName; + + public NamespacedHolderHelperMixin(ResourceKey> 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> 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 ref : this.holdersByName.values()) { + if(!ref.isBound()) + return; + } + if (this.unregisteredIntrusiveHolders != null) { + for(Holder.Reference ref : this.unregisteredIntrusiveHolders.values()) { + if(ref.getType() == Holder.Reference.Type.INTRUSIVE && !ref.isBound()) + return; + } + } + // Skip the creation of streams + cir.setReturnValue(this); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/faster_singleplayer_load/MinecraftServerMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/faster_singleplayer_load/MinecraftServerMixin.java deleted file mode 100644 index ad0b11a0..00000000 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/faster_singleplayer_load/MinecraftServerMixin.java +++ /dev/null @@ -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(); - } - } -} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/flatten_model_predicates/AndConditionMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/flatten_model_predicates/AndConditionMixin.java deleted file mode 100644 index 94283f0a..00000000 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/flatten_model_predicates/AndConditionMixin.java +++ /dev/null @@ -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 conditions; - - /** - * @author JellySquid - * @reason Flatten predicates - */ - @Overwrite - public Predicate getPredicate(StateDefinition stateManager) { - return StatePropertyPredicateHelper.allMatch(Streams.stream(this.conditions).map((multipartModelSelector) -> { - return multipartModelSelector.getPredicate(stateManager); - }).collect(Collectors.toList())); - } -} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/flatten_model_predicates/OrConditionMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/flatten_model_predicates/OrConditionMixin.java deleted file mode 100644 index f336a997..00000000 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/flatten_model_predicates/OrConditionMixin.java +++ /dev/null @@ -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 conditions; - - /** - * @author JellySquid - * @reason Flatten predicates - */ - @Overwrite - public Predicate getPredicate(StateDefinition stateManager) { - return StatePropertyPredicateHelper.anyMatch(Streams.stream(this.conditions).map((multipartModelSelector) -> { - return multipartModelSelector.getPredicate(stateManager); - }).collect(Collectors.toList())); - } -} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/flatten_model_predicates/PropertyValueConditionMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/flatten_model_predicates/PropertyValueConditionMixin.java deleted file mode 100644 index 80c8991d..00000000 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/flatten_model_predicates/PropertyValueConditionMixin.java +++ /dev/null @@ -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 getPredicate(StateDefinition 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 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 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 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; - } -} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/kubejs/RecipeEventJSMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/kubejs/RecipeEventJSMixin.java new file mode 100644 index 00000000..ffbd0e26 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/kubejs/RecipeEventJSMixin.java @@ -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); + } + } + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/modern_resourcepacks/VanillaPackResourcesMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/modern_resourcepacks/VanillaPackResourcesMixin.java index 68f0a216..8de18d38 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/modern_resourcepacks/VanillaPackResourcesMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/modern_resourcepacks/VanillaPackResourcesMixin.java @@ -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 ROOT_DIR_BY_TYPE; + private static LoadingCache, List> pathStreamLoadingCache = CacheBuilder.newBuilder() + .build(FileWalker.INSTANCE); + + private static Set containedPaths = null; + + //@Inject(method = "", 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 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 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 cir) { + if(!PackTypeHelper.isVanillaPackType(type)) + return; + cir.setReturnValue(containedPaths.contains(type.getDirectory() + "/" + location.getNamespace() + "/" + FileUtil.normalize(location.getPath()))); + } /** * @author embeddedt diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/nbt_memory_usage/CompoundTagMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/nbt_memory_usage/CompoundTagMixin.java new file mode 100644 index 00000000..348a8f4b --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/nbt_memory_usage/CompoundTagMixin.java @@ -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 tags; + + /** + * Ensure that the backing map is always a CanonizingStringMap. + */ + @Redirect(method = "(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 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 cir) { + if(this.tags instanceof CanonizingStringMap) { + cir.setReturnValue(new CompoundTag(CanonizingStringMap.deepCopy((CanonizingStringMap)this.tags, Tag::copy))); + } + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/patchouli_deduplicate_books/ClientBookRegistryMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/patchouli_deduplicate_books/ClientBookRegistryMixin.java new file mode 100644 index 00000000..971bf43e --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/patchouli_deduplicate_books/ClientBookRegistryMixin.java @@ -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 components; + BookTemplate template = (BookTemplate) templateField.get(page); + if(template == null) + continue; + components = (List) 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); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/tag_id_caching/TagEntryMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/tag_id_caching/TagEntryMixin.java new file mode 100644 index 00000000..82ffcda6 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/tag_id_caching/TagEntryMixin.java @@ -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; + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/tag_id_caching/TagOrElementLocationMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/tag_id_caching/TagOrElementLocationMixin.java new file mode 100644 index 00000000..6b3e1d55 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/tag_id_caching/TagOrElementLocationMixin.java @@ -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; + } +} diff --git a/src/main/java/org/embeddedt/modernfix/predicate/AllPredicate.java b/src/main/java/org/embeddedt/modernfix/predicate/AllPredicate.java deleted file mode 100644 index e741e58a..00000000 --- a/src/main/java/org/embeddedt/modernfix/predicate/AllPredicate.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.embeddedt.modernfix.predicate; - -import java.util.function.Predicate; - -public class AllPredicate implements Predicate { - private final Predicate[] predicates; - - public AllPredicate(Predicate[] predicates) { - this.predicates = predicates; - } - - @Override - public boolean test(T t) { - for (Predicate predicate : this.predicates) { - if (!predicate.test(t)) { - return false; - } - } - - return true; - } -} diff --git a/src/main/java/org/embeddedt/modernfix/predicate/AnyPredicate.java b/src/main/java/org/embeddedt/modernfix/predicate/AnyPredicate.java deleted file mode 100644 index 7c04f5fb..00000000 --- a/src/main/java/org/embeddedt/modernfix/predicate/AnyPredicate.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.embeddedt.modernfix.predicate; - -import java.util.function.Predicate; - -public class AnyPredicate implements Predicate { - private final Predicate[] predicates; - - public AnyPredicate(Predicate[] predicates) { - this.predicates = predicates; - } - - @Override - public boolean test(T t) { - for (Predicate predicate : this.predicates) { - if (predicate.test(t)) { - return true; - } - } - - return false; - } -} diff --git a/src/main/java/org/embeddedt/modernfix/predicate/CachedModelPredicate.java b/src/main/java/org/embeddedt/modernfix/predicate/CachedModelPredicate.java deleted file mode 100644 index 8c5a4566..00000000 --- a/src/main/java/org/embeddedt/modernfix/predicate/CachedModelPredicate.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.embeddedt.modernfix.predicate; - -/** - * Calculates the - */ -public class CachedModelPredicate { -} diff --git a/src/main/java/org/embeddedt/modernfix/predicate/StatePropertyPredicateHelper.java b/src/main/java/org/embeddedt/modernfix/predicate/StatePropertyPredicateHelper.java deleted file mode 100644 index 8125e307..00000000 --- a/src/main/java/org/embeddedt/modernfix/predicate/StatePropertyPredicateHelper.java +++ /dev/null @@ -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 allMatch(List> 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 anyMatch(List> predicates) { - return new AnyPredicate<>(predicates.toArray(new Predicate[0])); - } -} diff --git a/src/main/java/org/embeddedt/modernfix/predicate/all/AllMatchOneBoolean.java b/src/main/java/org/embeddedt/modernfix/predicate/all/AllMatchOneBoolean.java deleted file mode 100644 index 06984eb1..00000000 --- a/src/main/java/org/embeddedt/modernfix/predicate/all/AllMatchOneBoolean.java +++ /dev/null @@ -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 { - private final Property[] properties; - private final boolean[] values; - - public AllMatchOneBoolean(List> 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> 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; - } -} diff --git a/src/main/java/org/embeddedt/modernfix/predicate/all/AllMatchOneObject.java b/src/main/java/org/embeddedt/modernfix/predicate/all/AllMatchOneObject.java deleted file mode 100644 index 5a90591b..00000000 --- a/src/main/java/org/embeddedt/modernfix/predicate/all/AllMatchOneObject.java +++ /dev/null @@ -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 { - private final Property[] properties; - private final Object[] values; - - public AllMatchOneObject(List> 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; - } -} diff --git a/src/main/java/org/embeddedt/modernfix/predicate/any/AllMatchAnyObject.java b/src/main/java/org/embeddedt/modernfix/predicate/any/AllMatchAnyObject.java deleted file mode 100644 index f9585376..00000000 --- a/src/main/java/org/embeddedt/modernfix/predicate/any/AllMatchAnyObject.java +++ /dev/null @@ -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 { - private final Property[] properties; - private final Object[][] values; - - public AllMatchAnyObject(List> 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; - } -} diff --git a/src/main/java/org/embeddedt/modernfix/predicate/single/SingleMatchAny.java b/src/main/java/org/embeddedt/modernfix/predicate/single/SingleMatchAny.java deleted file mode 100644 index 1c7906e9..00000000 --- a/src/main/java/org/embeddedt/modernfix/predicate/single/SingleMatchAny.java +++ /dev/null @@ -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 { - public static final ObjectOpenHashSet PREDICATES = new ObjectOpenHashSet<>(); - - public final Property property; - public final Object[] values; - - private SingleMatchAny(Property property, List values) { - this.property = property; - this.values = values.toArray(); - } - - public static SingleMatchAny create(Property property, List values) { - return PREDICATES.addOrGet(new SingleMatchAny(property, values)); - } - - public static boolean areOfType(List> predicates) { - return predicates.stream() - .allMatch(p -> { - return p instanceof SingleMatchAny; - }); - } - - public static boolean valuesMatchType(List> 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; - } -} diff --git a/src/main/java/org/embeddedt/modernfix/predicate/single/SingleMatchOne.java b/src/main/java/org/embeddedt/modernfix/predicate/single/SingleMatchOne.java deleted file mode 100644 index e44e0515..00000000 --- a/src/main/java/org/embeddedt/modernfix/predicate/single/SingleMatchOne.java +++ /dev/null @@ -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 { - 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> predicates) { - return predicates.stream() - .allMatch(p -> { - return p instanceof SingleMatchOne; - }); - } - - public static boolean valuesMatchType(List> 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; - } -} diff --git a/src/main/java/org/embeddedt/modernfix/registry/DeferredRegisterBaker.java b/src/main/java/org/embeddedt/modernfix/registry/DeferredRegisterBaker.java deleted file mode 100644 index be78ace2..00000000 --- a/src/main/java/org/embeddedt/modernfix/registry/DeferredRegisterBaker.java +++ /dev/null @@ -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>>> supplierMap = new HashMap<>(); - public static Supplier cacheForComputationLater(ResourceLocation registry, String modid, Supplier supplier) { - synchronized (supplierMap) { - HashMap>> registrySupplierMap = supplierMap.computeIfAbsent(registry, reg -> new HashMap<>()); - List> modSupplierList = registrySupplierMap.computeIfAbsent(modid, id -> new ArrayList<>()); - CachedSupplier cacher = new CachedSupplier<>(supplier); - modSupplierList.add(cacher); - return cacher; - } - } - - public static void bakeSuppliers(ResourceLocation registry) { - synchronized (supplierMap) { - Set modErrors = Collections.synchronizedSet(new HashSet<>()); - HashMap>> 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> 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"); - } - } -} diff --git a/src/main/java/org/embeddedt/modernfix/screen/DeferredLevelLoadingScreen.java b/src/main/java/org/embeddedt/modernfix/screen/DeferredLevelLoadingScreen.java deleted file mode 100644 index d66bde97..00000000 --- a/src/main/java/org/embeddedt/modernfix/screen/DeferredLevelLoadingScreen.java +++ /dev/null @@ -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); - } -} diff --git a/src/main/java/org/embeddedt/modernfix/util/AsyncStopwatch.java b/src/main/java/org/embeddedt/modernfix/util/AsyncStopwatch.java deleted file mode 100644 index a45db852..00000000 --- a/src/main/java/org/embeddedt/modernfix/util/AsyncStopwatch.java +++ /dev/null @@ -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 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(); - } - } -} diff --git a/src/main/java/org/embeddedt/modernfix/util/CachedSupplier.java b/src/main/java/org/embeddedt/modernfix/util/CachedSupplier.java deleted file mode 100644 index 50b2303d..00000000 --- a/src/main/java/org/embeddedt/modernfix/util/CachedSupplier.java +++ /dev/null @@ -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 implements Supplier { - private T value = null; - - private boolean hasBeenComputed; - private final Supplier delegate; - - public CachedSupplier(Supplier 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(); - } - } -} diff --git a/src/main/java/org/embeddedt/modernfix/util/CanonizingStringMap.java b/src/main/java/org/embeddedt/modernfix/util/CanonizingStringMap.java new file mode 100644 index 00000000..737e9bca --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/util/CanonizingStringMap.java @@ -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 implements Map { + private Object2ObjectMap backingMap; + + private static final int GROWTH_THRESHOLD = 4; + private static final Interner KEY_INTERNER = Interners.newStrongInterner(); + + public CanonizingStringMap() { + this(new Object2ObjectArrayMap<>()); + } + + protected CanonizingStringMap(Object2ObjectMap 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 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 keySet() { + return Collections.unmodifiableSet(this.backingMap.keySet()); + } + + @NotNull + @Override + public Collection values() { + return Collections.unmodifiableCollection(this.backingMap.values()); + } + + @NotNull + @Override + public Set> 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> i = Object2ObjectMaps.fastIterator(backingMap); + int h = 0, n = backingMap.size(); + while (n-- != 0) + h += i.next().hashCode(); + return h; + } + + public static CanonizingStringMap deepCopy(CanonizingStringMap inputMap, Function deepCopier) { + Object2ObjectMap 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); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/util/OrderedParallelModDispatcher.java b/src/main/java/org/embeddedt/modernfix/util/OrderedParallelModDispatcher.java deleted file mode 100644 index 9c53ac86..00000000 --- a/src/main/java/org/embeddedt/modernfix/util/OrderedParallelModDispatcher.java +++ /dev/null @@ -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 task, Collection modIDsToFilter) { - Set finishedMods = Collections.synchronizedSet(new HashSet<>(modIDsToFilter)); - HashMap> submittedFutures = new HashMap<>(); - Semaphore jobWaitingSemaphore = new Semaphore(0); - ArrayList remainingModList = new ArrayList<>(ModList.get().getMods()); - while(remainingModList.size() > 0) { - remainingModList.removeIf(modInfo -> { - if(finishedMods.contains(modInfo.getModId())) - return true; - List 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 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 task) { - dispatchBlocking(executor, task, Collections.emptyList()); - } -} diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 7a21c5da..ad15f64e 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -30,4 +30,6 @@ public net.minecraft.client.resources.model.ModelBakery$BakedCacheKey public net.minecraft.client.resources.model.ModelBakery$BakedCacheKey (Lnet/minecraft/resources/ResourceLocation;Lcom/mojang/math/Transformation;Z)V # 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 (Lnet/minecraft/client/resources/model/ModelBakery;Ljava/util/function/BiFunction;Lnet/minecraft/resources/ResourceLocation;)V # \ No newline at end of file +public net.minecraft.client.resources.model.ModelBakery$ModelBakerImpl (Lnet/minecraft/client/resources/model/ModelBakery;Ljava/util/function/BiFunction;Lnet/minecraft/resources/ResourceLocation;)V # +public net.minecraft.nbt.CompoundTag (Ljava/util/Map;)V # +public net.minecraft.client.renderer.texture.TextureAtlasSprite (Lnet/minecraft/client/renderer/texture/TextureAtlas;Lnet/minecraft/client/renderer/texture/TextureAtlasSprite$Info;IIIIILcom/mojang/blaze3d/platform/NativeImage;)V # diff --git a/src/main/resources/modernfix.mixins.json b/src/main/resources/modernfix.mixins.json index 47fe026f..19b871b6 100644 --- a/src/main/resources/modernfix.mixins.json +++ b/src/main/resources/modernfix.mixins.json @@ -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" ],