diff --git a/build.gradle b/build.gradle index 10f02268..69b647a7 100644 --- a/build.gradle +++ b/build.gradle @@ -108,9 +108,11 @@ tasks.withType(JavaCompile) { options.encoding = "UTF-8" def targetVersion = 17 + /* if (JavaVersion.current().isJava9Compatible()) { options.release = targetVersion } + */ } java { diff --git a/src/main/java/org/embeddedt/modernfix/ModernFix.java b/src/main/java/org/embeddedt/modernfix/ModernFix.java index c1601f8b..3a0a4274 100644 --- a/src/main/java/org/embeddedt/modernfix/ModernFix.java +++ b/src/main/java/org/embeddedt/modernfix/ModernFix.java @@ -1,26 +1,37 @@ package org.embeddedt.modernfix; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerLevel; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.server.ServerStartedEvent; +import net.minecraftforge.event.server.ServerStoppedEvent; +import net.minecraftforge.eventbus.api.EventPriority; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.*; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.config.ModConfig; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import net.minecraftforge.fml.event.lifecycle.FMLLoadCompleteEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.fml.loading.FMLConfig; import net.minecraftforge.fml.loading.FMLLoader; +import net.minecraftforge.fml.util.ObfuscationReflectionHelper; import net.minecraftforge.network.NetworkConstants; import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.embeddedt.modernfix.classloading.ModFileScanDataDeduplicator; import org.embeddedt.modernfix.core.config.ModernFixConfig; import org.embeddedt.modernfix.entity.EntityDataIDSyncHandler; import org.embeddedt.modernfix.packet.PacketHandler; - +import org.embeddedt.modernfix.registry.ObjectHolderClearer; +import org.embeddedt.modernfix.util.ClassInfoManager; import java.lang.management.ManagementFactory; +import java.lang.reflect.Field; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; @@ -66,12 +77,14 @@ public class ModernFix { // Register ourselves for server and other game events we are interested in MinecraftForge.EVENT_BUS.register(this); FMLJavaModLoadingContext.get().getModEventBus().addListener(this::commonSetup); + FMLJavaModLoadingContext.get().getModEventBus().addListener(this::onLoadComplete); DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> MinecraftForge.EVENT_BUS.register(new ModernFixClient())); ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(() -> NetworkConstants.IGNORESERVERONLY, (a, b) -> true)); ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, ModernFixConfig.COMMON_CONFIG); MinecraftForge.EVENT_BUS.register(EntityDataIDSyncHandler.class); PacketHandler.register(); + ModFileScanDataDeduplicator.deduplicate(); } private static boolean dfuModPresent() { @@ -91,6 +104,7 @@ public class ModernFix { ModLoader.get().addWarning(new ModLoadingWarning(ModLoadingContext.get().getActiveContainer().getModInfo(), ModLoadingStage.COMMON_SETUP, "modernfix.no_lazydfu")); }); } + ObjectHolderClearer.clearThrowables(); } @SubscribeEvent @@ -99,5 +113,32 @@ public class ModernFix { float gameStartTime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000f; ModernFix.LOGGER.warn("Dedicated server took " + gameStartTime + " seconds to load"); } + ClassInfoManager.clear(); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void onLoadComplete(FMLLoadCompleteEvent event) { + ClassInfoManager.clear(); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void onServerDead(ServerStoppedEvent event) { + /* Clear as much data from the integrated server as possible, in case a mod holds on to it */ + try { + Field updatingMapField = ObfuscationReflectionHelper.findField(ChunkMap.class, "f_140129_"); + Field visibleMapField = ObfuscationReflectionHelper.findField(ChunkMap.class, "f_140130_"); + Field pendingUnloadsField = ObfuscationReflectionHelper.findField(ChunkMap.class, "f_140131_"); + for(ServerLevel level : event.getServer().getAllLevels()) { + ChunkMap chunkMap = level.getChunkSource().chunkMap; + Long2ObjectMap map = (Long2ObjectMap)updatingMapField.get(chunkMap); + map.clear(); + map = (Long2ObjectMap)visibleMapField.get(chunkMap); + map.clear(); + map = (Long2ObjectMap)pendingUnloadsField.get(chunkMap); + map.clear(); + } + } catch(RuntimeException | IllegalAccessException e) { + ModernFix.LOGGER.error("Couldn't clear chunk data", e); + } } } diff --git a/src/main/java/org/embeddedt/modernfix/classloading/ModFileScanDataDeduplicator.java b/src/main/java/org/embeddedt/modernfix/classloading/ModFileScanDataDeduplicator.java new file mode 100644 index 00000000..5ea71cb7 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/classloading/ModFileScanDataDeduplicator.java @@ -0,0 +1,89 @@ +package org.embeddedt.modernfix.classloading; + +import com.google.common.collect.Interner; +import com.google.common.collect.Interners; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.forgespi.language.ModFileScanData; +import net.minecraftforge.forgespi.locating.IModFile; +import org.objectweb.asm.Type; + +import java.lang.reflect.Field; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class ModFileScanDataDeduplicator { + private final Interner typeInterner = Interners.newStrongInterner(); + + private final Function internerFn = type -> type != null ? typeInterner.intern(type) : null; + + private static Field classClazzField, parentField, interfacesField, annotationClazzField, annotationTypeField; + private static final boolean reflectionSuccessful; + + static { + boolean success = false; + try { + classClazzField = ModFileScanData.ClassData.class.getDeclaredField("clazz"); + classClazzField.setAccessible(true); + parentField = ModFileScanData.ClassData.class.getDeclaredField("parent"); + parentField.setAccessible(true); + interfacesField = ModFileScanData.ClassData.class.getDeclaredField("interfaces"); + interfacesField.setAccessible(true); + annotationClazzField = ModFileScanData.AnnotationData.class.getDeclaredField("clazz"); + annotationClazzField.setAccessible(true); + annotationTypeField = ModFileScanData.AnnotationData.class.getDeclaredField("annotationType"); + annotationTypeField.setAccessible(true); + success = true; + } catch(ReflectiveOperationException | RuntimeException e) { + } + reflectionSuccessful = success; + } + + ModFileScanDataDeduplicator() { + } + + private void runDeduplication() { + ModList.get().forEachModFile(this::deduplicateFile); + } + + private void deduplicateFile(IModFile file) { + ModFileScanData data = file.getScanResult(); + if(data != null) { + data.getClasses().forEach(this::deduplicateClass); + data.getAnnotations().forEach(this::deduplicateAnnotation); + } + } + + private void deduplicateClass(ModFileScanData.ClassData data) { + try { + Type type = (Type)classClazzField.get(data); + type = internerFn.apply(type); + classClazzField.set(data, type); + type = (Type)parentField.get(data); + type = internerFn.apply(type); + parentField.set(data, type); + Set types = (Set)interfacesField.get(data); + types = types.stream().map(internerFn).collect(Collectors.toSet()); + interfacesField.set(data, types); + } catch(ReflectiveOperationException e) { + } + } + + private void deduplicateAnnotation(ModFileScanData.AnnotationData data) { + try { + Type type = (Type)annotationClazzField.get(data); + type = internerFn.apply(type); + annotationClazzField.set(data, type); + type = (Type)annotationTypeField.get(data); + type = internerFn.apply(type); + annotationTypeField.set(data, type); + } catch(ReflectiveOperationException e) { + } + } + + public static void deduplicate() { + if(!reflectionSuccessful) + return; + new ModFileScanDataDeduplicator().runDeduplication(); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java b/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java index 8d027fd3..1a368935 100644 --- a/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java +++ b/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java @@ -7,6 +7,7 @@ import org.apache.logging.log4j.Logger; import org.embeddedt.modernfix.classloading.FastAccessTransformerList; import org.embeddedt.modernfix.core.config.ModernFixEarlyConfig; import org.embeddedt.modernfix.core.config.Option; +import org.embeddedt.modernfix.dfu.DFUBlaster; import org.embeddedt.modernfix.load.ModWorkManagerQueue; import org.embeddedt.modernfix.util.DummyList; import org.objectweb.asm.Opcodes; @@ -42,6 +43,7 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin { FastAccessTransformerList.attemptReplace(); ModWorkManagerQueue.replace(); + DFUBlaster.blastMaps(); /* https://github.com/FabricMC/Mixin/pull/99 */ try { 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 581db6af..d3fcaef3 100644 --- a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java +++ b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java @@ -37,10 +37,13 @@ public class ModernFixEarlyConfig { /* Use a simpler ArrayMap if FerriteCore is using the map intelligently anyway */ this.addMixinRule("perf.state_definition_construct", modPresent("ferritecore")); this.addMixinRule("perf.cache_strongholds", true); + this.addMixinRule("perf.dedup_blockstate_flattening_map", false); + this.addMixinRule("perf.clear_mixin_classinfo", false); this.addMixinRule("perf.cache_upgraded_structures", true); this.addMixinRule("perf.compress_blockstate", false); this.addMixinRule("bugfix.concurrency", true); this.addMixinRule("bugfix.edge_chunk_not_saved", 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); @@ -65,14 +68,17 @@ public class ModernFixEarlyConfig { disableIfModPresent("mixin.perf.async_jei", "modernui"); disableIfModPresent("mixin.perf.compress_biome_container", "chocolate", "betterendforge"); disableIfModPresent("mixin.bugfix.mc218112", "performant"); - disableIfModPresent("mixin.perf.faster_baking", "touhou_little_maid"); disableIfModPresent("mixin.perf.reuse_datapacks", "tac"); } private void disableIfModPresent(String configName, String... ids) { for(String id : ids) { if(FMLLoader.getLoadingModList().getModFileById(id) != null) { - this.options.get(configName).addModOverride(false, id); + Option option = this.options.get(configName); + if(option != null) + option.addModOverride(false, id); + else + LOGGER.warn("Can't disable missing option {}", configName); } } } diff --git a/src/main/java/org/embeddedt/modernfix/dfu/DFUBlaster.java b/src/main/java/org/embeddedt/modernfix/dfu/DFUBlaster.java new file mode 100644 index 00000000..1083a9f7 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/dfu/DFUBlaster.java @@ -0,0 +1,49 @@ +package org.embeddedt.modernfix.dfu; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.mojang.datafixers.RewriteResult; +import com.mojang.datafixers.TypeRewriteRule; +import com.mojang.datafixers.functions.PointFreeRule; +import com.mojang.datafixers.types.Type; +import com.mojang.datafixers.util.Pair; +import org.apache.commons.lang3.tuple.Triple; +import org.embeddedt.modernfix.ModernFix; +import sun.misc.Unsafe; + +import java.lang.reflect.Field; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.IntFunction; + +public class DFUBlaster { + public static void blastMaps() { + Cache>, Integer>, RewriteResult> hmapApplyCache = CacheBuilder.newBuilder() + .maximumSize(200) /* should mean approximately 50MB used max */ + .expireAfterAccess(3, TimeUnit.MINUTES) + .build(); + Cache, TypeRewriteRule, PointFreeRule>, Optional>> rewriteCache = CacheBuilder.newBuilder() + .maximumSize(1000) + .expireAfterAccess(3, TimeUnit.MINUTES) + .build(); + try { + Class FOLD_CLASS = Class.forName("com.mojang.datafixers.functions.Fold"); + Field hmapField = FOLD_CLASS.getDeclaredField("HMAP_APPLY_CACHE"); + hmapField.setAccessible(true); + final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + Unsafe unsafe = (Unsafe)theUnsafe.get(null); + Object base = unsafe.staticFieldBase(hmapField); + long offset = unsafe.staticFieldOffset(hmapField); + unsafe.putObject(base, offset, hmapApplyCache.asMap()); + Field rewriteCacheField = Type.class.getDeclaredField("REWRITE_CACHE"); + rewriteCacheField.setAccessible(true); + base = unsafe.staticFieldBase(rewriteCacheField); + offset = unsafe.staticFieldOffset(rewriteCacheField); + unsafe.putObject(base, offset, rewriteCache.asMap()); + } catch(Throwable e) { + ModernFix.LOGGER.error("Could not replace DFU map", e); + } + } +} diff --git a/src/main/java/org/embeddedt/modernfix/duck/ICachedMaterialsModel.java b/src/main/java/org/embeddedt/modernfix/duck/ICachedMaterialsModel.java new file mode 100644 index 00000000..855b8c3b --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/duck/ICachedMaterialsModel.java @@ -0,0 +1,5 @@ +package org.embeddedt.modernfix.duck; + +public interface ICachedMaterialsModel { + public void clearMaterialsCache(); +} diff --git a/src/main/java/org/embeddedt/modernfix/duck/IDynamicModelBakery.java b/src/main/java/org/embeddedt/modernfix/duck/IDynamicModelBakery.java deleted file mode 100644 index 53420430..00000000 --- a/src/main/java/org/embeddedt/modernfix/duck/IDynamicModelBakery.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.embeddedt.modernfix.duck; - -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.resources.ResourceLocation; - -public interface IDynamicModelBakery { - BakedModel bakeDefault(ResourceLocation modelLocation); -} diff --git a/src/main/java/org/embeddedt/modernfix/duck/IExtendedModelBakery.java b/src/main/java/org/embeddedt/modernfix/duck/IExtendedModelBakery.java new file mode 100644 index 00000000..ffe5a8f8 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/duck/IExtendedModelBakery.java @@ -0,0 +1,14 @@ +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.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; + +public interface IExtendedModelBakery { + ImmutableList getBlockStatesForMRL(StateDefinition stateDefinition, ModelResourceLocation location); + BakedModel bakeDefault(ResourceLocation modelLocation); +} diff --git a/src/main/java/org/embeddedt/modernfix/dynamicresources/DynamicBakedModelProvider.java b/src/main/java/org/embeddedt/modernfix/dynamicresources/DynamicBakedModelProvider.java index 9647a984..d7db7461 100644 --- a/src/main/java/org/embeddedt/modernfix/dynamicresources/DynamicBakedModelProvider.java +++ b/src/main/java/org/embeddedt/modernfix/dynamicresources/DynamicBakedModelProvider.java @@ -2,13 +2,12 @@ package org.embeddedt.modernfix.dynamicresources; import com.mojang.math.Transformation; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import net.minecraft.client.resources.model.BakedModel; import net.minecraft.client.resources.model.BlockModelRotation; import net.minecraft.client.resources.model.ModelBakery; import net.minecraft.resources.ResourceLocation; import org.apache.commons.lang3.tuple.Triple; -import org.embeddedt.modernfix.duck.IDynamicModelBakery; +import org.embeddedt.modernfix.duck.IExtendedModelBakery; import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; @@ -55,7 +54,7 @@ public class DynamicBakedModelProvider implements Map useDummyUv = ThreadLocal.withInitial(() -> Boolean.FALSE); + public static final BlockFaceUV dummyUv = new BlockFaceUV(new float[4], 0); +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/cache_model_materials/BlockModelMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/cache_model_materials/BlockModelMixin.java new file mode 100644 index 00000000..691a28e5 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/cache_model_materials/BlockModelMixin.java @@ -0,0 +1,108 @@ +package org.embeddedt.modernfix.mixin.perf.cache_model_materials; + +import com.mojang.datafixers.util.Either; +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.Material; +import org.embeddedt.modernfix.duck.ICachedMaterialsModel; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +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.Collection; +import java.util.Map; +import java.util.Set; + +@Mixin(BlockModel.class) +public class BlockModelMixin { + @Shadow @Final @Mutable public Map> textureMap; + + /** + * @author embeddedt + * @reason detect changes to the texture map, and clear the material cache as needed + */ + @Inject(method = "", at = @At("RETURN")) + private void useTrackingTextureMap(CallbackInfo ci) { + Map> backingMap = this.textureMap; + ICachedMaterialsModel cacheHolder = (ICachedMaterialsModel)this; + this.textureMap = new Map>() { + @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 Either get(Object o) { + return backingMap.get(o); + } + + @Nullable + @Override + public Either put(String s, Either materialStringEither) { + Either old = backingMap.put(s, materialStringEither); + cacheHolder.clearMaterialsCache(); + return old; + } + + @Override + public Either remove(Object o) { + Either e = backingMap.remove(o); + cacheHolder.clearMaterialsCache(); + return e; + } + + @Override + public void putAll(@NotNull Map> map) { + backingMap.putAll(map); + cacheHolder.clearMaterialsCache(); + } + + @Override + public void clear() { + backingMap.clear(); + cacheHolder.clearMaterialsCache(); + } + + @NotNull + @Override + public Set keySet() { + cacheHolder.clearMaterialsCache(); + return backingMap.keySet(); + } + + @NotNull + @Override + public Collection> values() { + cacheHolder.clearMaterialsCache(); + return backingMap.values(); + } + + @NotNull + @Override + public Set>> entrySet() { + cacheHolder.clearMaterialsCache(); + return backingMap.entrySet(); + } + }; + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/dedup_blockstate_flattening_map/BlockStateDataMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/dedup_blockstate_flattening_map/BlockStateDataMixin.java new file mode 100644 index 00000000..63da3acc --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/dedup_blockstate_flattening_map/BlockStateDataMixin.java @@ -0,0 +1,21 @@ +package org.embeddedt.modernfix.mixin.perf.dedup_blockstate_flattening_map; + +import net.minecraft.util.datafix.fixes.BlockStateData; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(BlockStateData.class) +public class BlockStateDataMixin { + @Inject(method = {"register", "finalizeMaps"}, at = @At("HEAD"), cancellable = true) + private static void noFlattening(CallbackInfo ci) { + ci.cancel(); + } + + @Inject(method = {"upgradeBlockStateTag", "upgradeBlock(I)Ljava/lang/String;", "upgradeBlock(Ljava/lang/String;)Ljava/lang/String;", "getTag"}, at = @At("HEAD"), require = 4) + private static void preventCorruption(CallbackInfoReturnable cir) { + throw new UnsupportedOperationException("Performing the Flattening is currently disabled in the ModernFix config."); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/dedup_blockstate_flattening_map/ChunkPalettedStorageFixMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/dedup_blockstate_flattening_map/ChunkPalettedStorageFixMixin.java new file mode 100644 index 00000000..962a7fee --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/dedup_blockstate_flattening_map/ChunkPalettedStorageFixMixin.java @@ -0,0 +1,31 @@ +package org.embeddedt.modernfix.mixin.perf.dedup_blockstate_flattening_map; + +import com.mojang.serialization.Dynamic; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.util.datafix.fixes.ChunkPalettedStorageFix; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.function.Consumer; + +@Mixin(ChunkPalettedStorageFix.class) +public class ChunkPalettedStorageFixMixin { + @Redirect(method = "", at = @At(value = "INVOKE", target = "Lcom/mojang/datafixers/DataFixUtils;make(Ljava/lang/Object;Ljava/util/function/Consumer;)Ljava/lang/Object;")) + private static Object skipMakingMap(Object o, Consumer consumer) { + return o; + } + + @Redirect(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/datafix/fixes/BlockStateData;getTag(I)Lcom/mojang/serialization/Dynamic;")) + private static Dynamic getFakeAirTag(int id) { + return new Dynamic<>(NbtOps.INSTANCE, new CompoundTag()); + } + + @Inject(method = "fix", at = @At("HEAD")) + private void skipFix(CallbackInfoReturnable> cir) { + throw new UnsupportedOperationException("No Flattening for you."); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/BlockElementFaceDeserializerMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/BlockElementFaceDeserializerMixin.java new file mode 100644 index 00000000..eb4b3ee5 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/BlockElementFaceDeserializerMixin.java @@ -0,0 +1,21 @@ +package org.embeddedt.modernfix.mixin.perf.dynamic_resources; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; +import net.minecraft.client.renderer.block.model.BlockElementFace; +import org.embeddedt.modernfix.dynamicresources.UVController; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.lang.reflect.Type; + +@Mixin(BlockElementFace.Deserializer.class) +public class BlockElementFaceDeserializerMixin { + + @Redirect(method = "deserialize(Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lnet/minecraft/client/renderer/block/model/BlockElementFace;", + at = @At(value = "INVOKE", target = "Lcom/google/gson/JsonDeserializationContext;deserialize(Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;)Ljava/lang/Object;", ordinal = 0)) + private Object skipUvsForInitialLoad(JsonDeserializationContext context, JsonElement element, Type type) { + return UVController.useDummyUv.get() ? UVController.dummyUv : context.deserialize(element, type); + } +} 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 827655ab..a3b7e7c0 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 @@ -7,21 +7,18 @@ import com.google.common.cache.RemovalNotification; import com.google.common.collect.ImmutableList; import com.mojang.math.Transformation; import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.renderer.block.model.ItemModelGenerator; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.resources.model.*; import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.util.profiling.ProfilerFiller; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.properties.Property; -import net.minecraftforge.client.model.geometry.GeometryLoaderManager; import org.apache.commons.lang3.tuple.Triple; import org.embeddedt.modernfix.ModernFix; -import org.embeddedt.modernfix.duck.IDynamicModelBakery; import org.embeddedt.modernfix.dynamicresources.DynamicBakedModelProvider; +import org.embeddedt.modernfix.duck.IExtendedModelBakery; import org.slf4j.Logger; import org.spongepowered.asm.mixin.*; import org.spongepowered.asm.mixin.injection.At; @@ -39,7 +36,7 @@ import java.util.function.BiFunction; /* high priority so that our injectors are added before other mods' */ @Mixin(value = ModelBakery.class, priority = 600) -public abstract class ModelBakeryMixin implements IDynamicModelBakery { +public abstract class ModelBakeryMixin implements IExtendedModelBakery { private static final boolean debugDynamicModelLoading = Boolean.getBoolean("modernfix.debugDynamicModelLoading"); @@ -268,8 +265,13 @@ public abstract class ModelBakeryMixin implements IDynamicModelBakery { @Override public BakedModel bakeDefault(ResourceLocation modelLocation) { - ModelBakery self = (ModelBakery)(Object)this; + ModelBakery self = (ModelBakery) (Object) this; ModelBaker theBaker = self.new ModelBakerImpl(textureGetter, modelLocation); return theBaker.bake(modelLocation, BlockModelRotation.X0_Y0); } + + @Override + public ImmutableList getBlockStatesForMRL(StateDefinition stateDefinition, ModelResourceLocation location) { + return loadOnlyRelevantBlockState(stateDefinition, location); + } } diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ctm/CTMPackReloadListenerMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ctm/CTMPackReloadListenerMixin.java index 079575ef..81133d71 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ctm/CTMPackReloadListenerMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ctm/CTMPackReloadListenerMixin.java @@ -1,5 +1,6 @@ package org.embeddedt.modernfix.mixin.perf.dynamic_resources.ctm; +import com.google.common.collect.ImmutableList; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.minecraft.client.renderer.ItemBlockRenderTypes; import net.minecraft.client.renderer.RenderType; @@ -9,13 +10,17 @@ import net.minecraft.client.resources.model.ModelResourceLocation; import net.minecraft.client.resources.model.MultiPartBakedModel; import net.minecraft.client.resources.model.WeightedBakedModel; import net.minecraft.core.Holder; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import net.minecraftforge.client.ChunkRenderTypeSet; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.eventbus.api.EventPriority; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.registries.ForgeRegistries; +import org.embeddedt.modernfix.ModernFix; +import org.embeddedt.modernfix.duck.IExtendedModelBakery; import org.embeddedt.modernfix.dynamicresources.DynamicModelBakeEvent; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -28,18 +33,22 @@ import team.chisel.ctm.client.model.AbstractCTMBakedModel; import team.chisel.ctm.client.util.CTMPackReloadListener; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; @Mixin(CTMPackReloadListener.class) public abstract class CTMPackReloadListenerMixin { + /* caches the original render checks */ @Shadow @Final private static Map, Predicate> blockRenderChecks; + private static Map, Predicate> renderCheckOverrides = new ConcurrentHashMap<>(); + + private static ChunkRenderTypeSet DEFAULT_TYPE_SET = ChunkRenderTypeSet.of(RenderType.solid()); + @Shadow protected abstract Predicate getLayerCheck(BlockState state, BakedModel model); @Shadow protected abstract ChunkRenderTypeSet getExistingRenderCheck(Block block); - private Map locationToState = new Object2ObjectOpenHashMap<>(); - @Inject(method = "", at = @At("RETURN")) private void onInit(CallbackInfo ci) { MinecraftForge.EVENT_BUS.addListener(EventPriority.LOW, this::onModelBake); @@ -47,31 +56,48 @@ public abstract class CTMPackReloadListenerMixin { @Overwrite(remap = false) private void refreshLayerHacks() { - blockRenderChecks.forEach((b, p) -> ItemBlockRenderTypes.setRenderLayer((Block) b.get(), p)); - blockRenderChecks.clear(); - if(locationToState.isEmpty()) { + renderCheckOverrides.clear(); + if(blockRenderChecks.isEmpty()) { for(Block block : ForgeRegistries.BLOCKS.getValues()) { - for(BlockState state : block.getStateDefinition().getPossibleStates()) { - locationToState.put(BlockModelShaper.stateToModelLocation(state), state); - } + Holder.Reference holder = ForgeRegistries.BLOCKS.getDelegateOrThrow(block); + ChunkRenderTypeSet original = this.getExistingRenderCheck(block); + if(original == null) + original = DEFAULT_TYPE_SET; + blockRenderChecks.put(holder, original::contains); + ItemBlockRenderTypes.setRenderLayer(block, type -> this.useOverrideIfPresent(holder, type)); } } } + private boolean useOverrideIfPresent(Holder.Reference delegate, RenderType type) { + Predicate override = renderCheckOverrides.get(delegate); + if(override == null) + override = blockRenderChecks.get(delegate); + return override.test(type); + } + private void onModelBake(DynamicModelBakeEvent event) { if(!(event.getModel() instanceof AbstractCTMBakedModel || event.getModel() instanceof WeightedBakedModel || event.getModel() instanceof MultiPartBakedModel)) return; - BlockState state = locationToState.get(event.getLocation()); - if(state == null) + /* we construct a new ResourceLocation because an MRL is coming in */ + Block block = ForgeRegistries.BLOCKS.getValue(new ResourceLocation(event.getLocation().getNamespace(), event.getLocation().getPath())); + Holder.Reference delegate = block != null ? ForgeRegistries.BLOCKS.getDelegateOrThrow(block) : null; + if(block == null || block == Blocks.AIR || renderCheckOverrides.containsKey(delegate)) return; - Block block = state.getBlock(); - Holder.Reference delegate = ForgeRegistries.BLOCKS.getDelegateOrThrow(block); - if(blockRenderChecks.containsKey(delegate)) + /* find all states that match this MRL */ + ImmutableList allStates; + try { + allStates = ((IExtendedModelBakery)(Object)event.getModelLoader()).getBlockStatesForMRL(block.getStateDefinition(), (ModelResourceLocation)event.getLocation()); + } catch(RuntimeException e) { + ModernFix.LOGGER.error("Couldn't get state for MRL " + event.getLocation(), e); return; - Predicate newPredicate = this.getLayerCheck(state, event.getModel()); - if(newPredicate != null) { - blockRenderChecks.put(delegate, this.getExistingRenderCheck(block)::contains); - ItemBlockRenderTypes.setRenderLayer(block, newPredicate); + } + for(BlockState state : allStates) { + Predicate newPredicate = this.getLayerCheck(state, event.getModel()); + if(newPredicate != null) { + renderCheckOverrides.put(delegate, newPredicate); + return; + } } } } diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_structure_manager/StructureManagerMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_structure_manager/StructureManagerMixin.java new file mode 100644 index 00000000..84c075a5 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_structure_manager/StructureManagerMixin.java @@ -0,0 +1,37 @@ +package org.embeddedt.modernfix.mixin.perf.dynamic_structure_manager; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.mojang.datafixers.DataFixer; +import net.minecraft.core.HolderGetter; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import net.minecraft.world.level.storage.LevelStorageSource; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +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.Optional; + +@Mixin(StructureTemplateManager.class) +public class StructureManagerMixin { + @Shadow @Final @Mutable + private Map> structureRepository; + + @Inject(method = "", at = @At("RETURN")) + private void makeStructuresSafe(ResourceManager arg, LevelStorageSource.LevelStorageAccess arg2, DataFixer dataFixer, HolderGetter arg3, CallbackInfo ci) { + /* Structures needing to be reloaded is not a huge issue since we optimize loading them already */ + Cache> structureCache = CacheBuilder.newBuilder() + .softValues() + .build(); + this.structureRepository = structureCache.asMap(); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/modern_resourcepacks/PathPackResourcesMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/modern_resourcepacks/PathPackResourcesMixin.java index 5ea6b903..b38de21e 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/modern_resourcepacks/PathPackResourcesMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/modern_resourcepacks/PathPackResourcesMixin.java @@ -5,8 +5,10 @@ import com.mojang.datafixers.util.Pair; import net.minecraft.server.packs.PackType; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.resource.PathPackResources; +import org.embeddedt.modernfix.util.CachedResourcePath; import org.embeddedt.modernfix.util.FileUtil; import org.jetbrains.annotations.NotNull; +import org.embeddedt.modernfix.util.PackTypeHelper; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -40,9 +42,9 @@ public abstract class PathPackResourcesMixin { @Shadow @NotNull protected abstract Set getNamespacesFromDisk(PackType type); private EnumMap> namespacesByType; - private EnumMap>>> rootListingByNamespaceAndType; + private EnumMap>> rootListingByNamespaceAndType; private boolean hasGeneratedListings; - private Set containedPaths; + private Set containedPaths; private FileSystem resourcePackFS; @@ -59,24 +61,25 @@ public abstract class PathPackResourcesMixin { synchronized (this) { if(hasGeneratedListings) return; - EnumMap>>> rootListingByNamespaceAndType = new EnumMap<>(PackType.class); - HashSet containedPaths = new HashSet<>(); + EnumMap>> rootListingByNamespaceAndType = new EnumMap<>(PackType.class); + HashSet containedPaths = new HashSet<>(); for(PackType type : PackType.values()) { Set namespaces = this.getNamespacesFromDisk(type); - HashMap>> rootListingForNamespaces = new HashMap<>(); + HashMap> rootListingForNamespaces = new HashMap<>(); for(String namespace : namespaces) { try { Path root = this.resolve(type.getDirectory(), namespace).toAbsolutePath(); try (Stream stream = Files.walk(root)) { - ArrayList> rootListingPaths = new ArrayList<>(); + ArrayList rootListingPaths = new ArrayList<>(); stream .map(path -> root.relativize(path.toAbsolutePath())) .filter(this::isValidCachedResourcePath) .forEach(path -> { - if(!path.toString().endsWith(".mcmeta")) - rootListingPaths.add(Pair.of(path, slashJoiner.join(path))); - String mergedPath = slashJoiner.join(type.getDirectory(), namespace, path); - containedPaths.add(mergedPath); + CachedResourcePath listing = new CachedResourcePath(path); + if(!listing.getFileName().endsWith(".mcmeta")) { + rootListingPaths.add(listing); + } + containedPaths.add(new CachedResourcePath(new String[] { type.getDirectory(), namespace }, listing)); }); rootListingPaths.trimToSize(); rootListingForNamespaces.put(namespace, rootListingPaths); @@ -85,7 +88,8 @@ public abstract class PathPackResourcesMixin { rootListingForNamespaces.put(namespace, Collections.emptyList()); } } - rootListingByNamespaceAndType.put(type, rootListingForNamespaces); + if(PackTypeHelper.isVanillaPackType(type)) + rootListingByNamespaceAndType.put(type, rootListingForNamespaces); } this.rootListingByNamespaceAndType = rootListingByNamespaceAndType; this.containedPaths = containedPaths; @@ -94,7 +98,11 @@ public abstract class PathPackResourcesMixin { } private boolean isValidCachedResourcePath(Path path) { + if(path.getFileName() == null || path.getNameCount() == 0) + return false; String str = path.toString(); + if(str.length() == 0) + return false; for(int i = 0; i < str.length(); i++) { if(!ResourceLocation.validPathChar(str.charAt(i))) { return false; @@ -105,6 +113,8 @@ public abstract class PathPackResourcesMixin { @Inject(method = "getNamespaces", at = @At("HEAD"), cancellable = true) private void useCacheForNamespaces(PackType type, CallbackInfoReturnable> cir) { + if(!PackTypeHelper.isVanillaPackType(type)) + return; Set cachedNamespaces; synchronized (this.namespacesByType) { cachedNamespaces = this.namespacesByType.get(type); @@ -116,6 +126,8 @@ public abstract class PathPackResourcesMixin { @Inject(method = "getNamespaces", at = @At("TAIL")) private void storeCacheForNamespaces(PackType type, CallbackInfoReturnable> cir) { + if(!PackTypeHelper.isVanillaPackType(type)) + return; synchronized (this.namespacesByType) { this.namespacesByType.put(type, cir.getReturnValue()); } @@ -124,7 +136,7 @@ public abstract class PathPackResourcesMixin { //@Inject(method = "hasResource(Ljava/lang/String;)Z", at = @At(value = "HEAD"), cancellable = true) private void useCacheForExistence(String path, CallbackInfoReturnable cir) { this.generateResourceCache(); - cir.setReturnValue(this.containedPaths.contains(FileUtil.normalize(path))); + cir.setReturnValue(this.containedPaths.contains(new CachedResourcePath(path))); } /** @@ -134,16 +146,18 @@ public abstract class PathPackResourcesMixin { @Inject(method = "getResources", at = @At("HEAD"), cancellable = true, remap = false) public void getResources(PackType type, String resourceNamespace, String pathIn, Predicate filter, CallbackInfoReturnable> cir) { + if(!PackTypeHelper.isVanillaPackType(type)) + return; this.generateResourceCache(); if(!pathIn.endsWith("/")) pathIn = pathIn + "/"; final String testPath = pathIn; Collection cachedListing = this.rootListingByNamespaceAndType.get(type).getOrDefault(resourceNamespace, Collections.emptyList()).stream(). - filter(path -> path.getSecond().startsWith(testPath)). // Make sure the target path is inside this one + filter(path -> path.getFullPath().startsWith(testPath)). // Make sure the target path is inside this one // Finally we need to form the RL, so use the first name as the domain, and the rest as the path // It is VERY IMPORTANT that we do not rely on Path.toString as this is inconsistent between operating systems // Join the path names ourselves to force forward slashes - map(path -> new ResourceLocation(resourceNamespace, path.getSecond())). + map(path -> new ResourceLocation(resourceNamespace, path.getFullPath())). filter(filter::test). // Test the file name against the predicate collect(Collectors.toList()); cir.setReturnValue(cachedListing); diff --git a/src/main/java/org/embeddedt/modernfix/mixin/safety/BlockColorsMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/safety/BlockColorsMixin.java index fd5e407f..fb36013f 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/safety/BlockColorsMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/safety/BlockColorsMixin.java @@ -16,11 +16,11 @@ import java.util.concurrent.locks.ReentrantLock; public class BlockColorsMixin { private Lock mapLock = new ReentrantLock(); @Inject(method = "register", at = @At("HEAD")) - private void lockMapBeforeAccess(BlockColor pBlockColor, Block[] pBlocks, CallbackInfo ci) { + private void lockMapBeforeAccess(CallbackInfo ci) { mapLock.lock(); } @Inject(method = "register", at = @At("TAIL")) - private void unlockMap(BlockColor pBlockColor, Block[] pBlocks, CallbackInfo ci) { + private void unlockMap(CallbackInfo ci) { mapLock.unlock(); } } diff --git a/src/main/java/org/embeddedt/modernfix/mixin/safety/ItemColorsMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/safety/ItemColorsMixin.java new file mode 100644 index 00000000..628fb721 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/safety/ItemColorsMixin.java @@ -0,0 +1,23 @@ +package org.embeddedt.modernfix.mixin.safety; + +import net.minecraft.client.color.item.ItemColors; +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.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +@Mixin(value = ItemColors.class, priority = 700) +public class ItemColorsMixin { + private Lock mapLock = new ReentrantLock(); + @Inject(method = "register", at = @At("HEAD")) + private void lockMapBeforeAccess(CallbackInfo ci) { + mapLock.lock(); + } + @Inject(method = "register", at = @At("TAIL")) + private void unlockMap(CallbackInfo ci) { + mapLock.unlock(); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/registry/ObjectHolderClearer.java b/src/main/java/org/embeddedt/modernfix/registry/ObjectHolderClearer.java new file mode 100644 index 00000000..ca1eee0d --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/registry/ObjectHolderClearer.java @@ -0,0 +1,47 @@ +package org.embeddedt.modernfix.registry; + +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.fml.util.ObfuscationReflectionHelper; +import net.minecraftforge.registries.ObjectHolderRegistry; +import org.embeddedt.modernfix.ModernFix; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Predicate; + +public class ObjectHolderClearer { + /** + * Many of the built-in Forge holders needlessly hold on to an exception. + */ + public static void clearThrowables() { + Set>> holders = ObfuscationReflectionHelper.getPrivateValue(ObjectHolderRegistry.class, null, "objectHolders"); + if(holders != null) { + int numCleared = 0; + HashMap, Field> throwableField = new HashMap<>(); + Throwable singletonThrowable = new Throwable("[This stacktrace was cleared to save memory]"); + try { + for(Consumer> holder : holders) { + Field target = throwableField.computeIfAbsent(holder.getClass(), clz -> { + Field[] clzFields = clz.getDeclaredFields(); + for(Field f : clzFields) { + if(Throwable.class.isAssignableFrom(f.getType())) { + f.setAccessible(true); + return f; + } + } + return null; + }); + if(target != null) { + target.set(holder, singletonThrowable); + numCleared++; + } + } + } catch(RuntimeException | ReflectiveOperationException | NoClassDefFoundError ignored) { + } + ModernFix.LOGGER.debug("Cleared " + numCleared + " object holder stacktrace references"); + } + } +} diff --git a/src/main/java/org/embeddedt/modernfix/util/CachedResourcePath.java b/src/main/java/org/embeddedt/modernfix/util/CachedResourcePath.java new file mode 100644 index 00000000..5b32b41f --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/util/CachedResourcePath.java @@ -0,0 +1,101 @@ +package org.embeddedt.modernfix.util; + +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Interner; +import com.google.common.collect.Interners; +import com.google.common.collect.Streams; + +import java.lang.ref.WeakReference; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class CachedResourcePath { + private final String[] pathComponents; + private int hashCode = 0; + + private static final Interner PATH_COMPONENT_INTERNER = Interners.newStrongInterner(); + private static final Splitter SLASH_SPLITTER = Splitter.on('/'); + private static final Joiner SLASH_JOINER = Joiner.on('/'); + private WeakReference fullPathCache = new WeakReference<>(null); + private static final String[] NO_PREFIX = new String[0]; + + public CachedResourcePath(Path path) { + this(NO_PREFIX, path, path.getNameCount()); + } + + public CachedResourcePath(String s) { + // normalize so we can guarantee there are no empty sections + this(NO_PREFIX, SLASH_SPLITTER.splitToList(FileUtil.normalize(s))); + } + + public CachedResourcePath(String[] prefixElements, Collection collection) { + this(prefixElements, collection, collection.size()); + } + + public CachedResourcePath(String[] prefixElements, Iterable path, int count) { + String[] components = new String[prefixElements.length + count]; + int i = 0; + while(i < prefixElements.length) { + components[i] = PATH_COMPONENT_INTERNER.intern(prefixElements[i]); + i++; + } + for(Object component : path) { + String s = component.toString(); + if(s.length() == 0) + continue; + components[i] = PATH_COMPONENT_INTERNER.intern(s); + i++; + } + pathComponents = components; + } + + public CachedResourcePath(String[] prefixElements, CachedResourcePath other) { + String[] components = new String[prefixElements.length + other.pathComponents.length]; + int i = 0; + while(i < prefixElements.length) { + components[i] = PATH_COMPONENT_INTERNER.intern(prefixElements[i]); + i++; + } + System.arraycopy(other.pathComponents, 0, components, i, other.pathComponents.length); + pathComponents = components; + } + + @Override + public int hashCode() { + int result = hashCode; + if(result != 0) + return result; + hashCode = Arrays.hashCode(pathComponents); + return hashCode; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CachedResourcePath that = (CachedResourcePath) o; + return Arrays.equals(pathComponents, that.pathComponents); + } + + public String getFileName() { + return pathComponents[pathComponents.length - 1]; + } + + public int getNameCount() { + return pathComponents.length; + } + + public String getFullPath() { + String fPath = fullPathCache.get(); + if(fPath == null) { + fPath = SLASH_JOINER.join(pathComponents); + fullPathCache = new WeakReference<>(fPath); + } + return fPath; + } +} diff --git a/src/main/java/org/embeddedt/modernfix/util/ClassInfoManager.java b/src/main/java/org/embeddedt/modernfix/util/ClassInfoManager.java new file mode 100644 index 00000000..34a6d53f --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/util/ClassInfoManager.java @@ -0,0 +1,31 @@ +package org.embeddedt.modernfix.util; + +import org.embeddedt.modernfix.core.ModernFixMixinPlugin; +import org.spongepowered.asm.mixin.transformer.ClassInfo; + +import java.lang.reflect.Field; +import java.util.Iterator; +import java.util.Map; + +public class ClassInfoManager { + private static Map classInfoCache = null; + public static void clear() { + if(!ModernFixMixinPlugin.instance.isOptionEnabled("perf.clear_mixin_classinfo.ClassInfoManager")) + return; + if(classInfoCache == null) { + try { + Field field = ClassInfo.class.getDeclaredField("cache"); + field.setAccessible(true); + classInfoCache = (Map)field.get(null); + } catch(ReflectiveOperationException | RuntimeException e) { + e.printStackTrace(); + return; + } + } + try { + classInfoCache.entrySet().removeIf(entry -> !entry.getKey().equals("java/lang/Object") && (entry.getValue() == null || !entry.getValue().isMixin())); + } catch(RuntimeException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/org/embeddedt/modernfix/util/PackTypeHelper.java b/src/main/java/org/embeddedt/modernfix/util/PackTypeHelper.java new file mode 100644 index 00000000..13a76311 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/util/PackTypeHelper.java @@ -0,0 +1,9 @@ +package org.embeddedt.modernfix.util; + +import net.minecraft.server.packs.PackType; + +public class PackTypeHelper { + public static boolean isVanillaPackType(PackType type) { + return type == PackType.CLIENT_RESOURCES || type == PackType.SERVER_DATA; + } +} diff --git a/src/main/java/org/embeddedt/modernfix/world/IntegratedWatchdog.java b/src/main/java/org/embeddedt/modernfix/world/IntegratedWatchdog.java index c0e8f5a9..e7c9afd8 100644 --- a/src/main/java/org/embeddedt/modernfix/world/IntegratedWatchdog.java +++ b/src/main/java/org/embeddedt/modernfix/world/IntegratedWatchdog.java @@ -9,25 +9,29 @@ import org.slf4j.Logger; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; +import java.lang.ref.WeakReference; import java.util.concurrent.TimeUnit; public class IntegratedWatchdog extends Thread { private static final Logger LOGGER = LogUtils.getLogger(); - private final MinecraftServer server; + private final WeakReference server; private static final long MAX_TICK_DELTA = 40*1000; public IntegratedWatchdog(MinecraftServer server) { - this.server = server; + this.server = new WeakReference<>(server); this.setDaemon(true); this.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandlerWithName(LOGGER)); this.setName("ModernFix integrated server watchdog"); } public void run() { - while(server.isRunning()) { - long nextTick = this.server.getNextTickTime(); + while(true) { + MinecraftServer server = this.server.get(); + if(server == null || !server.isRunning()) + return; + long nextTick = server.getNextTickTime(); long curTime = Util.getMillis(); long delta = curTime - nextTick; if(delta > MAX_TICK_DELTA) { @@ -53,6 +57,7 @@ public class IntegratedWatchdog extends Thread { nextTick = 0; curTime = 0; } + server = null; /* allow GC */ try { Thread.sleep(nextTick + MAX_TICK_DELTA - curTime); } catch(InterruptedException ignored) { diff --git a/src/main/resources/modernfix.mixins.json b/src/main/resources/modernfix.mixins.json index f1387d17..264687ff 100644 --- a/src/main/resources/modernfix.mixins.json +++ b/src/main/resources/modernfix.mixins.json @@ -7,6 +7,7 @@ "refmap": "modernfix.refmap.json", "mixins": [ "bugfix.edge_chunk_not_saved.ChunkManagerMixin", + "perf.dynamic_structure_manager.StructureManagerMixin", "bugfix.chunk_deadlock.ServerChunkCacheMixin", "perf.remove_biome_temperature_cache.BiomeMixin", "perf.reduce_blockstate_cache_rebuilds.GameDataMixin", @@ -31,6 +32,8 @@ "perf.state_definition_construct.StateDefinitionMixin", "perf.compress_blockstate.BlockStateBaseMixin", "perf.compress_blockstate.BlockBehaviourMixin", + "perf.dedup_blockstate_flattening_map.BlockStateDataMixin", + "perf.dedup_blockstate_flattening_map.ChunkPalettedStorageFixMixin", "devenv.MinecraftServerMixin" ], "client": [ @@ -38,6 +41,7 @@ "core.SynchedEntityDataMixin", "feature.measure_time.MinecraftMixin", "bugfix.concurrency.MinecraftMixin", + "perf.dynamic_resources.BlockElementFaceDeserializerMixin", "perf.dynamic_resources.BlockModelShaperMixin", "perf.dynamic_resources.ItemModelShaperMixin", "perf.dynamic_resources.ModelBakerImplMixin", @@ -53,6 +57,7 @@ "perf.model_optimizations.PropertyMixin", "perf.thread_priorities.IntegratedServerMixin", "safety.BlockColorsMixin", + "safety.ItemColorsMixin", "perf.flatten_model_predicates.AndConditionMixin", "perf.flatten_model_predicates.OrConditionMixin", "perf.flatten_model_predicates.PropertyValueConditionMixin",