Require a ILevelWrapper when deserializing BlockStateWrappers

This commit is contained in:
James Seibel
2023-08-24 20:10:59 -05:00
parent 888651ef52
commit 5b4049e0ca
6 changed files with 118 additions and 98 deletions
@@ -23,6 +23,8 @@ import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiW
import com.seibel.distanthorizons.common.wrappers.block.BiomeWrapper;
import com.seibel.distanthorizons.common.wrappers.block.BlockStateWrapper;
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import com.seibel.distanthorizons.common.wrappers.world.ClientLevelWrapper;
import com.seibel.distanthorizons.common.wrappers.world.ServerLevelWrapper;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.level.IDhServerLevel;
@@ -32,6 +34,9 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.worldGeneration.AbstractBatchGenerationEnvironmentWrapper;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.chunk.ChunkAccess;
@@ -68,7 +73,7 @@ public class WrapperFactory implements IWrapperFactory
public IBiomeWrapper deserializeBiomeWrapper(String str) throws IOException { return BiomeWrapper.deserialize(str); }
@Override
public IBlockStateWrapper deserializeBlockStateWrapper(String str) throws IOException { return BlockStateWrapper.deserialize(str); }
public IBlockStateWrapper deserializeBlockStateWrapper(String str, ILevelWrapper levelWrapper) throws IOException { return BlockStateWrapper.deserialize(str, levelWrapper); }
@Override
public IBlockStateWrapper getAirBlockStateWrapper() { return BlockStateWrapper.AIR; }
@@ -114,15 +119,34 @@ public class WrapperFactory implements IWrapperFactory
}
ChunkAccess chunk = (ChunkAccess) objectArray[0];
// light source
if (!(objectArray[1] instanceof LevelReader))
// level / light source
if (!(objectArray[1] instanceof Level))
{
throw new ClassCastException(createChunkWrapperErrorMessage(objectArray));
}
LevelReader lightSource = (LevelReader) objectArray[1];
// the level is needed for the DH level wrapper...
Level level = (Level) objectArray[1];
// ...the LevelReader is needed for chunk lighting
LevelReader lightSource = level;
return new ChunkWrapper(chunk, lightSource, /*A DH wrapped level isn't necessary*/null);
// level wrapper
ILevelWrapper levelWrapper;
if (level instanceof ServerLevel)
{
levelWrapper = ServerLevelWrapper.getWrapper((ServerLevel)level);
}
else if (level instanceof ClientLevel)
{
levelWrapper = ClientLevelWrapper.getWrapper((ClientLevel)level);
}
else
{
throw new ClassCastException(createChunkWrapperErrorMessage(objectArray));
}
return new ChunkWrapper(chunk, lightSource, levelWrapper);
}
// incorrect number of parameters from the API
else
@@ -153,7 +177,7 @@ public class WrapperFactory implements IWrapperFactory
// MC 1.16, 1.18, 1.19, 1.20
#if POST_MC_1_17_1 || MC_1_16_5
message.append("[" + ChunkAccess.class.getName() + "], \n");
message.append("[" + LevelReader.class.getName() + "]. \n");
message.append("[" + ServerLevel.class.getName() + "] or [" + ClientLevel.class.getName() + "]. \n");
#else
// See preprocessor comment in createChunkWrapper() for full documentation
not implemented for this version of Minecraft!
@@ -32,7 +32,7 @@ import net.minecraft.world.level.EmptyBlockGetter;
public class BlockStateWrapper implements IBlockStateWrapper
{
/** example "minecraft:plains" */
/** example "minecraft:water" */
public static final String RESOURCE_LOCATION_SEPARATOR = ":";
/** example "minecraft:water_STATE_{level:0}" */
public static final String STATE_STRING_SEPARATOR = "_STATE_";
@@ -44,7 +44,7 @@ public class BlockStateWrapper implements IBlockStateWrapper
public static final ConcurrentHashMap<BlockState, BlockStateWrapper> WRAPPER_BY_BLOCK_STATE = new ConcurrentHashMap<>();
public static String AIR_STRING = "AIR";
public static final BlockStateWrapper AIR = new BlockStateWrapper(null);
public static final BlockStateWrapper AIR = new BlockStateWrapper(null, null);
public static final String[] RENDERER_IGNORED_BLOCKS_RESOURCE_LOCATIONS = { AIR_STRING, "minecraft:barrier", "minecraft:structure_void", "minecraft:light" };
@@ -53,12 +53,8 @@ public class BlockStateWrapper implements IBlockStateWrapper
public final BlockState blockState;
/**
* Cached so it can be quickly used as a semi-stable hashing method. <br>
* This may also fix the issue where we can serialize and save after a level has been shut down.
*/
private String serializationResult = null;
/** technically final, but since it requires a method call to generate it can't be marked as such */
private String serialString;
@@ -66,19 +62,20 @@ public class BlockStateWrapper implements IBlockStateWrapper
// constructors //
//==============//
public static BlockStateWrapper fromBlockState(BlockState blockState)
public static BlockStateWrapper fromBlockState(BlockState blockState, ILevelWrapper levelWrapper)
{
if (blockState == null || blockState.isAir())
{
return AIR;
}
return WRAPPER_BY_BLOCK_STATE.computeIfAbsent(blockState, newBlockState -> new BlockStateWrapper(newBlockState));
return WRAPPER_BY_BLOCK_STATE.computeIfAbsent(blockState, newBlockState -> new BlockStateWrapper(newBlockState, levelWrapper));
}
private BlockStateWrapper(BlockState blockState)
private BlockStateWrapper(BlockState blockState, ILevelWrapper levelWrapper)
{
this.blockState = blockState;
this.serialString = this.serialize(levelWrapper);
LOGGER.trace("Created BlockStateWrapper for ["+blockState+"]");
}
@@ -119,7 +116,7 @@ public class BlockStateWrapper implements IBlockStateWrapper
List<BlockState> blockStatesToIgnore = DefaultBlockStateToIgnore.blockState.getBlock().getStateDefinition().getPossibleStates();
for (BlockState blockState : blockStatesToIgnore)
{
BlockStateWrapper newBlockToIgnore = BlockStateWrapper.fromBlockState(blockState);
BlockStateWrapper newBlockToIgnore = BlockStateWrapper.fromBlockState(blockState, levelWrapper);
blockStateWrappers.add(newBlockToIgnore);
}
}
@@ -159,15 +156,73 @@ public class BlockStateWrapper implements IBlockStateWrapper
public int getLightEmission() { return (this.blockState != null) ? this.blockState.getLightEmission() : 0; }
@Override
public String serialize() { return this.serialize(null); }
public String serialize(ILevelWrapper levelWrapper)
public String getSerialString() { return this.serialString; }
@Override
public boolean equals(Object obj)
{
// the serialization result can be quickly used as a semi-stable hashing method, so it needs to be cached for speed
if (this.serializationResult != null)
if (this == obj)
{
return this.serializationResult;
return true;
}
if (obj == null || this.getClass() != obj.getClass())
{
return false;
}
BlockStateWrapper that = (BlockStateWrapper) obj;
// the serialized value is used so we can test the contents instead of the references
return Objects.equals(this.getSerialString(), that.getSerialString());
}
@Override
public int hashCode() { return Objects.hash(this.getSerialString()); }
@Override
public Object getWrappedMcObject() { return this.blockState; }
@Override
public boolean isAir() { return this.isAir(this.blockState); }
public boolean isAir(BlockState blockState) { return blockState == null || blockState.isAir(); }
@Override
public boolean isSolid()
{
#if PRE_MC_1_20_1
return this.blockState.getMaterial().isSolid();
#else
return !this.blockState.getCollisionShape(EmptyBlockGetter.INSTANCE, BlockPos.ZERO).isEmpty();
#endif
}
@Override
public boolean isLiquid()
{
if (this.isAir())
{
return false;
}
#if PRE_MC_1_20_1
return this.blockState.getMaterial().isLiquid();
#else
return !this.blockState.getFluidState().isEmpty();
#endif
}
@Override
public String toString() { return this.getSerialString(); }
//=======================//
// serialization methods //
//=======================//
private String serialize(ILevelWrapper levelWrapper)
{
if (this.blockState == null)
{
return AIR_STRING;
@@ -175,37 +230,37 @@ public class BlockStateWrapper implements IBlockStateWrapper
// older versions of MC have a static registry
#if !(MC_1_16_5 || MC_1_17_1)
// use the given level if possible, otherwise try using the currently loaded one
Level level = (levelWrapper != null ? (Level)levelWrapper.getWrappedMcObject() : null);
level = (level == null ? Minecraft.getInstance().level : level);
Level level = (Level)levelWrapper.getWrappedMcObject();
net.minecraft.core.RegistryAccess registryAccess = level.registryAccess();
#endif
ResourceLocation resourceLocation;
#if MC_1_16_5 || MC_1_17_1
resourceLocation = Registry.BLOCK.getKey(this.blockState.getBlock());
#elif MC_1_18_2 || MC_1_19_2
net.minecraft.core.RegistryAccess registryAccess = level.registryAccess();
resourceLocation = registryAccess.registryOrThrow(Registry.BLOCK_REGISTRY).getKey(this.blockState.getBlock());
#else
net.minecraft.core.RegistryAccess registryAccess = level.registryAccess();
resourceLocation = registryAccess.registryOrThrow(Registries.BLOCK).getKey(this.blockState.getBlock());
#endif
if (resourceLocation == null)
{
LOGGER.warn("unable to serialize: " + this.blockState);
LOGGER.warn("No ResourceLocation found, unable to serialize: " + this.blockState);
return AIR_STRING;
}
this.serializationResult = resourceLocation.getNamespace() + RESOURCE_LOCATION_SEPARATOR + resourceLocation.getPath()
this.serialString = resourceLocation.getNamespace() + RESOURCE_LOCATION_SEPARATOR + resourceLocation.getPath()
+ STATE_STRING_SEPARATOR + serializeBlockStateProperties(this.blockState);
return this.serializationResult;
return this.serialString;
}
/** will only work if a level is currently loaded */
public static IBlockStateWrapper deserialize(String resourceStateString) throws IOException { return deserialize(resourceStateString, null); }
public static IBlockStateWrapper deserialize(String resourceStateString, ILevelWrapper levelWrapper) throws IOException
{
if (resourceStateString.equals(AIR_STRING) || resourceStateString.equals("")) // the empty string shouldn't normally happen, but just in case
@@ -292,7 +347,7 @@ public class BlockStateWrapper implements IBlockStateWrapper
foundState = block.defaultBlockState();
}
return new BlockStateWrapper(foundState);
return new BlockStateWrapper(foundState, levelWrapper);
}
catch (Exception e)
{
@@ -331,61 +386,5 @@ public class BlockStateWrapper implements IBlockStateWrapper
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null || this.getClass() != obj.getClass())
{
return false;
}
BlockStateWrapper that = (BlockStateWrapper) obj;
// the serialized value is used so we can test the contents instead of the references
return Objects.equals(this.serialize(), that.serialize());
}
@Override
public int hashCode() { return Objects.hash(this.serialize()); }
@Override
public Object getWrappedMcObject() { return this.blockState; }
@Override
public boolean isAir() { return this.isAir(this.blockState); }
public boolean isAir(BlockState blockState) { return blockState == null || blockState.isAir(); }
@Override
public boolean isSolid()
{
#if PRE_MC_1_20_1
return this.blockState.getMaterial().isSolid();
#else
return !this.blockState.getCollisionShape(EmptyBlockGetter.INSTANCE, BlockPos.ZERO).isEmpty();
#endif
}
@Override
public boolean isLiquid()
{
if (this.isAir())
{
return false;
}
#if PRE_MC_1_20_1
return this.blockState.getMaterial().isLiquid();
#else
return !this.blockState.getFluidState().isEmpty();
#endif
}
@Override
public String toString() { return this.serialize(); }
}
@@ -95,7 +95,7 @@ public class ChunkWrapper implements IChunkWrapper
// constructor //
//=============//
public ChunkWrapper(ChunkAccess chunk, LevelReader lightSource, @Nullable ILevelWrapper wrappedLevel)
public ChunkWrapper(ChunkAccess chunk, LevelReader lightSource, ILevelWrapper wrappedLevel)
{
this.chunk = chunk;
this.lightSource = lightSource;
@@ -156,8 +156,6 @@ public class ChunkWrapper implements IChunkWrapper
@Override
public IBiomeWrapper getBiome(int relX, int relY, int relZ)
{
//if (wrappedLevel != null) return wrappedLevel.getBiome(new DhBlockPos(x + getMinX(), y, z + getMinZ()));
#if PRE_MC_1_17_1
return BiomeWrapper.getBiomeWrapper(this.chunk.getBiomes().getNoiseBiome(
relX >> 2, relY >> 2, relZ >> 2));
@@ -367,12 +365,11 @@ public class ChunkWrapper implements IChunkWrapper
@Override
public IBlockStateWrapper getBlockState(int relX, int relY, int relZ)
{
//if (wrappedLevel != null) return wrappedLevel.getBlockState(new DhBlockPos(x + getMinX(), y, z + getMinZ()));
return BlockStateWrapper.fromBlockState(this.chunk.getBlockState(new BlockPos(relX, relY, relZ)));
return BlockStateWrapper.fromBlockState(this.chunk.getBlockState(new BlockPos(relX, relY, relZ)), this.wrappedLevel);
}
@Override
public boolean isStillValid() { return this.wrappedLevel == null || this.wrappedLevel.tryGetChunk(this.chunkPos) == this; }
public boolean isStillValid() { return this.wrappedLevel.tryGetChunk(this.chunkPos) == this; }
#if POST_MC_1_20_1
private static boolean checkLightSectionsOnChunk(LevelChunk chunk, LevelLightEngine engine)
@@ -165,7 +165,7 @@ public class ClientLevelWrapper implements IClientLevelWrapper
@Override
public IBlockStateWrapper getBlockState(DhBlockPos pos)
{
return BlockStateWrapper.fromBlockState(this.level.getBlockState(McObjectConverter.Convert(pos)));
return BlockStateWrapper.fromBlockState(this.level.getBlockState(McObjectConverter.Convert(pos)), this);
}
@Override
@@ -156,7 +156,7 @@ public class ServerLevelWrapper implements IServerLevelWrapper
@Override
public IBlockStateWrapper getBlockState(DhBlockPos pos)
{
return BlockStateWrapper.fromBlockState(level.getBlockState(McObjectConverter.Convert(pos)));
return BlockStateWrapper.fromBlockState(level.getBlockState(McObjectConverter.Convert(pos)), this);
}
@Override