diff --git a/src/main/java/org/embeddedt/modernfix/ModernFix.java b/src/main/java/org/embeddedt/modernfix/ModernFix.java index 0c052d3e..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() { @@ -100,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/config/ModernFixEarlyConfig.java b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java index 35f549de..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,6 +37,8 @@ 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); diff --git a/src/main/java/org/embeddedt/modernfix/dynamicresources/UVController.java b/src/main/java/org/embeddedt/modernfix/dynamicresources/UVController.java new file mode 100644 index 00000000..7b2efe7e --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/dynamicresources/UVController.java @@ -0,0 +1,8 @@ +package org.embeddedt.modernfix.dynamicresources; + +import net.minecraft.client.renderer.block.model.BlockFaceUV; + +public class UVController { + public static final ThreadLocal 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/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 3c610504..9b443d6c 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 @@ -38,10 +38,7 @@ import net.minecraftforge.registries.ForgeRegistries; 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.embeddedt.modernfix.dynamicresources.ModelLocationCache; -import org.embeddedt.modernfix.dynamicresources.ResourcePackHandler; +import org.embeddedt.modernfix.dynamicresources.*; import org.slf4j.Logger; import org.spongepowered.asm.mixin.*; import org.spongepowered.asm.mixin.injection.At; @@ -291,10 +288,12 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery { } modelFiles.clear(); CompletableFuture.allOf(modelBytes.toArray(new CompletableFuture[0])).join(); + UVController.useDummyUv.set(Boolean.TRUE); for(CompletableFuture> future : modelBytes) { Pair pair = future.join(); try { if(pair.getSecond() != null) { + BlockModel model = ExtendedBlockModelDeserializer.INSTANCE.fromJson(pair.getSecond(), BlockModel.class); model.name = pair.getFirst().toString(); modelFiles.addAll(model.getDependencies()); @@ -306,6 +305,7 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery { } basicModels.put(pair.getFirst(), (BlockModel)missingModel); } + UVController.useDummyUv.set(Boolean.FALSE); } modelFiles = null; Function modelGetter = loc -> { 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/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/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 1a0f026e..3312dcf9 100644 --- a/src/main/resources/modernfix.mixins.json +++ b/src/main/resources/modernfix.mixins.json @@ -34,6 +34,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": [ @@ -42,6 +44,7 @@ "feature.measure_time.MinecraftMixin", "feature.reduce_loading_screen_freezes.ModelBakeryMixin", "bugfix.concurrency.MinecraftMixin", + "perf.dynamic_resources.BlockElementFaceDeserializerMixin", "perf.dynamic_resources.BlockModelShaperMixin", "perf.dynamic_resources.ItemModelShaperMixin", "perf.dynamic_resources.ModelBakeryMixin", @@ -55,6 +58,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",