Rewrite and improve mixin.perf.cache_strongholds

This commit is contained in:
embeddedt 2026-03-19 20:11:11 -04:00
parent 670e06816b
commit a9340b2642
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
5 changed files with 159 additions and 148 deletions

View File

@ -1,68 +1,169 @@
package org.embeddedt.modernfix.common.mixin.perf.cache_strongholds;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import net.minecraft.Util;
import net.minecraft.core.Holder;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.core.RegistryAccess;
import net.minecraft.nbt.*;
import net.minecraft.resources.RegistryOps;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.levelgen.structure.StructureSet;
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.duck.IChunkGenerator;
import org.embeddedt.modernfix.duck.IServerLevel;
import org.embeddedt.modernfix.world.StrongholdLocationCache;
import org.spongepowered.asm.mixin.Final;
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.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.Shadow;
import java.lang.ref.WeakReference;
import java.lang.ref.SoftReference;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@Mixin(ChunkGeneratorStructureState.class)
public class ChunkGeneratorMixin implements IChunkGenerator {
private WeakReference<ServerLevel> mfix$serverLevel;
@Shadow
@Final
private long concentricRingsSeed;
@Shadow
@Final
private BiomeSource biomeSource;
private Path mfix$dimensionPath;
private RegistryAccess.Frozen mfix$registryAccess;
private SoftReference<Map<String, List<ChunkPos>>> mfix$cachedPositions = new SoftReference<>(null);
private static final String CACHE_FILENAME = "mfix_stronghold_cache_v2.nbt";
@Override
public void mfix$setAssociatedServerLevel(ServerLevel level) {
mfix$serverLevel = new WeakReference<>(level);
public void mfix$setStrongholdCachePath(Path cachePath, RegistryAccess.Frozen registryAccess) {
this.mfix$dimensionPath = cachePath;
this.mfix$registryAccess = registryAccess;
}
@Inject(method = "generateRingPositions", at = @At("HEAD"), cancellable = true)
private void useCachedDataIfAvailable(Holder<StructureSet> structureSet, ConcentricRingsStructurePlacement placement, CallbackInfoReturnable<CompletableFuture<List<ChunkPos>>> cir) {
if(placement.count() == 0)
return;
ServerLevel level = searchLevel();
if(level == null)
return;
StrongholdLocationCache cache = ((IServerLevel)level).mfix$getStrongholdCache();
List<ChunkPos> positions = cache.getChunkPosList();
if(positions.isEmpty())
return;
ModernFix.LOGGER.debug("Loaded stronghold cache for dimension {} with {} positions", level.dimension().location(), positions.size());
cir.setReturnValue(CompletableFuture.completedFuture(positions));
@WrapMethod(method = "generateRingPositions")
private CompletableFuture<List<ChunkPos>> modernfix$cacheRingPositions(Holder<StructureSet> structureSet,
ConcentricRingsStructurePlacement placement,
Operation<CompletableFuture<List<ChunkPos>>> original) {
if (this.mfix$registryAccess == null || this.mfix$dimensionPath == null) {
return original.call(structureSet, placement);
}
String cacheKey = mfix$makeCacheKey(placement);
// Try reading from cache
List<ChunkPos> cached = mfix$readFromCache(cacheKey);
if (cached != null) {
ModernFix.LOGGER.debug("Using cached stronghold positions for {}", cacheKey);
return CompletableFuture.completedFuture(List.copyOf(cached));
}
return original.call(structureSet, placement).thenApplyAsync(positions -> {
mfix$writeToCache(cacheKey, positions);
return positions;
}, Util.ioPool());
}
private ServerLevel searchLevel() {
if(mfix$serverLevel != null)
return mfix$serverLevel.get();
else
private String mfix$makeCacheKey(ConcentricRingsStructurePlacement placement) {
RegistryOps<Tag> ops = RegistryOps.create(NbtOps.INSTANCE, this.mfix$registryAccess);
String placementKey = ConcentricRingsStructurePlacement.CODEC.encodeStart(ops, placement)
.result().map(Tag::toString).orElse(null);
String biomeSourceKey = BiomeSource.CODEC.encodeStart(ops, this.biomeSource)
.result().map(Tag::toString).orElse(null);
if (placementKey == null || biomeSourceKey == null) {
ModernFix.LOGGER.warn("Failed to create cache key for concentric structure placement");
return null;
}
String data = placementKey + ";biomes=" + biomeSourceKey + ";seed=" + this.concentricRingsSeed;
try {
byte[] hash = MessageDigest.getInstance("SHA-256").digest(data.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder(64);
for (byte b : hash) {
sb.append(String.format("%02x", b));
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
return null;
}
}
@Inject(method = "generateRingPositions", at = @At("RETURN"), cancellable = true)
private void saveCachedData(Holder<StructureSet> structureSet, ConcentricRingsStructurePlacement placement, CallbackInfoReturnable<CompletableFuture<List<ChunkPos>>> cir) {
cir.setReturnValue(cir.getReturnValue().thenApplyAsync(list -> {
if(list.size() == 0)
return list;
ServerLevel level = searchLevel();
if(level != null) {
StrongholdLocationCache cache = ((IServerLevel)level).mfix$getStrongholdCache();
cache.setChunkPosList(list);
ModernFix.LOGGER.debug("Saved stronghold cache for dimension {}", level.dimension().location());
private synchronized List<ChunkPos> mfix$readFromCache(String cacheKey) {
Map<String, List<ChunkPos>> cache = mfix$getOrLoadCache();
return cache.get(cacheKey);
}
private synchronized void mfix$writeToCache(String cacheKey, List<ChunkPos> positions) {
Map<String, List<ChunkPos>> cache = mfix$getOrLoadCache();
cache.put(cacheKey, List.copyOf(positions));
mfix$cachedPositions = new SoftReference<>(cache);
mfix$saveCacheFile(cache);
}
private Map<String, List<ChunkPos>> mfix$getOrLoadCache() {
Map<String, List<ChunkPos>> cache = mfix$cachedPositions.get();
if (cache != null) {
return cache;
}
cache = mfix$loadCacheFile();
mfix$cachedPositions = new SoftReference<>(cache);
return cache;
}
private Map<String, List<ChunkPos>> mfix$loadCacheFile() {
Path file = mfix$dimensionPath.resolve(CACHE_FILENAME);
if (!Files.exists(file)) {
return new HashMap<>();
}
try {
CompoundTag root = NbtIo.readCompressed(file.toFile());
Map<String, List<ChunkPos>> result = new HashMap<>();
for (String key : root.getAllKeys()) {
if (root.contains(key, Tag.TAG_INT_ARRAY)) {
int[] data = root.getIntArray(key);
if (data.length >= 2 && data.length % 2 == 0) {
List<ChunkPos> positions = new ArrayList<>(data.length / 2);
for (int i = 0; i < data.length; i += 2) {
positions.add(new ChunkPos(data[i], data[i + 1]));
}
result.put(key, positions);
}
}
}
return list;
}, Util.backgroundExecutor()));
return result;
} catch (Exception e) {
ModernFix.LOGGER.warn("Failed to read stronghold cache, will recompute", e);
return new HashMap<>();
}
}
private void mfix$saveCacheFile(Map<String, List<ChunkPos>> cache) {
CompoundTag root = new CompoundTag();
for (var entry : cache.entrySet()) {
List<ChunkPos> positions = entry.getValue();
int[] data = new int[positions.size() * 2];
for (int i = 0; i < positions.size(); i++) {
ChunkPos pos = positions.get(i);
data[i * 2] = pos.x;
data[i * 2 + 1] = pos.z;
}
root.putIntArray(entry.getKey(), data);
}
Path file = mfix$dimensionPath.resolve(CACHE_FILENAME);
try {
NbtIo.writeCompressed(root, file.toFile());
} catch (Exception e) {
ModernFix.LOGGER.warn("Failed to write stronghold cache", e);
}
}
}

View File

@ -1,61 +1,30 @@
package org.embeddedt.modernfix.common.mixin.perf.cache_strongholds;
import net.minecraft.core.Holder;
import net.minecraft.core.RegistryAccess;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.storage.DimensionDataStorage;
import net.minecraft.world.level.storage.WritableLevelData;
import net.minecraft.world.level.storage.LevelStorageSource;
import org.embeddedt.modernfix.duck.IChunkGenerator;
import org.embeddedt.modernfix.duck.IServerLevel;
import org.embeddedt.modernfix.world.StrongholdLocationCache;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.function.Supplier;
@Mixin(ServerLevel.class)
public abstract class ServerLevelMixin extends Level implements IServerLevel {
protected ServerLevelMixin(WritableLevelData arg, ResourceKey<Level> arg2, RegistryAccess arg3, Holder<DimensionType> arg4, Supplier<ProfilerFiller> supplier, boolean bl, boolean bl2, long l, int i) {
super(arg, arg2, arg3, arg4, supplier, bl, bl2, l, i);
}
@Shadow public abstract DimensionDataStorage getDataStorage();
@Shadow @Final private ServerChunkCache chunkSource;
private StrongholdLocationCache mfix$strongholdCache;
public class ServerLevelMixin {
/**
* Initialize the stronghold cache but don't force any structure generation yet.
* @author embeddedt
* @reason Make the dimension path accessible to ChunkGeneratorStructureState.
*/
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/chunk/ChunkGeneratorStructureState;ensureStructuresGenerated()V"))
private void hookStrongholdCache(ChunkGeneratorStructureState generator) {
((IChunkGenerator)generator).mfix$setAssociatedServerLevel((ServerLevel)(Object)this);
}
/**
* Now start the stronghold generation process.
*/
@Inject(method = "<init>", at = @At("TAIL"))
private void ensureGeneration(CallbackInfo ci) {
mfix$strongholdCache = this.getDataStorage().computeIfAbsent(StrongholdLocationCache::load,
StrongholdLocationCache::new,
StrongholdLocationCache.getFileId(this.dimensionTypeRegistration()));
this.chunkSource.getGeneratorState().ensureStructuresGenerated();
}
@Override
public StrongholdLocationCache mfix$getStrongholdCache() {
return mfix$strongholdCache;
@WrapOperation(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/chunk/ChunkGeneratorStructureState;ensureStructuresGenerated()V"))
private void setCachePath(ChunkGeneratorStructureState instance, Operation<Void> original,
@Local(ordinal = 0, argsOnly = true) LevelStorageSource.LevelStorageAccess levelStorageAccess,
@Local(ordinal = 0, argsOnly = true) ResourceKey<Level> dimension,
@Local(ordinal = 0, argsOnly = true) MinecraftServer server) {
((IChunkGenerator)instance).mfix$setStrongholdCachePath(levelStorageAccess.getDimensionPath(dimension), server.registryAccess());
original.call(instance);
}
}

View File

@ -1,7 +1,9 @@
package org.embeddedt.modernfix.duck;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.core.RegistryAccess;
import java.nio.file.Path;
public interface IChunkGenerator {
void mfix$setAssociatedServerLevel(ServerLevel level);
void mfix$setStrongholdCachePath(Path cachePath, RegistryAccess.Frozen registryAccess);
}

View File

@ -1,7 +0,0 @@
package org.embeddedt.modernfix.duck;
import org.embeddedt.modernfix.world.StrongholdLocationCache;
public interface IServerLevel {
StrongholdLocationCache mfix$getStrongholdCache();
}

View File

@ -1,54 +0,0 @@
package org.embeddedt.modernfix.world;
import net.minecraft.core.Holder;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.saveddata.SavedData;
import java.util.ArrayList;
import java.util.List;
public class StrongholdLocationCache extends SavedData {
private List<ChunkPos> chunkPosList;
public StrongholdLocationCache() {
super();
chunkPosList = new ArrayList<>();
}
public List<ChunkPos> getChunkPosList() {
return new ArrayList<>(chunkPosList);
}
public void setChunkPosList(List<ChunkPos> positions) {
this.chunkPosList = new ArrayList<>(positions);
this.setDirty();
}
public static StrongholdLocationCache load(CompoundTag arg) {
StrongholdLocationCache cache = new StrongholdLocationCache();
if(arg.contains("Positions", Tag.TAG_LONG_ARRAY)) {
long[] positions = arg.getLongArray("Positions");
for(long position : positions) {
cache.chunkPosList.add(new ChunkPos(position));
}
}
return cache;
}
@Override
public CompoundTag save(CompoundTag compoundTag) {
long[] serialized = new long[chunkPosList.size()];
for(int i = 0; i < chunkPosList.size(); i++) {
ChunkPos thePos = chunkPosList.get(i);
serialized[i] = thePos.toLong();
}
compoundTag.putLongArray("Positions", serialized);
return compoundTag;
}
public static String getFileId(Holder<DimensionType> dimensionType) {
return "mfix_strongholds";
}
}