refactor chunk file handling

This commit is contained in:
James Seibel
2025-11-22 12:23:54 -06:00
parent e026cf104c
commit b7253b6549
6 changed files with 543 additions and 465 deletions
@@ -24,9 +24,9 @@ import com.google.common.collect.ImmutableMap;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.common.wrappers.world.ServerLevelWrapper;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.chunkFileHandling.ChunkFileReader;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.internalServer.InternalServerGenerator;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject.*;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.level.IDhServerLevel;
import com.seibel.distanthorizons.core.config.Config;
@@ -39,20 +39,14 @@ import com.seibel.distanthorizons.core.util.gridList.ArrayGridList;
import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.ChunkLightStorage;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IModChecker;
import com.seibel.distanthorizons.core.wrapperInterfaces.worldGeneration.IBatchGeneratorEnvironmentWrapper;
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import java.io.IOException;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.ClosedChannelException;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.step.StepBiomes;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.step.StepFeatures;
@@ -61,16 +55,12 @@ import com.seibel.distanthorizons.common.wrappers.worldGeneration.step.StepStruc
import com.seibel.distanthorizons.common.wrappers.worldGeneration.step.StepStructureStart;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.step.StepSurface;
import net.minecraft.server.level.*;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.chunk.storage.IOWorker;
import net.minecraft.world.level.chunk.storage.RegionFileStorage;
import net.minecraft.world.level.levelgen.DebugLevelSource;
import net.minecraft.world.level.levelgen.FlatLevelSource;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import net.minecraft.nbt.CompoundTag;
#if MC_VER <= MC_1_17_1
#elif MC_VER <= MC_1_19_2
@@ -96,13 +86,6 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm
.fileLevelConfig(Config.Common.Logging.logWorldGenEventToFile)
.build();
public static final DhLogger CHUNK_LOAD_LOGGER = new DhLoggerBuilder()
.name("LOD Chunk Loading")
.fileLevelConfig(Config.Common.Logging.logWorldGenChunkLoadEventToFile)
.build();
private static final IModChecker MOD_CHECKER = SingletonInjector.INSTANCE.get(IModChecker.class);
@NotNull
public static final ImmutableMap<EDhApiWorldGenerationStep, Integer> WORLD_GEN_CHUNK_BORDER_NEEDED_BY_GEN_STEP;
public static final int MAX_WORLD_GEN_CHUNK_BORDER_NEEDED;
@@ -114,14 +97,8 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm
private final IDhServerLevel dhServerLevel;
/**
* will be true if C2ME is installed (since they require us to
* pull chunks using their async method), or if there
* was an issue with the sync pulling method.
*/
private boolean pullExistingChunkUsingMcAsyncMethod = false;
public final InternalServerGenerator internalServerGenerator;
public final ChunkFileReader chunkFileReader;
@@ -139,21 +116,6 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm
public int unknownExceptionCount = 0;
public long lastExceptionTriggerTime = 0;
private final AtomicReference<RegionFileStorageExternalCache> regionFileStorageCacheRef = new AtomicReference<>();
public RegionFileStorageExternalCache getOrCreateRegionFileCache(RegionFileStorage storage)
{
RegionFileStorageExternalCache cache = this.regionFileStorageCacheRef.get();
if (cache == null)
{
cache = new RegionFileStorageExternalCache(storage);
if (!this.regionFileStorageCacheRef.compareAndSet(null, cache))
{
cache = this.regionFileStorageCacheRef.get();
}
}
return cache;
}
public static ThreadLocal<Boolean> isDhWorldGenThreadRef = new ThreadLocal<>();
public static boolean isThisDhWorldGenThread() { return (isDhWorldGenThreadRef.get() != null); }
@@ -201,6 +163,7 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm
this.dhServerLevel = dhServerLevel;
this.params = new GlobalWorldGenParams(dhServerLevel);
this.internalServerGenerator = new InternalServerGenerator(this.params, this.dhServerLevel);
this.chunkFileReader = new ChunkFileReader(this.params);
ChunkGenerator generator = ((ServerLevelWrapper) (dhServerLevel.getServerLevelWrapper())).getLevel().getChunkSource().getGenerator();
boolean isMcGenerator =
@@ -226,12 +189,6 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm
}
}
if (MOD_CHECKER.isModLoaded("c2me"))
{
LOGGER.info("C2ME detected: DH's pre-existing chunk accessing will use methods handled by C2ME.");
this.pullExistingChunkUsingMcAsyncMethod = true;
}
}
@@ -352,7 +309,7 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm
CompletableFuture<?>[] readFutures =
// the extra radius of 8 is to account for structure references which need a chunk radius of 8
ChunkPosGenStream.getStream(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.widthInChunks, 8)
.map((chunkPos) -> this.createEmptyOrPreExistingChunkAsync(chunkPos.x, chunkPos.z, chunkSkyLightingByDhPos, chunkBlockLightingByDhPos, generatedChunkByDhPos))
.map((chunkPos) -> this.chunkFileReader.createEmptyOrPreExistingChunkAsync(chunkPos.x, chunkPos.z, chunkSkyLightingByDhPos, chunkBlockLightingByDhPos, generatedChunkByDhPos))
.toArray(CompletableFuture[]::new);
// join to prevent an issue where DH queues too many tasks or something(?)
@@ -502,225 +459,6 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm
// get existing chunk //
/**
* If the given chunk pos already exists in the world, that chunk will be returned,
* otherwise this will return an empty chunk.
*/
private CompletableFuture<ChunkAccess> createEmptyOrPreExistingChunkAsync(
int chunkX, int chunkZ,
Map<DhChunkPos, ChunkLightStorage> chunkSkyLightingByDhPos,
Map<DhChunkPos, ChunkLightStorage> chunkBlockLightingByDhPos,
Map<DhChunkPos, ChunkAccess> generatedChunkByDhPos)
{
ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
DhChunkPos dhChunkPos = new DhChunkPos(chunkX, chunkZ);
if (generatedChunkByDhPos.containsKey(dhChunkPos))
{
return CompletableFuture.completedFuture(generatedChunkByDhPos.get(dhChunkPos));
}
return this.getChunkNbtDataAsync(chunkPos)
.thenApply((chunkData) ->
{
ChunkAccess newChunk = this.loadOrMakeChunk(chunkPos, chunkData);
if (Config.Common.LodBuilding.pullLightingForPregeneratedChunks.get())
{
// attempt to get chunk lighting
ChunkFileReader.CombinedChunkLightStorage combinedLights = ChunkFileReader.readLight(newChunk, chunkData);
if (combinedLights != null)
{
chunkSkyLightingByDhPos.put(dhChunkPos, combinedLights.skyLightStorage);
chunkBlockLightingByDhPos.put(dhChunkPos, combinedLights.blockLightStorage);
}
}
return newChunk;
})
// separate handle so we can cleanly handle missing chunks and/or thrown errors
.handle((newChunk, throwable) ->
{
if (newChunk != null)
{
return newChunk;
}
else
{
return CreateEmptyChunk(this.params.level, chunkPos);
}
})
.thenApply((newChunk) ->
{
generatedChunkByDhPos.put(dhChunkPos, newChunk);
return newChunk;
});
}
// TODO FIXME this method can be called up to 25 times for the same chunk position, why?
private CompletableFuture<CompoundTag> getChunkNbtDataAsync(ChunkPos chunkPos)
{
ServerLevel level = this.params.level;
//if (true)
// return CompletableFuture.completedFuture(null);
// TODO disabling drastically reduces GC overhead (2Gb/s -> 1GB/s)
try
{
IOWorker ioWorker = level.getChunkSource().chunkMap.worker;
#if MC_VER <= MC_1_18_2
return CompletableFuture.completedFuture(ioWorker.load(chunkPos));
#else
// storage will be null if C2ME is installed
if (!this.pullExistingChunkUsingMcAsyncMethod && ioWorker.storage != null)
{
try
{
RegionFileStorage storage = this.params.level.getChunkSource().chunkMap.worker.storage;
RegionFileStorageExternalCache cache = this.getOrCreateRegionFileCache(storage);
return CompletableFuture.completedFuture(cache.read(chunkPos));
}
catch (NullPointerException e)
{
// this shouldn't happen, if anything is null it should be
// ioWorker.storage
// but just in case
LOGGER.error("Unexpected issue pulling pre-existing chunk ["+chunkPos+"], falling back to async chunk pulling. This may cause server-tick lag.", e);
this.pullExistingChunkUsingMcAsyncMethod = true;
// try again now using the async method
return this.getChunkNbtDataAsync(chunkPos);
}
}
else
{
// log if we unexpectedly weren't able to run the sync chunk pulling
if (!this.pullExistingChunkUsingMcAsyncMethod)
{
// this shouldn't happen, but just in case
LOGGER.info("Unable to pull pre-existing chunk using synchronous method. Falling back to async method. this may cause server-tick lag.");
this.pullExistingChunkUsingMcAsyncMethod = true;
}
//GET_CHUNK_COUNT_REF.incrementAndGet();
// When running in vanilla MC on versions before 1.21.4,
// DH would attempt to run loadAsync on this same thread via a threading mixin,
// to prevent causing lag on the server thread.
// However, if a mod like C2ME is installed this will run on a C2ME thread instead.
return ioWorker.loadAsync(chunkPos)
.thenApply(optional ->
{
// Debugging note:
// If there are reports of extreme memory use when C2ME is installed, that probably means
// this method is queuing a lot of tasks (1,000+), which causes C2ME to explode.
//GET_CHUNK_COUNT_REF.decrementAndGet();
//PREF_LOGGER.info("chunk getter count ["+F3Screen.NUMBER_FORMAT.format(GET_CHUNK_COUNT_REF.get())+"]");
return optional.orElse(null);
})
.exceptionally((throwable) ->
{
// unwrap the CompletionException if necessary
Throwable actualThrowable = throwable;
while (actualThrowable instanceof CompletionException completionException)
{
actualThrowable = completionException.getCause();
}
boolean isShutdownException = ExceptionUtil.isShutdownException(actualThrowable);
if (!isShutdownException)
{
CHUNK_LOAD_LOGGER.warn("DistantHorizons: Couldn't load or make chunk ["+chunkPos+"], error: ["+actualThrowable.getMessage()+"].", actualThrowable);
}
return null;
});
}
#endif
}
catch (ClosedByInterruptException ignore)
{
// this just means the world generator is being shut down
return CompletableFuture.completedFuture(null);
}
catch (Exception e)
{
CHUNK_LOAD_LOGGER.warn("Couldn't load or make chunk [" + chunkPos + "]. Error: [" + e.getMessage() + "].", e);
return CompletableFuture.completedFuture(null);
}
}
private ChunkAccess loadOrMakeChunk(ChunkPos chunkPos, CompoundTag chunkData)
{
ServerLevel level = this.params.level;
if (chunkData == null)
{
return CreateEmptyChunk(level, chunkPos);
}
else
{
try
{
CHUNK_LOAD_LOGGER.debug("DistantHorizons: Loading chunk [" + chunkPos + "] from disk.");
@Nullable
ChunkAccess chunk = ChunkFileReader.read(level, chunkPos, chunkData);
if (chunk != null)
{
if (Config.Common.LodBuilding.assumePreExistingChunksAreFinished.get())
{
// Sometimes the chunk status is wrong
// (this might be an issue with some versions of chunky)
// which can cause issues with some world gen steps re-running and locking up
ChunkWrapper.trySetStatus(chunk, ChunkStatus.FULL);
}
}
else
{
chunk = CreateEmptyChunk(level, chunkPos);
}
return chunk;
}
catch (Exception e)
{
CHUNK_LOAD_LOGGER.error(
"DistantHorizons: couldn't load or make chunk at [" + chunkPos + "]." +
"Please try optimizing your world to fix this issue. \n" +
"World optimization can be done from the singleplayer world selection screen.\n" +
"Error: [" + e.getMessage() + "]."
, e);
return CreateEmptyChunk(level, chunkPos);
}
}
}
private static ProtoChunk CreateEmptyChunk(ServerLevel level, ChunkPos chunkPos)
{
#if MC_VER <= MC_1_16_5
return new ProtoChunk(chunkPos, UpgradeData.EMPTY);
#elif MC_VER <= MC_1_17_1
return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level);
#elif MC_VER <= MC_1_19_2
return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, level.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), null);
#elif MC_VER <= MC_1_19_4
return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, level.registryAccess().registryOrThrow(Registries.BIOME), null);
#elif MC_VER < MC_1_21_3
return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, level.registryAccess().registryOrThrow(Registries.BIOME), null);
#elif MC_VER < MC_1_21_9
return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, level.registryAccess().lookupOrThrow(Registries.BIOME), null);
#else
return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, PalettedContainerFactory.create(level.registryAccess()), null);
#endif
}
// direct generation //
public void generateDirect(
@@ -884,20 +622,8 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm
}
// clear the chunk cache
RegionFileStorageExternalCache regionStorage = this.regionFileStorageCacheRef.get();
if (regionStorage != null)
{
try
{
regionStorage.close();
}
catch (ClosedChannelException ignore) { /* world generator is being shut down */ }
catch (IOException e)
{
LOGGER.error("Failed to close region file storage cache, error: ["+e.getMessage()+"].", e);
}
}
this.chunkFileReader.close();
}
@@ -17,14 +17,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject;
package com.seibel.distanthorizons.common.wrappers.worldGeneration.chunkFileHandling;
import com.mojang.serialization.Codec;
import com.mojang.serialization.Dynamic;
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.ChunkLightStorage;
@@ -34,22 +35,19 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import it.unimi.dsi.fastutil.shorts.ShortList;
import net.minecraft.core.Registry;
#if MC_VER >= MC_1_19_4
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
#endif
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.Tag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.*;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.*;
@@ -86,14 +84,16 @@ import net.minecraft.world.level.chunk.status.ChunkType;
#endif
import net.minecraft.world.level.material.Fluid;
import org.jetbrains.annotations.Nullable;
public class ChunkFileReader
public class ChunkCompoundTagParser
{
private static final AtomicBoolean ZERO_CHUNK_POS_ERROR_LOGGED_REF = new AtomicBoolean(false);
private static final DhLogger LOGGER = BatchGenerationEnvironment.CHUNK_LOAD_LOGGER;
public static final DhLogger LOGGER = new DhLoggerBuilder()
.name("LOD Chunk Reader")
.fileLevelConfig(Config.Common.Logging.logWorldGenChunkLoadEventToFile)
.build();
#if MC_VER >= MC_1_21_9
@@ -121,7 +121,7 @@ public class ChunkFileReader
// read chunk //
//============//
public static LevelChunk read(WorldGenLevel level, ChunkPos chunkPos, CompoundTag chunkData)
public static LevelChunk createFromTag(WorldGenLevel level, ChunkPos chunkPos, CompoundTag chunkData)
{
#if MC_VER < MC_1_18_2
CompoundTag tagLevel = chunkData.getCompound("Level");
@@ -129,8 +129,8 @@ public class ChunkFileReader
CompoundTag tagLevel = chunkData;
#endif
int chunkX = tagGetInt(tagLevel,"xPos");
int chunkZ = tagGetInt(tagLevel, "zPos");
int chunkX = CompoundTagUtil.getInt(tagLevel,"xPos");
int chunkZ = CompoundTagUtil.getInt(tagLevel, "zPos");
ChunkPos actualPos = new ChunkPos(chunkX, chunkZ);
if (!Objects.equals(chunkPos, actualPos))
@@ -165,18 +165,25 @@ public class ChunkFileReader
chunkType = readChunkType(tagLevel);
#if MC_VER < MC_1_18_2
if (chunkType != ChunkStatus.ChunkType.LEVELCHUNK)
return null;
if (chunkType != ChunkStatus.ChunkType.LEVELCHUNK)
{
return null;
}
#elif MC_VER < MC_1_21_6
BlendingData blendingData = readBlendingData(tagLevel);
#if MC_VER < MC_1_19_2
if (chunkType == ChunkStatus.ChunkType.PROTOCHUNK && (blendingData == null || !blendingData.oldNoise()))
{
return null;
}
#else
if (chunkType == #if MC_VER < MC_1_20_6 ChunkStatus.ChunkType.PROTOCHUNK #else ChunkType.PROTOCHUNK #endif && blendingData == null)
{
return null;
}
#endif
#else
// ignore blending data, there appears to be an issue with parsing it in 1.21.6
@@ -188,31 +195,31 @@ public class ChunkFileReader
}
#endif
long inhabitedTime = tagGetLong(tagLevel, "InhabitedTime");
long inhabitedTime = CompoundTagUtil.getLong(tagLevel, "InhabitedTime");
//================== Read params for making the LevelChunk ==================
UpgradeData upgradeData = UpgradeData.EMPTY;
// commented out 2025-06-04 as a test to see if the upgrade data
// is actually necessary for DH or if it can be ignored
// (if it can't be ignored we'll need to handle null responses from tagGetCompoundTag())
// (if it can't be ignored we'll need to handle null responses from CompoundTagUtil.getCompoundTag())
//
//#if MC_VER < MC_1_17_1
//upgradeData = tagLevel.contains(TAG_UPGRADE_DATA, 10)
// ? new UpgradeData(tagGetCompoundTag(tagLevel, TAG_UPGRADE_DATA))
// ? new UpgradeData(CompoundTagUtil.getCompoundTag(tagLevel, TAG_UPGRADE_DATA))
// : UpgradeData.EMPTY;
//#elif MC_VER < MC_1_21_5
//upgradeData = tagLevel.contains(TAG_UPGRADE_DATA, 10)
// ? new UpgradeData(tagGetCompoundTag(tagLevel, TAG_UPGRADE_DATA), level)
// ? new UpgradeData(CompoundTagUtil.getCompoundTag(tagLevel, TAG_UPGRADE_DATA), level)
// : UpgradeData.EMPTY;
//#else
//upgradeData = tagLevel.contains(TAG_UPGRADE_DATA)
// ? new UpgradeData(tagGetCompoundTag(tagLevel, TAG_UPGRADE_DATA), level)
// ? new UpgradeData(CompoundTagUtil.getCompoundTag(tagLevel, TAG_UPGRADE_DATA), level)
// : UpgradeData.EMPTY;
//#endif
boolean isLightOn = tagGetBoolean(tagLevel, "isLightOn");
boolean isLightOn = CompoundTagUtil.getBoolean(tagLevel, "isLightOn");
#if MC_VER < MC_1_18_2
ChunkBiomeContainer chunkBiomeContainer = new ChunkBiomeContainer(
level.getLevel().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY)#if MC_VER >= MC_1_17_1 , level #endif ,
@@ -301,10 +308,10 @@ public class ChunkFileReader
int sectionYIndex = #if MC_VER < MC_1_17_1 16; #else level.getSectionsCount(); #endif
LevelChunkSection[] chunkSections = new LevelChunkSection[sectionYIndex];
ListTag tagSections = tagGetListTag(chunkData, "Sections", 10);
ListTag tagSections = CompoundTagUtil.getListTag(chunkData, "Sections", 10);
if (tagSections == null || tagSections.isEmpty())
{
tagSections = tagGetListTag(chunkData, "sections", 10);
tagSections = CompoundTagUtil.getListTag(chunkData, "sections", 10);
}
@@ -312,13 +319,13 @@ public class ChunkFileReader
{
for (int j = 0; j < tagSections.size(); ++j)
{
CompoundTag tagSection = tagGetCompoundTag(tagSections, j);
CompoundTag tagSection = CompoundTagUtil.getCompoundTag(tagSections, j);
if (tagSection == null)
{
continue;
}
final int sectionYPos = tagGetByte(tagSection, "Y");
final int sectionYPos = CompoundTagUtil.getByte(tagSection, "Y");
#if MC_VER < MC_1_18_2
if (tagSection.contains("Palette", 9) && tagSection.contains("BlockStates", 12))
@@ -353,11 +360,11 @@ public class ChunkFileReader
if (containsBlockStates)
{
#if MC_VER < MC_1_20_6
blockStateContainer = BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, tagGetCompoundTag(tagSection, "block_states"))
blockStateContainer = BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, CompoundTagUtil.getCompoundTag(tagSection, "block_states"))
.promotePartial(string -> logBlockDeserializationWarning(chunkPos, sectionYPos, string))
.getOrThrow(false, (message) -> logParsingWarningOnce(message));
#else
blockStateContainer = BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, tagGetCompoundTag(tagSection, "block_states"))
blockStateContainer = BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, CompoundTagUtil.getCompoundTag(tagSection, "block_states"))
.promotePartial(string -> logBlockDeserializationWarning(chunkPos, sectionYPos, string))
.getOrThrow((message) -> logErrorAndReturnException(message));
#endif
@@ -389,11 +396,11 @@ public class ChunkFileReader
if (containsBiomes)
{
#if MC_VER < MC_1_20_6
biomeContainer = biomeCodec.parse(NbtOps.INSTANCE, tagGetCompoundTag(tagSection, "biomes"))
biomeContainer = biomeCodec.parse(NbtOps.INSTANCE, CompoundTagUtil.getCompoundTag(tagSection, "biomes"))
.promotePartial(string -> logBiomeDeserializationWarning(chunkPos, sectionYIndex, (String) string))
.getOrThrow(false, (message) -> logParsingWarningOnce(message));
#else
biomeContainer = biomeCodec.parse(NbtOps.INSTANCE, tagGetCompoundTag(tagSection, "biomes"))
biomeContainer = biomeCodec.parse(NbtOps.INSTANCE, CompoundTagUtil.getCompoundTag(tagSection, "biomes"))
.promotePartial(string -> logBiomeDeserializationWarning(chunkPos, sectionYIndex, (String) string))
.getOrThrow((message) -> logErrorAndReturnException(message));
#endif
@@ -434,7 +441,7 @@ public class ChunkFileReader
#else ChunkType #endif
readChunkType(CompoundTag tagLevel)
{
String statusString = tagGetString(tagLevel,"Status");
String statusString = CompoundTagUtil.getString(tagLevel,"Status");
if (statusString != null)
{
ChunkStatus chunkStatus = ChunkStatus.byName(statusString);
@@ -452,7 +459,7 @@ public class ChunkFileReader
}
private static void readHeightmaps(LevelChunk chunk, CompoundTag chunkData)
{
CompoundTag tagHeightmaps = tagGetCompoundTag(chunkData, "Heightmaps");
CompoundTag tagHeightmaps = CompoundTagUtil.getCompoundTag(chunkData, "Heightmaps");
if (tagHeightmaps != null)
{
for (Heightmap.Types type : ChunkStatus.FULL.heightmapsAfter())
@@ -482,18 +489,18 @@ public class ChunkFileReader
// DH probably doesn't need any chunk post-processing data
//private static void readPostPocessings(LevelChunk chunk, CompoundTag chunkData)
//{
// ListTag tagPostProcessings = tagGetListTag(chunkData,"PostProcessing", 9);
// ListTag tagPostProcessings = CompoundTagUtil.getListTag(chunkData,"PostProcessing", 9);
// if (tagPostProcessings != null)
// {
// for (int i = 0; i < tagPostProcessings.size(); ++i)
// {
// ListTag listTag3 = tagGetListTag(tagPostProcessings, i);
// ListTag listTag3 = CompoundTagUtil.getListTag(tagPostProcessings, i);
// for (int j = 0; j < listTag3.size(); ++j)
// {
// #if MC_VER < MC_1_21_3
// chunk.addPackedPostProcess(listTag3.getShort(j), i);
// #else
// chunk.addPackedPostProcess(ShortList.of(tagGetShort(listTag3, j)), i);
// chunk.addPackedPostProcess(ShortList.of(CompoundTagUtil.getShort(listTag3, j)), i);
// #endif
// }
// }
@@ -547,9 +554,7 @@ public class ChunkFileReader
// read chunk lighting //
//=====================//
/**
* https://minecraft.wiki/w/Chunk_format
*/
/** https://minecraft.wiki/w/Chunk_format */
public static CombinedChunkLightStorage readLight(ChunkAccess chunk, CompoundTag chunkData)
{
#if MC_VER <= MC_1_17_1
@@ -612,8 +617,8 @@ public class ChunkFileReader
// if null all lights = 0
byte[] blockLightNibbleArray = tagGetByteArray(chunkSectionCompoundTag, "BlockLight");
byte[] skyLightNibbleArray = tagGetByteArray(chunkSectionCompoundTag, "SkyLight");
byte[] blockLightNibbleArray = CompoundTagUtil.getByteArray(chunkSectionCompoundTag, "BlockLight");
byte[] skyLightNibbleArray = CompoundTagUtil.getByteArray(chunkSectionCompoundTag, "SkyLight");
if (blockLightNibbleArray != null
&& skyLightNibbleArray != null)
@@ -725,137 +730,6 @@ public class ChunkFileReader
//====================//
// tag helper methods //
//====================//
// TODO move into separate file (this file is getting long)
// these tag helpers are to simplify tag accessing between MC versions
/** defaults to "false" if the tag isn't present */
private static boolean tagGetBoolean(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getBoolean(key);
#else
return tag.getBoolean(key).orElse(false);
#endif
}
/** defaults to "0" if the tag isn't present */
private static byte tagGetByte(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getByte(key);
#else
return tag.getByte(key).orElse((byte)0);
#endif
}
/** defaults to "0" if the tag isn't present */
private static short tagGetShort(ListTag tag, int index)
{
#if MC_VER < MC_1_21_5
return tag.getShort(index);
#else
return tag.getShort(index).orElse((short)0);
#endif
}
/** defaults to "0" if the tag isn't present */
private static int tagGetInt(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getInt(key);
#else
return tag.getInt(key).orElse(0);
#endif
}
/** defaults to "0" if the tag isn't present */
private static long tagGetLong(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getInt(key);
#else
return tag.getLong(key).orElse(0L);
#endif
}
/** defaults to null if the tag isn't present */
@Nullable
private static String tagGetString(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getString(key);
#else
return tag.getString(key).orElse(null);
#endif
}
/** defaults to null if the tag isn't present */
@Nullable
private static byte[] tagGetByteArray(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getByteArray(key);
#else
return tag.getByteArray(key).orElse(null);
#endif
}
/** defaults to null if the tag isn't present */
@Nullable
private static CompoundTag tagGetCompoundTag(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getCompound(key);
#else
return tag.getCompound(key).orElse(null);
#endif
}
/** defaults to null if the tag isn't present */
@Nullable
private static CompoundTag tagGetCompoundTag(ListTag tag, int index)
{
#if MC_VER < MC_1_21_5
return tag.getCompound(index);
#else
return tag.getCompound(index).orElse(null);
#endif
}
/**
* defaults to null if the tag isn't present
* @param elementType unused after MC 1.21.5
*/
@Nullable
private static ListTag tagGetListTag(CompoundTag tag, String key, int elementType)
{
#if MC_VER < MC_1_21_5
return tag.getList(key, elementType);
#else
return tag.getList(key).orElse(null);
#endif
}
/** defaults to null if the tag isn't present */
@Nullable
private static ListTag tagGetListTag(ListTag tag, int index)
{
#if MC_VER < MC_1_21_5
return tag.getList(index);
#else
return tag.getList(index).orElse(null);
#endif
}
//================//
// helper classes //
//================//
@@ -0,0 +1,339 @@
package com.seibel.distanthorizons.common.wrappers.worldGeneration.chunkFileHandling;
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.GlobalWorldGenParams;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject.RegionFileStorageExternalCache;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.util.ExceptionUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.ChunkLightStorage;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IModChecker;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.PalettedContainerFactory;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.UpgradeData;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.chunk.storage.IOWorker;
import net.minecraft.world.level.chunk.storage.RegionFileStorage;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.ClosedChannelException;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.atomic.AtomicReference;
public class ChunkFileReader implements AutoCloseable
{
public static final DhLogger LOGGER = new DhLoggerBuilder()
.name("LOD World Gen")
.fileLevelConfig(Config.Common.Logging.logWorldGenEventToFile)
.build();
public static final DhLogger CHUNK_LOAD_LOGGER = new DhLoggerBuilder()
.name("LOD Chunk Loading")
.fileLevelConfig(Config.Common.Logging.logWorldGenChunkLoadEventToFile)
.build();
private static final IModChecker MOD_CHECKER = SingletonInjector.INSTANCE.get(IModChecker.class);
public final GlobalWorldGenParams params;
/**
* will be true if C2ME is installed (since they require us to
* pull chunks using their async method), or if there
* was an issue with the sync pulling method.
*/
private boolean pullExistingChunkUsingMcAsyncMethod = false;
private final AtomicReference<RegionFileStorageExternalCache> regionFileStorageCacheRef = new AtomicReference<>();
public RegionFileStorageExternalCache getOrCreateRegionFileCache(RegionFileStorage storage)
{
RegionFileStorageExternalCache cache = this.regionFileStorageCacheRef.get();
if (cache == null)
{
cache = new RegionFileStorageExternalCache(storage);
if (!this.regionFileStorageCacheRef.compareAndSet(null, cache))
{
cache = this.regionFileStorageCacheRef.get();
}
}
return cache;
}
//=============//
// constructor //
//=============//
public ChunkFileReader(GlobalWorldGenParams params)
{
this.params = params;
if (MOD_CHECKER.isModLoaded("c2me"))
{
LOGGER.info("C2ME detected: DH's pre-existing chunk accessing will use methods handled by C2ME.");
this.pullExistingChunkUsingMcAsyncMethod = true;
}
}
//=============//
// constructor //
//=============//
/**
* If the given chunk pos already exists in the world, that chunk will be returned,
* otherwise this will return an empty chunk.
*/
public CompletableFuture<ChunkAccess> createEmptyOrPreExistingChunkAsync(
int chunkX, int chunkZ,
Map<DhChunkPos, ChunkLightStorage> chunkSkyLightingByDhPos,
Map<DhChunkPos, ChunkLightStorage> chunkBlockLightingByDhPos,
Map<DhChunkPos, ChunkAccess> generatedChunkByDhPos)
{
ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
DhChunkPos dhChunkPos = new DhChunkPos(chunkX, chunkZ);
if (generatedChunkByDhPos.containsKey(dhChunkPos))
{
return CompletableFuture.completedFuture(generatedChunkByDhPos.get(dhChunkPos));
}
return this.getChunkNbtDataAsync(chunkPos)
.thenApply((CompoundTag chunkData) ->
{
ChunkAccess newChunk = this.loadOrMakeChunk(chunkPos, chunkData);
if (Config.Common.LodBuilding.pullLightingForPregeneratedChunks.get())
{
// attempt to get chunk lighting
ChunkCompoundTagParser.CombinedChunkLightStorage combinedLights = ChunkCompoundTagParser.readLight(newChunk, chunkData);
if (combinedLights != null)
{
chunkSkyLightingByDhPos.put(dhChunkPos, combinedLights.skyLightStorage);
chunkBlockLightingByDhPos.put(dhChunkPos, combinedLights.blockLightStorage);
}
}
return newChunk;
})
// separate handle so we can cleanly handle missing chunks and/or thrown errors
.handle((newChunk, throwable) ->
{
if (newChunk != null)
{
return newChunk;
}
else
{
return CreateEmptyChunk(this.params.level, chunkPos);
}
})
.thenApply((newChunk) ->
{
generatedChunkByDhPos.put(dhChunkPos, newChunk);
return newChunk;
});
}
// TODO FIXME this method can be called up to 25 times for the same chunk position, why?
private CompletableFuture<CompoundTag> getChunkNbtDataAsync(ChunkPos chunkPos)
{
ServerLevel level = this.params.level;
//if (true)
// return CompletableFuture.completedFuture(null);
// TODO disabling drastically reduces GC overhead (2Gb/s -> 1GB/s)
try
{
IOWorker ioWorker = level.getChunkSource().chunkMap.worker;
#if MC_VER <= MC_1_18_2
return CompletableFuture.completedFuture(ioWorker.load(chunkPos));
#else
// storage will be null if C2ME is installed
if (!this.pullExistingChunkUsingMcAsyncMethod
&& ioWorker.storage != null)
{
try
{
RegionFileStorage storage = this.params.level.getChunkSource().chunkMap.worker.storage;
RegionFileStorageExternalCache cache = this.getOrCreateRegionFileCache(storage);
return CompletableFuture.completedFuture(cache.read(chunkPos));
}
catch (NullPointerException e)
{
// this shouldn't happen, if anything is null it should be
// ioWorker.storage
// but just in case
LOGGER.error("Unexpected issue pulling pre-existing chunk ["+chunkPos+"], falling back to async chunk pulling. This may cause server-tick lag.", e);
this.pullExistingChunkUsingMcAsyncMethod = true;
// try again now using the async method
return this.getChunkNbtDataAsync(chunkPos);
}
}
else
{
// log if we unexpectedly weren't able to run the sync chunk pulling
if (!this.pullExistingChunkUsingMcAsyncMethod)
{
// this shouldn't happen, but just in case
LOGGER.info("Unable to pull pre-existing chunk using synchronous method. Falling back to async method. this may cause server-tick lag.");
this.pullExistingChunkUsingMcAsyncMethod = true;
}
//GET_CHUNK_COUNT_REF.incrementAndGet();
// When running in vanilla MC on versions before 1.21.4,
// DH would attempt to run loadAsync on this same thread via a threading mixin,
// to prevent causing lag on the server thread.
// However, if a mod like C2ME is installed this will run on a C2ME thread instead.
return ioWorker.loadAsync(chunkPos)
.thenApply(optional ->
{
// Debugging note:
// If there are reports of extreme memory use when C2ME is installed, that probably means
// this method is queuing a lot of tasks (1,000+), which causes C2ME to explode.
//GET_CHUNK_COUNT_REF.decrementAndGet();
//PREF_LOGGER.info("chunk getter count ["+F3Screen.NUMBER_FORMAT.format(GET_CHUNK_COUNT_REF.get())+"]");
return optional.orElse(null);
})
.exceptionally((throwable) ->
{
// unwrap the CompletionException if necessary
Throwable actualThrowable = throwable;
while (actualThrowable instanceof CompletionException completionException)
{
actualThrowable = completionException.getCause();
}
boolean isShutdownException = ExceptionUtil.isShutdownException(actualThrowable);
if (!isShutdownException)
{
CHUNK_LOAD_LOGGER.warn("DistantHorizons: Couldn't load or make chunk ["+chunkPos+"], error: ["+actualThrowable.getMessage()+"].", actualThrowable);
}
return null;
});
}
#endif
}
catch (ClosedByInterruptException ignore)
{
// this just means the world generator is being shut down
return CompletableFuture.completedFuture(null);
}
catch (Exception e)
{
CHUNK_LOAD_LOGGER.warn("Couldn't load or make chunk [" + chunkPos + "]. Error: [" + e.getMessage() + "].", e);
return CompletableFuture.completedFuture(null);
}
}
private ChunkAccess loadOrMakeChunk(ChunkPos chunkPos, CompoundTag chunkTagData)
{
ServerLevel level = this.params.level;
if (chunkTagData == null)
{
return CreateEmptyChunk(level, chunkPos);
}
else
{
try
{
CHUNK_LOAD_LOGGER.debug("DistantHorizons: Loading chunk [" + chunkPos + "] from disk.");
@Nullable
ChunkAccess chunk = ChunkCompoundTagParser.createFromTag(level, chunkPos, chunkTagData);
if (chunk != null)
{
if (Config.Common.LodBuilding.assumePreExistingChunksAreFinished.get())
{
// Sometimes the chunk status is wrong
// (this might be an issue with some versions of chunky)
// which can cause issues with some world gen steps re-running and locking up
ChunkWrapper.trySetStatus(chunk, ChunkStatus.FULL);
}
}
else
{
chunk = CreateEmptyChunk(level, chunkPos);
}
return chunk;
}
catch (Exception e)
{
CHUNK_LOAD_LOGGER.error(
"DistantHorizons: couldn't load or make chunk at [" + chunkPos + "]." +
"Please try optimizing your world to fix this issue. \n" +
"World optimization can be done from the singleplayer world selection screen.\n" +
"Error: [" + e.getMessage() + "]."
, e);
return CreateEmptyChunk(level, chunkPos);
}
}
}
private static ProtoChunk CreateEmptyChunk(ServerLevel level, ChunkPos chunkPos)
{
#if MC_VER <= MC_1_16_5
return new ProtoChunk(chunkPos, UpgradeData.EMPTY);
#elif MC_VER <= MC_1_17_1
return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level);
#elif MC_VER <= MC_1_19_2
return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, level.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), null);
#elif MC_VER <= MC_1_19_4
return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, level.registryAccess().registryOrThrow(Registries.BIOME), null);
#elif MC_VER < MC_1_21_3
return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, level.registryAccess().registryOrThrow(Registries.BIOME), null);
#elif MC_VER < MC_1_21_9
return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, level.registryAccess().lookupOrThrow(Registries.BIOME), null);
#else
return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, PalettedContainerFactory.create(level.registryAccess()), null);
#endif
}
//================//
// base overrides //
//================//
@Override
public void close()
{
RegionFileStorageExternalCache regionStorage = this.regionFileStorageCacheRef.get();
if (regionStorage != null)
{
try
{
regionStorage.close();
}
catch (ClosedChannelException ignore) { /* world generator is being shut down */ }
catch (IOException e)
{
LOGGER.error("Failed to close region file storage cache, error: ["+e.getMessage()+"].", e);
}
}
}
}
@@ -0,0 +1,137 @@
package com.seibel.distanthorizons.common.wrappers.worldGeneration.chunkFileHandling;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import org.jetbrains.annotations.Nullable;
/**
* these tag helpers are usedd to simplify tag accessing between MC versions
*/
public class CompoundTagUtil
{
/** defaults to "false" if the tag isn't present */
public static boolean getBoolean(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getBoolean(key);
#else
return tag.getBoolean(key).orElse(false);
#endif
}
/** defaults to "0" if the tag isn't present */
public static byte getByte(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getByte(key);
#else
return tag.getByte(key).orElse((byte)0);
#endif
}
/** defaults to "0" if the tag isn't present */
public static short getShort(ListTag tag, int index)
{
#if MC_VER < MC_1_21_5
return tag.getShort(index);
#else
return tag.getShort(index).orElse((short)0);
#endif
}
/** defaults to "0" if the tag isn't present */
public static int getInt(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getInt(key);
#else
return tag.getInt(key).orElse(0);
#endif
}
/** defaults to "0" if the tag isn't present */
public static long getLong(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getInt(key);
#else
return tag.getLong(key).orElse(0L);
#endif
}
/** defaults to null if the tag isn't present */
@Nullable
public static String getString(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getString(key);
#else
return tag.getString(key).orElse(null);
#endif
}
/** defaults to null if the tag isn't present */
@Nullable
public static byte[] getByteArray(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getByteArray(key);
#else
return tag.getByteArray(key).orElse(null);
#endif
}
/** defaults to null if the tag isn't present */
@Nullable
public static CompoundTag getCompoundTag(CompoundTag tag, String key)
{
#if MC_VER < MC_1_21_5
return tag.getCompound(key);
#else
return tag.getCompound(key).orElse(null);
#endif
}
/** defaults to null if the tag isn't present */
@Nullable
public static CompoundTag getCompoundTag(ListTag tag, int index)
{
#if MC_VER < MC_1_21_5
return tag.getCompound(index);
#else
return tag.getCompound(index).orElse(null);
#endif
}
/**
* defaults to null if the tag isn't present
* @param elementType unused after MC 1.21.5
*/
@Nullable
public static ListTag getListTag(CompoundTag tag, String key, int elementType)
{
#if MC_VER < MC_1_21_5
return tag.getList(key, elementType);
#else
return tag.getList(key).orElse(null);
#endif
}
/** defaults to null if the tag isn't present */
@Nullable
public static ListTag getListTag(ListTag tag, int index)
{
#if MC_VER < MC_1_21_5
return tag.getList(index);
#else
return tag.getList(index).orElse(null);
#endif
}
}
@@ -1,6 +1,6 @@
package com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.chunkFileHandling.ChunkFileReader;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
@@ -55,7 +55,7 @@ public class RegionFileStorageExternalCache implements AutoCloseable
public RegionFileStorageExternalCache(RegionFileStorage storage) { this.storage = storage; }
@Nullable
public RegionFile getRegionFile(ChunkPos pos) throws IOException
public RegionFile getRegionFile(ChunkPos chunkPos) throws IOException
{
if (this.storage == null)
{
@@ -70,8 +70,8 @@ public class RegionFileStorageExternalCache implements AutoCloseable
long posLong = ChunkPos.asLong(pos.getRegionX(), pos.getRegionZ());
RegionFile rFile = null;
long chunkPosLong = ChunkPos.asLong(chunkPos.getRegionX(), chunkPos.getRegionZ());
RegionFile regionFile = null;
// Check vanilla cache
int retryCount = 0;
@@ -85,7 +85,7 @@ public class RegionFileStorageExternalCache implements AutoCloseable
this.getRegionFileLock.lock();
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
rFile = this.storage.getRegionFile(pos);
regionFile = this.storage.getRegionFile(chunkPos);
// keeping the region cache size low helps prevent concurrency issues
if (this.storage.regionCache.size() > 150) // max 256
@@ -97,7 +97,7 @@ public class RegionFileStorageExternalCache implements AutoCloseable
}
}
#else
rFile = this.storage.regionCache.getOrDefault(posLong, null);
regionFile = this.storage.regionCache.getOrDefault(chunkPosLong, null);
#endif
break;
@@ -139,19 +139,19 @@ public class RegionFileStorageExternalCache implements AutoCloseable
if (retryCount >= maxRetryCount)
{
BatchGenerationEnvironment.CHUNK_LOAD_LOGGER.warn("Concurrency issue detected when getting region file for chunk at [" + pos + "].");
ChunkFileReader.CHUNK_LOAD_LOGGER.warn("Concurrency issue detected when getting region file for chunk at [" + chunkPos + "].");
}
if (rFile != null)
if (regionFile != null)
{
return rFile;
return regionFile;
}
// Then check our custom cache
for (RegionFileCache cache : this.regionFileCache)
{
if (cache.pos == posLong)
if (cache.pos == chunkPosLong)
{
return cache.file;
}
@@ -170,22 +170,22 @@ public class RegionFileStorageExternalCache implements AutoCloseable
return null;
}
Path regionFilePath = storageFolderPath.resolve("r." + pos.getRegionX() + "." + pos.getRegionZ() + ".mca");
Path regionFilePath = storageFolderPath.resolve("r." + chunkPos.getRegionX() + "." + chunkPos.getRegionZ() + ".mca");
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
rFile = new RegionFile(regionFilePath.toFile(), storageFolderPath.toFile(), false);
regionFile = new RegionFile(regionFilePath.toFile(), storageFolderPath.toFile(), false);
#elif MC_VER <= MC_1_20_4
rFile = new RegionFile(regionFilePath, storageFolderPath, false);
regionFile = new RegionFile(regionFilePath, storageFolderPath, false);
#else
rFile = new RegionFile(new RegionStorageInfo("level", null, "level type"), regionFilePath, storageFolderPath, false);
regionFile = new RegionFile(new RegionStorageInfo("level", null, "level type"), regionFilePath, storageFolderPath, false);
#endif
this.regionFileCache.add(new RegionFileCache(ChunkPos.asLong(pos.getRegionX(), pos.getRegionZ()), rFile));
this.regionFileCache.add(new RegionFileCache(ChunkPos.asLong(chunkPos.getRegionX(), chunkPos.getRegionZ()), regionFile));
while (this.regionFileCache.size() > MAX_CACHE_SIZE)
{
this.regionFileCache.poll().file.close();
}
return rFile;
return regionFile;
}
@@ -71,6 +71,8 @@ public final class StepStructureStart extends AbstractWorldGenStep
{
ArrayList<ChunkWrapper> chunksToDo = this.getChunkWrappersToGenerate(chunkWrappers);
// TODO should be put in wrapped environment so we can skip some other world gen steps
// SURFACE wouldn't need structure generation either
#if MC_VER < MC_1_19_2
if (!this.environment.params.worldGenSettings.generateFeatures())
#elif MC_VER < MC_1_19_4