Skip to content

Commit e81dbeb

Browse files
Merge embeddedt:26.1 to coredex-source:neoforge/26.2
* Fix random CMEs from NightConfigWatchThrottler * Add feature level system for mixins * Optimize TerraBlender using extended surface biome context Supersedes TerraBlenderFix * Log the state of each mixin at DEBUG level * Fix mixin AP complaints * Fix mixin failing at runtime due to missing AT * Fix stability level being impossible to override * Improve accuracy of possible biomes check * Fix potential stronghold cache corruption if player exits world too quickly * Avoid blocking chunk generation on concentric rings calculation where possible * Run stronghold gen on dedicated thread pool * Optimize ZIP resource packs significantly * Improvements to ZipPackIndex - Allow it to work on channels that don't support mapping - Skip indexing folders that are not part of a pack type * Fix potential crash during worldgen with release_protochunks enabled The crash can occur if a protochunk next to a FULL chunk is dropped, and then later re-requested. If it was not persisted to disk for any reason, it starts regeneration from scratch. At FEATURES stage, it may try to place blocks into the adjacent LevelChunk already in the world. The fix is to prevent this situation from even happening by pinning protochunks directly next to FULL chunks, and preventing them from unloading. * Fix ImposterProtoChunk leaking live block entities to worldgen * Improve ZipPackIndex * Fix Forge pack finder being injected multiple times into pack repository * Fix Forge calling getResource on every loot table unnecessarily * Allow ZipPackIndex to work with any byte channel * Remove the item stack reference thread * Add experimental KubeJS memory usage optimization * Fix an instance of vanilla leaking a BufferBuilder * Allow feature level requirement to be set at package level * Remove error when missing_block_entities sees null BE Blocks may legitimately not have a block entity for some states * Fix optimize_surface_rules breaking mods that provide custom BiomeManagers * Fix blast_search_trees not working as expected with custom creative search tabs The fix is two parts: - The JEI search tree now filters results to ensure they belong to the expected tab - A fallback is added for modded search tabs whose items don't properly appear in JEI. In this case, the optimization is disabled on the problematic tab. This fallback is not applied to the main search tab, even though that is technically more correct, as it would defeat the purpose of the optimization. Fixes embeddedt#669 * Improve emulation of genuinely missing models Fixes embeddedt#670 * Make generated capability providers more memory and JIT compilation friendly We now use LOOKUPSWITCH over capability identity hashes rather than an immutable map. This technique is inspired by how the JDK used to compile string switches: https://mail.openjdk.org/pipermail/amber-spec-observers/2018-April/000568.html As a nice side effect, this allows us to get rid of the map per provider. The map is essentially "inlined" into the bytecode. * Add simple utility script to find FeatureLevel-gated code Assisted-by: DeepSeek V4 Flash * Add support for config options that are not booleans * Make the active feature level customizable via modernfix-mixins.properties * Add guidance about stability level to config * Enable several more options when beta stability is selected * Do not assume WorldGenRegion's center is the currently generating chunk Distant Horizons does not preserve this invariant. Thanks to ZenXArch for flagging this. --------- Co-authored-by: embeddedt <42941056+embeddedt@users.noreply.github.com>
1 parent 4611771 commit e81dbeb

31 files changed

Lines changed: 1279 additions & 155 deletions
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package org.embeddedt.modernfix.annotation;
2+
3+
public enum FeatureLevel {
4+
GA, BETA;
5+
6+
public boolean isAtLeast(FeatureLevel required) {
7+
return this.ordinal() >= required.ordinal();
8+
}
9+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.embeddedt.modernfix.annotation;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Retention(RetentionPolicy.CLASS)
9+
@Target({ElementType.TYPE, ElementType.PACKAGE})
10+
public @interface RequiresFeatureLevel {
11+
FeatureLevel value() default FeatureLevel.GA;
12+
}

annotations/src/main/java/org/embeddedt/modernfix/annotation/RequiresMod.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import java.lang.annotation.Target;
77

88
@Retention(RetentionPolicy.CLASS)
9-
@Target(ElementType.TYPE)
9+
@Target({ElementType.TYPE, ElementType.PACKAGE})
1010
public @interface RequiresMod {
1111
String value() default "";
1212
}

build.gradle.kts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,15 +118,41 @@ dependencies {
118118
compileOnly("curse.maven:cofhcore-69162:5374122")
119119
compileOnly("curse.maven:resourcefullib-570073:5659871")
120120
compileOnly("curse.maven:kubejs-238086:5853326")
121+
compileOnly("curse.maven:terrablender-neoforge-940057:8046313")
121122
}
122123

123124
tasks.named<Jar>("jar") {
124125
from(embed.map { if (it.isDirectory) it else zipTree(it) })
125126
}
126127

128+
val checkDanglingMixinPackageInfo by tasks.registering {
129+
val mixinDir = file("src/main/java/org/embeddedt/modernfix/common/mixin")
130+
inputs.dir(mixinDir)
131+
doLast {
132+
val dangling = mutableListOf<File>()
133+
mixinDir.walkTopDown()
134+
.filter { it.name == "package-info.java" }
135+
.forEach { pkgInfo ->
136+
val dir = pkgInfo.parentFile
137+
val hasOtherJava = dir.listFiles { f -> f.name != "package-info.java" && f.extension == "java" }?.isNotEmpty() == true
138+
val hasSubpackage = dir.listFiles { f -> f.isDirectory }?.isNotEmpty() == true
139+
if (!hasOtherJava && !hasSubpackage) {
140+
dangling += pkgInfo
141+
}
142+
}
143+
if (dangling.isNotEmpty()) {
144+
throw GradleException(
145+
"Dangling package-info.java files found (no sibling classes or subpackages):\n" +
146+
dangling.joinToString("\n") { " ${it.relativeTo(projectDir)}" }
147+
)
148+
}
149+
}
150+
}
151+
127152
// For the AP
128153
tasks.withType<JavaCompile>().configureEach {
129154
if (!name.lowercase().contains("test")) {
155+
dependsOn(checkDanglingMixinPackageInfo)
130156
options.compilerArgs.addAll(
131157
listOf(
132158
"-ArootProject.name=${rootProject.name}",

scripts/feature-gates.sh

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/env bash
2+
# Show FeatureLevel gates, excluding permanent infrastructure.
3+
# If a level is given, show only gates at that level.
4+
#
5+
# Usage:
6+
# ./scripts/feature-gates.sh # all FeatureLevel gates
7+
# ./scripts/feature-gates.sh beta # only BETA gates
8+
# ./scripts/feature-gates.sh ga # only GA gates
9+
10+
set -euo pipefail
11+
12+
# Permanent infrastructure — these are NOT gates to remove when promoting β → GA.
13+
PERMANENT=(
14+
"ModernFixMixinPlugin\.java.*!= FeatureLevel\.GA"
15+
"ModernFixEarlyConfig\.java.*return FeatureLevel\.GA"
16+
"ModernFixEarlyConfig\.java.*FeatureLevel requiredLevel = FeatureLevel\.GA"
17+
)
18+
19+
join() { local IFS='|'; echo "$*"; }
20+
21+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
22+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
23+
24+
cd "$REPO_ROOT"
25+
26+
if [ $# -ge 1 ]; then
27+
LEVEL="${1^^}"
28+
PATTERN="FeatureLevel\.${LEVEL}"
29+
else
30+
PATTERN="FeatureLevel\.[A-Z]+"
31+
fi
32+
33+
git grep -rE "$PATTERN" src/ | grep -v -E "$(join "${PERMANENT[@]}")" || true

src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/missing_block_entities/LevelChunkMixin.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,9 @@ private void makeBlockEntityIfNotExists(BlockState state, BlockPos.MutableBlockP
8585
}
8686

8787
BlockEntity blockEntity = this.getBlockEntity(pos.immutable(), LevelChunk.EntityCreationType.IMMEDIATE);
88-
String blockName = state.getBlock().toString();
89-
if (blockEntity != null) {
90-
if (ModernFix.LOGGER.isDebugEnabled()) {
91-
ModernFix.LOGGER.debug("Created missing block entity for {} at {}", blockName, pos.toShortString());
92-
}
93-
} else {
94-
ModernFix.LOGGER.error("Block entity is missing for {} at {}, but could not be created", blockName, pos.toShortString());
88+
if (blockEntity != null && ModernFix.LOGGER.isDebugEnabled()) {
89+
String blockName = state.getBlock().toString();
90+
ModernFix.LOGGER.debug("Created missing block entity for {} at {}", blockName, pos.toShortString());
9591
}
9692
}
9793
}

src/main/java/org/embeddedt/modernfix/common/mixin/perf/cache_strongholds/ChunkGeneratorMixin.java

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22

33
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
44
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
5+
import com.llamalad7.mixinextras.sugar.Share;
6+
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
7+
import net.minecraft.TracingExecutor;
58
import net.minecraft.core.Holder;
6-
import net.minecraft.core.RegistryAccess;
79
import net.minecraft.nbt.*;
810
import net.minecraft.resources.RegistryOps;
11+
import net.minecraft.server.MinecraftServer;
912
import net.minecraft.util.Util;
1013
import net.minecraft.world.level.ChunkPos;
1114
import net.minecraft.world.level.biome.BiomeSource;
@@ -17,6 +20,8 @@
1720
import org.spongepowered.asm.mixin.Final;
1821
import org.spongepowered.asm.mixin.Mixin;
1922
import org.spongepowered.asm.mixin.Shadow;
23+
import org.spongepowered.asm.mixin.injection.At;
24+
import org.spongepowered.asm.mixin.injection.Redirect;
2025

2126
import java.lang.ref.SoftReference;
2227
import java.nio.charset.StandardCharsets;
@@ -29,6 +34,8 @@
2934
import java.util.List;
3035
import java.util.Map;
3136
import java.util.concurrent.CompletableFuture;
37+
import java.util.concurrent.ExecutorService;
38+
import java.util.concurrent.Executors;
3239

3340
@Mixin(ChunkGeneratorStructureState.class)
3441
public class ChunkGeneratorMixin implements IChunkGenerator {
@@ -41,22 +48,24 @@ public class ChunkGeneratorMixin implements IChunkGenerator {
4148
private BiomeSource biomeSource;
4249

4350
private Path mfix$dimensionPath;
44-
private RegistryAccess.Frozen mfix$registryAccess;
51+
private MinecraftServer mfix$server;
52+
4553
private SoftReference<Map<String, List<ChunkPos>>> mfix$cachedPositions = new SoftReference<>(null);
4654

4755
private static final String CACHE_FILENAME = "mfix_stronghold_cache_v2.nbt";
4856

4957
@Override
50-
public void mfix$setStrongholdCachePath(Path cachePath, RegistryAccess.Frozen registryAccess) {
58+
public void mfix$setStrongholdCachePath(Path cachePath, MinecraftServer server) {
5159
this.mfix$dimensionPath = cachePath;
52-
this.mfix$registryAccess = registryAccess;
60+
this.mfix$server = server;
5361
}
5462

5563
@WrapMethod(method = "generateRingPositions")
5664
private CompletableFuture<List<ChunkPos>> modernfix$cacheRingPositions(Holder<StructureSet> structureSet,
57-
ConcentricRingsStructurePlacement placement,
58-
Operation<CompletableFuture<List<ChunkPos>>> original) {
59-
if (this.mfix$registryAccess == null || this.mfix$dimensionPath == null) {
65+
ConcentricRingsStructurePlacement placement,
66+
Operation<CompletableFuture<List<ChunkPos>>> original,
67+
@Share("threadPool") LocalRef<TracingExecutor> threadPoolRef) {
68+
if (this.mfix$server == null || this.mfix$dimensionPath == null) {
6069
return original.call(structureSet, placement);
6170
}
6271

@@ -69,14 +78,35 @@ public class ChunkGeneratorMixin implements IChunkGenerator {
6978
return CompletableFuture.completedFuture(List.copyOf(cached));
7079
}
7180

72-
return original.call(structureSet, placement).thenApplyAsync(positions -> {
73-
mfix$writeToCache(cacheKey, positions);
74-
return positions;
75-
}, Util.ioPool());
81+
var server = this.mfix$server;
82+
ExecutorService strongholdPool = Executors.newFixedThreadPool(Math.max(1, Runtime.getRuntime().availableProcessors() - 2));
83+
threadPoolRef.set(new TracingExecutor(strongholdPool));
84+
try {
85+
return original.call(structureSet, placement).thenApplyAsync(positions -> {
86+
// Skip write if server exited before we finished
87+
if (server.isRunning()) {
88+
mfix$writeToCache(cacheKey, positions);
89+
}
90+
return positions;
91+
}, Util.ioPool());
92+
} finally {
93+
strongholdPool.shutdown();
94+
}
95+
}
96+
97+
/**
98+
* @author embeddedt
99+
* @reason Ring position calculation is often not required for initial chunk generation, but the tasks still occupy
100+
* CPU time on the main worker pool and prevent higher priority work from progressing. To fix this we use a
101+
* dedicated pool.
102+
*/
103+
@Redirect(method = "generateRingPositions", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Util;backgroundExecutor()Lnet/minecraft/TracingExecutor;"))
104+
private TracingExecutor useDedicatedService(@Share("threadPool") LocalRef<TracingExecutor> threadPoolRef) {
105+
return threadPoolRef.get();
76106
}
77107

78108
private String mfix$makeCacheKey(ConcentricRingsStructurePlacement placement) {
79-
RegistryOps<Tag> ops = RegistryOps.create(NbtOps.INSTANCE, this.mfix$registryAccess);
109+
RegistryOps<Tag> ops = RegistryOps.create(NbtOps.INSTANCE, this.mfix$server.registryAccess());
80110
String placementKey = ConcentricRingsStructurePlacement.CODEC.codec().encodeStart(ops, placement)
81111
.result().map(Tag::toString).orElse(null);
82112
String biomeSourceKey = BiomeSource.CODEC.encodeStart(ops, this.biomeSource)
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package org.embeddedt.modernfix.common.mixin.perf.cache_strongholds;
2+
3+
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
4+
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
5+
import org.embeddedt.modernfix.annotation.FeatureLevel;
6+
import org.embeddedt.modernfix.annotation.RequiresFeatureLevel;
7+
import org.spongepowered.asm.mixin.Final;
8+
import org.spongepowered.asm.mixin.Mixin;
9+
import org.spongepowered.asm.mixin.Shadow;
10+
import org.spongepowered.asm.mixin.Unique;
11+
import org.spongepowered.asm.mixin.injection.At;
12+
import org.spongepowered.asm.mixin.injection.Inject;
13+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
14+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
15+
16+
@Mixin(ConcentricRingsStructurePlacement.class)
17+
@RequiresFeatureLevel(FeatureLevel.BETA)
18+
public class ConcentricRingsStructurePlacementMixin {
19+
20+
@Shadow @Final private int distance;
21+
@Shadow @Final private int spread;
22+
@Shadow @Final private int count;
23+
24+
/**
25+
* Maximum per-axis section displacement from the initial ring chunk after biome snapping.
26+
*
27+
* Vanilla calls findBiomeHorizontal with radius=112 blocks. In quart space this is ±28,
28+
* and converting the selected quart back to section coordinates yields at most ±7 chunks
29+
* per axis from the original (initialX, initialZ).
30+
*/
31+
@Unique private static final int MFIX_MAX_BIOME_SNAP_SECTIONS_PER_AXIS = 7;
32+
/**
33+
* Worst-case Euclidean error introduced by rounding:
34+
* initialX/Z = round(cos(angle) * dist), round(sin(angle) * dist).
35+
*/
36+
@Unique private static final double MFIX_MAX_ROUNDING_ERROR = Math.sqrt(2.0) * 0.5;
37+
/**
38+
* Worst-case Euclidean biome-snap displacement when each axis can move by at most 7 chunks.
39+
*/
40+
@Unique private static final double MFIX_MAX_BIOME_SNAP_ERROR = MFIX_MAX_BIOME_SNAP_SECTIONS_PER_AXIS * Math.sqrt(2.0);
41+
/**
42+
* Total conservative positional slack (rounding + biome snap) applied to radial bounds.
43+
*/
44+
@Unique private static final double MFIX_MAX_POSITION_ERROR = MFIX_MAX_ROUNDING_ERROR + MFIX_MAX_BIOME_SNAP_ERROR;
45+
46+
/** Squared chunk-distance below which no ring position can ever land. */
47+
@Unique private long mfix$innerRadiusSq;
48+
/** Squared chunk-distance above which no ring position can ever land. */
49+
@Unique private long mfix$outerRadiusSq;
50+
51+
/**
52+
* Precomputes conservative radial bounds for vanilla's ring placement distance:
53+
* {@code dist = 4*i + i*i1*6 + noise}, where {@code i=distance} and {@code i1=circle}.
54+
*
55+
* - Inner bound uses the minimum possible base term ({@code i1=0} => {@code 4*i}).
56+
* - Outer bound uses the maximum reachable {@code i1} for this ({@code spread,count}) pair.
57+
*
58+
* Both bounds are expanded by {@link #MFIX_MAX_POSITION_ERROR} so we never reject a valid
59+
* chunk produced by rounding and biome snapping.
60+
*/
61+
@Inject(
62+
method = "<init>(Lnet/minecraft/core/Vec3i;Lnet/minecraft/world/level/levelgen/structure/placement/StructurePlacement$FrequencyReductionMethod;FILjava/util/Optional;IIILnet/minecraft/core/HolderSet;)V",
63+
at = @At("RETURN")
64+
)
65+
private void mfix$computeRadiusBounds(CallbackInfo ci) {
66+
double maxNoise = this.distance * 1.25; // (nextDouble() - 0.5) * (distance * 2.5)
67+
68+
// min(dist): 4*i + i*0*6 - maxNoise
69+
double minDist = 4.0 * this.distance - maxNoise;
70+
double safeInnerRadius = minDist - MFIX_MAX_POSITION_ERROR;
71+
this.mfix$innerRadiusSq = (long)Math.max(0.0, Math.floor(safeInnerRadius * safeInnerRadius));
72+
73+
if (this.spread == 0) {
74+
// Vanilla behavior becomes non-finite here (angle += 2π / 0), so keep only inner rejection.
75+
this.mfix$outerRadiusSq = Long.MAX_VALUE;
76+
return;
77+
}
78+
79+
int maxCircle = this.mfix$computeMaxCircleIndex();
80+
// max(dist): 4*i + i*maxCircle*6 + maxNoise
81+
double maxDist = 4.0 * this.distance + (double)this.distance * maxCircle * 6.0 + maxNoise;
82+
double safeOuterRadius = maxDist + MFIX_MAX_POSITION_ERROR;
83+
this.mfix$outerRadiusSq = (long)Math.ceil(safeOuterRadius * safeOuterRadius);
84+
}
85+
86+
/**
87+
* Computes the highest ring index ({@code circle}) that vanilla can reach for this placement.
88+
*
89+
* This mirrors the spread/total update logic in
90+
* {@link net.minecraft.world.level.chunk.ChunkGeneratorStructureState#generateRingPositions},
91+
* but only tracks deterministic loop state (no RNG).
92+
*/
93+
@Unique
94+
private int mfix$computeMaxCircleIndex() {
95+
int ringSpread = this.spread;
96+
int total = 0;
97+
int circle = 0;
98+
99+
while (total + ringSpread < this.count) {
100+
total += ringSpread;
101+
circle++;
102+
ringSpread += 2 * ringSpread / (circle + 1);
103+
ringSpread = Math.min(ringSpread, this.count - total);
104+
}
105+
106+
return circle;
107+
}
108+
109+
/**
110+
* @author embeddedt, GPT-5.3-Codex
111+
* @reason Avoid calling getRingPositionsFor() when we know the current chunk lies outside the region where
112+
* concentric placement can even happen. This is particularly helpful when creating new worlds, because we can
113+
* avoid blocking on the slow noise computations within the spawn region around (0, 0).
114+
*/
115+
@Inject(method = "isPlacementChunk", at = @At("HEAD"), cancellable = true)
116+
private void mfix$earlyRejectByRadius(ChunkGeneratorStructureState structureState, int x, int z,
117+
CallbackInfoReturnable<Boolean> cir) {
118+
long distSq = (long)x * x + (long)z * z;
119+
if (distSq < this.mfix$innerRadiusSq || distSq > this.mfix$outerRadiusSq) {
120+
cir.setReturnValue(false);
121+
}
122+
}
123+
}

src/main/java/org/embeddedt/modernfix/common/mixin/perf/cache_strongholds/ServerLevelMixin.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ private void setCachePath(ChunkGeneratorStructureState instance, Operation<Void>
2424
@Local(ordinal = 0, argsOnly = true) LevelStorageSource.LevelStorageAccess levelStorageAccess,
2525
@Local(ordinal = 0, argsOnly = true) ResourceKey<Level> dimension,
2626
@Local(ordinal = 0, argsOnly = true) MinecraftServer server) {
27-
((IChunkGenerator)instance).mfix$setStrongholdCachePath(levelStorageAccess.getDimensionPath(dimension), server.registryAccess());
27+
((IChunkGenerator)instance).mfix$setStrongholdCachePath(levelStorageAccess.getDimensionPath(dimension), server);
2828
original.call(instance);
2929
}
3030
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@RequiresFeatureLevel(FeatureLevel.BETA)
2+
package org.embeddedt.modernfix.common.mixin.perf.compact_entity_models;
3+
4+
import org.embeddedt.modernfix.annotation.FeatureLevel;
5+
import org.embeddedt.modernfix.annotation.RequiresFeatureLevel;

0 commit comments

Comments
 (0)