Dh and level wrapper refactoring and commenting

This commit is contained in:
James Seibel
2025-10-17 07:21:16 -05:00
parent 0902d3f0f5
commit f4ab101403
17 changed files with 92 additions and 80 deletions
@@ -491,6 +491,7 @@ public class SharedApi
IChunkWrapper chunkWrapper = updateData.chunkWrapper;
IDhLevel dhLevel = updateData.dhLevel;
ILevelWrapper levelWrapper = dhLevel.getLevelWrapper();
// having a list of the nearby chunks is needed for lighting and beacon generation
@Nullable ArrayList<IChunkWrapper> nearbyChunkList = updateData.neighborChunkList;
@@ -504,7 +505,7 @@ public class SharedApi
try
{
// sky lighting is populated later at the data source level
DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, levelWrapper.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
dhLevel.updateBeaconBeamsForChunk(chunkWrapper, nearbyChunkList);
@@ -198,7 +198,7 @@ public class FullDataSourceV1 implements IDataSource<IDhLevel>
{
outputStream.writeInt(this.getDataDetailLevel());
outputStream.writeInt(WIDTH);
outputStream.writeInt(level.getMinY());
outputStream.writeInt(level.getLevelWrapper().getMinHeight());
outputStream.writeByte(this.worldGenStep.value);
}
@@ -217,9 +217,9 @@ public class FullDataSourceV1 implements IDataSource<IDhLevel>
}
int minY = inputStream.readInt();
if (minY != level.getMinY())
if (minY != level.getLevelWrapper().getMinHeight())
{
LOGGER.warn("Data minY mismatch: [" + minY + "] != [" + level.getMinY() + "]. Will ignore data's y level");
LOGGER.warn("Data minY mismatch: [" + minY + "] != [" + level.getLevelWrapper().getMinHeight() + "]. Will ignore data's y level");
}
byte worldGenByte = inputStream.readByte();
@@ -89,7 +89,7 @@ public class ColumnBox
int caveCullingMaxY = Integer.MIN_VALUE;
if (Config.Client.Advanced.Graphics.Culling.enableCaveCulling.get())
{
caveCullingMaxY = Config.Client.Advanced.Graphics.Culling.caveCullingHeight.get() - clientLevel.getMinY();
caveCullingMaxY = Config.Client.Advanced.Graphics.Culling.caveCullingHeight.get() - clientLevel.getLevelWrapper().getMinHeight();
}
@@ -29,7 +29,6 @@ import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.StatsMap;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
@@ -53,8 +52,8 @@ public class ColumnRenderBuffer implements AutoCloseable
public static final int FULL_SIZED_BUFFER = MAX_QUADS_PER_BUFFER * QUADS_BYTE_SIZE;
public final DhBlockPos blockPos;
/** the position closest to minimum X/Z infinity and the level's lowest Y */
public final DhBlockPos minCornerBlockPos;
public boolean buffersUploaded = false;
@@ -69,9 +68,9 @@ public class ColumnRenderBuffer implements AutoCloseable
// constructors //
//==============//
public ColumnRenderBuffer(DhBlockPos blockPos)
public ColumnRenderBuffer(DhBlockPos minCornerBlockPos)
{
this.blockPos = blockPos;
this.minCornerBlockPos = minCornerBlockPos;
this.vbos = new GLVertexBuffer[0];
this.vbosTransparent = new GLVertexBuffer[0];
}
@@ -134,7 +133,7 @@ public class ColumnRenderBuffer implements AutoCloseable
}
catch (Exception e)
{
LOGGER.error("Unexpected issue uploading buffer ["+this.blockPos +"], error: ["+e.getMessage()+"].", e);
LOGGER.error("Unexpected issue uploading buffer ["+this.minCornerBlockPos +"], error: ["+e.getMessage()+"].", e);
this.uploadFuture.completeExceptionally(e);
this.uploadFuture = null;
@@ -231,7 +230,7 @@ public class ColumnRenderBuffer implements AutoCloseable
public boolean renderOpaque(LodRenderer renderContext, DhApiRenderParam renderEventParam)
{
boolean hasRendered = false;
renderContext.setModelViewMatrixOffset(this.blockPos, renderEventParam);
renderContext.setModelViewMatrixOffset(this.minCornerBlockPos, renderEventParam);
for (GLVertexBuffer vbo : this.vbos)
{
if (vbo == null)
@@ -258,7 +257,7 @@ public class ColumnRenderBuffer implements AutoCloseable
try
{
// can throw an IllegalStateException if the GL program was freed before it should've been
renderContext.setModelViewMatrixOffset(this.blockPos, renderEventParam);
renderContext.setModelViewMatrixOffset(this.minCornerBlockPos, renderEventParam);
for (GLVertexBuffer vbo : this.vbosTransparent)
{
@@ -278,7 +277,7 @@ public class ColumnRenderBuffer implements AutoCloseable
}
catch (IllegalStateException e)
{
LOGGER.error("renderContext program doesn't exist for pos: "+this.blockPos, e);
LOGGER.error("renderContext program doesn't exist for pos: "+this.minCornerBlockPos, e);
}
return hasRendered;
@@ -61,7 +61,7 @@ public class ColumnRenderBufferBuilder
LodQuadBuilder quadBuilder
)
{
ColumnRenderBuffer buffer = new ColumnRenderBuffer(new DhBlockPos(DhSectionPos.getMinCornerBlockX(pos), clientLevel.getMinY(), DhSectionPos.getMinCornerBlockZ(pos)));
ColumnRenderBuffer buffer = new ColumnRenderBuffer(new DhBlockPos(DhSectionPos.getMinCornerBlockX(pos), clientLevel.getLevelWrapper().getMinHeight(), DhSectionPos.getMinCornerBlockZ(pos)));
CompletableFuture<ColumnRenderBuffer> uploadFuture = buffer.makeAndUploadBuffersAsync(quadBuilder, GLProxy.getInstance().getGpuUploadMethod());
uploadFuture.whenComplete((uploadedBuffer, exception) ->
{
@@ -416,7 +416,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
// but this should work for now
ArrayList<IChunkWrapper> nearbyChunkList = new ArrayList<IChunkWrapper>();
nearbyChunkList.add(chunkWrapper);
DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, this.level.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, this.level.getLevelWrapper().hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
try (FullDataSourceV2 dataSource = LodDataBuilder.createFromChunk(this.level.getLevelWrapper(), chunkWrapper))
{
@@ -663,24 +663,27 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
@Override
public void debugRender(DebugRenderer renderer)
{
int levelMinY = this.level.getLevelWrapper().getMinHeight();
int levelMaxY = this.level.getLevelWrapper().getMaxHeight();
// show the wireframe a bit lower than world max height,
// since most worlds don't render all the way up to the max height
int levelHeightRange = (this.level.getMaxY() - this.level.getMinY());
int maxY = this.level.getMaxY() - (levelHeightRange / 2);
int levelHeightRange = (levelMaxY - levelMinY);
int maxY = levelMaxY - (levelHeightRange / 2);
// blue - queued
this.waitingTasks.keySet().forEach((pos) ->
{
renderer.renderBox(
new DebugRenderer.Box(pos, this.level.getMinY(), maxY, 0.05f, Color.blue));
new DebugRenderer.Box(pos, levelMinY, maxY, 0.05f, Color.blue));
});
// red - in progress
this.inProgressGenTasksByLodPos.forEach((pos, t) ->
{
renderer.renderBox(
new DebugRenderer.Box(pos, this.level.getMinY(), maxY, 0.05f, Color.red));
new DebugRenderer.Box(pos, levelMinY, maxY, 0.05f, Color.red));
});
}
@@ -86,8 +86,6 @@ public abstract class AbstractDhLevel implements IDhLevel
@Nullable
protected CloudRenderHandler cloudRenderHandler;
private IDhApiRenderableBoxGroup unexploredFogRenderableBoxGroup;
//=============//
@@ -187,7 +185,7 @@ public abstract class AbstractDhLevel implements IDhLevel
// block lights should have been populated at the chunkWrapper stage
// waiting to populate the data source's skylight at this stage prevents re-lighting and
// allows us to reduce cross-chunk lighting issues by lighting the whole 4x4 LOD at once
DhLightingEngine.INSTANCE.bakeDataSourceSkyLight(fullDataSource, this.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
DhLightingEngine.INSTANCE.bakeDataSourceSkyLight(fullDataSource, this.getLevelWrapper().hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
return this.updateDataSourcesAsync(fullDataSource)
@@ -393,15 +391,10 @@ public abstract class AbstractDhLevel implements IDhLevel
}
GenericObjectRenderer genericRenderer = this.getGenericRenderer();
if (genericRenderer != null
&& this.unexploredFogRenderableBoxGroup != null)
{
genericRenderer.remove(this.unexploredFogRenderableBoxGroup.getId());
}
this.delayedFullDataSourceSaveCache.close();
}
}
@@ -27,6 +27,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapp
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import java.util.List;
@@ -326,15 +327,11 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
// getters //
//=========//
@Override
public int getMinY() { return this.getLevelWrapper().getMinHeight(); }
@Override
public int getMaxY() { return this.getLevelWrapper().getMaxHeight(); }
@Override
public IServerLevelWrapper getServerLevelWrapper() { return this.serverLevelWrapper; }
@Override
@NotNull
public ILevelWrapper getLevelWrapper() { return this.getServerLevelWrapper(); }
@Override
@@ -343,9 +340,6 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
@Override
public ISaveStructure getSaveStructure() { return this.serverside.saveStructure; }
@Override
public boolean hasSkyLight() { return this.serverLevelWrapper.hasSkyLight(); }
//==========//
@@ -45,6 +45,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftCli
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.annotation.CheckForNull;
@@ -76,6 +77,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
@Nullable
private final ScopedNetworkEventSource networkEventSource;
/** used when connected to a DH supported server so we don't process the same chunks multiple times */
private final Set<DhChunkPos> loadedOnceChunks = Collections.newSetFromMap(
CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
@@ -297,25 +299,18 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
public void clearRenderCache() { this.clientside.clearRenderCache(); }
@Override
@NotNull
public ILevelWrapper getLevelWrapper() { return this.levelWrapper; }
@Override
public CompletableFuture<Void> updateDataSourcesAsync(FullDataSourceV2 data) { return this.clientside.updateDataSourcesAsync(data); }
@Override
public int getMinY() { return this.levelWrapper.getMinHeight(); }
@Override
public int getMaxY() { return this.levelWrapper.getMaxHeight(); }
@Override
public FullDataSourceProviderV2 getFullDataProvider() { return this.dataFileHandler; }
@Override
public ISaveStructure getSaveStructure() { return this.saveStructure; }
@Override
public boolean hasSkyLight() { return this.levelWrapper.hasSkyLight(); }
@Override
public GenericObjectRenderer getGenericRenderer() { return this.clientside.genericRenderer; }
@Override
@@ -41,7 +41,7 @@ import java.awt.*;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/** The level used on a singleplayer world */
/** The level used for a singleplayer world */
public class DhClientServerLevel extends AbstractDhServerLevel implements IDhClientLevel
{
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
@@ -27,6 +27,10 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import org.jetbrains.annotations.Nullable;
/**
* Used when running in singleplayer
* or when connected to a server.
*/
public interface IDhClientLevel extends IDhLevel
{
void clientTick();
@@ -21,6 +21,8 @@ package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.api.internal.ServerApi;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider;
@@ -33,23 +35,39 @@ import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/**
* A DH Level handles all DH-centric logic related to a given MC level.
* A Level in this context is defined as a Minecraft dimension the player can play in
* (IE the overworld, nether, end, etc.). <br><br>
*
* This is different from a {@link ILevelWrapper}
* in the following ways: <br>
* - a DH level is created after a MC level is wrapped and passed into the {@link ClientApi} or {@link ServerApi} respectively <br>
* - a DH level doesn't handle any MC level logic (IE getting the min/max world height) <br>
* - a DH level keeps track of DH's database file paths and rendering <br>
*
* @see ILevelWrapper
* @see IDhClientLevel
* @see IDhServerLevel
*/
public interface IDhLevel extends AutoCloseable, GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener
{
@Deprecated
void worldGenTick();
int getMinY();
int getMaxY();
/**
* May return either a client or server level wrapper. <br>
* Should not return null
*/
@NotNull
ILevelWrapper getLevelWrapper();
/** @return 0 if no hash is known */
@@ -72,8 +90,6 @@ public interface IDhLevel extends AutoCloseable, GeneratedFullDataSourceProvider
ISaveStructure getSaveStructure();
boolean hasSkyLight();
CompletableFuture<Void> updateDataSourcesAsync(FullDataSourceV2 data);
void addDebugMenuStringsToList(List<String> messageList);
@@ -8,12 +8,12 @@ public class LevelInitMessage extends AbstractNetworkMessage
{
public static final int MAX_LENGTH = 150;
public static final String PART_ALLOWED_CHARS_REGEX = "a-zA-Z0-9-_";
public static final String ALLOWED_CHARS_REGEX = "a-zA-Z0-9-_";
// prefix@namespace:path
// 1-150 characters in total, all parts except namespace can be omitted
public static final String VALIDATION_REGEX = String.format("^(?=.{1,%s}$)([%s]+@)?[%s]+(:[%s]+)?$",
MAX_LENGTH, PART_ALLOWED_CHARS_REGEX, PART_ALLOWED_CHARS_REGEX, PART_ALLOWED_CHARS_REGEX);
MAX_LENGTH, ALLOWED_CHARS_REGEX, ALLOWED_CHARS_REGEX, ALLOWED_CHARS_REGEX);
public String levelKey;
@@ -474,7 +474,7 @@ public class LodRenderer
{
if (RATE_LIMITED_LOGGER.canLog()) // can log check to prevent creating a bunch of strings unnecessarily
{
RATE_LIMITED_LOGGER.warn("Attempted to draw invalid buffer: [" + vbo.getId() + "], expected size: ["+vbo.getSize()+"], upload complete: [" + parentBufferContainer.buffersUploaded + "], upload in progress: [" + parentBufferContainer.uploadInProgress() + "], buffer blockPos: ["+parentBufferContainer.blockPos+"].");
RATE_LIMITED_LOGGER.warn("Attempted to draw invalid buffer: [" + vbo.getId() + "], expected size: ["+vbo.getSize()+"], upload complete: [" + parentBufferContainer.buffersUploaded + "], upload in progress: [" + parentBufferContainer.uploadInProgress() + "], buffer blockPos: ["+parentBufferContainer.minCornerBlockPos +"].");
}
return;
}
@@ -26,9 +26,6 @@ import org.jetbrains.annotations.Nullable;
import java.awt.*;
/**
* @version 2022-9-16
*/
public interface IClientLevelWrapper extends ILevelWrapper
{
@@ -38,14 +35,8 @@ public interface IClientLevelWrapper extends ILevelWrapper
int getBlockColor(DhBlockPos pos, IBiomeWrapper biome, FullDataSourceV2 fullDataSource, IBlockStateWrapper blockState);
/** @return -1 if there was a problem getting the color */
int getDirtBlockColor();
/** @return -1 if there was a problem getting the color */
int getWaterBlockColor();
void clearBlockColorCache();
/** Will return null if there was an issue finding the biome. */
@Nullable
IBiomeWrapper getPlainsBiomeWrapper();
Color getCloudColor(float tickDelta);
}
@@ -21,15 +21,37 @@ package com.seibel.distanthorizons.core.wrapperInterfaces.world;
import com.google.common.io.BaseEncoding;
import com.google.common.primitives.Longs;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.api.internal.ServerApi;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.level.IDhServerLevel;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
/** Can be either a Server world or a Client world. */
/**
* A Level wrapper handles all MC related logic related to a given MC level.
* A Level in this context is defined as a Minecraft dimension the player can play in
* (IE the overworld, nether, end, etc.). <br><br>
*
* This is different from a {@link IDhLevel}
* in the following ways: <br>
* - A level wrapper holds a specific MC level object and will exist before a corresponding {@link IDhLevel} is created <br>
* - A level wrapper handles all MC logic (IE getting min/max world height) <br>
* - A level wrapper is accessible via the {@link DhApi} <br>
* - A level wrapper can access some DH logic when the API needs it
* (in general DH logic should be handled via a {@link IDhLevel} but due to how the API is currently configured
* it's easier to handle that logic here). <br>
*
* @see IDhLevel
* @see IClientLevelWrapper
* @see IServerLevelWrapper
*/
public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable
{
@Override
@@ -49,9 +71,7 @@ public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable
return encoded.substring(0, 13).toLowerCase(); // Remaining 3 chars are padding
}
/**
* A string intended to uniquely identify this level.
*/
/** A string that uniquely identifies this level. */
@Override
String getDhIdentifier();
@@ -64,23 +84,19 @@ public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable
@Override
int getMaxHeight();
@Override
default int getMinHeight() { return 0; }
int getMinHeight();
default IChunkWrapper tryGetChunk(DhChunkPos pos) { return null; }
boolean hasChunkLoaded(int chunkX, int chunkZ);
@Deprecated
IBlockStateWrapper getBlockState(DhBlockPos pos);
@Deprecated
IBiomeWrapper getBiome(DhBlockPos pos);
/** Fired when the level is being unloaded. Doesn't unload the level. */
void onUnload();
// TODO James doesn't like this circular reference, can we merge the level wrapper and DhLevels?
@Deprecated
/**
* Used so we can access DH related methods/objects
* from the {@link DhApi}.
*/
void setParentLevel(IDhLevel parentLevel);
}
@@ -43,7 +43,7 @@ public interface IServerLevelWrapper extends ILevelWrapper
if (SharedApi.getEnvironment() == EWorldEnvironment.CLIENT_SERVER)
{
String cleanWorldFolderName = this.getWorldFolderName()
.replaceAll("[^" + LevelInitMessage.PART_ALLOWED_CHARS_REGEX + " ]", "")
.replaceAll("[^" + LevelInitMessage.ALLOWED_CHARS_REGEX + " ]", "")
.replaceAll(" ", "_");
levelKeyPrefix += (!levelKeyPrefix.isEmpty() ? "_" : "") + cleanWorldFolderName