diff --git a/build.gradle b/build.gradle index 69b2acff..650ad0d1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id "architectury-plugin" version "3.4-SNAPSHOT" - id "dev.architectury.loom" version "1.7-SNAPSHOT" apply false + id "dev.architectury.loom" version "1.9-SNAPSHOT" apply false id "maven-publish" id 'com.matthewprenger.cursegradle' version '1.4.0' apply false id 'com.palantir.git-version' version '1.0.0' diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/chunk_deadlock/EntityMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/chunk_deadlock/EntityMixin.java new file mode 100644 index 00000000..1cf541f0 --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/chunk_deadlock/EntityMixin.java @@ -0,0 +1,31 @@ +package org.embeddedt.modernfix.common.mixin.bugfix.chunk_deadlock; + +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; +import net.minecraft.core.Holder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.gameevent.GameEvent; +import org.embeddedt.modernfix.ModernFix; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(Entity.class) +public class EntityMixin { + /** + * @author embeddedt + * @reason When an entity is added to the world via the worldgen load path (ChunkMap#postLoadProtoChunk calling + * ServerLevel#addWorldGenChunkEntities), attempts to add a passenger result in a deadlock when the sculk event + * tries to raytrace blocks. To fix this, we skip firing the sculk event if the chunk the entity is within is not + * loaded. + */ + @WrapWithCondition(method = "addPassenger", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;gameEvent(Lnet/minecraft/core/Holder;Lnet/minecraft/world/entity/Entity;)V")) + private boolean onlyAddIfSelfChunkLoaded(Entity instance, Holder gameEvent, Entity entity) { + var chunkPos = instance.chunkPosition(); + if (instance.level() instanceof ServerLevel serverLevel && serverLevel.getChunkSource().getChunkNow(chunkPos.x, chunkPos.z) == null) { + ModernFix.LOGGER.warn("Skipped emitting ENTITY_MOUNT game event for entity {} as it would cause deadlock", instance.toString()); + return false; + } else { + return true; + } + } +} diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/blockentity_incorrect_thread/ChunkAccessMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/blockentity_incorrect_thread/ChunkAccessMixin.java new file mode 100644 index 00000000..1aa9091a --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/blockentity_incorrect_thread/ChunkAccessMixin.java @@ -0,0 +1,34 @@ +package org.embeddedt.modernfix.common.mixin.feature.blockentity_incorrect_thread; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Registry; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.levelgen.blending.BlendingData; +import org.embeddedt.modernfix.util.ConcurrencySanitizingMap; +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; + +@Mixin(ChunkAccess.class) +public class ChunkAccessMixin { + @Shadow @Final @Mutable protected Map blockEntities; + + @Inject(method = "", at = @At("RETURN")) + private void wrapInConcurrencyDetector(ChunkPos chunkPos, UpgradeData upgradeData, LevelHeightAccessor levelHeightAccessor, Registry biomeRegistry, long inhabitedTime, LevelChunkSection[] sections, BlendingData blendingData, CallbackInfo ci) { + if (levelHeightAccessor instanceof Level level) { + this.blockEntities = new ConcurrencySanitizingMap<>(this.blockEntities, ((LevelThreadAccessor)level).getThread()); + } + } +} diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/blockentity_incorrect_thread/LevelThreadAccessor.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/blockentity_incorrect_thread/LevelThreadAccessor.java new file mode 100644 index 00000000..8cd13990 --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/blockentity_incorrect_thread/LevelThreadAccessor.java @@ -0,0 +1,11 @@ +package org.embeddedt.modernfix.common.mixin.feature.blockentity_incorrect_thread; + +import net.minecraft.world.level.Level; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(Level.class) +public interface LevelThreadAccessor { + @Accessor + Thread getThread(); +} diff --git a/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java b/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java index f113e866..01fe319b 100644 --- a/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java +++ b/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java @@ -167,6 +167,7 @@ public class ModernFixEarlyConfig { .put("mixin.perf.worldgen_allocation", false) // experimental .put("mixin.feature.cause_lag_by_disabling_threads", false) .put("mixin.bugfix.missing_block_entities", false) + .put("mixin.feature.blockentity_incorrect_thread", false) .put("mixin.perf.clear_mixin_classinfo", false) .put("mixin.perf.deduplicate_climate_parameters", false) .put("mixin.bugfix.packet_leak", false) diff --git a/common/src/main/java/org/embeddedt/modernfix/util/ConcurrencySanitizingMap.java b/common/src/main/java/org/embeddedt/modernfix/util/ConcurrencySanitizingMap.java new file mode 100644 index 00000000..a0055391 --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/util/ConcurrencySanitizingMap.java @@ -0,0 +1,100 @@ +package org.embeddedt.modernfix.util; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * A map wrapper that throws if the map is ever accessed on the wrong thread. + */ +public class ConcurrencySanitizingMap implements Map { + private final Map map; + private final Thread owner; + + public ConcurrencySanitizingMap(Map map, Thread owner) { + this.map = map; + this.owner = owner; + } + + private void checkThread() { + var current = Thread.currentThread(); + if (current != owner) { + throw new IllegalStateException("Map is being accessed on thread " + current + " while owned by " + owner); + } + } + + @Override + public int size() { + checkThread(); + return map.size(); + } + + @Override + public boolean isEmpty() { + checkThread(); + return map.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + checkThread(); + return map.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + checkThread(); + return map.containsValue(value); + } + + @Override + public V get(Object key) { + checkThread(); + return map.get(key); + } + + @Override + public @Nullable V put(K key, V value) { + checkThread(); + return map.put(key, value); + } + + @Override + public V remove(Object key) { + checkThread(); + return map.remove(key); + } + + @Override + public void putAll(@NotNull Map m) { + checkThread(); + map.putAll(m); + } + + @Override + public void clear() { + checkThread(); + map.clear(); + } + + @Override + public @NotNull Set keySet() { + checkThread(); + return map.keySet(); + } + + @Override + public @NotNull Collection values() { + checkThread(); + return map.values(); + } + + @Override + public @NotNull Set> entrySet() { + checkThread(); + return map.entrySet(); + } +} diff --git a/fabric/src/main/java/org/embeddedt/modernfix/ModernFixPreLaunchFabric.java b/fabric/src/main/java/org/embeddedt/modernfix/ModernFixPreLaunchFabric.java index 596237c3..3a08c5ef 100644 --- a/fabric/src/main/java/org/embeddedt/modernfix/ModernFixPreLaunchFabric.java +++ b/fabric/src/main/java/org/embeddedt/modernfix/ModernFixPreLaunchFabric.java @@ -5,7 +5,6 @@ import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint; import net.fabricmc.loader.impl.gui.FabricGuiEntry; import net.fabricmc.loader.impl.gui.FabricStatusTree; import org.embeddedt.modernfix.core.ModernFixMixinPlugin; -import org.embeddedt.modernfix.fabric.mappings.MappingsClearer; import org.embeddedt.modernfix.spark.SparkLaunchProfiler; import org.embeddedt.modernfix.util.CommonModUtil; @@ -19,9 +18,6 @@ public class ModernFixPreLaunchFabric implements PreLaunchEntrypoint { if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.spark_profile_launch.OnFabric")) { CommonModUtil.runWithoutCrash(() -> SparkLaunchProfiler.start("launch"), "Failed to start profiler"); } - if(ModernFixMixinPlugin.instance.isOptionEnabled("perf.clear_fabric_mapping_tables.MappingsClearer")) { - MappingsClearer.clear(); - } // Prevent launching with Continuity when dynamic resources is on if(false && ModernFixMixinPlugin.instance.isOptionEnabled("perf.dynamic_resources.ContinuityCheck") diff --git a/fabric/src/main/java/org/embeddedt/modernfix/fabric/mappings/MappingsClearer.java b/fabric/src/main/java/org/embeddedt/modernfix/fabric/mappings/MappingsClearer.java deleted file mode 100644 index 1425c562..00000000 --- a/fabric/src/main/java/org/embeddedt/modernfix/fabric/mappings/MappingsClearer.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.embeddedt.modernfix.fabric.mappings; - -import net.fabricmc.loader.api.*; -import net.fabricmc.loader.impl.launch.FabricLauncherBase; -import net.fabricmc.loader.impl.launch.MappingConfiguration; -import org.embeddedt.modernfix.util.CommonModUtil; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.util.Map; -import java.util.Optional; - -/** - * Designed for Fabric Loader 0.14.x, probably has issues on other versions. The entire thing is wrapped in a try-catch - * so it should never cause crashes, just fail to work. - */ -public class MappingsClearer { - private static final Version LOADER_015; - - static { - try { - LOADER_015 = Version.parse("0.15.0"); - } catch (VersionParsingException e) { - throw new RuntimeException(e); - } - } - - @SuppressWarnings("unchecked") - public static void clear() { - // TODO: Port this to Fabric Loader 0.15 - - if(true) //FabricLoader.getInstance().isDevelopmentEnvironment()) - return; // never do this in dev - - Optional loaderVersion = FabricLoader.getInstance().getModContainer("fabricloader").map(m -> m.getMetadata().getVersion()); - if(!loaderVersion.isPresent() || LOADER_015.compareTo(loaderVersion.get()) < 0) { - // Fabric Loader is probably too new, abort - return; - } - - CommonModUtil.runWithoutCrash(() -> { - // For now, force the mapping resolver to be initialized. Fabric Loader 0.14.23 stops initializing it early, - // which means that we actually need to keep the TinyMappingFactory tree around for initialization to work - // later. We force init it now because then we can store less in memory. - // Comparing heap dumps on 0.14.23 suggests a savings of 20MB by doing it our way, since many mods will - // end up needing the mapping resolver. - // This will need to be revisited when https://github.com/FabricMC/fabric-loader/pull/812 is merged. - FabricLoader.getInstance().getMappingResolver(); - - // clear notch->intermediary mappings - MappingConfiguration config = FabricLauncherBase.getLauncher().getMappingConfiguration(); - Field mappingsField = MappingConfiguration.class.getDeclaredField("mappings"); - mappingsField.setAccessible(true); - //mappingsField.set(config, TinyMappingFactory.EMPTY_TREE); - - // clear useless intermediary->intermediary mappings - MappingResolver resolver = FabricLoader.getInstance().getMappingResolver(); - Class targetResolverClz = Class.forName("net.fabricmc.loader.impl.MappingResolverImpl"); - if(targetResolverClz.isAssignableFrom(resolver.getClass())) { - // hopefully still Loader 0.14.x, proceed - Class namespaceDataClz = Class.forName("net.fabricmc.loader.impl.MappingResolverImpl$NamespaceData"); - Constructor constructor = namespaceDataClz.getDeclaredConstructor(); - constructor.setAccessible(true); - Object theData = constructor.newInstance(); - Field mapField = resolver.getClass().getDeclaredField("namespaceDataMap"); - mapField.setAccessible(true); - Map theMap = (Map)mapField.get(resolver); - theMap.replace("intermediary", theData); - } - }, "Failed to clear mappings"); - } -} diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index c9bbc663..52e808e3 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -42,7 +42,7 @@ ], "depends": { "minecraft": ">=1.16.2", - "fabricloader": ">=0.15.0" + "fabricloader": ">=0.16.10" }, "breaks": { "dashloader": "<5.0.0-beta.1" diff --git a/gradle.properties b/gradle.properties index 3852a2ed..c65e72b1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx2G junit_version=5.10.0-M1 -mixinextras_version=0.3.2 +mixinextras_version=0.4.1 mod_id=modernfix minecraft_version=1.21.4 @@ -19,7 +19,7 @@ kubejs_version=1902.6.0-build.142 rhino_version=1902.2.2-build.268 supported_minecraft_versions=1.21.4 -fabric_loader_version=0.16.9 +fabric_loader_version=0.16.10 fabric_api_version=0.113.0+1.21.4 continuity_version=3.0.0-beta.4+1.20.2 @@ -29,7 +29,7 @@ diagonal_fences_version=4558828 spark_version=5759671 -use_fabric_api_at_runtime=false +use_fabric_api_at_runtime=true # Look up maven coordinates when changing shadow_version shadow_version=7.1.2 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a4413138..e2847c82 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME