diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/cache_strongholds/ChunkGeneratorMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/cache_strongholds/ChunkGeneratorMixin.java index f9ca5195..65d5b383 100644 --- a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/cache_strongholds/ChunkGeneratorMixin.java +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/cache_strongholds/ChunkGeneratorMixin.java @@ -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 mfix$serverLevel; + @Shadow + @Final + private long concentricRingsSeed; + + @Shadow + @Final + private BiomeSource biomeSource; + + private Path mfix$dimensionPath; + private RegistryAccess.Frozen mfix$registryAccess; + private SoftReference>> 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, ConcentricRingsStructurePlacement placement, CallbackInfoReturnable>> cir) { - if(placement.count() == 0) - return; - ServerLevel level = searchLevel(); - if(level == null) - return; - StrongholdLocationCache cache = ((IServerLevel)level).mfix$getStrongholdCache(); - List 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> modernfix$cacheRingPositions(Holder structureSet, + ConcentricRingsStructurePlacement placement, + Operation>> 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 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 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, ConcentricRingsStructurePlacement placement, CallbackInfoReturnable>> 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 mfix$readFromCache(String cacheKey) { + Map> cache = mfix$getOrLoadCache(); + return cache.get(cacheKey); + } + + private synchronized void mfix$writeToCache(String cacheKey, List positions) { + Map> cache = mfix$getOrLoadCache(); + cache.put(cacheKey, List.copyOf(positions)); + mfix$cachedPositions = new SoftReference<>(cache); + mfix$saveCacheFile(cache); + } + + private Map> mfix$getOrLoadCache() { + Map> cache = mfix$cachedPositions.get(); + if (cache != null) { + return cache; + } + cache = mfix$loadCacheFile(); + mfix$cachedPositions = new SoftReference<>(cache); + return cache; + } + + private Map> mfix$loadCacheFile() { + Path file = mfix$dimensionPath.resolve(CACHE_FILENAME); + if (!Files.exists(file)) { + return new HashMap<>(); + } + try { + CompoundTag root = NbtIo.readCompressed(file.toFile()); + Map> 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 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> cache) { + CompoundTag root = new CompoundTag(); + for (var entry : cache.entrySet()) { + List 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); + } } } diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/cache_strongholds/ServerLevelMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/cache_strongholds/ServerLevelMixin.java index eba1767b..8505303e 100644 --- a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/cache_strongholds/ServerLevelMixin.java +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/cache_strongholds/ServerLevelMixin.java @@ -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 arg2, RegistryAccess arg3, Holder arg4, Supplier 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 = "", 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 = "", 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 = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/chunk/ChunkGeneratorStructureState;ensureStructuresGenerated()V")) + private void setCachePath(ChunkGeneratorStructureState instance, Operation original, + @Local(ordinal = 0, argsOnly = true) LevelStorageSource.LevelStorageAccess levelStorageAccess, + @Local(ordinal = 0, argsOnly = true) ResourceKey dimension, + @Local(ordinal = 0, argsOnly = true) MinecraftServer server) { + ((IChunkGenerator)instance).mfix$setStrongholdCachePath(levelStorageAccess.getDimensionPath(dimension), server.registryAccess()); + original.call(instance); } } diff --git a/src/main/java/org/embeddedt/modernfix/duck/IChunkGenerator.java b/src/main/java/org/embeddedt/modernfix/duck/IChunkGenerator.java index 5f48ba9e..3cf83acc 100644 --- a/src/main/java/org/embeddedt/modernfix/duck/IChunkGenerator.java +++ b/src/main/java/org/embeddedt/modernfix/duck/IChunkGenerator.java @@ -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); } diff --git a/src/main/java/org/embeddedt/modernfix/duck/IServerLevel.java b/src/main/java/org/embeddedt/modernfix/duck/IServerLevel.java deleted file mode 100644 index 34c6b0c8..00000000 --- a/src/main/java/org/embeddedt/modernfix/duck/IServerLevel.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.embeddedt.modernfix.duck; - -import org.embeddedt.modernfix.world.StrongholdLocationCache; - -public interface IServerLevel { - StrongholdLocationCache mfix$getStrongholdCache(); -} diff --git a/src/main/java/org/embeddedt/modernfix/world/StrongholdLocationCache.java b/src/main/java/org/embeddedt/modernfix/world/StrongholdLocationCache.java deleted file mode 100644 index 4ffafada..00000000 --- a/src/main/java/org/embeddedt/modernfix/world/StrongholdLocationCache.java +++ /dev/null @@ -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 chunkPosList; - public StrongholdLocationCache() { - super(); - chunkPosList = new ArrayList<>(); - } - - public List getChunkPosList() { - return new ArrayList<>(chunkPosList); - } - - public void setChunkPosList(List 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) { - return "mfix_strongholds"; - } -}