Compare commits

..

1 Commits

Author SHA1 Message Date
James Seibel 4a3c24f39e Add proof-of-concept dynamic fade 2026-01-17 10:16:35 -06:00
278 changed files with 16184 additions and 10366 deletions
+4 -20
View File
@@ -2,26 +2,10 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins { plugins {
id "java" id "java"
id "com.gradleup.shadow"
id "com.github.johnrengelman.shadow" version '8.1.1' apply false
} }
repositories {
mavenCentral()
}
tasks.withType(JavaCompile).configureEach {
options.release = 8
options.encoding = "UTF-8"
}
configurations {
testImplementation.extendsFrom compileOnly
}
dependencies {
compileOnly "org.apache.logging.log4j:log4j-api:${rootProject.log4j_version}"
testImplementation "junit:junit:4.13"
}
shadowJar { shadowJar {
// required for basic shadowJar setup // required for basic shadowJar setup
@@ -37,7 +21,7 @@ task addSourcesToCompiledJar(type: ShadowJar) {
doFirst { doFirst {
System.out.println("Adding source files from: \n" + System.out.println("Adding source files from: \n" +
"[" + sourceJarPath + "] to compiled API jar: \n" + "[" + sourceJarPath + "] to compiled API jar: \n" +
"[" + shadowJar.archiveFile.get().asFile + "]") "[" + shadowJar.archivePath + "]")
// Validate the input JAR file // Validate the input JAR file
if (!secondJarFile.exists()) { if (!secondJarFile.exists()) {
@@ -58,7 +42,7 @@ task addSourcesToCompiledJar(type: ShadowJar) {
} }
// set the jars to merge // set the jars to merge
from shadowJar.archiveFile.get().asFile from shadowJar.archivePath
from secondJarFile from secondJarFile
// alternative method to Include the source files in the combined JAR // alternative method to Include the source files in the combined JAR
@@ -35,12 +35,29 @@ public enum EDhApiGpuUploadMethod
/** Picks the best option based on the GPU the user has. */ /** Picks the best option based on the GPU the user has. */
AUTO(false, false), AUTO(false, false),
// commented out since it isn't currently in use
//BUFFER_STORAGE_MAPPING(true, true),
/** Fast rendering, no stuttering. */ /** Fast rendering, no stuttering. */
BUFFER_STORAGE(false, true), BUFFER_STORAGE(false, true),
/** Fast rendering but may stutter when uploading. */ /** Fast rendering but may stutter when uploading. */
SUB_DATA(false, false), SUB_DATA(false, false),
///** Don't upload, only should be used for debugging */
//@Deprecated
//NONE(false, false),
/**
* May end up storing buffers in System memory. <br>
* Fast rending if in GPU memory, slow if in system memory, <br>
* but won't stutter when uploading.
*
* @deprecated not currently supported
*/
@Deprecated
BUFFER_MAPPING(true, false),
/** Fast rendering but may stutter when uploading. */ /** Fast rendering but may stutter when uploading. */
DATA(false, false); DATA(false, false);
@@ -1,19 +0,0 @@
package com.seibel.distanthorizons.api.enums.config;
import com.seibel.distanthorizons.coreapi.ModInfo;
/**
* AUTO, <br>
* OPEN_GL, <br>
* BLAZE_3D, <br><br>
*
* @since API 6.0.0
* @version 2026-3-10
*/
public enum EDhApiRenderApi
{
AUTO,
OPEN_GL,
BLAZE_3D;
}
@@ -53,7 +53,7 @@ public enum EDhApiVerticalQuality
public int calculateMaxNumberOfVerticalSlicesAtDetailLevel(byte dataDetail) public int calculateMaxVerticalData(byte dataDetail)
{ {
// for detail levels lower than what the enum defines, use the lowest quality item // for detail levels lower than what the enum defines, use the lowest quality item
int index = MathUtil.clamp(0, dataDetail, this.maxVerticalData.length - 1); int index = MathUtil.clamp(0, dataDetail, this.maxVerticalData.length - 1);
@@ -43,7 +43,11 @@ public enum EDhApiDebugRendering
SHOW_BLOCK_MATERIAL, SHOW_BLOCK_MATERIAL,
/** Only draw overlapping LOD quads. */ /** Only draw overlapping LOD quads. */
SHOW_OVERLAPPING_QUADS; SHOW_OVERLAPPING_QUADS,
/** LOD colors are based on renderSource flags. */
SHOW_RENDER_SOURCE_FLAG;
public static EDhApiDebugRendering next(EDhApiDebugRendering type) public static EDhApiDebugRendering next(EDhApiDebugRendering type)
{ {
@@ -56,7 +60,7 @@ public enum EDhApiDebugRendering
case SHOW_BLOCK_MATERIAL: case SHOW_BLOCK_MATERIAL:
return SHOW_OVERLAPPING_QUADS; return SHOW_OVERLAPPING_QUADS;
case SHOW_OVERLAPPING_QUADS: case SHOW_OVERLAPPING_QUADS:
return OFF; return SHOW_RENDER_SOURCE_FLAG;
default: default:
return OFF; return OFF;
} }
@@ -67,6 +71,8 @@ public enum EDhApiDebugRendering
switch (type) switch (type)
{ {
case OFF: case OFF:
return SHOW_RENDER_SOURCE_FLAG;
case SHOW_RENDER_SOURCE_FLAG:
return SHOW_OVERLAPPING_QUADS; return SHOW_OVERLAPPING_QUADS;
case SHOW_OVERLAPPING_QUADS: case SHOW_OVERLAPPING_QUADS:
return SHOW_DETAIL; return SHOW_DETAIL;
@@ -20,17 +20,17 @@
package com.seibel.distanthorizons.api.enums.rendering; package com.seibel.distanthorizons.api.enums.rendering;
/** /**
* DEFAULT <br> * Default <br>
* DEBUG_TRIANGLE <br> * Debug <br>
* DISABLED <br> * Disabled <br>
* *
* @since API 2.0.0 * @since API 2.0.0
* @version 2026-03-23 * @version 2024-4-6
*/ */
public enum EDhApiRendererMode public enum EDhApiRendererMode
{ {
DEFAULT, DEFAULT,
DEBUG_TRIANGLE, DEBUG,
DISABLED; DISABLED;
@@ -40,8 +40,8 @@ public enum EDhApiRendererMode
switch (type) switch (type)
{ {
case DEFAULT: case DEFAULT:
return DEBUG_TRIANGLE; return DEBUG;
case DEBUG_TRIANGLE: case DEBUG:
return DISABLED; return DISABLED;
default: default:
return DEFAULT; return DEFAULT;
@@ -55,10 +55,10 @@ public enum EDhApiRendererMode
{ {
case DEFAULT: case DEFAULT:
return DISABLED; return DISABLED;
case DEBUG_TRIANGLE: case DEBUG:
return DEFAULT; return DEFAULT;
default: default:
return DEBUG_TRIANGLE; return DEBUG;
} }
} }
@@ -23,10 +23,10 @@ import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigGroup;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue; import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
/** /**
* Distant Horizons' SSAO configuration. <br><br> * Distant Horizons' fog configuration. <br><br>
* *
* @author James Seibel * @author James Seibel
* @version 2026-02-05 * @version 2022-9-6
* @since API 1.0.0 * @since API 1.0.0
*/ */
public interface IDhApiAmbientOcclusionConfig extends IDhApiConfigGroup public interface IDhApiAmbientOcclusionConfig extends IDhApiConfigGroup
@@ -34,4 +34,32 @@ public interface IDhApiAmbientOcclusionConfig extends IDhApiConfigGroup
/** Determines if Ambient Occlusion is rendered */ /** Determines if Ambient Occlusion is rendered */
IDhApiConfigValue<Boolean> enabled(); IDhApiConfigValue<Boolean> enabled();
/**
* Determines how many points in space are sampled for the occlusion test.
* Higher numbers will improve quality and reduce banding, but will increase GPU load.
*/
IDhApiConfigValue<Integer> sampleCount();
/** Determines the radius Screen Space Ambient Occlusion is applied, measured in blocks. */
IDhApiConfigValue<Double> radius();
/** Determines how dark the Screen Space Ambient Occlusion effect will be. */
IDhApiConfigValue<Double> strength();
/** Increasing the value can reduce banding at the cost of reducing the strength of the effect. */
IDhApiConfigValue<Double> bias();
/**
* Determines how dark the occlusion shadows can be. <br>
* 0 = totally black at the corners <br>
* 1 = no shadow
*/
IDhApiConfigValue<Double> minLight();
/**
* The radius, measured in pixels, that blurring is calculated. <br>
* Higher numbers will reduce banding at the cost of GPU performance.
*/
IDhApiConfigValue<Integer> blurRadius();
} }
@@ -44,7 +44,7 @@ public interface IDhApiFarFogConfig extends IDhApiConfigGroup
* 0.0 = fog starts at the camera <br> * 0.0 = fog starts at the camera <br>
* 1.0 = fog starts at the edge of the fake chunk render distance <br> * 1.0 = fog starts at the edge of the fake chunk render distance <br>
*/ */
IDhApiConfigValue<Float> farFogStartDistance(); IDhApiConfigValue<Double> farFogStartDistance();
/** /**
* Defines where the fog ends as a percent of the radius * Defines where the fog ends as a percent of the radius
@@ -54,18 +54,18 @@ public interface IDhApiFarFogConfig extends IDhApiConfigGroup
* 0.0 = fog ends at the camera <br> * 0.0 = fog ends at the camera <br>
* 1.0 = fog ends at the edge of the fake chunk render distance <br> * 1.0 = fog ends at the edge of the fake chunk render distance <br>
*/ */
IDhApiConfigValue<Float> farFogEndDistance(); IDhApiConfigValue<Double> farFogEndDistance();
/** Defines how opaque the fog is at its thinnest point. */ /** Defines how opaque the fog is at its thinnest point. */
IDhApiConfigValue<Float> farFogMinThickness(); IDhApiConfigValue<Double> farFogMinThickness();
/** Defines how opaque the fog is at its thickest point. */ /** Defines how opaque the fog is at its thickest point. */
IDhApiConfigValue<Float> farFogMaxThickness(); IDhApiConfigValue<Double> farFogMaxThickness();
/** Defines how the fog changes in thickness. */ /** Defines how the fog changes in thickness. */
IDhApiConfigValue<EDhApiFogFalloff> farFogFalloff(); IDhApiConfigValue<EDhApiFogFalloff> farFogFalloff();
/** Defines the fog density. */ /** Defines the fog density. */
IDhApiConfigValue<Float> farFogDensity(); IDhApiConfigValue<Double> farFogDensity();
} }
@@ -124,19 +124,19 @@ public interface IDhApiGraphicsConfig extends IDhApiConfigGroup
* *
* @since API 2.0.0 * @since API 2.0.0
*/ */
IDhApiConfigValue<Float> overdrawPreventionRadius(); IDhApiConfigValue<Double> overdrawPreventionRadius();
/** /**
* Modifies how bright fake chunks are. <br> * Modifies how bright fake chunks are. <br>
* This is done when generating the vertex data and is applied before any shaders. * This is done when generating the vertex data and is applied before any shaders.
*/ */
IDhApiConfigValue<Float> brightnessMultiplier(); IDhApiConfigValue<Double> brightnessMultiplier();
/** /**
* Modifies how saturated fake chunks are. <br> * Modifies how saturated fake chunks are. <br>
* This is done when generating the vertex data and is applied before any shaders. * This is done when generating the vertex data and is applied before any shaders.
*/ */
IDhApiConfigValue<Float> saturationMultiplier(); IDhApiConfigValue<Double> saturationMultiplier();
/** Defines if Distant Horizons should attempt to cull fake chunk cave geometry. */ /** Defines if Distant Horizons should attempt to cull fake chunk cave geometry. */
IDhApiConfigValue<Boolean> caveCullingEnabled(); IDhApiConfigValue<Boolean> caveCullingEnabled();
@@ -150,6 +150,12 @@ public interface IDhApiGraphicsConfig extends IDhApiConfigGroup
/** If enabled vanilla chunk rendering is disabled and only fake chunks are rendered. */ /** If enabled vanilla chunk rendering is disabled and only fake chunks are rendered. */
IDhApiConfigValue<Boolean> lodOnlyMode(); IDhApiConfigValue<Boolean> lodOnlyMode();
/**
* Setting this to a non-zero number will modify vanilla Minecraft's LOD Bias,
* increasing how quickly its textures fade away.
*/
IDhApiConfigValue<Double> lodBias();
/** /**
* Determines how LODs should be shaded. * Determines how LODs should be shaded.
* *
@@ -52,24 +52,24 @@ public interface IDhApiHeightFogConfig extends IDhApiConfigGroup
* Defines the height fog's base height if {@link IDhApiHeightFogConfig#heightFogDirection()} * Defines the height fog's base height if {@link IDhApiHeightFogConfig#heightFogDirection()}
* is set to use a specific height. * is set to use a specific height.
*/ */
IDhApiConfigValue<Float> heightFogBaseHeight(); IDhApiConfigValue<Double> heightFogBaseHeight();
/** Defines the height fog's starting height as a percent of the world height. */ /** Defines the height fog's starting height as a percent of the world height. */
IDhApiConfigValue<Float> heightFogStartingHeightPercent(); IDhApiConfigValue<Double> heightFogStartingHeightPercent();
/** Defines the height fog's ending height as a percent of the world height. */ /** Defines the height fog's ending height as a percent of the world height. */
IDhApiConfigValue<Float> heightFogEndingHeightPercent(); IDhApiConfigValue<Double> heightFogEndingHeightPercent();
/** Defines how opaque the height fog is at its thinnest point. */ /** Defines how opaque the height fog is at its thinnest point. */
IDhApiConfigValue<Float> heightFogMinThickness(); IDhApiConfigValue<Double> heightFogMinThickness();
/** Defines how opaque the height fog is at its thickest point. */ /** Defines how opaque the height fog is at its thickest point. */
IDhApiConfigValue<Float> heightFogMaxThickness(); IDhApiConfigValue<Double> heightFogMaxThickness();
/** Defines how the height fog changes in thickness. */ /** Defines how the height fog changes in thickness. */
IDhApiConfigValue<EDhApiFogFalloff> heightFogFalloff(); IDhApiConfigValue<EDhApiFogFalloff> heightFogFalloff();
/** Defines the height fog's density. */ /** Defines the height fog's density. */
IDhApiConfigValue<Float> heightFogDensity(); IDhApiConfigValue<Double> heightFogDensity();
} }
@@ -37,8 +37,8 @@ public interface IDhApiNoiseTextureConfig extends IDhApiConfigGroup
/** Defines how many steps of noise should be applied. */ /** Defines how many steps of noise should be applied. */
IDhApiConfigValue<Integer> noiseSteps(); IDhApiConfigValue<Integer> noiseSteps();
/** Defines how intense the noise will be, between 0.0 and 1.0. */ /** Defines how intense the noise will be. */
IDhApiConfigValue<Float> noiseIntensity(); IDhApiConfigValue<Double> noiseIntensity();
/** /**
* Defines how far should the noise texture render before it fades away. (in blocks) <br> * Defines how far should the noise texture render before it fades away. (in blocks) <br>
@@ -1,18 +1,15 @@
package com.seibel.distanthorizons.api.interfaces.data; package com.seibel.distanthorizons.api.interfaces.data;
/** /**
* Can be used to drastically speed up repeat read operations in {@link IDhApiTerrainDataRepo}. <br><br> * Can be used to drastically speed up repeat read operations in {@link IDhApiTerrainDataRepo}.
* *
* Once you are done with this cache, closing it will free up any objects
* the cache is holding. This can reduce Garbage Collector overhead and reduce stuttering.
*
* @see IDhApiTerrainDataRepo * @see IDhApiTerrainDataRepo
* *
* @author James Seibel * @author James Seibel
* @version 2026-1-29 * @version 2024-7-14
* @since API 3.0.0 * @since API 3.0.0
*/ */
public interface IDhApiTerrainDataCache extends AutoCloseable public interface IDhApiTerrainDataCache // TODO should this be AutoClosable?
{ {
/** /**
* Removes any data that's currently stored in this cache. * Removes any data that's currently stored in this cache.
@@ -32,7 +32,7 @@ import com.seibel.distanthorizons.api.objects.data.DhApiTerrainDataPoint;
* @see IDhApiTerrainDataCache * @see IDhApiTerrainDataCache
* *
* @author James Seibel * @author James Seibel
* @version 2026-02-03 * @version 2023-6-22
* @since API 1.0.0 * @since API 1.0.0
*/ */
public interface IDhApiTerrainDataRepo public interface IDhApiTerrainDataRepo
@@ -42,17 +42,24 @@ public interface IDhApiTerrainDataRepo
// getters // // getters //
//=========// //=========//
/** @see IDhApiTerrainDataRepo#getSingleDataPointAtBlockPos(IDhApiLevelWrapper, int, int, int, IDhApiTerrainDataCache) */
default DhApiResult<DhApiTerrainDataPoint> getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ) { return this.getSingleDataPointAtBlockPos(levelWrapper, blockPosX, blockPosY, blockPosZ, null); }
/** /**
* Returns the terrain datapoint at the given block position, at/or containing the given Y position. * Returns the terrain datapoint at the given block position, at/or containing the given Y position.
* @since API 3.0.0 * @since API 3.0.0
*/ */
DhApiResult<DhApiTerrainDataPoint> getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ, IDhApiTerrainDataCache dataCache); DhApiResult<DhApiTerrainDataPoint> getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ, IDhApiTerrainDataCache dataCache);
/** @see IDhApiTerrainDataRepo#getColumnDataAtBlockPos(IDhApiLevelWrapper, int, int, IDhApiTerrainDataCache) */
default DhApiResult<DhApiTerrainDataPoint[]> getColumnDataAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosZ) { return this.getColumnDataAtBlockPos(levelWrapper, blockPosX, blockPosZ, null); }
/** /**
* Returns every datapoint in the column located at the given block X and Z position top to bottom. * Returns every datapoint in the column located at the given block X and Z position top to bottom.
* @since API 3.0.0 * @since API 3.0.0
*/ */
DhApiResult<DhApiTerrainDataPoint[]> getColumnDataAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosZ, IDhApiTerrainDataCache dataCache); DhApiResult<DhApiTerrainDataPoint[]> getColumnDataAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosZ, IDhApiTerrainDataCache dataCache);
/** @see IDhApiTerrainDataRepo#getAllTerrainDataAtChunkPos(IDhApiLevelWrapper, int, int, IDhApiTerrainDataCache) */
default DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtChunkPos(IDhApiLevelWrapper levelWrapper, int chunkPosX, int chunkPosZ) { return this.getAllTerrainDataAtChunkPos(levelWrapper, chunkPosX, chunkPosZ, null); }
/** /**
* Returns every datapoint in the given chunk's X and Z position. <br><br> * Returns every datapoint in the given chunk's X and Z position. <br><br>
* *
@@ -64,6 +71,8 @@ public interface IDhApiTerrainDataRepo
*/ */
DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtChunkPos(IDhApiLevelWrapper levelWrapper, int chunkPosX, int chunkPosZ, IDhApiTerrainDataCache dataCache); DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtChunkPos(IDhApiLevelWrapper levelWrapper, int chunkPosX, int chunkPosZ, IDhApiTerrainDataCache dataCache);
/** @see IDhApiTerrainDataRepo#getAllTerrainDataAtRegionPos(IDhApiLevelWrapper, int, int, IDhApiTerrainDataCache) */
default DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, int regionPosX, int regionPosZ) { return this.getAllTerrainDataAtRegionPos(levelWrapper, regionPosX, regionPosZ, null); }
/** /**
* Returns every datapoint in the given region's X and Z position. <br><br> * Returns every datapoint in the given region's X and Z position. <br><br>
* *
@@ -75,6 +84,8 @@ public interface IDhApiTerrainDataRepo
*/ */
DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, int regionPosX, int regionPosZ, IDhApiTerrainDataCache dataCache); DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, int regionPosX, int regionPosZ, IDhApiTerrainDataCache dataCache);
/** @see IDhApiTerrainDataRepo#getAllTerrainDataAtDetailLevelAndPos(IDhApiLevelWrapper, byte, int, int, IDhApiTerrainDataCache) */
default DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, byte detailLevel, int posX, int posZ) { return this.getAllTerrainDataAtDetailLevelAndPos(levelWrapper, detailLevel, posX, posZ, null); }
/** /**
* Returns every datapoint in the column located at the given detail level and X/Z position. <br> * Returns every datapoint in the column located at the given detail level and X/Z position. <br>
* This can be used to return terrain data for non-standard sizes (IE 2x2 blocks or 2x2 chunks). * This can be used to return terrain data for non-standard sizes (IE 2x2 blocks or 2x2 chunks).
@@ -90,6 +101,21 @@ public interface IDhApiTerrainDataRepo
/** @see IDhApiTerrainDataRepo#raycast(IDhApiLevelWrapper, double, double, double, float, float, float, int, IDhApiTerrainDataCache) */
default DhApiResult<DhApiRaycastResult> raycast(
IDhApiLevelWrapper levelWrapper,
double rayOriginX, double rayOriginY, double rayOriginZ,
float rayDirectionX, float rayDirectionY, float rayDirectionZ,
int maxRayBlockLength)
{
return this.raycast(
levelWrapper,
rayOriginX, rayOriginY, rayOriginZ,
rayDirectionX, rayDirectionY, rayDirectionZ,
maxRayBlockLength,
null);
}
/** /**
* Returns the datapoint and position of the LOD * Returns the datapoint and position of the LOD
* at the end of the given ray. <br><br> * at the end of the given ray. <br><br>
@@ -40,7 +40,8 @@ public interface IDhApiEventInjector extends IDependencyInjector<IDhApiEvent>
* @return true if the handler was unbound, false if the handler wasn't bound. * @return true if the handler was unbound, false if the handler wasn't bound.
* @throws IllegalArgumentException if the implementation object doesn't implement the interface * @throws IllegalArgumentException if the implementation object doesn't implement the interface
*/ */
// Note to DH Devs: Don't try adding a generic type to IDhApiEvent, the constructor won't accept it // Note to self: Don't try adding a generic type to IDhApiEvent, the constructor won't accept it
// TODO why are we removing the class instead of an instance?
boolean unbind(Class<? extends IDhApiEvent> dependencyInterface, Class<? extends IDhApiEvent> dependencyClassToRemove) throws IllegalArgumentException; boolean unbind(Class<? extends IDhApiEvent> dependencyInterface, Class<? extends IDhApiEvent> dependencyClassToRemove) throws IllegalArgumentException;
@@ -3,7 +3,6 @@ package com.seibel.distanthorizons.api.interfaces.render;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d; import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f; import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox; import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
import java.util.List; import java.util.List;
@@ -19,7 +18,7 @@ import java.util.List;
* @version 2024-7-3 * @version 2024-7-3
* @since API 3.0.0 * @since API 3.0.0
*/ */
public interface IDhApiCustomRenderObjectFactory extends IBindable public interface IDhApiCustomRenderObjectFactory
{ {
/** /**
* Creates a {@link IDhApiRenderableBoxGroup} from for the given {@link DhApiRenderableBox} * Creates a {@link IDhApiRenderableBoxGroup} from for the given {@link DhApiRenderableBox}
@@ -73,7 +73,7 @@ public interface IDhApiRenderableBoxGroup extends List<DhApiRenderableBox>
* This is a good place to change the origin or notify of any box changes. * This is a good place to change the origin or notify of any box changes.
*/ */
void setPreRenderFunc(Consumer<DhApiRenderParam> renderEventParam); void setPreRenderFunc(Consumer<DhApiRenderParam> renderEventParam);
void setPostRenderFunc(Consumer<DhApiRenderParam> renderEventParam); void setPostRenderFunc(Consumer<DhApiRenderParam> renderEventParam); // TODO name?
/** /**
* If a cube's color, position, or other property is changed this method * If a cube's color, position, or other property is changed this method
@@ -60,8 +60,6 @@ public class DhApiRenderParam implements IDhApiEventParam
public final DhApiMat4f dhProjectionMatrix; public final DhApiMat4f dhProjectionMatrix;
/** The model view matrix Distant Horizons is using to render this frame. */ /** The model view matrix Distant Horizons is using to render this frame. */
public final DhApiMat4f dhModelViewMatrix; public final DhApiMat4f dhModelViewMatrix;
/** combination of the MVM and projection matrices */
public final DhApiMat4f dhMvmProjMatrix;
public final int worldYOffset; public final int worldYOffset;
@@ -113,10 +111,6 @@ public class DhApiRenderParam implements IDhApiEventParam
this.dhProjectionMatrix = newDhProjectionMatrix; this.dhProjectionMatrix = newDhProjectionMatrix;
this.dhModelViewMatrix = newDhModelViewMatrix; this.dhModelViewMatrix = newDhModelViewMatrix;
DhApiMat4f combinedMatrix = new DhApiMat4f(this.dhProjectionMatrix);
combinedMatrix.multiply(this.dhModelViewMatrix);
this.dhMvmProjMatrix = combinedMatrix;
this.worldYOffset = worldYOffset; this.worldYOffset = worldYOffset;
this.clientLevelWrapper = clientLevelWrapper; this.clientLevelWrapper = clientLevelWrapper;
@@ -23,7 +23,6 @@ public class DhApiRenderableBox
public DhApiVec3d maxPos; public DhApiVec3d maxPos;
public Color color; public Color color;
/** @see EDhApiBlockMaterial */
public byte material; public byte material;
@@ -43,11 +43,9 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
protected final boolean allowDuplicateBindings; protected final boolean allowDuplicateBindings;
//==============// //==============//
// constructors // // constructors //
//==============// //==============//
//region
public DependencyInjector(Class<BindableType> newBindableInterface, boolean newAllowDuplicateBindings) public DependencyInjector(Class<BindableType> newBindableInterface, boolean newAllowDuplicateBindings)
{ {
@@ -55,14 +53,11 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
this.allowDuplicateBindings = newAllowDuplicateBindings; this.allowDuplicateBindings = newAllowDuplicateBindings;
} }
//endregion
//=========// //=========//
// binding // // binding //
//=========// //=========//
//region
@Override @Override
public void bind(Class<? extends BindableType> dependencyInterface, BindableType dependencyImplementation) throws IllegalStateException, IllegalArgumentException public void bind(Class<? extends BindableType> dependencyInterface, BindableType dependencyImplementation) throws IllegalStateException, IllegalArgumentException
@@ -74,11 +69,6 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
throw new IllegalStateException("The dependency [" + dependencyInterface.getSimpleName() + "] has already been bound."); throw new IllegalStateException("The dependency [" + dependencyInterface.getSimpleName() + "] has already been bound.");
} }
if (dependencyImplementation == null)
{
throw new NullPointerException("Can't bind null to ["+dependencyInterface.getSimpleName()+"]");
}
// make sure the given dependency implements the necessary interfaces // make sure the given dependency implements the necessary interfaces
boolean implementsInterface = this.checkIfClassImplements(dependencyImplementation.getClass(), dependencyInterface) || boolean implementsInterface = this.checkIfClassImplements(dependencyImplementation.getClass(), dependencyInterface) ||
@@ -141,27 +131,13 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
@Override @Override
public boolean checkIfClassExtends(Class<?> classToTest, Class<?> extensionToLookFor) { return extensionToLookFor.isAssignableFrom(classToTest); } public boolean checkIfClassExtends(Class<?> classToTest, Class<?> extensionToLookFor) { return extensionToLookFor.isAssignableFrom(classToTest); }
//endregion
//===========// //===========//
// unbinding // // unbinding //
//===========// //===========//
//region
public void replaceBinding(Class<? extends BindableType> dependencyInterface, BindableType dependencyImplementation) throws IllegalStateException, IllegalArgumentException
{
this.unbindAll(dependencyInterface);
this.bind(dependencyInterface, dependencyImplementation);
}
public void unbindAll(Class<? extends BindableType> dependencyInterface) throws IllegalStateException, IllegalArgumentException
{
// remove the dependency if present
this.dependencies.remove(dependencyInterface);
}
// TODO having a bindOrReplace method would probably be better since it wouldn't have the possiblity of having nothing bound
public void unbind(Class<? extends BindableType> dependencyInterface, BindableType dependencyImplementation) throws IllegalStateException, IllegalArgumentException public void unbind(Class<? extends BindableType> dependencyInterface, BindableType dependencyImplementation) throws IllegalStateException, IllegalArgumentException
{ {
// check if this object is bound // check if this object is bound
@@ -198,27 +174,30 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
this.dependencies.remove(dependencyInterface); this.dependencies.remove(dependencyInterface);
} }
//endregion
//=========// //=========//
// getters // // getters //
//=========// //=========//
//region
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public <T extends BindableType> T get(Class<T> interfaceClass) throws ClassCastException public <T extends BindableType> T get(Class<T> interfaceClass) throws ClassCastException
{ return (T) this.getInternalLogic(interfaceClass, false).get(0); } {
return (T) this.getInternalLogic(interfaceClass, false).get(0);
}
@Override @Override
public <T extends BindableType> ArrayList<T> getAll(Class<T> interfaceClass) throws ClassCastException public <T extends BindableType> ArrayList<T> getAll(Class<T> interfaceClass) throws ClassCastException
{ return this.getInternalLogic(interfaceClass, false); } {
return this.getInternalLogic(interfaceClass, false);
}
@Override @Override
public <T extends BindableType> T get(Class<T> interfaceClass, boolean allowIncompleteDependencies) throws ClassCastException public <T extends BindableType> T get(Class<T> interfaceClass, boolean allowIncompleteDependencies) throws ClassCastException
{ return (T) this.getInternalLogic(interfaceClass, allowIncompleteDependencies).get(0); } {
return (T) this.getInternalLogic(interfaceClass, allowIncompleteDependencies).get(0);
}
/** /**
* Always returns a list of size 1 or greater, * Always returns a list of size 1 or greater,
@@ -251,7 +230,6 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
return emptyList; return emptyList;
} }
//endregion
/** Removes all bound dependencies. */ /** Removes all bound dependencies. */
@@ -38,19 +38,19 @@ public final class ModInfo
public static final String NAME = "DistantHorizons"; public static final String NAME = "DistantHorizons";
/** Human-readable version of NAME */ /** Human-readable version of NAME */
public static final String READABLE_NAME = "Distant Horizons"; public static final String READABLE_NAME = "Distant Horizons";
public static final String VERSION = "3.0.0-b-dev"; public static final String VERSION = "2.4.6-b-dev";
/** Returns true if the current build is an unstable developer build, false otherwise. */ /** Returns true if the current build is an unstable developer build, false otherwise. */
public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev"); public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
/** This version should only be updated when breaking changes are introduced to the DH API */ /** This version should only be updated when breaking changes are introduced to the DH API */
public static final int API_MAJOR_VERSION = 6; public static final int API_MAJOR_VERSION = 5;
/** This version should be updated whenever new methods are added to the DH API */ /** This version should be updated whenever new methods are added to the DH API */
public static final int API_MINOR_VERSION = 0; public static final int API_MINOR_VERSION = 1;
/** This version should be updated whenever non-breaking fixes are added to the DH API */ /** This version should be updated whenever non-breaking fixes are added to the DH API */
public static final int API_PATCH_VERSION = 0; public static final int API_PATCH_VERSION = 0;
/** If the config file has an older version it'll be re-created from scratch. */ /** If the config file has an older version it'll be re-created from scratch. */
public static final int CONFIG_FILE_VERSION = 4; public static final int CONFIG_FILE_VERSION = 3;
/** All DH owned threads should start with this string to allow for easier debugging and profiling. */ /** All DH owned threads should start with this string to allow for easier debugging and profiling. */
public static final String THREAD_NAME_PREFIX = "DH-"; public static final String THREAD_NAME_PREFIX = "DH-";
+30 -45
View File
@@ -1,23 +1,18 @@
plugins { plugins {
id "java" id "java"
id "com.gradleup.shadow" id "com.github.johnrengelman.shadow" version '8.1.1' apply false // Set this to true if you're using the standalone Core project
} }
repositories { apply plugin: "application"
mavenCentral()
maven { url "https://repo.enonic.com/public/" }
}
tasks.withType(JavaCompile).configureEach { application {
options.release = 8 mainClass.set("com.seibel.distanthorizons.core.jar.JarMain")
options.encoding = "UTF-8"
} }
configurations { configurations {
shadowedArtifact // Used by DH to specify that we want to implement the shadowed core JAR file instead of the regular JAR file shadowedArtifact // Used by DH to specify that we want to implement the shadowed core JAR file instead of the regular JAR file
shade shade
implementation.extendsFrom shade implementation.extendsFrom shade
testImplementation.extendsFrom compileOnly
} }
OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem; OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem;
@@ -25,46 +20,36 @@ OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePl
// Set the OS lwjgl is using to the current os // Set the OS lwjgl is using to the current os
project.ext.lwjglNatives = "natives-" + os.toFamilyName() project.ext.lwjglNatives = "natives-" + os.toFamilyName()
dependencies { dependencies { // All of these dependencies are in Vanilla Minecraft, but we need to depend on it as we arent importing Minecraft in the core
// API project dependency // Imports most of lwjgl's libraries (well, only the ones that we need)
implementation project(":api") implementation platform("org.lwjgl:lwjgl-bom:${rootProject.lwjgl_version}") // TODO: Use Minecraft's version for lwjgl_version (which changes in nearly every version) instead of a hard defined version for all versions
// MC-provided libraries (available at runtime via Minecraft) // REMEMBER: Dont shadow stuff here, these are just the libs that are included in Minecraft so that the core can use
compileOnly platform("org.lwjgl:lwjgl-bom:${rootProject.lwjgl_version}") implementation "org.lwjgl:lwjgl"
compileOnly "org.lwjgl:lwjgl" implementation "org.lwjgl:lwjgl-assimp"
compileOnly "org.lwjgl:lwjgl-assimp" implementation "org.lwjgl:lwjgl-glfw"
compileOnly "org.lwjgl:lwjgl-glfw" implementation "org.lwjgl:lwjgl-openal"
compileOnly "org.lwjgl:lwjgl-stb" implementation "org.lwjgl:lwjgl-opengl"
compileOnly "org.lwjgl:lwjgl-tinyfd" implementation "org.lwjgl:lwjgl-stb"
testRuntimeOnly platform("org.lwjgl:lwjgl-bom:${rootProject.lwjgl_version}") implementation "org.lwjgl:lwjgl-tinyfd"
testRuntimeOnly "org.lwjgl:lwjgl::$lwjglNatives" runtimeOnly "org.lwjgl:lwjgl::$lwjglNatives"
testRuntimeOnly "org.lwjgl:lwjgl-assimp::$lwjglNatives" runtimeOnly "org.lwjgl:lwjgl-assimp::$lwjglNatives"
testRuntimeOnly "org.lwjgl:lwjgl-glfw::$lwjglNatives" runtimeOnly "org.lwjgl:lwjgl-glfw::$lwjglNatives"
testRuntimeOnly "org.lwjgl:lwjgl-stb::$lwjglNatives" runtimeOnly "org.lwjgl:lwjgl-openal::$lwjglNatives"
testRuntimeOnly "org.lwjgl:lwjgl-tinyfd::$lwjglNatives" runtimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-stb::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-tinyfd::$lwjglNatives"
compileOnly("org.apache.logging.log4j:log4j-api:${rootProject.log4j_version}") // FIXME for some reason this line doesn't actually shade in the library
compileOnly("org.apache.logging.log4j:log4j-core:${rootProject.log4j_version}") // shade "it.unimi.dsi:fastutil:${rootProject.fastutil_version}" // Add our own fastutil version
compileOnly("it.unimi.dsi:fastutil:${rootProject.fastutil_version}")
compileOnly("org.joml:joml:${rootProject.joml_version}")
compileOnly("io.netty:netty-buffer:${rootProject.netty_version}")
compileOnly("org.jetbrains:annotations:16.0.2")
compileOnly("com.google.code.findbugs:jsr305:3.0.2")
compileOnly("com.google.common:google-collect:0.5")
compileOnly("com.google.guava:guava:31.1-jre")
// DH's bundled libraries (shadowed + relocated in loader jars)
implementation("com.github.luben:zstd-jni:${rootProject.zstd_version}")
implementation("org.lz4:lz4-java:${rootProject.lz4_version}")
implementation("org.tukaani:xz:${rootProject.xz_version}")
implementation("org.xerial:sqlite-jdbc:${rootProject.sqlite_jdbc_version}")
implementation("com.electronwill.night-config:toml:${rootProject.nightconfig_version}")
implementation("com.electronwill.night-config:json:${rootProject.nightconfig_version}")
// JUnit (core tests only) // Some other dependencies
compileOnly("junit:junit:4.13") implementation("org.jetbrains:annotations:16.0.2")
compileOnly("org.junit.jupiter:junit-jupiter:5.8.2") implementation("com.google.code.findbugs:jsr305:3.0.2")
compileOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") implementation("com.google.common:google-collect:0.5")
implementation("com.google.guava:guava:31.1-jre")
} }
artifacts { artifacts {
@@ -20,14 +20,12 @@
package com.seibel.distanthorizons.core; package com.seibel.distanthorizons.core;
import com.github.luben.zstd.ZstdOutputStream; import com.github.luben.zstd.ZstdOutputStream;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderObjectFactory;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBeforeRenderEvent; import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBeforeRenderEvent;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.eventHandlers.IgnoredDimensionCsvHandler; import com.seibel.distanthorizons.core.config.eventHandlers.IgnoredDimensionCsvHandler;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory;
import com.seibel.distanthorizons.core.sql.DatabaseUpdater; import com.seibel.distanthorizons.core.sql.DatabaseUpdater;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.world.DhApiWorldProxy; import com.seibel.distanthorizons.core.world.DhApiWorldProxy;
@@ -36,6 +34,7 @@ import com.seibel.distanthorizons.core.api.external.methods.data.DhApiTerrainDat
import com.seibel.distanthorizons.api.DhApi; import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.core.render.DhApiRenderProxy; import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.coreapi.util.StringUtil;
import net.jpountz.lz4.LZ4FrameOutputStream; import net.jpountz.lz4.LZ4FrameOutputStream;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.sqlite.SQLiteJDBCLoader; import org.sqlite.SQLiteJDBCLoader;
@@ -56,11 +55,6 @@ public class Initializer
public static void init() public static void init()
{ {
//============================//
// check referenced libraries //
//============================//
//region
LOGGER.info("Running library validation..."); LOGGER.info("Running library validation...");
// confirm that all referenced libraries are available to use // confirm that all referenced libraries are available to use
@@ -100,15 +94,7 @@ public class Initializer
MC_CLIENT.crashMinecraft("Distant Horizons critical setup error: One or more libraries are either in-accessible, corrupted, or overwritten by another mod. Error: [" + e.getMessage() + "].", e); MC_CLIENT.crashMinecraft("Distant Horizons critical setup error: One or more libraries are either in-accessible, corrupted, or overwritten by another mod. Error: [" + e.getMessage() + "].", e);
} }
//endregion // confirm the resource directory is present
//==========================//
// check resource directory //
//==========================//
//region
try try
{ {
int scriptCount = DatabaseUpdater.getAutoUpdateScriptCount(); int scriptCount = DatabaseUpdater.getAutoUpdateScriptCount();
@@ -122,15 +108,6 @@ public class Initializer
MC_CLIENT.crashMinecraft("Critical programmer error: Can't read SQL Scripts resource folder is either missing or malformed. Error: [" + e.getMessage() + "].", e); MC_CLIENT.crashMinecraft("Critical programmer error: Can't read SQL Scripts resource folder is either missing or malformed. Error: [" + e.getMessage() + "].", e);
} }
//endregion
//===========================//
// Java AWT Headless setting //
//===========================//
//region
// This code has been disabled since it can cause Mac // This code has been disabled since it can cause Mac
// to lock up and refuse the load (there's a bug with Java.awt texture loading) // to lock up and refuse the load (there's a bug with Java.awt texture loading)
//if (MC_CLIENT != null) //if (MC_CLIENT != null)
@@ -147,43 +124,18 @@ public class Initializer
// } // }
//} //}
//endregion
//===================//
// API delayed setup //
//===================//
//region
// link Core's config to the API // link Core's config to the API
DhApi.Delayed.configs = DhApiConfig.INSTANCE; DhApi.Delayed.configs = DhApiConfig.INSTANCE;
DhApi.Delayed.terrainRepo = DhApiTerrainDataRepo.INSTANCE; DhApi.Delayed.terrainRepo = DhApiTerrainDataRepo.INSTANCE;
DhApi.Delayed.worldProxy = DhApiWorldProxy.INSTANCE; DhApi.Delayed.worldProxy = DhApiWorldProxy.INSTANCE;
DhApi.Delayed.renderProxy = DhApiRenderProxy.INSTANCE; DhApi.Delayed.renderProxy = DhApiRenderProxy.INSTANCE;
DhApi.Delayed.customRenderObjectFactory = GenericRenderObjectFactory.INSTANCE;
DhApi.Delayed.wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class); DhApi.Delayed.wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
if (DhApi.Delayed.wrapperFactory == null) if (DhApi.Delayed.wrapperFactory == null)
{ {
MC_CLIENT.crashMinecraft("Programmer Error: No ["+IWrapperFactory.class.getSimpleName()+"] assigned to the DhApi.", new Exception()); LOGGER.error("Programmer Error: No ["+IWrapperFactory.class.getSimpleName()+"] assigned to the DhApi.");
} }
DhApi.Delayed.customRenderObjectFactory = SingletonInjector.INSTANCE.get(IDhApiCustomRenderObjectFactory.class);
if (DhApi.Delayed.customRenderObjectFactory == null)
{
MC_CLIENT.crashMinecraft("Programmer Error: No ["+IDhApiCustomRenderObjectFactory.class.getSimpleName()+"] assigned to the DhApi.", new Exception());
}
DhApi.events.bind(DhApiBeforeRenderEvent.class, IgnoredDimensionCsvHandler.INSTANCE);
//endregion
//==============================//
// G1 Garbage collector warning //
//==============================//
//region
// log a warning if G1GC is being used // log a warning if G1GC is being used
// (this garbage collector is known to cause stuttering) // (this garbage collector is known to cause stuttering)
{ {
@@ -208,37 +160,20 @@ public class Initializer
LOGGER.info("Garbage collectors: ["+garbageCollectorNames+"]"); LOGGER.info("Garbage collectors: ["+garbageCollectorNames+"]");
if (g1GcInUse) if (g1GcInUse
&& Config.Common.Logging.Warning.logGarbageCollectorWarning.get())
{ {
String warningMessageHeader = "Distant Horizons: G1 Garbage collector detected."; LOGGER.warn(
String warningMessageBody = "Distant Horizons: G1 Garbage collector detected. \n" +
"This can cause FPS stuttering. \n" + "This garbage collector can cause FPS stuttering. \n" +
"It's recommended to use a concurrent garbage collector \n" + "It's recommended to use a concurrent garbage collector \n" +
"like ZGC (Java 21+) or Shenandoah (Java 8 through 17) \n" + "like ZGC (Java 21+) or Shenandoah (Java 8 through 17) for a smoother experience. \n" +
"for a smoother experience." "");
;
if (Config.Common.Logging.Warning.logGarbageCollectorWarning.get())
{
LOGGER.warn(
warningMessageHeader + "\n" +
warningMessageBody +
"");
}
if (Config.Common.Logging.Warning.showGarbageCollectorWarning.get())
{
ClientApi.INSTANCE.showChatMessageNextFrame(
MinecraftTextFormat.ORANGE + warningMessageHeader + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
warningMessageBody +
"");
}
} }
} }
//endregion DhApi.events.bind(DhApiBeforeRenderEvent.class, IgnoredDimensionCsvHandler.INSTANCE);
} }
@@ -35,6 +35,30 @@ public class DhApiAmbientOcclusionConfig implements IDhApiAmbientOcclusionConfig
@Override @Override
public IDhApiConfigValue<Boolean> enabled() public IDhApiConfigValue<Boolean> enabled()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.enableSsao); } { return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.Ssao.enableSsao); }
@Override
public IDhApiConfigValue<Integer> sampleCount()
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.Graphics.Ssao.sampleCount); }
@Override
public IDhApiConfigValue<Double> radius()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Ssao.radius); }
@Override
public IDhApiConfigValue<Double> strength()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Ssao.strength); }
@Override
public IDhApiConfigValue<Double> bias()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Ssao.bias); }
@Override
public IDhApiConfigValue<Double> minLight()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Ssao.minLight); }
@Override
public IDhApiConfigValue<Integer> blurRadius()
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.Graphics.Ssao.blurRadius); }
} }
@@ -34,7 +34,7 @@ public class DhApiDebuggingConfig implements IDhApiDebuggingConfig
public IDhApiConfigValue<EDhApiDebugRendering> debugRendering() public IDhApiConfigValue<EDhApiDebugRendering> debugRendering()
{ return new DhApiConfigValue<EDhApiDebugRendering, EDhApiDebugRendering>(Config.Client.Advanced.Debugging.debugRenderingColors); } { return new DhApiConfigValue<EDhApiDebugRendering, EDhApiDebugRendering>(Config.Client.Advanced.Debugging.debugRendering); }
public IDhApiConfigValue<Boolean> debugKeybindings() public IDhApiConfigValue<Boolean> debugKeybindings()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Debugging.enableDebugKeybindings); } { return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Debugging.enableDebugKeybindings); }
@@ -34,27 +34,27 @@ public class DhApiFarFogConfig implements IDhApiFarFogConfig
@Override @Override
public IDhApiConfigValue<Float> farFogStartDistance() public IDhApiConfigValue<Double> farFogStartDistance()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.farFogStart); } { return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.farFogStart); }
@Override @Override
public IDhApiConfigValue<Float> farFogEndDistance() public IDhApiConfigValue<Double> farFogEndDistance()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.farFogEnd); } { return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.farFogEnd); }
@Override @Override
public IDhApiConfigValue<Float> farFogMinThickness() public IDhApiConfigValue<Double> farFogMinThickness()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.farFogMin); } { return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.farFogMin); }
@Override @Override
public IDhApiConfigValue<Float> farFogMaxThickness() public IDhApiConfigValue<Double> farFogMaxThickness()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.farFogMax); } { return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.farFogMax); }
@Override @Override
public IDhApiConfigValue<EDhApiFogFalloff> farFogFalloff() public IDhApiConfigValue<EDhApiFogFalloff> farFogFalloff()
{ return new DhApiConfigValue<EDhApiFogFalloff, EDhApiFogFalloff>(Config.Client.Advanced.Graphics.Fog.farFogFalloff); } { return new DhApiConfigValue<EDhApiFogFalloff, EDhApiFogFalloff>(Config.Client.Advanced.Graphics.Fog.farFogFalloff); }
@Override @Override
public IDhApiConfigValue<Float> farFogDensity() public IDhApiConfigValue<Double> farFogDensity()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.farFogDensity); } { return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.farFogDensity); }
} }
@@ -108,16 +108,16 @@ public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
//===========================// //===========================//
@Override @Override
public IDhApiConfigValue<Float> overdrawPreventionRadius() public IDhApiConfigValue<Double> overdrawPreventionRadius()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Culling.overdrawPrevention); } { return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Culling.overdrawPrevention); }
@Override @Override
public IDhApiConfigValue<Float> brightnessMultiplier() public IDhApiConfigValue<Double> brightnessMultiplier()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Quality.brightnessMultiplier); } { return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Quality.brightnessMultiplier); }
@Override @Override
public IDhApiConfigValue<Float> saturationMultiplier() public IDhApiConfigValue<Double> saturationMultiplier()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Quality.saturationMultiplier); } { return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Quality.saturationMultiplier); }
@Override @Override
public IDhApiConfigValue<Boolean> caveCullingEnabled() public IDhApiConfigValue<Boolean> caveCullingEnabled()
@@ -135,6 +135,10 @@ public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
public IDhApiConfigValue<Boolean> lodOnlyMode() public IDhApiConfigValue<Boolean> lodOnlyMode()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Debugging.lodOnlyMode); } { return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Debugging.lodOnlyMode); }
@Override
public IDhApiConfigValue<Double> lodBias()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Quality.lodBias); }
@Override @Override
public IDhApiConfigValue<EDhApiLodShading> lodShading() public IDhApiConfigValue<EDhApiLodShading> lodShading()
{ return new DhApiConfigValue<EDhApiLodShading, EDhApiLodShading>(Config.Client.Advanced.Graphics.Quality.lodShading); } { return new DhApiConfigValue<EDhApiLodShading, EDhApiLodShading>(Config.Client.Advanced.Graphics.Quality.lodShading); }
@@ -44,31 +44,31 @@ public class DhApiHeightFogConfig implements IDhApiHeightFogConfig
{ return new DhApiConfigValue<EDhApiHeightFogDirection, EDhApiHeightFogDirection>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogDirection); } { return new DhApiConfigValue<EDhApiHeightFogDirection, EDhApiHeightFogDirection>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogDirection); }
@Override @Override
public IDhApiConfigValue<Float> heightFogBaseHeight() public IDhApiConfigValue<Double> heightFogBaseHeight()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogBaseHeight); } { return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogBaseHeight); }
@Override @Override
public IDhApiConfigValue<Float> heightFogStartingHeightPercent() public IDhApiConfigValue<Double> heightFogStartingHeightPercent()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogStart); } { return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogStart); }
@Override @Override
public IDhApiConfigValue<Float> heightFogEndingHeightPercent() public IDhApiConfigValue<Double> heightFogEndingHeightPercent()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogEnd); } { return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogEnd); }
@Override @Override
public IDhApiConfigValue<Float> heightFogMinThickness() public IDhApiConfigValue<Double> heightFogMinThickness()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMin); } { return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMin); }
@Override @Override
public IDhApiConfigValue<Float> heightFogMaxThickness() public IDhApiConfigValue<Double> heightFogMaxThickness()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMax); } { return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMax); }
@Override @Override
public IDhApiConfigValue<EDhApiFogFalloff> heightFogFalloff() public IDhApiConfigValue<EDhApiFogFalloff> heightFogFalloff()
{ return new DhApiConfigValue<EDhApiFogFalloff, EDhApiFogFalloff>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogFalloff); } { return new DhApiConfigValue<EDhApiFogFalloff, EDhApiFogFalloff>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogFalloff); }
@Override @Override
public IDhApiConfigValue<Float> heightFogDensity() public IDhApiConfigValue<Double> heightFogDensity()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogDensity); } { return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogDensity); }
} }
@@ -41,8 +41,8 @@ public class DhApiNoiseTextureConfig implements IDhApiNoiseTextureConfig
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.Graphics.NoiseTexture.noiseSteps); } { return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.Graphics.NoiseTexture.noiseSteps); }
@Override @Override
public IDhApiConfigValue<Float> noiseIntensity() public IDhApiConfigValue<Double> noiseIntensity()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.NoiseTexture.noiseIntensity); } { return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.NoiseTexture.noiseIntensity); }
@Override @Override
public IDhApiConfigValue<Integer> noiseDropoff() public IDhApiConfigValue<Integer> noiseDropoff()
@@ -13,7 +13,7 @@ import java.lang.ref.SoftReference;
public class DhApiTerrainDataCache implements IDhApiTerrainDataCache public class DhApiTerrainDataCache implements IDhApiTerrainDataCache
{ {
private final Object modificationLock = new Object(); private final Object modificationLock = new Object();
private final Long2ReferenceOpenHashMap<SoftReference<FullDataSourceV2>> posToFullDataRef = new Long2ReferenceOpenHashMap<>(); private Long2ReferenceOpenHashMap<SoftReference<FullDataSourceV2>> posToFullDataRef = new Long2ReferenceOpenHashMap<>();
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
@@ -22,7 +22,6 @@ public class DhApiTerrainDataCache implements IDhApiTerrainDataCache
//==================// //==================//
// internal methods // // internal methods //
//==================// //==================//
//region
public void add(long pos, FullDataSourceV2 dataSource) public void add(long pos, FullDataSourceV2 dataSource)
{ {
@@ -49,14 +48,11 @@ public class DhApiTerrainDataCache implements IDhApiTerrainDataCache
} }
} }
//endregion
//=============// //=============//
// API methods // // API methods //
//=============// //=============//
//region
@Override @Override
public void clear() public void clear()
@@ -86,23 +82,6 @@ public class DhApiTerrainDataCache implements IDhApiTerrainDataCache
} }
} }
//endregion
//================//
// base overrides //
//================//
//region
@Override
public void close() { this.clear(); }
@Override
public String toString() { return "Size: " + this.posToFullDataRef.size(); }
//endregion
} }
@@ -32,9 +32,9 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSour
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.util.DhApiTerrainDataPointUtil; import com.seibel.distanthorizons.core.util.DhApiTerrainDataPointUtil;
import com.seibel.distanthorizons.core.util.FullDataPointUtil; import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
@@ -66,8 +66,6 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final AbstractDebugWireframeRenderer DEBUG_RENDERER = SingletonInjector.INSTANCE.get(AbstractDebugWireframeRenderer.class);
// debugging values // debugging values
private static volatile boolean debugThreadRunning = false; private static volatile boolean debugThreadRunning = false;
private static DhApiTerrainDataCache debugDataCache = new DhApiTerrainDataCache(); private static DhApiTerrainDataCache debugDataCache = new DhApiTerrainDataCache();
@@ -91,30 +89,30 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
//================// //================//
@Override @Override
public DhApiResult<DhApiTerrainDataPoint> getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ, IDhApiTerrainDataCache dataCache) public DhApiResult<DhApiTerrainDataPoint> getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ, @Nullable IDhApiTerrainDataCache dataCache)
{ return getTerrainDataAtBlockYPos(levelWrapper, DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), blockPosY, dataCache); } { return getTerrainDataAtBlockYPos(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), blockPosY, dataCache); }
@Override @Override
public DhApiResult<DhApiTerrainDataPoint[]> getColumnDataAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosZ, IDhApiTerrainDataCache dataCache) public DhApiResult<DhApiTerrainDataPoint[]> getColumnDataAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosZ, @Nullable IDhApiTerrainDataCache dataCache)
{ return getTerrainDataColumnArray(levelWrapper, DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), null, dataCache); } { return getTerrainDataColumnArray(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), null, dataCache); }
@Override @Override
public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtChunkPos(IDhApiLevelWrapper levelWrapper, int chunkPosX, int chunkPosZ, IDhApiTerrainDataCache dataCache) public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtChunkPos(IDhApiLevelWrapper levelWrapper, int chunkPosX, int chunkPosZ, @Nullable IDhApiTerrainDataCache dataCache)
{ return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, DhSectionPos.encode(LodUtil.CHUNK_DETAIL_LEVEL, chunkPosX, chunkPosZ), dataCache); } { return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, new DhLodPos(LodUtil.CHUNK_DETAIL_LEVEL, chunkPosX, chunkPosZ), dataCache); }
@Override @Override
public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, int regionPosX, int regionPosZ, IDhApiTerrainDataCache dataCache) public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, int regionPosX, int regionPosZ, @Nullable IDhApiTerrainDataCache dataCache)
{ return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, DhSectionPos.encode(LodUtil.REGION_DETAIL_LEVEL, regionPosX, regionPosZ), dataCache); } { return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, new DhLodPos(LodUtil.REGION_DETAIL_LEVEL, regionPosX, regionPosZ), dataCache); }
@Override @Override
public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtDetailLevelAndPos(IDhApiLevelWrapper levelWrapper, byte detailLevel, int posX, int posZ, IDhApiTerrainDataCache dataCache) public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtDetailLevelAndPos(IDhApiLevelWrapper levelWrapper, byte detailLevel, int posX, int posZ, @Nullable IDhApiTerrainDataCache dataCache)
{ return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, DhSectionPos.encode(detailLevel, posX, posZ), dataCache); } { return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, new DhLodPos(detailLevel, posX, posZ), dataCache); }
// private getters // // private getters //
/** Returns a single API terrain datapoint that contains the given Y block position */ /** Returns a single API terrain datapoint that contains the given Y block position */
private static DhApiResult<DhApiTerrainDataPoint> getTerrainDataAtBlockYPos(IDhApiLevelWrapper levelWrapper, long requestedColumnPos, Integer blockYPos, IDhApiTerrainDataCache dataCache) private static DhApiResult<DhApiTerrainDataPoint> getTerrainDataAtBlockYPos(IDhApiLevelWrapper levelWrapper, DhLodPos requestedColumnPos, Integer blockYPos, @Nullable IDhApiTerrainDataCache dataCache)
{ {
DhApiResult<DhApiTerrainDataPoint[]> result = getTerrainDataColumnArray(levelWrapper, requestedColumnPos, blockYPos, dataCache); DhApiResult<DhApiTerrainDataPoint[]> result = getTerrainDataColumnArray(levelWrapper, requestedColumnPos, blockYPos, dataCache);
if (result.success && result.payload.length > 0) if (result.success && result.payload.length > 0)
@@ -128,7 +126,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
} }
/** /**
* Returns all the block columns represented by the given {@link DhSectionPos}. <br> * Returns all the block columns represented by the given {@link DhLodPos}. <br>
* IE, A position with the detail level: <br> * IE, A position with the detail level: <br>
* 0 (block): will return a 1x1 matrix of data. (don't do this, we have a specific method for that.) <br> * 0 (block): will return a 1x1 matrix of data. (don't do this, we have a specific method for that.) <br>
* 1 (2 blocks): will return a 2x2 matrix of data. <br> * 1 (2 blocks): will return a 2x2 matrix of data. <br>
@@ -137,14 +135,11 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
* will stop and return the in progress data if any errors are encountered. * will stop and return the in progress data if any errors are encountered.
*/ */
private static DhApiResult<DhApiTerrainDataPoint[][][]> getTerrainDataOverAreaForPositionDetailLevel( private static DhApiResult<DhApiTerrainDataPoint[][][]> getTerrainDataOverAreaForPositionDetailLevel(
IDhApiLevelWrapper levelWrapper, long requestedAreaPos, IDhApiLevelWrapper levelWrapper, DhLodPos requestedAreaPos,
IDhApiTerrainDataCache dataCache) @Nullable IDhApiTerrainDataCache dataCache)
{ {
byte requestedDetailLevel = DhSectionPos.getDetailLevel(requestedAreaPos); DhLodPos startingBlockPos = requestedAreaPos.getCornerLodPos(LodUtil.BLOCK_DETAIL_LEVEL);
long startingBlockPos = DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, int widthOfAreaInBlocks = BitShiftUtil.powerOfTwo(requestedAreaPos.detailLevel);
DhSectionPos.getX(requestedAreaPos) * BitShiftUtil.powerOfTwo(requestedDetailLevel - LodUtil.BLOCK_DETAIL_LEVEL),
DhSectionPos.getZ(requestedAreaPos) * BitShiftUtil.powerOfTwo(requestedDetailLevel - LodUtil.BLOCK_DETAIL_LEVEL));
int widthOfAreaInBlocks = BitShiftUtil.powerOfTwo(requestedDetailLevel);
DhApiTerrainDataPoint[][][] returnArray = new DhApiTerrainDataPoint[widthOfAreaInBlocks][widthOfAreaInBlocks][]; DhApiTerrainDataPoint[][][] returnArray = new DhApiTerrainDataPoint[widthOfAreaInBlocks][widthOfAreaInBlocks][];
int dataColumnsReturned = 0; int dataColumnsReturned = 0;
@@ -154,7 +149,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
{ {
for (int z = 0; z < widthOfAreaInBlocks; z++) for (int z = 0; z < widthOfAreaInBlocks; z++)
{ {
long blockColumnPos = DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, DhSectionPos.getX(startingBlockPos) + x, DhSectionPos.getZ(startingBlockPos) + z); DhLodPos blockColumnPos = new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, startingBlockPos.x + x, startingBlockPos.z + z);
DhApiResult<DhApiTerrainDataPoint[]> result = getTerrainDataColumnArray(levelWrapper, blockColumnPos, null, dataCache); DhApiResult<DhApiTerrainDataPoint[]> result = getTerrainDataColumnArray(levelWrapper, blockColumnPos, null, dataCache);
if (result.success) if (result.success)
{ {
@@ -180,8 +175,8 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
*/ */
private static DhApiResult<DhApiTerrainDataPoint[]> getTerrainDataColumnArray( private static DhApiResult<DhApiTerrainDataPoint[]> getTerrainDataColumnArray(
IDhApiLevelWrapper levelWrapper, IDhApiLevelWrapper levelWrapper,
long requestedColumnPos, Integer nullableBlockYPos, DhLodPos requestedColumnPos, Integer nullableBlockYPos,
IDhApiTerrainDataCache apiDataCache) @Nullable IDhApiTerrainDataCache apiDataCache)
{ {
//============// //============//
// validation // // validation //
@@ -202,14 +197,9 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
ILevelWrapper coreLevelWrapper = (ILevelWrapper) levelWrapper; ILevelWrapper coreLevelWrapper = (ILevelWrapper) levelWrapper;
// require a data cache to prevent horrible performance (especially on ray-casts) // the data cache can be null, but must be our own implementation
if (apiDataCache == null) if (apiDataCache != null
{ && !(apiDataCache instanceof DhApiTerrainDataCache))
return DhApiResult.createFail("Missing [" + IDhApiTerrainDataCache.class.getSimpleName() + "], if a cache isn't provided your repo operations will be significantly slower.");
}
// the data cache must be our own implementation
if (!(apiDataCache instanceof DhApiTerrainDataCache))
{ {
return DhApiResult.createFail("Unsupported [" + IDhApiTerrainDataCache.class.getSimpleName() + "] implementation, only the core class [" + DhApiTerrainDataCache.class.getSimpleName() + "] is a valid parameter."); return DhApiResult.createFail("Unsupported [" + IDhApiTerrainDataCache.class.getSimpleName() + "] implementation, only the core class [" + DhApiTerrainDataCache.class.getSimpleName() + "] is a valid parameter.");
} }
@@ -223,12 +213,12 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
} }
// get the detail levels for this request // get the detail levels for this request
byte requestedDetailLevel = DhSectionPos.getDetailLevel(requestedColumnPos); byte requestedDetailLevel = requestedColumnPos.detailLevel;
byte sectionDetailLevel = (byte) (requestedDetailLevel + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL); byte sectionDetailLevel = (byte) (requestedDetailLevel + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
// get the positions for this request // get the positions for this request
long sectionPos = DhSectionPos.convertToDetailLevel(requestedColumnPos, sectionDetailLevel); long sectionPos = requestedColumnPos.getSectionPosWithSectionDetailLevel(sectionDetailLevel);
long relativePos = DhSectionPos.getDhSectionRelativePositionForDetailLevel(requestedColumnPos, DhSectionPos.getDetailLevel(requestedColumnPos)); DhLodPos relativePos = requestedColumnPos.getDhSectionRelativePositionForDetailLevel();
@@ -268,7 +258,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
//===============================// //===============================//
FullDataPointIdMap mapping = dataSource.mapping; FullDataPointIdMap mapping = dataSource.mapping;
LongArrayList dataColumn = dataSource.getColumnAtRelPos(DhSectionPos.getX(relativePos), DhSectionPos.getZ(relativePos)); LongArrayList dataColumn = dataSource.getColumnAtRelPos(relativePos.x, relativePos.z);
if (dataColumn != null) if (dataColumn != null)
{ {
int dataColumnIndexCount = dataColumn.size(); int dataColumnIndexCount = dataColumn.size();
@@ -518,7 +508,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
// this will throw a cast exception if the chunk object array isn't correct // this will throw a cast exception if the chunk object array isn't correct
IChunkWrapper chunk = SingletonInjector.INSTANCE.get(IWrapperFactory.class).createChunkWrapper(chunkObjectArray); IChunkWrapper chunk = SingletonInjector.INSTANCE.get(IWrapperFactory.class).createChunkWrapper(chunkObjectArray);
SharedApi.INSTANCE.applyChunkUpdate(chunk, dhLevel.getLevelWrapper()); SharedApi.INSTANCE.applyChunkUpdate(chunk, dhLevel.getLevelWrapper(), true, true);
return DhApiResult.createSuccess(); return DhApiResult.createSuccess();
@@ -551,10 +541,10 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
Thread thread = new Thread(() -> { Thread thread = new Thread(() -> {
try try
{ {
DhApiResult<DhApiTerrainDataPoint> single = getTerrainDataAtBlockYPos(levelWrapper, DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), blockPosY, debugDataCache); DhApiResult<DhApiTerrainDataPoint> single = getTerrainDataAtBlockYPos(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), blockPosY, debugDataCache);
DhApiResult<DhApiTerrainDataPoint[]> column = getTerrainDataColumnArray(levelWrapper, DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), null, debugDataCache); DhApiResult<DhApiTerrainDataPoint[]> column = getTerrainDataColumnArray(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), null, debugDataCache);
long chunkPos = DhSectionPos.encodeContaining(LodUtil.CHUNK_DETAIL_LEVEL, new DhChunkPos(blockPosX, blockPosZ)); DhLodPos chunkPos = new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ).convertToDetailLevel(LodUtil.CHUNK_DETAIL_LEVEL);
DhApiResult<DhApiTerrainDataPoint[][][]> area = getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, chunkPos, debugDataCache); DhApiResult<DhApiTerrainDataPoint[][][]> area = getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, chunkPos, debugDataCache);
@@ -590,9 +580,9 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
if (rayCast.success if (rayCast.success
&& rayCast.payload != null) && rayCast.payload != null)
{ {
DEBUG_RENDERER.makeParticle( DebugRenderer.makeParticle(
new AbstractDebugWireframeRenderer.BoxParticle( new DebugRenderer.BoxParticle(
new AbstractDebugWireframeRenderer.Box( new DebugRenderer.Box(
DhSectionPos.encode((byte) 0, rayCast.payload.pos.x, rayCast.payload.pos.z), DhSectionPos.encode((byte) 0, rayCast.payload.pos.x, rayCast.payload.pos.z),
rayCast.payload.dataPoint.bottomYBlockPos, rayCast.payload.dataPoint.bottomYBlockPos,
rayCast.payload.dataPoint.topYBlockPos, rayCast.payload.dataPoint.topYBlockPos,
@@ -23,35 +23,29 @@ import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode; import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass; import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*; import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
import com.seibel.distanthorizons.api.objects.DhApiResult;
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState; import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat; import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure; import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.network.messages.MessageRegistry; import com.seibel.distanthorizons.core.network.messages.MessageRegistry;
import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.DhApiRenderProxy; import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
import com.seibel.distanthorizons.core.render.RenderParams;
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
import com.seibel.distanthorizons.core.render.renderer.*; import com.seibel.distanthorizons.core.render.renderer.*;
import com.seibel.distanthorizons.core.util.TimerUtil; import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.math.Vec3d; import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.util.objects.Pair; import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhMetaRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhVanillaFadeRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhTestTriangleRenderer;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector; import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage; import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.core.network.session.NetworkSession; import com.seibel.distanthorizons.core.network.session.NetworkSession;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode; import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel; import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.world.AbstractDhWorld; import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.world.DhClientWorld; import com.seibel.distanthorizons.core.world.DhClientWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
@@ -61,13 +55,23 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapp
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
import org.joml.Vector4f;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL46;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.text.NumberFormat;
import java.util.*; import java.util.*;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
import java.util.function.Function;
/** /**
* This holds the methods that should be called * This holds the methods that should be called
@@ -77,9 +81,11 @@ import java.util.concurrent.ThreadPoolExecutor;
public class ClientApi public class ClientApi
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final DhLogger RATE_LIMITED_LOGGER = new DhLoggerBuilder().maxCountPerSecond(1).build();
public static boolean prefLoggerEnabled = false;
public static final ClientApi INSTANCE = new ClientApi(); public static final ClientApi INSTANCE = new ClientApi();
public static final TestRenderer TEST_RENDERER = new TestRenderer();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
@@ -97,13 +103,6 @@ public class ClientApi
*/ */
public static final DhRenderState RENDER_STATE = new DhRenderState(); public static final DhRenderState RENDER_STATE = new DhRenderState();
/**
* 50ms = 20 FPS
* @link https://fpstoms.com/
* @see ClientApi#cameraSpeedRollingAverage
*/
private static final long MIN_MS_BETWEEN_SPEED_CHECKS = 50;
private boolean isDevBuildMessagePrinted = false; private boolean isDevBuildMessagePrinted = false;
private boolean lowMemoryWarningPrinted = false; private boolean lowMemoryWarningPrinted = false;
@@ -127,26 +126,10 @@ public class ClientApi
/** Holds any chunks that were loaded before the {@link ClientApi#clientLevelLoadEvent(IClientLevelWrapper)} was fired. */ /** Holds any chunks that were loaded before the {@link ClientApi#clientLevelLoadEvent(IClientLevelWrapper)} was fired. */
public final HashMap<Pair<IClientLevelWrapper, DhChunkPos>, IChunkWrapper> waitingChunkByClientLevelAndPos = new HashMap<>(); public final HashMap<Pair<IClientLevelWrapper, DhChunkPos>, IChunkWrapper> waitingChunkByClientLevelAndPos = new HashMap<>();
/** publicly available so {@link F3Screen} can display the error */
@Nullable @Nullable
public String lastRenderParamValidationMessage = null; public String lastRenderParamValidationMessage = null;
/**
* measured in blocks/second <br>
*
* The number of points tracked here is related
* to the rate at which we check for speed.
* So if the ms_between is changed the number of points
* tracked should also be to keep the ratio roughly the same.
* @see ClientApi#MIN_MS_BETWEEN_SPEED_CHECKS
*/
public RollingAverage cameraSpeedRollingAverage = new RollingAverage(40);
private Vec3d lastCameraPosForSpeedCheck = new Vec3d();
private long msSinceLastSpeedCheck = 0L;
//==============// //==============//
// constructors // // constructors //
@@ -159,7 +142,6 @@ public class ClientApi
//==============// //==============//
// world events // // world events //
//==============// //==============//
///region
/** /**
* May be fired slightly before or after the associated * May be fired slightly before or after the associated
@@ -189,12 +171,13 @@ public class ClientApi
MC_CLIENT.sendChatMessage("DH may behave strangely or have missing functionality."); MC_CLIENT.sendChatMessage("DH may behave strangely or have missing functionality.");
MC_CLIENT.sendChatMessage("In order to use pre-generated LODs, put your DH database(s) in:"); MC_CLIENT.sendChatMessage("In order to use pre-generated LODs, put your DH database(s) in:");
MC_CLIENT.sendChatMessage(MinecraftTextFormat.GRAY +".Minecraft" + File.separator + ClientOnlySaveStructure.SERVER_DATA_FOLDER_NAME + File.separator + ClientOnlySaveStructure.REPLAY_SERVER_FOLDER_NAME + File.separator + "DIMENSION_NAME"+ MinecraftTextFormat.CLEAR_FORMATTING); MC_CLIENT.sendChatMessage(MinecraftTextFormat.GRAY +".Minecraft" + File.separator + ClientOnlySaveStructure.SERVER_DATA_FOLDER_NAME + File.separator + ClientOnlySaveStructure.REPLAY_SERVER_FOLDER_NAME + File.separator + "DIMENSION_NAME"+ MinecraftTextFormat.CLEAR_FORMATTING);
MC_CLIENT.sendChatMessage("This message can be disabled in DH's config under Advanced -> Logging."); MC_CLIENT.sendChatMessage("This can be disabled in DH's config under Advanced -> Logging.");
MC_CLIENT.sendChatMessage(""); MC_CLIENT.sendChatMessage("");
} }
} }
// firing after clientLevelLoadEvent
// TODO if level has prepped to load it should fire level load event
DhClientWorld world = new DhClientWorld(); DhClientWorld world = new DhClientWorld();
SharedApi.setDhWorld(world); SharedApi.setDhWorld(world);
@@ -237,14 +220,11 @@ public class ClientApi
this.waitingClientLevels.clear(); this.waitingClientLevels.clear();
} }
///endregion
//==============// //==============//
// level events // // level events //
//==============// //==============//
///region
public void clientLevelUnloadEvent(IClientLevelWrapper level) public void clientLevelUnloadEvent(IClientLevelWrapper level)
{ {
@@ -261,6 +241,7 @@ public class ClientApi
if (world != null) if (world != null)
{ {
world.unloadLevel(level); world.unloadLevel(level);
SharedApi.INSTANCE.clearQueuedChunkUpdates();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(level)); ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(level));
} }
else else
@@ -277,13 +258,6 @@ public class ClientApi
public void clientLevelLoadEvent(@Nullable IClientLevelWrapper levelWrapper) public void clientLevelLoadEvent(@Nullable IClientLevelWrapper levelWrapper)
{ {
// can happen if there was an issue during level load
if (levelWrapper == null)
{
return;
}
// wait a moment before loading the level to give the server a chance to handle the client's login request // wait a moment before loading the level to give the server a chance to handle the client's login request
if (MC_CLIENT.clientConnectedToDedicatedServer()) if (MC_CLIENT.clientConnectedToDedicatedServer())
{ {
@@ -344,7 +318,7 @@ public class ClientApi
if (levelWrapper.equals(level)) if (levelWrapper.equals(level))
{ {
IChunkWrapper chunkWrapper = this.waitingChunkByClientLevelAndPos.get(levelChunkPair); IChunkWrapper chunkWrapper = this.waitingChunkByClientLevelAndPos.get(levelChunkPair);
SharedApi.INSTANCE.applyChunkUpdate(chunkWrapper, levelWrapper); SharedApi.INSTANCE.chunkLoadEvent(chunkWrapper, levelWrapper);
keysToRemove.add(levelChunkPair); keysToRemove.add(levelChunkPair);
} }
} }
@@ -356,14 +330,11 @@ public class ClientApi
} }
} }
///endregion
//============// //============//
// networking // // networking //
//============// //============//
///region
/** /**
* Forwards a decoded message into the registered handlers. * Forwards a decoded message into the registered handlers.
@@ -396,14 +367,11 @@ public class ClientApi
} }
} }
///endregion
//===============// //===============//
// LOD rendering // // LOD rendering //
//===============// //===============//
///region
/** Should be called before {@link ClientApi#renderDeferredLodsForShaders} */ /** Should be called before {@link ClientApi#renderDeferredLodsForShaders} */
public void renderLods() { this.renderLodLayer(false); } public void renderLods() { this.renderLodLayer(false); }
@@ -414,101 +382,50 @@ public class ClientApi
*/ */
public void renderDeferredLodsForShaders() { this.renderLodLayer(true); } public void renderDeferredLodsForShaders() { this.renderLodLayer(true); }
public static long firstRenderTimeMs = 0;
private void renderLodLayer(boolean renderingDeferredLayer) private void renderLodLayer(boolean renderingDeferredLayer)
{ {
//=========//
// logging //
//=========//
this.sendQueuedChatMessages();
IProfilerWrapper profiler = MC_CLIENT.getProfiler(); IProfilerWrapper profiler = MC_CLIENT.getProfiler();
profiler.pop(); // get out of "terrain"
profiler.push("DH-RenderLevel"); profiler.push("DH-RenderLevel");
//===========//
// debugging //
//===========//
//region
//DhApiTerrainDataRepo.asyncDebugMethod(
// RENDER_STATE.clientLevelWrapper,
// MC_CLIENT.getPlayerBlockPos().getX(),
// MC_CLIENT.getPlayerBlockPos().getY(),
// MC_CLIENT.getPlayerBlockPos().getZ()
//);
//endregion
//=====================// //=====================//
// render thread tasks // // render thread tasks //
//=====================// //=====================//
///region
// only run these tasks once per frame // only run these tasks once per frame
if (!renderingDeferredLayer) if (!renderingDeferredLayer)
{ {
profiler.push("DH render thread tasks"); profiler.push("DH render thread tasks");
//===============//
// chat messages //
//===============//
this.sendQueuedChatMessages();
//======================//
// GL Proxy queued jobs //
//======================//
try try
{ {
// make sure the GLProxy is created for future use
GLProxy.getInstance();
// these tasks always need to be called, regardless of whether the renderer is enabled or not to prevent memory leaks // these tasks always need to be called, regardless of whether the renderer is enabled or not to prevent memory leaks
RenderThreadTaskHandler.INSTANCE.runRenderThreadTasks(); GLProxy.runRenderThreadTasks();
} }
catch (Exception e) catch (Exception e)
{ {
LOGGER.error("Unexpected issue running render thread tasks, error: [" + e.getMessage() + "].", e); LOGGER.error("Unexpected issue running render thread tasks, error: [" + e.getMessage() + "].", e);
} }
//==============//
// camera speed //
//==============//
long nowMs = System.currentTimeMillis();
if (this.msSinceLastSpeedCheck + MIN_MS_BETWEEN_SPEED_CHECKS < nowMs)
{
// calc time since last check
double secSinceLastCheck = (nowMs - this.msSinceLastSpeedCheck) / 1_000.0;
this.msSinceLastSpeedCheck = nowMs;
// get the distance traveled since last frame
Vec3d camPos = MC_RENDER.getCameraExactPosition();
double distanceInBlocks = camPos.getDistance(this.lastCameraPosForSpeedCheck);
double speed = distanceInBlocks / secSinceLastCheck;
// record new values for next check
this.cameraSpeedRollingAverage.add(speed);
this.lastCameraPosForSpeedCheck = camPos;
}
profiler.pop(); profiler.pop();
} }
///endregion
//=================// //=================//
// parameter setup // // parameter setup //
//=================// //=================//
///region
EDhApiRenderPass renderPass; EDhApiRenderPass renderPass;
if (DhApiRenderProxy.INSTANCE.getDeferTransparentRendering()) if (DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
@@ -531,26 +448,24 @@ public class ClientApi
// render prep and actual rendering into different threads/methods // render prep and actual rendering into different threads/methods
// this is annoying since it's possible to start a render with only // this is annoying since it's possible to start a render with only
// partially complete info, but there isn't a better option at the moment // partially complete info, but there isn't a better option at the moment
RenderParams renderParams = new RenderParams(renderPass, RENDER_STATE); RenderParams renderParams =
new RenderParams(
///endregion renderPass,
RENDER_STATE.frameTime,
RENDER_STATE.mcProjectionMatrix, RENDER_STATE.mcModelViewMatrix,
RENDER_STATE.clientLevelWrapper
);
//============// //============//
// validation // // validation //
//============// //============//
///region
if (firstRenderTimeMs == 0) // TODO write this message to the F3 menu so people can see when a different mod screws with the lightmap
{ String validationMessage = renderParams.getValidationErrorMessage();
firstRenderTimeMs = System.currentTimeMillis();
}
String validationMessage = renderParams.getValidationErrorMessage(firstRenderTimeMs);
if (validationMessage != null) if (validationMessage != null)
{ {
// store the error message so it can be seen on the F3 screen
this.lastRenderParamValidationMessage = validationMessage; this.lastRenderParamValidationMessage = validationMessage;
return; return;
} }
@@ -572,29 +487,22 @@ public class ClientApi
return; return;
} }
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DISABLED)
{
return;
}
///endregion
//===========// //===========//
// rendering // // rendering //
//===========// //===========//
///region
try try
{ {
// render pass // // render pass //
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT)
if (!renderingDeferredLayer)
{ {
if (!renderingDeferredLayer) if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT)
{ {
boolean renderingCancelled = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderEvent.class, renderParams); boolean renderingCancelledForThisFrame = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderEvent.class, renderParams);
if (!renderingCancelled) if (!renderingCancelledForThisFrame)
{ {
LodRenderer.INSTANCE.render(renderParams, profiler); LodRenderer.INSTANCE.render(renderParams, profiler);
} }
@@ -604,42 +512,25 @@ public class ClientApi
ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterRenderEvent.class, null); ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterRenderEvent.class, null);
} }
} }
else else if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEBUG)
{ {
boolean renderingCancelled = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeDeferredRenderEvent.class, renderParams); profiler.push("Render Debug");
if (!renderingCancelled) ClientApi.TEST_RENDERER.render();
{ profiler.pop();
LodRenderer.INSTANCE.renderDeferred(renderParams, profiler);
}
if (DhApi.Delayed.renderProxy.getDeferTransparentRendering())
{
ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterRenderEvent.class, null);
}
} }
} }
else else
{ {
if (!renderingDeferredLayer) boolean renderingCancelled = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeDeferredRenderEvent.class, renderParams);
if (!renderingCancelled)
{ {
IDhMetaRenderer metaRenderer = SingletonInjector.INSTANCE.get(IDhMetaRenderer.class); LodRenderer.INSTANCE.renderDeferred(renderParams, profiler);
IDhTestTriangleRenderer testRenderer = SingletonInjector.INSTANCE.get(IDhTestTriangleRenderer.class); }
if (testRenderer != null
&& metaRenderer != null)
{ if (DhApi.Delayed.renderProxy.getDeferTransparentRendering())
// meta renderer needed for render state/texture {
// for setup on some APIs (IE openGL) ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterRenderEvent.class, null);
metaRenderer.runRenderPassSetup(renderParams);
testRenderer.render(renderParams);
metaRenderer.runRenderPassCleanup(renderParams);
}
else
{
RATE_LIMITED_LOGGER.warn("Unable to find singleton [" + IDhTestTriangleRenderer.class.getSimpleName() + "]");
}
} }
} }
} }
@@ -654,36 +545,29 @@ public class ClientApi
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Error: " + MinecraftTextFormat.CLEAR_FORMATTING + e); MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Error: " + MinecraftTextFormat.CLEAR_FORMATTING + e);
} }
///endregion
profiler.pop(); // end LOD profiler.pop(); // end LOD
profiler.push("terrain"); // go back into "terrain"
} }
///endregion
//================// //================//
// fade rendering // // fade rendering //
//================// //================//
///region
/** /**
* The first fade pass. * The first fade pass.
* Called after MC finishes rendering the opaque passes. * Called after MC finishes rendering the opaque passes.
*/ */
public void renderFadeOpaque() public void renderFadeOpaque() // TODO this is actually the transparent pass
{ {
IDhVanillaFadeRenderer fadeRenderer = SingletonInjector.INSTANCE.get(IDhVanillaFadeRenderer.class); DepthCalculator.INSTANCE.getMcTransparentDepthTexture();
if (fadeRenderer == null) DepthCalculator.INSTANCE.tryCalculateAsync();
{
return;
}
// only fade when DH is rendering // only fade when DH is rendering
if (Config.Client.Advanced.Debugging.rendererMode.get() != EDhApiRendererMode.DISABLED if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT
&& &&
( (
// only fade when requested // only fade when requested
@@ -694,8 +578,7 @@ public class ClientApi
// don't fade when Iris shaders are active, otherwise the rendering can get weird // don't fade when Iris shaders are active, otherwise the rendering can get weird
&& !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering()) && !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
{ {
RenderParams renderParams = new RenderParams(EDhApiRenderPass.OPAQUE, RENDER_STATE); VanillaFadeRenderer.INSTANCE.render(RENDER_STATE.mcModelViewMatrix, RENDER_STATE.mcProjectionMatrix, RENDER_STATE.frameTime, RENDER_STATE.clientLevelWrapper);
fadeRenderer.render(renderParams);
} }
} }
/** /**
@@ -703,16 +586,12 @@ public class ClientApi
* Called after MC finishes rendering both opaque * Called after MC finishes rendering both opaque
* and transparent passes. * and transparent passes.
*/ */
public void renderFadeTransparent() public void renderFadeTransparent() // TODO this is actually the opaque pass
{ {
IDhVanillaFadeRenderer fadeRenderer = SingletonInjector.INSTANCE.get(IDhVanillaFadeRenderer.class); DepthCalculator.INSTANCE.getMcOpaqueDepthTexture();
if (fadeRenderer == null)
{
return;
}
// only fade when DH is rendering // only fade when DH is rendering
if (Config.Client.Advanced.Debugging.rendererMode.get() != EDhApiRendererMode.DISABLED) if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT)
{ {
boolean renderFade = boolean renderFade =
( (
@@ -725,57 +604,47 @@ public class ClientApi
&& !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering(); && !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering();
if (renderFade) if (renderFade)
{ {
RenderParams renderParams = new RenderParams(EDhApiRenderPass.TRANSPARENT, RENDER_STATE); VanillaFadeRenderer.INSTANCE.render(RENDER_STATE.mcModelViewMatrix, RENDER_STATE.mcProjectionMatrix, RENDER_STATE.frameTime, RENDER_STATE.clientLevelWrapper);
fadeRenderer.render(renderParams);
} }
} }
} }
///endregion
//=================//
//==========// // DEBUG USE //
// keyboard // //=================//
//==========//
///region
/** Trigger once on key press, with CLIENT PLAYER. */ /** Trigger once on key press, with CLIENT PLAYER. */
public void keyPressedEvent(int glfwKey) public void keyPressedEvent(int glfwKey)
{ {
if (!Config.Client.Advanced.Debugging.enableDebugKeybindings.get()) //if (!Config.Client.Advanced.Debugging.enableDebugKeybindings.get())
{ //{
// keybindings are disabled // // keybindings are disabled
return; // return;
} //}
if (glfwKey == GLFW.GLFW_KEY_F6) if (glfwKey == GLFW.GLFW_KEY_F8)
{ {
Config.Client.Advanced.Debugging.rendererMode.set(EDhApiRendererMode.next(Config.Client.Advanced.Debugging.rendererMode.get())); DepthCalculator.INSTANCE.pause = true;
MC_CLIENT.sendChatMessage("F6: Set rendering to " + Config.Client.Advanced.Debugging.rendererMode.get()); //Config.Client.Advanced.Debugging.debugRendering.set(EDhApiDebugRendering.next(Config.Client.Advanced.Debugging.debugRendering.get()));
//MC_CLIENT.sendChatMessage("F8: Set debug mode to " + Config.Client.Advanced.Debugging.debugRendering.get());
} }
else if (glfwKey == GLFW.GLFW_KEY_F7) else if (glfwKey == GLFW.GLFW_KEY_F6)
{ {
Config.Client.Advanced.Debugging.lodOnlyMode.set(!Config.Client.Advanced.Debugging.lodOnlyMode.get()); DepthCalculator.INSTANCE.pause = true;
MC_CLIENT.sendChatMessage("F7: Set LOD only mode to " + Config.Client.Advanced.Debugging.lodOnlyMode.get()); //Config.Client.Advanced.Debugging.rendererMode.set(EDhApiRendererMode.next(Config.Client.Advanced.Debugging.rendererMode.get()));
//MC_CLIENT.sendChatMessage("F6: Set rendering to " + Config.Client.Advanced.Debugging.rendererMode.get());
} }
else if (glfwKey == GLFW.GLFW_KEY_F8) else if (glfwKey == GLFW.GLFW_KEY_P)
{ {
Config.Client.Advanced.Debugging.debugRenderingColors.set(EDhApiDebugRendering.next(Config.Client.Advanced.Debugging.debugRenderingColors.get())); DepthCalculator.INSTANCE.pause = true;
MC_CLIENT.sendChatMessage("F8: Set debug mode to " + Config.Client.Advanced.Debugging.debugRenderingColors.get()); //prefLoggerEnabled = !prefLoggerEnabled;
//MC_CLIENT.sendChatMessage("P: Debug Pref Logger is " + (prefLoggerEnabled ? "enabled" : "disabled"));
} }
} }
///endregion
//======//
// chat //
//======//
///region
private void sendQueuedChatMessages() private void sendQueuedChatMessages()
{ {
// this includes if the current build is a dev build // this includes if the current build is a dev build
@@ -908,8 +777,6 @@ public class ClientApi
*/ */
public void showOverlayMessageNextFrame(String message) { this.overlayMessageQueueForNextFrame.add(message); } public void showOverlayMessageNextFrame(String message) { this.overlayMessageQueueForNextFrame.add(message); }
///endregion
} }
@@ -9,7 +9,7 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent; import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent;
import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage; import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage;
import com.seibel.distanthorizons.core.network.session.NetworkSession; import com.seibel.distanthorizons.core.network.session.NetworkSession;
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler; import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -90,7 +90,7 @@ public class ClientPluginChannelApi
LOGGER.info("Server level key received: [" + msg.levelKey + "]."); LOGGER.info("Server level key received: [" + msg.levelKey + "].");
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("ClientPluginChannelApi onLevelInitMessage", () -> GLProxy.queueRunningOnRenderThread(() ->
{ {
IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true); IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true);
IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel(); IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel();
@@ -101,6 +101,7 @@ public class ServerApi
if (serverWorld != null) if (serverWorld != null)
{ {
serverWorld.unloadLevel(level); serverWorld.unloadLevel(level);
SharedApi.INSTANCE.clearQueuedChunkUpdates();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(level)); ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(level));
} }
} }
@@ -111,8 +112,8 @@ public class ServerApi
// chunk modified events // // chunk modified events //
//=======================// //=======================//
public void serverChunkLoadEvent(IChunkWrapper chunkWrapper, ILevelWrapper level) { SharedApi.INSTANCE.applyChunkUpdate(chunkWrapper, level); } public void serverChunkLoadEvent(IChunkWrapper chunkWrapper, ILevelWrapper level) { SharedApi.INSTANCE.applyChunkUpdate(chunkWrapper, level, false, false); }
public void serverChunkSaveEvent(IChunkWrapper chunkWrapper, ILevelWrapper level) { SharedApi.INSTANCE.applyChunkUpdate(chunkWrapper, level); } public void serverChunkSaveEvent(IChunkWrapper chunkWrapper, ILevelWrapper level) { SharedApi.INSTANCE.applyChunkUpdate(chunkWrapper, level, true, false); }
@@ -24,22 +24,27 @@ import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiWorldUn
import com.seibel.distanthorizons.core.Initializer; import com.seibel.distanthorizons.core.Initializer;
import com.seibel.distanthorizons.core.api.internal.chunkUpdating.ChunkUpdateData; import com.seibel.distanthorizons.core.api.internal.chunkUpdating.ChunkUpdateData;
import com.seibel.distanthorizons.core.api.internal.chunkUpdating.ChunkUpdateQueueManager; import com.seibel.distanthorizons.core.api.internal.chunkUpdating.ChunkUpdateQueueManager;
import com.seibel.distanthorizons.core.api.internal.chunkUpdating.WorldChunkUpdateManager; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.eventHandlers.IgnoredDimensionCsvHandler; import com.seibel.distanthorizons.core.config.eventHandlers.IgnoredDimensionCsvHandler;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.level.DhClientLevel; import com.seibel.distanthorizons.core.level.DhClientLevel;
import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer;
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo; import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.Pair; import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.world.*; import com.seibel.distanthorizons.core.world.*;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector; import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
@@ -58,82 +63,82 @@ public class SharedApi
/** will be null on the server-side */ /** will be null on the server-side */
@Nullable @Nullable
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
/** will be null on the server-side */
@Nullable
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftSharedWrapper MC_SHARED = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
public static final WorldChunkUpdateManager WORLD_CHUNK_UPDATE_MANAGER = WorldChunkUpdateManager.INSTANCE; // local fariable for quick access public static final ChunkUpdateQueueManager CHUNK_UPDATE_QUEUE_MANAGER = new ChunkUpdateQueueManager();
/**
* how many chunks can be queued for updating per thread + player (in multiplayer),
* used to prevent updates from infinitely pilling up if the user flies around extremely fast
*/
public static final int MAX_UPDATING_CHUNK_COUNT_PER_THREAD_AND_PLAYER = 1_000;
/** how many milliseconds must pass before an overloaded message can be sent in chat or the log */
public static final int MIN_MS_BETWEEN_OVERLOADED_LOG_MESSAGE = 30_000;
@Nullable @Nullable
private static AbstractDhWorld currentWorld; private static AbstractDhWorld currentWorld;
private static final Object worldLockObject = new Object(); private static int lastWorldGenTickDelta = 0;
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
//region
private SharedApi() { } private SharedApi() { }
public static void init() { Initializer.init(); } public static void init() { Initializer.init(); }
//endregion
//===============// //===============//
// world methods // // world methods //
//===============// //===============//
//region
public static EWorldEnvironment getEnvironment() { return (currentWorld == null) ? null : currentWorld.environment; } public static EWorldEnvironment getEnvironment() { return (currentWorld == null) ? null : currentWorld.environment; }
public static void setDhWorld(AbstractDhWorld newWorld) public static void setDhWorld(AbstractDhWorld newWorld)
{ {
synchronized (worldLockObject) AbstractDhWorld oldWorld = currentWorld;
if (oldWorld != null)
{ {
AbstractDhWorld oldWorld = currentWorld; oldWorld.close();
if (oldWorld != null) }
{ currentWorld = newWorld;
oldWorld.close();
} // starting and stopping the DataRenderTransformer is necessary to prevent attempting to
currentWorld = newWorld; // access the MC level at inappropriate times, which can cause exceptions
if (currentWorld != null)
{
ThreadPoolUtil.setupThreadPools();
// starting and stopping the DataRenderTransformer is necessary to prevent attempting to ApiEventInjector.INSTANCE.fireAllEvents(DhApiWorldLoadEvent.class, new DhApiWorldLoadEvent.EventParam());
// access the MC level at inappropriate times, which can cause exceptions }
if (currentWorld != null) else
{
ThreadPoolUtil.shutdownThreadPools();
DebugRenderer.clearRenderables();
if (MC_RENDER != null)
{ {
ThreadPoolUtil.setupThreadPools(); MC_RENDER.clearTargetFrameBuffer();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiWorldLoadEvent.class, new DhApiWorldLoadEvent.EventParam());
}
else
{
ThreadPoolUtil.shutdownThreadPools();
// delayed get because SharedApi will be created before the singleton has been bound
AbstractDebugWireframeRenderer debugWireframeRenderer = SingletonInjector.INSTANCE.get(AbstractDebugWireframeRenderer.class);
debugWireframeRenderer.clearRenderables();
if (MC_RENDER != null)
{
MC_RENDER.clearTargetFrameBuffer();
}
// shouldn't be necessary, but if we missed closing one of the connections this should make sure they're all closed
AbstractDhRepo.closeAllConnections();
// needs to be closed on world shutdown to clear out un-processed chunks
WORLD_CHUNK_UPDATE_MANAGER.clear();
RenderThreadTaskHandler.INSTANCE.clearDebugStats();
// recommend that the garbage collector cleans up any objects from the old world and thread pools
System.gc();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiWorldUnloadEvent.class, new DhApiWorldUnloadEvent.EventParam());
// fired after the unload event so API users can't change the read-only for any new worlds
DhApiWorldProxy.INSTANCE.setReadOnly(false, false);
} }
// shouldn't be necessary, but if we missed closing one of the connections this should make sure they're all closed
AbstractDhRepo.closeAllConnections();
// needs to be closed on world shutdown to clear out un-processed chunks
CHUNK_UPDATE_QUEUE_MANAGER.clear();
// recommend that the garbage collector cleans up any objects from the old world and thread pools
System.gc();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiWorldUnloadEvent.class, new DhApiWorldUnloadEvent.EventParam());
// fired after the unload event so API users can't change the read-only for any new worlds
DhApiWorldProxy.INSTANCE.setReadOnly(false, false);
} }
} }
@@ -148,44 +153,39 @@ public class SharedApi
@Nullable @Nullable
public static IDhServerWorld tryGetDhServerWorld() { return (currentWorld instanceof IDhServerWorld) ? (IDhServerWorld) currentWorld : null; } public static IDhServerWorld tryGetDhServerWorld() { return (currentWorld instanceof IDhServerWorld) ? (IDhServerWorld) currentWorld : null; }
//endregion
//==============// //==============//
// chunk update // // chunk update //
//==============// //==============//
//region
/** /**
* Used to prevent getting a full chunk from MC if it isn't necessary. <br> * Used to prevent getting a full chunk from MC if it isn't necessary. <br>
* This is important since asking MC for a chunk is slow and may block the render thread. * This is important since asking MC for a chunk is slow and may block the render thread.
*/ */
public static boolean isChunkAtBlockPosAlreadyUpdating(ILevelWrapper levelWrapper, int blockPosX, int blockPosZ) public static boolean isChunkAtBlockPosAlreadyUpdating(int blockPosX, int blockPosZ)
{ { return CHUNK_UPDATE_QUEUE_MANAGER.contains(new DhChunkPos(new DhBlockPos2D(blockPosX, blockPosZ))); }
ChunkUpdateQueueManager manager = WORLD_CHUNK_UPDATE_MANAGER.getByLevelWrapper(levelWrapper);
if (manager == null)
{
return true;
}
return manager.contains(new DhChunkPos(new DhBlockPos2D(blockPosX, blockPosZ)));
}
public static boolean isChunkAtChunkPosAlreadyUpdating(ILevelWrapper levelWrapper, int chunkPosX, int chunkPosZ) public static boolean isChunkAtChunkPosAlreadyUpdating(int chunkPosX, int chunkPosZ)
{ { return CHUNK_UPDATE_QUEUE_MANAGER.contains(new DhChunkPos(chunkPosX, chunkPosZ)); }
ChunkUpdateQueueManager manager = WORLD_CHUNK_UPDATE_MANAGER.getByLevelWrapper(levelWrapper);
if (manager == null) /**
{ * This is often fired when unloading a level.
return true; * This is done to prevent overloading the system when
} * rapidly changing dimensions.
* (IE prevent DH from infinitely allocating memory
return manager.contains(new DhChunkPos(chunkPosX, chunkPosZ)); */
} public void clearQueuedChunkUpdates() { CHUNK_UPDATE_QUEUE_MANAGER.clear(); }
public int getQueuedChunkUpdateCount() { return CHUNK_UPDATE_QUEUE_MANAGER.getQueuedCount(); }
public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper levelWrapper) /** handles both block place and break events */
public void chunkBlockChangedEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, true, false); }
public void chunkLoadEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, true, true); }
public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper level, boolean canGetNeighboringChunks, boolean newlyLoaded)
{ {
//===================// //===================//
// validation checks // // validation checks //
@@ -200,11 +200,11 @@ public class SharedApi
AbstractDhWorld dhWorld = SharedApi.getAbstractDhWorld(); AbstractDhWorld dhWorld = SharedApi.getAbstractDhWorld();
if (dhWorld == null) if (dhWorld == null)
{ {
if (levelWrapper instanceof IClientLevelWrapper) if (level instanceof IClientLevelWrapper)
{ {
// If the client world isn't loaded yet, keep track of which chunks were loaded so we can use them later. // If the client world isn't loaded yet, keep track of which chunks were loaded so we can use them later.
// This may happen if the client world and client level load events happen out of order // This may happen if the client world and client level load events happen out of order
IClientLevelWrapper clientLevel = (IClientLevelWrapper) levelWrapper; IClientLevelWrapper clientLevel = (IClientLevelWrapper) level;
ClientApi.INSTANCE.waitingChunkByClientLevelAndPos.replace(new Pair<>(clientLevel, chunkWrapper.getChunkPos()), chunkWrapper); ClientApi.INSTANCE.waitingChunkByClientLevelAndPos.replace(new Pair<>(clientLevel, chunkWrapper.getChunkPos()), chunkWrapper);
} }
@@ -218,13 +218,13 @@ public class SharedApi
} }
// only continue if the level is loaded // only continue if the level is loaded
IDhLevel dhLevel = dhWorld.getLevel(levelWrapper); IDhLevel dhLevel = dhWorld.getLevel(level);
if (dhLevel == null) if (dhLevel == null)
{ {
if (levelWrapper instanceof IClientLevelWrapper) if (level instanceof IClientLevelWrapper)
{ {
// the client level isn't loaded yet // the client level isn't loaded yet
IClientLevelWrapper clientLevel = (IClientLevelWrapper) levelWrapper; IClientLevelWrapper clientLevel = (IClientLevelWrapper) level;
ClientApi.INSTANCE.waitingChunkByClientLevelAndPos.replace(new Pair<>(clientLevel, chunkWrapper.getChunkPos()), chunkWrapper); ClientApi.INSTANCE.waitingChunkByClientLevelAndPos.replace(new Pair<>(clientLevel, chunkWrapper.getChunkPos()), chunkWrapper);
} }
@@ -247,23 +247,21 @@ public class SharedApi
return; return;
} }
ChunkUpdateQueueManager chunkManager = WORLD_CHUNK_UPDATE_MANAGER.getByLevelWrapper(levelWrapper); // shouldn't normally happen, but just in case
// ignore the wrong level wrapper type or if (CHUNK_UPDATE_QUEUE_MANAGER.contains(chunkWrapper.getChunkPos()))
// if the chunk is already queued for handling
if (chunkManager == null
|| chunkManager.contains(chunkWrapper.getChunkPos()))
{ {
// TODO this will prevent some LODs from updating across dimensions if multiple levels are loaded
return; return;
} }
queueChunkUpdate(chunkManager, chunkWrapper, dhLevel); queueChunkUpdate(chunkWrapper, dhLevel);
} }
private static void queueChunkUpdate(ChunkUpdateQueueManager chunkManager, IChunkWrapper chunkWrapper, IDhLevel dhLevel) private static void queueChunkUpdate(IChunkWrapper chunkWrapper, IDhLevel dhLevel)
{ {
// return if the chunk is already queued // return if the chunk is already queued
if (chunkManager.contains(chunkWrapper.getChunkPos())) if (CHUNK_UPDATE_QUEUE_MANAGER.contains(chunkWrapper.getChunkPos()))
{ {
return; return;
} }
@@ -271,16 +269,19 @@ public class SharedApi
// add chunk update data to preUpdate queue // add chunk update data to preUpdate queue
ChunkUpdateData updateData = new ChunkUpdateData(chunkWrapper, dhLevel); ChunkUpdateData updateData = new ChunkUpdateData(chunkWrapper, dhLevel);
chunkManager.addItemToPreUpdateQueue(chunkWrapper.getChunkPos(), updateData); CHUNK_UPDATE_QUEUE_MANAGER.addItemToPreUpdateQueue(chunkWrapper.getChunkPos(), updateData);
// queue the next position if there are still positions to process // queue updates up to the number of CPU cores allocated for the job
AbstractExecutorService executor = ThreadPoolUtil.getChunkToLodBuilderExecutor(); // (this prevents doing extra work queuing tasks that may not be necessary)
if (executor != null) // and makes sure the chunks closest to the player are updated first
PriorityTaskPicker.Executor executor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
if (executor != null
&& executor.getQueueSize() < executor.getPoolSize())
{ {
try try
{ {
executor.execute(WORLD_CHUNK_UPDATE_MANAGER::processEachQueue); executor.execute(SharedApi::processQueue);
} }
catch (RejectedExecutionException ignore) catch (RejectedExecutionException ignore)
{ {
@@ -289,18 +290,167 @@ public class SharedApi
} }
} }
//endregion private static void processQueue()
{
// update the center & max size of the queue manager
int maxUpdateSizeMultiplier;
if (MC_CLIENT != null && MC_CLIENT.playerExists())
{
// Local worlds & multiplayer
CHUNK_UPDATE_QUEUE_MANAGER.setCenter(MC_CLIENT.getPlayerChunkPos());
maxUpdateSizeMultiplier = MC_CLIENT.clientConnectedToDedicatedServer() ? 1 : MC_SHARED.getPlayerCount();
}
else
{
// Dedicated servers
// Also includes spawn chunks since they're likely to be intentionally utilized with updates
maxUpdateSizeMultiplier = 1 + MC_SHARED.getPlayerCount();
}
CHUNK_UPDATE_QUEUE_MANAGER.maxSize = MAX_UPDATING_CHUNK_COUNT_PER_THREAD_AND_PLAYER
* Config.Common.MultiThreading.numberOfThreads.get()
* maxUpdateSizeMultiplier;
//===============================//
// update the necessary chunk(s) //
//===============================//
processQueuedChunkPreUpdate();
processQueuedChunkUpdate();
// queue the next position if there are still positions to process
AbstractExecutorService executor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
if (executor != null && !CHUNK_UPDATE_QUEUE_MANAGER.isEmpty())
{
try
{
executor.execute(SharedApi::processQueue);
}
catch (RejectedExecutionException ignore)
{
// the executor was shut down, it should be back up shortly and able to accept new jobs
}
}
}
private static void processQueuedChunkPreUpdate()
{
ChunkUpdateData preUpdateData = CHUNK_UPDATE_QUEUE_MANAGER.preUpdateQueue.popClosest();
if (preUpdateData == null)
{
return;
}
IDhLevel dhLevel = preUpdateData.dhLevel;
IChunkWrapper chunkWrapper = preUpdateData.chunkWrapper;
chunkWrapper.createDhHeightMaps();
try
{
// check if this chunk has been converted into an LOD already
boolean checkChunkHash = !Config.Common.LodBuilding.disableUnchangedChunkCheck.get();
if (checkChunkHash)
{
int oldChunkHash = dhLevel.getChunkHash(chunkWrapper.getChunkPos()); // shouldn't happen on the render thread since it may take a few moments to run
int newChunkHash = chunkWrapper.getBlockBiomeHashCode();
boolean hasNewChunkHash = (oldChunkHash != newChunkHash);
if (!hasNewChunkHash)
{
// do not update the chunk if the hash is the same
return;
}
}
CHUNK_UPDATE_QUEUE_MANAGER.addItemToUpdateQueue(chunkWrapper.getChunkPos(), preUpdateData);
}
catch (Exception e)
{
LOGGER.error("Unexpected error when pre-updating chunk at pos: [" + chunkWrapper.getChunkPos() + "]", e);
}
}
private static void processQueuedChunkUpdate()
{
ChunkUpdateData updateData = CHUNK_UPDATE_QUEUE_MANAGER.updateQueue.popClosest();
if (updateData == null)
{
return;
}
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
ArrayList<IChunkWrapper> nearbyChunkList = tryGetNeighborChunkListForChunk(chunkWrapper);
try
{
// sky lighting is populated later at the data source level
DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, levelWrapper.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
dhLevel.updateBeaconBeamsForChunk(chunkWrapper, nearbyChunkList);
int newChunkHash = chunkWrapper.getBlockBiomeHashCode();
dhLevel.updateChunkAsync(chunkWrapper, newChunkHash);
}
catch (Exception e)
{
LOGGER.error("Unexpected error when updating chunk at pos: [" + chunkWrapper.getChunkPos() + "]", e);
}
CHUNK_UPDATE_QUEUE_MANAGER.queuedChunkWrapperByChunkPos.remove(updateData.chunkWrapper.getChunkPos());
}
private static ArrayList<IChunkWrapper> tryGetNeighborChunkListForChunk(IChunkWrapper chunkWrapper)
{
// get the neighboring chunk list
ArrayList<IChunkWrapper> neighborChunkList = new ArrayList<>(9);
for (int xOffset = -1; xOffset <= 1; xOffset++)
{
for (int zOffset = -1; zOffset <= 1; zOffset++)
{
if (xOffset == 0 && zOffset == 0)
{
// center chunk
neighborChunkList.add(chunkWrapper);
}
else
{
// neighboring chunk
DhChunkPos neighborPos = new DhChunkPos(chunkWrapper.getChunkPos().getX() + xOffset, chunkWrapper.getChunkPos().getZ() + zOffset);
IChunkWrapper neighborChunk = CHUNK_UPDATE_QUEUE_MANAGER.tryGetChunk(neighborPos);
if (neighborChunk != null)
{
neighborChunkList.add(neighborChunk);
}
}
}
}
return neighborChunkList;
}
//=========// //=========//
// F3 Menu // // F3 Menu //
//=========// //=========//
//region
public ArrayList<String> getDebugMenuString() { return WORLD_CHUNK_UPDATE_MANAGER.getDebugMenuString(); } public String getDebugMenuString()
{
//endregion String preUpdatingCountStr = F3Screen.NUMBER_FORMAT.format(CHUNK_UPDATE_QUEUE_MANAGER.preUpdateQueue.getQueuedCount());
String updatingCountStr = F3Screen.NUMBER_FORMAT.format(CHUNK_UPDATE_QUEUE_MANAGER.updateQueue.getQueuedCount());
String queuedCountStr = F3Screen.NUMBER_FORMAT.format(CHUNK_UPDATE_QUEUE_MANAGER.getQueuedCount());
String maxUpdateCountStr = F3Screen.NUMBER_FORMAT.format(CHUNK_UPDATE_QUEUE_MANAGER.maxSize);
return "Queued chunk updates: "+"( "+preUpdatingCountStr+" + "+updatingCountStr+" ) [ "+queuedCountStr+" / "+maxUpdateCountStr+" ]";
}
@@ -4,58 +4,28 @@ import com.google.common.cache.CacheBuilder;
import com.seibel.distanthorizons.core.api.internal.ClientApi; import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.api.internal.SharedApi; import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat; import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.world.EWorldEnvironment; import com.seibel.distanthorizons.core.world.EWorldEnvironment;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Set; import java.util.Set;
import java.util.concurrent.*; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
/**
* @see WorldChunkUpdateManager
*/
public class ChunkUpdateQueueManager public class ChunkUpdateQueueManager
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftSharedWrapper MC_SHARED = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
/**
* how many chunks can be queued for updating per thread + player (in multiplayer),
* used to prevent updates from infinitely pilling up if the user flies around extremely fast
*/
public static final int MAX_UPDATING_CHUNK_COUNT_PER_THREAD_AND_PLAYER = 1_000;
/** how many milliseconds must pass before an overloaded message can be sent in chat or the log */
public static final int MIN_MS_BETWEEN_OVERLOADED_LOG_MESSAGE = 30_000;
private final Set<DhChunkPos> ignoredChunkPosSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
private static long lastOverloadedLogMessageMsTime = 0;
public final ChunkPosQueue updateQueue; public final ChunkPosQueue updateQueue;
public final ChunkPosQueue preUpdateQueue; public final ChunkPosQueue preUpdateQueue;
public final Set<DhChunkPos> ignoredChunkPosSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
public final ConcurrentMap<DhChunkPos, IChunkWrapper> queuedChunkWrapperByChunkPos = CacheBuilder.newBuilder() public final ConcurrentMap<DhChunkPos, IChunkWrapper> queuedChunkWrapperByChunkPos = CacheBuilder.newBuilder()
.expireAfterWrite(20, TimeUnit.SECONDS) .expireAfterWrite(20, TimeUnit.SECONDS)
@@ -65,15 +35,13 @@ public class ChunkUpdateQueueManager
/** dynamically changes based on the number of threads currently available */ /** dynamically changes based on the number of threads currently available */
public int maxSize = 500; public int maxSize = 500;
/** used to prevent flickering */ private static long lastOverloadedLogMessageMsTime = 0;
public long lastMsTimeShownActiveInF3Screen = System.currentTimeMillis();
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
//region
public ChunkUpdateQueueManager() public ChunkUpdateQueueManager()
{ {
@@ -81,14 +49,11 @@ public class ChunkUpdateQueueManager
this.preUpdateQueue = new ChunkPosQueue(); this.preUpdateQueue = new ChunkPosQueue();
} }
//endregion
//==================// //==================//
// list/set methods // // list/set methods //
//==================// //==================//
//region
public boolean contains(DhChunkPos pos) public boolean contains(DhChunkPos pos)
{ {
@@ -104,8 +69,7 @@ public class ChunkUpdateQueueManager
this.ignoredChunkPosSet.clear(); this.ignoredChunkPosSet.clear();
} }
public int getQueuedCount() { return this.updateQueue.getQueuedCount() + this.preUpdateQueue.getQueuedCount(); } public int getQueuedCount() { return this.updateQueue.getQueuedCount() + this.preUpdateQueue.getQueuedCount(); }
public boolean isEmpty()
public boolean updateQueuesEmpty()
{ {
return this.updateQueue.isEmpty() return this.updateQueue.isEmpty()
&& this.preUpdateQueue.isEmpty(); && this.preUpdateQueue.isEmpty();
@@ -150,14 +114,14 @@ public class ChunkUpdateQueueManager
{ {
// limit how often an overloaded message can be sent // limit how often an overloaded message can be sent
long msBetweenLastLog = System.currentTimeMillis() - lastOverloadedLogMessageMsTime; long msBetweenLastLog = System.currentTimeMillis() - lastOverloadedLogMessageMsTime;
if (msBetweenLastLog >= MIN_MS_BETWEEN_OVERLOADED_LOG_MESSAGE) if (msBetweenLastLog >= SharedApi.MIN_MS_BETWEEN_OVERLOADED_LOG_MESSAGE)
{ {
lastOverloadedLogMessageMsTime = System.currentTimeMillis(); lastOverloadedLogMessageMsTime = System.currentTimeMillis();
String message = MinecraftTextFormat.ORANGE + "Distant Horizons overloaded, too many chunks queued for LOD processing. " + MinecraftTextFormat.CLEAR_FORMATTING + String message = MinecraftTextFormat.ORANGE + "Distant Horizons overloaded, too many chunks queued for LOD processing. " + MinecraftTextFormat.CLEAR_FORMATTING +
"\nThis may result in holes in your LODs. " + "\nThis may result in holes in your LODs. " +
"\nFix: move through the world slower, decrease your vanilla render distance, slow down your world pre-generator (IE Chunky), or increase the Distant Horizons' CPU thread counts. " + "\nFix: move through the world slower, decrease your vanilla render distance, slow down your world pre-generator (IE Chunky), or increase the Distant Horizons' CPU thread counts. " +
"\nMax queue count [" + this.maxSize + "] ([" + MAX_UPDATING_CHUNK_COUNT_PER_THREAD_AND_PLAYER + "] per thread+players)."; "\nMax queue count [" + SharedApi.CHUNK_UPDATE_QUEUE_MANAGER.maxSize + "] ([" + SharedApi.MAX_UPDATING_CHUNK_COUNT_PER_THREAD_AND_PLAYER + "] per thread+players).";
boolean showWarningInChat = Config.Common.Logging.Warning.showUpdateQueueOverloadedChatWarning.get(); boolean showWarningInChat = Config.Common.Logging.Warning.showUpdateQueueOverloadedChatWarning.get();
if (showWarningInChat) if (showWarningInChat)
@@ -195,180 +159,20 @@ public class ChunkUpdateQueueManager
return existingWrapper.copy(); return existingWrapper.copy();
} }
//endregion
//=========// //=========//
// ignores // // ignores //
//=========// //=========//
//region
public void addPosToIgnore(DhChunkPos chunkPos) { this.ignoredChunkPosSet.add(chunkPos); } public void addPosToIgnore(DhChunkPos chunkPos) { this.ignoredChunkPosSet.add(chunkPos); }
public void removePosToIgnore(DhChunkPos chunkPos) { this.ignoredChunkPosSet.remove(chunkPos); } public void removePosToIgnore(DhChunkPos chunkPos) { this.ignoredChunkPosSet.remove(chunkPos); }
//endregion
//===================//
// update processing //
//===================//
//region
public void processQueue()
{
// update the center & max size of the queue manager
int maxUpdateSizeMultiplier;
if (MC_CLIENT != null && MC_CLIENT.playerExists())
{
// Local worlds & multiplayer
this.setCenter(MC_CLIENT.getPlayerChunkPos());
maxUpdateSizeMultiplier = MC_CLIENT.clientConnectedToDedicatedServer() ? 1 : MC_SHARED.getPlayerCount();
}
else
{
// Dedicated servers
// Also includes spawn chunks since they're likely to be intentionally utilized with updates
maxUpdateSizeMultiplier = 1 + MC_SHARED.getPlayerCount();
}
this.maxSize = MAX_UPDATING_CHUNK_COUNT_PER_THREAD_AND_PLAYER
* Config.Common.MultiThreading.numberOfThreads.get()
* maxUpdateSizeMultiplier;
//===============================//
// update the necessary chunk(s) //
//===============================//
this.processQueuedChunkPreUpdate();
this.processQueuedChunkUpdate();
// queue the next position if there are still positions to process
AbstractExecutorService executor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
if (executor != null && !this.updateQueuesEmpty())
{
try
{
executor.execute(this::processQueue);
}
catch (RejectedExecutionException ignore)
{
// the executor was shut down, it should be back up shortly and able to accept new jobs
}
}
}
private void processQueuedChunkPreUpdate()
{
ChunkUpdateData preUpdateData = this.preUpdateQueue.popClosest();
if (preUpdateData == null)
{
return;
}
IDhLevel dhLevel = preUpdateData.dhLevel;
IChunkWrapper chunkWrapper = preUpdateData.chunkWrapper;
chunkWrapper.createDhHeightMaps();
try
{
// check if this chunk has been converted into an LOD already
boolean checkChunkHash = !Config.Common.LodBuilding.disableUnchangedChunkCheck.get();
if (checkChunkHash)
{
int oldChunkHash = dhLevel.getChunkHash(chunkWrapper.getChunkPos()); // shouldn't happen on the render thread since it may take a few moments to run
int newChunkHash = chunkWrapper.getBlockBiomeHashCode();
boolean hasNewChunkHash = (oldChunkHash != newChunkHash);
if (!hasNewChunkHash)
{
// do not update the chunk if the hash is the same
return;
}
}
this.addItemToUpdateQueue(chunkWrapper.getChunkPos(), preUpdateData);
}
catch (Exception e)
{
LOGGER.error("Unexpected error when pre-updating chunk at pos: [" + chunkWrapper.getChunkPos() + "]", e);
}
}
private void processQueuedChunkUpdate()
{
ChunkUpdateData updateData = this.updateQueue.popClosest();
if (updateData == null)
{
return;
}
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
ArrayList<IChunkWrapper> nearbyChunkList = this.tryGetNeighborChunkListForChunk(chunkWrapper);
try
{
// sky lighting is populated later at the data source level
DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, levelWrapper.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
dhLevel.updateBeaconBeamsForChunk(chunkWrapper, nearbyChunkList);
int newChunkHash = chunkWrapper.getBlockBiomeHashCode();
dhLevel.updateChunkAsync(chunkWrapper, newChunkHash);
}
catch (Exception e)
{
LOGGER.error("Unexpected error when updating chunk at pos: [" + chunkWrapper.getChunkPos() + "]", e);
}
this.queuedChunkWrapperByChunkPos.remove(updateData.chunkWrapper.getChunkPos());
}
private ArrayList<IChunkWrapper> tryGetNeighborChunkListForChunk(IChunkWrapper chunkWrapper)
{
// get the neighboring chunk list
ArrayList<IChunkWrapper> neighborChunkList = new ArrayList<>(9);
for (int xOffset = -1; xOffset <= 1; xOffset++)
{
for (int zOffset = -1; zOffset <= 1; zOffset++)
{
if (xOffset == 0 && zOffset == 0)
{
// center chunk
neighborChunkList.add(chunkWrapper);
}
else
{
// neighboring chunk
DhChunkPos neighborPos = new DhChunkPos(chunkWrapper.getChunkPos().getX() + xOffset, chunkWrapper.getChunkPos().getZ() + zOffset);
IChunkWrapper neighborChunk = this.tryGetChunk(neighborPos);
if (neighborChunk != null)
{
neighborChunkList.add(neighborChunk);
}
}
}
}
return neighborChunkList;
}
//endregion
//==================// //==================//
// position methods // // position methods //
//==================// //==================//
//region
public void setCenter(DhChunkPos newCenter) public void setCenter(DhChunkPos newCenter)
{ {
@@ -376,33 +180,5 @@ public class ChunkUpdateQueueManager
this.preUpdateQueue.setCenter(newCenter); this.preUpdateQueue.setCenter(newCenter);
} }
//endregion
//=========//
// F3 Menu //
//=========//
//region
public String getDebugMenuString()
{
String y = MinecraftTextFormat.YELLOW;
String o = MinecraftTextFormat.ORANGE;
String cf = MinecraftTextFormat.CLEAR_FORMATTING;
String preUpdatingCountStr = F3Screen.NUMBER_FORMAT.format(this.preUpdateQueue.getQueuedCount());
String updatingCountStr = F3Screen.NUMBER_FORMAT.format(this.updateQueue.getQueuedCount());
String queuedCountStr = F3Screen.NUMBER_FORMAT.format(this.getQueuedCount());
String maxUpdateCountStr = F3Screen.NUMBER_FORMAT.format(this.maxSize);
return "Queued chunk updates: "+"("+y+preUpdatingCountStr+cf+" + "+o+updatingCountStr+cf+") ["+queuedCountStr+"/"+maxUpdateCountStr+"]";
}
//endregion
} }
@@ -1,190 +0,0 @@
package com.seibel.distanthorizons.core.api.internal.chunkUpdating;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.world.EWorldEnvironment;
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.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Holds all the {@link ChunkUpdateQueueManager} for a loaded world.
* Different queues are needed for each level to prevent
* chunks from bleeding between levels (IE a nether chunk applied to the overworld).
*
* @see ChunkUpdateQueueManager
*/
public class WorldChunkUpdateManager
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
/** singleton since we only expect to have one world loaded at a time */
public static final WorldChunkUpdateManager INSTANCE = new WorldChunkUpdateManager();
public static final Set<String> LOGGED_GET_ERROR_MESSAGES = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
/**
* Queues are only removed during world shutdown.
* The assumption is that there will be a limited number of {@link ILevelWrapper}'s
* for a given world.
*/
private final ConcurrentHashMap<ILevelWrapper, ChunkUpdateQueueManager> updateQueueByLevelWrapper = new ConcurrentHashMap<>();
//=============//
// constructor //
//=============//
//region
private WorldChunkUpdateManager() { }
//endregion
//=================//
// manager methods //
//=================//
//region
/**
* @return null if the world is unloaded or the given level wrapper is the wrong type
*/
@Nullable
public ChunkUpdateQueueManager getByLevelWrapper(ILevelWrapper levelWrapper)
{
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world == null)
{
// world isn't loaded, no warnings need to be logged
return null;
}
// we only want to load chunks for certain level wrappers
// this is done specifically on a local-server to prevent
// loading both the server and client level wrappers
if (world.environment == EWorldEnvironment.CLIENT_ONLY
// when connected to a server we should only ever load client wrappers anyway
// but this check confirms it
&& !(levelWrapper instanceof IClientLevelWrapper))
{
// how did we get a server level wrapper on the client?
// this shouldn't happen, but just in case
return null;
}
else if (
(world.environment == EWorldEnvironment.SERVER_ONLY
|| world.environment == EWorldEnvironment.CLIENT_SERVER)
// when hosting a server we only care about the server wrappers
&& !(levelWrapper instanceof IServerLevelWrapper))
{
// ignore client updates on the server
return null;
}
ChunkUpdateQueueManager queueManager = this.updateQueueByLevelWrapper.get(levelWrapper);
if (queueManager != null)
{
return queueManager;
}
return this.updateQueueByLevelWrapper.compute(levelWrapper,
(ILevelWrapper newLevelWrapper, ChunkUpdateQueueManager oldQueueManager) ->
{
if (oldQueueManager != null)
{
return oldQueueManager;
}
oldQueueManager = new ChunkUpdateQueueManager();
return oldQueueManager;
});
}
public void processEachQueue()
{
this.updateQueueByLevelWrapper.forEach(
(ILevelWrapper levelWrapper, ChunkUpdateQueueManager updateManager) ->
{
updateManager.processQueue();
});
}
public int getTotalQueuedCount()
{
AtomicInteger queueCountRef = new AtomicInteger(0);
this.updateQueueByLevelWrapper.forEach(
(ILevelWrapper levelWrapper, ChunkUpdateQueueManager updateManager) ->
{
queueCountRef.addAndGet(updateManager.getQueuedCount());
});
return queueCountRef.get();
}
public void clear() { this.updateQueueByLevelWrapper.clear(); }
//endregion
//=========//
// F3 Menu //
//=========//
//region
public ArrayList<String> getDebugMenuString()
{
ArrayList<String> stringList = new ArrayList<>();
stringList.add("");// placeholder for the total count
// add each queue to the list
AtomicInteger totalQueueCountRef = new AtomicInteger(0);
AtomicInteger activeQueueCountRef = new AtomicInteger(0);
this.updateQueueByLevelWrapper.forEach(
(ILevelWrapper levelWrapper, ChunkUpdateQueueManager updateManager) ->
{
// is this queue active?
if (!updateManager.updateQueuesEmpty())
{
updateManager.lastMsTimeShownActiveInF3Screen = System.currentTimeMillis();
activeQueueCountRef.incrementAndGet();
}
// show this queue if it hasn't been empty long enough
// (done to prevent flickering on the F3 screen when the queue rapidly fills/empties)
long timeSinceQueueLastShownActiveMs = System.currentTimeMillis() - updateManager.lastMsTimeShownActiveInF3Screen;
if (timeSinceQueueLastShownActiveMs < 4_000)
{
stringList.add(levelWrapper.getDimensionName() + ": " + updateManager.getDebugMenuString());
}
totalQueueCountRef.incrementAndGet();
});
// replace the first line with the number of total/active queues
// (helpful if we need to diagnose a leak due to a massive number of queue level wrappers)
stringList.set(0, "Chunk Update Queues: "+activeQueueCountRef.get()+"/"+totalQueueCountRef.get());
return stringList;
}
//endregion
}
@@ -2,7 +2,6 @@ package com.seibel.distanthorizons.core.api.internal.rendering;
import com.seibel.distanthorizons.core.api.internal.ClientApi; import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.util.math.Mat4f; import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
/** /**
@@ -14,32 +13,9 @@ public class DhRenderState
{ {
public Mat4f mcModelViewMatrix = null; public Mat4f mcModelViewMatrix = null;
public Mat4f mcProjectionMatrix = null; public Mat4f mcProjectionMatrix = null;
/** public float frameTime = -1;
* percentage of time into the current client tick. <br><br>
*
* Can be converted to a millisecond frametime
* (IE time between frames in milliseconds) using the formula: <br>
* <code>
* (partialTickTime/20*1000)
* </code> <br>
* IE 60 FPS = 16.6 MS <br>
*
* @link https://fpstoms.com/
* @see IMinecraftRenderWrapper#getPartialTickTime()
*/
public float partialTickTime = -1;
public IClientLevelWrapper clientLevelWrapper = null; public IClientLevelWrapper clientLevelWrapper = null;
/**
* This will generally be true if the player is: <br>
* - blinded <br>
* - under lava/water <br>
* <br>
* In those cases some rendering logic may need to be changed
* to look correct.
*/
public boolean vanillaFogEnabled = false;
//========// //========//
@@ -62,7 +38,7 @@ public class DhRenderState
errorReasons += "no Projection Matrix, "; errorReasons += "no Projection Matrix, ";
} }
if (this.partialTickTime == -1) if (this.frameTime == -1)
{ {
errorReasons += "no Frame Time, "; errorReasons += "no Frame Time, ";
} }
@@ -75,6 +51,13 @@ public class DhRenderState
return errorReasons; return errorReasons;
} }
public boolean canRender()
{
// separated variable to allow for easy checking with the debugger
String errorReasons = this.unableToRenderBecause();
return errorReasons.isEmpty();
}
public void canRenderOrThrow() throws IllegalStateException public void canRenderOrThrow() throws IllegalStateException
{ {
String errorReasons = this.unableToRenderBecause(); String errorReasons = this.unableToRenderBecause();
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.config; package com.seibel.distanthorizons.core.config;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.enums.config.*; import com.seibel.distanthorizons.api.enums.config.*;
import com.seibel.distanthorizons.api.enums.config.quickOptions.*; import com.seibel.distanthorizons.api.enums.config.quickOptions.*;
import com.seibel.distanthorizons.api.enums.rendering.*; import com.seibel.distanthorizons.api.enums.rendering.*;
@@ -26,12 +27,14 @@ import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGenerat
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorProgressDisplayLocation; import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorProgressDisplayLocation;
import com.seibel.distanthorizons.core.config.eventHandlers.*; import com.seibel.distanthorizons.core.config.eventHandlers.*;
import com.seibel.distanthorizons.core.config.eventHandlers.presets.*; import com.seibel.distanthorizons.core.config.eventHandlers.presets.*;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.config.types.*; import com.seibel.distanthorizons.core.config.types.*;
import com.seibel.distanthorizons.core.config.types.enums.*; import com.seibel.distanthorizons.core.config.types.enums.*;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.NativeDialogUtil; import com.seibel.distanthorizons.core.util.NativeDialogUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
@@ -98,7 +101,6 @@ public class Config
.build(); .build();
public static ConfigUiLinkedEntry quickEnableWorldGenerator = new ConfigUiLinkedEntry(Common.WorldGenerator.enableDistantGeneration); public static ConfigUiLinkedEntry quickEnableWorldGenerator = new ConfigUiLinkedEntry(Common.WorldGenerator.enableDistantGeneration);
public static ConfigUiLinkedEntry quickEnableServerGeneration = new ConfigUiLinkedEntry(Server.enableServerGeneration);
public static ConfigUiLinkedEntry quickShowWorldGenProgress = new ConfigUiLinkedEntry(Common.WorldGenerator.showGenerationProgress); public static ConfigUiLinkedEntry quickShowWorldGenProgress = new ConfigUiLinkedEntry(Common.WorldGenerator.showGenerationProgress);
@@ -116,6 +118,20 @@ public class Config
public static ConfigEntry<Boolean> dynamicFadeUseOpaqueMcDepth = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "")
.build();
public static ConfigEntry<String> dynamicFadeExportPath = new ConfigEntry.Builder<String>()
.set("C:/Users/James_Seibel/Desktop/")
.comment(""
+ "")
.build();
public static class Advanced public static class Advanced
{ {
// common config links need to have their destination // common config links need to have their destination
@@ -143,11 +159,8 @@ public class Config
public static ConfigCategory quality = new ConfigCategory.Builder().set(Quality.class).build(); public static ConfigCategory quality = new ConfigCategory.Builder().set(Quality.class).build();
public static ConfigUISpacer qualitySpacer = new ConfigUISpacer.Builder().build(); public static ConfigUISpacer qualitySpacer = new ConfigUISpacer.Builder().build();
public static ConfigUiLinkedEntry quickEnableSsao = new ConfigUiLinkedEntry(Ssao.enableSsao);
public static ConfigEntry<Boolean> enableSsao = new ConfigEntry.Builder<Boolean>() public static ConfigCategory ssao = new ConfigCategory.Builder().set(Ssao.class).build();
.set(true)
.comment("Enable Screen Space Ambient Occlusion")
.build();
public static ConfigUISpacer ssaoSpacer = new ConfigUISpacer.Builder().build(); public static ConfigUISpacer ssaoSpacer = new ConfigUISpacer.Builder().build();
@@ -270,6 +283,13 @@ public class Config
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE) .addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
public static ConfigEntry<Double> lodBias = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(0d, 0d, null)
.comment(""
+ "What value should vanilla Minecraft's texture LodBias be? \n"
+ "If set to 0 the mod wont overwrite vanilla's default (which so happens to also be 0)")
.build();
public static ConfigEntry<EDhApiLodShading> lodShading = new ConfigEntry.Builder<EDhApiLodShading>() public static ConfigEntry<EDhApiLodShading> lodShading = new ConfigEntry.Builder<EDhApiLodShading>()
.set(EDhApiLodShading.AUTO) .set(EDhApiLodShading.AUTO)
.comment("" .comment(""
@@ -323,8 +343,8 @@ public class Config
+ "") + "")
.build(); .build();
public static ConfigEntry<Float> brightnessMultiplier = new ConfigEntry.Builder<Float>() public static ConfigEntry<Double> brightnessMultiplier = new ConfigEntry.Builder<Double>() // TODO: Make this a float (the ClassicConfigGUI doesnt support floats)
.set(1.0f) .set(1.0)
.comment("" .comment(""
+ "How bright LOD colors are. \n" + "How bright LOD colors are. \n"
+ "\n" + "\n"
@@ -334,8 +354,8 @@ public class Config
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE) .addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
public static ConfigEntry<Float> saturationMultiplier = new ConfigEntry.Builder<Float>() public static ConfigEntry<Double> saturationMultiplier = new ConfigEntry.Builder<Double>() // TODO: Make this a float (the ClassicConfigGUI doesnt support floats)
.set(1.0f) .set(1.0)
.comment("" .comment(""
+ "How saturated LOD colors are. \n" + "How saturated LOD colors are. \n"
+ "\n" + "\n"
@@ -358,6 +378,71 @@ public class Config
.build(); .build();
} }
public static class Ssao
{
public static ConfigUIComment ssaoHeader = new ConfigUIComment.Builder().setParentConfigClass(Ssao.class).build();
public static ConfigEntry<Boolean> enableSsao = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment("Enable Screen Space Ambient Occlusion")
.build();
public static ConfigEntry<Integer> sampleCount = new ConfigEntry.Builder<Integer>()
.set(6)
.comment("" +
"Determines how many points in space are sampled for the occlusion test. \n" +
"Higher numbers will improve quality and reduce banding, but will increase GPU load." +
"")
.build();
public static ConfigEntry<Double> radius = new ConfigEntry.Builder<Double>()
.set(4.0)
.comment("" +
"Determines the radius Screen Space Ambient Occlusion is applied, measured in blocks." +
"")
.build();
public static ConfigEntry<Double> strength = new ConfigEntry.Builder<Double>()
.set(0.2)
.comment("" +
"Determines how dark the Screen Space Ambient Occlusion effect will be." +
"")
.build();
public static ConfigEntry<Double> bias = new ConfigEntry.Builder<Double>()
.set(0.02)
.comment("" +
"Increasing the value can reduce banding at the cost of reducing the strength of the effect." +
"")
.build();
public static ConfigEntry<Double> minLight = new ConfigEntry.Builder<Double>()
.set(0.25)
.comment("" +
"Determines how dark the occlusion shadows can be. \n" +
"0 = totally black at the corners \n" +
"1 = no shadow" +
"")
.build();
public static ConfigEntry<Integer> blurRadius = new ConfigEntry.Builder<Integer>()
.set(2)
.comment("" +
"The radius, measured in pixels, that blurring is calculated for the SSAO. \n" +
"Higher numbers will reduce banding at the cost of GPU performance." +
"")
.build();
public static ConfigEntry<Integer> fadeDistanceInBlocks = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(0, 1_600, 30_000_000)
.comment("" +
"The distance in blocks from the camera where the SSAO will fade out to. \n"+
"This is done to prevent banding and noise at extreme distances. \n"+
"")
.build();
}
public static class GenericRendering public static class GenericRendering
{ {
public static ConfigUIComment genericRendererHeader = new ConfigUIComment.Builder().setParentConfigClass(GenericRendering.class).build(); public static ConfigUIComment genericRendererHeader = new ConfigUIComment.Builder().setParentConfigClass(GenericRendering.class).build();
@@ -402,23 +487,19 @@ public class Config
+ "") + "")
.build(); .build();
public static ConfigEntry<String> dimensionEnabledCloudRenderingCsv = new ConfigEntry.Builder<String>() public static ConfigEntry<Boolean> enableInstancedRendering = new ConfigEntry.Builder<Boolean>()
.set("minecraft:overworld") .set(true)
.setAppearance(EConfigEntryAppearance.ALL) .comment(""
.comment("" + "Can be disabled to use much slower but more compatible direct rendering. \n"
+ "A comma separated separated list of dimension resource locations where DH clouds will render.\n" + "Disabling this can be used to fix some crashes on Mac. \n"
+ "\n" + "")
+ "Example: \"minecraft:overworld,minecraft:the_end\"\n" .build();
+ "\n"
+ "Changes will only be seen when the world is re-loaded.\n"
+ "")
.build();
} }
public static class Fog public static class Fog
{ {
private static final Float FOG_RANGE_MIN = 0.0f; private static final Double FOG_RANGE_MIN = 0.0;
private static final Float FOG_RANGE_MAX = (float)Math.sqrt(2.0); private static final Double FOG_RANGE_MAX = Math.sqrt(2.0);
@@ -450,8 +531,8 @@ public class Config
public static ConfigEntry<Float> farFogStart = new ConfigEntry.Builder<Float>() public static ConfigEntry<Double> farFogStart = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(FOG_RANGE_MIN, 0.4f, FOG_RANGE_MAX) .setMinDefaultMax(FOG_RANGE_MIN, 0.4, FOG_RANGE_MAX)
.comment("" .comment(""
+ "At what distance should the far fog start? \n" + "At what distance should the far fog start? \n"
+ "\n" + "\n"
@@ -460,8 +541,8 @@ public class Config
+ "1.414: Fog starts at the corner of the vanilla render distance.") + "1.414: Fog starts at the corner of the vanilla render distance.")
.build(); .build();
public static ConfigEntry<Float> farFogEnd = new ConfigEntry.Builder<Float>() public static ConfigEntry<Double> farFogEnd = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(FOG_RANGE_MIN, 1.0f, FOG_RANGE_MAX) .setMinDefaultMax(FOG_RANGE_MIN, 1.0, FOG_RANGE_MAX)
.comment("" .comment(""
+ "Where should the far fog end? \n" + "Where should the far fog end? \n"
+ "\n" + "\n"
@@ -470,8 +551,8 @@ public class Config
+ "1.414: Fog ends at the corner of the vanilla render distance.") + "1.414: Fog ends at the corner of the vanilla render distance.")
.build(); .build();
public static ConfigEntry<Float> farFogMin = new ConfigEntry.Builder<Float>() public static ConfigEntry<Double> farFogMin = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(-5.0f, 0.0f, FOG_RANGE_MAX) .setMinDefaultMax(-5.0, 0.0, FOG_RANGE_MAX)
.comment("" .comment(""
+ "What is the minimum fog thickness? \n" + "What is the minimum fog thickness? \n"
+ "\n" + "\n"
@@ -479,8 +560,8 @@ public class Config
+ "1.0: Fully opaque fog.") + "1.0: Fully opaque fog.")
.build(); .build();
public static ConfigEntry<Float> farFogMax = new ConfigEntry.Builder<Float>() public static ConfigEntry<Double> farFogMax = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(FOG_RANGE_MIN, 1.0f, 5.0f) .setMinDefaultMax(FOG_RANGE_MIN, 1.0, 5.0)
.comment("" .comment(""
+ "What is the maximum fog thickness? \n" + "What is the maximum fog thickness? \n"
+ "\n" + "\n"
@@ -498,8 +579,8 @@ public class Config
+ EDhApiFogFalloff.EXPONENTIAL_SQUARED + ": 1/(e^((distance*density)^2)") + EDhApiFogFalloff.EXPONENTIAL_SQUARED + ": 1/(e^((distance*density)^2)")
.build(); .build();
public static ConfigEntry<Float> farFogDensity = new ConfigEntry.Builder<Float>() public static ConfigEntry<Double> farFogDensity = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(0.01f, 2.5f, 50.0f) .setMinDefaultMax(0.01, 2.5, 50.0)
.comment("" .comment(""
+ "Used in conjunction with the Fog Falloff.") + "Used in conjunction with the Fog Falloff.")
.build(); .build();
@@ -545,13 +626,13 @@ public class Config
+ EDhApiHeightFogDirection.ABOVE_AND_BELOW_SET_HEIGHT + ": Height fog starts from a set height and goes towards both the sky and void") + EDhApiHeightFogDirection.ABOVE_AND_BELOW_SET_HEIGHT + ": Height fog starts from a set height and goes towards both the sky and void")
.build(); .build();
public static ConfigEntry<Float> heightFogBaseHeight = new ConfigEntry.Builder<Float>() public static ConfigEntry<Double> heightFogBaseHeight = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(-3_000_000.0f, 80.0f, 3_000_000.0f) .setMinDefaultMax(-4096.0, 80.0, 4096.0)
.comment("If the height fog is calculated around a set height, what is that height position?") .comment("If the height fog is calculated around a set height, what is that height position?")
.build(); .build();
public static ConfigEntry<Float> heightFogStart = new ConfigEntry.Builder<Float>() public static ConfigEntry<Double> heightFogStart = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(FOG_RANGE_MIN, 0.0f, FOG_RANGE_MAX) .setMinDefaultMax(FOG_RANGE_MIN, 0.0, FOG_RANGE_MAX)
.comment("" .comment(""
+ "Should the start of the height fog be offset? \n" + "Should the start of the height fog be offset? \n"
+ "\n" + "\n"
@@ -559,8 +640,8 @@ public class Config
+ "1.0: Fog start with offset of the entire world's height. (Includes depth)") + "1.0: Fog start with offset of the entire world's height. (Includes depth)")
.build(); .build();
public static ConfigEntry<Float> heightFogEnd = new ConfigEntry.Builder<Float>() public static ConfigEntry<Double> heightFogEnd = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(FOG_RANGE_MIN, 0.6f, FOG_RANGE_MAX) .setMinDefaultMax(FOG_RANGE_MIN, 0.6, FOG_RANGE_MAX)
.comment("" .comment(""
+ "Should the end of the height fog be offset? \n" + "Should the end of the height fog be offset? \n"
+ "\n" + "\n"
@@ -568,8 +649,8 @@ public class Config
+ "1.0: Fog end with offset of the entire world's height. (Include depth)") + "1.0: Fog end with offset of the entire world's height. (Include depth)")
.build(); .build();
public static ConfigEntry<Float> heightFogMin = new ConfigEntry.Builder<Float>() public static ConfigEntry<Double> heightFogMin = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(0.0f, 0.0f, FOG_RANGE_MAX) .setMinDefaultMax(0.0, 0.0, FOG_RANGE_MAX)
.comment("" .comment(""
+ "What is the minimum fog thickness? \n" + "What is the minimum fog thickness? \n"
+ "\n" + "\n"
@@ -577,8 +658,8 @@ public class Config
+ "1.0: Fully opaque fog.") + "1.0: Fully opaque fog.")
.build(); .build();
public static ConfigEntry<Float> heightFogMax = new ConfigEntry.Builder<Float>() public static ConfigEntry<Double> heightFogMax = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(FOG_RANGE_MIN, 1.0f, 5.0f) .setMinDefaultMax(FOG_RANGE_MIN, 1.0, 5.0)
.comment("" .comment(""
+ "What is the maximum fog thickness? \n" + "What is the maximum fog thickness? \n"
+ "\n" + "\n"
@@ -596,8 +677,8 @@ public class Config
+ EDhApiFogFalloff.EXPONENTIAL_SQUARED + ": 1/(e^((height*density)^2)") + EDhApiFogFalloff.EXPONENTIAL_SQUARED + ": 1/(e^((height*density)^2)")
.build(); .build();
public static ConfigEntry<Float> heightFogDensity = new ConfigEntry.Builder<Float>() public static ConfigEntry<Double> heightFogDensity = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(0.01f, 20.0f, 50.0f) .setMinDefaultMax(0.01, 20.0, 50.0)
.comment("What is the height fog's density?") .comment("What is the height fog's density?")
.build(); .build();
@@ -625,13 +706,13 @@ public class Config
+ "") + "")
.build(); .build();
public static ConfigEntry<Float> noiseIntensity = new ConfigEntry.Builder<Float>() public static ConfigEntry<Double> noiseIntensity = new ConfigEntry.Builder<Double>() // TODO: Make this a float (the ClassicConfigGUI doesn't support floats)
.setMinDefaultMax(0f, 0.05f, 1f) .setMinDefaultMax(0d, 5d, 100d) // TODO: Once this becomes a float make it 0-1 instead of 0-100 (I did this cus doubles only allow 2 decimal places)
.comment("" .comment(""
+ "How intense should the noise should be?") + "How intense should the noise should be?")
.build(); .build();
public static ConfigEntry<Integer> noiseDropoff = new ConfigEntry.Builder<Integer>() public static ConfigEntry<Integer> noiseDropoff = new ConfigEntry.Builder<Integer>() // TODO: Make this a float (the ClassicConfigGUI doesn't support floats)
.setMinDefaultMax(0, 1024, null) .setMinDefaultMax(0, 1024, null)
.comment("" .comment(""
+ "Defines how far should the noise texture render before it fades away. (in blocks) \n" + "Defines how far should the noise texture render before it fades away. (in blocks) \n"
@@ -645,33 +726,22 @@ public class Config
{ {
public static ConfigUIComment cullingHeader = new ConfigUIComment.Builder().setParentConfigClass(Culling.class).build(); public static ConfigUIComment cullingHeader = new ConfigUIComment.Builder().setParentConfigClass(Culling.class).build();
public static ConfigEntry<Float> overdrawPrevention = new ConfigEntry.Builder<Float>() public static ConfigEntry<Double> overdrawPrevention = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(-1.0f, -1.0f, 1.0f) .setMinDefaultMax(0.0, 0.0, 1.0) // TODO change -1 to auto
.comment("" .comment(""
+ "Determines how far from the camera Distant Horizons will start rendering. \n" + "Determines how far from the camera Distant Horizons will start rendering. \n"
+ "Measured as a percentage of the vanilla render distance.\n" + "Measured as a percentage of the vanilla render distance.\n"
+ "\n" + "\n"
+ "-1 = auto, overdraw will change based on the vanilla render distance.\n" + "0 = auto, overdraw will change based on the vanilla render distance.\n"
+ "\n" + "\n"
+ "Higher values will prevent LODs from rendering behind vanilla blocks at a higher distance,\n" + "Higher values will prevent LODs from rendering behind vanilla blocks at a higher distance,\n"
+ "but may cause holes in the world. \n" + "but may cause holes in the world. \n"
+ "Holes are most likely to appear when flying through unloaded terrain. \n" + "Holes are most likely to appear when flying through unloaded terrain. \n"
+ "\n" + "\n"
+ "Increasing the vanilla render distance increases the effectiveness of this setting." + "Increasing the vanilla render distance increases the effectiveness of this setting."
+ "") + "")
.build(); .build();
public static ConfigEntry<Boolean> reduceOverdrawWithFastMovement = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "If set to true the overdraw prevention radius will get closer\n"
+ "to the camera when flying/moving quickly.\n"
+ "\n"
+ "This helps reduce issues where Minecraft can't load or\n"
+ "generate chunks fast enough to keep up with DH.\n"
+ "")
.build();
public static ConfigEntry<Boolean> enableCaveCulling = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> enableCaveCulling = new ConfigEntry.Builder<Boolean>()
.set(true) .set(true)
.comment("" .comment(""
@@ -726,56 +796,59 @@ public class Config
+ "Disable this if shadows render incorrectly.") + "Disable this if shadows render incorrectly.")
.build(); .build();
public static ConfigUISpacer ignoreCsvStartSpacer = new ConfigUISpacer.Builder().build(); public static ConfigEntry<String> ignoredRenderBlockCsv = new ConfigEntry.Builder<String>() // TODO accept wildcards
public static ConfigEntry<String> ignoredRenderBlockCsv = new ConfigEntry.Builder<String>()
.set("minecraft:barrier,minecraft:structure_void,minecraft:light,minecraft:tripwire,minecraft:brown_mushroom") .set("minecraft:barrier,minecraft:structure_void,minecraft:light,minecraft:tripwire,minecraft:brown_mushroom")
.setAppearance(EConfigEntryAppearance.ALL) .setAppearance(EConfigEntryAppearance.ONLY_IN_FILE) // only shown in file since the UI has a character limit
.addListener(RenderBlockCacheCsvHandler.INSTANCE)
.comment("" .comment(""
+ "A comma separated list of block resource locations that won't be rendered by DH. \n" + "A comma separated list of block resource locations that won't be rendered by DH. \n"
+ "Air is always included in this list. \n" + "Air is always included in this list. \n"
+ "Requires a restart to change. \n"
+ "\n" + "\n"
+ "Note:\n" + "Note:\n"
+ "If you see gaps, or holes you may have to change\n" + "If you see gaps, or holes you may have to change\n"
+ "worldCompression to ["+EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS+"] and re-generate the LODs.\n" + "worldCompression to ["+EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS+"] and re-generate the LODs.\n"
+ "Black spots may happen occur to block lighting being zero for covered blocks.\n"
+ "") + "")
.build(); .build();
public static ConfigEntry<String> ignoredRenderCaveBlockCsv = new ConfigEntry.Builder<String>() public static ConfigEntry<String> ignoredRenderCaveBlockCsv = new ConfigEntry.Builder<String>() // TODO accept wildcards
.set("") .set("minecraft:glow_lichen,minecraft:rail,minecraft:water,minecraft:lava,minecraft:bubble_column," +
.setAppearance(EConfigEntryAppearance.ALL) "minecraft:cave_vines_plant,minecraft:vine,minecraft:cave_vines,minecraft:short_grass,minecraft:tall_grass," +
.addListener(RenderBlockCacheCsvHandler.INSTANCE) "minecraft:small_dripleaf,minecraft:big_dripleaf,minecraft:big_dripleaf_stem,minecraft:sculk_vein")
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE) // only shown in file since the UI has a character limit
.comment("" .comment(""
+ "A comma separated list of block resource locations that shouldn't be rendered \n" + "A comma separated list of block resource locations that shouldn't be rendered \n"
+ "if they are in a 0 sky light underground area. \n" + "if they are in a 0 sky light underground area. \n"
+ "Air is always included in this list. \n" + "Air is always included in this list. \n"
+ "\n" + "Requires a restart to change. \n"
+ "Defaults to an empty list since most cave blocks will be automatically ignored due to being: \n"
+ "transparent, non-solid, or liquids, but new blocks can be added here if needed.\n"
+ "") + "")
.build(); .build();
public static ConfigEntry<String> waterSubSurfaceBlockReplacementCsv = new ConfigEntry.Builder<String>()
.set("minecraft:kelp,minecraft:tall_seagrass,minecraft:seagrass")
.setAppearance(EConfigEntryAppearance.ALL)
.addListener(RenderBlockCacheCsvHandler.INSTANCE)
.comment(""
+ "A comma separated list of block resource locations that will be replaced by water \n"
+ "if they're visible on the water's surface. \n"
+ "")
.build();
public static ConfigEntry<String> waterSurfaceBlockReplacementCsv = new ConfigEntry.Builder<String>()
.set("minecraft:lily_pad")
.setAppearance(EConfigEntryAppearance.ALL)
.addListener(RenderBlockCacheCsvHandler.INSTANCE)
.comment(""
+ "A comma separated list of block resource locations that will be removed \n"
+ "when on top of water. \n"
+ "")
.build();
static
{
ignoredRenderBlockCsv.addListener(new ConfigChangeListener<String>(ignoredRenderBlockCsv,
(blockCsv) ->
{
IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
if (wrapperFactory != null)
{
wrapperFactory.resetRendererIgnoredBlocksSet();
DhApi.Delayed.renderProxy.clearRenderDataCache();
}
}));
ignoredRenderCaveBlockCsv.addListener(new ConfigChangeListener<String>(ignoredRenderCaveBlockCsv,
(blockCsv) ->
{
IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
if (wrapperFactory != null)
{
wrapperFactory.resetRendererIgnoredCaveBlocks();
DhApi.Delayed.renderProxy.clearRenderDataCache();
}
}));
}
} }
public static class Experimental public static class Experimental
@@ -813,19 +886,6 @@ public class Config
.addListener(IgnoredDimensionCsvHandler.INSTANCE) .addListener(IgnoredDimensionCsvHandler.INSTANCE)
.build(); .build();
public static ConfigEntry<EDhApiRenderApi> renderingApi = new ConfigEntry.Builder<EDhApiRenderApi>()
.set(EDhApiRenderApi.AUTO)
.comment(""
+ "Requires a restart to change. \n"
+ " \n"
+ "Options: \n"
+ EDhApiRenderApi.AUTO + " - changes based on the most likely API for that MC version \n"
+ EDhApiRenderApi.OPEN_GL + " - Default \n"
+ EDhApiRenderApi.BLAZE_3D + " - Only supported on MC 1.21.11 \n"
+ "")
.build();
} }
} }
@@ -887,11 +947,11 @@ public class Config
+ "What renderer is active? \n" + "What renderer is active? \n"
+ "\n" + "\n"
+ EDhApiRendererMode.DEFAULT + ": Default lod renderer \n" + EDhApiRendererMode.DEFAULT + ": Default lod renderer \n"
+ EDhApiRendererMode.DEBUG_TRIANGLE + ": Debug testing renderer \n" + EDhApiRendererMode.DEBUG + ": Debug testing renderer \n"
+ EDhApiRendererMode.DISABLED + ": Disable rendering") + EDhApiRendererMode.DISABLED + ": Disable rendering")
.build(); .build();
public static ConfigEntry<EDhApiDebugRendering> debugRenderingColors = new ConfigEntry.Builder<EDhApiDebugRendering>() public static ConfigEntry<EDhApiDebugRendering> debugRendering = new ConfigEntry.Builder<EDhApiDebugRendering>()
.set(EDhApiDebugRendering.OFF) .set(EDhApiDebugRendering.OFF)
.comment("" .comment(""
+ "Should specialized colors/rendering modes be used? \n" + "Should specialized colors/rendering modes be used? \n"
@@ -904,13 +964,6 @@ public class Config
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE) .addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
public static ConfigEntry<Boolean> enableWhiteWorld = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
+ "Stops vertex colors from being passed. \n"
+ "Useful for debugging shaders")
.build();
public static ConfigEntry<Boolean> lodOnlyMode = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> lodOnlyMode = new ConfigEntry.Builder<Boolean>()
.set(false) .set(false)
.comment("" .comment(""
@@ -928,15 +981,20 @@ public class Config
+ "") + "")
.build(); .build();
// TODO add LOD-only mode to this
public static ConfigEntry<Boolean> enableDebugKeybindings = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> enableDebugKeybindings = new ConfigEntry.Builder<Boolean>()
.set(false) .set(false)
.comment("" .comment(""
+ "If true several keys can be used to toggle debug states. \n" + "If true the F8 key can be used to cycle through the different debug modes. \n"
+ "F6 - enable/disable LOD rendering \n" + "and the F6 key can be used to enable and disable LOD rendering.")
+ "F7 - enable/disable LOD only rendering \n" .build();
+ "F8 - cycle through the different debug rendering modes \n"
+ "") public static ConfigEntry<Boolean> enableWhiteWorld = new ConfigEntry.Builder<Boolean>()
.build(); .set(false)
.comment(""
+ "Stops vertex colors from being passed. \n"
+ "Useful for debugging shaders")
.build();
public static ConfigEntry<Boolean> showOverlappingQuadErrors = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> showOverlappingQuadErrors = new ConfigEntry.Builder<Boolean>()
.set(false) .set(false)
@@ -998,6 +1056,15 @@ public class Config
.set(false) .set(false)
.comment("Render LOD section status?") .comment("Render LOD section status?")
.build(); .build();
public static ConfigEntry<Boolean> showRenderSectionToggling = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment("" +
"A white box will be drawn when an LOD starts rendering \n" +
"and a purple box when an LOD stops rendering. \n" +
"\n" +
"This can be used to debug Quad Tree holes.\n" +
"")
.build();
public static ConfigEntry<Boolean> showQuadTreeRenderStatus = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> showQuadTreeRenderStatus = new ConfigEntry.Builder<Boolean>()
.set(false) .set(false)
@@ -1050,6 +1117,13 @@ public class Config
+ "") + "")
.build(); .build();
public static ConfigEntry<EDhApiGpuUploadMethod> glUploadMode = new ConfigEntry.Builder<EDhApiGpuUploadMethod>()
.set(EDhApiGpuUploadMethod.AUTO)
.comment(""
+ "\n"
+ "")
.build();
} }
public static class ColumnBuilderDebugging public static class ColumnBuilderDebugging
@@ -1116,11 +1190,6 @@ public class Config
.comment("Shows info about each thread pool.") .comment("Shows info about each thread pool.")
.build(); .build();
public static ConfigEntry<Boolean> showRenderThreadTasks = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment("Shows info about the render thread tasks.")
.build();
public static ConfigEntry<Boolean> showCombinedObjectPools = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> showCombinedObjectPools = new ConfigEntry.Builder<Boolean>()
.set(false) .set(false)
.comment("Shows the combined memory use and array counts for all DH pooled objects.") .comment("Shows the combined memory use and array counts for all DH pooled objects.")
@@ -1132,7 +1201,7 @@ public class Config
public static ConfigEntry<Boolean> showQueuedChunkUpdateCount = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> showQueuedChunkUpdateCount = new ConfigEntry.Builder<Boolean>()
.set(true) .set(true)
.comment("Shows how many chunks are queued for processing and the max count that can be queued.") .comment("Shows how many chunks are queud for processing and the max count that can be queued.")
.build(); .build();
public static ConfigEntry<Boolean> showLevelStatus = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> showLevelStatus = new ConfigEntry.Builder<Boolean>()
@@ -1140,11 +1209,6 @@ public class Config
.comment("Shows what levels are loaded and world gen/rendering info about those levels.") .comment("Shows what levels are loaded and world gen/rendering info about those levels.")
.build(); .build();
public static ConfigEntry<Boolean> onlyShowRenderingLevels = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment("Only show levels that DH is actively rendering.")
.build();
} }
/** This class is used to debug the different features of the config GUI */ /** This class is used to debug the different features of the config GUI */
@@ -1628,16 +1692,7 @@ public class Config
.comment("" .comment(""
+ "If enabled, a message will be logged if the garbage \n" + "If enabled, a message will be logged if the garbage \n"
+ "collector Java is currently using is known \n" + "collector Java is currently using is known \n"
+ "to cause frame stuttering and/or other issues. \n" + "to cause stutters and/or issues. \n"
+ "")
.build();
public static ConfigEntry<Boolean> showGarbageCollectorWarning = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "If enabled, a chat message will be displayed if the garbage \n"
+ "collector Java is currently using is known \n"
+ "to cause frame stuttering and/or other issues. \n"
+ "") + "")
.build(); .build();
@@ -1693,15 +1748,6 @@ public class Config
// Generation // Generation
public static ConfigEntry<Boolean> enableServerGeneration = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "When enabled, Distant Horizons will attempt to download missing LODs from the server.\n"
+ "\n"
+ "Note: the server must have Distant Generation enabled for it to work."
+ "")
.build();
public static ConfigEntry<Integer> generationRequestRateLimit = new ConfigEntry.Builder<Integer>() public static ConfigEntry<Integer> generationRequestRateLimit = new ConfigEntry.Builder<Integer>()
.setChatCommandName("generation.requestRateLimit") .setChatCommandName("generation.requestRateLimit")
.setMinDefaultMax(1, 20, 100) .setMinDefaultMax(1, 20, 100)
@@ -62,7 +62,7 @@ public class ConfigHandler
* <br> {@link String} * <br> {@link String}
* <br> * <br>
* <br> // Below, "T" should be a value from above * <br> // Below, "T" should be a value from above
* <br> // Note: This is not checked, so we trust that you are doing the right thing * <br> // Note: This is not checked, so we trust that you are doing the right thing (TODO: Check it)
* <br> List<T> * <br> List<T>
* <br> ArrayList<T> * <br> ArrayList<T>
* <br> Map<String, T> * <br> Map<String, T>
@@ -261,6 +261,7 @@ public class ConfigHandler
if (ConfigUIComment.class.isAssignableFrom(entry.getClass()) if (ConfigUIComment.class.isAssignableFrom(entry.getClass())
&& ((ConfigUIComment)entry).parentConfigPath != null) && ((ConfigUIComment)entry).parentConfigPath != null)
{ {
// TODO this could potentially add the same item multiple times
entryPrefix = "distanthorizons.config." + ((ConfigUIComment)entry).parentConfigPath; entryPrefix = "distanthorizons.config." + ((ConfigUIComment)entry).parentConfigPath;
} }
@@ -46,12 +46,6 @@ public class DhApiConfigValue<coreType, apiType> implements IDhApiConfigValue<ap
private final IConverter<coreType, apiType> configConverter; private final IConverter<coreType, apiType> configConverter;
//==============//
// constructors //
//==============//
//region
/** /**
* This constructor should only be called internally. <br> * This constructor should only be called internally. <br>
* There is no reason for API users to create this object. <br><br> * There is no reason for API users to create this object. <br><br>
@@ -75,29 +69,11 @@ public class DhApiConfigValue<coreType, apiType> implements IDhApiConfigValue<ap
this.configConverter = newConverter; this.configConverter = newConverter;
} }
//endregion
public apiType getValue() { return this.configConverter.convertToApiType(this.configBase.get()); }
public apiType getTrueValue() { return this.configConverter.convertToApiType(this.configBase.getTrueValue()); }
public apiType getApiValue() { return this.configConverter.convertToApiType(this.configBase.getApiValue()); }
//===========//
// overrides //
//===========//
//region
@Override public apiType getValue() { return this.configConverter.convertToApiType(this.configBase.get()); }
@Override public apiType getTrueValue() { return this.configConverter.convertToApiType(this.configBase.getTrueValue()); }
@Override public apiType getApiValue()
{
// if no API value is set, this should return null
if (this.configBase.getApiValue() == null)
{
return null;
}
return this.configConverter.convertToApiType(this.configBase.getApiValue());
}
@Override
public boolean setValue(apiType newValue) public boolean setValue(apiType newValue)
{ {
if (this.configBase.getAllowApiOverride()) if (this.configBase.getAllowApiOverride())
@@ -111,12 +87,12 @@ public class DhApiConfigValue<coreType, apiType> implements IDhApiConfigValue<ap
} }
} }
@Override
public boolean clearValue() public boolean clearValue()
{ {
if (this.configBase.getAllowApiOverride()) if (this.configBase.getAllowApiOverride())
{ {
// no converter should be used here since null objects may need to be handled differently // no converter should be used here since null objects may need to be handled differently
// TODO the API should just have a bool to keep track of whether the API value is in use instead of using NULL
this.configBase.setApiValue(null); this.configBase.setApiValue(null);
return true; return true;
} }
@@ -126,15 +102,13 @@ public class DhApiConfigValue<coreType, apiType> implements IDhApiConfigValue<ap
} }
} }
@Override
public boolean getCanBeOverrodeByApi() { return this.configBase.getAllowApiOverride(); } public boolean getCanBeOverrodeByApi() { return this.configBase.getAllowApiOverride(); }
@Override public apiType getDefaultValue() { return this.configConverter.convertToApiType(this.configBase.getDefaultValue()); } public apiType getDefaultValue() { return this.configConverter.convertToApiType(this.configBase.getDefaultValue()); }
@Override public apiType getMaxValue() { return this.configConverter.convertToApiType(this.configBase.getMax()); } public apiType getMaxValue() { return this.configConverter.convertToApiType(this.configBase.getMax()); }
@Override public apiType getMinValue() { return this.configConverter.convertToApiType(this.configBase.getMin()); } public apiType getMinValue() { return this.configConverter.convertToApiType(this.configBase.getMin()); }
@Override
public void addChangeListener(Consumer<apiType> onValueChangeFunc) public void addChangeListener(Consumer<apiType> onValueChangeFunc)
{ {
this.configBase.addValueChangeListener((coreValue) -> this.configBase.addValueChangeListener((coreValue) ->
@@ -144,6 +118,4 @@ public class DhApiConfigValue<coreType, apiType> implements IDhApiConfigValue<ap
}); });
} }
//endregion
} }
@@ -1,69 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.config.eventHandlers;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBeforeRenderEvent;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiCancelableEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
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.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.coreapi.util.StringUtil;
public class RenderBlockCacheCsvHandler implements IConfigListener
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public static RenderBlockCacheCsvHandler INSTANCE = new RenderBlockCacheCsvHandler();
//=============//
// constructor //
//=============//
/** private since we only ever need one handler at a time */
private RenderBlockCacheCsvHandler() { }
//=================//
// config handling //
//=================//
@Override
public void onConfigValueSet()
{
IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
if (wrapperFactory != null)
{
wrapperFactory.resetCachedIgnoredBlocksSets();
DhApi.Delayed.renderProxy.clearRenderDataCache();
}
}
}
@@ -78,7 +78,7 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.put(EDhApiQualityPreset.HIGH, EDhApiTransparency.COMPLETE); this.put(EDhApiQualityPreset.HIGH, EDhApiTransparency.COMPLETE);
this.put(EDhApiQualityPreset.EXTREME, EDhApiTransparency.COMPLETE); this.put(EDhApiQualityPreset.EXTREME, EDhApiTransparency.COMPLETE);
}}); }});
private final ConfigPresetOptions<EDhApiQualityPreset, Boolean> ssaoEnabled = new ConfigPresetOptions<>(Config.Client.Advanced.Graphics.enableSsao, private final ConfigPresetOptions<EDhApiQualityPreset, Boolean> ssaoEnabled = new ConfigPresetOptions<>(Config.Client.Advanced.Graphics.Ssao.enableSsao,
new HashMap<EDhApiQualityPreset, Boolean>() new HashMap<EDhApiQualityPreset, Boolean>()
{{ {{
this.put(EDhApiQualityPreset.MINIMUM, false); this.put(EDhApiQualityPreset.MINIMUM, false);
@@ -241,8 +241,8 @@ public class ConfigFileHandler
} }
else if (entry.getTrueValue() == null) else if (entry.getTrueValue() == null)
{ {
// shouldn't happen, but just in case // TODO when can this happen?
throw new IllegalArgumentException("ConfigEntry [" + entry.getNameAndCategory() + "] is null, how did this happen?"); throw new IllegalArgumentException("BlockBiomeWrapperPair [" + entry.getNameAndCategory() + "] is null, this may be a problem with [" + ModInfo.NAME + "]. Please contact the authors.");
} }
workConfig.set(entry.getNameAndCategory(), ConfigTypeConverters.attemptToConvertToString(entry.getType(), entry.getTrueValue())); workConfig.set(entry.getNameAndCategory(), ConfigTypeConverters.attemptToConvertToString(entry.getType(), entry.getTrueValue()));
@@ -360,8 +360,7 @@ public class ConfigFileHandler
{ {
LOGGER.error("File creation failed at ["+this.configPath+"], error: ["+e.getMessage()+"].", e); LOGGER.error("File creation failed at ["+this.configPath+"], error: ["+e.getMessage()+"].", e);
// delayed MC getter since this object may be created before // TODO is there a reason this is lazily gotten?
// the singleton has been bound
IMinecraftClientWrapper mc = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); IMinecraftClientWrapper mc = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
mc.crashMinecraft("Loading file and resetting config file failed at path [" + this.configPath + "]. Please check the file is ok and you have the permissions", e); mc.crashMinecraft("Loading file and resetting config file failed at path [" + this.configPath + "]. Please check the file is ok and you have the permissions", e);
} }
@@ -0,0 +1,196 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.config.gui;
import com.seibel.distanthorizons.core.jar.EPlatform;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.system.jawt.JAWT;
import org.lwjgl.system.macosx.*;
import java.awt.*;
import java.lang.reflect.*;
import java.util.regex.*;
import static org.lwjgl.glfw.GLFWNativeCocoa.*;
import static org.lwjgl.glfw.GLFWNativeWin32.*;
import static org.lwjgl.glfw.GLFWNativeX11.*;
import static org.lwjgl.system.JNI.*;
import static org.lwjgl.system.jawt.JAWTFunctions.*;
import static org.lwjgl.system.macosx.ObjCRuntime.*;
// Some of the code is from https://github.com/LWJGL/lwjgl3/blob/master/modules/samples/src/test/java/org/lwjgl/demo/system/jawt/EmbeddedFrameUtil.java
// which is licensed under https://www.lwjgl.org/license
/**
* Some utils for embedding awt and swing items into lwjgl windows
*
* @author Ran
* @author coolGi
*/
public final class EmbeddedFrameUtil
{
private static final int JAVA_VERSION;
private static final JAWT awt;
static
{
Pattern p = Pattern.compile("^(?:1[.])?([1-9][0-9]*)[.-]");
Matcher m = p.matcher(System.getProperty("java.version"));
if (!m.find())
{
throw new IllegalStateException("Failed to parse java.version");
}
JAVA_VERSION = Integer.parseInt(m.group(1));
awt = JAWT.calloc();
awt.version(JAVA_VERSION < 9 ? JAWT_VERSION_1_4 : JAWT_VERSION_9);
if (!JAWT_GetAWT(awt))
{
throw new RuntimeException("GetAWT failed");
}
}
private static String getEmbeddedFrameImpl()
{
switch (EPlatform.get())
{
case LINUX:
return "sun.awt.X11.XEmbeddedFrame";
case WINDOWS:
return "sun.awt.windows.WEmbeddedFrame";
case MACOS:
return "sun.lwawt.macosx.CViewEmbeddedFrame";
default:
throw new IllegalStateException();
}
}
private static long getEmbeddedFrameHandle(long window)
{
switch (EPlatform.get())
{
case LINUX:
return glfwGetX11Window(window);
case WINDOWS:
return glfwGetWin32Window(window);
case MACOS:
long objc_msgSend = ObjCRuntime.getLibrary().getFunctionAddress("objc_msgSend");
return invokePPP(glfwGetCocoaWindow(window), sel_getUid("contentView"), objc_msgSend);
default:
throw new IllegalStateException();
}
}
public static Frame embeddedFrameCreate(long window)
{
if (JAVA_VERSION < 9)
{
try
{
@SuppressWarnings("unchecked")
Class<? extends Frame> EmdeddedFrame = (Class<? extends Frame>) Class.forName(getEmbeddedFrameImpl());
Constructor<? extends Frame> c = EmdeddedFrame.getConstructor(long.class);
return c.newInstance(getEmbeddedFrameHandle(window));
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
else
{
return nJAWT_CreateEmbeddedFrame(getEmbeddedFrameHandle(window), awt.CreateEmbeddedFrame());
}
}
static void embeddedFrameSynthesizeWindowActivation(Frame embeddedFrame, boolean doActivate)
{
if (JAVA_VERSION < 9)
{
try
{
embeddedFrame
.getClass()
.getMethod("synthesizeWindowActivation", boolean.class)
.invoke(embeddedFrame, doActivate);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
else
{
JAWT_SynthesizeWindowActivation(embeddedFrame, doActivate, awt.SynthesizeWindowActivation());
}
}
public static void embeddedFrameSetBounds(Frame embeddedFrame, int x, int y, int width, int height)
{
if (JAVA_VERSION < 9)
{
try
{
Method setLocationPrivate = embeddedFrame
.getClass()
.getSuperclass()
.getDeclaredMethod("setBoundsPrivate", int.class, int.class, int.class, int.class);
setLocationPrivate.setAccessible(true);
setLocationPrivate.invoke(embeddedFrame, x, y, width, height);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
else
{
JAWT_SetBounds(embeddedFrame, x, y, width, height, awt.SetBounds());
}
}
public static void hideFrame(@NotNull Frame embeddedFrame)
{
embeddedFrame.setVisible(false);
embeddedFrameSynthesizeWindowActivation(embeddedFrame, false);
}
public static void showFrame(@NotNull Frame embeddedFrame)
{
embeddedFrameSynthesizeWindowActivation(embeddedFrame, true);
embeddedFrame.setVisible(true);
}
public static void placeAtCenter(Frame embeddedFrame, int windowWidth, int windowHeight, int frameWidth, int frameHeight, float scale)
{
float scaleFactor = (100.0F - scale) / 100.0F;
float newWidth = frameWidth * scaleFactor;
float newHeight = frameHeight * scaleFactor;
float newX = (windowWidth - newWidth) / 2F;
float newY = (windowHeight - newHeight) / 2F;
embeddedFrameSetBounds(embeddedFrame, Math.round(newX), Math.round(newY), Math.round(newWidth), Math.round(newHeight));
}
}
@@ -0,0 +1,164 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.config.gui;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
/**
*
*/
public class JavaScreenHandlerScreen extends AbstractScreen
{
public static Frame frame;
public static boolean firstRun = true;
public final Component jComponent;
static
{
// Note: this code can cause Mac
// to lock up and refuse the load (there's a bug with Java.awt texture loading)
// Needs to be called before any Swing code is called, otherwise
// Swing will get stuck thinking it's headless
System.setProperty("java.awt.headless", "false");
}
public JavaScreenHandlerScreen(@NotNull Component component)
{
this.jComponent = component;
}
@Override
public void init()
{
if (firstRun)
{
frame = EmbeddedFrameUtil.embeddedFrameCreate(this.minecraftWindow); // Don't call this multiple times
}
frame.add(this.jComponent);
frame.setBackground(new Color(0, 125, 155));
JavaScreenHandlerScreen thiss = this;
frame.addKeyListener(new KeyListener()
{
@Override
public void keyPressed(KeyEvent keyEvent)
{
System.out.println("Key pressed code=" + keyEvent.getKeyCode() + ", char=" + keyEvent.getKeyChar());
if (keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE)
{
thiss.close = true;
}
}
@Override
public void keyTyped(KeyEvent keyEvent) { }
@Override
public void keyReleased(KeyEvent keyEvent) { }
});
if (firstRun)
{
EmbeddedFrameUtil.embeddedFrameSetBounds(frame, 0, 0, this.width, this.height);
firstRun = false;
}
EmbeddedFrameUtil.showFrame(frame);
}
/** A testing/debug screen */
public static class ExampleScreen extends JComponent
{
public ExampleScreen()
{
this.setLayout(new GridBagLayout());
this.setBackground(new Color(255, 0, 0)); // doesn't appear to be used
GridBagConstraints helloWorldConstraints = new GridBagConstraints();
helloWorldConstraints.weightx = 0.5;
helloWorldConstraints.gridx = 0;
helloWorldConstraints.gridy = 0;
//helloWorldConstraints.fill = GridBagConstraints.BOTH;
this.add(new JLabel("Hello World!"), helloWorldConstraints);
GridBagConstraints buttonConstraints = new GridBagConstraints();
buttonConstraints.weightx = 0.5;
buttonConstraints.gridx = 0;
buttonConstraints.gridy = 1;
//buttonConstraints.fill = GridBagConstraints.BOTH;
JButton button = new JButton();
button.setBackground(Color.GREEN);
button.setFocusable(false); // otherwise we can't use escape to leave
button.setAction(new ExampleButtonEventHandler("Button text"));
this.add(button, buttonConstraints);
}
private class ExampleButtonEventHandler extends AbstractAction
{
public ExampleButtonEventHandler(String text)
{
super(text);
//this.putValue(SHORT_DESCRIPTION, text);
//this.putValue(MNEMONIC_KEY, text);
}
@Override
public void actionPerformed(ActionEvent e)
{
System.out.println("button pressed");
}
}
}
@Override
public void render(float delta)
{
// TODO: Make screen only update on this being called
}
@Override
public void onResize()
{
EmbeddedFrameUtil.embeddedFrameSetBounds(frame, 0, 0, this.width, this.height);
}
@Override
public void onClose()
{
frame.remove(this.jComponent);
EmbeddedFrameUtil.hideFrame(frame);
}
}
@@ -52,17 +52,11 @@ public abstract class AbstractConfigBase<T>
protected AbstractConfigBase(EConfigEntryAppearance appearance, T defaultValue) protected AbstractConfigBase(EConfigEntryAppearance appearance, T defaultValue)
{ {
this.defaultValue = defaultValue; this.defaultValue = defaultValue;
if (this.defaultValue == null)
{
throw new IllegalArgumentException("defaultValue cannot be null");
}
this.value = defaultValue; this.value = defaultValue;
this.appearance = appearance; this.appearance = appearance;
Class<?> defaultValueClass = defaultValue.getClass(); Class<?> defaultValueClass = defaultValue.getClass();
this.isFloatingPointNumber = (defaultValueClass == Double.class || defaultValueClass == Float.class); this.isFloatingPointNumber = (defaultValueClass == Double.class || defaultValueClass == Float.class);
} }
@@ -60,7 +60,6 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
//region
private ConfigEntry( private ConfigEntry(
EConfigEntryAppearance appearance, EConfigEntryAppearance appearance,
@@ -79,14 +78,11 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
this.listenerList = listenerList; this.listenerList = listenerList;
} }
//endregion
//==========================// //==========================//
// property getters/setters // // property getters/setters //
//==========================// //==========================//
//region
/** the string used when entering the config into the command line or chat */ /** the string used when entering the config into the command line or chat */
public String getChatCommandName() { return this.chatCommandName; } public String getChatCommandName() { return this.chatCommandName; }
@@ -104,22 +100,16 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
public T getMax() { return this.max; } public T getMax() { return this.max; }
public void setMax(T newMax) { this.max = newMax; } public void setMax(T newMax) { this.max = newMax; }
//endregion
//===============// //===============//
// value setters // // value setters //
//===============// //===============//
//region
public void setApiValue(T newApiValue) public void setApiValue(T newApiValue)
{ {
this.apiValue = newApiValue; this.apiValue = newApiValue;
synchronized (this.listenerList) this.listenerList.forEach(IConfigListener::onConfigValueSet);
{
this.listenerList.forEach(IConfigListener::onConfigValueSet);
}
} }
public boolean apiIsOverriding() public boolean apiIsOverriding()
@@ -139,11 +129,7 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
public void setWithoutSaving(T newValue) public void setWithoutSaving(T newValue)
{ {
super.set(newValue); super.set(newValue);
this.listenerList.forEach(IConfigListener::onConfigValueSet);
synchronized (this.listenerList)
{
this.listenerList.forEach(IConfigListener::onConfigValueSet);
}
} }
@Override @Override
public void set(T newValue) public void set(T newValue)
@@ -155,30 +141,19 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
public void uiSetWithoutSaving(T newValue) public void uiSetWithoutSaving(T newValue)
{ {
this.setWithoutSaving(newValue); this.setWithoutSaving(newValue);
this.listenerList.forEach(IConfigListener::onUiModify);
synchronized (this.listenerList)
{
this.listenerList.forEach(IConfigListener::onUiModify);
}
} }
public void uiSet(T newValue) public void uiSet(T newValue)
{ {
this.set(newValue); this.set(newValue);
this.listenerList.forEach(IConfigListener::onUiModify);
synchronized (this.listenerList)
{
this.listenerList.forEach(IConfigListener::onUiModify);
}
} }
//endregion
//===============// //===============//
// value getters // // value getters //
//===============// //===============//
//region
@Override @Override
public T get() public T get()
@@ -199,14 +174,11 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
@Nullable @Nullable
public T getApiValue() { return this.apiValue; } public T getApiValue() { return this.apiValue; }
//endregion
//===========// //===========//
// listeners // // listeners //
//===========// //===========//
//region
/** Fired whenever the config value changes to a new value. */ /** Fired whenever the config value changes to a new value. */
public void addValueChangeListener(Consumer<T> onValueChangeFunc) public void addValueChangeListener(Consumer<T> onValueChangeFunc)
@@ -215,38 +187,26 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
this.addListener(changeListener); this.addListener(changeListener);
} }
/** Fired whenever the config value is updated, including when the value doesn't change (IE when the UI changes state or the config is reloaded). */ /** Fired whenever the config value is updated, including when the value doesn't change (IE when the UI changes state or the config is reloaded). */
public void addListener(IConfigListener newListener) public void addListener(IConfigListener newListener) { this.listenerList.add(newListener); }
{
synchronized (this.listenerList)
{
this.listenerList.add(newListener);
}
}
public void removeListener(IConfigListener oldListener) //public void removeValueChangeListener(Consumer<T> onValueChangeFunc) { } // not currently implemented
{ public void removeListener(IConfigListener oldListener) { this.listenerList.remove(oldListener); }
synchronized (this.listenerList)
{
this.listenerList.remove(oldListener);
}
}
public void clearListeners() public void clearListeners() { this.listenerList.clear(); }
public ArrayList<IConfigListener> getListeners() { return this.listenerList; }
/** Replaces the listener list */
public void setListeners(ArrayList<IConfigListener> newListeners)
{ {
synchronized (this.listenerList) this.listenerList.clear();
{ this.listenerList.addAll(newListeners);
this.listenerList.clear();
}
} }
public void setListeners(IConfigListener... newListeners) { this.listenerList.addAll(Arrays.asList(newListeners)); }
//endregion
//====================// //====================//
// min/max validation // // min/max validation //
//====================// //====================//
//region
/** Checks if this config's current value is valid */ /** Checks if this config's current value is valid */
public EConfigValidity getValidity() { return this.getValidity(this.value, this.min, this.max); } public EConfigValidity getValidity() { return this.getValidity(this.value, this.min, this.max); }
@@ -297,31 +257,22 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
} }
} }
//endregion
//===============// //===============//
// file handling // // file handling //
//===============// //===============//
//region
/** This should normally not be called since set() automatically calls this */ /** This should normally not be called since set() automatically calls this */
public void save() { ConfigHandler.INSTANCE.configFileHandler.saveEntry(this); } public void save() { ConfigHandler.INSTANCE.configFileHandler.saveEntry(this); }
/** This should normally not be called except for special circumstances */ /** This should normally not be called except for special circumstances */
public void load() { ConfigHandler.INSTANCE.configFileHandler.loadEntry(this); } public void load() { ConfigHandler.INSTANCE.configFileHandler.loadEntry(this); }
//endregion
//================// //================//
// base overrides // // base overrides //
//================// //================//
//region
@Override
public String toString() { return this.name + ": [" + this.get() + "]"; }
public boolean equals(AbstractConfigBase<?> obj) public boolean equals(AbstractConfigBase<?> obj)
{ {
@@ -343,14 +294,11 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
} }
} }
//endregion
//=========// //=========//
// builder // // builder //
//=========// //=========//
//region
public static class Builder<T> extends AbstractConfigBase.Builder<T, Builder<T>> public static class Builder<T> extends AbstractConfigBase.Builder<T, Builder<T>>
{ {
@@ -449,8 +397,4 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
} }
//endregion
} }
@@ -2,9 +2,11 @@ package com.seibel.distanthorizons.core.dataObjects;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap; import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
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 java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@@ -119,9 +121,9 @@ public class BlockBiomeWrapperPair
//=============// //=================//
// serializing // // (de)serializing //
//=============// //=================//
public String serialize() public String serialize()
{ {
@@ -133,6 +135,17 @@ public class BlockBiomeWrapperPair
return this.serialString; return this.serialString;
} }
public static BlockBiomeWrapperPair deserialize(String str, ILevelWrapper levelWrapper) throws DataCorruptedException
{
int separatorIndex = str.indexOf(FullDataPointIdMap.BLOCK_STATE_SEPARATOR_STRING);
if (separatorIndex == -1)
{
throw new DataCorruptedException("Failed to deserialize BiomeBlockStateEntry ["+str+"], unable to find separator.");
}
IBiomeWrapper biome = WRAPPER_FACTORY.deserializeBiomeWrapperOrGetDefault(str.substring(0, separatorIndex), levelWrapper);
IBlockStateWrapper blockState = WRAPPER_FACTORY.deserializeBlockStateWrapperOrGetDefault(str.substring(separatorIndex+FullDataPointIdMap.BLOCK_STATE_SEPARATOR_STRING.length()), levelWrapper);
return BlockBiomeWrapperPair.get(blockState, biome);
}
} }
@@ -20,42 +20,37 @@
package com.seibel.distanthorizons.core.dataObjects.fullData; package com.seibel.distanthorizons.core.dataObjects.fullData;
import com.seibel.distanthorizons.core.dataObjects.BlockBiomeWrapperPair; import com.seibel.distanthorizons.core.dataObjects.BlockBiomeWrapperPair;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.util.objects.pooling.StringPool;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException; import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream; import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream; import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
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.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import it.unimi.dsi.fastutil.chars.CharArrayList;
import org.jetbrains.annotations.NotNull;
import java.io.*; import java.io.*;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
/** /**
* Used to map a numerical IDs to a Biome/BlockState pair. <br> * WARNING: This is not THREAD-SAFE! <br><br>
* Note: This is not thread safe. <br> *
* Used to map a numerical IDs to a Biome/BlockState pair. <br><br>
*
* TODO the serializing of this map might be really big
* since it stringifies every block and biome name, which is quite bulky.
* It might be worth while to have a biome and block ID that then both get mapped
* to the data point ID to reduce file size.
* And/or it would be good to dynamically remove IDs that aren't currently in use.
* *
* @author Leetom * @author Leetom
*/ */
public class FullDataPointIdMap public class FullDataPointIdMap
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
public static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("IdMap");
/** /**
* Should only be enabled when debugging. * Should only be enabled when debugging.
* Has the system check if any duplicate Entries were read/written * Has the system check if any duplicate Entries were read/written
@@ -80,18 +75,14 @@ public class FullDataPointIdMap
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
//region
public FullDataPointIdMap(long pos) { this.pos = pos; } public FullDataPointIdMap(long pos) { this.pos = pos; }
//endregion
//=========// //=========//
// getters // // getters //
//=========// //=========//
//region
/** @see FullDataPointIdMap#getEntry(int) */ /** @see FullDataPointIdMap#getEntry(int) */
public IBiomeWrapper getBiomeWrapper(int id) throws IndexOutOfBoundsException { return this.getEntry(id).biome; } public IBiomeWrapper getBiomeWrapper(int id) throws IndexOutOfBoundsException { return this.getEntry(id).biome; }
@@ -122,14 +113,11 @@ public class FullDataPointIdMap
public long getPos() { return this.pos; } public long getPos() { return this.pos; }
//endregion
//=========// //=========//
// setters // // setters //
//=========// //=========//
//region
/** /**
* If an entry with the given values already exists nothing will * If an entry with the given values already exists nothing will
@@ -227,14 +215,11 @@ public class FullDataPointIdMap
this.cachedHashCode = 0; this.cachedHashCode = 0;
} }
//endregion
//=============// //=============//
// serializing // // serializing //
//=============// //=============//
//region
/** Serializes all contained entries into the given stream, formatted in UTF */ /** Serializes all contained entries into the given stream, formatted in UTF */
public void serialize(DhDataOutputStream outputStream) throws IOException public void serialize(DhDataOutputStream outputStream) throws IOException
@@ -264,9 +249,8 @@ public class FullDataPointIdMap
} }
} }
/** Clears and populates the given {@link FullDataPointIdMap} from the given UTF formatted stream */ /** Creates a new IdBiomeBlockStateMap from the given UTF formatted stream */
public static void deserialize(@NotNull FullDataPointIdMap map, DhDataInputStream inputStream, long pos, ILevelWrapper levelWrapper) public static FullDataPointIdMap deserialize(DhDataInputStream inputStream, long pos, ILevelWrapper levelWrapper) throws IOException, InterruptedException, DataCorruptedException
throws IOException, InterruptedException, DataCorruptedException
{ {
int entityCount = inputStream.readInt(); int entityCount = inputStream.readInt();
if (entityCount < 0) if (entityCount < 0)
@@ -274,140 +258,52 @@ public class FullDataPointIdMap
throw new DataCorruptedException("FullDataPointIdMap deserialize entry count should have a number greater than or equal to 0, returned value ["+entityCount+"]."); throw new DataCorruptedException("FullDataPointIdMap deserialize entry count should have a number greater than or equal to 0, returned value ["+entityCount+"].");
} }
// clearing the old values is necessary so we can re-use the same map multiple times
map.clear(pos);
// only used when debugging // only used when debugging
HashMap<String, BlockBiomeWrapperPair> dataPointEntryBySerialization = new HashMap<>(); HashMap<String, BlockBiomeWrapperPair> dataPointEntryBySerialization = new HashMap<>();
try(PhantomArrayListCheckout checkout = ARRAY_LIST_POOL.checkoutArrays(0, 0, 0, 3)) FullDataPointIdMap newMap = new FullDataPointIdMap(pos);
for (int i = 0; i < entityCount; i++)
{ {
for (int i = 0; i < entityCount; i++) // necessary to prevent issues with deserializing objects after the level has been closed
if (Thread.interrupted())
{ {
// necessary to prevent issues with deserializing objects after the level has been closed throw new InterruptedException("[" + FullDataPointIdMap.class.getSimpleName() + "] deserializing interrupted.");
if (Thread.interrupted())
{
throw new InterruptedException("[" + FullDataPointIdMap.class.getSimpleName() + "] deserializing interrupted.");
}
int length = inputStream.readUnsignedShort();
CharArrayList fullCharList = checkout.getCharArray(0, length);
CharArrayList biomeCharList = checkout.getCharArray(1, length);
CharArrayList blockCharList = checkout.getCharArray(2, length);
// parse the full UTF string
for (int stringIndex = 0; stringIndex < length; stringIndex++)
{
byte b = inputStream.readByte();
char c = (char) (b & 0xFF);
fullCharList.set(stringIndex, c);
}
splitCharArray(fullCharList, biomeCharList, blockCharList);
String biomeString = StringPool.INSTANCE.getPooledString(biomeCharList);
IBiomeWrapper biome = WRAPPER_FACTORY.deserializeBiomeWrapperOrGetDefault(biomeString, levelWrapper);
String blockStateString = StringPool.INSTANCE.getPooledString(blockCharList);
IBlockStateWrapper blockState = WRAPPER_FACTORY.deserializeBlockStateWrapperOrGetDefault(blockStateString, levelWrapper);
BlockBiomeWrapperPair newPair = BlockBiomeWrapperPair.get(blockState, biome);
map.blockBiomePairList.add(newPair);
if (RUN_SERIALIZATION_DUPLICATE_VALIDATION)
{
String entryString = StringPool.INSTANCE.getPooledString(fullCharList);
if (dataPointEntryBySerialization.containsKey(entryString))
{
LOGGER.error("Duplicate deserialized entry found with serial: " + entryString);
}
if (dataPointEntryBySerialization.containsValue(newPair))
{
LOGGER.error("Duplicate deserialized entry found with value: " + newPair.serialize());
}
dataPointEntryBySerialization.put(entryString, newPair);
}
}
}
if (map.size() != entityCount)
{
// if the mappings are out of sync then the LODs will render incorrectly due to IDs being wrong
LodUtil.assertNotReach("ID maps failed to deserialize for pos: ["+ DhSectionPos.toString(pos)+"], incorrect entity count. Expected count ["+entityCount+"], actual count ["+map.size()+"]");
}
}
/**
* Splits up the given input {@link CharArrayList} into the
* necessary biome and blockstate lists based on the location of
* {@link FullDataPointIdMap#BLOCK_STATE_SEPARATOR_STRING}
*/
private static void splitCharArray(
CharArrayList input,
CharArrayList biomeString, CharArrayList blockString) throws DataCorruptedException
{
boolean separatorFound = false;
int foundStartIndex = -1;
int separatorIndex = 0;
for (int inputIndex = 0; inputIndex < input.size(); inputIndex++)
{
char ch = input.getChar(inputIndex);
if (ch == FullDataPointIdMap.BLOCK_STATE_SEPARATOR_STRING.charAt(separatorIndex))
{
if (!separatorFound)
{
foundStartIndex = inputIndex;
}
separatorFound = true;
separatorIndex++;
}
else
{
separatorFound = false;
foundStartIndex = -1;
separatorIndex = 0;
} }
if (separatorFound
&& separatorIndex == FullDataPointIdMap.BLOCK_STATE_SEPARATOR_STRING.length()) String entryString = inputStream.readUTF();
BlockBiomeWrapperPair newPair = BlockBiomeWrapperPair.deserialize(entryString, levelWrapper);
newMap.blockBiomePairList.add(newPair);
if (RUN_SERIALIZATION_DUPLICATE_VALIDATION)
{ {
break; if (dataPointEntryBySerialization.containsKey(entryString))
{
LOGGER.error("Duplicate deserialized entry found with serial: " + entryString);
}
if (dataPointEntryBySerialization.containsValue(newPair))
{
LOGGER.error("Duplicate deserialized entry found with value: " + newPair.serialize());
}
dataPointEntryBySerialization.put(entryString, newPair);
} }
} }
if (newMap.size() != entityCount)
if (foundStartIndex == -1)
{ {
throw new DataCorruptedException("Failed to deserialize BiomeBlockStateEntry ["+input.toString()+"], unable to find separator."); // if the mappings are out of sync then the LODs will render incorrectly due to IDs being wrong
LodUtil.assertNotReach("ID maps failed to deserialize for pos: ["+ DhSectionPos.toString(pos)+"], incorrect entity count. Expected count ["+entityCount+"], actual count ["+newMap.size()+"]");
} }
biomeString.clear(); return newMap;
for (int i = 0; i < foundStartIndex; i++)
{
biomeString.push(input.getChar(i));
}
blockString.clear();
for (int i = foundStartIndex + FullDataPointIdMap.BLOCK_STATE_SEPARATOR_STRING.length();
i < input.size();
i++)
{
blockString.push(input.getChar(i));
}
} }
//endregion
//===========// //===========//
// overrides // // overrides //
//===========// //===========//
//region
@Override
public String toString() { return DhSectionPos.toString(this.pos) + " size: " + this.blockBiomePairList.size(); }
@Override @Override
public boolean equals(Object other) public boolean equals(Object other)
@@ -443,8 +339,6 @@ public class FullDataPointIdMap
this.cachedHashCode = result; this.cachedHashCode = result;
} }
//endregion
} }
@@ -166,7 +166,8 @@ public class FullDataSourceV1
this.setDataPoints(dataPoints); this.setDataPoints(dataPoints);
this.readIdMappings(this.mapping, inputStream, level.getLevelWrapper()); FullDataPointIdMap mapping = this.readIdMappings(inputStream, level.getLevelWrapper());
this.setIdMapping(mapping);
} }
@@ -346,7 +347,7 @@ public class FullDataSourceV1
outputStream.writeInt(DATA_GUARD_BYTE); outputStream.writeInt(DATA_GUARD_BYTE);
this.mapping.serialize(outputStream); this.mapping.serialize(outputStream);
} }
public void readIdMappings(FullDataPointIdMap map, DhDataInputStream inputStream, ILevelWrapper levelWrapper) throws IOException, InterruptedException, DataCorruptedException public FullDataPointIdMap readIdMappings(DhDataInputStream inputStream, ILevelWrapper levelWrapper) throws IOException, InterruptedException, DataCorruptedException
{ {
int guardByte = inputStream.readInt(); int guardByte = inputStream.readInt();
if (guardByte != DATA_GUARD_BYTE) if (guardByte != DATA_GUARD_BYTE)
@@ -354,8 +355,9 @@ public class FullDataSourceV1
throw new IOException("Invalid data content end guard for ID mapping"); throw new IOException("Invalid data content end guard for ID mapping");
} }
FullDataPointIdMap.deserialize(map, inputStream, this.pos, levelWrapper); return FullDataPointIdMap.deserialize(inputStream, this.pos, levelWrapper);
} }
public void setIdMapping(FullDataPointIdMap mappings) { this.mapping.mergeAndReturnRemappedEntityIds(mappings); }
@@ -31,9 +31,10 @@ import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2; import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.objects.pooling.AbstractPhantomArrayList; import com.seibel.distanthorizons.core.pooling.AbstractPhantomArrayList;
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListCheckout; import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListPool; import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.sql.dto.util.FullDataMinMaxPosUtil; import com.seibel.distanthorizons.core.sql.dto.util.FullDataMinMaxPosUtil;
@@ -43,7 +44,6 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import it.unimi.dsi.fastutil.bytes.ByteArrayList; import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -78,9 +78,7 @@ public class FullDataSourceV2
/** how many chunks wide this datasource is at detail level 0. */ /** how many chunks wide this datasource is at detail level 0. */
public static final int NUMB_OF_CHUNKS_WIDE = WIDTH / LodUtil.CHUNK_WIDTH; public static final int NUMB_OF_CHUNKS_WIDE = WIDTH / LodUtil.CHUNK_WIDTH;
private static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("FullDataV2"); public static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("FullDataV2");
private static final ThreadLocal<FullDataPointIdMap> DATA_MAP_FOR_REMAPPING_REF = ThreadLocal.withInitial(() -> new FullDataPointIdMap(DhSectionPos.encode((byte)0, 0, 0)));
@@ -130,7 +128,6 @@ public class FullDataSourceV2
//==============// //==============//
// constructors // // constructors //
//==============// //==============//
//region
public static FullDataSourceV2 createFromChunk(ILevelWrapper levelWrapper, IChunkWrapper chunkWrapper) { return LodDataBuilder.createFromChunk(levelWrapper, chunkWrapper); } public static FullDataSourceV2 createFromChunk(ILevelWrapper levelWrapper, IChunkWrapper chunkWrapper) { return LodDataBuilder.createFromChunk(levelWrapper, chunkWrapper); }
@@ -225,7 +222,7 @@ public class FullDataSourceV2
byte @Nullable [] columnGenerationSteps, byte @Nullable [] columnWorldCompressionMode, byte @Nullable [] columnGenerationSteps, byte @Nullable [] columnWorldCompressionMode,
boolean empty) boolean empty)
{ {
super(ARRAY_LIST_POOL, 2, 0, WIDTH * WIDTH, 0); super(ARRAY_LIST_POOL, 2, 0, WIDTH * WIDTH);
LodUtil.assertTrue(data == null || data.length == WIDTH * WIDTH); LodUtil.assertTrue(data == null || data.length == WIDTH * WIDTH);
@@ -277,14 +274,11 @@ public class FullDataSourceV2
} }
} }
//endregion
//=========// //=========//
// getters // // getters //
//=========// //=========//
//region
public LongArrayList getColumnAtRelPos(int relX, int relZ) throws IndexOutOfBoundsException public LongArrayList getColumnAtRelPos(int relX, int relZ) throws IndexOutOfBoundsException
{ return this.dataPoints[relativePosToIndex(relX, relZ)]; } { return this.dataPoints[relativePosToIndex(relX, relZ)]; }
@@ -307,11 +301,16 @@ public class FullDataSourceV2
*/ */
public long getDataPointAtBlockPos(int blockPosX, int blockPosY, int blockPosZ, int levelMinY) public long getDataPointAtBlockPos(int blockPosX, int blockPosY, int blockPosZ, int levelMinY)
{ {
long requestedPos = DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ); DhLodPos requestedPos = new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ);
// stop if the requested blockPos is outside this datasource // stop if the requested blockPos is outside this datasource
{ {
long sectionPos = DhSectionPos.encodeContaining(DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL, new DhBlockPos(blockPosX, blockPosY, blockPosZ)); // get the detail levels for this request
byte requestedDetailLevel = requestedPos.detailLevel;
byte requestedSectionDetailLevel = (byte) (requestedDetailLevel + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
// get the positions for this request
long sectionPos = requestedPos.getSectionPosWithSectionDetailLevel(requestedSectionDetailLevel);
if (!DhSectionPos.contains(this.pos, sectionPos)) if (!DhSectionPos.contains(this.pos, sectionPos))
{ {
return FullDataPointUtil.EMPTY_DATA_POINT; return FullDataPointUtil.EMPTY_DATA_POINT;
@@ -321,10 +320,10 @@ public class FullDataSourceV2
// get the relative data source position // get the relative data source position
byte requestDetailLevel = (byte) (DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL); byte requestDetailLevel = (byte) (DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
long relativePos = DhSectionPos.getDhSectionRelativePositionForDetailLevel(requestedPos, requestDetailLevel); DhLodPos relativePos = requestedPos.getDhSectionRelativePositionForDetailLevel(requestDetailLevel);
// get the data column // get the data column
LongArrayList dataColumn = this.getColumnAtRelPos(DhSectionPos.getX(relativePos), DhSectionPos.getZ(relativePos)); LongArrayList dataColumn = this.getColumnAtRelPos(relativePos.x, relativePos.z);
if (dataColumn == null) if (dataColumn == null)
{ {
return FullDataPointUtil.EMPTY_DATA_POINT; return FullDataPointUtil.EMPTY_DATA_POINT;
@@ -362,14 +361,11 @@ public class FullDataSourceV2
return FullDataPointUtil.EMPTY_DATA_POINT; return FullDataPointUtil.EMPTY_DATA_POINT;
} }
//endregion
//==========// //==========//
// updating // // updating //
//==========// //==========//
//region
public boolean updateFromDataSource(@NotNull FullDataSourceV2 inputDataSource) public boolean updateFromDataSource(@NotNull FullDataSourceV2 inputDataSource)
{ {
@@ -443,8 +439,6 @@ public class FullDataSourceV2
} }
// needed to prevent infinite mapped ID growth
this.removeUnusedIdsAndRemap();
@@ -682,13 +676,13 @@ public class FullDataSourceV2
return dataChanged; return dataChanged;
} }
/** /**
* The minimum value is used because we don't want to accidentally record that * The minimum value is used because we don't want to accidentally record that
* something was generated when it wasn't. * something was generated when it wasn't.
*/ */
private static byte determineMinWorldGenStepForTwoByTwoColumn(ByteArrayList columnGenerationSteps, int relX, int relZ) private static byte determineMinWorldGenStepForTwoByTwoColumn(ByteArrayList columnGenerationSteps, int relX, int relZ)
{ {
// TODO merge similar logic with determineHighestWorldCompressionForTwoByTwoColumn
byte minWorldGenStepValue = Byte.MAX_VALUE; byte minWorldGenStepValue = Byte.MAX_VALUE;
for (int x = 0; x < 2; x++) for (int x = 0; x < 2; x++)
{ {
@@ -707,6 +701,7 @@ public class FullDataSourceV2
*/ */
private static byte determineHighestWorldCompressionForTwoByTwoColumn(ByteArrayList columnCompressionMode, int relX, int relZ) private static byte determineHighestWorldCompressionForTwoByTwoColumn(ByteArrayList columnCompressionMode, int relX, int relZ)
{ {
// TODO merge similar logic with determineMinWorldGenStepForTwoByTwoColumn
byte minWorldGenStepValue = Byte.MIN_VALUE; byte minWorldGenStepValue = Byte.MIN_VALUE;
for (int x = 0; x < 2; x++) for (int x = 0; x < 2; x++)
{ {
@@ -782,7 +777,7 @@ public class FullDataSourceV2
return newColumnList; return newColumnList;
} }
// sort the transitions from bottom to top // sort the transitions from bottom to top // TODO
yTransitions.sort(null); yTransitions.sort(null);
// create index trackers for each column, // create index trackers for each column,
@@ -1134,76 +1129,11 @@ public class FullDataSourceV2
return dataChanged; return dataChanged;
} }
/**
* Should be run at the end of {@link FullDataSourceV2#updateFromDataSource}
* so the {@link FullDataPointIdMap} will only contain ID's that are actively in use. <br><br>
*
* This prevents the {@link FullDataPointIdMap} from growing infinitely when merged.
*
* @see FullDataPointIdMap#mergeAndReturnRemappedEntityIds(FullDataPointIdMap)
*/
private void removeUnusedIdsAndRemap()
{
Int2IntOpenHashMap newIdByOldId = new Int2IntOpenHashMap();
FullDataPointIdMap newMap = DATA_MAP_FOR_REMAPPING_REF.get();
newMap.clear(this.pos);
// find all the IDs that are currently in use
for (int x = 0; x < WIDTH; x++)
{
for (int z = 0; z < WIDTH; z++)
{
int index = relativePosToIndex(x, z);
LongArrayList dataColumn = this.dataPoints[index];
for (int i = 0; i < dataColumn.size(); i++)
{
long dataPoint = dataColumn.getLong(i);
int oldId = FullDataPointUtil.getId(dataPoint);
int newId = newMap.addIfNotPresentAndGetId(
this.mapping.getBiomeWrapper(oldId),
this.mapping.getBlockStateWrapper(oldId));
newIdByOldId.put(oldId, newId);
}
}
}
// replace the old entries to remove any unneeded ones
this.mapping.clear(this.pos);
this.mapping.addAll(newMap);
// remap the data
for (int x = 0; x < WIDTH; x++)
{
for (int z = 0; z < WIDTH; z++)
{
int index = relativePosToIndex(x, z);
LongArrayList dataColumn = this.dataPoints[index];
for (int i = 0; i < dataColumn.size(); i++)
{
long oldDataPoint = dataColumn.getLong(i);
int oldId = FullDataPointUtil.getId(oldDataPoint);
int newId = newIdByOldId.get(oldId);
long newDataPoint = FullDataPointUtil.setId(oldDataPoint, newId);
dataColumn.set(i, newDataPoint);
}
}
}
}
//endregion
//===================// //===================//
// adjacent clearing // // adjacent clearing //
//===================// //===================//
//region
/** Removes any non-adjacent data from the given direction. */ /** Removes any non-adjacent data from the given direction. */
public void clearAllNonAdjData(EDhDirection direction) public void clearAllNonAdjData(EDhDirection direction)
@@ -1232,13 +1162,10 @@ public class FullDataSourceV2
} }
} }
//endregion
//================// //================//
// helper methods // // helper methods //
//================// //================//
//region
/** /**
* Usually this should just be used internally, but there may be instances * Usually this should just be used internally, but there may be instances
@@ -1330,14 +1257,11 @@ public class FullDataSourceV2
} }
} }
//endregion
//=====================// //=====================//
// setters and getters // // setters and getters //
//=====================// //=====================//
//region
public long getPos() { return this.pos; } public long getPos() { return this.pos; }
@@ -1367,14 +1291,11 @@ public class FullDataSourceV2
} }
} }
//endregion
//=============// //=============//
// API methods // // API methods //
//=============// //=============//
//region
public void setRunApiSetterValidation(boolean runValidation) { this.runApiSetterValidation = runValidation; } public void setRunApiSetterValidation(boolean runValidation) { this.runApiSetterValidation = runValidation; }
@@ -1395,6 +1316,7 @@ public class FullDataSourceV2
LongArrayList packedDataPoints = LodDataBuilder.convertApiDataPointListToPackedLongArray(columnDataPoints, this, 0, true); LongArrayList packedDataPoints = LodDataBuilder.convertApiDataPointListToPackedLongArray(columnDataPoints, this, 0, true);
// TODO there should be an "unknown" compression and generation step, or be defined via the datapoints
this.setSingleColumn(packedDataPoints, relX, relZ, EDhApiWorldGenerationStep.SURFACE, EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS); this.setSingleColumn(packedDataPoints, relX, relZ, EDhApiWorldGenerationStep.SURFACE, EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS);
return columnDataPoints; return columnDataPoints;
@@ -1422,26 +1344,20 @@ public class FullDataSourceV2
return apiList; return apiList;
} }
//endregion
//============// //============//
// unit tests // // unit tests //
//============// //============//
//region
public PhantomArrayListCheckout getPhantomArrayCheckoutForUnitTesting() public PhantomArrayListCheckout getPhantomArrayCheckoutForUnitTesting()
{ return this.pooledArraysCheckout; } { return this.pooledArraysCheckout; }
//endregion
//================// //================//
// base overrides // // base overrides //
//================// //================//
//region
@Override @Override
public String toString() { return DhSectionPos.toString(this.pos); } public String toString() { return DhSectionPos.toString(this.pos); }
@@ -1488,8 +1404,6 @@ public class FullDataSourceV2
} }
} }
//endregion
} }
@@ -19,12 +19,13 @@
package com.seibel.distanthorizons.core.dataObjects.render; package com.seibel.distanthorizons.core.dataObjects.render;
import com.seibel.distanthorizons.api.enums.config.EDhApiVerticalQuality;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.objects.pooling.AbstractPhantomArrayList; import com.seibel.distanthorizons.core.pooling.AbstractPhantomArrayList;
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListPool; import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnRenderView; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnQuadView;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil; import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
@@ -45,16 +46,15 @@ public class ColumnRenderSource extends AbstractPhantomArrayList
/** /** will be zero if an empty data source was created */
* will be zero if an empty data source was created public int verticalDataCount;
* @see EDhApiVerticalQuality#calculateMaxNumberOfVerticalSlicesAtDetailLevel(byte)
*/
public int maxVerticalSliceCount;
public long pos; public long pos;
public int yOffset; public int yOffset;
public final LongArrayList renderDataContainer; public final LongArrayList renderDataContainer;
public final DebugSourceFlag[] debugSourceFlags;
private boolean isEmpty = true; private boolean isEmpty = true;
@@ -62,67 +62,64 @@ public class ColumnRenderSource extends AbstractPhantomArrayList
//==============// //==============//
// constructors // // constructors //
//==============// //==============//
//region
public static ColumnRenderSource createEmpty(long pos, int maxVertSliceCount, int yOffset) public static ColumnRenderSource createEmpty(long pos, int maxVerticalSize, int yOffset)
{ return new ColumnRenderSource(pos, maxVertSliceCount, yOffset); } { return new ColumnRenderSource(pos, maxVerticalSize, yOffset); }
/** /**
* Creates an empty ColumnRenderSource. * Creates an empty ColumnRenderSource.
* *
* @param pos the relative position of the container * @param pos the relative position of the container
* @param maxVertSliceCount the maximum vertical size of the container * @param maxVerticalSize the maximum vertical size of the container
*/ */
private ColumnRenderSource(long pos, int maxVertSliceCount, int yOffset) private ColumnRenderSource(long pos, int maxVerticalSize, int yOffset)
{ {
super(ARRAY_LIST_POOL, 0, 0, 1, 0); super(ARRAY_LIST_POOL, 0, 0, 1);
this.pos = pos; this.pos = pos;
this.yOffset = yOffset; this.yOffset = yOffset;
this.maxVerticalSliceCount = maxVertSliceCount; this.verticalDataCount = maxVerticalSize;
this.renderDataContainer = this.pooledArraysCheckout.getLongArray(0, WIDTH * WIDTH * this.maxVerticalSliceCount); this.renderDataContainer = this.pooledArraysCheckout.getLongArray(0, WIDTH * WIDTH * this.verticalDataCount);
this.debugSourceFlags = new DebugSourceFlag[WIDTH * WIDTH];
} }
//endregion
//========================// //========================//
// datapoint manipulation // // datapoint manipulation //
//========================// //========================//
//region
public long getDataPoint(int posX, int posZ, int verticalIndex) { return this.renderDataContainer.getLong(posX * WIDTH * this.maxVerticalSliceCount + posZ * this.maxVerticalSliceCount + verticalIndex); } public long getDataPoint(int posX, int posZ, int verticalIndex) { return this.renderDataContainer.getLong(posX * WIDTH * this.verticalDataCount + posZ * this.verticalDataCount + verticalIndex); }
public void populateColumnView(ColumnRenderView view, int posX, int posZ) throws IllegalArgumentException public ColumnArrayView getVerticalDataPointView(int posX, int posZ)
{ {
int offset = posX * WIDTH * this.maxVerticalSliceCount + posZ * this.maxVerticalSliceCount; int offset = posX * WIDTH * this.verticalDataCount + posZ * this.verticalDataCount;
// don't allow returning views that are outside this render source's bounds // don't allow returning views that are outside this render source's bounds
if (offset >= this.renderDataContainer.size()) if (offset >= this.renderDataContainer.size())
{ {
throw new IllegalArgumentException("Column View offset ["+offset+"] greater than parent render data container ["+DhSectionPos.toString(this.pos)+"] size ["+this.renderDataContainer.size()+"]."); return null;
} }
else if (posX < 0 || posX >= WIDTH else if (posX < 0 || posX >= WIDTH
|| posZ < 0 || posZ >= WIDTH) || posZ < 0 || posZ >= WIDTH)
{ {
throw new IllegalArgumentException("Column View pos outside valid range ["+posX+","+posZ+"]."); return null;
} }
view.populate( return new ColumnArrayView(this.renderDataContainer, this.verticalDataCount,
this.renderDataContainer, this.maxVerticalSliceCount, offset, this.verticalDataCount);
offset, this.maxVerticalSliceCount);
} }
//endregion public ColumnQuadView getFullQuadView() { return this.getQuadViewOverRange(0, 0, WIDTH, WIDTH); }
public ColumnQuadView getQuadViewOverRange(int quadX, int quadZ, int quadXSize, int quadZSize) { return new ColumnQuadView(this.renderDataContainer, WIDTH, this.verticalDataCount, quadX, quadZ, quadXSize, quadZSize); }
//=====================// //=====================//
// data helper methods // // data helper methods //
//=====================// //=====================//
//region
public Long getPos() { return this.pos; } public Long getPos() { return this.pos; }
public Long getKey() { return this.pos; } public Long getKey() { return this.pos; }
@@ -140,20 +137,18 @@ public class ColumnRenderSource extends AbstractPhantomArrayList
return false; return false;
} }
try (ColumnRenderView columnView = ColumnRenderView.getPooled())
for (int x = 0; x < WIDTH; x++)
{ {
for (int x = 0; x < WIDTH; x++) for (int z = 0; z < WIDTH; z++)
{ {
for (int z = 0; z < WIDTH; z++) ColumnArrayView columnArrayView = this.getVerticalDataPointView(x,z);
for (int i = 0; i < columnArrayView.size; i++)
{ {
this.populateColumnView(columnView, x, z); long dataPoint = columnArrayView.get(i);
for (int i = 0; i < columnView.size; i++) if (!RenderDataPointUtil.hasZeroHeight(dataPoint))
{ {
long dataPoint = columnView.get(i); return true;
if (!RenderDataPointUtil.hasZeroHeight(dataPoint))
{
return true;
}
} }
} }
} }
@@ -162,14 +157,31 @@ public class ColumnRenderSource extends AbstractPhantomArrayList
return false; return false;
} }
//endregion
//=======//
// debug //
//=======//
/** Sets the debug flag for the given area */
public void fillDebugFlag(int xStart, int zStart, int xWidth, int zWidth, DebugSourceFlag flag)
{
for (int x = xStart; x < xStart + xWidth; x++)
{
for (int z = zStart; z < zStart + zWidth; z++)
{
this.debugSourceFlags[x * WIDTH + z] = flag;
}
}
}
public DebugSourceFlag debugGetFlag(int ox, int oz) { return this.debugSourceFlags[ox * WIDTH + oz]; }
//==============// //==============//
// base methods // // base methods //
//==============// //==============//
//region
@Override @Override
public String toString() public String toString()
@@ -187,11 +199,11 @@ public class ColumnRenderSource extends AbstractPhantomArrayList
{ {
for (int x = 0; x < size; x++) for (int x = 0; x < size; x++)
{ {
for (int y = 0; y < this.maxVerticalSliceCount; y++) for (int y = 0; y < this.verticalDataCount; y++)
{ {
//Converting the dataToHex //Converting the dataToHex
stringBuilder.append(Long.toHexString(this.getDataPoint(x, z, y))); stringBuilder.append(Long.toHexString(this.getDataPoint(x, z, y)));
if (y != this.maxVerticalSliceCount - 1) if (y != this.verticalDataCount - 1)
stringBuilder.append(SUBDATA_DELIMITER); stringBuilder.append(SUBDATA_DELIMITER);
} }
@@ -205,8 +217,21 @@ public class ColumnRenderSource extends AbstractPhantomArrayList
return stringBuilder.toString(); return stringBuilder.toString();
} }
//endregion
//==============//
// helper enums //
//==============//
public enum DebugSourceFlag
{
FULL(ColorUtil.BLUE),
DIRECT(ColorUtil.WHITE),
FILE(ColorUtil.BROWN);
public final int color;
DebugSourceFlag(int color) { this.color = color; }
}
} }
@@ -47,7 +47,7 @@ public final class BufferQuad
public short widthEastWest; public short widthEastWest;
/** This is both North/South and Up/Down since the merging logic is the same either way */ /** This is both North/South and Up/Down since the merging logic is the same either way */
public short widthNorthSouthOrHeight; public short widthNorthSouthOrUpDown;
public final int color; public final int color;
/** used by the Iris shader mod to determine how each LOD should be rendered */ /** used by the Iris shader mod to determine how each LOD should be rendered */
@@ -62,24 +62,20 @@ public final class BufferQuad
BufferQuad( BufferQuad(
short x, short y, short z, short widthEastWest, short widthNorthSouthOrHeight, short x, short y, short z, short widthEastWest, short widthNorthSouthOrUpDown,
int color, byte irisBlockMaterialId, byte skylight, byte blockLight, int color, byte irisBlockMaterialId, byte skylight, byte blockLight,
EDhDirection direction) EDhDirection direction)
{ {
if (widthEastWest == 0 || widthNorthSouthOrHeight == 0) if (widthEastWest == 0 || widthNorthSouthOrUpDown == 0)
{
throw new IllegalArgumentException("Size 0 quad!"); throw new IllegalArgumentException("Size 0 quad!");
} if (widthEastWest < 0 || widthNorthSouthOrUpDown < 0)
if (widthEastWest < 0 || widthNorthSouthOrHeight < 0)
{
throw new IllegalArgumentException("Negative sized quad!"); throw new IllegalArgumentException("Negative sized quad!");
}
this.x = x; this.x = x;
this.y = y; this.y = y;
this.z = z; this.z = z;
this.widthEastWest = widthEastWest; this.widthEastWest = widthEastWest;
this.widthNorthSouthOrHeight = widthNorthSouthOrHeight; this.widthNorthSouthOrUpDown = widthNorthSouthOrUpDown;
this.color = color; this.color = color;
this.irisBlockMaterialId = irisBlockMaterialId; this.irisBlockMaterialId = irisBlockMaterialId;
this.skyLight = skylight; this.skyLight = skylight;
@@ -242,17 +238,17 @@ public final class BufferQuad
if (mergeDirection == BufferMergeDirectionEnum.EastWest) if (mergeDirection == BufferMergeDirectionEnum.EastWest)
{ {
thisPerpendicularCompareWidth = this.widthEastWest; thisPerpendicularCompareWidth = this.widthEastWest;
thisParallelCompareWidth = this.widthNorthSouthOrHeight; thisParallelCompareWidth = this.widthNorthSouthOrUpDown;
otherPerpendicularCompareWidth = quad.widthEastWest; otherPerpendicularCompareWidth = quad.widthEastWest;
otherParallelCompareWidth = quad.widthNorthSouthOrHeight; otherParallelCompareWidth = quad.widthNorthSouthOrUpDown;
} }
else else
{ {
thisPerpendicularCompareWidth = this.widthNorthSouthOrHeight; thisPerpendicularCompareWidth = this.widthNorthSouthOrUpDown;
thisParallelCompareWidth = this.widthEastWest; thisParallelCompareWidth = this.widthEastWest;
otherPerpendicularCompareWidth = quad.widthNorthSouthOrHeight; otherPerpendicularCompareWidth = quad.widthNorthSouthOrUpDown;
otherParallelCompareWidth = quad.widthEastWest; otherParallelCompareWidth = quad.widthEastWest;
} }
@@ -322,7 +318,7 @@ public final class BufferQuad
// merge the two quads // merge the two quads
if (mergeDirection == BufferMergeDirectionEnum.NorthSouthOrUpDown) if (mergeDirection == BufferMergeDirectionEnum.NorthSouthOrUpDown)
{ {
this.widthNorthSouthOrHeight += quad.widthNorthSouthOrHeight; this.widthNorthSouthOrUpDown += quad.widthNorthSouthOrUpDown;
} }
else // if (mergeDirection == MergeDirection.EastWest) else // if (mergeDirection == MergeDirection.EastWest)
{ {
@@ -23,11 +23,12 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection; import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.level.IDhClientLevel; import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListCheckout; import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.util.ColorUtil; import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil; import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnRenderView; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.coreapi.util.MathUtil; import com.seibel.distanthorizons.coreapi.util.MathUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
@@ -54,7 +55,7 @@ public class ColumnBox
short width, short yHeight, short width, short yHeight,
short minX, short minY, short minZ, short minX, short minY, short minZ,
int color, byte irisBlockMaterialId, byte skyLight, byte blockLight, int color, byte irisBlockMaterialId, byte skyLight, byte blockLight,
long topData, long bottomData, ColumnRenderView[] adjData, boolean[] isAdjDataSameDetailLevel) long topData, long bottomData, ColumnArrayView[] adjData, boolean[] isAdjDataSameDetailLevel)
{ {
//================// //================//
// variable setup // // variable setup //
@@ -122,7 +123,7 @@ public class ColumnBox
&& !isTopTransparent; && !isTopTransparent;
if (!skipTop) if (!skipTop)
{ {
builder.addQuadUp(minX, maxY, minZ, width, ColorUtil.applyShade(color, MC_RENDER.getShade(EDhDirection.UP)), irisBlockMaterialId, skyLightTop, blockLight); builder.addQuadUp(minX, maxY, minZ, width, width, ColorUtil.applyShade(color, MC_RENDER.getShade(EDhDirection.UP)), irisBlockMaterialId, skyLightTop, blockLight);
} }
} }
@@ -133,7 +134,7 @@ public class ColumnBox
&& !isBottomTransparent; && !isBottomTransparent;
if (!skipBottom) if (!skipBottom)
{ {
builder.addQuadDown(minX, minY, minZ, width, ColorUtil.applyShade(color, MC_RENDER.getShade(EDhDirection.DOWN)), irisBlockMaterialId, skyLightBot, blockLight); builder.addQuadDown(minX, minY, minZ, width, width, ColorUtil.applyShade(color, MC_RENDER.getShade(EDhDirection.DOWN)), irisBlockMaterialId, skyLightBot, blockLight);
} }
} }
@@ -145,7 +146,7 @@ public class ColumnBox
// NORTH face // NORTH face
{ {
ColumnRenderView adjCol = adjData[EDhDirection.NORTH.compassIndex]; ColumnArrayView adjCol = adjData[EDhDirection.NORTH.compassIndex];
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.NORTH.compassIndex]; boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.NORTH.compassIndex];
// if the adjacent column is null that generally means the adjacent area hasn't been generated yet // if the adjacent column is null that generally means the adjacent area hasn't been generated yet
if (adjCol == null) if (adjCol == null)
@@ -172,7 +173,7 @@ public class ColumnBox
// SOUTH face // SOUTH face
{ {
ColumnRenderView adjCol = adjData[EDhDirection.SOUTH.compassIndex]; ColumnArrayView adjCol = adjData[EDhDirection.SOUTH.compassIndex];
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.SOUTH.compassIndex]; boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.SOUTH.compassIndex];
if (adjCol == null) if (adjCol == null)
{ {
@@ -197,7 +198,7 @@ public class ColumnBox
// WEST face // WEST face
{ {
ColumnRenderView adjCol = adjData[EDhDirection.WEST.compassIndex]; ColumnArrayView adjCol = adjData[EDhDirection.WEST.compassIndex];
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.WEST.compassIndex]; boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.WEST.compassIndex];
if (adjCol == null) if (adjCol == null)
{ {
@@ -222,7 +223,7 @@ public class ColumnBox
// EAST face // EAST face
{ {
ColumnRenderView adjCol = adjData[EDhDirection.EAST.compassIndex]; ColumnArrayView adjCol = adjData[EDhDirection.EAST.compassIndex];
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.EAST.compassIndex]; boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.EAST.compassIndex];
if (adjCol == null) if (adjCol == null)
{ {
@@ -248,7 +249,7 @@ public class ColumnBox
private static void makeAdjVerticalQuad( private static void makeAdjVerticalQuad(
LodQuadBuilder builder, PhantomArrayListCheckout phantomArrayCheckout, LodQuadBuilder builder, PhantomArrayListCheckout phantomArrayCheckout,
@NotNull ColumnRenderView adjColumnView, boolean adjacentIsSameDetailLevel, int caveCullingMaxY, EDhDirection direction, @NotNull ColumnArrayView adjColumnView, boolean adjacentIsSameDetailLevel, int caveCullingMaxY, EDhDirection direction,
short x, short yMin, short z, short horizontalWidth, short ySize, short x, short yMin, short z, short horizontalWidth, short ySize,
int color, byte irisBlockMaterialId, byte blockLight) int color, byte irisBlockMaterialId, byte blockLight)
{ {
@@ -283,7 +284,7 @@ public class ColumnBox
short yMax = (short) (yMin + ySize); short yMax = (short) (yMin + ySize);
int adjCount = adjColumnView.size; int adjCount = adjColumnView.size();
// Start with the entire range at max light // Start with the entire range at max light
segments.add(YSegmentUtil.encode(yMin, yMax, LodUtil.MAX_MC_LIGHT)); segments.add(YSegmentUtil.encode(yMin, yMax, LodUtil.MAX_MC_LIGHT));
@@ -27,14 +27,15 @@ import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.level.IDhClientLevel; import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListCheckout; import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListPool; import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.util.ColorUtil; import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil; import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnRenderView; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -64,7 +65,7 @@ public class ColumnRenderBufferBuilder
{ {
DhBlockPos minBlockPos = new DhBlockPos(DhSectionPos.getMinCornerBlockX(pos), clientLevel.getLevelWrapper().getMinHeight(), DhSectionPos.getMinCornerBlockZ(pos)); DhBlockPos minBlockPos = new DhBlockPos(DhSectionPos.getMinCornerBlockX(pos), clientLevel.getLevelWrapper().getMinHeight(), DhSectionPos.getMinCornerBlockZ(pos));
LodBufferContainer bufferContainer = new LodBufferContainer(pos, minBlockPos); LodBufferContainer bufferContainer = new LodBufferContainer(pos, minBlockPos);
CompletableFuture<LodBufferContainer> uploadFuture = bufferContainer.tryMakeAndUploadBuffersAsync(quadBuilder); CompletableFuture<LodBufferContainer> uploadFuture = bufferContainer.makeAndUploadBuffersAsync(quadBuilder);
uploadFuture.whenComplete((uploadedBuffer, exception) -> uploadFuture.whenComplete((uploadedBuffer, exception) ->
{ {
// clean up if not uploaded // clean up if not uploaded
@@ -107,36 +108,24 @@ public class ColumnRenderBufferBuilder
//===================// //===================//
// pooled arrays for ColumnBox use // pooled arrays for ColumnBox use
try (PhantomArrayListCheckout phantomArrayCheckout = ARRAY_LIST_POOL.checkoutLongArrays(2); try (PhantomArrayListCheckout phantomArrayCheckout = ARRAY_LIST_POOL.checkoutArrays(0, 0, 2))
ColumnRenderView columnRenderData = ColumnRenderView.getPooled();
ColumnRenderView northAdjView = ColumnRenderView.getPooled();
ColumnRenderView southAdjView = ColumnRenderView.getPooled();
ColumnRenderView eastAdjView = ColumnRenderView.getPooled();
ColumnRenderView westAdjView = ColumnRenderView.getPooled())
{ {
ColumnRenderView[] adjColumnViews = new ColumnRenderView[EDhDirection.CARDINAL_COMPASS.length];
adjColumnViews[EDhDirection.NORTH.compassIndex] = northAdjView;
adjColumnViews[EDhDirection.SOUTH.compassIndex] = southAdjView;
adjColumnViews[EDhDirection.EAST.compassIndex] = eastAdjView;
adjColumnViews[EDhDirection.WEST.compassIndex] = westAdjView;
byte thisDetailLevel = renderSource.getDataDetailLevel(); byte thisDetailLevel = renderSource.getDataDetailLevel();
for (int relX = 0; relX < ColumnRenderSource.WIDTH; relX++) for (int relX = 0; relX < ColumnRenderSource.WIDTH; relX++)
{ {
for (int relZ = 0; relZ < ColumnRenderSource.WIDTH; relZ++) for (int relZ = 0; relZ < ColumnRenderSource.WIDTH; relZ++)
{ {
renderSource.populateColumnView(columnRenderData, relX, relZ); // ignore empty/null columns
ColumnArrayView columnRenderData = renderSource.getVerticalDataPointView(relX, relZ);
// ignore empty columns if (columnRenderData.size() == 0
if (columnRenderData.size == 0 || !RenderDataPointUtil.doesDataPointExist(columnRenderData.get(0))
|| !RenderDataPointUtil.doesDataPointExist(columnRenderData.get(0)) || RenderDataPointUtil.hasZeroHeight(columnRenderData.get(0)))
|| RenderDataPointUtil.hasZeroHeight(columnRenderData.get(0)))
{ {
continue; continue;
} }
//=============// //=============//
// debug limit // // debug limit //
//=============// //=============//
@@ -163,12 +152,7 @@ public class ColumnRenderBufferBuilder
// get adjacent render data columns // // get adjacent render data columns //
//==================================// //==================================//
// clear the old data so we can handle if one of the adjacent columns is missing/empty ColumnArrayView[] adjColumnViews = new ColumnArrayView[EDhDirection.CARDINAL_COMPASS.length];
adjColumnViews[EDhDirection.NORTH.compassIndex].clear();
adjColumnViews[EDhDirection.SOUTH.compassIndex].clear();
adjColumnViews[EDhDirection.EAST.compassIndex].clear();
adjColumnViews[EDhDirection.WEST.compassIndex].clear();
for (EDhDirection direction : EDhDirection.CARDINAL_COMPASS) for (EDhDirection direction : EDhDirection.CARDINAL_COMPASS)
{ {
try try
@@ -176,8 +160,8 @@ public class ColumnRenderBufferBuilder
int xAdj = relX + direction.normal.x; int xAdj = relX + direction.normal.x;
int zAdj = relZ + direction.normal.z; int zAdj = relZ + direction.normal.z;
boolean isCrossRenderSourceBoundary = boolean isCrossRenderSourceBoundary =
(xAdj < 0 || xAdj >= ColumnRenderSource.WIDTH) (xAdj < 0 || xAdj >= ColumnRenderSource.WIDTH) ||
|| (zAdj < 0 || zAdj >= ColumnRenderSource.WIDTH); (zAdj < 0 || zAdj >= ColumnRenderSource.WIDTH);
ColumnRenderSource adjRenderSource; ColumnRenderSource adjRenderSource;
byte adjDetailLevel; byte adjDetailLevel;
@@ -246,7 +230,7 @@ public class ColumnRenderBufferBuilder
LodUtil.assertNotReach("Mismatch between adjacent detail level ["+adjDetailLevel+"] and this render source's detail level ["+thisDetailLevel+"]. Detail levels should be adj >= this."); LodUtil.assertNotReach("Mismatch between adjacent detail level ["+adjDetailLevel+"] and this render source's detail level ["+thisDetailLevel+"]. Detail levels should be adj >= this.");
} }
adjRenderSource.populateColumnView(adjColumnViews[direction.compassIndex], xAdj, zAdj); adjColumnViews[direction.compassIndex] = adjRenderSource.getVerticalDataPointView(xAdj, zAdj);
} }
catch (RuntimeException e) catch (RuntimeException e)
{ {
@@ -260,14 +244,16 @@ public class ColumnRenderBufferBuilder
// build this render column // // build this render column //
//==========================// //==========================//
for (int i = 0; i < columnRenderData.size; i++) ColumnRenderSource.DebugSourceFlag debugSourceFlag = renderSource.debugGetFlag(relX, relZ);
for (int i = 0; i < columnRenderData.size(); i++)
{ {
// can be uncommented to limit which vertical LOD is generated // can be uncommented to limit which vertical LOD is generated
if (Config.Client.Advanced.Debugging.ColumnBuilderDebugging.columnBuilderDebugEnable.get()) if (Config.Client.Advanced.Debugging.ColumnBuilderDebugging.columnBuilderDebugEnable.get())
{ {
int wantedColumnIndex = Config.Client.Advanced.Debugging.ColumnBuilderDebugging.columnBuilderDebugColumnIndex.get(); int wantedColumnIndex = Config.Client.Advanced.Debugging.ColumnBuilderDebugging.columnBuilderDebugColumnIndex.get();
if (wantedColumnIndex >= 0 if (wantedColumnIndex >= 0
&& i != wantedColumnIndex) && i != wantedColumnIndex)
{ {
continue; continue;
} }
@@ -277,21 +263,22 @@ public class ColumnRenderBufferBuilder
// If the data is not render-able (Void or non-existing) we stop since there is // If the data is not render-able (Void or non-existing) we stop since there is
// no data left in this position // no data left in this position
if (RenderDataPointUtil.hasZeroHeight(data) if (RenderDataPointUtil.hasZeroHeight(data)
|| !RenderDataPointUtil.doesDataPointExist(data)) || !RenderDataPointUtil.doesDataPointExist(data))
{ {
break; break;
} }
long topDataPoint = (i - 1) >= 0 ? columnRenderData.get(i - 1) : RenderDataPointUtil.EMPTY_DATA; long topDataPoint = (i - 1) >= 0 ? columnRenderData.get(i - 1) : RenderDataPointUtil.EMPTY_DATA;
long bottomDataPoint = (i + 1) < columnRenderData.size ? columnRenderData.get(i + 1) : RenderDataPointUtil.EMPTY_DATA; long bottomDataPoint = (i + 1) < columnRenderData.size() ? columnRenderData.get(i + 1) : RenderDataPointUtil.EMPTY_DATA;
addRenderDataPointToBuilder( addRenderDataPointToBuilder(
clientLevel, phantomArrayCheckout, clientLevel, phantomArrayCheckout,
data, topDataPoint, bottomDataPoint, data, topDataPoint, bottomDataPoint,
adjColumnViews, isSameDetailLevel, adjColumnViews, isSameDetailLevel,
thisDetailLevel, relX, relZ, thisDetailLevel, relX, relZ,
quadBuilder); quadBuilder, debugSourceFlag);
} }
}// for z }// for z
}// for x }// for x
}// phantom checkout }// phantom checkout
@@ -301,9 +288,9 @@ public class ColumnRenderBufferBuilder
private static void addRenderDataPointToBuilder( private static void addRenderDataPointToBuilder(
IDhClientLevel clientLevel, PhantomArrayListCheckout phantomArrayCheckout, IDhClientLevel clientLevel, PhantomArrayListCheckout phantomArrayCheckout,
long renderData, long topRenderData, long bottomRenderData, long renderData, long topRenderData, long bottomRenderData,
ColumnRenderView[] adjColumnViews, boolean[] isSameDetailLevel, ColumnArrayView[] adjColumnViews, boolean[] isSameDetailLevel,
byte detailLevel, int renderSourceOffsetPosX, int renderSourceOffsetPosZ, byte detailLevel, int renderSourceOffsetPosX, int renderSourceOffsetPosZ,
LodQuadBuilder quadBuilder) LodQuadBuilder quadBuilder, ColumnRenderSource.DebugSourceFlag debugSource)
{ {
long sectionPos = DhSectionPos.encode(detailLevel, renderSourceOffsetPosX, renderSourceOffsetPosZ); long sectionPos = DhSectionPos.encode(detailLevel, renderSourceOffsetPosX, renderSourceOffsetPosZ);
@@ -328,13 +315,13 @@ public class ColumnRenderBufferBuilder
int color; int color;
boolean fullBright = false; boolean fullBright = false;
EDhApiDebugRendering debugging = Config.Client.Advanced.Debugging.debugRenderingColors.get(); EDhApiDebugRendering debugging = Config.Client.Advanced.Debugging.debugRendering.get();
switch (debugging) switch (debugging)
{ {
case OFF: case OFF:
{ {
float saturationMultiplier = Config.Client.Advanced.Graphics.Quality.saturationMultiplier.get(); float saturationMultiplier = Config.Client.Advanced.Graphics.Quality.saturationMultiplier.get().floatValue();
float brightnessMultiplier = Config.Client.Advanced.Graphics.Quality.brightnessMultiplier.get(); float brightnessMultiplier = Config.Client.Advanced.Graphics.Quality.brightnessMultiplier.get().floatValue();
if (saturationMultiplier == 1.0 && brightnessMultiplier == 1.0) if (saturationMultiplier == 1.0 && brightnessMultiplier == 1.0)
{ {
color = RenderDataPointUtil.getColor(renderData); color = RenderDataPointUtil.getColor(renderData);
@@ -420,6 +407,12 @@ public class ColumnRenderBufferBuilder
fullBright = true; fullBright = true;
break; break;
} }
case SHOW_RENDER_SOURCE_FLAG:
{
color = debugSource == null ? ColorUtil.RED : debugSource.color;
fullBright = true;
break;
}
default: default:
throw new IllegalArgumentException("Unknown debug mode: " + debugging); throw new IllegalArgumentException("Unknown debug mode: " + debugging);
} }
@@ -1,92 +0,0 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class IndexBufferBuilder
{
//==========//
// building //
//==========//
//region
/** Buffer should be freed by {@link MemoryUtil#memFree} */
public static ByteBuffer createBuffer(int quadCount)
{
int indexCount = quadCount * 6; // 2 triangles per quad
ByteBuffer buffer = MemoryUtil.memAlloc(indexCount * Integer.BYTES);
buffer.order(ByteOrder.nativeOrder());
buildBufferInt(quadCount, buffer);
return buffer;
}
private static void buildBufferByte(int quadCount, ByteBuffer buffer)
{
for (int i = 0; i < quadCount; i++)
{
int vIndex = i * 4;
// First triangle
buffer.put((byte) (vIndex));
buffer.put((byte) (vIndex + 1));
buffer.put((byte) (vIndex + 2));
// Second triangle
buffer.put((byte) (vIndex + 2));
buffer.put((byte) (vIndex + 3));
buffer.put((byte) (vIndex));
}
if (buffer.hasRemaining())
{
throw new IllegalStateException("QuadElementBuffer is not full somehow after building");
}
buffer.rewind();
}
private static void buildBufferShort(int quadCount, ByteBuffer buffer)
{
for (int i = 0; i < quadCount; i++)
{
int vIndex = i * 4;
// First triangle
buffer.putShort((short) (vIndex));
buffer.putShort((short) (vIndex + 1));
buffer.putShort((short) (vIndex + 2));
// Second triangle
buffer.putShort((short) (vIndex + 2));
buffer.putShort((short) (vIndex + 3));
buffer.putShort((short) (vIndex));
}
if (buffer.hasRemaining())
{
throw new IllegalStateException("QuadElementBuffer is not full somehow after building");
}
buffer.rewind();
}
private static void buildBufferInt(int quadCount, ByteBuffer buffer)
{
for (int i = 0; i < quadCount; i++)
{
int vIndex = i * 4;
// First triangle
buffer.putInt(vIndex);
buffer.putInt(vIndex + 1);
buffer.putInt(vIndex + 2);
// Second triangle
buffer.putInt(vIndex + 2);
buffer.putInt(vIndex + 3);
buffer.putInt(vIndex);
}
if (buffer.hasRemaining())
{
throw new IllegalStateException("QuadElementBuffer is not full somehow after building");
}
buffer.rewind();
}
//endregion
}
@@ -19,25 +19,19 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding; package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler; import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.util.ExceptionUtil; import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.util.objects.StatsMap;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.AbstractDhRenderApiDefinition; import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.ILodContainerUniformBufferWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.IVertexBufferWrapper;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
/** /**
* Java representation of one or more OpenGL buffers for rendering. * Java representation of one or more OpenGL buffers for rendering.
@@ -48,8 +42,12 @@ public class LodBufferContainer implements AutoCloseable
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class); /** number of bytes a single quad takes */
private static final AbstractDhRenderApiDefinition RENDER_DEF = SingletonInjector.INSTANCE.get(AbstractDhRenderApiDefinition.class); public static final int QUADS_BYTE_SIZE = LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 4;
/** how big a single VBO can be in bytes */
public static final int MAX_VBO_BYTE_SIZE = 10 * 1024 * 1024; // 10 MB
public static final int MAX_QUADS_PER_BUFFER = MAX_VBO_BYTE_SIZE / QUADS_BYTE_SIZE;
public static final int FULL_SIZED_BUFFER = MAX_QUADS_PER_BUFFER * QUADS_BYTE_SIZE;
/** the position closest to minimum X/Z infinity and the level's lowest Y */ /** the position closest to minimum X/Z infinity and the level's lowest Y */
@@ -58,224 +56,117 @@ public class LodBufferContainer implements AutoCloseable
public boolean buffersUploaded = false; public boolean buffersUploaded = false;
public IVertexBufferWrapper[] vboOpaqueWrappers; public GLVertexBuffer[] vbos;
public IVertexBufferWrapper[] vboTransparentWrappers; public GLVertexBuffer[] vbosTransparent;
public ILodContainerUniformBufferWrapper uniformContainer = WRAPPER_FACTORY.createLodContainerUniformWrapper(); private CompletableFuture<LodBufferContainer> uploadFuture = null;
private final AtomicReference<CompletableFuture<LodBufferContainer>> uploadFutureRef = new AtomicReference<>(null);
//==============// //==============//
// constructors // // constructors //
//==============// //==============//
//region
public LodBufferContainer(long pos, DhBlockPos minCornerBlockPos) public LodBufferContainer(long pos, DhBlockPos minCornerBlockPos)
{ {
this.pos = pos; this.pos = pos;
this.minCornerBlockPos = minCornerBlockPos; this.minCornerBlockPos = minCornerBlockPos;
this.vboOpaqueWrappers = new IVertexBufferWrapper[0]; this.vbos = new GLVertexBuffer[0];
this.vboTransparentWrappers = new IVertexBufferWrapper[0]; this.vbosTransparent = new GLVertexBuffer[0];
this.uniformContainer.createUniformData(this);
} }
//endregion
//==================// //==================//
// buffer uploading // // buffer uploading //
//==================// //==================//
//region
/** Should be run on a DH thread. */ /** Should be run on a DH thread. */
public synchronized CompletableFuture<LodBufferContainer> tryMakeAndUploadBuffersAsync(LodQuadBuilder builder) public synchronized CompletableFuture<LodBufferContainer> makeAndUploadBuffersAsync(LodQuadBuilder builder)
{ {
//================//
// handle futures //
//================//
//region
// separate variable to prevent race condition when checking null // separate variable to prevent race condition when checking null
CompletableFuture<LodBufferContainer> oldFuture = this.uploadFutureRef.get(); CompletableFuture<LodBufferContainer> future = this.uploadFuture;
if (oldFuture != null) if (future != null)
{ {
// upload already in process // upload already in process
return oldFuture; return future;
} }
// new upload needed // new upload needed
CompletableFuture<LodBufferContainer> future = new CompletableFuture<>(); future = new CompletableFuture<>();
future.handle((lodBufferContainer, throwable) -> this.uploadFuture = future;
{
if (!this.uploadFutureRef.compareAndSet(future, null))
{
LOGGER.warn("upload future ref changed for pos ["+DhSectionPos.toString(this.pos)+"].");
}
return null;
});
if (!this.uploadFutureRef.compareAndSet(null, future))
{
oldFuture = this.uploadFutureRef.get();
LodUtil.assertTrue(oldFuture != null, "Concurrency error");
return oldFuture;
}
//endregion
//================// // make the buffers
// create buffers //
//================//
//region
ArrayList<ByteBuffer> opaqueBuffers = builder.makeOpaqueVertexBuffers(); ArrayList<ByteBuffer> opaqueBuffers = builder.makeOpaqueVertexBuffers();
ArrayList<ByteBuffer> transparentBuffers = builder.makeTransparentVertexBuffers(); ArrayList<ByteBuffer> transparentBuffers = builder.makeTransparentVertexBuffers();
this.vboOpaqueWrappers = resizeWrapperArray(this.vboOpaqueWrappers, opaqueBuffers.size()); this.vbos = resizeBuffer(this.vbos, opaqueBuffers.size());
this.vboTransparentWrappers = resizeWrapperArray(this.vboTransparentWrappers, transparentBuffers.size()); this.vbosTransparent = resizeBuffer(this.vbosTransparent, transparentBuffers.size());
// mac requires separate IBO objects for each VBO when using OpenGL,
// all other OS's can share a single IBO for quicker loading times
boolean useSingleIbo = RENDER_DEF.useSingleIbo();
@Nullable ArrayList<ByteBuffer> opaqueIndexBuffers = useSingleIbo ? null : this.createIndexBuffers(opaqueBuffers);
@Nullable ArrayList<ByteBuffer> transparentIndexBuffers = useSingleIbo ? null : this.createIndexBuffers(transparentBuffers);
//endregion
// upload on MC's render thread
//================// GLProxy.queueRunningOnRenderThread(() ->
// upload buffers //
//================//
//region
try
{ {
//=============// try
// create VBOs //
//=============//
CompletableFuture<Void> createOpaqueFuture = createBufferWrappersAsync(future, this.vboOpaqueWrappers, opaqueBuffers);
CompletableFuture<Void> createTransparentFuture = createBufferWrappersAsync(future, this.vboTransparentWrappers, transparentBuffers);
CompletableFuture<Void> createFuture = CompletableFuture.allOf(createOpaqueFuture, createTransparentFuture);
createFuture.exceptionally((Throwable e) ->
{ {
// create VBOs failed // // skip this event if requested
if (Thread.interrupted()
if (!ExceptionUtil.isShutdownException(e)) || this.uploadFuture.isCancelled())
{ {
LOGGER.error("Unexpected issue creating buffer [" + this.minCornerBlockPos + "], error: [" + e.getMessage() + "].", e); throw new InterruptedException();
} }
future.completeExceptionally(e);
return null;
});
createFuture.thenRun(() ->
{
//=============//
// upload VBOs //
//=============//
CompletableFuture<Void> opaqueFuture = uploadBuffersAsync(future, this.vboOpaqueWrappers, opaqueBuffers, opaqueIndexBuffers); EDhApiGpuUploadMethod gpuUploadMethod = GLProxy.getInstance().getGpuUploadMethod();
CompletableFuture<Void> transparentFuture = uploadBuffersAsync(future, this.vboTransparentWrappers, transparentBuffers, transparentIndexBuffers);
CompletableFuture<Void> uploadFuture = CompletableFuture.allOf(opaqueFuture, transparentFuture); // upload on the render thread
uploadFuture.exceptionally((Throwable e) -> uploadBuffersDirect(this.vbos, opaqueBuffers, gpuUploadMethod);
{ uploadBuffersDirect(this.vbosTransparent, transparentBuffers, gpuUploadMethod);
// upload failed // this.buffersUploaded = true;
if (!ExceptionUtil.isShutdownException(e)) // success
{ this.uploadFuture.complete(this);
LOGGER.error("Unexpected issue uploading buffer [" + this.minCornerBlockPos + "], error: [" + e.getMessage() + "].", e); this.uploadFuture = null;
} }
future.completeExceptionally(e); catch (InterruptedException ignore)
return null; {
}); this.uploadFuture.complete(this);
uploadFuture.thenRun(() -> this.uploadFuture = null;
{ }
// upload success / catch (Exception e)
{
this.buffersUploaded = true; LOGGER.error("Unexpected issue uploading buffer ["+this.minCornerBlockPos +"], error: ["+e.getMessage()+"].", e);
future.complete(this);
}); this.uploadFuture.completeExceptionally(e);
}); this.uploadFuture = null;
} }
catch (Exception e) finally
{ {
if (!ExceptionUtil.isShutdownException(e)) // all the buffers must be manually freed to prevent memory leaks
{
LOGGER.error("Unexpected issue prepping buffer uploading [" + this.minCornerBlockPos + "], error: [" + e.getMessage() + "].", e); for (ByteBuffer buffer : opaqueBuffers)
{
MemoryUtil.memFree(buffer);
}
for (ByteBuffer buffer : transparentBuffers)
{
MemoryUtil.memFree(buffer);
}
} }
future.completeExceptionally(e);
}
//================//
// buffer cleanup //
//================//
future.whenComplete((LodBufferContainer lodBufferContainer, Throwable throwable) ->
{
// all the buffers must be manually freed to prevent memory leaks
tryFreeByteBufferList(opaqueBuffers);
tryFreeByteBufferList(transparentBuffers);
tryFreeByteBufferList(opaqueIndexBuffers);
tryFreeByteBufferList(transparentIndexBuffers);
}); });
//endregion
return future; return future;
} }
private static void tryFreeByteBufferList(@Nullable ArrayList<ByteBuffer> list) private static GLVertexBuffer[] resizeBuffer(GLVertexBuffer[] vbos, int newSize)
{
if (list != null)
{
for (ByteBuffer buffer : list)
{
MemoryUtil.memFree(buffer);
}
}
}
private ArrayList<ByteBuffer> createIndexBuffers(ArrayList<ByteBuffer> vertexBuffers)
{
ArrayList<ByteBuffer> indexBuffers = new ArrayList<>();
for (int i = 0; i < vertexBuffers.size(); i++)
{
ByteBuffer buffer = vertexBuffers.get(i);
int size = buffer.limit() - buffer.position();
int vertexCount = size / LodQuadBuilder.BYTES_PER_VERTEX;
ByteBuffer indexBuffer = IndexBufferBuilder.createBuffer(vertexCount);
indexBuffers.add(indexBuffer);
}
return indexBuffers;
}
private static IVertexBufferWrapper[] resizeWrapperArray(IVertexBufferWrapper[] vbos, int newSize)
{ {
if (vbos.length == newSize) if (vbos.length == newSize)
{ {
return vbos; return vbos;
} }
IVertexBufferWrapper[] newVbos = new IVertexBufferWrapper[newSize]; GLVertexBuffer[] newVbos = new GLVertexBuffer[newSize];
System.arraycopy(vbos, 0, newVbos, 0, Math.min(vbos.length, newSize)); System.arraycopy(vbos, 0, newVbos, 0, Math.min(vbos.length, newSize));
if (newSize < vbos.length) if (newSize < vbos.length)
{ {
@@ -289,235 +180,109 @@ public class LodBufferContainer implements AutoCloseable
} }
return newVbos; return newVbos;
} }
private static void uploadBuffersDirect(
private static CompletableFuture<Void> createBufferWrappersAsync( GLVertexBuffer[] vbos, ArrayList<ByteBuffer> byteBuffers,
CompletableFuture<LodBufferContainer> parentFuture, EDhApiGpuUploadMethod uploadMethod) throws InterruptedException
IVertexBufferWrapper[] vboWrappers, ArrayList<ByteBuffer> vertexBuffers)
{ {
ArrayList<CompletableFuture<Void>> createVboFutureList = new ArrayList<>();
for (int i = 0; i < vertexBuffers.size(); i++)
{
if (i >= vboWrappers.length)
{
throw new RuntimeException("Too many vertex buffers!!");
}
if (vboWrappers[i] == null)
{
final int finalVboIndex = i;
CompletableFuture<Void> future = new CompletableFuture<>();
createVboFutureList.add(future);
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer Setup", () ->
{
try
{
// skip this event if requested
if (Thread.interrupted()
|| parentFuture.isCancelled())
{
throw new InterruptedException();
}
vboWrappers[finalVboIndex] = WRAPPER_FACTORY.createVboWrapper("distantHorizons:McLodRenderer");
future.complete(null);
}
catch (Exception e)
{
future.completeExceptionally(e);
}
});
}
}
if (createVboFutureList.size() == 0)
{
return CompletableFuture.completedFuture(null);
}
CompletableFuture<?>[] futureArray = new CompletableFuture[createVboFutureList.size()];
for (int i = 0; i < createVboFutureList.size(); i++)
{
futureArray[i] = createVboFutureList.get(i);
}
return CompletableFuture.allOf(futureArray);
}
/** Index buffers should be null if {@link AbstractDhRenderApiDefinition#useSingleIbo()} returns true. */
private static CompletableFuture<Void> uploadBuffersAsync(
CompletableFuture<LodBufferContainer> parentFuture,
IVertexBufferWrapper[] vboWrappers,
ArrayList<ByteBuffer> vertexBuffers, @Nullable ArrayList<ByteBuffer> indexBuffers
)
{
ArrayList<CompletableFuture<Void>> uploadFutureList = new ArrayList<>();
int vboIndex = 0; int vboIndex = 0;
for (int i = 0; i < vertexBuffers.size(); i++) for (int i = 0; i < byteBuffers.size(); i++)
{ {
if (vboIndex >= vboWrappers.length) if (vboIndex >= vbos.length)
{ {
throw new RuntimeException("Too many vertex buffers!!"); throw new RuntimeException("Too many vertex buffers!!");
} }
// get or create the VBO
// final variables for use in lambdas // if (vbos[vboIndex] == null)
final int finalVboIndex = vboIndex;
final IVertexBufferWrapper finalVboWrapper = vboWrappers[vboIndex];
final ByteBuffer finalVertexBuffer = vertexBuffers.get(vboIndex);
// index buffers are optional
@Nullable final ByteBuffer finalIndexBuffer = (indexBuffers != null) ? indexBuffers.get(vboIndex) : null;
final int finalVertexCount = vertexByteBufferToVertexCount(finalVertexBuffer);
//===============//
// vertex upload //
//===============//
//region
CompletableFuture<Void> vertexUploadFuture = new CompletableFuture<>();
uploadFutureList.add(vertexUploadFuture);
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer VBO Upload", () ->
{ {
try vbos[vboIndex] = new GLVertexBuffer(uploadMethod.useBufferStorage);
{
// skip this event if requested
if (Thread.interrupted()
|| parentFuture.isCancelled())
{
throw new InterruptedException();
}
try
{
finalVboWrapper.uploadVertexBuffer(finalVertexBuffer, finalVertexCount);
vertexUploadFuture.complete(null);
}
catch (Exception e)
{
vboWrappers[finalVboIndex] = null;
finalVboWrapper.close();
LOGGER.error("Failed to upload buffer. Error: [" + e.getMessage() + "].", e);
}
}
catch (Exception e)
{
vertexUploadFuture.completeExceptionally(e);
}
});
//endregion
//==============//
// index upload //
//==============//
//region
if (finalIndexBuffer != null)
{
CompletableFuture<Void> indexUploadFuture = new CompletableFuture<>();
uploadFutureList.add(indexUploadFuture);
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer IBO Upload", () ->
{
try
{
// skip this event if requested
if (Thread.interrupted()
|| parentFuture.isCancelled())
{
throw new InterruptedException();
}
finalVboWrapper.uploadIndexBuffer(finalIndexBuffer, finalVertexCount);
indexUploadFuture.complete(null);
}
catch (Exception e)
{
indexUploadFuture.completeExceptionally(e);
}
});
} }
//endregion GLVertexBuffer vbo = vbos[vboIndex];
ByteBuffer buffer = byteBuffers.get(i);
int size = buffer.limit() - buffer.position();
try
{
vbo.bind();
vbo.uploadBuffer(buffer, size / LodUtil.LOD_VERTEX_FORMAT.getByteSize(), uploadMethod, FULL_SIZED_BUFFER);
}
catch (Exception e)
{
vbos[vboIndex] = null;
vbo.close();
LOGGER.error("Failed to upload buffer. Error: ["+e.getMessage()+"].", e);
}
vboIndex++; vboIndex++;
} }
if (vboIndex < vboWrappers.length) if (vboIndex < vbos.length)
{ {
throw new RuntimeException("Too few vertex buffers!!"); throw new RuntimeException("Too few vertex buffers!!");
} }
// merge futures //
CompletableFuture<?>[] futureArray = new CompletableFuture[uploadFutureList.size()];
for (int i = 0; i < uploadFutureList.size(); i++)
{
futureArray[i] = uploadFutureList.get(i);
}
return CompletableFuture.allOf(futureArray);
} }
//endregion
//================// //================//
// helper methods // // helper methods //
//================// //================//
//region
private static int vertexByteBufferToVertexCount(ByteBuffer buffer)
{
int size = buffer.limit() - buffer.position();
int vertexCount = size / LodQuadBuilder.BYTES_PER_VERTEX;
return vertexCount;
}
/** can be used when debugging */ /** can be used when debugging */
public boolean hasNonNullVbos() { return this.vboOpaqueWrappers != null || this.vboTransparentWrappers != null; } public boolean hasNonNullVbos() { return this.vbos != null || this.vbosTransparent != null; }
/** can be used when debugging */ /** can be used when debugging */
public int vboBufferCount() public int vboBufferCount()
{ {
int count = 0; int count = 0;
if (this.vboOpaqueWrappers != null) if (this.vbos != null)
{ {
count += this.vboOpaqueWrappers.length; count += this.vbos.length;
} }
if (this.vboTransparentWrappers != null) if (this.vbosTransparent != null)
{ {
count += this.vboTransparentWrappers.length; count += this.vbosTransparent.length;
} }
return count; return count;
} }
//endregion public boolean uploadInProgress() { return this.uploadFuture != null; }
public void debugDumpStats(StatsMap statsMap)
{
statsMap.incStat("RenderBuffers");
statsMap.incStat("SimpleRenderBuffers");
for (GLVertexBuffer vertexBuffer : vbos)
{
if (vertexBuffer != null)
{
statsMap.incStat("VBOs");
if (vertexBuffer.getSize() == FULL_SIZED_BUFFER)
{
statsMap.incStat("FullsizedVBOs");
}
if (vertexBuffer.getSize() == 0)
{
GLProxy.LOGGER.warn("VBO with size 0");
}
statsMap.incBytesStat("TotalUsage", vertexBuffer.getSize());
}
}
}
//================// //================//
// base overrides // // base overrides //
//================// //================//
//region
/** /**
* This method is called when object is no longer in use. * This method is called when object is no longer in use.
@@ -530,30 +295,21 @@ public class LodBufferContainer implements AutoCloseable
{ {
this.buffersUploaded = false; this.buffersUploaded = false;
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer Close", () -> for (GLVertexBuffer buffer : this.vbos)
{ {
for (IVertexBufferWrapper buffer : this.vboOpaqueWrappers) if (buffer != null)
{ {
if (buffer != null) buffer.destroyAsync();
{
buffer.close();
}
} }
}
for (IVertexBufferWrapper buffer : this.vboTransparentWrappers)
for (GLVertexBuffer buffer : this.vbosTransparent)
{
if (buffer != null)
{ {
if (buffer != null) buffer.destroyAsync();
{
buffer.close();
}
} }
}
this.uniformContainer.close();
});
} }
//endregion
} }
@@ -31,8 +31,10 @@ import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.ColorUtil; import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
/** /**
@@ -56,60 +58,55 @@ public class LodQuadBuilder
private final EDhApiDebugRendering debugRenderingMode; private final EDhApiDebugRendering debugRenderingMode;
private final EDhApiGrassSideRendering grassSideRenderingMode; private final EDhApiGrassSideRendering grassSideRenderingMode;
/** the number of bytes for a single vertex */
public static final int BYTES_PER_VERTEX = 16;
public static final int BYTES_PER_QUAD = BYTES_PER_VERTEX * 4;
public static final int[][][] DIRECTION_VERTEX_IBO_QUAD = new int[][][] public static final int[][][] DIRECTION_VERTEX_IBO_QUAD = new int[][][]
///region {
{ // X,Z //
// X,Z // { // UP
{ // UP {1, 0}, // 0
{1, 0}, // 0 {1, 1}, // 1
{1, 1}, // 1 {0, 1}, // 2
{0, 1}, // 2 {0, 0}, // 3
{0, 0}, // 3 },
}, { // DOWN
{ // DOWN {0, 0}, // 0
{0, 0}, // 0 {0, 1}, // 1
{0, 1}, // 1 {1, 1}, // 2
{1, 1}, // 2 {1, 0}, // 3
{1, 0}, // 3 },
},
// X,Y //
// X,Y // { // NORTH
{ // NORTH {0, 0}, // 0
{0, 0}, // 0 {0, 1}, // 1
{0, 1}, // 1 {1, 1}, // 2
{1, 1}, // 2
{1, 0}, // 3
{1, 0}, // 3 },
}, { // SOUTH
{ // SOUTH {1, 0}, // 0
{1, 0}, // 0 {1, 1}, // 1
{1, 1}, // 1 {0, 1}, // 2
{0, 1}, // 2
{0, 0}, // 3
{0, 0}, // 3 },
},
// Z,Y //
// Z,Y // { // WEST
{ // WEST {0, 0}, // 0
{0, 0}, // 0 {1, 0}, // 1
{1, 0}, // 1 {1, 1}, // 2
{1, 1}, // 2
{0, 1}, // 3
{0, 1}, // 3 },
}, { // EAST
{ // EAST {0, 1}, // 0
{0, 1}, // 0 {1, 1}, // 1
{1, 1}, // 1 {1, 0}, // 2
{1, 0}, // 2
{0, 0}, // 3
{0, 0}, // 3 },
}, };
};
///endregion
private int premergeCount = 0; private int premergeCount = 0;
@@ -118,7 +115,6 @@ public class LodQuadBuilder
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
//region
public LodQuadBuilder(boolean doTransparency, IClientLevelWrapper clientLevelWrapper) public LodQuadBuilder(boolean doTransparency, IClientLevelWrapper clientLevelWrapper)
{ {
@@ -131,24 +127,21 @@ public class LodQuadBuilder
this.clientLevelWrapper = clientLevelWrapper; this.clientLevelWrapper = clientLevelWrapper;
this.debugRenderingMode = Config.Client.Advanced.Debugging.debugRenderingColors.get(); this.debugRenderingMode = Config.Client.Advanced.Debugging.debugRendering.get();
this.grassSideRenderingMode = Config.Client.Advanced.Graphics.Quality.grassSideRendering.get(); this.grassSideRenderingMode = Config.Client.Advanced.Graphics.Quality.grassSideRendering.get();
} }
//endregion
//===========// //===========//
// add quads // // add quads //
//===========// //===========//
///region
public void addQuadAdj( public void addQuadAdj(
EDhDirection dir, EDhDirection dir,
short x, short y, short z, short x, short y, short z,
short width, short height, short widthEastWest, short widthNorthSouthOrUpDown,
int color, byte irisBlockMaterialId, byte skyLight, byte blockLight) int color, byte irisBlockMaterialId, byte skyLight, byte blockLight)
{ {
if (dir == EDhDirection.DOWN) if (dir == EDhDirection.DOWN)
@@ -167,7 +160,7 @@ public class LodQuadBuilder
quadList = this.opaqueQuads[dir.ordinal()]; quadList = this.opaqueQuads[dir.ordinal()];
} }
BufferQuad quad = new BufferQuad(x, y, z, width, height, color, irisBlockMaterialId, skyLight, blockLight, dir); BufferQuad quad = new BufferQuad(x, y, z, widthEastWest, widthNorthSouthOrUpDown, color, irisBlockMaterialId, skyLight, blockLight, dir);
if (!quadList.isEmpty() if (!quadList.isEmpty()
&& ( && (
quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.EastWest) quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.EastWest)
@@ -182,35 +175,32 @@ public class LodQuadBuilder
} }
// XZ // XZ
public void addQuadUp(short minX, short maxY, short minZ, short width, int color, byte irisBlockMaterialId, byte skylight, byte blocklight) public void addQuadUp(short minX, short maxY, short minZ, short widthEastWest, short widthNorthSouthOrUpDown, int color, byte irisBlockMaterialId, byte skylight, byte blocklight) // TODO argument names are wrong
{ {
boolean isTransparent = (this.doTransparency && ColorUtil.getAlpha(color) < 255); boolean isTransparent = (this.doTransparency && ColorUtil.getAlpha(color) < 255);
ArrayList<BufferQuad> quadList = isTransparent ArrayList<BufferQuad> quadList = isTransparent
? this.transparentQuads[EDhDirection.UP.ordinal()] ? this.transparentQuads[EDhDirection.UP.ordinal()]
: this.opaqueQuads[EDhDirection.UP.ordinal()]; : this.opaqueQuads[EDhDirection.UP.ordinal()];
BufferQuad quad = new BufferQuad(minX, maxY, minZ, width, width, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.UP); BufferQuad quad = new BufferQuad(minX, maxY, minZ, widthEastWest, widthNorthSouthOrUpDown, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.UP);
quadList.add(quad); quadList.add(quad);
} }
public void addQuadDown(short x, short y, short z, short width, int color, byte irisBlockMaterialId, byte skylight, byte blocklight) public void addQuadDown(short x, short y, short z, short width, short wz, int color, byte irisBlockMaterialId, byte skylight, byte blocklight)
{ {
ArrayList<BufferQuad> quadArray = (this.doTransparency && ColorUtil.getAlpha(color) < 255) ArrayList<BufferQuad> quadArray = (this.doTransparency && ColorUtil.getAlpha(color) < 255)
? this.transparentQuads[EDhDirection.DOWN.ordinal()] ? this.transparentQuads[EDhDirection.DOWN.ordinal()]
: this.opaqueQuads[EDhDirection.DOWN.ordinal()]; : this.opaqueQuads[EDhDirection.DOWN.ordinal()];
BufferQuad quad = new BufferQuad(x, y, z, width, width, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.DOWN); BufferQuad quad = new BufferQuad(x, y, z, width, wz, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.DOWN);
quadArray.add(quad); quadArray.add(quad);
} }
///endregion
//=================// //=================//
// data finalizing // // data finalizing //
//=================// //=================//
///region
/** Uses Greedy meshing to merge this builder's Quads. */ /** Uses Greedy meshing to merge this builder's Quads. */
public void mergeQuads() public void mergeQuads()
@@ -279,14 +269,11 @@ public class LodQuadBuilder
return mergeCount; return mergeCount;
} }
///endregion
//==============// //==============//
// buffer setup // // buffer setup //
//==============// //==============//
///region
public ArrayList<ByteBuffer> makeOpaqueVertexBuffers() { return this.makeVertexBuffers(this.opaqueQuads); } public ArrayList<ByteBuffer> makeOpaqueVertexBuffers() { return this.makeVertexBuffers(this.opaqueQuads); }
public ArrayList<ByteBuffer> makeTransparentVertexBuffers() { return this.makeVertexBuffers(this.transparentQuads); } public ArrayList<ByteBuffer> makeTransparentVertexBuffers() { return this.makeVertexBuffers(this.transparentQuads); }
@@ -308,10 +295,9 @@ public class LodQuadBuilder
{ {
// if this is the first iteration or the buffer is full, // if this is the first iteration or the buffer is full,
// create a new buffer // create a new buffer
if (buffer == null if (buffer == null || !buffer.hasRemaining())
|| buffer.remaining() < BYTES_PER_QUAD)
{ {
buffer = MemoryUtil.memAlloc(getMaxBufferByteSize()); buffer = MemoryUtil.memAlloc(LodBufferContainer.FULL_SIZED_BUFFER);
byteBufferList.add(buffer); byteBufferList.add(buffer);
} }
@@ -333,7 +319,7 @@ public class LodQuadBuilder
{ {
int[][] quadBase = DIRECTION_VERTEX_IBO_QUAD[quad.direction.ordinal()]; int[][] quadBase = DIRECTION_VERTEX_IBO_QUAD[quad.direction.ordinal()];
short widthEastWest = quad.widthEastWest; short widthEastWest = quad.widthEastWest;
short widthNorthSouth = quad.widthNorthSouthOrHeight; short widthNorthSouth = quad.widthNorthSouthOrUpDown;
byte normalIndex = (byte) quad.direction.ordinal(); byte normalIndex = (byte) quad.direction.ordinal();
EDhDirection.Axis axis = quad.direction.axis; EDhDirection.Axis axis = quad.direction.axis;
for (int i = 0; i < quadBase.length; i++) for (int i = 0; i < quadBase.length; i++)
@@ -451,14 +437,11 @@ public class LodQuadBuilder
bb.putShort((short) 0); // padding to make sure the vertex format as a whole is a multiple of 4 bb.putShort((short) 0); // padding to make sure the vertex format as a whole is a multiple of 4
} }
///endregion
//=========// //=========//
// getters // // getters //
//=========// //=========//
///region
public int getCurrentOpaqueQuadsCount() public int getCurrentOpaqueQuadsCount()
{ {
@@ -486,35 +469,17 @@ public class LodQuadBuilder
return i; return i;
} }
private static int maxBufferByteSize = -1; /** Returns how many GpuBuffers will be needed to render opaque quads in this builder. */
/** public int getCurrentNeededOpaqueVertexBufferCount() { return MathUtil.ceilDiv(this.getCurrentOpaqueQuadsCount(), LodBufferContainer.MAX_QUADS_PER_BUFFER); }
* The max number of bytes we allow for a single Vertex buffer. /** Returns how many GpuBuffers will be needed to render transparent quads in this builder. */
* If an LOD has more data than this it will be split public int getCurrentNeededTransparentVertexBufferCount()
* up into multiple buffers.
*/
public static int getMaxBufferByteSize()
{ {
if (maxBufferByteSize != -1) if (!this.doTransparency)
{ {
return maxBufferByteSize; return 0;
} }
// 2 MB return MathUtil.ceilDiv(this.getCurrentTransparentQuadsCount(), LodBufferContainer.MAX_QUADS_PER_BUFFER);
// note: this is relatively small (10 MB was the previous max) to reduce stuttering
// during the upload process by having smaller upload steps
int maxVboByteSize = 2 * 1024 * 1024;
int maxQuadsPerBuffer = maxVboByteSize / BYTES_PER_QUAD;
// integer truncation to remove decimal component
int fullSizedBuffer = maxQuadsPerBuffer * BYTES_PER_QUAD;
maxBufferByteSize = fullSizedBuffer;
return fullSizedBuffer;
} }
///endregion
} }
@@ -0,0 +1,241 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.render.columnViews;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import java.util.Arrays;
import java.util.ConcurrentModificationException;
public final class ColumnArrayView implements IColumnDataView
{
public final LongArrayList data;
/**
* How many data points are currently being represented by this view. <br>
* Will be equal to or less than {@link ColumnArrayView#verticalSize}.
*/
public final int size;
/**
* Vertical size in data points. <Br>
* Can be 0 if this column was created for an empty data source.
*/
public final int verticalSize;
/**
* Where the relative starting index is in the {@link ColumnArrayView#data} array
* if this view is representing part of a {@link ColumnRenderSource}.
*/
public final int offset;
//=============//
// constructor //
//=============//
/** @throws IllegalArgumentException if the offset is greater than the data's size */
public ColumnArrayView(LongArrayList data, int size, int offset, int verticalSize) throws IllegalArgumentException
{
this.data = data;
this.size = size;
this.offset = offset;
this.verticalSize = verticalSize;
if (this.data.size() < this.offset)
{
throw new IllegalArgumentException("data size ["+this.data.size()+"] is shorter than offset ["+this.offset+"].");
}
}
//=====================//
// getters and setters //
//=====================//
@Override
public long get(int index)
{
try
{
return this.data.getLong(index + this.offset);
}
catch (IndexOutOfBoundsException e)
{
// we can fairly confidently say this is a concurrent exception over an actual
// index out of bounds, since we're generally iterating over the whole
// array any time we use this getter.
throw new ConcurrentModificationException("Potential concurrent modification detected. Make sure the parent ColumnRenderSource isn't being closed before the ColumnArrayView processing is complete.", e);
}
}
public void set(int index, long value) { data.set(index + offset, value); }
@Override
public int size() { return size; }
@Override
public int verticalSize() { return verticalSize; }
@Override
public int dataCount() { return (this.verticalSize != 0) ? (this.size / this.verticalSize) : 0; } // TODO what does the divide by mean?
@Override
public ColumnArrayView subView(int dataIndexStart, int dataCount)
{ return new ColumnArrayView(data, dataCount * verticalSize, offset + dataIndexStart * verticalSize, verticalSize); }
public void fill(long value) { Arrays.fill(data.elements(), offset, offset + size, value); }
public void copyFrom(IColumnDataView source) { copyFrom(source, 0); }
public void copyFrom(IColumnDataView source, int outputDataIndexOffset)
{
if (source.verticalSize() > verticalSize)
{
throw new IllegalArgumentException("source verticalSize must be <= self's verticalSize to copy");
}
else if (source.dataCount() + outputDataIndexOffset > dataCount())
{
throw new IllegalArgumentException("dataIndexStart + source.dataCount() must be <= self.dataCount() to copy");
}
else if (source.verticalSize() != verticalSize)
{
for (int i = 0; i < source.dataCount(); i++)
{
int outputOffset = offset + outputDataIndexOffset * verticalSize + i * verticalSize;
source.subView(i, 1).copyTo(data.elements(), outputOffset, source.verticalSize());
Arrays.fill(data.elements(), outputOffset + source.verticalSize(),
outputOffset + verticalSize, 0);
}
}
else
{
source.copyTo(data.elements(), offset + outputDataIndexOffset * verticalSize, source.size());
}
}
@Override
public void copyTo(long[] target, int offset, int size) { System.arraycopy(data.elements(), this.offset, target, offset, size); }
public boolean mergeWith(ColumnArrayView source, boolean override)
{
if (size != source.size)
{
throw new IllegalArgumentException("Cannot merge views of different sizes");
}
if (verticalSize != source.verticalSize)
{
throw new IllegalArgumentException("Cannot merge views of different vertical sizes");
}
boolean anyChange = false;
for (int o = 0; o < (source.size() * verticalSize); o += verticalSize)
{
if (override)
{
if (RenderDataPointUtil.compareDatapointPriority(source.get(o), get(o)) >= 0)
{
anyChange = true;
System.arraycopy(source.data, source.offset + o, data, offset + o, verticalSize);
}
}
else
{
if (RenderDataPointUtil.compareDatapointPriority(source.get(o), get(o)) > 0)
{
anyChange = true;
System.arraycopy(source.data, source.offset + o, data, offset + o, verticalSize);
}
}
}
return anyChange;
}
public void changeVerticalSizeFrom(IColumnDataView source)
{
if (this.dataCount() != source.dataCount())
{
throw new IllegalArgumentException("Cannot copy and resize to views with different dataCounts");
}
if (this.verticalSize >= source.verticalSize())
{
this.copyFrom(source);
}
else
{
for (int i = 0; i < this.dataCount(); i++)
{
RenderDataPointUtil.mergeMultiData(source.subView(i, 1), this.subView(i, 1));
}
}
}
//================//
// base overrides //
//================//
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("S:").append(this.size);
sb.append(" V:").append(this.verticalSize);
sb.append(" O:").append(this.offset);
sb.append(" [");
for (int i = 0; i < this.size; i++)
{
sb.append(RenderDataPointUtil.toString(this.data.getLong(this.offset + i)));
if (i < this.size - 1)
{
sb.append(",\n");
}
}
sb.append("]");
return sb.toString();
}
public int getDataHash() { return arrayHash(this.data, this.offset, this.size); }
private static int arrayHash(LongArrayList a, int offset, int length)
{
if (a == null)
{
return 0;
}
int result = 1;
int end = offset + length;
for (int i = offset; i < end; i++)
{
long element = a.getLong(i);
int elementHash = (int) (element ^ (element >>> 32));
result = 31 * result + elementHash;
}
return result;
}
}
@@ -0,0 +1,143 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.render.columnViews;
import it.unimi.dsi.fastutil.longs.LongArrayList;
public class ColumnQuadView implements IColumnDataView
{
private final LongArrayList data;
private final int perColumnOffset; // per column (of columns of data) offset in longs
private final int xSize; // x size in datapoints
private final int zSize; // x size in datapoints
private final int offset; // offset in longs
private final int vertSize; // vertical size in longs
public ColumnQuadView(LongArrayList data, int dataZWidth, int dataVertSize, int viewXOffset, int viewZOffset, int xSize, int zSize)
{
if (viewXOffset + xSize > (data.size() / (dataZWidth * dataVertSize)) || viewZOffset + zSize > dataZWidth)
{
throw new IllegalArgumentException("View is out of bounds");
}
this.data = data;
this.xSize = xSize;
this.zSize = zSize;
this.vertSize = dataVertSize;
this.perColumnOffset = dataZWidth * dataVertSize;
this.offset = viewXOffset * perColumnOffset + viewZOffset * dataVertSize;
}
private ColumnQuadView(LongArrayList data, int perColumnOffset, int offset, int vertSize, int xSize, int zSize)
{
this.data = data;
this.perColumnOffset = perColumnOffset;
this.offset = offset;
this.vertSize = vertSize;
this.xSize = xSize;
this.zSize = zSize;
}
@Override
public long get(int index)
{
int x = index / perColumnOffset;
int z = (index % perColumnOffset) / vertSize;
int v = index % vertSize;
return get(x, z, v);
}
public long get(int x, int z, int v)
{
return data.getLong(offset + x * perColumnOffset + z * vertSize + v);
}
public long set(int x, int z, int v, long value)
{
return data.set(offset + x * perColumnOffset + z * vertSize + v, value);
}
public ColumnArrayView get(int x, int z)
{
return new ColumnArrayView(data, vertSize, offset + x * perColumnOffset + z * vertSize, vertSize);
}
public ColumnArrayView getRow(int x)
{
return new ColumnArrayView(data, zSize * vertSize, offset + x * perColumnOffset, vertSize);
}
public void set(int x, int z, IColumnDataView singleColumn)
{
if (singleColumn.verticalSize() != vertSize) throw new IllegalArgumentException("Vertical size of singleColumn must be equal to vertSize");
if (singleColumn.dataCount() != 1) throw new IllegalArgumentException("SingleColumn must contain exactly one data point");
singleColumn.copyTo(data.elements(), offset + x * perColumnOffset + z * vertSize, singleColumn.size());
}
@Override
public int size()
{
return xSize * zSize * vertSize;
}
@Override
public int verticalSize()
{
return vertSize;
}
@Override
public int dataCount()
{
return xSize * zSize;
}
@Override
public IColumnDataView subView(int dataIndexStart, int dataCount)
{
if (dataCount != 1) throw new UnsupportedOperationException("Fixme: subView for QUadView only support one data point!");
int x = dataIndexStart / xSize;
int z = dataIndexStart % xSize;
return new ColumnArrayView(data, vertSize * dataCount, offset + x * perColumnOffset + z * vertSize, vertSize);
}
public ColumnQuadView subView(int xOffset, int zOffset, int xSize, int zSize)
{
if (xOffset + xSize > this.xSize || zOffset + zSize > this.zSize) throw new IllegalArgumentException("SubView is out of bounds");
return new ColumnQuadView(data, perColumnOffset, offset + xOffset * perColumnOffset + zOffset * vertSize, vertSize, xSize, zSize);
}
@Override
public void copyTo(long[] target, int offset, int size)
{
if (size != this.size() && size > zSize * vertSize) throw new UnsupportedOperationException("Not supported yet");
if (size <= xSize * vertSize)
{
System.arraycopy(data, this.offset, target, offset, size);
}
else
{
for (int x = 0; x < xSize; x++)
{
System.arraycopy(data, this.offset + x * perColumnOffset, target, offset + x * xSize * vertSize, xSize * vertSize);
}
}
}
}
@@ -1,309 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.render.columnViews;
import com.seibel.distanthorizons.api.enums.config.EDhApiVerticalQuality;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.util.RenderDataPointReducingList;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import java.util.Arrays;
import java.util.ConcurrentModificationException;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* Maps to part of a {@link ColumnRenderSource} for easier handling.
*
* @see ColumnRenderSource
*/
public final class ColumnRenderView implements AutoCloseable
{
private static final ConcurrentLinkedQueue<ColumnRenderView> POOL = new ConcurrentLinkedQueue<>();
public LongArrayList data;
/**
* How many data points are currently being represented by this view. <br>
* Will be equal to or less than {@link ColumnRenderView#maxVerticalSliceCount}.
*/
public int size;
/**
* Vertical size in data points. <Br>
* Can be 0 if this column was created for an empty data source.
* @see EDhApiVerticalQuality#calculateMaxNumberOfVerticalSlicesAtDetailLevel(byte)
*/
public int maxVerticalSliceCount;
/**
* Where the relative starting index is in the {@link ColumnRenderView#data} array
* if this view is representing part of a {@link ColumnRenderSource}.
*/
public int offset;
//=============//
// constructor //
//=============//
//region
private ColumnRenderView() { }
/**
* returns an un-populated view. <br>
* {@link ColumnRenderView#populate(LongArrayList, int, int, int)} must be called before the
* view can be used.s
*/
public static ColumnRenderView getPooled() { return getPooled(null, 0, 0, 0); }
public static ColumnRenderView getPooled(LongArrayList data, int size, int offset, int maxVerticalSliceCount) throws IllegalArgumentException
{
// try getting an existing pooled object first
ColumnRenderView view = POOL.poll();
if (view == null)
{
// no pooled object
view = new ColumnRenderView();
}
// data will be null if the object will be populated at a later date
if (data != null)
{
view.populate(data, size, offset, maxVerticalSliceCount);
}
return view;
}
/**
* Mutates this object so the necessary data is visible.
* @throws IllegalArgumentException if the offset is greater than the data's size
*/
public void populate(LongArrayList data, int size, int offset, int maxVerticalSliceCount) throws IllegalArgumentException
{
this.data = data;
this.size = size;
this.offset = offset;
this.maxVerticalSliceCount = maxVerticalSliceCount;
if (this.data.size() < this.offset)
{
throw new IllegalArgumentException("data size ["+this.data.size()+"] is shorter than offset ["+this.offset+"].");
}
}
public void clear()
{
this.data = null;
this.size = 0;
this.offset = 0;
this.maxVerticalSliceCount = 0;
}
//endregion
//=====================//
// getters and setters //
//=====================//
//region
public long get(int index)
{
try
{
return this.data.getLong(index + this.offset);
}
catch (IndexOutOfBoundsException e)
{
// we can fairly confidently say this is a concurrent exception over an actual
// index out of bounds, since we're generally iterating over the whole
// array any time we use this getter.
throw new ConcurrentModificationException("Potential concurrent modification detected. Make sure the parent ColumnRenderSource isn't being closed before the ColumnRenderView processing is complete.", e);
}
}
public void set(int index, long value) { this.data.set(index + this.offset, value); }
public void fill(long value) { Arrays.fill(this.data.elements(), this.offset, this.offset + this.size, value); }
//endregion
//=========//
// subview //
//=========//
//region
/** should be called in a try-finally block for automatic cleanup */
public ColumnRenderView subView(int dataIndexStart, int dataCount)
{
return ColumnRenderView.getPooled(
this.data,
dataCount * this.maxVerticalSliceCount,
this.offset + dataIndexStart * this.maxVerticalSliceCount,
this.maxVerticalSliceCount);
}
/** can be used to determine sub-view starting indexes */
public int subViewCount() { return (this.maxVerticalSliceCount != 0) ? (this.size / this.maxVerticalSliceCount) : 0; }
//endregion
//======================//
// change vertical size //
//======================//
//region
public void changeVerticalSizeFrom(ColumnRenderView source, RenderDataPointReducingList reducingList)
{
if (this.subViewCount() != source.subViewCount())
{
throw new IllegalArgumentException("Cannot copy and resize to views with different dataCounts");
}
if (this.maxVerticalSliceCount >= source.maxVerticalSliceCount)
{
this.copyFrom(source, 0);
}
else
{
for (int i = 0; i < this.subViewCount(); i++)
{
try(ColumnRenderView sourceSubView = source.subView(i, 1);
ColumnRenderView thisSubView = this.subView(i, 1))
{
mergeMultiData(sourceSubView, reducingList, thisSubView);
}
}
}
}
private void copyFrom(ColumnRenderView source, int outputDataIndexOffset)
{
if (source.maxVerticalSliceCount > this.maxVerticalSliceCount)
{
throw new IllegalArgumentException("source verticalSize must be <= self's verticalSize to copy");
}
else if (source.subViewCount() + outputDataIndexOffset > this.subViewCount())
{
throw new IllegalArgumentException("dataIndexStart + source.dataCount() must be <= self.dataCount() to copy");
}
else if (source.maxVerticalSliceCount != this.maxVerticalSliceCount)
{
for (int i = 0; i < source.subViewCount(); i++)
{
int outputOffset = this.offset + (outputDataIndexOffset * this.maxVerticalSliceCount) + (i * this.maxVerticalSliceCount);
try(ColumnRenderView subView = source.subView(i, 1))
{
subView.copyTo(this.data.elements(), outputOffset, source.maxVerticalSliceCount);
Arrays.fill(this.data.elements(),
outputOffset + source.maxVerticalSliceCount,
outputOffset + this.maxVerticalSliceCount,
0);
}
}
}
else
{
source.copyTo(this.data.elements(), this.offset + outputDataIndexOffset * this.maxVerticalSliceCount, source.size);
}
}
private void copyTo(long[] target, int offset, int size) { System.arraycopy(this.data.elements(), this.offset, target, offset, size); }
/**
* This method merge column of multiple data together
*
* @param sourceData one or more columns of data
* @param output one column of space for the result to be written to
*/
private static void mergeMultiData(ColumnRenderView sourceData, RenderDataPointReducingList reducingList, ColumnRenderView output)
{
int target = output.maxVerticalSliceCount;
if (target <= 0)
{
// I expect this to never be the case,
// but RenderDataPointReducingList handles it sanely,
// so I might as well handle it sanely here too.
output.fill(RenderDataPointUtil.EMPTY_DATA);
}
else if (target == 1)
{
output.set(0, RenderDataPointReducingList.reduceToOne(sourceData));
for (int index = 1, size = output.size; index < size; index++)
{
output.set(index, RenderDataPointUtil.EMPTY_DATA);
}
}
else
{
reducingList.populate(sourceData);
reducingList.reduce(output.maxVerticalSliceCount);
reducingList.copyTo(output);
}
}
//endregion
//================//
// base overrides //
//================//
//region
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("S:").append(this.size);
sb.append(" V:").append(this.maxVerticalSliceCount);
sb.append(" O:").append(this.offset);
sb.append(" [");
for (int i = 0; i < this.size; i++)
{
sb.append(RenderDataPointUtil.toString(this.data.getLong(this.offset + i)));
if (i < this.size - 1)
{
sb.append(",\n");
}
}
sb.append("]");
return sb.toString();
}
@Override
public void close()
{
// no validation is done to make sure this object is only added to the pool once
// please only use this object in a try-finally so the close is handled implicitly
POOL.add(this);
}
//endregion
}
@@ -0,0 +1,59 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.render.columnViews;
import it.unimi.dsi.fastutil.longs.LongIterator;
import java.util.Iterator;
public interface IColumnDataView
{
long get(int index);
// FIXME probably horizontal size in blocks?
int size();
default LongIterator iterator()
{
return new LongIterator()
{
private int index = 0;
private final int size = IColumnDataView.this.size();
@Override
public boolean hasNext() { return this.index < this.size; }
@Override
public long nextLong() { return IColumnDataView.this.get(this.index++); }
};
}
// FIXME measured in blocks?
int verticalSize();
// FIXME how many datapoints in this LOD?
int dataCount();
IColumnDataView subView(int dataIndexStart, int dataCount);
void copyTo(long[] target, int offset, int count);
}
@@ -24,13 +24,13 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap; import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource; import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnRenderView; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPosMutable; import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListCheckout; import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPosMutable;
import com.seibel.distanthorizons.core.util.*; import com.seibel.distanthorizons.core.util.*;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
@@ -40,7 +40,6 @@ import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.HashSet; import java.util.HashSet;
@@ -109,11 +108,11 @@ public class FullDataToRenderDataTransformer
final long pos = fullDataSource.getPos(); final long pos = fullDataSource.getPos();
final byte dataDetail = fullDataSource.getDataDetailLevel(); final byte dataDetail = fullDataSource.getDataDetailLevel();
final int maxVertSliceCount = Config.Client.Advanced.Graphics.Quality.verticalQuality.get().calculateMaxNumberOfVerticalSlicesAtDetailLevel(fullDataSource.getDataDetailLevel()); final int vertSize = Config.Client.Advanced.Graphics.Quality.verticalQuality.get().calculateMaxVerticalData(fullDataSource.getDataDetailLevel());
final ColumnRenderSource columnSource = ColumnRenderSource.createEmpty(pos, maxVertSliceCount, levelWrapper.getMinHeight()); final ColumnRenderSource columnSource = ColumnRenderSource.createEmpty(pos, vertSize, levelWrapper.getMinHeight());
if (fullDataSource.isEmpty) if (fullDataSource.isEmpty)
{ {
return columnSource; return columnSource;
@@ -123,40 +122,32 @@ public class FullDataToRenderDataTransformer
int baseX = DhSectionPos.getMinCornerBlockX(pos); int baseX = DhSectionPos.getMinCornerBlockX(pos);
int baseZ = DhSectionPos.getMinCornerBlockZ(pos); int baseZ = DhSectionPos.getMinCornerBlockZ(pos);
try(ColumnRenderView columnArrayView = ColumnRenderView.getPooled(); for (int x = 0; x < FullDataSourceV2.WIDTH; x++)
PhantomArrayListCheckout phantomCheckout = ARRAY_LIST_POOL.checkoutLongArrays(1);
ColumnRenderView tempExpandingColumnView = ColumnRenderView.getPooled();
RenderDataPointReducingList reducingList = new RenderDataPointReducingList())
{ {
for (int x = 0; x < FullDataSourceV2.WIDTH; x++) for (int z = 0; z < FullDataSourceV2.WIDTH; z++)
{ {
for (int z = 0; z < FullDataSourceV2.WIDTH; z++) ColumnArrayView columnArrayView = columnSource.getVerticalDataPointView(x, z);
{ LongArrayList dataColumn = fullDataSource.getColumnAtRelPos(x, z);
columnSource.populateColumnView(columnArrayView, x, z);
LongArrayList dataColumn = fullDataSource.getColumnAtRelPos(x, z); updateOrReplaceRenderDataViewColumnWithFullDataColumn(
levelWrapper, fullDataSource,
updateOrReplaceRenderDataViewColumnWithFullDataColumn( // bitshift is to account for LODs with a detail level greater than 0 so the block pos is correct
levelWrapper, fullDataSource, baseX + BitShiftUtil.pow(x,dataDetail), baseZ + BitShiftUtil.pow(z,dataDetail),
// bit shift is to account for LODs with a detail level greater than 0 so the block pos is correct columnArrayView, dataColumn);
baseX + BitShiftUtil.pow(x, dataDetail), baseZ + BitShiftUtil.pow(z, dataDetail),
columnArrayView, dataColumn,
// pooled references so we don't need to re-allocate/get them 4000 times per render source
phantomCheckout, tempExpandingColumnView, reducingList);
}
} }
} }
columnSource.fillDebugFlag(0, 0, ColumnRenderSource.WIDTH, ColumnRenderSource.WIDTH, ColumnRenderSource.DebugSourceFlag.FULL);
return columnSource; return columnSource;
} }
/** Updates the given {@link ColumnRenderView} to match the incoming Full data {@link LongArrayList} */ /** Updates the given {@link ColumnArrayView} to match the incoming Full data {@link LongArrayList} */
public static void updateOrReplaceRenderDataViewColumnWithFullDataColumn( public static void updateOrReplaceRenderDataViewColumnWithFullDataColumn(
IClientLevelWrapper levelWrapper, IClientLevelWrapper levelWrapper,
FullDataSourceV2 fullDataSource, int blockX, int blockZ, FullDataSourceV2 fullDataSource, int blockX, int blockZ,
ColumnRenderView columnArrayView, ColumnArrayView columnArrayView,
LongArrayList fullDataColumn, LongArrayList fullDataColumn)
// pooled references
PhantomArrayListCheckout phantomCheckout, ColumnRenderView tempExpandingColumnView, RenderDataPointReducingList reducingList)
{ {
// we can't do anything if the full data is missing or empty // we can't do anything if the full data is missing or empty
if (fullDataColumn == null if (fullDataColumn == null
@@ -166,26 +157,34 @@ public class FullDataToRenderDataTransformer
} }
int fullDataLength = fullDataColumn.size(); int fullDataLength = fullDataColumn.size();
if (fullDataLength <= columnArrayView.maxVerticalSliceCount) if (fullDataLength <= columnArrayView.verticalSize())
{ {
// Directly use the arrayView since it fits. // Directly use the arrayView since it fits.
setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, columnArrayView, fullDataColumn); setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, columnArrayView, fullDataColumn);
} }
else else
{ {
LongArrayList dataArrayList = phantomCheckout.getLongArray(0, fullDataLength); PhantomArrayListCheckout checkout = ARRAY_LIST_POOL.checkoutArrays(0, 0, 1);
LongArrayList dataArrayList = checkout.getLongArray(0, fullDataLength);
// expand the ColumnArrayView to fit the new larger max vertical size try
tempExpandingColumnView.populate(dataArrayList, fullDataLength, 0, fullDataLength); {
setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, tempExpandingColumnView, fullDataColumn); // expand the ColumnArrayView to fit the new larger max vertical size
ColumnArrayView newColumnArrayView = new ColumnArrayView(dataArrayList, fullDataLength, 0, fullDataLength);
columnArrayView.changeVerticalSizeFrom(tempExpandingColumnView, reducingList); setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, newColumnArrayView, fullDataColumn);
columnArrayView.changeVerticalSizeFrom(newColumnArrayView);
}
finally
{
ARRAY_LIST_POOL.returnCheckout(checkout);
}
} }
} }
private static void setRenderColumnView( private static void setRenderColumnView(
IClientLevelWrapper levelWrapper, FullDataSourceV2 fullDataSource, IClientLevelWrapper levelWrapper, FullDataSourceV2 fullDataSource,
int blockX, int blockZ, int blockX, int blockZ,
ColumnRenderView renderColumnData, LongArrayList fullColumnData) ColumnArrayView renderColumnData, LongArrayList fullColumnData)
{ {
//===============// //===============//
// config values // // config values //
@@ -194,12 +193,8 @@ public class FullDataToRenderDataTransformer
boolean ignoreNonCollidingBlocks = (Config.Client.Advanced.Graphics.Quality.blocksToIgnore.get() == EDhApiBlocksToAvoid.NON_COLLIDING); boolean ignoreNonCollidingBlocks = (Config.Client.Advanced.Graphics.Quality.blocksToIgnore.get() == EDhApiBlocksToAvoid.NON_COLLIDING);
boolean colorBelowWithAvoidedBlocks = Config.Client.Advanced.Graphics.Quality.tintWithAvoidedBlocks.get(); boolean colorBelowWithAvoidedBlocks = Config.Client.Advanced.Graphics.Quality.tintWithAvoidedBlocks.get();
final ObjectOpenHashSet<IBlockStateWrapper> blockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredBlocks(levelWrapper); HashSet<IBlockStateWrapper> blockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredBlocks(levelWrapper);
final ObjectOpenHashSet<IBlockStateWrapper> caveBlockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredCaveBlocks(levelWrapper); HashSet<IBlockStateWrapper> caveBlockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredCaveBlocks(levelWrapper);
final ObjectOpenHashSet<IBlockStateWrapper> waterSubsurfaceReplacementBlocks = WRAPPER_FACTORY.getWaterSubsurfaceReplacementBlocks(levelWrapper);
final ObjectOpenHashSet<IBlockStateWrapper> waterSurfaceReplacementBlocks = WRAPPER_FACTORY.getWaterSurfaceReplacementBlocks(levelWrapper);
final IBlockStateWrapper water = WRAPPER_FACTORY.getWaterBlockStateWrapper(levelWrapper);
// build snow block cache if needed // build snow block cache if needed
if (snowLayerBlockStates == null) if (snowLayerBlockStates == null)
@@ -227,7 +222,6 @@ public class FullDataToRenderDataTransformer
int colorToApplyToNextBlock = -1; int colorToApplyToNextBlock = -1;
int lastColor = 0; int lastColor = 0;
int lastBottom = -10_000; int lastBottom = -10_000;
IBlockStateWrapper lastBlock = null;
int skylightToApplyToNextBlock = -1; int skylightToApplyToNextBlock = -1;
int blocklightToApplyToNextBlock = -1; int blocklightToApplyToNextBlock = -1;
@@ -288,29 +282,19 @@ public class FullDataToRenderDataTransformer
// cave culling check // // cave culling check //
//====================// //====================//
if (waterSubsurfaceReplacementBlocks.contains(block)
&& (lastBlock == null || lastBlock.isAir()))
{
block = water;
}
boolean ignoreBlock = blockStatesToIgnore.contains(block); boolean ignoreBlock = blockStatesToIgnore.contains(block);
boolean caveBlock = caveBlockStatesToIgnore.contains(block); boolean caveBlock = caveBlockStatesToIgnore.contains(block); // TODO caves should also ignore transparent/non-solid blocks (IE grass and plants) wthout each being defined
if (caveBlock if (caveBlock)
// caves also ignore transparent/non-solid blocks (IE grass and plants) without each being defined
|| !block.isSolid()
|| block.isLiquid()
|| block.getOpacity() < LodUtil.BLOCK_FULLY_OPAQUE)
{ {
if (caveCullingEnabled if (caveCullingEnabled
// assume this data point is underground if it has no sky-light // assume this data point is underground if it has no sky-light
&& skyLight == LodUtil.MIN_MC_LIGHT && skyLight == LodUtil.MIN_MC_LIGHT
// ignore caves above a certain height to prevent floating islands from having walls underneath them // ignore caves above a certain height to prevent floating islands from having walls underneath them
&& topY < caveCullingMaxY && topY < caveCullingMaxY
// cave culling shouldn't happen when at the top of the world // cave culling shouldn't happen when at the top of the world
&& renderDataIndex != 0 && fullDataIndex != 0 && renderDataIndex != 0 && fullDataIndex != 0
// cave culling can't happen when at the bottom of the world // cave culling can't happen when at the bottom of the world
&& (fullDataIndex + 1) < fullColumnData.size()) && (fullDataIndex + 1) < fullColumnData.size())
{ {
// we need to get the next sky/block lights because // we need to get the next sky/block lights because
// the air block here will always have a light of 0/0 due to only the top of the LOD's light being saved. // the air block here will always have a light of 0/0 due to only the top of the LOD's light being saved.
@@ -318,7 +302,7 @@ public class FullDataToRenderDataTransformer
int nextSkyLight = FullDataPointUtil.getSkyLight(nextFullData); int nextSkyLight = FullDataPointUtil.getSkyLight(nextFullData);
if (nextSkyLight == LodUtil.MIN_MC_LIGHT if (nextSkyLight == LodUtil.MIN_MC_LIGHT
&& ColorUtil.getAlpha(lastColor) == 255) && ColorUtil.getAlpha(lastColor) == 255)
{ {
// replace the previous block with new bottom // replace the previous block with new bottom
long columnData = renderColumnData.get(renderDataIndex - 1); long columnData = renderColumnData.get(renderDataIndex - 1);
@@ -339,9 +323,6 @@ public class FullDataToRenderDataTransformer
else if (ignoreBlock) else if (ignoreBlock)
{ {
// this is an ignored block, but shouldn't be merged like a cave block // this is an ignored block, but shouldn't be merged like a cave block
// applying this sky light to the next block should prevent black spots for opaque covering blocks
skylightToApplyToNextBlock = skyLight;
continue; continue;
} }
@@ -357,68 +338,35 @@ public class FullDataToRenderDataTransformer
&& !block.isLiquid() && !block.isLiquid()
&& block.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE; && block.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE;
// handle height reduction // merge snow into the block below it
boolean isSnowLayer = snowLayerBlockStates.contains(block); if (snowLayerBlockStates.contains(block))
boolean isWaterSurfaceReplacement = waterSurfaceReplacementBlocks.contains(block);
if (isSnowLayer || isWaterSurfaceReplacement)
{ {
if (isWaterSurfaceReplacement) // sometimes a snow datapoint will be multiple blocks tall,
{
// replace the block with water
block = WRAPPER_FACTORY.getWaterBlockStateWrapper(levelWrapper);
}
// sometimes a datapoint will be multiple blocks tall,
// in that case we just want to drop the top by 1 // in that case we just want to drop the top by 1
blockHeight -= 1; blockHeight -= 1;
if (blockHeight == 0) if (blockHeight == 0)
{ {
// this block was entirely removed, just color the block below it // this snow block was entirely removed, just color the block below it
ignoreNonSolidBlock = true; ignoreNonSolidBlock = true;
if (isSnowLayer)
{
// snow is a special case where it should always tint the block
// below it, if not done grass will appear as gray
int snowColor = levelWrapper.getBlockColor(mutableBlockPos, biome, fullDataSource, block);
colorToApplyToNextBlock = ColorUtil.setAlpha(snowColor, 255);
}
else //if (isWaterSurfaceReplacement)
{
colorToApplyToNextBlock = levelWrapper.getBlockColor(mutableBlockPos, biome, fullDataSource, block);
}
} }
} }
if (ignoreNonSolidBlock) if (ignoreNonSolidBlock)
{ {
int ignoredColor = levelWrapper.getBlockColor(mutableBlockPos, biome, fullDataSource, block);
int ignoredAlpha = ColorUtil.getAlpha(ignoredColor);
if (colorBelowWithAvoidedBlocks) if (colorBelowWithAvoidedBlocks)
{ {
int tempColor = levelWrapper.getBlockColor(mutableBlockPos, biome, fullDataSource, block);
// don't transfer the color when alpha is 0 // don't transfer the color when alpha is 0
// this prevents issues if grass is transparent // this prevents issues if grass is transparent
if (ignoredAlpha != 0) if (ColorUtil.getAlpha(tempColor) != 0)
{ {
colorToApplyToNextBlock = ColorUtil.setAlpha(ignoredColor, 255); colorToApplyToNextBlock = ColorUtil.setAlpha(tempColor, 255);
skylightToApplyToNextBlock = skyLight;
blocklightToApplyToNextBlock = blockLight;
} }
} }
// Don't transfer the lighting when alpha is 0
// (the block below should have its own lighting).
if (ignoredAlpha != 0)
{
// Lighting is transferred even when "colorBelowWithAvoidedBlocks"
// is false, since otherwise the blocks underneath may have a light value of "0"
// which makes things look darker than they should.
// This can specifically manifest as grid lines on LOD borders
// (not entire sure why grid lines on LOD borders, maybe it has to do with the fact that those LODs aren't occluded?).
skylightToApplyToNextBlock = skyLight;
blocklightToApplyToNextBlock = blockLight;
}
// skip this non-colliding block // skip this non-colliding block
continue; continue;
} }
@@ -429,20 +377,6 @@ public class FullDataToRenderDataTransformer
{ {
// use this block's color // use this block's color
color = levelWrapper.getBlockColor(mutableBlockPos, biome, fullDataSource, block); color = levelWrapper.getBlockColor(mutableBlockPos, biome, fullDataSource, block);
// use the skylight override if present
if (skylightToApplyToNextBlock != -1)
{
skyLight = skylightToApplyToNextBlock;
// remove the override so we don't accidentally override the next datapoint
skylightToApplyToNextBlock = -1;
}
if (blocklightToApplyToNextBlock != -1)
{
blockLight = blocklightToApplyToNextBlock;
blocklightToApplyToNextBlock = -1;
}
} }
else else
{ {
@@ -479,7 +413,6 @@ public class FullDataToRenderDataTransformer
} }
lastBottom = bottomY; lastBottom = bottomY;
lastColor = color; lastColor = color;
lastBlock = block;
} }
@@ -154,7 +154,7 @@ public class LodDataBuilder
IBlockStateWrapper currentBlockState = AIR; IBlockStateWrapper currentBlockState = AIR;
int mappedId = dataSource.mapping.addIfNotPresentAndGetId(currentBiome, currentBlockState); int mappedId = dataSource.mapping.addIfNotPresentAndGetId(currentBiome, currentBlockState);
// Determine lighting (we are at the height limit. There are no torches here, and sky is not obscured.) // Determine lighting (we are at the height limit. There are no torches here, and sky is not obscured.) // TODO: Per face lighting someday?
byte blockLight = LodUtil.MIN_MC_LIGHT; byte blockLight = LodUtil.MIN_MC_LIGHT;
byte skyLight = LodUtil.MAX_MC_LIGHT; byte skyLight = LodUtil.MAX_MC_LIGHT;
@@ -321,6 +321,8 @@ public class LodDataBuilder
LongArrayList packedDataPoints = convertApiDataPointListToPackedLongArray(columnDataPoints, dataSource, apiChunk.bottomYBlockPos, runAdditionalValidation); LongArrayList packedDataPoints = convertApiDataPointListToPackedLongArray(columnDataPoints, dataSource, apiChunk.bottomYBlockPos, runAdditionalValidation);
// TODO add the ability for API users to define a different compression mode
// or add a "unkown" compression mode
dataSource.setSingleColumn( dataSource.setSingleColumn(
packedDataPoints, packedDataPoints,
relBlockX + relSourceBlockX, relBlockZ + relSourceBlockZ, relBlockX + relSourceBlockX, relBlockZ + relSourceBlockZ,
@@ -0,0 +1,286 @@
package com.seibel.distanthorizons.core.file.fullDatafile;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.KeyedLockContainer;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.NotNull;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;
/**
* Used to batch together multiple data source updates that all
* affect the same position.
*/
public class DelayedFullDataSourceSaveCache implements AutoCloseable
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
/**
* a cache won't automatically clean itself unless we trigger it's clean method
* if not done then we'd only see the cache invalidate when new inserts happen,
* which causes weird behavior when placing/breaking blocks.
*/
private static final ThreadPoolExecutor BACKGROUND_CLEAN_UP_THREAD = ThreadUtil.makeSingleDaemonThreadPool("delayed save cache cleaner");
private static final Set<WeakReference<DelayedFullDataSourceSaveCache>> SAVE_CACHE_SET = Collections.newSetFromMap(new ConcurrentHashMap<>());
/** how long between clean up checks */
private static final int CLEANUP_CHECK_TIME_IN_MS = 1_000;
private final ConcurrentHashMap<Long, DataSourceSavedTimePair> dataSourceByPosition = new ConcurrentHashMap<Long, DataSourceSavedTimePair>();
/* don't let two threads load the same position at the same time */
protected final KeyedLockContainer<Long> saveLockContainer = new KeyedLockContainer<>();
private final ISaveDataSourceFunc onSaveTimeoutAsyncFunc;
private final int saveDelayInMs;
//=============//
// constructor //
//=============//
static
{
BACKGROUND_CLEAN_UP_THREAD.execute(() -> runCleanupLoop());
}
public DelayedFullDataSourceSaveCache(@NotNull ISaveDataSourceFunc onSaveTimeoutAsyncFunc, int saveDelayInMs)
{
this.onSaveTimeoutAsyncFunc = onSaveTimeoutAsyncFunc;
// we can't clean items faster than the cleanup timer fires
if (saveDelayInMs < CLEANUP_CHECK_TIME_IN_MS)
{
LOGGER.warn("The save delay ["+saveDelayInMs+"] shouldn't be less than the cleanup check timer interval ["+CLEANUP_CHECK_TIME_IN_MS+"].");
}
this.saveDelayInMs = saveDelayInMs;
SAVE_CACHE_SET.add(new WeakReference<>(this));
}
//==============//
// update queue //
//==============//
/**
* Writing into memory is done synchronously so inputDataSource can
* be closed after this method finishes.
*/
public void writeDataSourceToMemoryAndQueueSave(@NotNull FullDataSourceV2 inputDataSource)
{
long inputPos = inputDataSource.getPos();
ReentrantLock lockForPos = this.saveLockContainer.getLockForPos(inputPos);
try
{
lockForPos.lock();
FullDataSourceV2 memoryDataSource;
DataSourceSavedTimePair pair = this.dataSourceByPosition.getOrDefault(inputPos, null);
if (pair == null)
{
// no data currently in the memory cache for this position
memoryDataSource = FullDataSourceV2.createEmpty(inputPos);
pair = new DataSourceSavedTimePair(memoryDataSource);
DataSourceSavedTimePair oldPair = this.dataSourceByPosition.put(inputPos, pair);
if (oldPair != null)
{
// shouldn't happen, but just in case
this.handleDataSourceRemoval(oldPair.dataSource);
}
}
else
{
memoryDataSource = pair.dataSource;
}
// write the new data into memory
memoryDataSource.updateFromDataSource(inputDataSource);
// keep track of when the last time we saved something was
pair.updateLastWrittenTimestamp();
}
finally
{
lockForPos.unlock();
}
}
/** when this method is called the datasource should no longer be in the memory cache */
public void handleDataSourceRemoval(@NotNull FullDataSourceV2 removedDataSource)
{
this.onSaveTimeoutAsyncFunc.saveAsync(removedDataSource)
.handle((voidObj, throwable) ->
{
try
{
// if this close method is fired multiple times
// monoliths can appear due to concurrent writing to the
// backend arrays
removedDataSource.close();
}
catch (Exception e)
{
LOGGER.error("Unable to close datasource ["+ DhSectionPos.toString(removedDataSource.getPos()) +"], error: ["+e.getMessage()+"].", e);
}
return null;
});
}
//==============//
// List methods //
//==============//
public int getUnsavedCount() { return this.dataSourceByPosition.size(); }
public void flush() { this.cleanUp(true); }
/** Removes everything from the memory cache and fires the {@link DelayedFullDataSourceSaveCache#onSaveTimeoutAsyncFunc} for each. */
public void cleanUp(boolean flushAll)
{
Enumeration<Long> keyIterator = this.dataSourceByPosition.keys();
while (keyIterator.hasMoreElements())
{
Long pos = keyIterator.nextElement();
ReentrantLock posLock = this.saveLockContainer.getLockForPos(pos);
try
{
posLock.lock();
DataSourceSavedTimePair savedPair = this.dataSourceByPosition.getOrDefault(pos, null);
if (savedPair != null)
{
if (flushAll
|| savedPair.dataSourceHasTimedOut(this.saveDelayInMs))
{
this.dataSourceByPosition.remove(pos);
this.handleDataSourceRemoval(savedPair.dataSource);
}
}
}
finally
{
posLock.unlock();
}
}
}
//================//
// static cleanup //
//================//
private static void runCleanupLoop()
{
while (true)
{
try
{
try
{
Thread.sleep(CLEANUP_CHECK_TIME_IN_MS);
}
catch (InterruptedException ignore) { }
SAVE_CACHE_SET.forEach((cacheRef) ->
{
DelayedFullDataSourceSaveCache cache = cacheRef.get();
if (cache == null)
{
// shouldn't be necessary, but if we forget to manually close a cache, this will prevent leaking
SAVE_CACHE_SET.remove(cacheRef);
}
else
{
cache.cleanUp(false);
}
});
}
catch (Exception e)
{
LOGGER.error("Unexpected error in cleanup thread: [" + e.getMessage() + "].", e);
}
}
}
//================//
// base overrides //
//================//
@Override
public void close()
{
// not the fastest way to handle removing,
// but we shouldn't have more than 20 or so at once
// so this should be just fine
SAVE_CACHE_SET.removeIf((cacheRef) ->
{
DelayedFullDataSourceSaveCache cache = cacheRef.get();
return cache != null && cache.equals(this);
});
}
//================//
// helper classes //
//================//
@FunctionalInterface
public interface ISaveDataSourceFunc
{
/** called after the timeout expires */
CompletableFuture<Void> saveAsync(FullDataSourceV2 inputDataSource);
}
/**
* used to keep track of when data sources
* were written to so we can flush them once
* enough time has passed.
*/
private static class DataSourceSavedTimePair
{
@NotNull
public final FullDataSourceV2 dataSource;
/** the last unix millisecond time this data source was written to */
public long lastWrittenDateTimeMs;
public DataSourceSavedTimePair(@NotNull FullDataSourceV2 dataSource)
{
this.dataSource = dataSource;
this.lastWrittenDateTimeMs = System.currentTimeMillis();
}
public void updateLastWrittenTimestamp()
{ this.lastWrittenDateTimeMs = System.currentTimeMillis(); }
public boolean dataSourceHasTimedOut(long msTillTimeout)
{
long currentTime = System.currentTimeMillis();
long timeSinceUpdate = currentTime - this.lastWrittenDateTimeMs;
return (timeSinceUpdate > msTillTimeout);
}
}
}
@@ -20,11 +20,11 @@
package com.seibel.distanthorizons.core.file.fullDatafile; package com.seibel.distanthorizons.core.file.fullDatafile;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep; import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.core.api.internal.chunkUpdating.WorldChunkUpdateManager; import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2; import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.util.delayedSaveCache.DelayedDataSourceSaveCache; import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataUpdatePropagatorV2;
import com.seibel.distanthorizons.core.file.structure.ISaveStructure; import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
import com.seibel.distanthorizons.core.generation.DhLightingEngine; import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue; import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue;
@@ -33,13 +33,15 @@ import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListCheckout; import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListPool; import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.ExceptionUtil; import com.seibel.distanthorizons.core.util.ExceptionUtil;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import it.unimi.dsi.fastutil.bytes.ByteArrayList; import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -52,6 +54,7 @@ import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.stream.IntStream; import java.util.stream.IntStream;
public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 implements IDebugRenderable public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 implements IDebugRenderable
@@ -62,6 +65,9 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
* Having this number too high causes the system to become overwhelmed by * Having this number too high causes the system to become overwhelmed by
* world gen requests and other jobs won't be done. <br> * world gen requests and other jobs won't be done. <br>
* IE: LODs won't update or render because world gen is hogging the CPU. * IE: LODs won't update or render because world gen is hogging the CPU.
* <br><br>
* TODO this should be dynamically allocated based on CPU load
* and abilities.
*/ */
public static final int MAX_WORLD_GEN_REQUESTS_PER_THREAD = 20; public static final int MAX_WORLD_GEN_REQUESTS_PER_THREAD = 20;
@@ -71,9 +77,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
private final AtomicReference<IFullDataSourceRetrievalQueue> worldGenQueueRef = new AtomicReference<>(null); private final AtomicReference<IFullDataSourceRetrievalQueue> worldGenQueueRef = new AtomicReference<>(null);
private final ArrayList<IOnWorldGenCompleteListener> onWorldGenTaskCompleteListeners = new ArrayList<>(); private final ArrayList<IOnWorldGenCompleteListener> onWorldGenTaskCompleteListeners = new ArrayList<>();
protected final DelayedDataSourceSaveCache delayedFullDataSourceSaveCache = new DelayedDataSourceSaveCache(this::onDataSourceSaveAsync, 10_000); protected final DelayedFullDataSourceSaveCache delayedFullDataSourceSaveCache = new DelayedFullDataSourceSaveCache(this::onDataSourceSaveAsync, 10_000);
private final ConcurrentHashMap<Long, CompletableFuture<DataSourceRetrievalResult>> queuedRetrievalFutureByPos = new ConcurrentHashMap<>();
@@ -133,17 +137,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
LodUtil.assertTrue(genTaskResult.dataSource != null, "Successful retrieval object should have a datasource."); LodUtil.assertTrue(genTaskResult.dataSource != null, "Successful retrieval object should have a datasource.");
this.dataUpdater.updateDataSource(genTaskResult.dataSource); this.dataUpdater.updateDataSource(genTaskResult.dataSource);
this.fireOnGenPosSuccessListeners(genTaskResult.pos);
// synchronized to prevent a rare issue where the world generator is being shut down while this listener is firing
synchronized (this.onWorldGenTaskCompleteListeners)
{
// fire the event listeners
for (IOnWorldGenCompleteListener listener : this.onWorldGenTaskCompleteListeners)
{
listener.onWorldGenTaskComplete(genTaskResult.pos);
}
}
genTaskResult.dataSource.close(); genTaskResult.dataSource.close();
} }
else if (genTaskResult.state == ERetrievalResultState.REQUIRES_SPLITTING) else if (genTaskResult.state == ERetrievalResultState.REQUIRES_SPLITTING)
@@ -161,9 +155,19 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
{ {
LOGGER.error("Unexpected issue during onWorldGenTaskComplete, error: ["+e.getMessage()+"].", e); LOGGER.error("Unexpected issue during onWorldGenTaskComplete, error: ["+e.getMessage()+"].", e);
} }
finally }
// TODO only fire after the section has finished generated or once every X seconds
private void fireOnGenPosSuccessListeners(long pos)
{
// synchronized to prevent a rare issue where the world generator is being shut down while this listener is firing
synchronized (this.onWorldGenTaskCompleteListeners)
{ {
this.queuedRetrievalFutureByPos.remove(genPos); // fire the event listeners
for (IOnWorldGenCompleteListener listener : this.onWorldGenTaskCompleteListeners)
{
listener.onWorldGenTaskComplete(pos);
}
} }
} }
@@ -218,28 +222,45 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
} }
// we can't queue anything if the world generator isn't set up yet
IFullDataSourceRetrievalQueue worldGenQueue = this.worldGenQueueRef.get(); IFullDataSourceRetrievalQueue worldGenQueue = this.worldGenQueueRef.get();
if (worldGenQueue == null) if (worldGenQueue == null)
{ {
// we can't queue anything if the world generator isn't set up yet
return false; return false;
} }
PriorityTaskPicker.Executor renderLoadExecutor = ThreadPoolUtil.getRenderLoadingExecutor();
if (renderLoadExecutor == null
|| renderLoadExecutor.getQueueSize() >= FullDataUpdatePropagatorV2.getMaxPropagateTaskCount() / 2)
{
// don't queue additional world gen requests if the render loader handler is overwhelmed,
// otherwise LODs may not load in properly
return false;
}
PriorityTaskPicker.Executor fileHandlerExecutor = ThreadPoolUtil.getFileHandlerExecutor();
if (fileHandlerExecutor == null
|| fileHandlerExecutor.getQueueSize() >= FullDataUpdatePropagatorV2.getMaxPropagateTaskCount() / 2)
{
// don't queue additional world gen requests if the file handler is overwhelmed,
// otherwise LODs may not load in properly
return false;
}
int maxQueuedChunkCount = MAX_WORLD_GEN_REQUESTS_PER_THREAD * Config.Common.MultiThreading.numberOfThreads.get(); // for now we're just using the same logic as the world gen threads, it works well enough
if (SharedApi.INSTANCE.getQueuedChunkUpdateCount() >= maxQueuedChunkCount)
{
// don't queue additional world gen requests if there are
// a lot of chunks waiting to update
// (this is done to reduce thread starvation for chunk updates)
return false;
}
int maxWorldGenQueueCount = MAX_WORLD_GEN_REQUESTS_PER_THREAD * Config.Common.MultiThreading.numberOfThreads.get(); int maxWorldGenQueueCount = MAX_WORLD_GEN_REQUESTS_PER_THREAD * Config.Common.MultiThreading.numberOfThreads.get();
int currentQueueCount = WorldChunkUpdateManager.INSTANCE.getTotalQueuedCount();
// don't queue additional world gen requests if there are
// a lot of chunks waiting to update
if (currentQueueCount >= maxWorldGenQueueCount)
{
return false;
}
if (this.delayedFullDataSourceSaveCache.getUnsavedCount() >= maxWorldGenQueueCount) if (this.delayedFullDataSourceSaveCache.getUnsavedCount() >= maxWorldGenQueueCount)
{ {
// don't queue additional world gen requests if there are // don't queue additional world gen requests if there are
@@ -286,14 +307,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
} }
CompletableFuture<DataSourceRetrievalResult> worldGenFuture = worldGenQueue.submitRetrievalTask(genPos, (byte) (DhSectionPos.getDetailLevel(genPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)); CompletableFuture<DataSourceRetrievalResult> worldGenFuture = worldGenQueue.submitRetrievalTask(genPos, (byte) (DhSectionPos.getDetailLevel(genPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL));
worldGenFuture.whenComplete((r, e) -> this.onWorldGenTaskComplete(genPos, r, e));
// only queue the when-complete once for each world gen task,
// otherwise we can end up trying to close the same datasource multiple times
CompletableFuture<DataSourceRetrievalResult> oldWorldGenFuture = this.queuedRetrievalFutureByPos.putIfAbsent(genPos, worldGenFuture);
if (oldWorldGenFuture == null)
{
worldGenFuture.whenComplete((r, e) -> this.onWorldGenTaskComplete(genPos, r, e));
}
return worldGenFuture; return worldGenFuture;
} }
@@ -337,7 +351,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
// don't check any child positions if this position is already fully generated // don't check any child positions if this position is already fully generated
if (this.repo.existsWithKey(pos)) if (this.repo.existsWithKey(pos))
{ {
try(PhantomArrayListCheckout checkout = ARRAY_LIST_POOL.checkoutByteArrays(1)) try(PhantomArrayListCheckout checkout = ARRAY_LIST_POOL.checkoutArrays(1, 0, 0))
{ {
ByteArrayList columnGenStepArray = checkout.getByteArray(0, FullDataSourceV2.WIDTH*FullDataSourceV2.WIDTH); ByteArrayList columnGenStepArray = checkout.getByteArray(0, FullDataSourceV2.WIDTH*FullDataSourceV2.WIDTH);
this.repo.getColumnGenerationStepForPos(pos, columnGenStepArray); this.repo.getColumnGenerationStepForPos(pos, columnGenStepArray);
@@ -384,7 +398,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
{ {
EDhApiWorldGenerationStep currentMinWorldGenStep = EDhApiWorldGenerationStep.LIGHT; EDhApiWorldGenerationStep currentMinWorldGenStep = EDhApiWorldGenerationStep.LIGHT;
try(PhantomArrayListCheckout checkout = ARRAY_LIST_POOL.checkoutByteArrays(1)) try(PhantomArrayListCheckout checkout = ARRAY_LIST_POOL.checkoutArrays(1, 0, 0))
{ {
ByteArrayList columnGenerationSteps = checkout.getByteArray(0, FullDataSourceV2.WIDTH*FullDataSourceV2.WIDTH); ByteArrayList columnGenerationSteps = checkout.getByteArray(0, FullDataSourceV2.WIDTH*FullDataSourceV2.WIDTH);
this.repo.getColumnGenerationStepForPos(genPos, columnGenerationSteps); this.repo.getColumnGenerationStepForPos(genPos, columnGenerationSteps);
@@ -1,12 +1,9 @@
package com.seibel.distanthorizons.core.file.fullDatafile.V1; package com.seibel.distanthorizons.core.file.fullDatafile.V1;
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV1; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV1;
import com.seibel.distanthorizons.core.file.structure.ISaveStructure; import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV1DTO; import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV1DTO;
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo; import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
@@ -32,8 +29,6 @@ public class FullDataSourceProviderV1<TDhLevel extends IDhLevel>
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("V1DTO");
protected final ReentrantLock closeLock = new ReentrantLock(); protected final ReentrantLock closeLock = new ReentrantLock();
protected volatile boolean isShutdown = false; protected volatile boolean isShutdown = false;
@@ -69,9 +64,7 @@ public class FullDataSourceProviderV1<TDhLevel extends IDhLevel>
protected FullDataSourceV1 createDataSourceFromDto(FullDataSourceV1DTO dto) throws InterruptedException, IOException, DataCorruptedException protected FullDataSourceV1 createDataSourceFromDto(FullDataSourceV1DTO dto) throws InterruptedException, IOException, DataCorruptedException
{ {
FullDataSourceV1 dataSource = FullDataSourceV1.createEmpty(dto.pos); FullDataSourceV1 dataSource = FullDataSourceV1.createEmpty(dto.pos);
try (PhantomArrayListCheckout checkout = ARRAY_LIST_POOL.checkoutByteArrays(1); try (DhDataInputStream inputStream = dto.getInputStream())
// LZ4 was used by DH before 2.1.0 and as such must be used until the data format is changed to record the compressor)
DhDataInputStream inputStream = DhDataInputStream.create(dto.dataArray, EDhApiDataCompressionMode.LZ4, checkout))
{ {
dataSource.populateFromStream(dto, inputStream, this.level); dataSource.populateFromStream(dto, inputStream, this.level);
} }
@@ -9,6 +9,8 @@ import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen; import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import java.io.File; import java.io.File;
@@ -20,7 +22,7 @@ import java.util.List;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
public class DataMigratorV1 implements AutoCloseable public class DataMigratorV1 implements IDebugRenderable, AutoCloseable
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
@@ -330,6 +332,12 @@ public class DataMigratorV1 implements AutoCloseable
// overrides // // overrides //
//===========// //===========//
@Override
public void debugRender(DebugRenderer renderer)
{
// nothing currently needed
}
@Override @Override
public void close() public void close()
{ {
@@ -22,7 +22,6 @@ package com.seibel.distanthorizons.core.file.fullDatafile.V2;
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode; import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection; import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.file.fullDatafile.IDataSourceUpdateListenerFunc; import com.seibel.distanthorizons.core.file.fullDatafile.IDataSourceUpdateListenerFunc;
import com.seibel.distanthorizons.core.file.structure.ISaveStructure; import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
@@ -31,7 +30,7 @@ import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO; import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo; import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
@@ -57,9 +56,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final AbstractDebugWireframeRenderer DEBUG_WIREFRAME_RENDERER = SingletonInjector.INSTANCE.get(AbstractDebugWireframeRenderer.class);
private static final Set<String> CORRUPT_DATA_ERRORS_LOGGED = Collections.newSetFromMap(new ConcurrentHashMap<>()); private static final Set<String> CORRUPT_DATA_ERRORS_LOGGED = Collections.newSetFromMap(new ConcurrentHashMap<>());
/** /**
@@ -111,7 +108,7 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
this.updatePropagator = new FullDataUpdatePropagatorV2(this, this.dataUpdater, this.levelId); this.updatePropagator = new FullDataUpdatePropagatorV2(this, this.dataUpdater, this.levelId);
this.dataMigratorV1 = new DataMigratorV1(this.dataUpdater, this.level, this.levelId, this.saveDir); this.dataMigratorV1 = new DataMigratorV1(this.dataUpdater, this.level, this.levelId, this.saveDir);
DEBUG_WIREFRAME_RENDERER.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showFullDataUpdateStatus); DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showFullDataUpdateStatus);
} }
@@ -450,10 +447,11 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
//===========// //===========//
@Override @Override
public void debugRender(AbstractDebugWireframeRenderer renderer) public void debugRender(DebugRenderer renderer)
{ {
this.dataUpdater.debugRender(renderer); this.dataUpdater.debugRender(renderer);
this.updatePropagator.debugRender(renderer); this.updatePropagator.debugRender(renderer);
this.dataMigratorV1.debugRender(renderer);
} }
@Override @Override
@@ -467,8 +465,6 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
this.updatePropagator.close(); this.updatePropagator.close();
this.dataMigratorV1.close(); this.dataMigratorV1.close();
DEBUG_WIREFRAME_RENDERER.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showFullDataUpdateStatus);
this.repo.close(); this.repo.close();
} }
@@ -7,13 +7,14 @@ import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.ThreadUtil; import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker; import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.jetbrains.annotations.Nullable;
import java.awt.*; import java.awt.*;
import java.util.HashMap; import java.util.HashMap;
@@ -34,10 +35,11 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
/** indicates how long the update queue thread should wait between queuing ticks */ /** indicates how long the update queue thread should wait between queuing ticks */
protected static final int PROPAGATE_QUEUE_THREAD_DELAY_IN_MS = 250; protected static final int PROPAGATE_QUEUE_THREAD_DELAY_IN_MS = 250;
public static final int NUMBER_OF_PARENT_UPDATE_TASKS_PER_THREAD = 10; public static final int NUMBER_OF_PARENT_UPDATE_TASKS_PER_THREAD = 5;
/** how many parent update tasks can be in the queue at once */ /** how many parent update tasks can be in the queue at once */
public static int getMaxPropagateTaskCount() { return NUMBER_OF_PARENT_UPDATE_TASKS_PER_THREAD * Config.Common.MultiThreading.numberOfThreads.get(); } public static int getMaxPropagateTaskCount()
{ return NUMBER_OF_PARENT_UPDATE_TASKS_PER_THREAD * Config.Common.MultiThreading.numberOfThreads.get(); }
@@ -47,11 +49,12 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
*/ */
private final Set<Long> updatingPosSet = ConcurrentHashMap.newKeySet(); private final Set<Long> updatingPosSet = ConcurrentHashMap.newKeySet();
// TODO only run thread if modifications happened recently
/** /**
* It'd be better if we could be told when changes are available, * Will be null on the dedicated server since updates don't need to be propagated,
* then run the update thread, but having a constantly running background * only the highest detail level is needed.
* thread is simpler to deal with and gets the job done.
*/ */
@Nullable
public final ThreadPoolExecutor updateQueueProcessor; public final ThreadPoolExecutor updateQueueProcessor;
private final AtomicBoolean isShutdownRef = new AtomicBoolean(false); private final AtomicBoolean isShutdownRef = new AtomicBoolean(false);
@@ -66,7 +69,6 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
//region
public FullDataUpdatePropagatorV2(FullDataSourceProviderV2 provider, FullDataUpdaterV2 dataUpdater, String levelId) public FullDataUpdatePropagatorV2(FullDataSourceProviderV2 provider, FullDataUpdaterV2 dataUpdater, String levelId)
{ {
@@ -79,14 +81,11 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
this.updateQueueProcessor.execute(this::runUpdateQueue); this.updateQueueProcessor.execute(this::runUpdateQueue);
} }
//endregion
//================// //================//
// parent updates // // parent updates //
//================// //================//
//region
private void runUpdateQueue() private void runUpdateQueue()
{ {
@@ -102,6 +101,8 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
continue; continue;
} }
// TODO it might be worth skipping this logic if no parent updates happened
// update positions closest to the player (if not on a server) // update positions closest to the player (if not on a server)
// to make world gen appear faster // to make world gen appear faster
DhBlockPos targetBlockPos = DhBlockPos.ZERO; DhBlockPos targetBlockPos = DhBlockPos.ZERO;
@@ -135,129 +136,121 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
int maxUpdateTaskCount = getMaxPropagateTaskCount(); int maxUpdateTaskCount = getMaxPropagateTaskCount();
// queue parent updates // queue parent updates
if (executor.getQueueSize() > maxUpdateTaskCount if (executor.getQueueSize() < maxUpdateTaskCount
|| this.updatingPosSet.size() > maxUpdateTaskCount) && this.updatingPosSet.size() < maxUpdateTaskCount)
{ {
return; // get the positions that need to be applied to their parents
} LongArrayList parentUpdatePosList = this.provider.repo.getPositionsToUpdate(targetBlockPos.getX(), targetBlockPos.getZ(), maxUpdateTaskCount);
// get the positions that need to be applied to their parents
LongArrayList parentUpdatePosList = this.provider.repo.getPositionsToUpdate(targetBlockPos.getX(), targetBlockPos.getZ(), maxUpdateTaskCount);
// combine updates together based on their parent
HashMap<Long, HashSet<Long>> updatePosByParentPos = new HashMap<>();
for (Long pos : parentUpdatePosList)
{
updatePosByParentPos.compute(DhSectionPos.getParentPos(pos), (parentPos, updatePosSet) ->
{
if (updatePosSet == null)
{
updatePosSet = new HashSet<>();
}
updatePosSet.add(pos);
return updatePosSet;
});
}
// queue the updates
for (Long parentUpdatePos : updatePosByParentPos.keySet())
{
// stop if there are already a bunch of updates queued
if (this.updatingPosSet.size() > maxUpdateTaskCount
|| executor.getQueueSize() > maxUpdateTaskCount)
{
break;
}
// skip any already-queued positions // combine updates together based on their parent
if (!this.updatingPosSet.add(parentUpdatePos)) HashMap<Long, HashSet<Long>> updatePosByParentPos = new HashMap<>();
for (Long pos : parentUpdatePosList)
{ {
continue; updatePosByParentPos.compute(DhSectionPos.getParentPos(pos), (parentPos, updatePosSet) ->
}
try
{
executor.execute(() ->
{ {
ReentrantLock parentWriteLock = this.dataUpdater.updateLockProvider.getLock(parentUpdatePos); if (updatePosSet == null)
boolean parentLocked = false;
try
{ {
//LOGGER.info("updating parent: "+parentUpdatePos); updatePosSet = new HashSet<>();
}
// Locking the parent before the children should prevent deadlocks. updatePosSet.add(pos);
// TryLock is used instead of lock so this thread can handle a different update. return updatePosSet;
if (parentWriteLock.tryLock()) });
}
// queue the updates
for (Long parentUpdatePos : updatePosByParentPos.keySet())
{
// stop if there are already a bunch of updates queued
if (this.updatingPosSet.size() > maxUpdateTaskCount
|| executor.getQueueSize() > maxUpdateTaskCount
|| !this.updatingPosSet.add(parentUpdatePos))
{
break;
}
try
{
executor.execute(() ->
{
ReentrantLock parentWriteLock = this.dataUpdater.updateLockProvider.getLock(parentUpdatePos);
boolean parentLocked = false;
try
{ {
parentLocked = true; //LOGGER.info("updating parent: "+parentUpdatePos);
this.dataUpdater.lockedPosSet.add(parentUpdatePos);
try (FullDataSourceV2 parentDataSource = this.provider.get(parentUpdatePos)) // Locking the parent before the children should prevent deadlocks.
// TryLock is used instead of lock so this thread can handle a different update.
if (parentWriteLock.tryLock())
{ {
// will return null if the file handler is shutting down parentLocked = true;
if (parentDataSource != null) this.dataUpdater.lockedPosSet.add(parentUpdatePos);
try (FullDataSourceV2 parentDataSource = this.provider.get(parentUpdatePos))
{ {
// apply each child pos to the parent // will return null if the file handler is shutting down
for (Long childPos : updatePosByParentPos.get(parentUpdatePos)) if (parentDataSource != null)
{ {
ReentrantLock childReadLock = this.dataUpdater.updateLockProvider.getLock(childPos); // apply each child pos to the parent
try for (Long childPos : updatePosByParentPos.get(parentUpdatePos))
{ {
childReadLock.lock(); ReentrantLock childReadLock = this.dataUpdater.updateLockProvider.getLock(childPos);
this.dataUpdater.lockedPosSet.add(childPos); try
try (FullDataSourceV2 childDataSource = this.provider.get(childPos))
{ {
// can return null when the file handler is being shut down childReadLock.lock();
if (childDataSource != null) this.dataUpdater.lockedPosSet.add(childPos);
try (FullDataSourceV2 childDataSource = this.provider.get(childPos))
{ {
parentDataSource.updateFromDataSource(childDataSource); // can return null when the file handler is being shut down
if (childDataSource != null)
{
parentDataSource.updateFromDataSource(childDataSource);
}
} }
} }
catch (Exception e)
{
LOGGER.error("Unexpected in parent update propagation for parent pos: ["+DhSectionPos.toString(parentUpdatePos)+"], child pos: [" + DhSectionPos.toString(parentUpdatePos) + "], Error: [" + e.getMessage() + "].", e);
}
finally
{
this.provider.repo.setApplyToParent(childPos, false);
childReadLock.unlock();
this.dataUpdater.lockedPosSet.remove(childPos);
}
} }
catch (Exception e)
if (DhSectionPos.getDetailLevel(parentUpdatePos) < FullDataSourceProviderV2.ROOT_SECTION_DETAIL_LEVEL)
{ {
LOGGER.error("Unexpected in parent update propagation for parent pos: ["+DhSectionPos.toString(parentUpdatePos)+"], child pos: [" + DhSectionPos.toString(parentUpdatePos) + "], Error: [" + e.getMessage() + "].", e); parentDataSource.applyToParent = true;
}
finally
{
this.provider.repo.setApplyToParent(childPos, false);
childReadLock.unlock();
this.dataUpdater.lockedPosSet.remove(childPos);
} }
this.dataUpdater.updateDataSource(parentDataSource);
} }
if (DhSectionPos.getDetailLevel(parentUpdatePos) < FullDataSourceProviderV2.ROOT_SECTION_DETAIL_LEVEL)
{
parentDataSource.applyToParent = true;
}
this.dataUpdater.updateDataSource(parentDataSource);
} }
} }
} }
} finally
finally
{
if (parentLocked)
{ {
parentWriteLock.unlock(); if (parentLocked)
this.dataUpdater.lockedPosSet.remove(parentUpdatePos); {
parentWriteLock.unlock();
this.dataUpdater.lockedPosSet.remove(parentUpdatePos);
}
this.updatingPosSet.remove(parentUpdatePos);
} }
});
this.updatingPosSet.remove(parentUpdatePos); }
} catch (RejectedExecutionException ignore)
}); { /* the executor was shut down, it should be back up shortly and able to accept new jobs */ }
} catch (Exception e)
catch (RejectedExecutionException ignore) {
{ /* the executor was shut down, it should be back up shortly and able to accept new jobs */ } this.updatingPosSet.remove(parentUpdatePos);
catch (Exception e) throw e;
{ }
this.updatingPosSet.remove(parentUpdatePos);
throw e;
} }
} }
} }
@@ -378,20 +371,17 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
} }
} }
//endregion
//===========// //===========//
// overrides // // overrides //
//===========// //===========//
//region
@Override @Override
public void debugRender(AbstractDebugWireframeRenderer renderer) public void debugRender(DebugRenderer renderer)
{ {
this.updatingPosSet this.updatingPosSet
.forEach((pos) -> { renderer.renderBox(new AbstractDebugWireframeRenderer.Box(pos, -32f, 80f, 0.20f, Color.MAGENTA)); }); .forEach((pos) -> { renderer.renderBox(new DebugRenderer.Box(pos, -32f, 80f, 0.20f, Color.MAGENTA)); });
} }
@Override @Override
@@ -403,8 +393,6 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
} }
} }
//endregion
} }
@@ -7,7 +7,7 @@ import com.seibel.distanthorizons.core.file.fullDatafile.IDataSourceUpdateListen
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO; import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.util.threading.PositionalLockProvider; import com.seibel.distanthorizons.core.util.threading.PositionalLockProvider;
@@ -225,18 +225,20 @@ public class FullDataUpdaterV2 implements IDebugRenderable, AutoCloseable
//===========// //===========//
@Override @Override
public void debugRender(AbstractDebugWireframeRenderer renderer) public void debugRender(DebugRenderer renderer)
{ {
this.lockedPosSet this.lockedPosSet
.forEach((pos) -> { renderer.renderBox(new AbstractDebugWireframeRenderer.Box(pos, -32f, 74f, 0.15f, Color.PINK)); }); .forEach((pos) -> { renderer.renderBox(new DebugRenderer.Box(pos, -32f, 74f, 0.15f, Color.PINK)); });
this.queuedUpdateCountsByPos this.queuedUpdateCountsByPos
.forEach((pos, updateCountRef) -> { renderer.renderBox(new AbstractDebugWireframeRenderer.Box(pos, -32f, 80f + (updateCountRef.get() * 16f), 0.20f, Color.WHITE)); }); .forEach((pos, updateCountRef) -> { renderer.renderBox(new DebugRenderer.Box(pos, -32f, 80f + (updateCountRef.get() * 16f), 0.20f, Color.WHITE)); });
} }
@Override @Override
public void close() { this.isShutdownRef.set(true); } public void close()
{
this.isShutdownRef.set(true);
}
} }
@@ -20,14 +20,13 @@
package com.seibel.distanthorizons.core.generation; package com.seibel.distanthorizons.core.generation;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection; import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPosMutable; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPosMutable;
import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.util.FullDataPointUtil; import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
@@ -53,8 +52,6 @@ public class DhLightingEngine
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public static final DhLightingEngine INSTANCE = new DhLightingEngine(); public static final DhLightingEngine INSTANCE = new DhLightingEngine();
private static final AbstractDebugWireframeRenderer DEBUG_RENDERER = SingletonInjector.INSTANCE.get(AbstractDebugWireframeRenderer.class);
/** /**
* Minor garbage collection optimization. <br> * Minor garbage collection optimization. <br>
* Since these objects are always mutated anyway, using a {@link ThreadLocal} will allow us to * Since these objects are always mutated anyway, using a {@link ThreadLocal} will allow us to
@@ -728,9 +725,9 @@ public class DhLightingEngine
// a color can be set to null if you only want to troubleshoot up to a certain light level // a color can be set to null if you only want to troubleshoot up to a certain light level
if (color != null) if (color != null)
{ {
DEBUG_RENDERER.makeParticle( DebugRenderer.makeParticle(
new AbstractDebugWireframeRenderer.BoxParticle( new DebugRenderer.BoxParticle(
new AbstractDebugWireframeRenderer.Box(DhSectionPos.encode((byte) 0, chunkMinX + x, chunkMinZ + z), y, y + 1, 0.2f, color), new DebugRenderer.Box(DhSectionPos.encode((byte) 0, chunkMinX + x, chunkMinZ + z), y, y + 1, 0.2f, color),
10.0, 0f 10.0, 0f
) )
); );
@@ -22,7 +22,7 @@ package com.seibel.distanthorizons.core.generation;
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult; import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.QuadTree.LodQuadTree; import com.seibel.distanthorizons.core.render.LodQuadTree;
import com.seibel.distanthorizons.core.util.objects.RollingAverage; import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import java.io.Closeable; import java.io.Closeable;
@@ -30,16 +30,17 @@ import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
/** /**
* Used to track what some of the full data sources the system currently * Used to track what full data sources the system currently
* wants but doesn't have. <br> * wants but doesn't have. <br>
* IE, what sections should be generated via the world generator. <br><br> * IE, what sections should be generated via the world generator. <br><br>
* *
* Note: <br> * Note: <br>
* This won't contain every position that needs to be retrieved * This won't contain every position that needs to be retrieved
* since that would cause issues when moving or with extreme * (due to causing issues at extreme render distances).
* render distances. <br><br> * TODO does that mean this object isn't necessary or
* should just be renamed since it isn't the full queue <br><br>
* *
* Used by both world gen and server networking. * Use by both world gen and server networking.
* *
* @see LodQuadTree * @see LodQuadTree
*/ */
@@ -48,7 +49,6 @@ public interface IFullDataSourceRetrievalQueue extends Closeable
//=========// //=========//
// getters // // getters //
//=========// //=========//
//region
/** /**
* The largest numerical detail level. <br> * The largest numerical detail level. <br>
@@ -63,14 +63,11 @@ public interface IFullDataSourceRetrievalQueue extends Closeable
*/ */
byte highestDataDetail(); byte highestDataDetail();
//endregion
//=======// //=======//
// setup // // setup //
//=======// //=======//
//region
/** /**
* Starts the retrieval process if not already running, * Starts the retrieval process if not already running,
@@ -81,14 +78,11 @@ public interface IFullDataSourceRetrievalQueue extends Closeable
* */ * */
void startAndSetTargetPos(DhBlockPos2D targetPos); void startAndSetTargetPos(DhBlockPos2D targetPos);
//endregion
//===============// //===============//
// task handling // // task handling //
//===============// //===============//
//region
/** /**
* Generally the retrieval queue should be fairly small, so its faster to iterate over the existing list * Generally the retrieval queue should be fairly small, so its faster to iterate over the existing list
@@ -98,14 +92,11 @@ public interface IFullDataSourceRetrievalQueue extends Closeable
CompletableFuture<DataSourceRetrievalResult> submitRetrievalTask(long pos, byte requiredDataDetail); CompletableFuture<DataSourceRetrievalResult> submitRetrievalTask(long pos, byte requiredDataDetail);
//endregion
//==========// //==========//
// shutdown // // shutdown //
//==========// //==========//
//region
/** Can be used to let any lingering generation requests finish before fully shutting down the system */ /** Can be used to let any lingering generation requests finish before fully shutting down the system */
CompletableFuture<Void> startClosingAsync(boolean cancelCurrentGeneration, boolean alsoInterruptRunning); CompletableFuture<Void> startClosingAsync(boolean cancelCurrentGeneration, boolean alsoInterruptRunning);
@@ -113,14 +104,11 @@ public interface IFullDataSourceRetrievalQueue extends Closeable
@Override @Override
void close(); void close();
//endregion
//===============// //===============//
// debug display // // debug display //
//===============// //===============//
//region
int getWaitingTaskCount(); int getWaitingTaskCount();
int getInProgressTaskCount(); int getInProgressTaskCount();
@@ -141,8 +129,6 @@ public interface IFullDataSourceRetrievalQueue extends Closeable
/** Can be used to determine roughly how fast the world generator is running. */ /** Can be used to determine roughly how fast the world generator is running. */
RollingAverage getRollingAverageChunkGenTimeInMs(); RollingAverage getRollingAverageChunkGenTimeInMs();
//endregion
} }
@@ -9,6 +9,7 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider; import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider;
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult; import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.util.FormatUtil; import com.seibel.distanthorizons.core.util.FormatUtil;
@@ -92,8 +93,7 @@ public class PregenManager
private final AtomicInteger nextSectionSpiralIndex = new AtomicInteger(0); private final AtomicInteger nextSectionSpiralIndex = new AtomicInteger(0);
private final AtomicLong lastTaskFinishTime = new AtomicLong(System.currentTimeMillis()); private final AtomicLong lastTaskFinishTime = new AtomicLong(System.currentTimeMillis());
private RollingAverage averageTaskCompletionIntervalMs = new RollingAverage(1000); private final RollingAverage averageTaskCompletionIntervalMs = new RollingAverage(1000);
private final RollingAverage averageTaskCompletionIntervalMsShort = new RollingAverage(50);
private final AtomicLong lastLogTime = new AtomicLong(); private final AtomicLong lastLogTime = new AtomicLong();
@@ -112,7 +112,6 @@ public class PregenManager
long timeSincePreviousTaskFinish = System.currentTimeMillis() - this.lastTaskFinishTime.getAndSet(System.currentTimeMillis()); long timeSincePreviousTaskFinish = System.currentTimeMillis() - this.lastTaskFinishTime.getAndSet(System.currentTimeMillis());
this.averageTaskCompletionIntervalMs.add(timeSincePreviousTaskFinish); this.averageTaskCompletionIntervalMs.add(timeSincePreviousTaskFinish);
this.averageTaskCompletionIntervalMsShort.add(timeSincePreviousTaskFinish);
PregenState.this.fillPendingQueue(); PregenState.this.fillPendingQueue();
}) })
@@ -182,14 +181,7 @@ public class PregenManager
double chunksToGenerate = Math.ceil(Math.sqrt(this.sectionsToGenerate) / 2 * 4 * 10) / 10; // ceil to nearest 0.1 double chunksToGenerate = Math.ceil(Math.sqrt(this.sectionsToGenerate) / 2 * 4 * 10) / 10; // ceil to nearest 0.1
int chunkRatePerSecond = (int) (1000 / this.averageTaskCompletionIntervalMs.getAverage() * 4 * 4); int chunkRatePerSecond = (int) (1000 / this.averageTaskCompletionIntervalMs.getAverage() * 4 * 4);
double etaMs = this.averageTaskCompletionIntervalMs.getAverage() * (this.sectionsToGenerate - this.nextSectionSpiralIndex.get()); double etaMs = this.averageTaskCompletionIntervalMs.getAverage() * (this.sectionsToGenerate - this.nextSectionSpiralIndex.get());
// Reset long rolling average if short average diverged too much (<0.5 / >2.0)
double averageRatio = this.averageTaskCompletionIntervalMsShort.getAverage() / this.averageTaskCompletionIntervalMs.getAverage();
if (averageRatio < 0.5 || averageRatio > 2.0)
{
this.averageTaskCompletionIntervalMs = new RollingAverage(1000);
}
return MessageFormat.format("Generated radius: {0,number,#.###} / {1,number,#.#} chunks ({2} cps, {3,number,#.###%}), ETA: {4}", return MessageFormat.format("Generated radius: {0,number,#.###} / {1,number,#.#} chunks ({2} cps, {3,number,#.###%}), ETA: {4}",
this.generatedRadius.getValue(), this.generatedRadius.getValue(),
chunksToGenerate, chunksToGenerate,
@@ -1,7 +1,6 @@
package com.seibel.distanthorizons.core.generation; package com.seibel.distanthorizons.core.generation;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult; import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState; import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
import com.seibel.distanthorizons.core.level.DhClientLevel; import com.seibel.distanthorizons.core.level.DhClientLevel;
@@ -48,7 +47,7 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue
public void startAndSetTargetPos(DhBlockPos2D targetPos) { super.tick(targetPos); } public void startAndSetTargetPos(DhBlockPos2D targetPos) { super.tick(targetPos); }
@Override @Override
public byte lowestDataDetail() { return FullDataSourceProviderV2.ROOT_SECTION_DETAIL_LEVEL; } public byte lowestDataDetail() { return LodUtil.BLOCK_DETAIL_LEVEL + 12; } // TODO should be the same as what the server's update propagator can provide
@Override @Override
public byte highestDataDetail() { return LodUtil.BLOCK_DETAIL_LEVEL; } public byte highestDataDetail() { return LodUtil.BLOCK_DETAIL_LEVEL; }
@@ -26,7 +26,6 @@ import com.seibel.distanthorizons.api.objects.data.DhApiChunk;
import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource; import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult; import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalTask; import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalTask;
import com.seibel.distanthorizons.core.level.IDhServerLevel; import com.seibel.distanthorizons.core.level.IDhServerLevel;
@@ -36,7 +35,7 @@ import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.transformers.LodDataBuilder; import com.seibel.distanthorizons.core.dataObjects.transformers.LodDataBuilder;
import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.ExceptionUtil; import com.seibel.distanthorizons.core.util.ExceptionUtil;
import com.seibel.distanthorizons.core.util.LodUtil.AssertFailureException; import com.seibel.distanthorizons.core.util.LodUtil.AssertFailureException;
@@ -61,7 +60,6 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class); private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
private static final AbstractDebugWireframeRenderer DEBUG_RENDERER = SingletonInjector.INSTANCE.get(AbstractDebugWireframeRenderer.class);
private final IDhApiWorldGenerator generator; private final IDhApiWorldGenerator generator;
@@ -80,11 +78,11 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
/** If not null this generator is in the process of shutting down */ /** If not null this generator is in the process of shutting down */
private volatile CompletableFuture<Void> generatorClosingFuture = null; private volatile CompletableFuture<Void> generatorClosingFuture = null;
/** // TODO this logic isn't great and can cause a limit to how many threads could be used for world generation,
* having a single thread queue for world gen can cause a bottleneck, // however it won't cause duplicate requests or concurrency issues, so it will be good enough for now.
* however that's usually only for extremely fast custom generators // A good long term fix may be to either:
* and doesn't generally come up in normal gameplay. // 1. allow the generator to deal with larger sections (let the generator threads split up larger tasks into smaller ones
*/ // 2. batch requests better. instead of sending 4 individual tasks of detail level N, send 1 task of detail level n+1
private final ExecutorService queueingThread = ThreadUtil.makeSingleThreadPool("World Gen Queue"); private final ExecutorService queueingThread = ThreadUtil.makeSingleThreadPool("World Gen Queue");
private boolean generationQueueRunning = false; private boolean generationQueueRunning = false;
private DhBlockPos2D generationTargetPos = DhBlockPos2D.ZERO; private DhBlockPos2D generationTargetPos = DhBlockPos2D.ZERO;
@@ -111,7 +109,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
this.lowestDataDetail = generator.getLargestDataDetailLevel(); this.lowestDataDetail = generator.getLargestDataDetailLevel();
this.highestDataDetail = generator.getSmallestDataDetailLevel(); this.highestDataDetail = generator.getSmallestDataDetailLevel();
DEBUG_RENDERER.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showWorldGenQueue); DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showWorldGenQueue);
LOGGER.info("Created world gen queue"); LOGGER.info("Created world gen queue");
} }
@@ -139,9 +137,6 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
DataSourceRetrievalTask existingGenTask = this.waitingTasks.get(pos); DataSourceRetrievalTask existingGenTask = this.waitingTasks.get(pos);
if (existingGenTask != null) if (existingGenTask != null)
{ {
// if the same future is returned
// the caller shouldn't close the datasource multiple times,
// otherwise issues will occur (holes and/or out-of-place LOD data)
return existingGenTask.future; return existingGenTask.future;
} }
@@ -551,8 +546,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
// only apply to children if we aren't at the bottom of the tree // only apply to children if we aren't at the bottom of the tree
pooledDataSource.applyToChildren = DhSectionPos.getDetailLevel(pooledDataSource.getPos()) > DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL; pooledDataSource.applyToChildren = DhSectionPos.getDetailLevel(pooledDataSource.getPos()) > DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL;
// apply to parents up to the top of the tree pooledDataSource.applyToParent = DhSectionPos.getDetailLevel(pooledDataSource.getPos()) < DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL + 12; // TODO what does this 12 reference?
pooledDataSource.applyToParent = DhSectionPos.getDetailLevel(pooledDataSource.getPos()) < FullDataSourceProviderV2.ROOT_SECTION_DETAIL_LEVEL;
CompletableFuture<Void> lodGenFuture = this.generator.generateLod( CompletableFuture<Void> lodGenFuture = this.generator.generateLod(
chunkPosMin.getX(), chunkPosMin.getZ(), chunkPosMin.getX(), chunkPosMin.getZ(),
@@ -624,7 +618,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
///region debug ///region debug
@Override @Override
public void debugRender(AbstractDebugWireframeRenderer renderer) public void debugRender(DebugRenderer renderer)
{ {
int levelMinY = this.level.getLevelWrapper().getMinHeight(); int levelMinY = this.level.getLevelWrapper().getMinHeight();
int levelMaxY = this.level.getLevelWrapper().getMaxHeight(); int levelMaxY = this.level.getLevelWrapper().getMaxHeight();
@@ -639,7 +633,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
this.waitingTasks.keySet().forEach((Long pos) -> this.waitingTasks.keySet().forEach((Long pos) ->
{ {
renderer.renderBox( renderer.renderBox(
new AbstractDebugWireframeRenderer.Box(pos, levelMinY, maxY, 0.05f, Color.blue) new DebugRenderer.Box(pos, levelMinY, maxY, 0.05f, Color.blue)
); );
}); });
@@ -647,7 +641,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
this.inProgressGenTasksByLodPos.forEach((Long pos, DataSourceRetrievalTask task) -> this.inProgressGenTasksByLodPos.forEach((Long pos, DataSourceRetrievalTask task) ->
{ {
renderer.renderBox( renderer.renderBox(
new AbstractDebugWireframeRenderer.Box(pos, levelMinY, maxY, 0.05f, Color.red) new DebugRenderer.Box(pos, levelMinY, maxY, 0.05f, Color.red)
); );
}); });
} }
@@ -733,7 +727,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
this.generator.close(); this.generator.close();
DEBUG_RENDERER.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showWorldGenQueue); DebugRenderer.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showWorldGenQueue);
try try
@@ -17,15 +17,18 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.seibel.distanthorizons.core.wrapperInterfaces.render.objects; package com.seibel.distanthorizons.core.jar;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.jar.wrapperInterfaces.config.LangWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.config.ILangWrapper;
public interface IUniformBufferWrapper extends IBindable, AutoCloseable public class JarDependencySetup
{ {
void upload(); public static void createInitialBindings()
{
@Override SingletonInjector.INSTANCE.bind(ILangWrapper.class, LangWrapper.INSTANCE);
void close(); LangWrapper.init();
}
} }
@@ -23,36 +23,45 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO; import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV2Repo; import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV2Repo;
import com.seibel.distanthorizons.core.util.NativeDialogUtil;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.jar.gui.BaseJFrame;
import com.seibel.distanthorizons.core.jar.gui.cusomJObject.JBox;
import com.seibel.distanthorizons.core.jar.installer.ModrinthGetter;
import com.seibel.distanthorizons.core.jar.installer.WebDownloader;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.LoggerContext;
import javax.swing.*;
import java.awt.*;
import java.io.*; import java.io.*;
import java.net.URL;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.*; import java.util.*;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
/** /**
* Only partially implemented. * The main class when you run the standalone jar
* No one has asked for the ability to parse/submit DH data outside the game *
* (and those who have gone down that path have all wanted to handle the * @author coolGi
* parsing themselves via their own implementations). <Br>
* But in the off chance someone in the future wants to add this functionality
* James will leave the code he's done so far here as a starting place.
* <Br><Br>
*
* Once built this would be in core/build/libs/DistantHorizons-<Version>-dev-all.jar
*/ */
// Once built it would be in core/build/libs/DistantHorizons-<Version>-dev-all.jar
public class JarMain public class JarMain
{ {
public static final DhLogger LOGGER = new DhLoggerBuilder().build(); public static final DhLogger LOGGER = new DhLoggerBuilder().build();
public static List<String> argList;
public static final boolean isDarkTheme = DarkModeDetector.isDarkMode();
public static boolean isOffline = WebDownloader.netIsAvailable();
// TODO: Rewrite the standalone jar
// Previous version here https://gitlab.com/jeseibel/distant-horizons-core/-/blob/333dc4d0e079777b712c0fff246837104ae9a2b6/core/src/main/java/com/seibel/lod/core/jar/JarMain.java
public static void main(String[] args) public static void main(String[] args)
{ {
List<String> argList = Arrays.asList(args); argList = Arrays.asList(args);
if (!argList.contains("--no-custom-logger")) if (!argList.contains("--no-custom-logger"))
{ {
@@ -63,152 +72,168 @@ public class JarMain
} }
catch (Exception e) catch (Exception e)
{ {
LOGGER.error("Failed to set log4j config. Try running with the [--no-custom-logger] argument", e); LOGGER.error("Failed to set log4j config. Try running with the \"--no-custom-logger\" argument");
e.printStackTrace();
} }
} }
LOGGER.debug("Running " + ModInfo.READABLE_NAME + " standalone jar"); LOGGER.debug("Running " + ModInfo.READABLE_NAME + " standalone jar");
LOGGER.warn("The standalone jar is still a massive WIP, expect bugs"); LOGGER.warn("The standalone jar is still a massive WIP, expect bugs");
LOGGER.debug("Java version " + System.getProperty("java.version")); LOGGER.debug("Java version " + System.getProperty("java.version"));
//LOGGER.debug(argList);
if (args.length == 0 || Arrays.asList(args).contains("--gui"))
Byte exportDetailLevel = null;
Long exportPos = null;
boolean showHelp = argList.contains("help");
if (!showHelp)
{ {
// assume something is wrong unless we find a valid arg set // Sets up the local
showHelp = true; if (JarUtils.accessFile("assets/lod/lang/" + Locale.getDefault().toString().toLowerCase() + ".json") == null)
if (argList.size() == 1)
{ {
// only --export LOGGER.warn("The language setting [" + Locale.getDefault().toString().toLowerCase() + "] isn't allowed yet. Defaulting to [" + Locale.US.toString().toLowerCase() + "].");
showHelp = false; Locale.setDefault(Locale.US);
}
else if (argList.size() == 2)
{
// --export 0
String detailLevelString = argList.get(1);
try
{
exportDetailLevel = Byte.parseByte(detailLevelString);
showHelp = false;
}
catch (NumberFormatException e)
{
LOGGER.error("Unable to parse detail level ["+detailLevelString+"], error: ["+e.getMessage()+"].");
}
}
else if (argList.size() == 4)
{
// --export 0 1 -2
String detailLevelString = argList.get(1);
String posXString = argList.get(2);
String posZString = argList.get(3);
try
{
byte detailLevel = Byte.parseByte(detailLevelString);
int posX = Integer.parseInt(posXString);
int posZ = Integer.parseInt(posZString);
exportPos = DhSectionPos.encode(detailLevel, posX, posZ);
showHelp = false;
}
catch (NumberFormatException e)
{
LOGGER.error("Unable to parse position ["+detailLevelString+"], ["+posXString+"], ["+posZString+"], error: ["+e.getMessage()+"].");
}
} }
JarDependencySetup.createInitialBindings();
startGUI();
} }
else if (argList.get(0).equals("--export"))
if (showHelp)
{ {
LOGGER.info("--export parses the 'DistantHorizons.sqlite' file next to this jar and exports the given data into a CSV file. \n" + Byte exportDetailLevel = null;
"Usage: \n" + Long exportPos = null;
"--export [LOD position Detail Level] [LOD position X] [LOD position Z] \n" +
boolean showHelp = argList.contains("help");
if (!showHelp)
{
// assume something is wrong unless we find a valid arg set
showHelp = true;
if (argList.size() == 1)
{
// only --export
showHelp = false;
}
else if (argList.size() == 2)
{
// --export 0
String detailLevelString = argList.get(1);
try
{
exportDetailLevel = Byte.parseByte(detailLevelString);
showHelp = false;
}
catch (NumberFormatException e)
{
LOGGER.error("Unable to parse detail level ["+detailLevelString+"], error: ["+e.getMessage()+"].");
}
}
else if (argList.size() == 4)
{
// --export 0 1 -2
String detailLevelString = argList.get(1);
String posXString = argList.get(2);
String posZString = argList.get(3);
try
{
byte detailLevel = Byte.parseByte(detailLevelString);
int posX = Integer.parseInt(posXString);
int posZ = Integer.parseInt(posZString);
exportPos = DhSectionPos.encode(detailLevel, posX, posZ);
showHelp = false;
}
catch (NumberFormatException e)
{
LOGGER.error("Unable to parse position ["+detailLevelString+"], ["+posXString+"], ["+posZString+"], error: ["+e.getMessage()+"].");
}
}
}
if (showHelp)
{
LOGGER.info("--export parses the 'DistantHorizons.sqlite' file next to this jar and exports the given data into a CSV file. \n" +
"Usage: \n" +
"--export [LOD position Detail Level] [LOD position X] [LOD position Z] \n" +
"\tExport the given position's data if present. \n" + "\tExport the given position's data if present. \n" +
"\tThe detail level should be absolute, IE 0 = block sized, 1 = 2x2 blocks, etc. \n" + "\tThe detail level should be absolute, IE 0 = block sized, 1 = 2x2 blocks, etc. \n" +
"--export [LOD position Detail Level]\n" + "--export [LOD position Detail Level]\n" +
"\tExport all data for a given detail level.\n" + "\tExport all data for a given detail level.\n" +
"\tThe detail level should be absolute, IE 0 = block sized, 1 = 2x2 blocks, etc. \n" + "\tThe detail level should be absolute, IE 0 = block sized, 1 = 2x2 blocks, etc. \n" +
"--export\n" + "--export\n" +
"\tExport the entire database.\n"); "\tExport the entire database.\n");
return; return;
} }
// find the database file
File dbFile = new File("./DistantHorizons.sqlite");
if (!dbFile.exists())
{
LOGGER.error("Unable to find a database to parse at: ["+dbFile.getAbsolutePath()+"].");
return;
}
// set the export file
File exportFile = new File("DistantHorizons-export.csv");
if (exportFile.isDirectory())
{
LOGGER.error("Export file can't be a folder. Given path: ["+exportFile+"].");
return;
}
// create the export file
try
{
boolean ignored = exportFile.mkdirs(); // we don't care if we're making new directories of if they already exist
if (exportFile.exists())
// find the database file
File dbFile = new File("./DistantHorizons.sqlite");
if (!dbFile.exists())
{ {
LOGGER.error("Export file already exists: ["+exportFile.getAbsolutePath()+"]."); LOGGER.error("Unable to find a database to parse at: ["+dbFile.getAbsolutePath()+"].");
return; return;
} }
else if (exportFile.createNewFile())
// set the export file
File exportFile = new File("DistantHorizons-export.csv"); // TODO allow setting an export folder
if (exportFile.isDirectory())
{ {
LOGGER.error("Failed to create file: ["+exportFile.getAbsolutePath()+"]."); LOGGER.error("Export file can't be a folder. Given path: ["+exportFile+"].");
return; return;
} }
}
catch (Exception e)
{ // create the export file
LOGGER.error("Unable to create export file: ["+exportFile.getAbsolutePath()+"]."); try
return; {
} boolean ignored = exportFile.mkdirs(); // we don't care if we're making new directories of if they already exist
LOGGER.info("LOD data will be exported to ["+exportFile.getAbsolutePath()+"]."); if (exportFile.exists())
{
LOGGER.error("Export file already exists: ["+exportFile.getAbsolutePath()+"].");
FullDataSourceV2Repo repo; return;
try }
{ else if (exportFile.createNewFile())
repo = new FullDataSourceV2Repo(FullDataSourceV2Repo.DEFAULT_DATABASE_TYPE, dbFile); {
} LOGGER.error("Failed to create file: ["+exportFile.getAbsolutePath()+"].");
catch (SQLException | IOException e) return;
{ }
LOGGER.error("Failed to initialize connection with database: ["+exportFile.getAbsolutePath()+"], error: ["+e.getMessage()+"].", e); }
return; catch (Exception e)
} {
LOGGER.error("Unable to create export file: ["+exportFile.getAbsolutePath()+"].");
if (exportPos != null) return;
{ }
exportLodDataAtPosition(repo, exportFile, exportPos);
} LOGGER.info("LOD data will be exported to ["+exportFile.getAbsolutePath()+"].");
else if (exportDetailLevel != null)
{
exportAllAtDetailLevel(repo, exportFile, exportDetailLevel); FullDataSourceV2Repo repo;
} try
else {
{ repo = new FullDataSourceV2Repo(FullDataSourceV2Repo.DEFAULT_DATABASE_TYPE, dbFile);
exportEntireDatabase(repo, exportFile); }
catch (SQLException | IOException e)
{
LOGGER.error("Failed to initialize connection with database: ["+exportFile.getAbsolutePath()+"], error: ["+e.getMessage()+"].", e);
return;
}
if (exportPos != null)
{
exportLodDataAtPosition(repo, exportFile, exportPos);
}
else if (exportDetailLevel != null)
{
exportAllAtDetailLevel(repo, exportFile, exportDetailLevel);
}
else
{
exportEntireDatabase(repo, exportFile);
}
} }
} }
@@ -220,21 +245,239 @@ public class JarMain
LOGGER.error("Unable to find any data at the position ["+DhSectionPos.toString(pos)+"]."); LOGGER.error("Unable to find any data at the position ["+DhSectionPos.toString(pos)+"].");
return; return;
} }
// TODO need a way to create datasources (specifically data mappings) without a MC level object to deserialize with
// In order to finish this, we'd need a way to create datasources (specifically data mappings) without a MC level object to deserialize with.
// Granted it would be possible to just export the mapping data as the raw string data it's stored as.
//dto.createPooledDataSource(); //dto.createPooledDataSource();
} }
private static void exportAllAtDetailLevel(FullDataSourceV2Repo repo, File exportFile, byte detailLevel) private static void exportAllAtDetailLevel(FullDataSourceV2Repo repo, File exportFile, byte detailLevel)
{ {
throw new UnsupportedOperationException("Method Not Implemented"); // TODO
} }
private static void exportEntireDatabase(FullDataSourceV2Repo repo, File exportFile) private static void exportEntireDatabase(FullDataSourceV2Repo repo, File exportFile)
{ {
throw new UnsupportedOperationException("Method Not Implemented"); // TODO
} }
public static void startGUI()
{
// Set up the theme
// System.setProperty("apple.awt.application.appearance", "system");
// if (isDarkTheme)
// FlatDarkLaf.setup();
// else
// FlatLightLaf.setup();
// This is done in BaseJFrame now
// GitlabGetter.init();
ModrinthGetter.init();
System.out.println("WARNING: The standalone jar still work in progress");
// JOptionPane.showMessageDialog(null, "The GUI for the standalone jar isn't made yet\nIf you want to use the mod then put it in your mods folder", "Distant Horizons", JOptionPane.WARNING_MESSAGE);
// if (getOperatingSystem().equals(OperatingSystem.MACOS)) {
// System.out.println("If you want the installer then please use Linux or Windows for the time being.\nMacOS support/testing will come later on");
// }
// Code will be changed later on to allow resizing and work better
BaseJFrame frame = new BaseJFrame(false, true);
frame.addExtraButtons(frame.getWidth(), 0, true, false);
// Buttons which you want to be stacked vertically should be added with this (`frame.add(obj, this);`)
GridBagConstraints verticalLayout = new GridBagConstraints();
verticalLayout.gridy = GridBagConstraints.RELATIVE;
verticalLayout.gridx = 0;
verticalLayout.fill = GridBagConstraints.HORIZONTAL;
verticalLayout.weightx = 1.0;
verticalLayout.anchor = GridBagConstraints.NORTH;
// Selected download
AtomicReference<String> downloadID = new AtomicReference<String>("");
// This is for the panel to show the update description
JPanel modVersionDescriptionPanel = new JPanel(new GridBagLayout());
JScrollPane modVersionDescriptionScroll = new JScrollPane(modVersionDescriptionPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
// Sets all the layout stuff for it
int modDescriptionWidth = 275;
modVersionDescriptionScroll.setBounds(frame.getWidth() - modDescriptionWidth, 225, modDescriptionWidth, frame.getHeight() - 255);
modVersionDescriptionScroll.setBorder(null); // Disables the border
modVersionDescriptionScroll.setWheelScrollingEnabled(true);
// The label
JLabel modVersionDescriptionLabel = new JLabel();
modVersionDescriptionPanel.add(modVersionDescriptionLabel, verticalLayout);
// Finally add it
frame.add(modVersionDescriptionScroll);
// This is for the pannel to select MinecraftVersion
JPanel modMinecraftVersionsPannel = new JPanel(new GridBagLayout());
JScrollPane modMinecraftVersionsScroll = new JScrollPane(modMinecraftVersionsPannel, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
// Sets all the layout stuff for it
modMinecraftVersionsScroll.setBounds(0, 225, 125, frame.getHeight() - 255);
modMinecraftVersionsScroll.setBorder(null); // Disables the border
modMinecraftVersionsScroll.setWheelScrollingEnabled(true);
// List to store all the buttons
ArrayList<JButton> modMinecraftReleaseButtons = new ArrayList<>();
frame.add(modMinecraftVersionsScroll);
// This is for selecting the mod version
JPanel modVersionsPannel = new JPanel(new GridBagLayout());
JScrollPane modVersionsScroll = new JScrollPane(modVersionsPannel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
// Sets all the layout stuff for it
modVersionsScroll.setBounds(125, 225, 100, frame.getHeight() - 255);
modVersionsScroll.setBorder(null); // Disables the border
modVersionsScroll.setWheelScrollingEnabled(true);
// List to store all the buttons
ArrayList<JButton> modReleaseButtons = new ArrayList<>();
frame.add(modVersionsScroll);
// Add all the buttons
for (String mcVer : ModrinthGetter.mcVersions)
{
JButton btn = new JButton(mcVer);
btn.setBackground(UIManager.getColor("Panel.background")); // Does the same thing as removing the background
btn.setBorderPainted(false); // Removes the borders
// btn.setHorizontalAlignment(SwingConstants.LEFT); // Sets the text to be on the left side rather than the center
btn.addActionListener(e -> {
// Clears the selected colors for the rest of the buttons
for (JButton currentBtn : modMinecraftReleaseButtons)
{
currentBtn.setBackground(UIManager.getColor("Panel.background"));
}
btn.setBackground(UIManager.getColor("Button.background")); // Sets this to the selected color
// Clears the minecraft version panel
modVersionsPannel.removeAll();
modReleaseButtons.clear();
// Adds all the buttons for the minecraft panel
for (String modID : ModrinthGetter.mcVerToReleaseID.get(mcVer))
{
// No need to comment most of these as it is the same this as before
JButton btnDownload = new JButton(ModrinthGetter.releaseNames.get(modID));
btnDownload.setBackground(UIManager.getColor("Panel.background"));
btnDownload.setBorderPainted(false);
btnDownload.setHorizontalAlignment(SwingConstants.LEFT);
btnDownload.addActionListener(f -> {
downloadID.set(modID);
for (JButton currentBtn : modReleaseButtons)
{
currentBtn.setBackground(UIManager.getColor("Panel.background"));
}
btnDownload.setBackground(UIManager.getColor("Button.background"));
modVersionDescriptionLabel.setText(
WebDownloader.formatMarkdownToHtml(
ModrinthGetter.changeLogs.get(modID), modDescriptionWidth - 75)
);
modVersionDescriptionPanel.repaint();
});
modVersionsPannel.add(btnDownload, verticalLayout);
modReleaseButtons.add(btnDownload);
}
modVersionsScroll.getVerticalScrollBar().setValue(0); // Reset the scroll bar back to the top
modVersionsPannel.repaint(); // Update the version pannel
frame.validate(); // Update the frame
});
modMinecraftVersionsPannel.add(btn, verticalLayout);
modMinecraftReleaseButtons.add(btn);
}
// Bar at the top
frame.add(new JBox(UIManager.getColor("Separator.foreground"), 0, 220, frame.getWidth(), 5));
// Minecraft version text
JLabel textMcVersionHeader = new JLabel("Minecraft version");
textMcVersionHeader.setBounds(0, 200, 125, 20);
frame.add(textMcVersionHeader);
// Version text
JLabel textVersionHeader = new JLabel("Mod version");
textVersionHeader.setBounds(125, 200, 150, 20);
frame.add(textVersionHeader);
// Stuff for setting the file install path
JFileChooser minecraftDirPop = new JFileChooser();
switch (EPlatform.get())
{
case WINDOWS:
minecraftDirPop.setCurrentDirectory(new File(System.getenv("APPDATA") + "/.minecraft/mods"));
case LINUX:
minecraftDirPop.setCurrentDirectory(new File(System.getProperty("user.home") + "/.minecraft/mods"));
}
minecraftDirPop.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
JButton minecraftDirBtn = new JButton("Click to select install path");
minecraftDirBtn.addActionListener(e -> {
if (minecraftDirPop.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION)
minecraftDirBtn.setText(minecraftDirPop.getSelectedFile().toString());
});
minecraftDirBtn.setBounds(230, frame.getHeight() - 100, 200, 20);
frame.add(minecraftDirBtn);
// Button for the install button
JButton installMod = new JButton("Install " + ModInfo.READABLE_NAME);
installMod.setBounds(230, frame.getHeight() - 70, 200, 20);
installMod.addActionListener(e -> {
if (minecraftDirPop.getSelectedFile() == null)
{
NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, "Please select your install directory", "ok", "warning");
return;
}
URL downloadPath = ModrinthGetter.downloadUrl.get(downloadID.get());
try
{
WebDownloader.downloadAsFile(
downloadPath,
minecraftDirPop.getSelectedFile().toPath().resolve(
ModInfo.NAME + "-" + ModrinthGetter.releaseNames.get(downloadID.get()) + ".jar"
).toFile());
NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, "Installation done. \nYou can now close the installer", "ok", "info");
}
catch (Exception f)
{
NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, "Download failed. Check your internet connection \nStacktrace: " + f.getMessage(), "error", "info");
}
});
frame.add(installMod);
// Fabric installer
// try {
// WebDownloader.downloadAsFile(new URL("https://maven.fabricmc.net/net/fabricmc/fabric-installer/0.11.0/fabric-installer-0.11.0.jar"), new File(System.getProperty("java.io.tmpdir") + "/fabricInstaller.jar"));
// Runtime.getRuntime().exec("java -jar " + System.getProperty("java.io.tmpdir") + "/fabricInstaller.jar");
// } catch (Exception e) {e.printStackTrace();}
frame.addLogo(); // Has to be run at the end cus of a bug with java swing (it may not be a bug but idk how to fix it so I'll call it a bug)
frame.validate(); // Update to add the widgets
frame.setVisible(true); // Start the ui
}
} }
@@ -53,7 +53,6 @@ public class JarUtils
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
//region
static static
{ {
@@ -81,14 +80,11 @@ public class JarUtils
} }
} }
//endregion
//=========// //=========//
// methods // // methods //
//=========// //=========//
//region
/** /**
* Gets the URI of a resource * Gets the URI of a resource
@@ -187,8 +183,27 @@ public class JarUtils
return sb.toString(); return sb.toString();
} }
//endregion
/** Please use the EPlatform enum instead */
@Deprecated
public enum OperatingSystem
{WINDOWS, MACOS, LINUX, NONE} // Easy to use enum for the 3 main os's
/** Please use the EPlatform enum instead */
@Deprecated
public static OperatingSystem getOperatingSystem()
{
// Get the os and turn it into that enum
switch (EPlatform.get())
{
case WINDOWS:
return OperatingSystem.WINDOWS;
case LINUX:
return OperatingSystem.LINUX;
case MACOS:
return OperatingSystem.MACOS;
default:
return OperatingSystem.NONE;
}
}
} }
@@ -0,0 +1,255 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.jar.gui;
import com.seibel.distanthorizons.core.jar.JarUtils;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.wrapperInterfaces.config.ILangWrapper;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.List;
/**
* @author coolGi
*/
// This will be removed later on to make a better ui
// To get colors use https://alvinalexander.com/java/java-uimanager-color-keys-list/
// TODO: Make the code less spaghetti later
public class BaseJFrame extends JFrame
{
public BaseJFrame()
{
init();
}
public BaseJFrame(boolean show, boolean resizable)
{
init();
setVisible(show);
setResizable(resizable);
}
public void init()
{
setTitle(SingletonInjector.INSTANCE.get(ILangWrapper.class).getLang("lod.title"));
try
{
setIconImage(ImageIO.read(JarUtils.accessFile("assets/distanthorizons/icon.png")));
}
catch (Exception e)
{
e.printStackTrace();
}
setSize(720, 480);
setLocationRelativeTo(null); // Puts the window at the middle of the screen
setLayout(new BorderLayout());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
initLookAndFeel();
}
/**
* Buttons for language and theme changing
*
* @param themeOnBottom Puts the theme buttons below the language
* @param rootPosOnLeft Where the start for the x is (on the left of the buttons or on the right)
*/
public void addExtraButtons(int x, int y, boolean themeOnBottom, boolean rootPosOnLeft)
{
// ========== LANGUAGE ==========
int langBoxHeight = 25;
int langBoxWidth = 100;
// Creates a list with all the options in it
List<String> langsToChoose = new ArrayList<>();
try (
final InputStreamReader isr = new InputStreamReader(JarUtils.accessFile("assets/distanthorizons/lang"), StandardCharsets.UTF_8);
final BufferedReader br = new BufferedReader(isr)
)
{
List<Object> col = Collections.unmodifiableList(new ArrayList<>(Arrays.asList(br.lines().toArray())));
for (Object obj : col)
{
langsToChoose.add(((String) obj).replaceAll("\\.json", ""));
}
}
catch (Exception e)
{
e.printStackTrace();
}
// Creates the box
JComboBox<String> languageBox = new JComboBox(new DefaultComboBoxModel(langsToChoose.toArray()));
languageBox.setSelectedIndex(langsToChoose.indexOf(Locale.getDefault().toString().toLowerCase()));
languageBox.addActionListener(e -> {
Locale.setDefault(Locale.forLanguageTag(languageBox.getSelectedItem().toString())); // Change lang on update
});
// Set where it goes
languageBox.setBounds(rootPosOnLeft ? x : x - langBoxWidth, themeOnBottom ? y : y + langBoxHeight, langBoxWidth, langBoxHeight);
// And finally add it
add(languageBox);
// ========== THEMING ========== //
/**
// TODO: Change the theme to a toggle switch rather than having 2 buttons
int themeButtonSize = 25;
JButton lightMode = null;
JButton darkMode = null;
// Try to set the icons for them
try
{
lightMode = new JButton(new ImageIcon(
new FlatSVGIcon(JarUtils.accessFile("assets/distanthorizons/textures/jar/themeLight.svg")).getImage() // Get the image
.getScaledInstance(themeButtonSize, themeButtonSize, Image.SCALE_DEFAULT) // Scale it to the correct size
));
darkMode = new JButton(new ImageIcon(
new FlatSVGIcon(JarUtils.accessFile("assets/distanthorizons/textures/jar/themeDark.svg")).getImage() // Get the image
.getScaledInstance(themeButtonSize, themeButtonSize, Image.SCALE_DEFAULT) // Scale it to the correct size
));
}
catch (Exception e)
{
e.printStackTrace();
}
// Where do the buttons go
lightMode.setBounds(rootPosOnLeft ? x : x - (themeButtonSize * 2), themeOnBottom ? y + langBoxHeight : y, themeButtonSize, themeButtonSize);
darkMode.setBounds(rootPosOnLeft ? x + themeButtonSize : x - themeButtonSize, themeOnBottom ? y + langBoxHeight : y, themeButtonSize, themeButtonSize);
// Tell buttons what to do
lightMode.addActionListener(e -> {
FlatLightLaf.setup();
FlatLightLaf.updateUI();
});
darkMode.addActionListener(e -> {
FlatDarkLaf.setup();
FlatDarkLaf.updateUI();
});
// Finally add the buttons
add(lightMode);
add(darkMode);
*/
}
public BaseJFrame addLogo()
{
int logoHeight = 200;
JPanel logo = new JPanel()
{
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
try
{
BufferedImage image = ImageIO.read(JarUtils.accessFile("assets/distanthorizons/logo.png"));
int logoWidth = (int) ((double) logoHeight * ((double) image.getWidth() / (double) image.getHeight())); // Calculate the aspect ratio and set the height correctly to not stretch it
g.drawImage(image, (getWidth() / 2) - (logoWidth / 2), 0, logoWidth, logoHeight, this); // Resize image and draw it
}
catch (Exception e)
{
e.printStackTrace();
}
}
};
logo.setBounds(logo.getX(), logo.getY(), logo.getWidth(), logo.getHeight());
add(logo);
return this;
}
// This part of the code is taken from the official java docs at https://docs.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
// Specify the look and feel to use by defining the LOOKANDFEEL constant
// Valid values are: null (use the default), "Metal", "System", "Motif", and "GTK"
final static String LOOKANDFEEL = "GTK";
private static void initLookAndFeel()
{
String lookAndFeel = null;
if (LOOKANDFEEL != null)
{
if (LOOKANDFEEL.equals("Metal"))
{
lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName();
// an alternative way to set the Metal L&F is to replace the
// previous line with:
// lookAndFeel = "javax.swing.plaf.metal.MetalLookAndFeel";
}
else if (LOOKANDFEEL.equals("System"))
{
lookAndFeel = UIManager.getSystemLookAndFeelClassName();
}
else if (LOOKANDFEEL.equals("Motif"))
{
lookAndFeel = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
}
else if (LOOKANDFEEL.equals("GTK"))
{
lookAndFeel = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel";
}
else
{
System.err.println("Unexpected value of LOOKANDFEEL specified: "
+ LOOKANDFEEL);
lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName();
}
try
{
UIManager.setLookAndFeel(lookAndFeel);
}
catch (ClassNotFoundException e)
{
System.err.println("Couldn't find class for specified look and feel:"
+ lookAndFeel);
System.err.println("Did you include the L&F library in the class path?");
System.err.println("Using the default look and feel.");
}
catch (UnsupportedLookAndFeelException e)
{
System.err.println("Can't use the specified look and feel ("
+ lookAndFeel
+ ") on this platform.");
System.err.println("Using the default look and feel.");
}
catch (Exception e)
{
System.err.println("Couldn't get specified look and feel ("
+ lookAndFeel
+ "), for some reason.");
System.err.println("Using the default look and feel.");
e.printStackTrace();
}
}
}
}
@@ -0,0 +1,179 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.jar.gui;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.ConstructorProperties;
import java.io.IOException;
import java.io.ObjectOutputStream;
import javax.swing.*;
import javax.swing.plaf.ButtonUI;
/**
* A switch button cus Java dosnt have one
*
* <p>
* Ever wanted a switch like
* <code><a href="https://www.overflowarchives.com/wp-content/uploads/2020/05/overflow-archives-unity-3d-switch-button-ui.jpg">this</a></code>
* or
* <code><a href="https://c8.alamy.com/comp/2E3PHHW/day-night-mode-switch-ui-button-light-dark-mode-slider-theme-2E3PHHW.jpg">this</a></code>?
* Well now you can this this class!
* </p>
*
* Based off Java's JButton
*
* @author coolGi
*/
// TODO: Make this for the theme (and finish the documentation once it is done)
@SuppressWarnings("serial")
public class JSwitch extends AbstractButton
{
private static final String uiClassID = "SwitchUI";
/** Creates a switch with no set text or icons */
public JSwitch()
{
this(null, null, null, null);
}
/**
* Creates a switch with an icon
*
* @param offIcon The deactivated icon image
* @param onIcon The activated icon image
*/
public JSwitch(Icon offIcon, Icon onIcon)
{
this(null, null, offIcon, onIcon);
}
/**
* Creates a switch with text
*
* @param offText the deactivated text of the button
* @param onText the activated text of the button
*/
@ConstructorProperties({"text"})
public JSwitch(String offText, String onText)
{
this(offText, onText, null, null);
}
/**
* Creates a switch where properties are taken from the
* <code>Action</code> supplied.
*
* @param a the <code>Action</code> used to specify the code that runs when pressing
*/
public JSwitch(Action a)
{
this();
setAction(a);
}
/**
* Creates a switch with initial text and an icon
*
* @param offText the deactivated text of the button
* @param onText the activated text of the button
* @param offIcon The deactivated icon image
* @param onIcon The activated icon image
*/
public JSwitch(String offText, String onText, Icon offIcon, Icon onIcon)
{
// Create the model
setModel(new DefaultButtonModel());
// this.trueLabel = trueLabel;
// this.falseLabel = falseLabel;
// double trueLenth = getFontMetrics( getFont() ).getStringBounds( trueLabel, getGraphics() ).getWidth();
// double falseLenght = getFontMetrics( getFont() ).getStringBounds( falseLabel, getGraphics() ).getWidth();
// max = (int)Math.max( trueLenth, falseLenght );
// gap = Math.max( 5, 5+(int)Math.abs(trueLenth - falseLenght ) );
// thumbBounds = new Dimension(max+gap*2,20);
// globalWitdh = max + thumbBounds.width+gap*2;
// setModel( new DefaultButtonModel() );
// setSelected( false );
// addMouseListener( new MouseAdapter() {
// @Override
// public void mouseReleased( MouseEvent e ) {
// if(new Rectangle( getPreferredSize() ).contains( e.getPoint() )) {
// setSelected( !isSelected() );
// }
// }
// });
}
/**
* Resets the UI property to a value from the current look and feel
*
* @see JComponent#updateUI
*/
public void updateUI()
{
setUI((ButtonUI) UIManager.getUI(this));
}
@Override
public void setSelected(boolean b)
{
// if(b){
// setText( trueLabel );
// setBackground( green );
// } else {
// setBackground( red );
// setText( falseLabel );
// }
super.setSelected(b);
}
/**
* Returns a string that specifies the name of the L&amp;F class
* that renders this component.
*
* @return the string "ButtonUI"
* @see JComponent#getUIClassID
* @see UIDefaults#getUI
*/
public String getUIClassID()
{
return uiClassID;
}
/**
* Overrides <code>JComponent.removeNotify</code> to check if
* this button is currently set as the default button on the
* <code>RootPane</code>, and if so, sets the <code>RootPane</code>'s
* default button to <code>null</code> to ensure the
* <code>RootPane</code> doesn't hold onto an invalid button reference.
*/
public void removeNotify()
{
JRootPane root = SwingUtilities.getRootPane(this);
super.removeNotify();
}
}
@@ -0,0 +1,219 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.jar.gui;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
import javax.swing.AbstractButton;
import javax.swing.DefaultButtonModel;
import javax.swing.JLabel;
import javax.swing.UIManager;
import javax.swing.border.Border;
/**
* Taken from https://github.com/sshtools/ui/blob/master/src/main/java/com/sshtools/ui/swing/JSwitchBox.java
*
* @author sshtools
*/
// TODO: Merge this with my own JSwitch rather than use all their code
// TODO: Make it work with the theme rather than do whatever it is doing now
public class JSwitchBox extends AbstractButton
{
private Color shadow1 = UIManager.getColor("controlHighlight");
private Color shadow2 = UIManager.getColor("control");
private Color colorBright = UIManager.getColor("Button.light");
private Color red = UIManager.getColor("controlShadow");
private Color redf = UIManager.getColor("Button.foreground");
private Color trackBackground = UIManager.getColor("textHighlight");
private Color trackBackgroundText = UIManager.getColor("textHighlightText");
private Border buttonBorder = UIManager.getBorder("Button.border");
private Border trackBorder = UIManager.getBorder("Button.border");
private Font font = new JLabel().getFont();
private int gap = 5;
private int globalWitdh = 0;
private final String trueLabel;
private final String falseLabel;
private Dimension thumbBounds;
private Rectangle2D bounds;
private int max;
public JSwitchBox(String trueLabel, String falseLabel)
{
setBackground(UIManager.getColor("Panel.background"));
this.trueLabel = trueLabel;
this.falseLabel = falseLabel;
FontMetrics fontMetrics = getFontMetrics(getFont());
double trueLenth = fontMetrics
.getStringBounds(trueLabel, getGraphics()).getWidth();
double falseLenght = fontMetrics.getStringBounds(falseLabel,
getGraphics()).getWidth();
max = (int) Math.max(trueLenth, falseLenght);
gap = Math.max(5, 5 + (int) Math.abs(trueLenth - falseLenght));
thumbBounds = new Dimension(max + gap * 2,
(int) ((float) fontMetrics.getHeight() * 1.5));
globalWitdh = max + thumbBounds.width + gap * 2;
setModel(new DefaultButtonModel());
setSelected(false);
addMouseListener(new MouseAdapter()
{
@Override
public void mouseReleased(MouseEvent e)
{
if (new Rectangle(getPreferredSize()).contains(e.getPoint()))
{
setSelected(!isSelected());
}
}
});
}
@Override
public Dimension getPreferredSize()
{
return new Dimension(globalWitdh, thumbBounds.height);
}
@Override
public void setSelected(boolean b)
{
if (b)
{
setText(trueLabel);
setBackground(trackBackground);
setForeground(trackBackgroundText);
}
else
{
setBackground(red);
setForeground(redf);
setText(falseLabel);
}
super.setSelected(b);
}
@Override
public void setText(String text)
{
super.setText(text);
}
@Override
public int getHeight()
{
return getPreferredSize().height;
}
@Override
public int getWidth()
{
return getPreferredSize().width;
}
@Override
public Font getFont()
{
return font;
}
@Override
protected void paintComponent(Graphics g)
{
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight() - 4);
Graphics2D g2 = (Graphics2D) g;
// g2.setColor(black);
// g2.drawRoundRect(1, 1, getWidth() - 2 - 1, getHeight() - 2 - 1, 2,
// 2);
// g2.setColor(white);
// g2.drawRoundRect(1 + 1, 1 + 1, getWidth() - 2 - 3, getHeight() - 2 -
// 3,
// 2, 2);
trackBorder.paintBorder(this, g2, 0, 2, getWidth(), getHeight() - 4);
int buttonX = 0;
int textX = 0;
if (isSelected())
{
textX = thumbBounds.width;
}
else
{
buttonX = thumbBounds.width;
}
int y = 0;
int w = thumbBounds.width;
int h = thumbBounds.height;
g2.setPaint(new GradientPaint(buttonX, (int) (y - 0.1 * h), shadow2,
buttonX, (int) (y + 1.2 * h), shadow1));
g2.fillRect(buttonX, y, w, h);
g2.setPaint(new GradientPaint(buttonX, (int) (y + .65 * h), shadow1,
buttonX, (int) (y + 1.3 * h), shadow2));
g2.fillRect(buttonX, (int) (y + .65 * h), w, (int) (h - .65 * h));
if (w > 14)
{
int size = 10;
g2.setColor(colorBright);
g2.fillRect(buttonX + w / 2 - size / 2, y + h / 2 - size / 2, size,
size);
g2.setColor(colorBright.darker());
g2.fillRect(buttonX + w / 2 - 4, h / 2 - 4, 2, 2);
g2.fillRect(buttonX + w / 2 - 1, h / 2 - 4, 2, 2);
g2.fillRect(buttonX + w / 2 + 2, h / 2 - 4, 2, 2);
g2.setColor(colorBright.darker().darker());
g2.fillRect(buttonX + w / 2 - 4, h / 2 - 2, 2, 6);
g2.fillRect(buttonX + w / 2 - 1, h / 2 - 2, 2, 6);
g2.fillRect(buttonX + w / 2 + 2, h / 2 - 2, 2, 6);
g2.setColor(colorBright.darker());
g2.fillRect(buttonX + w / 2 - 4, h / 2 + 2, 2, 2);
g2.fillRect(buttonX + w / 2 - 1, h / 2 + 2, 2, 2);
g2.fillRect(buttonX + w / 2 + 2, h / 2 + 2, 2, 2);
}
buttonBorder.paintBorder(this, g2, buttonX, y, w, h);
// g2.setColor(black);
// g2.drawRoundRect(x, y, w - 1, h - 1, 2, 2);
// g2.setColor(white);
// g2.drawRoundRect(x + 1, y + 1, w - 3, h - 3, 2, 2);
g2.setColor(getForeground());
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2.setFont(getFont());
g2.drawString(getText(), textX + gap, y + h / 2 + h / 4);
}
}
@@ -0,0 +1,75 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.jar.gui.cusomJObject;
import javax.swing.*;
import java.awt.*;
/**
* A rectangular box that can be placed with java swing
*
* @author coolGi
*/
public class JBox extends JComponent
{
private static final String uiClassID = "BoxBarUI";
private Color color;
private int x;
private int y;
private int width;
private int height;
public JBox()
{
this(null);
}
public JBox(Color color)
{
this.color = color;
}
public JBox(Color color, Rectangle rectangle)
{
this(color, rectangle.x, rectangle.y, rectangle.width, rectangle.height);
}
public JBox(Color color, int x, int y, int width, int height)
{
this.color = color;
setBounds(x, y, width, height);
}
public void setColor(Color color)
{
this.color = color;
}
@Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor(color);
g.fillRect(0, 0, getBounds().width, getBounds().height);
}
}
@@ -85,6 +85,7 @@ public class WebDownloader
totalDataRead = totalDataRead + i; totalDataRead = totalDataRead + i;
bout.write(data, 0, i); bout.write(data, 0, i);
// TODO: Link this to an atomic integer rather than printing it to log
int newPercent = (int) ((totalDataRead * 100) / filesize); int newPercent = (int) ((totalDataRead * 100) / filesize);
if (percent != newPercent) if (percent != newPercent)
{ {
@@ -99,6 +100,7 @@ public class WebDownloader
public static String downloadAsString(URL url) throws Exception public static String downloadAsString(URL url) throws Exception
{ {
StringBuilder stringBuilder = new StringBuilder(); StringBuilder stringBuilder = new StringBuilder();
// URL url = new URL(urlS);
URLConnection urlConnection = url.openConnection(); URLConnection urlConnection = url.openConnection();
urlConnection.setConnectTimeout(1000); urlConnection.setConnectTimeout(1000);
@@ -345,10 +345,9 @@ public class SelfUpdater
nextByte = inputStream.read(); nextByte = inputStream.read();
byteReadIndex++; byteReadIndex++;
// It would be better to change this divisor based on the expected size, // TODO it would be better to change this divisor based on the expected size,
// so it would always be split up into 100 1% increments // so it would always be split up into 100 1% increments
// but this works well enough. // but this will work for now when the expected size is about 17 MB, this will log about 170 times
// When the expected size is about 17 MB, this will log about 170 times
if (byteReadIndex % 100_000 == 0) if (byteReadIndex % 100_000 == 0)
{ {
LOGGER.info("Decompressing ["+outputFormat.format(((double)byteReadIndex / expectedSize)*100.0)+"]%"); LOGGER.info("Decompressing ["+outputFormat.format(((double)byteReadIndex / expectedSize)*100.0)+"]%");
@@ -19,33 +19,45 @@
package com.seibel.distanthorizons.core.level; package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiChunkModifiedEvent; import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiChunkModifiedEvent;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.render.renderer.CloudRenderHandler; import com.seibel.distanthorizons.core.file.fullDatafile.DelayedFullDataSourceSaveCache;
import com.seibel.distanthorizons.core.util.delayedSaveCache.DelayedBeaconSaveCache;
import com.seibel.distanthorizons.core.util.delayedSaveCache.DelayedDataSourceSaveCache;
import com.seibel.distanthorizons.core.generation.DhLightingEngine; import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.render.renderer.generic.CloudRenderHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO; import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.sql.dto.ChunkHashDTO; import com.seibel.distanthorizons.core.sql.dto.ChunkHashDTO;
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo; import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo; import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo;
import com.seibel.distanthorizons.core.sql.repo.ChunkHashRepo; import com.seibel.distanthorizons.core.sql.repo.ChunkHashRepo;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.KeyedLockContainer; import com.seibel.distanthorizons.core.util.KeyedLockContainer;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector; import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@@ -65,8 +77,8 @@ public abstract class AbstractDhLevel implements IDhLevel
public BeaconBeamRepo beaconBeamRepo; public BeaconBeamRepo beaconBeamRepo;
protected final KeyedLockContainer<Long> beaconUpdateLockContainer = new KeyedLockContainer<>(); protected final KeyedLockContainer<Long> beaconUpdateLockContainer = new KeyedLockContainer<>();
protected final DelayedDataSourceSaveCache delayedFullDataSourceSaveCache = new DelayedDataSourceSaveCache(this::onDataSourceSaveAsync, 1_000);
protected final DelayedBeaconSaveCache delayedBeaconSaveCache = new DelayedBeaconSaveCache(this::updateBeaconBeamsBetweenBlockPos, 1_000); protected final DelayedFullDataSourceSaveCache delayedFullDataSourceSaveCache = new DelayedFullDataSourceSaveCache(this::onDataSourceSaveAsync, 3_000);
/** contains the {@link DhChunkPos} for each {@link DhSectionPos} that are queued to save */ /** contains the {@link DhChunkPos} for each {@link DhSectionPos} that are queued to save */
protected final ConcurrentHashMap<Long, HashSet<DhChunkPos>> updatedChunkPosSetBySectionPos = new ConcurrentHashMap<>(); protected final ConcurrentHashMap<Long, HashSet<DhChunkPos>> updatedChunkPosSetBySectionPos = new ConcurrentHashMap<>();
protected final ConcurrentHashMap<DhChunkPos, Integer> updatedChunkHashesByChunkPos = new ConcurrentHashMap<>(); protected final ConcurrentHashMap<DhChunkPos, Integer> updatedChunkHashesByChunkPos = new ConcurrentHashMap<>();
@@ -80,7 +92,6 @@ public abstract class AbstractDhLevel implements IDhLevel
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
//region
protected AbstractDhLevel() { } protected AbstractDhLevel() { }
@@ -119,16 +130,15 @@ public abstract class AbstractDhLevel implements IDhLevel
/** handles any setup that needs the repos to be created */ /** handles any setup that needs the repos to be created */
protected void runRepoReliantSetup() protected void runRepoReliantSetup()
{ {
IDhGenericRenderer genericRenderer = this.getGenericRenderer(); GenericObjectRenderer genericRenderer = this.getGenericRenderer();
if (genericRenderer != null) if (genericRenderer != null)
{ {
// only client levels can render clouds // only client levels can render clouds
if (this instanceof IDhClientLevel) if (this instanceof IDhClientLevel)
{ {
// only add clouds for certain dimension types // only add clouds for certain dimension types
String enabledCloudDimensions = Config.Client.Advanced.Graphics.GenericRendering.dimensionEnabledCloudRenderingCsv.get(); if (!this.getLevelWrapper().hasCeiling()
String dimName = this.getLevelWrapper().getDimensionType().getName(); && !this.getLevelWrapper().getDimensionType().isTheEnd())
if (enabledCloudDimensions.contains(dimName))
{ {
this.cloudRenderHandler = new CloudRenderHandler((IDhClientLevel)this, genericRenderer); this.cloudRenderHandler = new CloudRenderHandler((IDhClientLevel)this, genericRenderer);
} }
@@ -136,14 +146,11 @@ public abstract class AbstractDhLevel implements IDhLevel
} }
} }
//endregion
//=================// //=================//
// default methods // // default methods //
//=================// //=================//
//region
@Override @Override
public void updateChunkAsync(IChunkWrapper chunkWrapper, int chunkHash) public void updateChunkAsync(IChunkWrapper chunkWrapper, int chunkHash)
@@ -170,7 +177,7 @@ public abstract class AbstractDhLevel implements IDhLevel
// merging writes together in memory significantly improves throughput, since most // merging writes together in memory significantly improves throughput, since most
// chunk modifications will be right next to each other, IE effecting the same LODs // chunk modifications will be right next to each other, IE effecting the same LODs
this.delayedFullDataSourceSaveCache.writeToMemoryAndQueueSave(dataSource); this.delayedFullDataSourceSaveCache.writeDataSourceToMemoryAndQueueSave(dataSource);
} }
} }
@@ -212,14 +219,13 @@ public abstract class AbstractDhLevel implements IDhLevel
}); });
} }
//endregion
//=======// //=======//
// repos // // repos //
//=======// //=======//
//region
// chunk hash //
@Override @Override
public int getChunkHash(DhChunkPos pos) public int getChunkHash(DhChunkPos pos)
@@ -233,26 +239,23 @@ public abstract class AbstractDhLevel implements IDhLevel
return (dto != null) ? dto.chunkHash : 0; return (dto != null) ? dto.chunkHash : 0;
} }
//endregion
//=================// //=================//
// beacon handling // // beacon handling //
//=================// //=================//
//region
@Override @Override
public void updateBeaconBeamsForSectionPos(long sectionPos, List<BeaconBeamDTO> activeBeamList) public void updateBeaconBeamsForSectionPos(long sectionPos, List<BeaconBeamDTO> activeBeamList)
{ {
int minBlockX = DhSectionPos.getMinCornerBlockX(sectionPos); int minBlockX = DhSectionPos.getMinCornerBlockX(sectionPos);
int minBlockZ = DhSectionPos.getMinCornerBlockZ(sectionPos); int minBlockZ = DhSectionPos.getMinCornerBlockZ(sectionPos);
// TODO special logic had to be done for DhChunkPos.getMaxBlock,
// does that need to be done here?
// The DhChunkPos issue caused beacons to appear/disappear incorrectly on negative chunk borders
int maxBlockX = minBlockX + DhSectionPos.getBlockWidth(sectionPos); int maxBlockX = minBlockX + DhSectionPos.getBlockWidth(sectionPos);
int maxBlockZ = minBlockZ + DhSectionPos.getBlockWidth(sectionPos); int maxBlockZ = minBlockZ + DhSectionPos.getBlockWidth(sectionPos);
// no delayed cache needed since each hit will be at a different position
// and this is generally called when LODs are received from the
// server, so repeat hits to the same position are unlikely
this.updateBeaconBeamsBetweenBlockPos( this.updateBeaconBeamsBetweenBlockPos(
sectionPos, sectionPos,
minBlockX, maxBlockX, minBlockX, maxBlockX,
@@ -263,11 +266,22 @@ public abstract class AbstractDhLevel implements IDhLevel
@Override @Override
public void updateBeaconBeamsForChunkPos(DhChunkPos chunkPos, List<BeaconBeamDTO> activeBeamList) public void updateBeaconBeamsForChunkPos(DhChunkPos chunkPos, List<BeaconBeamDTO> activeBeamList)
{ {
// a delayed cache is used to prevent lock contention long sectionPos = DhSectionPos.encodeContaining(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, chunkPos);
// when flying through new chunks (or updating modified chunks)
// that are right next to each other (requiring up to 16 hits to the same LOD position int minBlockX = chunkPos.getMinBlockX();
this.delayedBeaconSaveCache.queueBeaconBeamUpdatesForChunkPos(chunkPos, activeBeamList); int minBlockZ = chunkPos.getMinBlockZ();
int maxBlockX = chunkPos.getMaxBlockX();
int maxBlockZ = chunkPos.getMaxBlockZ();
//LOGGER.info("beacons ["+activeBeamList.size()+"] at ["+chunkPos+"] x["+minBlockX+"]-["+maxBlockX+"] z["+minBlockZ+"]-["+maxBlockZ+"].");
this.updateBeaconBeamsBetweenBlockPos(
sectionPos,
minBlockX, maxBlockX,
minBlockZ, maxBlockZ,
activeBeamList
);
} }
private void updateBeaconBeamsBetweenBlockPos( private void updateBeaconBeamsBetweenBlockPos(
@@ -275,7 +289,7 @@ public abstract class AbstractDhLevel implements IDhLevel
int minBlockX, int maxBlockX, int minBlockX, int maxBlockX,
int minBlockZ, int maxBlockZ, int minBlockZ, int maxBlockZ,
List<BeaconBeamDTO> activeBeamList List<BeaconBeamDTO> activeBeamList
) ) // TODO min/max block pos instead
{ {
if (this.beaconBeamRepo == null) if (this.beaconBeamRepo == null)
{ {
@@ -283,11 +297,8 @@ public abstract class AbstractDhLevel implements IDhLevel
} }
//LOGGER.info("beacons ["+activeBeamList.size()+"] at x["+minBlockX+"]-["+maxBlockX+"] z["+minBlockZ+"]-["+maxBlockZ+"]."); // locked to prevent two threads from updating the same section at the same time
ReentrantLock lock = this.beaconUpdateLockContainer.getLockForPos(sectionPosForLock); // TODO this can cause a lot of slow-downs
// locked to prevent two threads from updating the same position at the same time
ReentrantLock lock = this.beaconUpdateLockContainer.getLockForPos(sectionPosForLock);
try try
{ {
lock.lock(); lock.lock();
@@ -318,10 +329,14 @@ public abstract class AbstractDhLevel implements IDhLevel
for (DhBlockPos beaconPos : allPosSet) for (DhBlockPos beaconPos : allPosSet)
{ {
if (minBlockX > beaconPos.getX() || beaconPos.getX() > maxBlockX if (minBlockX <= beaconPos.getX() && beaconPos.getX() <= maxBlockX
|| minBlockZ > beaconPos.getZ() || beaconPos.getZ() > maxBlockZ) && minBlockZ <= beaconPos.getZ() && beaconPos.getZ() <= maxBlockZ)
{
//// don't modify beacons outside the updated range
//continue;
}
else
{ {
// don't modify beacons outside the updated range
continue; continue;
} }
@@ -330,12 +345,12 @@ public abstract class AbstractDhLevel implements IDhLevel
BeaconBeamDTO activeBeam = activeBeamByPos.get(beaconPos); BeaconBeamDTO activeBeam = activeBeamByPos.get(beaconPos);
if (activeBeam != null) if (activeBeam != null)
{ {
//LOGGER.info("add beacon ["+activeBeam.blockPos+"] x["+minBlockX+"]-["+maxBlockX+"] z["+minBlockZ+"]-["+maxBlockZ+"].");
if (existingBeam == null) if (existingBeam == null)
{ {
// new beam found, add to DB // new beam found, add to DB
this.beaconBeamRepo.save(activeBeam); this.beaconBeamRepo.save(activeBeam);
//LOGGER.info("new beacon ["+activeBeam+"].");
} }
else else
{ {
@@ -344,12 +359,6 @@ public abstract class AbstractDhLevel implements IDhLevel
{ {
// beam colors were changed // beam colors were changed
this.beaconBeamRepo.save(activeBeam); this.beaconBeamRepo.save(activeBeam);
//LOGGER.info("change beacon ["+existingBeam+"] -> ["+activeBeam+"].");
}
else
{
//LOGGER.info("existing beacon ["+existingBeam+"].");
} }
} }
} }
@@ -357,8 +366,7 @@ public abstract class AbstractDhLevel implements IDhLevel
{ {
// beam no longer exists at position, remove from DB // beam no longer exists at position, remove from DB
this.beaconBeamRepo.deleteWithKey(beaconPos); this.beaconBeamRepo.deleteWithKey(beaconPos);
//LOGGER.info("remove beacon ["+beaconPos+"] x["+minBlockX+"]-["+maxBlockX+"] z["+minBlockZ+"]-["+maxBlockZ+"].");
//LOGGER.info("remove beacon ["+existingBeam+"].");
} }
} }
} }
@@ -372,8 +380,6 @@ public abstract class AbstractDhLevel implements IDhLevel
@Nullable @Nullable
public BeaconBeamRepo getBeaconBeamRepo() { return this.beaconBeamRepo; } public BeaconBeamRepo getBeaconBeamRepo() { return this.beaconBeamRepo; }
//endregion
//================// //================//
@@ -46,7 +46,8 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
/** /**
* This queue is used for ensuring fair generation speed for each player. <br> * This queue is used for ensuring fair generation speed for each player. <br>
* Every tick the first player gets used for centering generation, and then is immediately moved into the back of the queue. * Every tick the first player gets used for centering generation, and then is immediately moved into the back of the queue. <br>
* TODO only add players that actually have something to generate
*/ */
protected final ConcurrentLinkedQueue<IServerPlayerWrapper> worldGenPlayerCenteringQueue = new ConcurrentLinkedQueue<>(); protected final ConcurrentLinkedQueue<IServerPlayerWrapper> worldGenPlayerCenteringQueue = new ConcurrentLinkedQueue<>();
@@ -98,7 +99,7 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
@Override @Override
public boolean shouldDoWorldGen() public boolean shouldDoWorldGen()
{ return Config.Common.WorldGenerator.enableDistantGeneration.get(); } { return Config.Common.WorldGenerator.enableDistantGeneration.get() && !this.worldGenPlayerCenteringQueue.isEmpty(); }
@Override @Override
public DhBlockPos2D getTargetPosForGeneration() public DhBlockPos2D getTargetPosForGeneration()
@@ -26,12 +26,11 @@ import com.seibel.distanthorizons.core.file.fullDatafile.IDataSourceUpdateListen
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2; import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.QuadTree.LodQuadTree; import com.seibel.distanthorizons.core.render.LodQuadTree;
import com.seibel.distanthorizons.core.render.RenderBufferHandler; import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
@@ -44,7 +43,6 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
private final IDhClientLevel clientLevel; private final IDhClientLevel clientLevel;
@@ -53,12 +51,12 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
public final AtomicReference<ClientRenderState> ClientRenderStateRef = new AtomicReference<>(); public final AtomicReference<ClientRenderState> ClientRenderStateRef = new AtomicReference<>();
/** /**
* This is handled outside of the {@link ClientRenderState} to prevent destroying * This is handled outside of the {@link ClientRenderState} to prevent destroying
* the {@link IDhGenericRenderer} when changing render distances or enabling/disabling rendering. <br><br> * the {@link GenericObjectRenderer} when changing render distances or enabling/disabling rendering. <br><br>
* *
* Destroying the {@link IDhGenericRenderer} would cause any existing bindings to be * Destroying the {@link GenericObjectRenderer} would cause any existing bindings to be
* erroneously removed. * erroneously removed.
*/ */
public final IDhGenericRenderer genericRenderer = WRAPPER_FACTORY.createGenericRenderer(); public final GenericObjectRenderer genericRenderer = new GenericObjectRenderer();
@@ -94,19 +92,27 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
return; return;
} }
// TODO this should probably be handled via a config change listener
// recreate the RenderState if the render distance changes // recreate the RenderState if the render distance changes
if (clientRenderState.viewDiameterInBlocks != ClientRenderState.getViewDiameterInBlocks()) if (clientRenderState.quadtree.blockRenderDistanceDiameter != Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistanceRadius.get() * LodUtil.CHUNK_WIDTH * 2)
{ {
// close the older renderer if (!this.ClientRenderStateRef.compareAndSet(clientRenderState, null))
clientRenderState.close(); {
this.ClientRenderStateRef.set(null); return;
}
// create the new renderer clientRenderState.close();
clientRenderState = new ClientRenderState(this.clientLevel, this.clientLevel.getFullDataProvider()); clientRenderState = new ClientRenderState(this.clientLevel, this.clientLevel.getFullDataProvider());
this.ClientRenderStateRef.set(clientRenderState); if (!this.ClientRenderStateRef.compareAndSet(null, clientRenderState))
{
//FIXME: How to handle this?
LOGGER.warn("Failed to set render state due to concurrency after changing view distance");
clientRenderState.close();
return;
}
} }
clientRenderState.quadtree.tryTick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos())); clientRenderState.quadtree.tick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
} }
@@ -115,13 +121,14 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
// render // // render //
//========// //========//
// TODO start rendering during frame request
public void startRenderer() public void startRenderer()
{ {
ClientRenderState clientRenderState = new ClientRenderState(this.clientLevel, this.clientLevel.getFullDataProvider()); ClientRenderState ClientRenderState = new ClientRenderState(this.clientLevel, this.clientLevel.getFullDataProvider());
if (!this.ClientRenderStateRef.compareAndSet(null, clientRenderState)) if (!this.ClientRenderStateRef.compareAndSet(null, ClientRenderState))
{ {
LOGGER.warn("Renderer already started for ["+this+"]."); LOGGER.warn("Failed to start renderer due to concurrency");
clientRenderState.close(); ClientRenderState.close();
} }
} }
@@ -129,15 +136,23 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
public void stopRenderer() public void stopRenderer()
{ {
ClientRenderState clientRenderState = this.ClientRenderStateRef.get(); LOGGER.info("Stopping renderer for " + this);
if (clientRenderState == null) ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState == null)
{ {
LOGGER.warn("Tried to stop the renderer for [" + this + "] when it was not started."); LOGGER.warn("Tried to stop renderer for " + this + " when it was not started!");
return; return;
} }
// stop the render state
this.ClientRenderStateRef.compareAndSet(clientRenderState, null); while (!this.ClientRenderStateRef.compareAndSet(ClientRenderState, null)) // TODO why is there a while loop here?
clientRenderState.close(); {
ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState == null)
{
return;
}
}
ClientRenderState.close();
} }
@@ -155,7 +170,7 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState != null) if (ClientRenderState != null)
{ {
ClientRenderState.quadtree.queuePosToReload(updatedFullDataSource.getPos()); ClientRenderState.quadtree.reloadPos(updatedFullDataSource.getPos());
} }
} }
@@ -165,8 +180,20 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState != null) if (ClientRenderState != null)
{ {
this.ClientRenderStateRef.compareAndSet(ClientRenderState, null); // TODO does this have to be in a while loop, if so why?
ClientRenderState.close(); while (!this.ClientRenderStateRef.compareAndSet(ClientRenderState, null))
{
ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState == null)
{
break;
}
}
if (ClientRenderState != null)
{
ClientRenderState.close();
}
} }
this.fullDataSourceProvider.removeDataSourceUpdateListener(this); this.fullDataSourceProvider.removeDataSourceUpdateListener(this);
@@ -198,7 +225,7 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
ClientRenderState clientRenderState = this.ClientRenderStateRef.get(); ClientRenderState clientRenderState = this.ClientRenderStateRef.get();
if (clientRenderState != null && clientRenderState.quadtree != null) if (clientRenderState != null && clientRenderState.quadtree != null)
{ {
clientRenderState.quadtree.queuePosToReload(pos); clientRenderState.quadtree.reloadPos(pos);
} }
} }
@@ -215,8 +242,6 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
public final LodQuadTree quadtree; public final LodQuadTree quadtree;
public final RenderBufferHandler renderBufferHandler; public final RenderBufferHandler renderBufferHandler;
public final int viewDiameterInBlocks;
//=============// //=============//
@@ -227,29 +252,16 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
IDhClientLevel dhClientLevel, IDhClientLevel dhClientLevel,
FullDataSourceProviderV2 fullDataSourceProvider) FullDataSourceProviderV2 fullDataSourceProvider)
{ {
this.viewDiameterInBlocks = getViewDiameterInBlocks();
this.quadtree = new LodQuadTree( this.quadtree = new LodQuadTree(
dhClientLevel, dhClientLevel,
this.viewDiameterInBlocks, Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistanceRadius.get() * LodUtil.CHUNK_WIDTH * 2,
// initial position is (0,0) just in case the player hasn't loaded in yet, the tree will be moved once the level starts ticking // initial position is (0,0) just in case the player hasn't loaded in yet, the tree will be moved once the level starts ticking
0, 0, 0, 0,
fullDataSourceProvider); fullDataSourceProvider);
this.renderBufferHandler = new RenderBufferHandler(this.quadtree); this.renderBufferHandler = new RenderBufferHandler(this.quadtree);
} }
/**
* Used both during setup and
* to determine if the render distance changed.
*/
public static int getViewDiameterInBlocks()
{
return Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistanceRadius.get()
* LodUtil.CHUNK_WIDTH
* 2;
}
//================// //================//
@@ -23,7 +23,6 @@ import com.google.common.cache.CacheBuilder;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2; import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.file.fullDatafile.RemoteFullDataSourceProvider; import com.seibel.distanthorizons.core.file.fullDatafile.RemoteFullDataSourceProvider;
import com.seibel.distanthorizons.core.file.structure.ISaveStructure; import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
@@ -36,17 +35,19 @@ import com.seibel.distanthorizons.core.network.event.ScopedNetworkEventSource;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPartialUpdateMessage; import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPartialUpdateMessage;
import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.RenderBufferHandler; import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO; import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import javax.annotation.CheckForNull; import javax.annotation.CheckForNull;
import java.awt.*;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
@@ -94,7 +95,6 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
//region
public DhClientLevel( public DhClientLevel(
ISaveStructure saveStructure, ISaveStructure saveStructure,
@@ -182,6 +182,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
{ {
try try
{ {
// TODO this has a lock which can cause stuttering/lag issues
this.updateBeaconBeamsForSectionPos(dataSourceDto.pos, message.payload.beaconBeams); this.updateBeaconBeamsForSectionPos(dataSourceDto.pos, message.payload.beaconBeams);
} }
catch (Exception e) catch (Exception e)
@@ -203,14 +204,11 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
}); });
} }
//endregion
//==============// //==============//
// tick methods // // tick methods //
//==============// //==============//
//region
@Override @Override
public void clientTick() public void clientTick()
@@ -251,14 +249,11 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
@Override @Override
public DhBlockPos2D getTargetPosForGeneration() { return new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()); } public DhBlockPos2D getTargetPosForGeneration() { return new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()); }
//endregion
//===========// //===========//
// world gen // // world gen //
//===========// //===========//
//region
@Override @Override
public void onWorldGenTaskComplete(long pos) public void onWorldGenTaskComplete(long pos)
@@ -266,14 +261,11 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
this.clientside.reloadPos(pos); this.clientside.reloadPos(pos);
} }
//endregion
//=========// //=========//
// getters // // getters //
//=========// //=========//
//region
@Override @Override
public IClientLevelWrapper getClientLevelWrapper() { return this.levelWrapper; } public IClientLevelWrapper getClientLevelWrapper() { return this.levelWrapper; }
@@ -295,7 +287,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
public ISaveStructure getSaveStructure() { return this.saveStructure; } public ISaveStructure getSaveStructure() { return this.saveStructure; }
@Override @Override
public IDhGenericRenderer getGenericRenderer() { return this.clientside.genericRenderer; } public GenericObjectRenderer getGenericRenderer() { return this.clientside.genericRenderer; }
@Override @Override
public RenderBufferHandler getRenderBufferHandler() public RenderBufferHandler getRenderBufferHandler()
{ {
@@ -303,9 +295,6 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
return (renderState != null) ? renderState.renderBufferHandler : null; return (renderState != null) ? renderState.renderBufferHandler : null;
} }
@Override
public boolean isRendering() { return this.clientside.isRendering(); }
public boolean shouldProcessChunkUpdate(DhChunkPos chunkPos) public boolean shouldProcessChunkUpdate(DhChunkPos chunkPos)
{ {
if (this.networkState == null || !this.networkState.isReady()) if (this.networkState == null || !this.networkState.isReady())
@@ -316,28 +305,18 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
return !this.networkState.sessionConfig.isRealTimeUpdatesEnabled() || this.loadedOnceChunks.add(chunkPos); return !this.networkState.sessionConfig.isRealTimeUpdatesEnabled() || this.loadedOnceChunks.add(chunkPos);
} }
//endregion
//===========// //===========//
// debugging // // debugging //
//===========// //===========//
//region
@Override @Override
public void addDebugMenuStringsToList(List<String> messageList) public void addDebugMenuStringsToList(List<String> messageList)
{ {
String o = MinecraftTextFormat.ORANGE;
String y = MinecraftTextFormat.YELLOW;
String g = MinecraftTextFormat.GREEN;
String cf = MinecraftTextFormat.CLEAR_FORMATTING;
String dimName = this.levelWrapper.getDhIdentifier(); String dimName = this.levelWrapper.getDhIdentifier();
boolean rendering = this.clientside.isRendering(); boolean rendering = this.clientside.isRendering();
String renderingString = rendering ? (g+"yes"+cf) : (o+"no"+cf); messageList.add("["+dimName+"] rendering: "+(rendering ? "yes" : "no"));
messageList.add("["+y+dimName+cf+"] rendering: "+renderingString);
this.remoteDataSourceProvider.addDebugMenuStringsToList(messageList); this.remoteDataSourceProvider.addDebugMenuStringsToList(messageList);
@@ -355,14 +334,11 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
} }
} }
//endregion
//================// //================//
// base overrides // // base overrides //
//================// //================//
//region
@Override @Override
public String toString() { return "DhClientLevel{" + this.getClientLevelWrapper().getDhIdentifier() + "}"; } public String toString() { return "DhClientLevel{" + this.getClientLevelWrapper().getDhIdentifier() + "}"; }
@@ -387,14 +363,11 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
LOGGER.info("Closed [" + DhClientLevel.class.getSimpleName() + "] for [" + this.levelWrapper + "]"); LOGGER.info("Closed [" + DhClientLevel.class.getSimpleName() + "] for [" + this.levelWrapper + "]");
} }
//endregion
//================// //================//
// helper classes // // helper classes //
//================// //================//
//region
private static class LodRequestState extends LodRequestModule.AbstractLodRequestState private static class LodRequestState extends LodRequestModule.AbstractLodRequestState
{ {
@@ -404,8 +377,4 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
} }
} }
//endregion
} }
@@ -19,17 +19,20 @@
package com.seibel.distanthorizons.core.level; package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.file.structure.ISaveStructure; import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager; import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager;
import com.seibel.distanthorizons.core.render.RenderBufferHandler; import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.io.IOException; import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List; import java.util.List;
@@ -46,7 +49,6 @@ public class DhClientServerLevel extends AbstractDhServerLevel implements IDhCli
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
//region
public DhClientServerLevel( public DhClientServerLevel(
ISaveStructure saveStructure, ISaveStructure saveStructure,
@@ -61,42 +63,30 @@ public class DhClientServerLevel extends AbstractDhServerLevel implements IDhCli
this.runRepoReliantSetup(); this.runRepoReliantSetup();
} }
//endregion
//==============// //==============//
// tick methods // // tick methods //
//==============// //==============//
//region
@Override @Override
public void clientTick() { this.clientside.clientTick(); } public void clientTick() { this.clientside.clientTick(); }
//endregion
//========// //========//
// render // // render //
//========// //========//
//region
public void startRenderer() { this.clientside.startRenderer(); } public void startRenderer() { this.clientside.startRenderer(); }
public void stopRenderer() { this.clientside.stopRenderer(); } public void stopRenderer() { this.clientside.stopRenderer(); }
@Override
public boolean isRendering() { return this.clientside.isRendering(); }
//endregion
//================// //================//
// level handling // // level handling //
//================// //================//
//region
@Nullable @Nullable
@Override @Override
@@ -105,36 +95,26 @@ public class DhClientServerLevel extends AbstractDhServerLevel implements IDhCli
@Override @Override
public void clearRenderCache() { this.clientside.clearRenderCache(); } public void clearRenderCache() { this.clientside.clearRenderCache(); }
//endregion
//===========// //===========//
// debugging // // debugging //
//===========// //===========//
//region
@Override @Override
public void addDebugMenuStringsToList(List<String> messageList) public void addDebugMenuStringsToList(List<String> messageList)
{ {
// header // header
String o = MinecraftTextFormat.ORANGE;
String y = MinecraftTextFormat.YELLOW;
String g = MinecraftTextFormat.GREEN;
String cf = MinecraftTextFormat.CLEAR_FORMATTING;
String dimName = this.serverLevelWrapper.getDhIdentifier(); String dimName = this.serverLevelWrapper.getDhIdentifier();
boolean rendering = this.clientside.isRendering(); boolean rendering = this.clientside.isRendering();
String renderingString = rendering ? (g+"yes"+cf) : (o+"no"+cf); messageList.add("["+dimName+"] rendering: "+(rendering ? "yes" : "no"));
messageList.add("["+y+dimName+cf+"] rendering: "+renderingString);
super.addDebugMenuStringsToList(messageList); super.addDebugMenuStringsToList(messageList);
} }
@Override @Override
public IDhGenericRenderer getGenericRenderer() { return this.clientside.genericRenderer; } public GenericObjectRenderer getGenericRenderer() { return this.clientside.genericRenderer; }
@Override @Override
public RenderBufferHandler getRenderBufferHandler() public RenderBufferHandler getRenderBufferHandler()
{ {
@@ -142,14 +122,11 @@ public class DhClientServerLevel extends AbstractDhServerLevel implements IDhCli
return (renderState != null) ? renderState.renderBufferHandler : null; return (renderState != null) ? renderState.renderBufferHandler : null;
} }
//endregion
//===============// //===============//
// data handling // // data handling //
//===============// //===============//
//region
@Override @Override
public void onWorldGenTaskComplete(long pos) public void onWorldGenTaskComplete(long pos)
@@ -158,14 +135,11 @@ public class DhClientServerLevel extends AbstractDhServerLevel implements IDhCli
this.clientside.reloadPos(pos); this.clientside.reloadPos(pos);
} }
//endregion
//================// //================//
// base overrides // // base overrides //
//================// //================//
//region
@Override @Override
public String toString() { return "DhClientServerLevel{"+this.serverLevelWrapper.getKeyedLevelDimensionName()+"}"; } public String toString() { return "DhClientServerLevel{"+this.serverLevelWrapper.getKeyedLevelDimensionName()+"}"; }
@@ -179,8 +153,4 @@ public class DhClientServerLevel extends AbstractDhServerLevel implements IDhCli
LOGGER.info("Closed " + this.getClass().getSimpleName() + " for " + this.getServerLevelWrapper()); LOGGER.info("Closed " + this.getClass().getSimpleName() + " for " + this.getServerLevelWrapper());
} }
//endregion
} }
@@ -23,8 +23,9 @@ import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager; import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.RenderBufferHandler; import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer; import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import org.jetbrains.annotations.Nullable;
import java.io.IOException; import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
@@ -50,7 +51,10 @@ public class DhServerLevel extends AbstractDhServerLevel
//=======// //=======//
@Override @Override
public boolean shouldDoWorldGen() { return super.shouldDoWorldGen(); } public boolean shouldDoWorldGen()
{
return true; //todo;
}
@Override @Override
public DhBlockPos2D getTargetPosForGeneration() public DhBlockPos2D getTargetPosForGeneration()
{ {
@@ -68,7 +72,7 @@ public class DhServerLevel extends AbstractDhServerLevel
//=========// //=========//
@Override @Override
public IDhGenericRenderer getGenericRenderer() public GenericObjectRenderer getGenericRenderer()
{ {
// server-only levels don't support rendering // server-only levels don't support rendering
return null; return null;
@@ -19,7 +19,6 @@
package com.seibel.distanthorizons.core.level; package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -40,12 +39,4 @@ public interface IDhClientLevel extends IDhLevel
*/ */
void clearRenderCache(); void clearRenderCache();
/**
* returns true if this level is currently rendering. <br>
* Designed for use for debugging and the {@link F3Screen}.
*/
boolean isRendering();
} }
@@ -27,10 +27,10 @@ import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSource
import com.seibel.distanthorizons.core.file.structure.ISaveStructure; import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.RenderBufferHandler; import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO; import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo; import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -92,7 +92,7 @@ public interface IDhLevel extends AutoCloseable, GeneratedFullDataSourceProvider
* Not supported on the server-side. * Not supported on the server-side.
*/ */
@Nullable @Nullable
IDhGenericRenderer getGenericRenderer(); GenericObjectRenderer getGenericRenderer();
/** /**
* Will return null if the renderer isn't set up yet. <br> * Will return null if the renderer isn't set up yet. <br>
* Not supported on the server-side. * Not supported on the server-side.
@@ -168,8 +168,7 @@ public class LodRequestModule implements Closeable
} }
} }
dataFileHandler.clearRetrievalQueue(); dataFileHandler.clearRetrievalQueue();
// synchronized shutdown necessary to make sure the tasks are all handled correctly worldGenState.closeAsync(true).join(); //TODO: Make it async.
worldGenState.closeAsync(true).join();
dataFileHandler.removeWorldGenCompleteListener(this.onWorldGenCompleteListener); dataFileHandler.removeWorldGenCompleteListener(this.onWorldGenCompleteListener);
} }
@@ -199,8 +198,7 @@ public class LodRequestModule implements Closeable
if (worldGenState != null) if (worldGenState != null)
{ {
// synchronized shutdown necessary to make sure the tasks are all handled correctly worldGenState.closeAsync(true).join(); //TODO: Make it async.
worldGenState.closeAsync(true).join();
} }
} }
} }
@@ -300,7 +298,7 @@ public class LodRequestModule implements Closeable
remainingChunkCount += this.retrievalQueue.getQueuedChunkCount(); remainingChunkCount += this.retrievalQueue.getQueuedChunkCount();
String remainingChunkCountStr = F3Screen.NUMBER_FORMAT.format(remainingChunkCount); String remainingChunkCountStr = F3Screen.NUMBER_FORMAT.format(remainingChunkCount);
String message = "DH is generating chunks. " + remainingChunkCountStr + " left."; // TODO getting stuck at 32 chunks remaining String message = "DH is generating chunks. " + remainingChunkCountStr + " left.";
// show a message about how to disable progress logging if requested // show a message about how to disable progress logging if requested
int msToShowDisableInstructions = Config.Common.WorldGenerator.generationProgressDisableMessageDisplayTimeInSeconds.get() * 1_000; int msToShowDisableInstructions = Config.Common.WorldGenerator.generationProgressDisableMessageDisplayTimeInSeconds.get() * 1_000;
@@ -390,17 +388,17 @@ public class LodRequestModule implements Closeable
this.progressUpdateThreadRunning = false; this.progressUpdateThreadRunning = false;
return this.retrievalQueue.startClosingAsync(true, doInterrupt) return this.retrievalQueue.startClosingAsync(true, doInterrupt)
.exceptionally(e -> .exceptionally(e ->
{ {
LOGGER.error("Error during first stage of generation queue shutdown, Error: ["+e.getMessage()+"].", e); LOGGER.error("Error during first stage of generation queue shutdown, Error: ["+e.getMessage()+"].", e);
return null; return null;
} }
).thenRun(this.retrievalQueue::close) ).thenRun(this.retrievalQueue::close)
.exceptionally(e -> .exceptionally(e ->
{ {
LOGGER.error("Error during second stage of generation queue shutdown, Error: ["+e.getMessage()+"].", e); LOGGER.error("Error during second stage of generation queue shutdown, Error: ["+e.getMessage()+"].", e);
return null; return null;
}); });
} }
@@ -23,27 +23,25 @@ import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.api.internal.SharedApi; import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.jar.ModJarInfo; import com.seibel.distanthorizons.core.jar.ModJarInfo;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler; import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.RenderBufferHandler; import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker; import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.world.AbstractDhWorld; import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.AbstractDhRenderApiDefinition;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.coreapi.util.StringUtil; import com.seibel.distanthorizons.coreapi.util.StringUtil;
import org.apache.logging.log4j.LogManager;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.*; import java.util.*;
import java.util.concurrent.ThreadPoolExecutor;
public class F3Screen public class F3Screen
{ {
@@ -83,12 +81,18 @@ public class F3Screen
*/ */
public static void addStringToDisplay(List<String> messageList) public static void addStringToDisplay(List<String> messageList)
{ {
String r = MinecraftTextFormat.RED; // multi thread pools
String y = MinecraftTextFormat.YELLOW; PriorityTaskPicker.Executor worldGenPool = ThreadPoolUtil.getWorldGenExecutor();
String a = MinecraftTextFormat.AQUA; PriorityTaskPicker.Executor fileHandlerPool = ThreadPoolUtil.getFileHandlerExecutor();
String cf = MinecraftTextFormat.CLEAR_FORMATTING; PriorityTaskPicker.Executor renderLoadingPool = ThreadPoolUtil.getRenderLoadingExecutor();
PriorityTaskPicker.Executor updatePool = ThreadPoolUtil.getUpdatePropagatorExecutor();
PriorityTaskPicker.Executor lodBuilderPool = ThreadPoolUtil.getChunkToLodBuilderExecutor();
PriorityTaskPicker.Executor networkPool = ThreadPoolUtil.getNetworkCompressionExecutor();
// single thread pools
ThreadPoolExecutor cleanupPool = ThreadPoolUtil.getCleanupExecutor();
ThreadPoolExecutor beaconCullingPool = ThreadPoolUtil.getBeaconCullingExecutor();
ThreadPoolExecutor migrationPool = ThreadPoolUtil.getFullDataMigrationExecutor();
AbstractDhWorld world = SharedApi.getAbstractDhWorld(); AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world == null) if (world == null)
@@ -110,7 +114,7 @@ public class F3Screen
// render validation error // render validation error
if (ClientApi.INSTANCE.lastRenderParamValidationMessage != null) if (ClientApi.INSTANCE.lastRenderParamValidationMessage != null)
{ {
messageList.add("Render Validation Err: " + r + ClientApi.INSTANCE.lastRenderParamValidationMessage + cf); messageList.add("Render Validation Err: " + ClientApi.INSTANCE.lastRenderParamValidationMessage);
} }
@@ -125,10 +129,7 @@ public class F3Screen
int detailLevel = DhSectionPos.getDetailLevel(sectionPos); int detailLevel = DhSectionPos.getDetailLevel(sectionPos);
int posX = DhSectionPos.getX(sectionPos); int posX = DhSectionPos.getX(sectionPos);
int posZ = DhSectionPos.getZ(sectionPos); int posZ = DhSectionPos.getZ(sectionPos);
messageList.add("LOD Pos: "+y+detailLevel+"*"+posX+","+posZ+cf); messageList.add("LOD Pos: " + detailLevel + "*"+posX+","+posZ);
AbstractDhRenderApiDefinition renderApiDef = SingletonInjector.INSTANCE.get(AbstractDhRenderApiDefinition.class);
messageList.add("Rendering API: "+a+renderApiDef.getApiName()+cf);
} }
messageList.add(""); messageList.add("");
} }
@@ -137,23 +138,16 @@ public class F3Screen
if (Config.Client.Advanced.Debugging.F3Screen.showThreadPools.get()) if (Config.Client.Advanced.Debugging.F3Screen.showThreadPools.get())
{ {
// multi thread pools // multi thread pools
messageList.add(PriorityTaskPicker.Executor.getThreadPoolStatString("World Gen/Import", ThreadPoolUtil.getWorldGenExecutor())); messageList.add(getThreadPoolStatString("World Gen/Import", worldGenPool));
messageList.add(PriorityTaskPicker.Executor.getThreadPoolStatString("Render Load", ThreadPoolUtil.getFileHandlerExecutor())); messageList.add(getThreadPoolStatString("Render Load", renderLoadingPool));
messageList.add(PriorityTaskPicker.Executor.getThreadPoolStatString("File Handler", ThreadPoolUtil.getRenderLoadingExecutor())); messageList.add(getThreadPoolStatString("File Handler", fileHandlerPool));
messageList.add(PriorityTaskPicker.Executor.getThreadPoolStatString("Update Propagator", ThreadPoolUtil.getUpdatePropagatorExecutor())); messageList.add(getThreadPoolStatString("Update Propagator", updatePool));
messageList.add(PriorityTaskPicker.Executor.getThreadPoolStatString("LOD Builder", ThreadPoolUtil.getChunkToLodBuilderExecutor())); messageList.add(getThreadPoolStatString("LOD Builder", lodBuilderPool));
messageList.add(PriorityTaskPicker.Executor.getThreadPoolStatString("Networking", ThreadPoolUtil.getNetworkCompressionExecutor())); messageList.add(getThreadPoolStatString("Networking", networkPool));
//// single thread pools //// single thread pools
//messageList.add(PriorityTaskPicker.Executor.getThreadPoolStatString("Cleanup", ThreadPoolUtil.getCleanupExecutor())); //messageList.add(getThreadPoolStatString("Cleanup", cleanupPool));
//messageList.add(PriorityTaskPicker.Executor.getThreadPoolStatString("Beacon Culling", ThreadPoolUtil.getBeaconCullingExecutor())); //messageList.add(getThreadPoolStatString("Beacon Culling", beaconCullingPool));
//messageList.add(PriorityTaskPicker.Executor.getThreadPoolStatString("Migration", ThreadPoolUtil.getFullDataMigrationExecutor())); //messageList.add(getThreadPoolStatString("Migration", migrationPool));
messageList.add("");
}
// render thread tasks
if (Config.Client.Advanced.Debugging.F3Screen.showRenderThreadTasks.get())
{
RenderThreadTaskHandler.INSTANCE.addDebugMenuStringsToList(messageList);
messageList.add(""); messageList.add("");
} }
@@ -173,8 +167,7 @@ public class F3Screen
// chunk updates // chunk updates
if (Config.Client.Advanced.Debugging.F3Screen.showQueuedChunkUpdateCount.get()) if (Config.Client.Advanced.Debugging.F3Screen.showQueuedChunkUpdateCount.get())
{ {
ArrayList<String> chunkQueueList = SharedApi.INSTANCE.getDebugMenuString(); messageList.add(SharedApi.INSTANCE.getDebugMenuString());
messageList.addAll(chunkQueueList);
messageList.add(""); messageList.add("");
} }
@@ -185,23 +178,7 @@ public class F3Screen
messageList.add(""); messageList.add("");
for (IDhLevel level : levelIterator) for (IDhLevel level : levelIterator)
{ {
// skip non-rendering levels if requested
if (Config.Client.Advanced.Debugging.F3Screen.onlyShowRenderingLevels.get())
{
if (level instanceof IDhClientLevel)
{
IDhClientLevel clientLevel = (IDhClientLevel) level;
if (!clientLevel.isRendering())
{
continue;
}
}
}
level.addDebugMenuStringsToList(messageList); level.addDebugMenuStringsToList(messageList);
// LOD rendering // LOD rendering
RenderBufferHandler renderBufferHandler = level.getRenderBufferHandler(); RenderBufferHandler renderBufferHandler = level.getRenderBufferHandler();
if (renderBufferHandler != null) if (renderBufferHandler != null)
@@ -213,9 +190,8 @@ public class F3Screen
messageList.add(showPassString); messageList.add(showPassString);
} }
} }
// Generic rendering // Generic rendering
IDhGenericRenderer genericRenderer = level.getGenericRenderer(); GenericObjectRenderer genericRenderer = level.getGenericRenderer();
if (genericRenderer != null) if (genericRenderer != null)
{ {
messageList.add(genericRenderer.getVboRenderDebugMenuString()); messageList.add(genericRenderer.getVboRenderDebugMenuString());
@@ -227,4 +203,43 @@ public class F3Screen
//================//
// helper methods //
//================//
private static String getThreadPoolStatString(String name, PriorityTaskPicker.Executor pool)
{
String queueSize = (pool != null) ? NUMBER_FORMAT.format(pool.getQueueSize()) : "-";
String completedCount = (pool != null) ? NUMBER_FORMAT.format(pool.getCompletedTaskCount()) : "-";
String message = name+", Tasks: "+queueSize+", Done: "+completedCount;
if (pool != null)
{
// active threads
int activeThreadCount = pool.getRunningTaskCount();
int threadCount = pool.getPoolSize();
message += ", Active: "+activeThreadCount+"/"+threadCount;
// thread runtime
String runTimeAvgStr;
double runTimeAvgInMs = pool.getAverageRunTimeInMs();
if (!Double.isNaN(runTimeAvgInMs))
{
runTimeAvgStr = NUMBER_FORMAT.format(runTimeAvgInMs);
}
else
{
runTimeAvgStr = "<0";
}
message += ", Avg: "+runTimeAvgStr+"ms";
}
return message;
}
} }
@@ -1,6 +1,7 @@
package com.seibel.distanthorizons.core.multiplayer.client; package com.seibel.distanthorizons.core.multiplayer.client;
import com.google.common.base.Stopwatch; import com.google.common.base.Stopwatch;
import com.google.common.cache.CacheBuilder;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.types.ConfigEntry; import com.seibel.distanthorizons.core.config.types.ConfigEntry;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
@@ -18,7 +19,7 @@ import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceR
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceResponseMessage; import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceResponseMessage;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO; import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
@@ -43,7 +44,6 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
.build(); .build();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final AbstractDebugWireframeRenderer DEBUG_RENDERER = SingletonInjector.INSTANCE.get(AbstractDebugWireframeRenderer.class);
private static final int MAX_RETRY_ATTEMPTS = 3; private static final int MAX_RETRY_ATTEMPTS = 3;
@@ -87,7 +87,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
this.level = level; this.level = level;
this.changedOnly = changedOnly; this.changedOnly = changedOnly;
this.showDebugWireframeConfig = showDebugWireframeConfig; this.showDebugWireframeConfig = showDebugWireframeConfig;
DEBUG_RENDERER.register(this, this.showDebugWireframeConfig); DebugRenderer.register(this, this.showDebugWireframeConfig);
} }
@@ -386,7 +386,10 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
} }
@Override @Override
public void close() { DEBUG_RENDERER.unregister(this, this.showDebugWireframeConfig); } public void close()
{
DebugRenderer.unregister(this, this.showDebugWireframeConfig);
}
@@ -395,7 +398,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
//===========// //===========//
@Override @Override
public void debugRender(AbstractDebugWireframeRenderer renderer) public void debugRender(DebugRenderer renderer)
{ {
if (MC_CLIENT.getWrappedClientLevel() != this.level.getClientLevelWrapper()) if (MC_CLIENT.getWrappedClientLevel() != this.level.getClientLevelWrapper())
{ {
@@ -426,7 +429,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
} }
} }
renderer.renderBox(new AbstractDebugWireframeRenderer.Box(pos, -32f, 64f, 0.05f, color)); renderer.renderBox(new DebugRenderer.Box(pos, -32f, 64f, 0.05f, color));
} }
} }
@@ -11,7 +11,6 @@ import java.io.Closeable;
import java.util.*; import java.util.*;
import java.util.function.BinaryOperator; import java.util.function.BinaryOperator;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class SessionConfig implements INetworkObject public class SessionConfig implements INetworkObject
@@ -32,23 +31,7 @@ public class SessionConfig implements INetworkObject
{ {
// Note: config values are transmitted in the insertion order // Note: config values are transmitted in the insertion order
registerConfigEntry(Config.Common.WorldGenerator.enableDistantGeneration.getChatCommandName(), new Entry( registerConfigEntry(Config.Common.WorldGenerator.enableDistantGeneration, Boolean::logicalAnd);
Config.Server.enableServerGeneration::get,
runnable -> new Closeable()
{
private final ConfigChangeListener<Boolean> distantGenerationChanges = new ConfigChangeListener<>(Config.Common.WorldGenerator.enableDistantGeneration, ignored -> runnable.run());
private final ConfigChangeListener<Boolean> serverGenerationChanges = new ConfigChangeListener<>(Config.Server.enableServerGeneration, ignored -> runnable.run());
@Override
public void close()
{
this.serverGenerationChanges.close();
this.distantGenerationChanges.close();
}
},
(Boolean client, Boolean server) -> client && Config.Common.WorldGenerator.enableDistantGeneration.get()
));
registerConfigEntry(Config.Server.maxGenerationRequestDistance, Math::min); registerConfigEntry(Config.Server.maxGenerationRequestDistance, Math::min);
registerConfigEntry(Config.Common.WorldGenerator.generationCenterChunkX, (x, y) -> y); registerConfigEntry(Config.Common.WorldGenerator.generationCenterChunkX, (x, y) -> y);
registerConfigEntry(Config.Common.WorldGenerator.generationCenterChunkZ, (x, y) -> y); registerConfigEntry(Config.Common.WorldGenerator.generationCenterChunkZ, (x, y) -> y);
@@ -107,24 +90,14 @@ public class SessionConfig implements INetworkObject
private static <T> void registerConfigEntry(ConfigEntry<T> configEntry, BinaryOperator<T> valueConstrainer) private static <T> void registerConfigEntry(ConfigEntry<T> configEntry, BinaryOperator<T> valueConstrainer)
{ {
registerConfigEntry( CONFIG_ENTRIES.compute(Objects.requireNonNull(configEntry.getChatCommandName()), (key, existingEntry) -> {
Objects.requireNonNull(configEntry.getChatCommandName()), if (existingEntry != null)
new Entry( {
configEntry::get, throw new IllegalArgumentException("Attempted to register config entry with duplicate chatCommandName: " + key);
runnable -> new ConfigChangeListener<>(configEntry, ignored -> runnable.run()), }
valueConstrainer
) return new Entry(configEntry, valueConstrainer);
); });
}
private static void registerConfigEntry(String key, Entry entry)
{
if (CONFIG_ENTRIES.containsKey(key))
{
throw new IllegalArgumentException("Attempted to register config entry with duplicate key: " + key);
}
CONFIG_ENTRIES.put(key, entry);
} }
@@ -142,7 +115,7 @@ public class SessionConfig implements INetworkObject
T value = (T) this.values.get(name); T value = (T) this.values.get(name);
if (value == null) if (value == null)
{ {
value = (T) entry.valueSupplier.get(); value = (T) entry.supplier.get();
} }
return (this.constrainingConfig != null return (this.constrainingConfig != null
@@ -237,15 +210,13 @@ public class SessionConfig implements INetworkObject
private static class Entry private static class Entry
{ {
public final Supplier<Object> valueSupplier; public final ConfigEntry<Object> supplier;
public final Function<Runnable, Closeable> changeListenerFactory;
public final BinaryOperator<Object> valueConstrainer; public final BinaryOperator<Object> valueConstrainer;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <T> Entry(Supplier<Object> valueSupplier, Function<Runnable, Closeable> changeListenerFactory, BinaryOperator<T> valueConstrainer) private <T> Entry(ConfigEntry<T> supplier, BinaryOperator<T> valueConstrainer)
{ {
this.valueSupplier = valueSupplier; this.supplier = (ConfigEntry<Object>) supplier;
this.changeListenerFactory = changeListenerFactory;
this.valueConstrainer = (BinaryOperator<Object>) valueConstrainer; this.valueConstrainer = (BinaryOperator<Object>) valueConstrainer;
} }
@@ -254,29 +225,23 @@ public class SessionConfig implements INetworkObject
/** fires if any config value was changed */ /** fires if any config value was changed */
public static class AnyChangeListener implements Closeable public static class AnyChangeListener implements Closeable
{ {
private final ArrayList<Closeable> changeListeners; private final ArrayList<ConfigChangeListener<?>> changeListeners;
public AnyChangeListener(Runnable runnable) public AnyChangeListener(Runnable runnable)
{ {
this.changeListeners = new ArrayList<>(CONFIG_ENTRIES.size()); this.changeListeners = new ArrayList<>(CONFIG_ENTRIES.size());
for (Entry entry : CONFIG_ENTRIES.values()) for (Entry entry : CONFIG_ENTRIES.values())
{ {
this.changeListeners.add(entry.changeListenerFactory.apply(runnable)); this.changeListeners.add(new ConfigChangeListener<>(entry.supplier, ignored -> runnable.run()));
} }
} }
@Override @Override
public void close() public void close()
{ {
for (Closeable changeListener : this.changeListeners) for (ConfigChangeListener<?> changeListener : this.changeListeners)
{ {
try changeListener.close();
{
changeListener.close();
}
catch (Exception ignored)
{
}
} }
this.changeListeners.clear(); this.changeListeners.clear();
} }
@@ -1,6 +1,7 @@
package com.seibel.distanthorizons.core.util.objects.pooling; package com.seibel.distanthorizons.core.pooling;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import org.apache.logging.log4j.LogManager;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import java.lang.ref.PhantomReference; import java.lang.ref.PhantomReference;
@@ -37,19 +38,18 @@ public abstract class AbstractPhantomArrayList implements AutoCloseable
//=============// //=============//
/** The Array counts can be 0 or greater. */ /** The Array counts can be 0 or greater. */
public AbstractPhantomArrayList(PhantomArrayListPool phantomArrayListPool, int byteArrayCount, int shortArrayCount, int longArrayCount, int charArrayCount) public AbstractPhantomArrayList(PhantomArrayListPool phantomArrayListPool, int byteArrayCount, int shortArrayCount, int longArrayCount)
{ {
if (byteArrayCount < 0 if (byteArrayCount < 0
|| shortArrayCount < 0 || shortArrayCount < 0
|| longArrayCount < 0 || longArrayCount < 0)
|| charArrayCount < 0)
{ {
throw new IllegalArgumentException("Can't get a negative number of pooled arrays."); throw new IllegalArgumentException("Can't get a negative number of pooled arrays.");
} }
this.phantomArrayListPool = phantomArrayListPool; this.phantomArrayListPool = phantomArrayListPool;
this.phantomReference = new PhantomReference<>(this, this.phantomArrayListPool.phantomRefQueue); this.phantomReference = new PhantomReference<>(this, this.phantomArrayListPool.phantomRefQueue);
this.pooledArraysCheckout = this.phantomArrayListPool.checkoutArrays(byteArrayCount, shortArrayCount, longArrayCount, charArrayCount); this.pooledArraysCheckout = this.phantomArrayListPool.checkoutArrays(byteArrayCount, shortArrayCount, longArrayCount);
this.phantomArrayListPool.phantomRefToCheckout.put(this.phantomReference, this.pooledArraysCheckout); this.phantomArrayListPool.phantomRefToCheckout.put(this.phantomReference, this.pooledArraysCheckout);
} }
@@ -1,9 +1,8 @@
package com.seibel.distanthorizons.core.util.objects.pooling; package com.seibel.distanthorizons.core.pooling;
import com.seibel.distanthorizons.core.util.ListUtil; import com.seibel.distanthorizons.core.util.ListUtil;
import com.seibel.distanthorizons.coreapi.util.StringUtil; import com.seibel.distanthorizons.coreapi.util.StringUtil;
import it.unimi.dsi.fastutil.bytes.ByteArrayList; import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import it.unimi.dsi.fastutil.chars.CharArrayList;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.shorts.ShortArrayList; import it.unimi.dsi.fastutil.shorts.ShortArrayList;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -40,7 +39,6 @@ public class PhantomArrayListCheckout implements AutoCloseable
private final ArrayList<ByteArrayList> byteArrayLists = new ArrayList<>(); private final ArrayList<ByteArrayList> byteArrayLists = new ArrayList<>();
private final ArrayList<ShortArrayList> shortArrayLists = new ArrayList<>(); private final ArrayList<ShortArrayList> shortArrayLists = new ArrayList<>();
private final ArrayList<LongArrayList> longArrayLists = new ArrayList<>(); private final ArrayList<LongArrayList> longArrayLists = new ArrayList<>();
private final ArrayList<CharArrayList> charArrayLists = new ArrayList<>();
@@ -73,7 +71,6 @@ public class PhantomArrayListCheckout implements AutoCloseable
public void addByteArrayList(ByteArrayList list) { this.byteArrayLists.add(list); } public void addByteArrayList(ByteArrayList list) { this.byteArrayLists.add(list); }
public void addShortArrayList(ShortArrayList list) { this.shortArrayLists.add(list); } public void addShortArrayList(ShortArrayList list) { this.shortArrayLists.add(list); }
public void addLongArrayListRef(LongArrayList list) { this.longArrayLists.add(list); } public void addLongArrayListRef(LongArrayList list) { this.longArrayLists.add(list); }
public void addCharArrayListRef(CharArrayList list) { this.charArrayLists.add(list); }
@@ -84,7 +81,6 @@ public class PhantomArrayListCheckout implements AutoCloseable
public int getByteArrayCount() { return this.byteArrayLists.size(); } public int getByteArrayCount() { return this.byteArrayLists.size(); }
public int getShortArrayCount() { return this.shortArrayLists.size(); } public int getShortArrayCount() { return this.shortArrayLists.size(); }
public int getLongArrayCount() { return this.longArrayLists.size(); } public int getLongArrayCount() { return this.longArrayLists.size(); }
public int getCharArrayCount() { return this.charArrayLists.size(); }
@@ -106,17 +102,10 @@ public class PhantomArrayListCheckout implements AutoCloseable
ListUtil.clearAndSetSize(list, size); ListUtil.clearAndSetSize(list, size);
return list; return list;
} }
public CharArrayList getCharArray(int index, int size)
{
CharArrayList list = this.charArrayLists.get(index);
ListUtil.clearAndSetSize(list, size);
return list;
}
public ArrayList<ByteArrayList> getAllByteArrays() { return this.byteArrayLists; } public ArrayList<ByteArrayList> getAllByteArrays() { return this.byteArrayLists; }
public ArrayList<ShortArrayList> getAllShortArrays() { return this.shortArrayLists; } public ArrayList<ShortArrayList> getAllShortArrays() { return this.shortArrayLists; }
public ArrayList<LongArrayList> getAllLongArrays() { return this.longArrayLists; } public ArrayList<LongArrayList> getAllLongArrays() { return this.longArrayLists; }
public ArrayList<CharArrayList> getAllCharArrays() { return this.charArrayLists; }
@@ -1,4 +1,4 @@
package com.seibel.distanthorizons.core.util.objects.pooling; package com.seibel.distanthorizons.core.pooling;
import com.seibel.distanthorizons.core.api.internal.ClientApi; import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
@@ -11,7 +11,6 @@ import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.coreapi.util.StringUtil; import com.seibel.distanthorizons.coreapi.util.StringUtil;
import it.unimi.dsi.fastutil.bytes.ByteArrayList; import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import it.unimi.dsi.fastutil.chars.CharArrayList;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.shorts.ShortArrayList; import it.unimi.dsi.fastutil.shorts.ShortArrayList;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -98,11 +97,6 @@ public class PhantomArrayListPool
private final AtomicInteger totalShortArrayCountRef = new AtomicInteger(0); private final AtomicInteger totalShortArrayCountRef = new AtomicInteger(0);
/** counts how many long arrays have been created by this pool */ /** counts how many long arrays have been created by this pool */
private final AtomicInteger totalLongArrayCountRef = new AtomicInteger(0); private final AtomicInteger totalLongArrayCountRef = new AtomicInteger(0);
/** counts how many char arrays have been created by this pool */
private final AtomicInteger totalCharArrayCountRef = new AtomicInteger(0);
// NOTE: if we ever need to add another pool type we should separate out the logic for each individually pooled object.
// Otherwise, this class will just get too cluttered with duplicate code.
// But for now since this works, it'll be fine.
/** used for debugging, represents an estimate for how many bytes the byte[] pool contains */ /** used for debugging, represents an estimate for how many bytes the byte[] pool contains */
private long lastBytePoolSizeInBytes = -1; private long lastBytePoolSizeInBytes = -1;
@@ -110,8 +104,6 @@ public class PhantomArrayListPool
private long lastShortPoolSizeInBytes = -1; private long lastShortPoolSizeInBytes = -1;
/** used for debugging, represents an estimate for how many bytes the long[] pool contains */ /** used for debugging, represents an estimate for how many bytes the long[] pool contains */
private long lastLongPoolSizeInBytes = -1; private long lastLongPoolSizeInBytes = -1;
/** used for debugging, represents an estimate for how many bytes the char[] pool contains */
private long lastCharPoolSizeInBytes = -1;
/** used for debugging, represents an estimate for how many byte[]'s are currently in this pool*/ /** used for debugging, represents an estimate for how many byte[]'s are currently in this pool*/
private int lastBytePoolCount = 0; private int lastBytePoolCount = 0;
@@ -119,8 +111,8 @@ public class PhantomArrayListPool
private int lastShortPoolCount = 0; private int lastShortPoolCount = 0;
/** used for debugging, represents an estimate for how many long[]'s are currently in this pool*/ /** used for debugging, represents an estimate for how many long[]'s are currently in this pool*/
private int lastLongPoolCount = 0; private int lastLongPoolCount = 0;
/** used for debugging, represents an estimate for how many char[]'s are currently in this pool*/ /** used for debugging, represents an estimate for how many checkouts are currently in this pool*/
private int lastCharPoolCount = 0; private int lastCheckoutPoolCount = 0;
/** For pools backed by {@link SoftReference}'s we may need to decrease the size when elements are garbage collected */ /** For pools backed by {@link SoftReference}'s we may need to decrease the size when elements are garbage collected */
private boolean clearLastRefPoolSizes = false; private boolean clearLastRefPoolSizes = false;
@@ -152,17 +144,12 @@ public class PhantomArrayListPool
// get checkout // // get checkout //
//==============// //==============//
public PhantomArrayListCheckout checkoutByteArrays(int count) { return this.checkoutArrays(count, 0, 0, 0); }
public PhantomArrayListCheckout checkoutShortArrays(int count) { return this.checkoutArrays(0, count, 0, 0); }
public PhantomArrayListCheckout checkoutLongArrays(int count) { return this.checkoutArrays(0, 0, count, 0); }
public PhantomArrayListCheckout checkoutCharArrays(int count) { return this.checkoutArrays(0, 0, 0, count); }
/** /**
* If possible all checkouts for a given pool should be the same size, * If possible all checkouts for a given pool should be the same size,
* since {@link PhantomArrayListCheckout}'s are shared, returning the same size * since {@link PhantomArrayListCheckout}'s are shared, returning the same size
* prevents accidentally returning a larger checkout than necessary, which wastes memory. * prevents accidentally returning a larger checkout than necessary, which wastes memory.
*/ */
public PhantomArrayListCheckout checkoutArrays(int byteArrayCount, int shortArrayCount, int longArrayCount, int charArrayCount) public PhantomArrayListCheckout checkoutArrays(int byteArrayCount, int shortArrayCount, int longArrayCount)
{ {
PhantomArrayListCheckout checkout = null; PhantomArrayListCheckout checkout = null;
while (checkout == null) while (checkout == null)
@@ -185,34 +172,24 @@ public class PhantomArrayListPool
else else
{ {
// this reference is pointing to null, // this reference is pointing to null,
// the checkout was garbage collected // the checkout must have been garbage collected,
// that means we don't have enough memory
if (!lowMemoryWarningLogged) if (!lowMemoryWarningLogged)
{ {
// Complain if there isn't much free ram. lowMemoryWarningLogged = true;
// Some garbage collectors may remove our soft references unnecessarily
// even when there is free space, and this should prevent the warning from
// popping up unnecessarily.
long freeMemoryBytes = Runtime.getRuntime().freeMemory(); String message = MinecraftTextFormat.ORANGE + "Distant Horizons: Insufficient memory detected." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
long expectedFreeMemoryBytes = 1_073_741_824 /* 1 Gibibyte */ / 8; // 128 MibiBytes
if (freeMemoryBytes < expectedFreeMemoryBytes)
{
lowMemoryWarningLogged = true;
String message = MinecraftTextFormat.ORANGE + "Distant Horizons: Insufficient memory detected." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"This may cause stuttering or crashing. \n" + "This may cause stuttering or crashing. \n" +
"Potential causes: \n" + "Potential causes: \n" +
"1. your allocated memory isn't high enough \n" + "1. your allocated memory isn't high enough \n" +
"2. your DH CPU preset is too high \n" + "2. your DH CPU preset is too high \n" +
"3. your DH quality preset is too high \n" + "3. your DH quality preset is too high \n" +
"4. you have other memory hungry mod(s)"; "4. you have other memory hungry mod(s)";
LOGGER.warn(message); LOGGER.warn(message);
if (Config.Common.Logging.Warning.showPoolInsufficientMemoryWarning.get()) if (Config.Common.Logging.Warning.showPoolInsufficientMemoryWarning.get())
{ {
ClientApi.INSTANCE.showChatMessageNextFrame(message); ClientApi.INSTANCE.showChatMessageNextFrame(message);
}
} }
} }
@@ -242,12 +219,6 @@ public class PhantomArrayListPool
checkout.addLongArrayListRef(this.createEmptyLongArrayList()); checkout.addLongArrayListRef(this.createEmptyLongArrayList());
} }
// char
for (int i = checkout.getCharArrayCount(); i < charArrayCount; i++)
{
checkout.addCharArrayListRef(this.createEmptyCharArrayList());
}
return checkout; return checkout;
} }
@@ -272,19 +243,12 @@ public class PhantomArrayListPool
this.totalLongArrayCountRef.getAndIncrement(); this.totalLongArrayCountRef.getAndIncrement();
return new LongArrayList(0); return new LongArrayList(0);
} }
private CharArrayList createEmptyCharArrayList()
{
//LOGGER.error("created new char array");
this.totalCharArrayCountRef.getAndIncrement();
return new CharArrayList(0);
}
//==================// //==================//
// phantom recovery // // phantom recovery //
//==================// //==================//
///region
private static void runPhantomReferenceCleanupLoop() private static void runPhantomReferenceCleanupLoop()
{ {
@@ -309,7 +273,6 @@ public class PhantomArrayListPool
int returnedByteArrayCount = 0; int returnedByteArrayCount = 0;
int returnedShortArrayCount = 0; int returnedShortArrayCount = 0;
int returnedLongArrayCount = 0; int returnedLongArrayCount = 0;
int returnedCharArrayCount = 0;
int checkoutCount = 0; int checkoutCount = 0;
allocationStackTraceCountPairList.clear(); allocationStackTraceCountPairList.clear();
@@ -324,12 +287,11 @@ public class PhantomArrayListPool
returnedByteArrayCount += checkout.getByteArrayCount(); returnedByteArrayCount += checkout.getByteArrayCount();
returnedShortArrayCount += checkout.getShortArrayCount(); returnedShortArrayCount += checkout.getShortArrayCount();
returnedLongArrayCount += checkout.getLongArrayCount(); returnedLongArrayCount += checkout.getLongArrayCount();
returnedCharArrayCount += checkout.getCharArrayCount();
checkoutCount++; checkoutCount++;
pool.returnCheckout(checkout); pool.returnCheckout(checkout);
if (pool.logGarbageCollectedStacks if (pool.logGarbageCollectedStacks
&& checkout.allocationStackTrace != null) // stack trace shouldn't be null, but just in case && checkout.allocationStackTrace != null) // stack trace shouldn't be null, but just in case
{ {
putAndIncrementTrackingString(checkout.allocationStackTrace, allocationStackTraceCountPairList); putAndIncrementTrackingString(checkout.allocationStackTrace, allocationStackTraceCountPairList);
} }
@@ -349,16 +311,9 @@ public class PhantomArrayListPool
if (checkoutCount != 0 if (checkoutCount != 0
|| returnedByteArrayCount != 0 || returnedByteArrayCount != 0
|| returnedShortArrayCount != 0 || returnedShortArrayCount != 0
|| returnedLongArrayCount != 0 || returnedLongArrayCount != 0)
|| returnedCharArrayCount != 0)
{ {
LOGGER.warn("Pool: ["+ pool.name+"] phantom recovery. " + LOGGER.warn("Pool: ["+ pool.name+"] phantom recovery. Returned checkouts:["+F3Screen.NUMBER_FORMAT.format(checkoutCount)+"], byte:["+F3Screen.NUMBER_FORMAT.format(returnedByteArrayCount)+"], short:["+F3Screen.NUMBER_FORMAT.format(returnedShortArrayCount)+"], long:["+F3Screen.NUMBER_FORMAT.format(returnedLongArrayCount)+"].");
"Returned checkouts:["+F3Screen.NUMBER_FORMAT.format(checkoutCount)+"], " +
"byte:["+F3Screen.NUMBER_FORMAT.format(returnedByteArrayCount)+"], " +
"short:["+F3Screen.NUMBER_FORMAT.format(returnedShortArrayCount)+"], " +
"long:["+F3Screen.NUMBER_FORMAT.format(returnedLongArrayCount)+"]," +
"char:["+F3Screen.NUMBER_FORMAT.format(returnedCharArrayCount)+"]."
);
// log stack traces if present // log stack traces if present
if (pool.logGarbageCollectedStacks) if (pool.logGarbageCollectedStacks)
@@ -420,14 +375,11 @@ public class PhantomArrayListPool
} }
} }
///endregion
//=================// //=================//
// return checkout // // return checkout //
//=================// //=================//
///region
public void returnParentPhantomRef(@NotNull PhantomReference<AbstractPhantomArrayList> parentRef) public void returnParentPhantomRef(@NotNull PhantomReference<AbstractPhantomArrayList> parentRef)
{ {
@@ -455,21 +407,18 @@ public class PhantomArrayListPool
//LOGGER.info("Returned ["+checkout.byteArrayLists.size()+"/"+this.pooledByteArrays.size()+"] bytes and ["+checkout.longArrayLists.size()+"/"+this.pooledLongArrays.size()+"] longs.");\ //LOGGER.info("Returned ["+checkout.byteArrayLists.size()+"/"+this.pooledByteArrays.size()+"] bytes and ["+checkout.longArrayLists.size()+"/"+this.pooledLongArrays.size()+"] longs.");\
} }
///endregion
//===============// //===============//
// debug methods // // debug methods //
//===============// //===============//
///region
public static void addDebugMenuStringsToListForCombinedPools(List<String> messageList) public static void addDebugMenuStringsToListForCombinedPools(List<String> messageList)
{ {
int totalByteArrayCount = 0, totalShortArrayCount = 0, totalLongArrayCount = 0, totalCharArrayCount = 0; int totalByteArrayCount = 0, totalShortArrayCount = 0, totalLongArrayCount = 0;
int pooledByteArraySize = 0, pooledShortArraySize = 0, pooledLongArraySize = 0, pooledCharArraySize = 0; int pooledByteArraySize = 0, pooledShortArraySize = 0, pooledLongArraySize = 0;
long lastBytePoolSizeInBytes = 0, lastShortPoolSizeInBytes = 0, lastLongPoolSizeInBytes = 0, lastCharPoolSizeInBytes = 0; long lastBytePoolSizeInBytes = 0, lastShortPoolSizeInBytes = 0, lastLongPoolSizeInBytes = 0;
for (int i = 0; i < POOL_LIST.size(); i++) for (int i = 0; i < POOL_LIST.size(); i++)
{ {
@@ -478,24 +427,21 @@ public class PhantomArrayListPool
totalByteArrayCount += pool.totalByteArrayCountRef.get(); totalByteArrayCount += pool.totalByteArrayCountRef.get();
totalShortArrayCount += pool.totalShortArrayCountRef.get(); totalShortArrayCount += pool.totalShortArrayCountRef.get();
totalLongArrayCount += pool.totalLongArrayCountRef.get(); totalLongArrayCount += pool.totalLongArrayCountRef.get();
totalCharArrayCount += pool.totalCharArrayCountRef.get();
pooledByteArraySize += pool.lastBytePoolCount; pooledByteArraySize += pool.lastBytePoolCount;
pooledShortArraySize += pool.lastShortPoolCount; pooledShortArraySize += pool.lastShortPoolCount;
pooledLongArraySize += pool.lastLongPoolCount; pooledLongArraySize += pool.lastLongPoolCount;
pooledCharArraySize += pool.lastCharPoolCount;
lastBytePoolSizeInBytes += pool.lastBytePoolSizeInBytes; lastBytePoolSizeInBytes += pool.lastBytePoolSizeInBytes;
lastShortPoolSizeInBytes += pool.lastShortPoolSizeInBytes; lastShortPoolSizeInBytes += pool.lastShortPoolSizeInBytes;
lastLongPoolSizeInBytes += pool.lastLongPoolSizeInBytes; lastLongPoolSizeInBytes += pool.lastLongPoolSizeInBytes;
lastCharPoolSizeInBytes += pool.lastCharPoolSizeInBytes;
} }
addDebugMenuStringsToList(messageList, addDebugMenuStringsToList(messageList,
"Combined", "Combined",
totalByteArrayCount, totalShortArrayCount, totalLongArrayCount, totalCharArrayCount, totalByteArrayCount, totalShortArrayCount, totalLongArrayCount,
pooledByteArraySize, pooledShortArraySize, pooledLongArraySize, pooledCharArraySize, pooledByteArraySize, pooledShortArraySize, pooledLongArraySize,
lastBytePoolSizeInBytes, lastShortPoolSizeInBytes, lastLongPoolSizeInBytes, lastCharPoolSizeInBytes lastBytePoolSizeInBytes, lastShortPoolSizeInBytes, lastLongPoolSizeInBytes
); );
} }
@@ -512,28 +458,26 @@ public class PhantomArrayListPool
{ {
addDebugMenuStringsToList(messageList, addDebugMenuStringsToList(messageList,
this.name, this.name,
this.totalByteArrayCountRef.get(), this.totalShortArrayCountRef.get(), this.totalLongArrayCountRef.get(), this.totalCharArrayCountRef.get(), this.totalByteArrayCountRef.get(), this.totalShortArrayCountRef.get(), this.totalLongArrayCountRef.get(),
this.lastBytePoolCount, this.lastShortPoolCount, this.lastLongPoolCount, this.lastCharPoolCount, this.lastBytePoolCount, this.lastShortPoolCount, this.lastLongPoolCount,
this.lastBytePoolSizeInBytes, this.lastShortPoolSizeInBytes, this.lastLongPoolSizeInBytes, this.lastCharPoolSizeInBytes this.lastBytePoolSizeInBytes, this.lastShortPoolSizeInBytes, this.lastLongPoolSizeInBytes
); );
} }
private static void addDebugMenuStringsToList(List<String> messageList, private static void addDebugMenuStringsToList(List<String> messageList,
String name, String name,
int totalByteArrayCount, int totalShortArrayCount, int totalLongArrayCount, int totalCharArrayCount, int totalByteArrayCount, int totalShortArrayCount, int totalLongArrayCount,
int numbOfByteArraysInPool, int numbOfShortArraysInPool, int numbOfLongArraysInPool, int numbOfCharArraysInPool, int numbOfByteArraysInPool, int numbOfShortArraysInPool, int numbOfLongArraysInPool,
long lastBytePoolSizeInBytes, long lastShortPoolSizeInBytes, long lastLongPoolSizeInBytes, long lastCharPoolSizeInBytes) long lastBytePoolSizeInBytes, long lastShortPoolSizeInBytes, long lastLongPoolSizeInBytes)
{ {
// total (all time created) count // total (all time created) count
String byteArrayTotalCount = F3Screen.NUMBER_FORMAT.format(totalByteArrayCount); String byteArrayTotalCount = F3Screen.NUMBER_FORMAT.format(totalByteArrayCount);
String shortArrayTotalCount = F3Screen.NUMBER_FORMAT.format(totalShortArrayCount); String shortArrayTotalCount = F3Screen.NUMBER_FORMAT.format(totalShortArrayCount);
String longArrayTotalCount = F3Screen.NUMBER_FORMAT.format(totalLongArrayCount); String longArrayTotalCount = F3Screen.NUMBER_FORMAT.format(totalLongArrayCount);
String charArrayTotalCount = F3Screen.NUMBER_FORMAT.format(totalCharArrayCount);
// inactive items in pool // inactive items in pool
String bytePoolCount = F3Screen.NUMBER_FORMAT.format(numbOfByteArraysInPool); String bytePoolCount = F3Screen.NUMBER_FORMAT.format(numbOfByteArraysInPool);
String shortPoolCount = F3Screen.NUMBER_FORMAT.format(numbOfShortArraysInPool); String shortPoolCount = F3Screen.NUMBER_FORMAT.format(numbOfShortArraysInPool);
String longPoolCount = F3Screen.NUMBER_FORMAT.format(numbOfLongArraysInPool); String longPoolCount = F3Screen.NUMBER_FORMAT.format(numbOfLongArraysInPool);
String charPoolCount = F3Screen.NUMBER_FORMAT.format(numbOfCharArraysInPool);
// pool byte size // pool byte size
String bytePoolSizeInBytes = (lastBytePoolSizeInBytes != -1) String bytePoolSizeInBytes = (lastBytePoolSizeInBytes != -1)
@@ -545,32 +489,20 @@ public class PhantomArrayListPool
String longPoolSizeInBytes = (lastLongPoolSizeInBytes != -1) String longPoolSizeInBytes = (lastLongPoolSizeInBytes != -1)
? " ~" + StringUtil.convertBytesToHumanReadable(lastLongPoolSizeInBytes) ? " ~" + StringUtil.convertBytesToHumanReadable(lastLongPoolSizeInBytes)
: ""; : "";
String charPoolSizeInBytes = (lastCharPoolSizeInBytes != -1)
? " ~" + StringUtil.convertBytesToHumanReadable(lastCharPoolSizeInBytes)
: "";
messageList.add(name + " - Pools:");
String a = MinecraftTextFormat.AQUA;
String y = MinecraftTextFormat.YELLOW;
String cf = MinecraftTextFormat.CLEAR_FORMATTING;
messageList.add(a+name+cf + " - Pools:");
if (totalByteArrayCount != 0) if (totalByteArrayCount != 0)
{ {
messageList.add("byte[]: " + bytePoolCount + "/" + byteArrayTotalCount + y+bytePoolSizeInBytes+cf); messageList.add("byte[]: " + bytePoolCount + "/" + byteArrayTotalCount + bytePoolSizeInBytes);
} }
if (totalShortArrayCount != 0) if (totalShortArrayCount != 0)
{ {
messageList.add("short[]: " + shortPoolCount + "/" + shortArrayTotalCount + y+shortPoolSizeInBytes+cf); messageList.add("short[]: " + shortPoolCount + "/" + shortArrayTotalCount + shortPoolSizeInBytes);
} }
if (totalLongArrayCount != 0) if (totalLongArrayCount != 0)
{ {
messageList.add("long[]: " + longPoolCount + "/" + longArrayTotalCount + y+longPoolSizeInBytes+cf); messageList.add("long[]: " + longPoolCount + "/" + longArrayTotalCount + longPoolSizeInBytes);
}
if (totalCharArrayCount != 0)
{
messageList.add("char[]: " + charPoolCount + "/" + charArrayTotalCount + y+charPoolSizeInBytes+cf);
} }
} }
@@ -584,12 +516,10 @@ public class PhantomArrayListPool
long bytePoolByteSize = 0; long bytePoolByteSize = 0;
long shortPoolByteSize = 0; long shortPoolByteSize = 0;
long longPoolByteSize = 0; long longPoolByteSize = 0;
long charPoolByteSize = 0;
int bytePoolCount = 0; int bytePoolCount = 0;
int shortPoolCount = 0; int shortPoolCount = 0;
int longPoolCount = 0; int longPoolCount = 0;
int charPoolCount = 0;
// checkouts // // checkouts //
@@ -607,8 +537,6 @@ public class PhantomArrayListPool
shortPoolCount += pooledCheckout.getAllShortArrays().size(); shortPoolCount += pooledCheckout.getAllShortArrays().size();
longPoolByteSize += estimateMemoryUsage(pooledCheckout.getAllLongArrays(), Long.BYTES); longPoolByteSize += estimateMemoryUsage(pooledCheckout.getAllLongArrays(), Long.BYTES);
longPoolCount += pooledCheckout.getAllLongArrays().size(); longPoolCount += pooledCheckout.getAllLongArrays().size();
charPoolByteSize += estimateMemoryUsage(pooledCheckout.getAllCharArrays(), Character.BYTES);
charPoolCount += pooledCheckout.getAllCharArrays().size();
} }
@@ -618,10 +546,11 @@ public class PhantomArrayListPool
this.lastBytePoolSizeInBytes = 0; this.lastBytePoolSizeInBytes = 0;
this.lastShortPoolSizeInBytes = 0; this.lastShortPoolSizeInBytes = 0;
this.lastLongPoolSizeInBytes = 0; this.lastLongPoolSizeInBytes = 0;
this.lastCharPoolSizeInBytes = 0;
this.clearLastRefPoolSizes = false; this.clearLastRefPoolSizes = false;
} }
this.lastCheckoutPoolCount = this.pooledCheckoutsRefs.size();
// byte // // byte //
// math.max is used since the pool should only grow until a soft reference is freed, // math.max is used since the pool should only grow until a soft reference is freed,
// and it's easier to understand if this constantly grows instead of jumping around // and it's easier to understand if this constantly grows instead of jumping around
@@ -635,10 +564,6 @@ public class PhantomArrayListPool
// long // // long //
this.lastLongPoolSizeInBytes = Math.max(longPoolByteSize, this.lastLongPoolSizeInBytes); this.lastLongPoolSizeInBytes = Math.max(longPoolByteSize, this.lastLongPoolSizeInBytes);
this.lastLongPoolCount = longPoolCount; this.lastLongPoolCount = longPoolCount;
// char //
this.lastCharPoolSizeInBytes = Math.max(charPoolByteSize, this.lastCharPoolSizeInBytes);
this.lastCharPoolCount = charPoolCount;
} }
private static <T extends Collection<?>> long estimateMemoryUsage(Iterable<T> pool, long elementSizeInBytes) private static <T extends Collection<?>> long estimateMemoryUsage(Iterable<T> pool, long elementSizeInBytes)
@@ -689,10 +614,6 @@ public class PhantomArrayListPool
{ {
elementCount = ((LongArrayList)array).elements().length; elementCount = ((LongArrayList)array).elements().length;
} }
else if (array instanceof CharArrayList)
{
elementCount = ((CharArrayList)array).elements().length;
}
else else
{ {
throw new UnsupportedOperationException("Not implemented for type ["+array.getClass().getSimpleName()+"]."); throw new UnsupportedOperationException("Not implemented for type ["+array.getClass().getSimpleName()+"].");
@@ -701,8 +622,6 @@ public class PhantomArrayListPool
return elementCount; return elementCount;
} }
///endregion
} }

Some files were not shown because too many files have changed in this diff Show More