Reduce memory allocation slightly during LOD loading

This commit is contained in:
James Seibel
2026-02-10 07:40:16 -06:00
parent 5897fc816c
commit 163ee63f9e
4 changed files with 146 additions and 29 deletions
@@ -39,6 +39,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.worldGeneration.IBatchGeneratorEnvironmentWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.worldGeneration.IBatchGeneratorEnvironmentWrapper;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.multiplayer.ClientLevel;
#if MC_VER > MC_1_17_1 #if MC_VER > MC_1_17_1
import net.minecraft.core.Holder; import net.minecraft.core.Holder;
@@ -121,9 +122,9 @@ public class WrapperFactory implements IWrapperFactory
public IBlockStateWrapper getAirBlockStateWrapper() { return BlockStateWrapper.AIR; } public IBlockStateWrapper getAirBlockStateWrapper() { return BlockStateWrapper.AIR; }
@Override @Override
public HashSet<IBlockStateWrapper> getRendererIgnoredBlocks(ILevelWrapper levelWrapper) { return BlockStateWrapper.getRendererIgnoredBlocks(levelWrapper); } public ObjectOpenHashSet<IBlockStateWrapper> getRendererIgnoredBlocks(ILevelWrapper levelWrapper) { return BlockStateWrapper.getRendererIgnoredBlocks(levelWrapper); }
@Override @Override
public HashSet<IBlockStateWrapper> getRendererIgnoredCaveBlocks(ILevelWrapper levelWrapper) { return BlockStateWrapper.getRendererIgnoredCaveBlocks(levelWrapper); } public ObjectOpenHashSet<IBlockStateWrapper> getRendererIgnoredCaveBlocks(ILevelWrapper levelWrapper) { return BlockStateWrapper.getRendererIgnoredCaveBlocks(levelWrapper); }
@Override @Override
public void resetRendererIgnoredCaveBlocks() { BlockStateWrapper.clearRendererIgnoredCaveBlocks(); } public void resetRendererIgnoredCaveBlocks() { BlockStateWrapper.clearRendererIgnoredCaveBlocks(); }
@@ -28,6 +28,7 @@ import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.minecraft.tags.BlockTags; import net.minecraft.tags.BlockTags;
import net.minecraft.world.level.block.BeaconBeamBlock; import net.minecraft.world.level.block.BeaconBeamBlock;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
@@ -43,6 +44,7 @@ import java.util.List;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1 #if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1
@@ -100,8 +102,8 @@ public class BlockStateWrapper implements IBlockStateWrapper
"netherite_block" "netherite_block"
); );
public static HashSet<IBlockStateWrapper> rendererIgnoredBlocks = null; public static ObjectOpenHashSet<IBlockStateWrapper> rendererIgnoredBlocks = null;
public static HashSet<IBlockStateWrapper> rendererIgnoredCaveBlocks = null; public static ObjectOpenHashSet<IBlockStateWrapper> rendererIgnoredCaveBlocks = null;
/** keep track of broken blocks so we don't log every time */ /** keep track of broken blocks so we don't log every time */
#if MC_VER <= MC_1_21_10 #if MC_VER <= MC_1_21_10
@@ -136,6 +138,7 @@ public class BlockStateWrapper implements IBlockStateWrapper
//==============// //==============//
// constructors // // constructors //
//==============// //==============//
//region
public static BlockStateWrapper fromBlockState(BlockState blockState, ILevelWrapper levelWrapper) public static BlockStateWrapper fromBlockState(BlockState blockState, ILevelWrapper levelWrapper)
{ {
@@ -177,7 +180,7 @@ public class BlockStateWrapper implements IBlockStateWrapper
} }
} }
private BlockStateWrapper(BlockState blockState, ILevelWrapper levelWrapper) private BlockStateWrapper(@Nullable BlockState blockState, ILevelWrapper levelWrapper)
{ {
this.blockState = blockState; this.blockState = blockState;
this.serialString = this.serialize(levelWrapper); this.serialString = this.serialize(levelWrapper);
@@ -299,17 +302,20 @@ public class BlockStateWrapper implements IBlockStateWrapper
//LOGGER.trace("Created BlockStateWrapper ["+this.serialString+"] for ["+blockState+"] with material ID ["+this.EDhApiBlockMaterialId+"]"); //LOGGER.trace("Created BlockStateWrapper ["+this.serialString+"] for ["+blockState+"] with material ID ["+this.EDhApiBlockMaterialId+"]");
} }
//endregion
//====================// //====================//
// LodBuilder methods // // LodBuilder methods //
//====================// //====================//
//region
/** /**
* Requires a {@link ILevelWrapper} since {@link BlockStateWrapper#deserialize(String,ILevelWrapper)} also requires one. * Requires a {@link ILevelWrapper} since {@link BlockStateWrapper#deserialize(String,ILevelWrapper)} also requires one.
* This way the method won't accidentally be called before the deserialization can be completed. * This way the method won't accidentally be called before the deserialization can be completed.
*/ */
public static HashSet<IBlockStateWrapper> getRendererIgnoredBlocks(ILevelWrapper levelWrapper) public static ObjectOpenHashSet<IBlockStateWrapper> getRendererIgnoredBlocks(ILevelWrapper levelWrapper)
{ {
// use the cached version if possible // use the cached version if possible
if (rendererIgnoredBlocks != null) if (rendererIgnoredBlocks != null)
@@ -317,7 +323,7 @@ public class BlockStateWrapper implements IBlockStateWrapper
return rendererIgnoredBlocks; return rendererIgnoredBlocks;
} }
HashSet<String> baseIgnoredBlock = new HashSet<>(); ObjectOpenHashSet<String> baseIgnoredBlock = new ObjectOpenHashSet<>();
baseIgnoredBlock.add(AIR_STRING); baseIgnoredBlock.add(AIR_STRING);
rendererIgnoredBlocks = getAllBlockWrappers(Config.Client.Advanced.Graphics.Culling.ignoredRenderBlockCsv, baseIgnoredBlock, levelWrapper); rendererIgnoredBlocks = getAllBlockWrappers(Config.Client.Advanced.Graphics.Culling.ignoredRenderBlockCsv, baseIgnoredBlock, levelWrapper);
return rendererIgnoredBlocks; return rendererIgnoredBlocks;
@@ -326,7 +332,7 @@ public class BlockStateWrapper implements IBlockStateWrapper
* Requires a {@link ILevelWrapper} since {@link BlockStateWrapper#deserialize(String,ILevelWrapper)} also requires one. * Requires a {@link ILevelWrapper} since {@link BlockStateWrapper#deserialize(String,ILevelWrapper)} also requires one.
* This way the method won't accidentally be called before the deserialization can be completed. * This way the method won't accidentally be called before the deserialization can be completed.
*/ */
public static HashSet<IBlockStateWrapper> getRendererIgnoredCaveBlocks(ILevelWrapper levelWrapper) public static ObjectOpenHashSet<IBlockStateWrapper> getRendererIgnoredCaveBlocks(ILevelWrapper levelWrapper)
{ {
// use the cached version if possible // use the cached version if possible
if (rendererIgnoredCaveBlocks != null) if (rendererIgnoredCaveBlocks != null)
@@ -334,7 +340,7 @@ public class BlockStateWrapper implements IBlockStateWrapper
return rendererIgnoredCaveBlocks; return rendererIgnoredCaveBlocks;
} }
HashSet<String> baseIgnoredBlock = new HashSet<>(); ObjectOpenHashSet<String> baseIgnoredBlock = new ObjectOpenHashSet<>();
baseIgnoredBlock.add(AIR_STRING); baseIgnoredBlock.add(AIR_STRING);
rendererIgnoredCaveBlocks = getAllBlockWrappers(Config.Client.Advanced.Graphics.Culling.ignoredRenderCaveBlockCsv, baseIgnoredBlock, levelWrapper); rendererIgnoredCaveBlocks = getAllBlockWrappers(Config.Client.Advanced.Graphics.Culling.ignoredRenderCaveBlockCsv, baseIgnoredBlock, levelWrapper);
return rendererIgnoredCaveBlocks; return rendererIgnoredCaveBlocks;
@@ -343,14 +349,19 @@ public class BlockStateWrapper implements IBlockStateWrapper
public static void clearRendererIgnoredBlocks() { rendererIgnoredBlocks = null; } public static void clearRendererIgnoredBlocks() { rendererIgnoredBlocks = null; }
public static void clearRendererIgnoredCaveBlocks() { rendererIgnoredCaveBlocks = null; } public static void clearRendererIgnoredCaveBlocks() { rendererIgnoredCaveBlocks = null; }
//endregion
//=====================//
// lod builder helpers // // lod builder helpers //
//=====================//
//region
private static HashSet<IBlockStateWrapper> getAllBlockWrappers(ConfigEntry<String> config, HashSet<String> baseResourceLocations, ILevelWrapper levelWrapper) private static ObjectOpenHashSet<IBlockStateWrapper> getAllBlockWrappers(ConfigEntry<String> config, ObjectOpenHashSet<String> baseResourceLocations, ILevelWrapper levelWrapper)
{ {
// get the base blocks // get the base blocks
HashSet<String> blockStringList = new HashSet<>(); ObjectOpenHashSet<String> blockStringList = new ObjectOpenHashSet<>();
if (baseResourceLocations != null) if (baseResourceLocations != null)
{ {
blockStringList.addAll(baseResourceLocations); blockStringList.addAll(baseResourceLocations);
@@ -365,10 +376,10 @@ public class BlockStateWrapper implements IBlockStateWrapper
return getAllBlockWrappers(blockStringList, levelWrapper); return getAllBlockWrappers(blockStringList, levelWrapper);
} }
private static HashSet<IBlockStateWrapper> getAllBlockWrappers(HashSet<String> blockResourceLocationSet, ILevelWrapper levelWrapper) private static ObjectOpenHashSet<IBlockStateWrapper> getAllBlockWrappers(ObjectOpenHashSet<String> blockResourceLocationSet, ILevelWrapper levelWrapper)
{ {
// deserialize each of the given resource locations // deserialize each of the given resource locations
HashSet<IBlockStateWrapper> blockStateWrappers = new HashSet<>(); ObjectOpenHashSet<IBlockStateWrapper> blockStateWrappers = new ObjectOpenHashSet<>();
for (String blockResourceLocation : blockResourceLocationSet) for (String blockResourceLocation : blockResourceLocationSet)
{ {
try try
@@ -417,11 +428,14 @@ public class BlockStateWrapper implements IBlockStateWrapper
return blockStateWrappers; return blockStateWrappers;
} }
//endregion
//=================// //=================//
// wrapper methods // // wrapper methods //
//=================// //=================//
//region
@Override @Override
public int getOpacity() { return this.opacity; } public int getOpacity() { return this.opacity; }
@@ -529,25 +543,37 @@ public class BlockStateWrapper implements IBlockStateWrapper
public boolean isAir() { return this.isAir(this.blockState); } public boolean isAir() { return this.isAir(this.blockState); }
public boolean isAir(BlockState blockState) { return blockState == null || blockState.isAir(); } public boolean isAir(BlockState blockState) { return blockState == null || blockState.isAir(); }
private Boolean blockIsSolid = null;
@Override @Override
public boolean isSolid() public boolean isSolid()
{ {
if (this.isAir()) if (this.isAir()
|| this.blockState == null) // == null isn't necessary since its handled in isAir() but is here to prevent intellij from complaining
{ {
return false; return false;
} }
// cached since getCollisionShape() is a dictionary lookup that allocates objects
// and this call is used in a high traffic location
if (this.blockIsSolid != null)
{
return this.blockIsSolid;
}
#if MC_VER < MC_1_20_1 #if MC_VER < MC_1_20_1
return this.blockState.getMaterial().isSolid(); this.blockIsSolid = this.blockState.getMaterial().isSolid();
#else #else
return !this.blockState.getCollisionShape(EmptyBlockGetter.INSTANCE, BlockPos.ZERO).isEmpty(); this.blockIsSolid = !this.blockState.getCollisionShape(EmptyBlockGetter.INSTANCE, BlockPos.ZERO).isEmpty();
#endif #endif
return this.blockIsSolid;
} }
@Override @Override
public boolean isLiquid() public boolean isLiquid()
{ {
if (this.isAir()) if (this.isAir()
|| this.blockState == null) // == null isn't necessary since its handled in isAir() but is here to prevent intellij from complaining
{ {
return false; return false;
} }
@@ -579,11 +605,14 @@ public class BlockStateWrapper implements IBlockStateWrapper
@Override @Override
public String toString() { return this.getSerialString(); } public String toString() { return this.getSerialString(); }
//endregion
//=======================// //=======================//
// serialization methods // // serialization methods //
//=======================// //=======================//
//region
private String serialize(ILevelWrapper levelWrapper) private String serialize(ILevelWrapper levelWrapper)
{ {
@@ -811,11 +840,14 @@ public class BlockStateWrapper implements IBlockStateWrapper
return stringBuilder.toString(); return stringBuilder.toString();
} }
//endregion
//==============// //==============//
// Iris methods // // Iris methods //
//==============// //==============//
//region
private EDhApiBlockMaterial calculateEDhApiBlockMaterialId() private EDhApiBlockMaterial calculateEDhApiBlockMaterialId()
{ {
@@ -920,4 +952,7 @@ public class BlockStateWrapper implements IBlockStateWrapper
} }
} }
//endregion
} }
@@ -87,14 +87,18 @@ public class ClientLevelWrapper implements IClientLevelWrapper
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
//region
protected ClientLevelWrapper(ClientLevel level) { this.level = level; } protected ClientLevelWrapper(ClientLevel level) { this.level = level; }
//endregion
//==================// //==================//
// instance methods // // instance methods //
//==================// //==================//
//region
/** /**
* can be used when speed is important and the same level is likely to be passed in, * can be used when speed is important and the same level is likely to be passed in,
@@ -201,11 +205,14 @@ public class ClientLevelWrapper implements IClientLevelWrapper
} }
} }
//endregion
//====================// //====================//
// base level methods // // base level methods //
//====================// //====================//
//region
@Override @Override
public int getBlockColor(DhBlockPos blockPos, IBiomeWrapper biome, FullDataSourceV2 fullDataSource, IBlockStateWrapper blockWrapper) public int getBlockColor(DhBlockPos blockPos, IBiomeWrapper biome, FullDataSourceV2 fullDataSource, IBlockStateWrapper blockWrapper)
@@ -221,7 +228,6 @@ public class ClientLevelWrapper implements IClientLevelWrapper
return blockColorCache.getColor((BiomeWrapper) biome, fullDataSource, blockPos); return blockColorCache.getColor((BiomeWrapper) biome, fullDataSource, blockPos);
} }
@Override @Override
public int getDirtBlockColor() public int getDirtBlockColor()
{ {
@@ -245,24 +251,43 @@ public class ClientLevelWrapper implements IClientLevelWrapper
@Override @Override
public void clearBlockColorCache() { this.blockColorCacheByBlockState.clear(); } public void clearBlockColorCache() { this.blockColorCacheByBlockState.clear(); }
private IDimensionTypeWrapper dimensionTypeWrapper = null;
@Override @Override
public IDimensionTypeWrapper getDimensionType() public IDimensionTypeWrapper getDimensionType()
{ {
// cached since dimensionType() is a dictionary lookup that allocates objects
// and this call is used in a high traffic location
if (this.dimensionTypeWrapper != null)
{
return this.dimensionTypeWrapper;
}
#if MC_VER <= MC_1_21_10 #if MC_VER <= MC_1_21_10
return DimensionTypeWrapper.getDimensionTypeWrapper(this.level.dimensionType()); this.dimensionTypeWrapper = DimensionTypeWrapper.getDimensionTypeWrapper(this.level.dimensionType());
#else #else
return DimensionTypeWrapper.getDimensionTypeWrapper(this.level.dimensionType(), this.getDimensionName()); this.dimensionTypeWrapper = DimensionTypeWrapper.getDimensionTypeWrapper(this.level.dimensionType(), this.getDimensionName());
#endif #endif
return this.dimensionTypeWrapper;
} }
private String dimensionName = null;
@Override @Override
public String getDimensionName() public String getDimensionName()
{ {
// cached since toString() allocates a new string each time
// and this call is used in a high traffic location
if (this.dimensionName != null)
{
return this.dimensionName;
}
#if MC_VER <= MC_1_21_10 #if MC_VER <= MC_1_21_10
return this.level.dimension().location().toString(); this.dimensionName = this.level.dimension().location().toString();
#else #else
return this.level.dimension().identifier().toString(); this.dimensionName = this.level.dimension().identifier().toString();
#endif #endif
return this.dimensionName;
} }
@Override @Override
@@ -276,25 +301,72 @@ public class ClientLevelWrapper implements IClientLevelWrapper
public ClientLevel getLevel() { return this.level; } public ClientLevel getLevel() { return this.level; }
private Boolean dimHasCeiling = null;
@Override @Override
public boolean hasCeiling() { return this.level.dimensionType().hasCeiling(); } public boolean hasCeiling()
{
// cached since dimensionType() is a dictionary lookup that allocates objects
// and this call is used in a high traffic location
if (this.dimHasCeiling != null)
{
return this.dimHasCeiling;
}
this.dimHasCeiling = this.level.dimensionType().hasCeiling();
return this.dimHasCeiling;
}
private Boolean dimHasSkyLight = null;
@Override @Override
public boolean hasSkyLight() { return this.level.dimensionType().hasSkyLight(); } public boolean hasSkyLight()
{
// cached since dimensionType() is a dictionary lookup that allocates objects
// and this call is used in a high traffic location
if (this.dimHasSkyLight != null)
{
return this.dimHasSkyLight;
}
this.dimHasSkyLight = this.level.dimensionType().hasSkyLight();
return this.dimHasSkyLight;
}
private Integer dimMaxHeight = null;
@Override @Override
public int getMaxHeight() { return this.level.getHeight(); } public int getMaxHeight()
{
// cached since getHeight() is a dictionary lookup that allocates objects
// and this call is used in a high traffic location
if (this.dimMaxHeight != null)
{
return this.dimMaxHeight;
}
this.dimMaxHeight = this.level.getHeight();
return this.dimMaxHeight;
}
private Integer dimMinHeight = null;
@Override @Override
public int getMinHeight() public int getMinHeight()
{ {
// cached since getMinY() is a dictionary lookup that allocates objects
// and this call is used in a high traffic location
if (this.dimMinHeight != null)
{
return this.dimMinHeight;
}
#if MC_VER < MC_1_17_1 #if MC_VER < MC_1_17_1
return 0; this.dimMinHeight = 0;
#elif MC_VER < MC_1_21_3 #elif MC_VER < MC_1_21_3
return this.level.getMinBuildHeight(); this.dimMinHeight = this.level.getMinBuildHeight();
#else #else
return this.level.getMinY(); this.dimMinHeight = this.level.getMinY();
#endif #endif
return this.dimMinHeight;
} }
@Override @Override
@@ -318,12 +390,14 @@ public class ClientLevelWrapper implements IClientLevelWrapper
return this.dhLevel.getSaveStructure().getSaveFolder(this); return this.dhLevel.getSaveStructure().getSaveFolder(this);
} }
//endregion
//===================// //===================//
// generic rendering // // generic rendering //
//===================// //===================//
//region
@Override @Override
public void setDhLevel(IDhLevel dhLevel) { this.dhLevel = dhLevel; } public void setDhLevel(IDhLevel dhLevel) { this.dhLevel = dhLevel; }
@@ -410,11 +484,14 @@ public class ClientLevelWrapper implements IClientLevelWrapper
#endif #endif
} }
//endregion
//================// //================//
// base overrides // // base overrides //
//================// //================//
//region
@Override @Override
public String toString() public String toString()
@@ -427,4 +504,8 @@ public class ClientLevelWrapper implements IClientLevelWrapper
return "Wrapped{" + this.level.toString() + "@" + this.getDhIdentifier() + "}"; return "Wrapped{" + this.level.toString() + "@" + this.getDhIdentifier() + "}";
} }
//endregion
} }