Compare commits

..

4 Commits

Author SHA1 Message Date
Ran f767215ff0 Merge remote-tracking branch 'origin/java_omega' into java_omega
# Conflicts:
#	core/build.gradle
2024-07-26 00:20:42 +10:00
Ran ef87a4e595 Attempt to migrate most of the codebase to newer versions of Java 2024-07-26 00:17:15 +10:00
Cutiepie 1e4fb66e9a Java Ω & cleanup core's code 2024-07-25 23:32:34 +10:00
Cutiepie fef87df09b Java Ω & cleanup core's code 2024-05-22 21:03:38 +10:00
161 changed files with 3869 additions and 3674 deletions
@@ -60,16 +60,15 @@ public class DhApi
* *
* Note: Don't use this string in your code. It may change and is only for reference. * Note: Don't use this string in your code. It may change and is only for reference.
*/ */
public static final String READ_ME = public static final String READ_ME =
"If you don't see Javadocs something is wrong. \n" + """
"If you are only using the full DH Mod in your build script, you won't have access to our javadocs and could potentially call into unsafe code. \n" + If you don't see Javadocs something is wrong.
"\n" + If you are only using the full DH Mod in your build script, you won't have access to our javadocs and could potentially call into unsafe code.
"Please use the API jar in your build script as a compile time dependency " +
"and the full DH jar as a runtime dependency. \n" + Please use the API jar in your build script as a compile time dependency and the full DH jar as a runtime dependency.
"\n" +
"Please refer to the example API project or the DH Developer Wiki for additional information " + Please refer to the example API project or the DH Developer Wiki for additional information and suggested setup.
"and suggested setup. \n" + // DH Dev note: no links were included to prevent link rot. """; // DH Dev note: no links were included to prevent link rot.
"";
public static String readMe() { return READ_ME; } public static String readMe() { return READ_ME; }
/** /**
@@ -180,7 +179,7 @@ public class DhApi
* This version should be updated whenever non-breaking fixes are added to the Distant Horizons API. * This version should be updated whenever non-breaking fixes are added to the Distant Horizons API.
* @since API 1.0.0 * @since API 1.0.0
*/ */
public static int getApiPatchVersion() { return ModInfo.API_PATCH_VERSION; } public static int getApiPatchVersion() { return ModInfo.API_PATH_VERSION; }
/** /**
* Returns the mod's semantic version number in the format: Major.Minor.Patch * Returns the mod's semantic version number in the format: Major.Minor.Patch
@@ -24,7 +24,7 @@ package com.seibel.distanthorizons.api.enums;
* CHUNK - Detail Level: 4, width 16 block, <br> * CHUNK - Detail Level: 4, width 16 block, <br>
* REGION - Detail Level: 9, width 512 block <br> <br> * REGION - Detail Level: 9, width 512 block <br> <br>
* *
* Detail levels in Distant Horizons represent how large a LOD * Detail levels in Distant Horizons represent how large a section (of either LODs or MC chunks)
* is, with the smallest being 0 (1 block wide). <br> * is, with the smallest being 0 (1 block wide). <br>
* The width of a detail level can be calculated by putting the detail level to the power of 2. <br> * The width of a detail level can be calculated by putting the detail level to the power of 2. <br>
* Example for the chunk detail level (4): 2^4 = 16 blocks wide <Br><br> * Example for the chunk detail level (4): 2^4 = 16 blocks wide <Br><br>
@@ -23,12 +23,13 @@ package com.seibel.distanthorizons.api.enums.config;
* AUTO, <br> * AUTO, <br>
* BUFFER_STORAGE, <br> * BUFFER_STORAGE, <br>
* SUB_DATA, <br> * SUB_DATA, <br>
* BUFFER_MAPPING, <br>
* DATA <br> * DATA <br>
* *
* @author Leetom * @author Leetom
* @author James Seibel * @author James Seibel
* @version 2024-4-6 * @version 2024-4-6
* @since API 3.0.0 * @since API 2.0.0
*/ */
public enum EDhApiGpuUploadMethod public enum EDhApiGpuUploadMethod
{ {
@@ -48,10 +49,7 @@ public enum EDhApiGpuUploadMethod
* May end up storing buffers in System memory. <br> * May end up storing buffers in System memory. <br>
* Fast rending if in GPU memory, slow if in system memory, <br> * Fast rending if in GPU memory, slow if in system memory, <br>
* but won't stutter when uploading. * but won't stutter when uploading.
*
* @deprecated not currently supported
*/ */
@Deprecated
BUFFER_MAPPING(true, false), BUFFER_MAPPING(true, false),
/** Fast rendering but may stutter when uploading. */ /** Fast rendering but may stutter when uploading. */
@@ -21,7 +21,6 @@ package com.seibel.distanthorizons.api.enums.rendering;
* AIR, <br> * AIR, <br>
* ILLUMINATED, <br> * ILLUMINATED, <br>
* *
* @author IMS
* @author James Seibel * @author James Seibel
* @since API 3.0.0 * @since API 3.0.0
* @version 2024-7-11 * @version 2024-7-11
@@ -56,34 +56,23 @@ public enum EDhApiDebugRendering
public static EDhApiDebugRendering next(EDhApiDebugRendering type) public static EDhApiDebugRendering next(EDhApiDebugRendering type)
{ {
switch (type) return switch (type) {
{ case OFF -> SHOW_DETAIL;
case OFF: case SHOW_DETAIL -> SHOW_BLOCK_MATERIAL;
return SHOW_DETAIL; case SHOW_BLOCK_MATERIAL -> SHOW_OVERLAPPING_QUADS;
case SHOW_DETAIL: case SHOW_OVERLAPPING_QUADS -> SHOW_RENDER_SOURCE_FLAG;
return SHOW_BLOCK_MATERIAL; default -> OFF;
case SHOW_BLOCK_MATERIAL: };
return SHOW_OVERLAPPING_QUADS;
case SHOW_OVERLAPPING_QUADS:
return SHOW_RENDER_SOURCE_FLAG;
default:
return OFF;
}
} }
public static EDhApiDebugRendering previous(EDhApiDebugRendering type) public static EDhApiDebugRendering previous(EDhApiDebugRendering type)
{ {
switch (type) return switch (type) {
{ case OFF -> SHOW_RENDER_SOURCE_FLAG;
case OFF: case SHOW_RENDER_SOURCE_FLAG -> SHOW_OVERLAPPING_QUADS;
return SHOW_RENDER_SOURCE_FLAG; case SHOW_OVERLAPPING_QUADS -> SHOW_DETAIL;
case SHOW_RENDER_SOURCE_FLAG: default -> OFF;
return SHOW_OVERLAPPING_QUADS; };
case SHOW_OVERLAPPING_QUADS:
return SHOW_DETAIL;
default:
return OFF;
}
} }
} }
@@ -42,29 +42,21 @@ public enum EDhApiRendererMode
/** Used by the config GUI to cycle through the available rendering options */ /** Used by the config GUI to cycle through the available rendering options */
public static EDhApiRendererMode next(EDhApiRendererMode type) public static EDhApiRendererMode next(EDhApiRendererMode type)
{ {
switch (type) return switch (type) {
{ case DEFAULT -> DEBUG;
case DEFAULT: case DEBUG -> DISABLED;
return DEBUG; default -> DEFAULT;
case DEBUG: };
return DISABLED;
default:
return DEFAULT;
}
} }
/** Used by the config GUI to cycle through the available rendering options */ /** Used by the config GUI to cycle through the available rendering options */
public static EDhApiRendererMode previous(EDhApiRendererMode type) public static EDhApiRendererMode previous(EDhApiRendererMode type)
{ {
switch (type) return switch (type) {
{ case DEFAULT -> DISABLED;
case DEFAULT: case DEBUG -> DEFAULT;
return DISABLED; default -> DEBUG;
case DEBUG: };
return DEFAULT;
default:
return DEBUG;
}
} }
} }
@@ -19,7 +19,6 @@
package com.seibel.distanthorizons.api.interfaces.block; package com.seibel.distanthorizons.api.interfaces.block;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.api.interfaces.IDhApiUnsafeWrapper; import com.seibel.distanthorizons.api.interfaces.IDhApiUnsafeWrapper;
/** /**
@@ -45,11 +44,7 @@ public interface IDhApiBlockStateWrapper extends IDhApiUnsafeWrapper
* @since API 3.0.0 * @since API 3.0.0
*/ */
String getSerialString(); String getSerialString();
/** /** @since API 3.0.0 */
* Returns the byte value representing the {@link EDhApiBlockMaterial} enum.
* @see EDhApiBlockMaterial
* @since API 3.0.0
*/
byte getMaterialId(); byte getMaterialId();
} }
@@ -36,6 +36,7 @@ public interface IDhApiConfig
IDhApiWorldGenerationConfig worldGenerator(); IDhApiWorldGenerationConfig worldGenerator();
IDhApiMultiplayerConfig multiplayer(); IDhApiMultiplayerConfig multiplayer();
IDhApiMultiThreadingConfig multiThreading(); IDhApiMultiThreadingConfig multiThreading();
IDhApiGpuBuffersConfig gpuBuffers();
// note: DON'T add the Auto Updater to this API. We only want the user's to have the ability to control when things are downloaded to their machines. // note: DON'T add the Auto Updater to this API. We only want the user's to have the ability to control when things are downloaded to their machines.
//IDhApiLoggingConfig logging(); // TODO implement //IDhApiLoggingConfig logging(); // TODO implement
IDhApiDebuggingConfig debugging(); IDhApiDebuggingConfig debugging();
@@ -0,0 +1,48 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.api.interfaces.config.client;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigGroup;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
/**
* Distant Horizons' OpenGL buffer configuration.
*
* @author James Seibel
* @version 2023-6-14
* @since API 1.0.0
*/
public interface IDhApiGpuBuffersConfig extends IDhApiConfigGroup
{
/** Defines how geometry data is uploaded to the GPU. */
IDhApiConfigValue<EDhApiGpuUploadMethod> gpuUploadMethod();
/**
* Defines how long we should wait after uploading one
* Megabyte of geometry data to the GPU before uploading
* the next Megabyte of data. <br>
* This can be set to a non-zero number to reduce stuttering caused by
* uploading buffers to the GPU.
*/
IDhApiConfigValue<Integer> gpuUploadPerMegabyteInMilliseconds();
}
@@ -90,29 +90,11 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable
*/ */
default byte getMaxGenerationGranularity() { return (byte) (EDhApiDetailLevel.CHUNK.detailLevel + 2); } default byte getMaxGenerationGranularity() { return (byte) (EDhApiDetailLevel.CHUNK.detailLevel + 2); }
/**
* Starting in API 3.0.0 DH now handles future queuing/management internally. <br><br>
*
* Previous description: <br>
* true if the generator is unable to accept new generation requests. <br>
*
* @since API 1.0.0
* @deprecated API 3.0.0
*/
@Deprecated
default boolean isBusy() { return false; }
/** /**
* Only used if {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_CHUNKS}. <Br> * @return true if the generator is unable to accept new generation requests.
* If true DH will run additional validation on the {@link DhApiChunk}'s returned. <Br> * @since API 1.0.0
* This should be disabled during release but should be enabled during development to help spot issues with your data format.
*
* @see #getReturnType()
* @see DhApiChunk
* @see EDhApiWorldGeneratorReturnType#API_CHUNKS
* @since API 3.0.0
*/ */
default boolean runApiChunkValidation() { return true; } boolean isBusy();
@@ -26,7 +26,7 @@ import com.seibel.distanthorizons.api.objects.DhApiResult;
* Used to interact with Distant Horizons' rendering system. * Used to interact with Distant Horizons' rendering system.
* *
* @author James Seibel * @author James Seibel
* @version 2024-7-27 * @version 2023-10-13
* @since API 1.0.0 * @since API 1.0.0
*/ */
public interface IDhApiRenderProxy public interface IDhApiRenderProxy
@@ -39,8 +39,10 @@ public interface IDhApiRenderProxy
* If this is called on a dedicated server it won't do anything and will return {@link DhApiResult#success} = false <Br><Br> * If this is called on a dedicated server it won't do anything and will return {@link DhApiResult#success} = false <Br><Br>
* *
* Background: <Br> * Background: <Br>
* When rendering Distant Horizons bakes each block's color into the geometry that's rendered. <Br> * Distant Horizons has two different file formats: Full data and Render data. <Br>
* This improves rendering speed and VRAM size, but prevents dynamically changing LOD colors. <Br> * - Full data files store the block, biome, etc. information and is the result of loading or generating new chunks. <Br>
* - Render data files store LOD colors and are created using the Full data and currently loaded resource packs. <Br>
* This is the data cleared by this method.
*/ */
DhApiResult<Boolean> clearRenderDataCache(); DhApiResult<Boolean> clearRenderDataCache();
@@ -28,7 +28,7 @@ import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderRegist
* A level is equivalent to a dimension in vanilla Minecraft. * A level is equivalent to a dimension in vanilla Minecraft.
* *
* @author James Seibel * @author James Seibel
* @version 2024-7-28 * @version 2022-7-14
* @since API 1.0.0 * @since API 1.0.0
*/ */
public interface IDhApiLevelWrapper extends IDhApiUnsafeWrapper public interface IDhApiLevelWrapper extends IDhApiUnsafeWrapper
@@ -41,18 +41,7 @@ public interface IDhApiLevelWrapper extends IDhApiUnsafeWrapper
boolean hasSkyLight(); boolean hasSkyLight();
/** /** Returns the max block height of the level(?) */
* Deprecated, use {@link IDhApiLevelWrapper#getMaxHeight} instead. <br>
* Returns the max block height of the level.
*
* @see IDhApiLevelWrapper#getMaxHeight
*/
@Deprecated
default int getHeight() { return this.getMaxHeight(); }
/**
* Returns the max block height of the level
* @since API 3.0.0
*/
int getMaxHeight(); int getMaxHeight();
/** /**
@@ -64,8 +53,6 @@ public interface IDhApiLevelWrapper extends IDhApiUnsafeWrapper
/** /**
* Will return null if called on the server, * Will return null if called on the server,
* or if called before the renderer has been set up. * or if called before the renderer has been set up.
*
* @since API 3.0.0
*/ */
IDhApiCustomRenderRegister getRenderRegister(); IDhApiCustomRenderRegister getRenderRegister();
@@ -59,12 +59,12 @@ public class DhApiResult<T>
public static <Pt> DhApiResult<Pt> createSuccess() { return new DhApiResult<>(true, ""); } public static <Pt> DhApiResult<Pt> createSuccess() { return new DhApiResult<>(true, ""); }
public static <Pt> DhApiResult<Pt> createSuccess(Pt payload) { return new DhApiResult<Pt>(true, "", payload); } public static <Pt> DhApiResult<Pt> createSuccess(Pt payload) { return new DhApiResult<>(true, "", payload); }
// There is no createSuccess(String message) method because it would be too easy to confuse with createSuccess(Pt payload) when returning null // There is no createSuccess(String message) method because it would be too easy to confuse with createSuccess(Pt payload) when returning null
public static <Pt> DhApiResult<Pt> createSuccess(String message, Pt payload) { return new DhApiResult<Pt>(true, message, payload); } public static <Pt> DhApiResult<Pt> createSuccess(String message, Pt payload) { return new DhApiResult<>(true, message, payload); }
// there is no createFail() since all fail results should give a reason for their failure // there is no createFail() since all fail results should give a reason for their failure
public static <Pt> DhApiResult<Pt> createFail(String message) { return new DhApiResult<>(false, message); } public static <Pt> DhApiResult<Pt> createFail(String message) { return new DhApiResult<>(false, message); }
public static <Pt> DhApiResult<Pt> createFail(String message, Pt payload) { return new DhApiResult<Pt>(false, message, payload); } public static <Pt> DhApiResult<Pt> createFail(String message, Pt payload) { return new DhApiResult<>(false, message, payload); }
} }
@@ -21,10 +21,8 @@ package com.seibel.distanthorizons.api.objects.data;
import com.seibel.distanthorizons.api.interfaces.factories.IDhApiWrapperFactory; import com.seibel.distanthorizons.api.interfaces.factories.IDhApiWrapperFactory;
import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGenerator; import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGenerator;
import org.apache.logging.log4j.LogManager;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
@@ -54,27 +52,7 @@ public class DhApiChunk
// constructors // // constructors //
//==============// //==============//
/** public DhApiChunk(int chunkPosX, int chunkPosZ, int bottomYBlockPos, int topYBlockPos)
* Deprecated due to the topYBlockPos and bottomYBlockPos variables being put in the wrong order.
* They should have been in bottom -> top order.
*
* @see DhApiChunk#create(int, int, int, int)
*/
@Deprecated
public DhApiChunk(int chunkPosX, int chunkPosZ, int topYBlockPos, int bottomYBlockPos)
{ this(chunkPosX, chunkPosZ, bottomYBlockPos, topYBlockPos, false); }
/**
* @since API 3.0.0
*/
public static DhApiChunk create(int chunkPosX, int chunkPosZ, int bottomYBlockPos, int topYBlockPos)
{ return new DhApiChunk(chunkPosX, chunkPosZ, bottomYBlockPos, topYBlockPos, false); }
/**
* Only visible to internal DH methods
* @param ignoredParameter is only present to differentiate the two constructors and isn't actually used
*/
private DhApiChunk(int chunkPosX, int chunkPosZ, int bottomYBlockPos, int topYBlockPos, boolean ignoredParameter)
{ {
this.chunkPosX = chunkPosX; this.chunkPosX = chunkPosX;
this.chunkPosZ = chunkPosZ; this.chunkPosZ = chunkPosZ;
@@ -116,34 +94,27 @@ public class DhApiChunk
*/ */
public void setDataPoints(int relX, int relZ, List<DhApiTerrainDataPoint> dataPoints) throws IndexOutOfBoundsException, IllegalArgumentException public void setDataPoints(int relX, int relZ, List<DhApiTerrainDataPoint> dataPoints) throws IndexOutOfBoundsException, IllegalArgumentException
{ {
//==================//
// basic validation //
//==================//
// heavier validation is done in the world generator if requested
int internalArrayIndex = (relZ << 4) | relX;
throwIfRelativePosOutOfBounds(relX, relZ); throwIfRelativePosOutOfBounds(relX, relZ);
if (dataPoints == null) // validate the incoming datapoints
if (dataPoints != null)
{ {
// we don't allow null columns for (int i = 0; i < dataPoints.size(); i++) // standard for-loop used instead of an enhanced for-loop to slightly reduce GC overhead due to iterator allocation
throw new IllegalArgumentException("Null columns aren't allowed. If you want to remove all data from a column please clear the list or pass in an empty list."); {
DhApiTerrainDataPoint dataPoint = dataPoints.get(i);
if (dataPoint == null)
{
throw new IllegalArgumentException("Null DhApiTerrainDataPoints are not allowed. If you want to represent empty terrain, please use AIR.");
}
if (dataPoint.detailLevel != 0)
{
throw new IllegalArgumentException("DhApiTerrainDataPoints has the wrong detail level ["+dataPoint.detailLevel+"], all data points must be block sized; IE their detail level must be [0].");
}
}
} }
this.dataPoints.set((relZ << 4) | relX, dataPoints);
//================//
// set datapoints //
//================//
List<DhApiTerrainDataPoint> column = this.dataPoints.get(internalArrayIndex);
if (column == null)
{
column = new ArrayList<>();
this.dataPoints.set(internalArrayIndex, column);
}
column.addAll(dataPoints);
} }
@@ -19,12 +19,9 @@
package com.seibel.distanthorizons.api.objects.data; package com.seibel.distanthorizons.api.objects.data;
import com.seibel.distanthorizons.api.enums.EDhApiDetailLevel;
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBiomeWrapper; import com.seibel.distanthorizons.api.interfaces.block.IDhApiBiomeWrapper;
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper; import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper;
import java.util.ArrayList;
/** /**
* Holds a single datapoint of terrain data. * Holds a single datapoint of terrain data.
* *
@@ -40,8 +37,6 @@ public class DhApiTerrainDataPoint
* 2 = 4x4 blocks <br> * 2 = 4x4 blocks <br>
* 4 = chunk (16x16 blocks) <br> * 4 = chunk (16x16 blocks) <br>
* 9 = region (512x512 blocks) <br> * 9 = region (512x512 blocks) <br>
*
* @see EDhApiDetailLevel
*/ */
public final byte detailLevel; public final byte detailLevel;
@@ -55,57 +50,7 @@ public class DhApiTerrainDataPoint
//==============// public DhApiTerrainDataPoint(byte detailLevel, int blockLightLevel, int skyLightLevel, int bottomYBlockPos, int topYBlockPos, IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper)
// constructors //
//==============//
/**
* Deprecated due to the topYBlockPos and bottomYBlockPos variables being put in the wrong order.
* They should have been in bottom -> top order.
*
* @see DhApiTerrainDataPoint#create(byte, int, int, int, int, IDhApiBlockStateWrapper, IDhApiBiomeWrapper)
*/
@Deprecated
public DhApiTerrainDataPoint(
byte detailLevel,
int blockLightLevel, int skyLightLevel,
int topYBlockPos, int bottomYBlockPos,
IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper)
{
this(detailLevel, blockLightLevel, skyLightLevel,
bottomYBlockPos, topYBlockPos,
blockStateWrapper, biomeWrapper,
false);
}
/**
* @since API 3.0.0
*/
public static DhApiTerrainDataPoint create(
byte detailLevel,
int blockLightLevel, int skyLightLevel,
int bottomYBlockPos, int topYBlockPos,
IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper
)
{
return new DhApiTerrainDataPoint(
detailLevel, blockLightLevel, skyLightLevel,
bottomYBlockPos, topYBlockPos,
blockStateWrapper, biomeWrapper,
false);
}
/**
* Only visible to internal DH methods
* @param ignoredParameter is only present to differentiate the two constructors and isn't actually used
*/
private DhApiTerrainDataPoint(
byte detailLevel,
int blockLightLevel, int skyLightLevel,
int bottomYBlockPos, int topYBlockPos,
IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper,
boolean ignoredParameter
)
{ {
this.detailLevel = detailLevel; this.detailLevel = detailLevel;
@@ -163,9 +163,8 @@ public class ApiEventInjector extends DependencyInjector<IDhApiEvent> implements
DhApiEventParam<T> eventParam = createEventParamWrapper(event, input); DhApiEventParam<T> eventParam = createEventParamWrapper(event, input);
event.fireEvent(eventParam); event.fireEvent(eventParam);
if (eventParam instanceof DhApiCancelableEventParam) if (eventParam instanceof DhApiCancelableEventParam<T> cancelableEventParam)
{ {
DhApiCancelableEventParam<T> cancelableEventParam = (DhApiCancelableEventParam<T>) eventParam;
cancelEvent |= cancelableEventParam.isEventCanceled(); cancelEvent |= cancelableEventParam.isEventCanceled();
} }
@@ -87,7 +87,7 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
// make sure the hashSet has an array to hold the dependency // make sure the hashSet has an array to hold the dependency
if (!this.dependencies.containsKey(dependencyInterface)) if (!this.dependencies.containsKey(dependencyInterface))
{ {
this.dependencies.put(dependencyInterface, new ArrayList<BindableType>()); this.dependencies.put(dependencyInterface, new ArrayList<>());
} }
// add the dependency // add the dependency
@@ -134,7 +134,7 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
@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).getFirst();
} }
@Override @Override
@@ -146,7 +146,7 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
@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).getFirst();
} }
/** /**
@@ -175,7 +175,7 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
// return an empty list to prevent null pointers // return an empty list to prevent null pointers
ArrayList<T> emptyList = new ArrayList<T>(); ArrayList<T> emptyList = new ArrayList<>();
emptyList.add(null); emptyList.add(null);
return emptyList; return emptyList;
} }
@@ -64,14 +64,14 @@ public class OverridePriorityListContainer implements IBindable
else else
{ {
// last item should have the highest priority // last item should have the highest priority
return this.overridePairList.get(this.overridePairList.size() - 1).override; return this.overridePairList.getLast().override;
} }
} }
public IDhApiOverrideable getOverrideWithHighestPriority() public IDhApiOverrideable getOverrideWithHighestPriority()
{ {
if (this.overridePairList.size() != 0) if (this.overridePairList.size() != 0)
{ {
return this.overridePairList.get(0).override; return this.overridePairList.getFirst().override;
} }
else else
{ {
@@ -34,7 +34,7 @@ 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 = "2.2.1-a"; public static final String VERSION = "2.1.3-a-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 boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev"); public static boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
@@ -43,7 +43,7 @@ public final class ModInfo
/** 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 = 0;
/** 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 = 1; public static final int API_PATH_VERSION = 0;
public static final String NETWORKING_RESOURCE_NAMESPACE = "distant_horizons"; public static final String NETWORKING_RESOURCE_NAMESPACE = "distant_horizons";
public static final String MULTIVERSE_PLUGIN_NAMESPACE = "world_control"; public static final String MULTIVERSE_PLUGIN_NAMESPACE = "world_control";
@@ -24,12 +24,12 @@ import java.util.Arrays;
/** /**
* Miscellaneous string helper functions. * Miscellaneous string helper functions.
*
* @author James Seibel
* @version 2022-7-19
*/ */
public class StringUtil public class StringUtil
{ {
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
/** /**
* Returns the n-th index of the given string. <br> <br> * Returns the n-th index of the given string. <br> <br>
* *
@@ -67,6 +67,8 @@ public class StringUtil
return stringBuilder.toString(); return stringBuilder.toString();
} }
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
/** /**
* Converts the given byte array into a hex string representation. <br> * Converts the given byte array into a hex string representation. <br>
* source: https://stackoverflow.com/a/9855338 * source: https://stackoverflow.com/a/9855338
@@ -83,20 +85,4 @@ public class StringUtil
return new String(hexChars); return new String(hexChars);
} }
/**
* Returns a shortened version of the given string that is no longer than maxLength. <br>
* If null returns the empty string.
*/
public static String shortenString(String str, int maxLength)
{
if (str == null)
{
return "";
}
else
{
return str.substring(0, Math.min(str.length(), maxLength));
}
}
} }
+3 -12
View File
@@ -10,7 +10,7 @@ application {
} }
configurations { configurations {
shadowedArtifact // Used by DH to specify that we want to implement the shadowed core JAR file instead of the regular JAR file downgradedArtifact // 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
} }
@@ -39,9 +39,7 @@ dependencies { // All of these dependencies are in Vanilla Minecraft, but we nee
runtimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives" runtimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-stb::$lwjglNatives" runtimeOnly "org.lwjgl:lwjgl-stb::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-tinyfd::$lwjglNatives" runtimeOnly "org.lwjgl:lwjgl-tinyfd::$lwjglNatives"
implementation "org.joml:joml:${rootProject.joml_version}"
// FIXME for some reason this line doesn't actually shade in the library
// shade "it.unimi.dsi:fastutil:${rootProject.fastutil_version}" // Add our own fastutil version
// Some other dependencies // Some other dependencies
@@ -49,15 +47,8 @@ dependencies { // All of these dependencies are in Vanilla Minecraft, but we nee
implementation("com.google.code.findbugs:jsr305:3.0.2") implementation("com.google.code.findbugs:jsr305:3.0.2")
implementation("com.google.common:google-collect:0.5") implementation("com.google.common:google-collect:0.5")
implementation("com.google.guava:guava:31.1-jre") implementation("com.google.guava:guava:31.1-jre")
} }
artifacts { artifacts {
shadowedArtifact shadowJar // Setup the configuration shadowedArtifact to be the shadowJar downgradedArtifact shadeDowngradedApi // Setup the configuration downgradedArtifact to be the `shadeDowngradedApi` which downgrades the core to a specified Java version
}
shadowJar {
def librariesLocation = "DistantHorizons.libraries"
// relocate "it.unimi.dsi.fastutil", "${librariesLocation}.unimi.dsi.fastutil"
mergeServiceFiles()
} }
@@ -42,6 +42,8 @@ public class DhApiConfig implements IDhApiConfig
@Override @Override
public IDhApiMultiThreadingConfig multiThreading() { return DhApiMultiThreadingConfig.INSTANCE; } public IDhApiMultiThreadingConfig multiThreading() { return DhApiMultiThreadingConfig.INSTANCE; }
@Override @Override
public IDhApiGpuBuffersConfig gpuBuffers() { return DhApiGpuBuffersConfig.INSTANCE; }
@Override
public IDhApiDebuggingConfig debugging() { return DhApiDebuggingConfig.INSTANCE; } public IDhApiDebuggingConfig debugging() { return DhApiDebuggingConfig.INSTANCE; }
} }
@@ -35,30 +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.Ssao.enabled); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Ssao.enabled); }
@Override @Override
public IDhApiConfigValue<Integer> sampleCount() public IDhApiConfigValue<Integer> sampleCount()
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.Graphics.Ssao.sampleCount); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Ssao.sampleCount); }
@Override @Override
public IDhApiConfigValue<Double> radius() public IDhApiConfigValue<Double> radius()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Ssao.radius); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Ssao.radius); }
@Override @Override
public IDhApiConfigValue<Double> strength() public IDhApiConfigValue<Double> strength()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Ssao.strength); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Ssao.strength); }
@Override @Override
public IDhApiConfigValue<Double> bias() public IDhApiConfigValue<Double> bias()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Ssao.bias); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Ssao.bias); }
@Override @Override
public IDhApiConfigValue<Double> minLight() public IDhApiConfigValue<Double> minLight()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Ssao.minLight); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Ssao.minLight); }
@Override @Override
public IDhApiConfigValue<Integer> blurRadius() public IDhApiConfigValue<Integer> blurRadius()
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.Graphics.Ssao.blurRadius); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Ssao.blurRadius); }
} }
@@ -34,18 +34,18 @@ public class DhApiDebuggingConfig implements IDhApiDebuggingConfig
public IDhApiConfigValue<EDhApiDebugRendering> debugRendering() public IDhApiConfigValue<EDhApiDebugRendering> debugRendering()
{ return new DhApiConfigValue<EDhApiDebugRendering, EDhApiDebugRendering>(Config.Client.Advanced.Debugging.debugRendering); } { return new DhApiConfigValue<>(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<>(Config.Client.Advanced.Debugging.enableDebugKeybindings); }
public IDhApiConfigValue<Boolean> renderWireframe() public IDhApiConfigValue<Boolean> renderWireframe()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Debugging.renderWireframe); } { return new DhApiConfigValue<>(Config.Client.Advanced.Debugging.renderWireframe); }
public IDhApiConfigValue<Boolean> lodOnlyMode() public IDhApiConfigValue<Boolean> lodOnlyMode()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Debugging.lodOnlyMode); } { return new DhApiConfigValue<>(Config.Client.Advanced.Debugging.lodOnlyMode); }
public IDhApiConfigValue<Boolean> debugWireframeRendering() public IDhApiConfigValue<Boolean> debugWireframeRendering()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Debugging.DebugWireframe.enableRendering); } { return new DhApiConfigValue<>(Config.Client.Advanced.Debugging.DebugWireframe.enableRendering); }
} }
@@ -35,26 +35,26 @@ public class DhApiFarFogConfig implements IDhApiFarFogConfig
@Override @Override
public IDhApiConfigValue<Double> farFogStartDistance() public IDhApiConfigValue<Double> farFogStartDistance()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.farFogStart); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.farFogStart); }
@Override @Override
public IDhApiConfigValue<Double> farFogEndDistance() public IDhApiConfigValue<Double> farFogEndDistance()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.farFogEnd); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.farFogEnd); }
@Override @Override
public IDhApiConfigValue<Double> farFogMinThickness() public IDhApiConfigValue<Double> farFogMinThickness()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.farFogMin); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.farFogMin); }
@Override @Override
public IDhApiConfigValue<Double> farFogMaxThickness() public IDhApiConfigValue<Double> farFogMaxThickness()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.farFogMax); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.farFogMax); }
@Override @Override
public IDhApiConfigValue<EDhApiFogFalloff> farFogFalloff() public IDhApiConfigValue<EDhApiFogFalloff> farFogFalloff()
{ return new DhApiConfigValue<EDhApiFogFalloff, EDhApiFogFalloff>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.farFogFalloff); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.farFogFalloff); }
@Override @Override
public IDhApiConfigValue<Double> farFogDensity() public IDhApiConfigValue<Double> farFogDensity()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.farFogDensity); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.farFogDensity); }
} }
@@ -35,12 +35,12 @@ public class DhApiGenericRenderingConfig implements IDhApiGenericRenderingConfig
@Override @Override
public IDhApiConfigValue<Boolean> renderingEnabled() public IDhApiConfigValue<Boolean> renderingEnabled()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.GenericRendering.enableRendering); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.GenericRendering.enableRendering); }
@Override @Override
public IDhApiConfigValue<Boolean> beaconRenderingEnabled() public IDhApiConfigValue<Boolean> beaconRenderingEnabled()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.GenericRendering.enableBeaconRendering); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.GenericRendering.enableBeaconRendering); }
@Override @Override
public IDhApiConfigValue<Boolean> cloudRenderingEnabled() public IDhApiConfigValue<Boolean> cloudRenderingEnabled()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.GenericRendering.enableCloudRendering); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.GenericRendering.enableCloudRendering); }
} }
@@ -0,0 +1,42 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.api.external.methods.config.client;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiGpuBuffersConfig;
import com.seibel.distanthorizons.api.objects.config.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
public class DhApiGpuBuffersConfig implements IDhApiGpuBuffersConfig
{
public static DhApiGpuBuffersConfig INSTANCE = new DhApiGpuBuffersConfig();
private DhApiGpuBuffersConfig() { }
public IDhApiConfigValue<EDhApiGpuUploadMethod> gpuUploadMethod()
{ return new DhApiConfigValue<>(Config.Client.Advanced.GpuBuffers.gpuUploadMethod); }
public IDhApiConfigValue<Integer> gpuUploadPerMegabyteInMilliseconds()
{ return new DhApiConfigValue<>(Config.Client.Advanced.GpuBuffers.gpuUploadPerMegabyteInMilliseconds); }
}
@@ -56,15 +56,15 @@ public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
@Override @Override
public IDhApiConfigValue<Integer> chunkRenderDistance() public IDhApiConfigValue<Integer> chunkRenderDistance()
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistanceRadius); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistanceRadius); }
@Override @Override
public IDhApiConfigValue<Boolean> renderingEnabled() public IDhApiConfigValue<Boolean> renderingEnabled()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.quickEnableRendering); } { return new DhApiConfigValue<>(Config.Client.quickEnableRendering); }
@Override @Override
public IDhApiConfigValue<EDhApiRendererMode> renderingMode() public IDhApiConfigValue<EDhApiRendererMode> renderingMode()
{ return new DhApiConfigValue<EDhApiRendererMode, EDhApiRendererMode>(Config.Client.Advanced.Debugging.rendererMode); } { return new DhApiConfigValue<>(Config.Client.Advanced.Debugging.rendererMode); }
@@ -74,27 +74,27 @@ public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
@Override @Override
public IDhApiConfigValue<EDhApiMaxHorizontalResolution> maxHorizontalResolution() public IDhApiConfigValue<EDhApiMaxHorizontalResolution> maxHorizontalResolution()
{ return new DhApiConfigValue<EDhApiMaxHorizontalResolution, EDhApiMaxHorizontalResolution>(Config.Client.Advanced.Graphics.Quality.maxHorizontalResolution); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Quality.maxHorizontalResolution); }
@Override @Override
public IDhApiConfigValue<EDhApiVerticalQuality> verticalQuality() public IDhApiConfigValue<EDhApiVerticalQuality> verticalQuality()
{ return new DhApiConfigValue<EDhApiVerticalQuality, EDhApiVerticalQuality>(Config.Client.Advanced.Graphics.Quality.verticalQuality); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Quality.verticalQuality); }
@Override @Override
public IDhApiConfigValue<EDhApiHorizontalQuality> horizontalQuality() public IDhApiConfigValue<EDhApiHorizontalQuality> horizontalQuality()
{ return new DhApiConfigValue<EDhApiHorizontalQuality, EDhApiHorizontalQuality>(Config.Client.Advanced.Graphics.Quality.horizontalQuality); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Quality.horizontalQuality); }
@Override @Override
public IDhApiConfigValue<EDhApiTransparency> transparency() public IDhApiConfigValue<EDhApiTransparency> transparency()
{ return new DhApiConfigValue<EDhApiTransparency, EDhApiTransparency>(Config.Client.Advanced.Graphics.Quality.transparency); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Quality.transparency); }
@Override @Override
public IDhApiConfigValue<EDhApiBlocksToAvoid> blocksToAvoid() public IDhApiConfigValue<EDhApiBlocksToAvoid> blocksToAvoid()
{ return new DhApiConfigValue<EDhApiBlocksToAvoid, EDhApiBlocksToAvoid>(Config.Client.Advanced.Graphics.Quality.blocksToIgnore); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Quality.blocksToIgnore); }
@Override @Override
public IDhApiConfigValue<Boolean> tintWithAvoidedBlocks() public IDhApiConfigValue<Boolean> tintWithAvoidedBlocks()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.Quality.tintWithAvoidedBlocks); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Quality.tintWithAvoidedBlocks); }
// TODO re-implement // TODO re-implement
// @Override // @Override
@@ -109,47 +109,47 @@ public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
@Override @Override
public IDhApiConfigValue<Double> overdrawPreventionRadius() public IDhApiConfigValue<Double> overdrawPreventionRadius()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.AdvancedGraphics.overdrawPrevention); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.AdvancedGraphics.overdrawPrevention); }
@Override @Override
public IDhApiConfigValue<Double> brightnessMultiplier() public IDhApiConfigValue<Double> brightnessMultiplier()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.AdvancedGraphics.brightnessMultiplier); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.AdvancedGraphics.brightnessMultiplier); }
@Override @Override
public IDhApiConfigValue<Double> saturationMultiplier() public IDhApiConfigValue<Double> saturationMultiplier()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.AdvancedGraphics.saturationMultiplier); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.AdvancedGraphics.saturationMultiplier); }
@Override @Override
public IDhApiConfigValue<Boolean> caveCullingEnabled() public IDhApiConfigValue<Boolean> caveCullingEnabled()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.AdvancedGraphics.enableCaveCulling); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.AdvancedGraphics.enableCaveCulling); }
@Override @Override
public IDhApiConfigValue<Integer> caveCullingHeight() public IDhApiConfigValue<Integer> caveCullingHeight()
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.Graphics.AdvancedGraphics.caveCullingHeight); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.AdvancedGraphics.caveCullingHeight); }
@Override @Override
public IDhApiConfigValue<Integer> earthCurvatureRatio() public IDhApiConfigValue<Integer> earthCurvatureRatio()
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.Graphics.AdvancedGraphics.earthCurveRatio); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.AdvancedGraphics.earthCurveRatio); }
@Override @Override
public IDhApiConfigValue<Boolean> lodOnlyMode() public IDhApiConfigValue<Boolean> lodOnlyMode()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Debugging.lodOnlyMode); } { return new DhApiConfigValue<>(Config.Client.Advanced.Debugging.lodOnlyMode); }
@Override @Override
public IDhApiConfigValue<Double> lodBias() public IDhApiConfigValue<Double> lodBias()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.AdvancedGraphics.lodBias); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.AdvancedGraphics.lodBias); }
@Override @Override
public IDhApiConfigValue<EDhApiLodShading> lodShading() public IDhApiConfigValue<EDhApiLodShading> lodShading()
{ return new DhApiConfigValue<EDhApiLodShading, EDhApiLodShading>(Config.Client.Advanced.Graphics.AdvancedGraphics.lodShading); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.AdvancedGraphics.lodShading); }
@Override @Override
public IDhApiConfigValue<Boolean> disableFrustumCulling() public IDhApiConfigValue<Boolean> disableFrustumCulling()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.AdvancedGraphics.disableFrustumCulling); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.AdvancedGraphics.disableFrustumCulling); }
@Override @Override
public IDhApiConfigValue<Boolean> disableShadowFrustumCulling() public IDhApiConfigValue<Boolean> disableShadowFrustumCulling()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.AdvancedGraphics.disableShadowPassFrustumCulling); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.AdvancedGraphics.disableShadowPassFrustumCulling); }
@@ -37,38 +37,38 @@ public class DhApiHeightFogConfig implements IDhApiHeightFogConfig
@Override @Override
public IDhApiConfigValue<EDhApiHeightFogMixMode> heightFogMixMode() public IDhApiConfigValue<EDhApiHeightFogMixMode> heightFogMixMode()
{ return new DhApiConfigValue<EDhApiHeightFogMixMode, EDhApiHeightFogMixMode>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogMixMode); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogMixMode); }
@Override @Override
public IDhApiConfigValue<EDhApiHeightFogMode> heightFogMode() public IDhApiConfigValue<EDhApiHeightFogMode> heightFogMode()
{ return new DhApiConfigValue<EDhApiHeightFogMode, EDhApiHeightFogMode>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogMode); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogMode); }
@Override @Override
public IDhApiConfigValue<Double> heightFogBaseHeight() public IDhApiConfigValue<Double> heightFogBaseHeight()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogBaseHeight); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogBaseHeight); }
@Override @Override
public IDhApiConfigValue<Double> heightFogStartingHeightPercent() public IDhApiConfigValue<Double> heightFogStartingHeightPercent()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogStart); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogStart); }
@Override @Override
public IDhApiConfigValue<Double> heightFogEndingHeightPercent() public IDhApiConfigValue<Double> heightFogEndingHeightPercent()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogEnd); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogEnd); }
@Override @Override
public IDhApiConfigValue<Double> heightFogMinThickness() public IDhApiConfigValue<Double> heightFogMinThickness()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogMin); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogMin); }
@Override @Override
public IDhApiConfigValue<Double> heightFogMaxThickness() public IDhApiConfigValue<Double> heightFogMaxThickness()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogMax); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogMax); }
@Override @Override
public IDhApiConfigValue<EDhApiFogFalloff> heightFogFalloff() public IDhApiConfigValue<EDhApiFogFalloff> heightFogFalloff()
{ return new DhApiConfigValue<EDhApiFogFalloff, EDhApiFogFalloff>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogFalloff); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogFalloff); }
@Override @Override
public IDhApiConfigValue<Double> heightFogDensity() public IDhApiConfigValue<Double> heightFogDensity()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogDensity); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogDensity); }
} }
@@ -34,14 +34,14 @@ public class DhApiMultiThreadingConfig implements IDhApiMultiThreadingConfig
@Override @Override
public IDhApiConfigValue<Integer> worldGeneratorThreads() public IDhApiConfigValue<Integer> worldGeneratorThreads()
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads); } { return new DhApiConfigValue<>(Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads); }
@Override @Override
public IDhApiConfigValue<Integer> fileHandlerThreads() public IDhApiConfigValue<Integer> fileHandlerThreads()
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.MultiThreading.numberOfFileHandlerThreads); } { return new DhApiConfigValue<>(Config.Client.Advanced.MultiThreading.numberOfFileHandlerThreads); }
@Override @Override
public IDhApiConfigValue<Integer> lodBuilderThreads() public IDhApiConfigValue<Integer> lodBuilderThreads()
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads); } { return new DhApiConfigValue<>(Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads); }
} }
@@ -34,9 +34,9 @@ public class DhApiMultiplayerConfig implements IDhApiMultiplayerConfig
public IDhApiConfigValue<EDhApiServerFolderNameMode> folderSavingMode() public IDhApiConfigValue<EDhApiServerFolderNameMode> folderSavingMode()
{ return new DhApiConfigValue<EDhApiServerFolderNameMode, EDhApiServerFolderNameMode>(Config.Client.Advanced.Multiplayer.serverFolderNameMode); } { return new DhApiConfigValue<>(Config.Client.Advanced.Multiplayer.serverFolderNameMode); }
public IDhApiConfigValue<Double> multiverseSimilarityRequirement() public IDhApiConfigValue<Double> multiverseSimilarityRequirement()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Multiplayer.multiverseSimilarityRequiredPercent); } { return new DhApiConfigValue<>(Config.Client.Advanced.Multiplayer.multiverseSimilarityRequiredPercent); }
} }
@@ -34,18 +34,18 @@ public class DhApiNoiseTextureConfig implements IDhApiNoiseTextureConfig
@Override @Override
public IDhApiConfigValue<Boolean> noiseEnabled() public IDhApiConfigValue<Boolean> noiseEnabled()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.NoiseTextureSettings.noiseEnabled); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.NoiseTextureSettings.noiseEnabled); }
@Override @Override
public IDhApiConfigValue<Integer> noiseSteps() public IDhApiConfigValue<Integer> noiseSteps()
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.Graphics.NoiseTextureSettings.noiseSteps); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.NoiseTextureSettings.noiseSteps); }
@Override @Override
public IDhApiConfigValue<Double> noiseIntensity() public IDhApiConfigValue<Double> noiseIntensity()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.NoiseTextureSettings.noiseIntensity); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.NoiseTextureSettings.noiseIntensity); }
@Override @Override
public IDhApiConfigValue<Integer> noiseDropoff() public IDhApiConfigValue<Integer> noiseDropoff()
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.Graphics.NoiseTextureSettings.noiseDropoff); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.NoiseTextureSettings.noiseDropoff); }
} }
@@ -187,22 +187,20 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
return DhApiResult.createFail("Unable to get terrain data before the world has loaded."); return DhApiResult.createFail("Unable to get terrain data before the world has loaded.");
} }
if (!(levelWrapper instanceof ILevelWrapper)) if (!(levelWrapper instanceof ILevelWrapper coreLevelWrapper))
{ {
// custom level wrappers aren't supported, // custom level wrappers aren't supported,
// the API user must get a level wrapper from our code somewhere // the API user must get a level wrapper from our code somewhere
return DhApiResult.createFail("Unsupported [" + IDhApiLevelWrapper.class.getSimpleName() + "] implementation, only the core class [" + IDhLevel.class.getSimpleName() + "] is a valid parameter."); return DhApiResult.createFail("Unsupported [" + IDhApiLevelWrapper.class.getSimpleName() + "] implementation, only the core class [" + IDhLevel.class.getSimpleName() + "] is a valid parameter.");
} }
ILevelWrapper coreLevelWrapper = (ILevelWrapper) levelWrapper;
if (!(apiDataCache instanceof DhApiTerrainDataCache)) if (!(apiDataCache instanceof DhApiTerrainDataCache dataCache))
{ {
// custom level wrappers aren't supported, // custom level wrappers aren't supported,
// the API user must get a level wrapper from our code somewhere // the API user must get a level wrapper from our code somewhere
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.");
} }
DhApiTerrainDataCache dataCache = (DhApiTerrainDataCache) apiDataCache;
IDhLevel level = currentWorld.getLevel(coreLevelWrapper); IDhLevel level = currentWorld.getLevel(coreLevelWrapper);
@@ -326,10 +324,9 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
int height = FullDataPointUtil.getHeight(dataPoint); int height = FullDataPointUtil.getHeight(dataPoint);
int topY = bottomY + height; int topY = bottomY + height;
return DhApiTerrainDataPoint.create( return new DhApiTerrainDataPoint(detailLevel,
detailLevel,
FullDataPointUtil.getBlockLight(dataPoint), FullDataPointUtil.getSkyLight(dataPoint), FullDataPointUtil.getBlockLight(dataPoint), FullDataPointUtil.getSkyLight(dataPoint),
bottomY, topY, topY, bottomY,
blockState, biomeWrapper); blockState, biomeWrapper);
} }
@@ -497,7 +497,7 @@ public class ClientApi
{ {
// logging // // logging //
this.sendQueuedChatMessages(); this.sendChatMessagesNow();
IProfilerWrapper profiler = MC.getProfiler(); IProfilerWrapper profiler = MC.getProfiler();
profiler.pop(); // get out of "terrain" profiler.pop(); // get out of "terrain"
@@ -552,17 +552,8 @@ public class ClientApi
} }
IDhClientLevel level = dhClientWorld.getOrLoadClientLevel(levelWrapper); IDhClientLevel level = dhClientWorld.getOrLoadClientLevel(levelWrapper);
if (this.rendererDisabledBecauseOfExceptions) if (this.rendererDisabledBecauseOfExceptions)
{ {
// re-enable rendering if the user toggles DH rendering
if (!Config.Client.quickEnableRendering.get())
{
LOGGER.info("DH Renderer re-enabled after exception. Some rendering issues may occur. Please reboot Minecraft if you see any rendering issues.");
this.rendererDisabledBecauseOfExceptions = false;
Config.Client.quickEnableRendering.set(true);
}
return; return;
} }
@@ -613,9 +604,9 @@ public class ClientApi
LOGGER.error("Unexpected Renderer error in render pass [" + renderPass + "]. Error: " + e.getMessage(), e); LOGGER.error("Unexpected Renderer error in render pass [" + renderPass + "]. Error: " + e.getMessage(), e);
MC.sendChatMessage("\u00A74\u00A7l\u00A7uERROR: Distant Horizons renderer has encountered an exception!"); MC.sendChatMessage("\u00A74\u00A7l\u00A7uERROR: Distant Horizons renderer has encountered an exception!");
MC.sendChatMessage("\u00A74Renderer disabled to try preventing GL state corruption."); MC.sendChatMessage("\u00A74Renderer is now disabled to prevent further issues.");
MC.sendChatMessage("\u00A74Toggle DH rendering via the config UI to re-activate DH rendering."); MC.sendChatMessage("\u00A74Please restart your game to re-enable Distant Horizons' LOD rendering.");
MC.sendChatMessage("\u00A74Error: " + e); MC.sendChatMessage("\u00A74Exception detail: " + e);
} }
finally finally
{ {
@@ -669,7 +660,7 @@ public class ClientApi
} }
} }
private void sendQueuedChatMessages() private void sendChatMessagesNow()
{ {
// dev build // dev build
if (ModInfo.IS_DEV_BUILD && !this.configOverrideReminderPrinted && MC.playerExists()) if (ModInfo.IS_DEV_BUILD && !this.configOverrideReminderPrinted && MC.playerExists())
@@ -677,12 +668,10 @@ public class ClientApi
this.configOverrideReminderPrinted = true; this.configOverrideReminderPrinted = true;
// remind the user that this is a development build // remind the user that this is a development build
String message = MC.sendChatMessage("\u00A72" + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + "\u00A7r");
// green text MC.sendChatMessage("Issues may occur with this version.");
"\u00A72" + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + "\u00A7r\n" + MC.sendChatMessage("Here be dragons!");
"Issues may occur with this version.\n" + MC.sendChatMessage("");
"Here be dragons!\n";
MC.sendChatMessage(message);
} }
// memory // memory
@@ -697,13 +686,11 @@ public class ClientApi
long maxMemoryInBytes = Runtime.getRuntime().maxMemory(); long maxMemoryInBytes = Runtime.getRuntime().maxMemory();
if (maxMemoryInBytes < minimumRecommendedMemoryInBytes) if (maxMemoryInBytes < minimumRecommendedMemoryInBytes)
{ {
String message = MC.sendChatMessage("\u00A76" + "Distant Horizons: Low memory detected." + "\u00A7r");
// orange text MC.sendChatMessage("Stuttering or low FPS may occur.");
"\u00A76" + "Distant Horizons: Low memory detected." + "\u00A7r \n" + MC.sendChatMessage("Please increase Minecraft's available memory to 4 gigabytes.");
"Stuttering or low FPS may occur. \n" + MC.sendChatMessage("This warning can be disabled in DH's config under Advanced -> Logging.");
"Please increase Minecraft's available memory to 4 gigabytes. \n" + MC.sendChatMessage("");
"This warning can be disabled in DH's config under Advanced -> Logging. \n";
MC.sendChatMessage(message);
} }
} }
@@ -26,11 +26,11 @@ import com.seibel.distanthorizons.core.generation.DhLightingEngine;
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.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
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.TimerUtil; import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.objects.Pair; import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
@@ -43,7 +43,9 @@ import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
/** Contains code and variables used by both {@link ClientApi} and {@link ServerApi} */ /** Contains code and variables used by both {@link ClientApi} and {@link ServerApi} */
public class SharedApi public class SharedApi
@@ -60,7 +62,6 @@ public class SharedApi
private static final Timer CHUNK_UPDATE_TIMER = TimerUtil.CreateTimer("ChunkUpdateTimer"); private static final Timer CHUNK_UPDATE_TIMER = TimerUtil.CreateTimer("ChunkUpdateTimer");
private static AbstractDhWorld currentWorld; private static AbstractDhWorld currentWorld;
private static int lastWorldGenTickDelta = 0; private static int lastWorldGenTickDelta = 0;
private static long lastOverloadedLogMessageMsTime = 0; private static long lastOverloadedLogMessageMsTime = 0;
@@ -138,13 +139,23 @@ public class SharedApi
public static boolean isChunkAtBlockPosAlreadyUpdating(int blockPosX, int blockPosZ) public static boolean isChunkAtBlockPosAlreadyUpdating(int blockPosX, int blockPosZ)
{ return UPDATING_CHUNK_POS_SET.contains(new DhChunkPos(new DhBlockPos2D(blockPosX, blockPosZ))); } { return UPDATING_CHUNK_POS_SET.contains(new DhChunkPos(new DhBlockPos2D(blockPosX, blockPosZ))); }
public static boolean isChunkAtChunkPosAlreadyUpdating(int chunkPosX, int chunkPosZ)
{ return UPDATING_CHUNK_POS_SET.contains(new DhChunkPos(chunkPosX, chunkPosZ)); }
/** handles both block place and break events */ /** handles both block place and break events */
public void chunkBlockChangedEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, true); } public void chunkBlockChangedEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, true); }
public void chunkLoadEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, false); } public void chunkLoadEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, false); }
public void chunkUnloadEvent(IChunkWrapper chunk, ILevelWrapper level)
{
// temporarily disabled since this was originally incorrectly designated as "chunkSaveEvent"
// but didn't actually fire on chunk save
// and generally this is unnecessary and drastically reduces LOD building performance
// when traveling around the world
if (false)
{
this.applyChunkUpdate(chunk, level, false);
}
}
public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper level, boolean updateNeighborChunks) public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper level, boolean updateNeighborChunks)
{ {
@@ -161,11 +172,10 @@ public class SharedApi
AbstractDhWorld dhWorld = SharedApi.getAbstractDhWorld(); AbstractDhWorld dhWorld = SharedApi.getAbstractDhWorld();
if (dhWorld == null) if (dhWorld == null)
{ {
if (level instanceof IClientLevelWrapper) if (level instanceof IClientLevelWrapper clientLevel)
{ {
// 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) level;
ClientApi.INSTANCE.waitingChunkByClientLevelAndPos.replace(new Pair<>(clientLevel, chunkWrapper.getChunkPos()), chunkWrapper); ClientApi.INSTANCE.waitingChunkByClientLevelAndPos.replace(new Pair<>(clientLevel, chunkWrapper.getChunkPos()), chunkWrapper);
} }
@@ -176,10 +186,9 @@ public class SharedApi
IDhLevel dhLevel = dhWorld.getLevel(level); IDhLevel dhLevel = dhWorld.getLevel(level);
if (dhLevel == null) if (dhLevel == null)
{ {
if (level instanceof IClientLevelWrapper) if (level instanceof IClientLevelWrapper clientLevel)
{ {
// the client level isn't loaded yet // the client level isn't loaded yet
IClientLevelWrapper clientLevel = (IClientLevelWrapper) level;
ClientApi.INSTANCE.waitingChunkByClientLevelAndPos.replace(new Pair<>(clientLevel, chunkWrapper.getChunkPos()), chunkWrapper); ClientApi.INSTANCE.waitingChunkByClientLevelAndPos.replace(new Pair<>(clientLevel, chunkWrapper.getChunkPos()), chunkWrapper);
} }
@@ -254,7 +263,7 @@ public class SharedApi
else else
{ {
// neighboring chunk // neighboring chunk
DhChunkPos neighbourPos = new DhChunkPos(chunkWrapper.getChunkPos().getX() + xOffset, chunkWrapper.getChunkPos().getZ() + zOffset); DhChunkPos neighbourPos = new DhChunkPos(chunkWrapper.getChunkPos().x + xOffset, chunkWrapper.getChunkPos().z + zOffset);
IChunkWrapper neighbourChunk = dhLevel.getLevelWrapper().tryGetChunk(neighbourPos); IChunkWrapper neighbourChunk = dhLevel.getLevelWrapper().tryGetChunk(neighbourPos);
if (neighbourChunk != null) if (neighbourChunk != null)
{ {
@@ -271,43 +280,36 @@ public class SharedApi
} }
} }
} }
/** returning a {@link CompletableFuture} isn't necessary, but allows Intellij to properly show the full stack trace when debugging. */ private static void bakeChunkLightingAndSendToLevelAsync(IChunkWrapper chunkWrapper, @Nullable ArrayList<IChunkWrapper> neighbourChunkList, IDhLevel dhLevel)
@SuppressWarnings("UnusedReturnValue")
private static CompletableFuture<Void> bakeChunkLightingAndSendToLevelAsync(IChunkWrapper chunkWrapper, @Nullable ArrayList<IChunkWrapper> neighbourChunkList, IDhLevel dhLevel)
{ {
// lighting the chunk needs to be done on a separate thread to prevent lagging any of the event threads // lighting the chunk needs to be done on a separate thread to prevent lagging any of the event threads
ThreadPoolExecutor executor = ThreadPoolUtil.getChunkToLodBuilderExecutor(); ThreadPoolExecutor executor = ThreadPoolUtil.getLightPopulatorExecutor();
if (executor == null) if (executor == null)
{ {
return CompletableFuture.completedFuture(null); return;
} }
try try
{ {
return CompletableFuture.runAsync(() -> executor.execute(() ->
{ {
//LOGGER.trace(chunkWrapper.getChunkPos() + " " + executor.getActiveCount() + " / " + executor.getQueue().size() + " - " + executor.getCompletedTaskCount()); //LOGGER.trace(chunkWrapper.getChunkPos() + " " + executor.getActiveCount() + " / " + executor.getQueue().size() + " - " + executor.getCompletedTaskCount());
try try
{ {
boolean checkChunkHash = !Config.Client.Advanced.LodBuilding.disableUnchangedChunkCheck.get();
// check if this chunk has been converted into an LOD already // check if this chunk has been converted into an LOD already
int oldChunkHash = dhLevel.getChunkHash(chunkWrapper.getChunkPos()); // shouldn't happen on the render thread since it may take a few moments to run 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(); int newChunkHash = chunkWrapper.getBlockBiomeHashCode();
if (checkChunkHash) if (oldChunkHash == newChunkHash)
{ {
if (oldChunkHash == newChunkHash) // if the chunk hashes are the same then we don't need to bother with lighting the chunk
{ // or creating/updating the LODs
// if the chunk hashes are the same then we don't need to bother with lighting the chunk //LOGGER.info("skipping: "+chunkWrapper.getChunkPos()+" "+newChunkHash);
// or creating/updating the LODs return;
//LOGGER.info("skipping: "+chunkWrapper.getChunkPos()+" "+newChunkHash); }
return; else
} {
else //LOGGER.info("g: "+chunkWrapper.getChunkPos()+" "+newChunkHash);
{
//LOGGER.info("g: "+chunkWrapper.getChunkPos()+" "+newChunkHash);
}
} }
@@ -324,44 +326,36 @@ public class SharedApi
} }
// chunk light baking is disabled since profiling revealed it used // Save or populate the chunk wrapper's lighting
// roughly the same amount of time as generating the lighting ourselves and // this is done so we don't have to worry about MC unloading the lighting data for this chunk
// was much more likely to have issues with corrupt (all black or all bright) chunks boolean onlyUseDhLighting = Config.Client.Advanced.LodBuilding.onlyUseDhLightingEngine.get();
boolean tryUsingMcLightingEngine = false; if (!onlyUseDhLighting && chunkWrapper.isLightCorrect())
if (tryUsingMcLightingEngine)
{ {
// Save or populate the chunk wrapper's lighting try
// this is done so we don't have to worry about MC unloading the lighting data for this chunk
boolean chunkLightPopulated = false;
boolean onlyUseDhLighting = Config.Client.Advanced.LodBuilding.onlyUseDhLightingEngine.get();
if (!onlyUseDhLighting && chunkWrapper.isLightCorrect())
{ {
// If MC's lighting engine isn't thread safe this may cause the server thread to lag // If MC's lighting engine isn't thread safe this may cause the server thread to lag
chunkLightPopulated = chunkWrapper.bakeDhLightingUsingMcLightingEngine(dhLevel.getLevelWrapper()); chunkWrapper.bakeDhLightingUsingMcLightingEngine();
if (!chunkLightPopulated)
{
// clear any existing data to prevent partial or corrupt lighting
// when re-generating it
chunkWrapper.clearDhBlockLighting();
chunkWrapper.clearDhSkyLighting();
}
} }
catch (IllegalStateException e)
// something went wrong during the baking process so we have to generate the lighting ourselves
if (!chunkLightPopulated)
{ {
DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT); LOGGER.warn("Chunk light baking error: " + e.getMessage(), e);
} }
} }
else else
{ {
DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT); // generate the chunk's lighting, using neighboring chunks if present
DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? 15 : 0);
} }
dhLevel.updateBeaconBeamsForChunk(chunkWrapper, nearbyChunkList); // get this chunk's active beacons
dhLevel.updateChunkAsync(chunkWrapper, newChunkHash); List<BeaconBeamDTO> beaconBeamList = chunkWrapper.getAllActiveBeacons(nearbyChunkList);
dhLevel.setBeaconBeamsForChunk(chunkWrapper.getChunkPos(), beaconBeamList);
dhLevel.updateChunkAsync(chunkWrapper);
dhLevel.setChunkHash(chunkWrapper.getChunkPos(), newChunkHash);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -386,13 +380,9 @@ public class SharedApi
UPDATING_CHUNK_POS_SET.remove(chunkWrapper.getChunkPos()); UPDATING_CHUNK_POS_SET.remove(chunkWrapper.getChunkPos());
} }
} }
}, executor); });
}
catch (RejectedExecutionException ignore)
{
// the executor was shut down, it should be back up shortly and able to accept new jobs
return CompletableFuture.completedFuture(null);
} }
catch (RejectedExecutionException ignore) { /* the executor was shut down, it should be back up shortly and able to accept new jobs */ }
} }
@@ -54,6 +54,7 @@ import java.util.List;
* Otherwise, you will have issues where only some of the config entries will exist when your listener is created. * Otherwise, you will have issues where only some of the config entries will exist when your listener is created.
* *
* @author coolGi * @author coolGi
* @version 2023-7-16
*/ */
public class Config public class Config
@@ -106,8 +107,6 @@ public class Config
public static ConfigLinkedEntry quickEnableWorldGenerator = new ConfigLinkedEntry(Advanced.WorldGenerator.enableDistantGeneration); public static ConfigLinkedEntry quickEnableWorldGenerator = new ConfigLinkedEntry(Advanced.WorldGenerator.enableDistantGeneration);
public static ConfigLinkedEntry quickLodCloudRendering = new ConfigLinkedEntry(Advanced.Graphics.GenericRendering.enableCloudRendering);
public static ConfigEntry<Boolean> optionsButton = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> optionsButton = new ConfigEntry.Builder<Boolean>()
.set(true) .set(true)
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE) .setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
@@ -127,6 +126,7 @@ public class Config
public static ConfigCategory multiplayer = new ConfigCategory.Builder().set(Multiplayer.class).build(); public static ConfigCategory multiplayer = new ConfigCategory.Builder().set(Multiplayer.class).build();
public static ConfigCategory lodBuilding = new ConfigCategory.Builder().set(LodBuilding.class).build(); public static ConfigCategory lodBuilding = new ConfigCategory.Builder().set(LodBuilding.class).build();
public static ConfigCategory multiThreading = new ConfigCategory.Builder().set(MultiThreading.class).build(); public static ConfigCategory multiThreading = new ConfigCategory.Builder().set(MultiThreading.class).build();
public static ConfigCategory buffers = new ConfigCategory.Builder().set(GpuBuffers.class).build();
public static ConfigCategory autoUpdater = new ConfigCategory.Builder().set(AutoUpdater.class).build(); public static ConfigCategory autoUpdater = new ConfigCategory.Builder().set(AutoUpdater.class).build();
public static ConfigCategory logging = new ConfigCategory.Builder().set(Logging.class).build(); public static ConfigCategory logging = new ConfigCategory.Builder().set(Logging.class).build();
@@ -179,7 +179,6 @@ public class Config
+ "Lowest Quality: " + EDhApiVerticalQuality.HEIGHT_MAP + "\n" + "Lowest Quality: " + EDhApiVerticalQuality.HEIGHT_MAP + "\n"
+ "Highest Quality: " + EDhApiVerticalQuality.EXTREME) + "Highest Quality: " + EDhApiVerticalQuality.EXTREME)
.setPerformance(EConfigEntryPerformance.VERY_HIGH) .setPerformance(EConfigEntryPerformance.VERY_HIGH)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build(); .build();
public static ConfigEntry<EDhApiHorizontalQuality> horizontalQuality = new ConfigEntry.Builder<EDhApiHorizontalQuality>() public static ConfigEntry<EDhApiHorizontalQuality> horizontalQuality = new ConfigEntry.Builder<EDhApiHorizontalQuality>()
@@ -201,7 +200,6 @@ public class Config
+ EDhApiTransparency.DISABLED + ": LODs will be opaque. \n" + EDhApiTransparency.DISABLED + ": LODs will be opaque. \n"
+ "") + "")
.setPerformance(EConfigEntryPerformance.MEDIUM) .setPerformance(EConfigEntryPerformance.MEDIUM)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build(); .build();
public static ConfigEntry<EDhApiBlocksToAvoid> blocksToIgnore = new ConfigEntry.Builder<EDhApiBlocksToAvoid>() public static ConfigEntry<EDhApiBlocksToAvoid> blocksToIgnore = new ConfigEntry.Builder<EDhApiBlocksToAvoid>()
@@ -213,7 +211,6 @@ public class Config
+ EDhApiBlocksToAvoid.NON_COLLIDING + ": Only represent solid blocks in the LODs (tall grass, torches, etc. won't count for a LOD's height) \n" + EDhApiBlocksToAvoid.NON_COLLIDING + ": Only represent solid blocks in the LODs (tall grass, torches, etc. won't count for a LOD's height) \n"
+ "") + "")
.setPerformance(EConfigEntryPerformance.NONE) .setPerformance(EConfigEntryPerformance.NONE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build(); .build();
public static ConfigEntry<Boolean> tintWithAvoidedBlocks = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> tintWithAvoidedBlocks = new ConfigEntry.Builder<Boolean>()
@@ -225,7 +222,6 @@ public class Config
+ "False: skipped blocks will not change color of surface below them. " + "False: skipped blocks will not change color of surface below them. "
+ "") + "")
.setPerformance(EConfigEntryPerformance.NONE) .setPerformance(EConfigEntryPerformance.NONE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build(); .build();
// TODO fixme // TODO fixme
@@ -591,7 +587,6 @@ public class Config
+ "0 = black \n" + "0 = black \n"
+ "1 = normal \n" + "1 = normal \n"
+ "2 = near white") + "2 = near white")
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build(); .build();
public static ConfigEntry<Double> saturationMultiplier = new ConfigEntry.Builder<Double>() // TODO: Make this a float (the ClassicConfigGUI doesnt support floats) public static ConfigEntry<Double> saturationMultiplier = new ConfigEntry.Builder<Double>() // TODO: Make this a float (the ClassicConfigGUI doesnt support floats)
@@ -602,7 +597,6 @@ public class Config
+ "0 = black and white \n" + "0 = black and white \n"
+ "1 = normal \n" + "1 = normal \n"
+ "2 = very saturated") + "2 = very saturated")
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build(); .build();
public static ConfigEntry<Boolean> enableCaveCulling = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> enableCaveCulling = new ConfigEntry.Builder<Boolean>()
@@ -617,15 +611,14 @@ public class Config
+ "Additional Info: Currently this cull all faces \n" + "Additional Info: Currently this cull all faces \n"
+ " with skylight value of 0 in dimensions that \n" + " with skylight value of 0 in dimensions that \n"
+ " does not have a ceiling.") + " does not have a ceiling.")
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build(); .build();
@Deprecated
public static ConfigEntry<Integer> caveCullingHeight = new ConfigEntry.Builder<Integer>() public static ConfigEntry<Integer> caveCullingHeight = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(-4096, 60, 4096) .setMinDefaultMax(-4096, 40, 4096)
.setAppearance(EConfigEntryAppearance.ONLY_IN_API)
.comment("" .comment(""
+ "At what Y value should cave culling start? \n" + "At what Y value should cave culling start?")
+ "Lower this value if you get walls for areas with 0 light.")
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build(); .build();
public static ConfigEntry<Integer> earthCurveRatio = new ConfigEntry.Builder<Integer>() public static ConfigEntry<Integer> earthCurveRatio = new ConfigEntry.Builder<Integer>()
@@ -663,7 +656,6 @@ public class Config
+ EDhApiLodShading.DISABLED + ": All LOD sides will be rendered with the same brightness. \n" + EDhApiLodShading.DISABLED + ": All LOD sides will be rendered with the same brightness. \n"
+ "") + "")
.setPerformance(EConfigEntryPerformance.NONE) .setPerformance(EConfigEntryPerformance.NONE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build(); .build();
public static ConfigEntry<Boolean> disableFrustumCulling = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> disableFrustumCulling = new ConfigEntry.Builder<Boolean>()
@@ -698,18 +690,6 @@ public class Config
+ EDhApiGrassSideRendering.AS_DIRT + ": sides render entirely as dirt. \n" + EDhApiGrassSideRendering.AS_DIRT + ": sides render entirely as dirt. \n"
+ "") + "")
.setPerformance(EConfigEntryPerformance.NONE) .setPerformance(EConfigEntryPerformance.NONE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Boolean> disableBeaconDistanceCulling = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
+ "If true all beacons near the camera won't be drawn to prevent vanilla overdraw. \n"
+ "If false all beacons will be rendered. \n"
+ "\n"
+ "Generally this should be left as false. It's main purpose is for debugging\n"
+ "beacon updating/rendering.\n"
+ "")
.build(); .build();
} }
@@ -793,23 +773,8 @@ public class Config
+ "") + "")
.build(); .build();
public static ConfigEntry<Boolean> disableUnchangedChunkCheck = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
+ "Normally DH will attempt to skip creating LODs for chunks it's already seen\n"
+ "and that haven't changed.\n"
+ "\n"
+ "However sometimes that logic incorrecly prevents LODs from being updated.\n"
+ "Disabling this check may fix issues where LODs aren't updated after\n"
+ "blocks have been changed.\n"
+ "")
.build();
/** Currently we always use the DH lighting engine because there's a high likelyhood of MC returning incorrect lighting otherwise */
@Deprecated
public static ConfigEntry<Boolean> onlyUseDhLightingEngine = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> onlyUseDhLightingEngine = new ConfigEntry.Builder<Boolean>()
.set(false) .set(false)
.setAppearance(EConfigEntryAppearance.ONLY_IN_API)
.comment("" .comment(""
+ "If false LODs will be lit by Minecraft's lighting engine when possible \n" + "If false LODs will be lit by Minecraft's lighting engine when possible \n"
+ "and fall back to the DH lighting engine only when necessary. \n" + "and fall back to the DH lighting engine only when necessary. \n"
@@ -902,8 +867,8 @@ public class Config
static static
{ {
ignoredRenderBlockCsv.addListener(new ConfigChangeListener<String>(Config.Client.Advanced.LodBuilding.ignoredRenderBlockCsv, ignoredRenderBlockCsv.addListener(new ConfigChangeListener<>(Config.Client.Advanced.LodBuilding.ignoredRenderBlockCsv,
(blockCsv) -> (blockCsv) ->
{ {
IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class); IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
if (wrapperFactory != null) if (wrapperFactory != null)
@@ -913,8 +878,8 @@ public class Config
} }
})); }));
ignoredRenderCaveBlockCsv.addListener(new ConfigChangeListener<String>(Config.Client.Advanced.LodBuilding.ignoredRenderCaveBlockCsv, ignoredRenderCaveBlockCsv.addListener(new ConfigChangeListener<>(Config.Client.Advanced.LodBuilding.ignoredRenderCaveBlockCsv,
(blockCsv) -> (blockCsv) ->
{ {
IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class); IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
if (wrapperFactory != null) if (wrapperFactory != null)
@@ -1097,6 +1062,53 @@ public class Config
} }
public static class GpuBuffers
{
public static ConfigEntry<EDhApiGpuUploadMethod> gpuUploadMethod = new ConfigEntry.Builder<EDhApiGpuUploadMethod>()
.set(EDhApiGpuUploadMethod.AUTO)
.comment(""
+ "What method should be used to upload geometry to the GPU? \n"
+ "\n"
+ EDhApiGpuUploadMethod.AUTO + ": Picks the best option based on the GPU you have. \n"
+ "\n"
+ EDhApiGpuUploadMethod.BUFFER_STORAGE + ": Default if OpenGL 4.5 is supported. \n"
+ " Fast rendering, no stuttering. \n"
+ "\n"
+ EDhApiGpuUploadMethod.SUB_DATA + ": Backup option for NVIDIA. \n"
+ " Fast rendering but may stutter when uploading. \n"
+ "\n"
+ EDhApiGpuUploadMethod.BUFFER_MAPPING + ": Slow rendering but won't stutter when uploading. \n"
+ " Generally the best option for integrated GPUs. \n"
+ " Default option for AMD/Intel if OpenGL 4.5 isn't supported. \n"
+ " May end up storing buffers in System memory. \n"
+ " Fast rendering if in GPU memory, slow if in system memory, \n"
+ " but won't stutter when uploading. \n"
+ "\n"
+ EDhApiGpuUploadMethod.DATA + ": Fast rendering but will stutter when uploading. \n"
+ " Backup option for AMD/Intel. \n"
+ " Fast rendering but may stutter when uploading. \n"
+ "\n"
+ "If you don't see any difference when changing these settings, \n"
+ "or the world looks corrupted: restart your game."
+ "")
.build();
public static ConfigEntry<Integer> gpuUploadPerMegabyteInMilliseconds = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(0, 0, 50)
.comment(""
+ "How long should a buffer wait per Megabyte of data uploaded? \n"
+ "Helpful resource for frame times: https://fpstoms.com \n"
+ "\n"
+ "Longer times may reduce stuttering but will make LODs \n"
+ "transition and load slower. Change this to [0] for no timeout. \n"
+ "\n"
+ "NOTE:\n"
+ "Before changing this config, try changing the \"GPU Upload method\" first. \n"
+ "")
.build();
}
public static class AutoUpdater public static class AutoUpdater
{ {
public static ConfigEntry<Boolean> enableAutoUpdater = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> enableAutoUpdater = new ConfigEntry.Builder<Boolean>()
@@ -1128,63 +1140,63 @@ public class Config
// TODO add change all option // TODO add change all option
// TODO default to error chat and info file // TODO default to error chat and info file
public static ConfigEntry<EDhApiLoggerMode> logWorldGenEvent = new ConfigEntry.Builder<EDhApiLoggerMode>() public static ConfigEntry<EDhApiLoggerMode> logWorldGenEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.comment("" .comment(""
+ "If enabled, the mod will log information about the world generation process. \n" + "If enabled, the mod will log information about the world generation process. \n"
+ "This can be useful for debugging.") + "This can be useful for debugging.")
.build(); .build();
public static ConfigEntry<EDhApiLoggerMode> logWorldGenPerformance = new ConfigEntry.Builder<EDhApiLoggerMode>() public static ConfigEntry<EDhApiLoggerMode> logWorldGenPerformance = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.comment("" .comment(""
+ "If enabled, the mod will log performance about the world generation process. \n" + "If enabled, the mod will log performance about the world generation process. \n"
+ "This can be useful for debugging.") + "This can be useful for debugging.")
.build(); .build();
public static ConfigEntry<EDhApiLoggerMode> logWorldGenLoadEvent = new ConfigEntry.Builder<EDhApiLoggerMode>() public static ConfigEntry<EDhApiLoggerMode> logWorldGenLoadEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.comment("" .comment(""
+ "If enabled, the mod will log information about the world generation process. \n" + "If enabled, the mod will log information about the world generation process. \n"
+ "This can be useful for debugging.") + "This can be useful for debugging.")
.build(); .build();
public static ConfigEntry<EDhApiLoggerMode> logLodBuilderEvent = new ConfigEntry.Builder<EDhApiLoggerMode>() public static ConfigEntry<EDhApiLoggerMode> logLodBuilderEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.comment("" .comment(""
+ "If enabled, the mod will log information about the LOD generation process. \n" + "If enabled, the mod will log information about the LOD generation process. \n"
+ "This can be useful for debugging.") + "This can be useful for debugging.")
.build(); .build();
public static ConfigEntry<EDhApiLoggerMode> logRendererBufferEvent = new ConfigEntry.Builder<EDhApiLoggerMode>() public static ConfigEntry<EDhApiLoggerMode> logRendererBufferEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.comment("" .comment(""
+ "If enabled, the mod will log information about the renderer buffer process. \n" + "If enabled, the mod will log information about the renderer buffer process. \n"
+ "This can be useful for debugging.") + "This can be useful for debugging.")
.build(); .build();
public static ConfigEntry<EDhApiLoggerMode> logRendererGLEvent = new ConfigEntry.Builder<EDhApiLoggerMode>() public static ConfigEntry<EDhApiLoggerMode> logRendererGLEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.comment("" .comment(""
+ "If enabled, the mod will log information about the renderer OpenGL process. \n" + "If enabled, the mod will log information about the renderer OpenGL process. \n"
+ "This can be useful for debugging.") + "This can be useful for debugging.")
.build(); .build();
public static ConfigEntry<EDhApiLoggerMode> logFileReadWriteEvent = new ConfigEntry.Builder<EDhApiLoggerMode>() public static ConfigEntry<EDhApiLoggerMode> logFileReadWriteEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.comment("" .comment(""
+ "If enabled, the mod will log information about file read/write operations. \n" + "If enabled, the mod will log information about file read/write operations. \n"
+ "This can be useful for debugging.") + "This can be useful for debugging.")
.build(); .build();
public static ConfigEntry<EDhApiLoggerMode> logFileSubDimEvent = new ConfigEntry.Builder<EDhApiLoggerMode>() public static ConfigEntry<EDhApiLoggerMode> logFileSubDimEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.comment("" .comment(""
+ "If enabled, the mod will log information about file sub-dimension operations. \n" + "If enabled, the mod will log information about file sub-dimension operations. \n"
+ "This can be useful for debugging.") + "This can be useful for debugging.")
.build(); .build();
public static ConfigEntry<EDhApiLoggerMode> logNetworkEvent = new ConfigEntry.Builder<EDhApiLoggerMode>() public static ConfigEntry<EDhApiLoggerMode> logNetworkEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.comment("" .comment(""
+ "If enabled, the mod will log information about network operations. \n" + "If enabled, the mod will log information about network operations. \n"
+ "This can be useful for debugging.") + "This can be useful for debugging.")
@@ -1205,13 +1217,6 @@ public class Config
+ "giving some basic information about how DH will function.") + "giving some basic information about how DH will function.")
.build(); .build();
public static ConfigEntry<Boolean> showModCompatibilityWarningsOnStartup = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "If enabled, a chat message will be displayed when a potentially problematic \n"
+ "mod is installed alongside DH.")
.build();
} }
public static class Debugging public static class Debugging
@@ -1298,38 +1303,22 @@ public class Config
public static ConfigEntry<Boolean> columnBuilderDebugEnable = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> columnBuilderDebugEnable = new ConfigEntry.Builder<Boolean>()
.set(false) .set(false)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(ReloadLodsConfigEventHandler.INSTANCE) .addListener(DebugColumnConfigEventHandler.INSTANCE)
.build(); .build();
public static ConfigEntry<Integer> columnBuilderDebugDetailLevel = new ConfigEntry.Builder<Integer>() public static ConfigEntry<Integer> columnBuilderDebugDetailLevel = new ConfigEntry.Builder<Integer>()
.set((int) DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL) .set((int) DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(ReloadLodsConfigEventHandler.INSTANCE) .addListener(DebugColumnConfigEventHandler.INSTANCE)
.build(); .build();
public static ConfigEntry<Integer> columnBuilderDebugXPos = new ConfigEntry.Builder<Integer>() public static ConfigEntry<Integer> columnBuilderDebugXPos = new ConfigEntry.Builder<Integer>()
.set(0) .set(0)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(ReloadLodsConfigEventHandler.INSTANCE) .addListener(DebugColumnConfigEventHandler.INSTANCE)
.build(); .build();
public static ConfigEntry<Integer> columnBuilderDebugZPos = new ConfigEntry.Builder<Integer>() public static ConfigEntry<Integer> columnBuilderDebugZPos = new ConfigEntry.Builder<Integer>()
.set(0) .set(0)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(ReloadLodsConfigEventHandler.INSTANCE) .addListener(DebugColumnConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Integer> columnBuilderDebugXRow = new ConfigEntry.Builder<Integer>()
.set(-1)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Integer> columnBuilderDebugZRow = new ConfigEntry.Builder<Integer>()
.set(-1)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Integer> columnBuilderDebugColumnIndex = new ConfigEntry.Builder<Integer>()
.set(-1)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build(); .build();
@@ -1508,27 +1497,25 @@ public class Config
.build(); .build();
public static ConfigEntry<List<String>> listTest = new ConfigEntry.Builder<List<String>>() public static ConfigEntry<List<String>> listTest = new ConfigEntry.Builder<List<String>>()
.set(new ArrayList<String>(Arrays.asList("option 1", "option 2", "option 3"))) .set(new ArrayList<>(Arrays.asList("option 1", "option 2", "option 3")))
.build(); .build();
public static ConfigEntry<Map<String, String>> mapTest = new ConfigEntry.Builder<Map<String, String>>() public static ConfigEntry<Map<String, String>> mapTest = new ConfigEntry.Builder<Map<String, String>>()
.set(new HashMap<String, String>()) .set(new HashMap<>())
.build(); .build();
public static ConfigUIButton uiButtonTest = new ConfigUIButton(() -> public static ConfigUIButton uiButtonTest = new ConfigUIButton(() ->
{ new Thread(() ->
new Thread(() ->
{
if (!GraphicsEnvironment.isHeadless())
{ {
JOptionPane.showMessageDialog(null, "Button pressed!", "UITester dialog", JOptionPane.INFORMATION_MESSAGE); if (!GraphicsEnvironment.isHeadless())
} {
else JOptionPane.showMessageDialog(null, "Button pressed!", "UITester dialog", JOptionPane.INFORMATION_MESSAGE);
{ }
LOGGER.info("button pressed!"); else
} {
}); LOGGER.info("button pressed!");
}); }
}));
public static ConfigCategory categoryTest = new ConfigCategory.Builder().set(CategoryTest.class).build(); public static ConfigCategory categoryTest = new ConfigCategory.Builder().set(CategoryTest.class).build();
@@ -77,7 +77,7 @@ public class ConfigBase
* <br> Map<String, T> * <br> Map<String, T>
* <br> HashMap<String, T> * <br> HashMap<String, T>
*/ */
public static final List<Class<?>> acceptableInputs = new ArrayList<Class<?>>() public static final List<Class<?>> acceptableInputs = new ArrayList<>()
{{ {{
add(Boolean.class); add(Boolean.class);
add(Byte.class); add(Byte.class);
@@ -149,7 +149,7 @@ public class ConfigBase
LOGGER.warn(exception); LOGGER.warn(exception);
} }
AbstractConfigType<?, ?> entry = entries.get(entries.size() - 1); AbstractConfigType<?, ?> entry = entries.getLast();
entry.category = category; entry.category = category;
entry.name = field.getName(); entry.name = field.getName();
entry.configBase = this; entry.configBase = this;
@@ -160,7 +160,7 @@ public class ConfigBase
{ {
LOGGER.error("Invalid variable type at [" + (category.isEmpty() ? "" : category + ".") + field.getName() + "]."); LOGGER.error("Invalid variable type at [" + (category.isEmpty() ? "" : category + ".") + field.getName() + "].");
LOGGER.error("Type [" + entry.getType() + "] is not one of these types [" + acceptableInputs.toString() + "]"); LOGGER.error("Type [" + entry.getType() + "] is not one of these types [" + acceptableInputs.toString() + "]");
entries.remove(entries.size() - 1); // Delete the entry if it is invalid so the game can still run entries.removeLast(); // Delete the entry if it is invalid so the game can still run
} }
} }
@@ -32,7 +32,7 @@ import java.util.Map;
public class NumberUtil public class NumberUtil
{ {
// Is there no better way of doing this? // Is there no better way of doing this?
public static Map<Class<?>, Number> minValues = new HashMap<Class<?>, Number>() public static Map<Class<?>, Number> minValues = new HashMap<>()
{{ {{
put(Byte.class, Byte.MIN_VALUE); put(Byte.class, Byte.MIN_VALUE);
put(Short.class, Short.MIN_VALUE); put(Short.class, Short.MIN_VALUE);
@@ -41,7 +41,7 @@ public class NumberUtil
put(Double.class, Double.MIN_VALUE); put(Double.class, Double.MIN_VALUE);
put(Float.class, Float.MIN_VALUE); put(Float.class, Float.MIN_VALUE);
}}; }};
public static Map<Class<?>, Number> maxValues = new HashMap<Class<?>, Number>() public static Map<Class<?>, Number> maxValues = new HashMap<>()
{{ {{
put(Byte.class, Byte.MAX_VALUE); put(Byte.class, Byte.MAX_VALUE);
put(Short.class, Short.MAX_VALUE); put(Short.class, Short.MAX_VALUE);
@@ -23,9 +23,9 @@ import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderProxy; import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderProxy;
import com.seibel.distanthorizons.core.config.listeners.IConfigListener; import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
public class ReloadLodsConfigEventHandler implements IConfigListener public class DebugColumnConfigEventHandler implements IConfigListener
{ {
public static ReloadLodsConfigEventHandler INSTANCE = new ReloadLodsConfigEventHandler(); public static DebugColumnConfigEventHandler INSTANCE = new DebugColumnConfigEventHandler();
@Override @Override
public void onConfigValueSet() public void onConfigValueSet()
@@ -35,8 +35,8 @@ public class QuickRenderToggleConfigEventHandler
/** private since we only ever need one handler at a time */ /** private since we only ever need one handler at a time */
private QuickRenderToggleConfigEventHandler() private QuickRenderToggleConfigEventHandler()
{ {
this.quickRenderChangeListener = new ConfigChangeListener<>(Config.Client.quickEnableRendering, (val) -> { Config.Client.Advanced.Debugging.rendererMode.set(Config.Client.quickEnableRendering.get() ? EDhApiRendererMode.DEFAULT : EDhApiRendererMode.DISABLED); }); this.quickRenderChangeListener = new ConfigChangeListener<>(Config.Client.quickEnableRendering, (val) -> Config.Client.Advanced.Debugging.rendererMode.set(Config.Client.quickEnableRendering.get() ? EDhApiRendererMode.DEFAULT : EDhApiRendererMode.DISABLED));
this.rendererModeChangeListener = new ConfigChangeListener<>(Config.Client.Advanced.Debugging.rendererMode, (val) -> { Config.Client.quickEnableRendering.set(Config.Client.Advanced.Debugging.rendererMode.get() != EDhApiRendererMode.DISABLED); }); this.rendererModeChangeListener = new ConfigChangeListener<>(Config.Client.Advanced.Debugging.rendererMode, (val) -> Config.Client.quickEnableRendering.set(Config.Client.Advanced.Debugging.rendererMode.get() != EDhApiRendererMode.DISABLED));
} }
/** /**
@@ -32,7 +32,7 @@ public class ResetConfigEventHandler
/** private since we only ever need one handler at a time */ /** private since we only ever need one handler at a time */
private ResetConfigEventHandler() private ResetConfigEventHandler()
{ {
this.configChangeListener = new ConfigChangeListener<>(Config.Client.ResetConfirmation.resetAllSettings, (resetSettings) -> { doStuff(resetSettings); }); this.configChangeListener = new ConfigChangeListener<>(Config.Client.ResetConfirmation.resetAllSettings, this::doStuff);
} }
@@ -57,7 +57,7 @@ public abstract class AbstractPresetConfigEventHandler<TPresetEnum extends Enum<
public AbstractPresetConfigEventHandler() public AbstractPresetConfigEventHandler()
{ {
configGui.addOnScreenChangeListener(() -> this.onConfigUiClosed()); configGui.addOnScreenChangeListener(this::onConfigUiClosed);
} }
@@ -168,7 +168,7 @@ public abstract class AbstractPresetConfigEventHandler<TPresetEnum extends Enum<
if (newPreset != currentPreset) if (newPreset != currentPreset)
{ {
this.getPresetConfigEntry().set(newPreset); this.getPresetConfigEntry().set(this.getCustomPresetEnum());
} }
} }
@@ -216,7 +216,7 @@ public abstract class AbstractPresetConfigEventHandler<TPresetEnum extends Enum<
possiblePrestList.add(this.getCustomPresetEnum()); possiblePrestList.add(this.getCustomPresetEnum());
} }
return possiblePrestList.get(0); return possiblePrestList.getFirst();
} }
@@ -41,7 +41,7 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, EDhApiMaxHorizontalResolution> drawResolution = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Quality.maxHorizontalResolution, private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, EDhApiMaxHorizontalResolution> drawResolution = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Quality.maxHorizontalResolution,
new HashMap<EDhApiQualityPreset, EDhApiMaxHorizontalResolution>() new HashMap<>()
{{ {{
this.put(EDhApiQualityPreset.MINIMUM, EDhApiMaxHorizontalResolution.TWO_BLOCKS); this.put(EDhApiQualityPreset.MINIMUM, EDhApiMaxHorizontalResolution.TWO_BLOCKS);
this.put(EDhApiQualityPreset.LOW, EDhApiMaxHorizontalResolution.BLOCK); this.put(EDhApiQualityPreset.LOW, EDhApiMaxHorizontalResolution.BLOCK);
@@ -50,7 +50,7 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.put(EDhApiQualityPreset.EXTREME, EDhApiMaxHorizontalResolution.BLOCK); this.put(EDhApiQualityPreset.EXTREME, EDhApiMaxHorizontalResolution.BLOCK);
}}); }});
private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, EDhApiVerticalQuality> verticalQuality = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Quality.verticalQuality, private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, EDhApiVerticalQuality> verticalQuality = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Quality.verticalQuality,
new HashMap<EDhApiQualityPreset, EDhApiVerticalQuality>() new HashMap<>()
{{ {{
this.put(EDhApiQualityPreset.MINIMUM, EDhApiVerticalQuality.HEIGHT_MAP); this.put(EDhApiQualityPreset.MINIMUM, EDhApiVerticalQuality.HEIGHT_MAP);
this.put(EDhApiQualityPreset.LOW, EDhApiVerticalQuality.LOW); this.put(EDhApiQualityPreset.LOW, EDhApiVerticalQuality.LOW);
@@ -59,7 +59,7 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.put(EDhApiQualityPreset.EXTREME, EDhApiVerticalQuality.EXTREME); this.put(EDhApiQualityPreset.EXTREME, EDhApiVerticalQuality.EXTREME);
}}); }});
private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, EDhApiHorizontalQuality> horizontalQuality = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Quality.horizontalQuality, private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, EDhApiHorizontalQuality> horizontalQuality = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Quality.horizontalQuality,
new HashMap<EDhApiQualityPreset, EDhApiHorizontalQuality>() new HashMap<>()
{{ {{
this.put(EDhApiQualityPreset.MINIMUM, EDhApiHorizontalQuality.LOWEST); this.put(EDhApiQualityPreset.MINIMUM, EDhApiHorizontalQuality.LOWEST);
this.put(EDhApiQualityPreset.LOW, EDhApiHorizontalQuality.LOW); this.put(EDhApiQualityPreset.LOW, EDhApiHorizontalQuality.LOW);
@@ -68,7 +68,7 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.put(EDhApiQualityPreset.EXTREME, EDhApiHorizontalQuality.EXTREME); this.put(EDhApiQualityPreset.EXTREME, EDhApiHorizontalQuality.EXTREME);
}}); }});
private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, EDhApiTransparency> transparency = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Quality.transparency, private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, EDhApiTransparency> transparency = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Quality.transparency,
new HashMap<EDhApiQualityPreset, EDhApiTransparency>() new HashMap<>()
{{ {{
this.put(EDhApiQualityPreset.MINIMUM, EDhApiTransparency.DISABLED); this.put(EDhApiQualityPreset.MINIMUM, EDhApiTransparency.DISABLED);
this.put(EDhApiQualityPreset.LOW, EDhApiTransparency.DISABLED); // should be fake if/when fake is fixed this.put(EDhApiQualityPreset.LOW, EDhApiTransparency.DISABLED); // should be fake if/when fake is fixed
@@ -77,7 +77,7 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.put(EDhApiQualityPreset.EXTREME, EDhApiTransparency.COMPLETE); this.put(EDhApiQualityPreset.EXTREME, EDhApiTransparency.COMPLETE);
}}); }});
private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, Boolean> ssaoEnabled = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Ssao.enabled, private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, Boolean> ssaoEnabled = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Ssao.enabled,
new HashMap<EDhApiQualityPreset, Boolean>() new HashMap<>()
{{ {{
this.put(EDhApiQualityPreset.MINIMUM, false); this.put(EDhApiQualityPreset.MINIMUM, false);
this.put(EDhApiQualityPreset.LOW, false); this.put(EDhApiQualityPreset.LOW, false);
@@ -106,7 +106,7 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
for (ConfigEntryWithPresetOptions<EDhApiQualityPreset, ?> config : this.configList) for (ConfigEntryWithPresetOptions<EDhApiQualityPreset, ?> config : this.configList)
{ {
// ignore try-using, the listener should only ever be added once and should never be removed // ignore try-using, the listener should only ever be added once and should never be removed
new ConfigChangeListener<>(config.configEntry, (val) -> { this.onConfigValueChanged(); }); new ConfigChangeListener<>(config.configEntry, (val) -> this.onConfigValueChanged());
} }
} }
@@ -42,7 +42,7 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
public static int getWorldGenDefaultThreadCount() { return getThreadCountByPercent(0.1); } public static int getWorldGenDefaultThreadCount() { return getThreadCountByPercent(0.1); }
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Integer> worldGenThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads, private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Integer> worldGenThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads,
new HashMap<EDhApiThreadPreset, Integer>() new HashMap<>()
{{ {{
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 1); this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 1);
this.put(EDhApiThreadPreset.LOW_IMPACT, getWorldGenDefaultThreadCount()); this.put(EDhApiThreadPreset.LOW_IMPACT, getWorldGenDefaultThreadCount());
@@ -52,7 +52,7 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
}}); }});
public static double getWorldGenDefaultRunTimeRatio() { return 0.5; } public static double getWorldGenDefaultRunTimeRatio() { return 0.5; }
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Double> worldGenRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForWorldGenerationThreads, private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Double> worldGenRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForWorldGenerationThreads,
new HashMap<EDhApiThreadPreset, Double>() new HashMap<>()
{{ {{
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 0.1); this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 0.1);
this.put(EDhApiThreadPreset.LOW_IMPACT, getWorldGenDefaultRunTimeRatio()); this.put(EDhApiThreadPreset.LOW_IMPACT, getWorldGenDefaultRunTimeRatio());
@@ -64,7 +64,7 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
public static int getFileHandlerDefaultThreadCount() { return getThreadCountByPercent(0.1); } public static int getFileHandlerDefaultThreadCount() { return getThreadCountByPercent(0.1); }
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Integer> fileHandlerThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfFileHandlerThreads, private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Integer> fileHandlerThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfFileHandlerThreads,
new HashMap<EDhApiThreadPreset, Integer>() new HashMap<>()
{{ {{
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 1); this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 1);
this.put(EDhApiThreadPreset.LOW_IMPACT, getFileHandlerDefaultThreadCount()); this.put(EDhApiThreadPreset.LOW_IMPACT, getFileHandlerDefaultThreadCount());
@@ -74,7 +74,7 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
}}); }});
public static double getFileHandlerDefaultRunTimeRatio() { return 0.5; } public static double getFileHandlerDefaultRunTimeRatio() { return 0.5; }
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Double> fileHandlerRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForFileHandlerThreads, private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Double> fileHandlerRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForFileHandlerThreads,
new HashMap<EDhApiThreadPreset, Double>() new HashMap<>()
{{ {{
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 0.25); this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 0.25);
this.put(EDhApiThreadPreset.LOW_IMPACT, getFileHandlerDefaultRunTimeRatio()); this.put(EDhApiThreadPreset.LOW_IMPACT, getFileHandlerDefaultRunTimeRatio());
@@ -86,7 +86,7 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
public static int getUpdatePropagatorDefaultThreadCount() { return getThreadCountByPercent(0.10); } public static int getUpdatePropagatorDefaultThreadCount() { return getThreadCountByPercent(0.10); }
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Integer> UpdatePropagatorThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfUpdatePropagatorThreads, private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Integer> UpdatePropagatorThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfUpdatePropagatorThreads,
new HashMap<EDhApiThreadPreset, Integer>() new HashMap<>()
{{ {{
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 1); this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 1);
this.put(EDhApiThreadPreset.LOW_IMPACT, getUpdatePropagatorDefaultThreadCount()); this.put(EDhApiThreadPreset.LOW_IMPACT, getUpdatePropagatorDefaultThreadCount());
@@ -96,7 +96,7 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
}}); }});
public static double getUpdatePropagatorDefaultRunTimeRatio() { return 0.25; } public static double getUpdatePropagatorDefaultRunTimeRatio() { return 0.25; }
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Double> UpdatePropagatorRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForUpdatePropagatorThreads, private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Double> UpdatePropagatorRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForUpdatePropagatorThreads,
new HashMap<EDhApiThreadPreset, Double>() new HashMap<>()
{{ {{
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 0.1); this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 0.1);
this.put(EDhApiThreadPreset.LOW_IMPACT, getUpdatePropagatorDefaultRunTimeRatio()); this.put(EDhApiThreadPreset.LOW_IMPACT, getUpdatePropagatorDefaultRunTimeRatio());
@@ -108,7 +108,7 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
public static int getLodBuilderDefaultThreadCount() { return getThreadCountByPercent(0.1); } public static int getLodBuilderDefaultThreadCount() { return getThreadCountByPercent(0.1); }
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Integer> lodBuilderThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads, private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Integer> lodBuilderThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads,
new HashMap<EDhApiThreadPreset, Integer>() new HashMap<>()
{{ {{
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 1); this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 1);
this.put(EDhApiThreadPreset.LOW_IMPACT, getLodBuilderDefaultThreadCount()); this.put(EDhApiThreadPreset.LOW_IMPACT, getLodBuilderDefaultThreadCount());
@@ -118,7 +118,7 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
}}); }});
public static double getLodBuilderDefaultRunTimeRatio() { return 0.25; } public static double getLodBuilderDefaultRunTimeRatio() { return 0.25; }
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Double> lodBuilderRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForLodBuilderThreads, private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Double> lodBuilderRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForLodBuilderThreads,
new HashMap<EDhApiThreadPreset, Double>() new HashMap<>()
{{ {{
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 0.1); this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 0.1);
this.put(EDhApiThreadPreset.LOW_IMPACT, getLodBuilderDefaultRunTimeRatio()); this.put(EDhApiThreadPreset.LOW_IMPACT, getLodBuilderDefaultRunTimeRatio());
@@ -153,7 +153,7 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
for (ConfigEntryWithPresetOptions<EDhApiThreadPreset, ?> config : this.configList) for (ConfigEntryWithPresetOptions<EDhApiThreadPreset, ?> config : this.configList)
{ {
// ignore try-using, the listeners should only ever be added once and should never be removed // ignore try-using, the listeners should only ever be added once and should never be removed
new ConfigChangeListener<>(config.configEntry, (val) -> { this.onConfigValueChanged(); }); new ConfigChangeListener<>(config.configEntry, (val) -> this.onConfigValueChanged());
} }
} }
@@ -35,7 +35,7 @@ import java.util.Map;
public class ConfigTypeConverters public class ConfigTypeConverters
{ {
// Once you've made a converter add it to here where the first value is the type you want to convert and the 2nd value is the converter // Once you've made a converter add it to here where the first value is the type you want to convert and the 2nd value is the converter
public static final Map<Class<?>, ConverterBase> convertObjects = new HashMap<Class<?>, ConverterBase>() public static final Map<Class<?>, ConverterBase> convertObjects = new HashMap<>()
{{ {{
this.put(Short.class, new ShortConverter()); this.put(Short.class, new ShortConverter());
this.put(Long.class, new LongConverter()); this.put(Long.class, new LongConverter());
@@ -73,33 +73,28 @@ public final class EmbeddedFrameUtil
private static String getEmbeddedFrameImpl() private static String getEmbeddedFrameImpl()
{ {
switch (EPlatform.get()) return switch (EPlatform.get())
{ {
case LINUX: case LINUX -> "sun.awt.X11.XEmbeddedFrame";
return "sun.awt.X11.XEmbeddedFrame"; case WINDOWS -> "sun.awt.windows.WEmbeddedFrame";
case WINDOWS: case MACOS -> "sun.lwawt.macosx.CViewEmbeddedFrame";
return "sun.awt.windows.WEmbeddedFrame"; default -> throw new IllegalStateException();
case MACOS: };
return "sun.lwawt.macosx.CViewEmbeddedFrame";
default:
throw new IllegalStateException();
}
} }
private static long getEmbeddedFrameHandle(long window) private static long getEmbeddedFrameHandle(long window)
{ {
switch (EPlatform.get()) return switch (EPlatform.get())
{ {
case LINUX: case LINUX -> glfwGetX11Window(window);
return glfwGetX11Window(window); case WINDOWS -> glfwGetWin32Window(window);
case WINDOWS: case MACOS ->
return glfwGetWin32Window(window); {
case MACOS:
long objc_msgSend = ObjCRuntime.getLibrary().getFunctionAddress("objc_msgSend"); long objc_msgSend = ObjCRuntime.getLibrary().getFunctionAddress("objc_msgSend");
return invokePPP(glfwGetCocoaWindow(window), sel_getUid("contentView"), objc_msgSend); yield invokePPP(glfwGetCocoaWindow(window), sel_getUid("contentView"), objc_msgSend);
default: }
throw new IllegalStateException(); default -> throw new IllegalStateException();
} };
} }
public static Frame embeddedFrameCreate(long window) public static Frame embeddedFrameCreate(long window)
@@ -516,10 +516,9 @@ public class FullDataPointIdMap
if (otherObj == this) if (otherObj == this)
return true; return true;
if (!(otherObj instanceof Entry)) if (!(otherObj instanceof Entry other))
return false; return false;
Entry other = (Entry) otherObj;
return other.biome.getSerialString().equals(this.biome.getSerialString()) return other.biome.getSerialString().equals(this.biome.getSerialString())
&& other.blockState.getSerialString().equals(this.blockState.getSerialString()); && other.blockState.getSerialString().equals(this.blockState.getSerialString());
} }
@@ -927,11 +927,10 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
@Override @Override
public boolean equals(Object obj) public boolean equals(Object obj)
{ {
if (!(obj instanceof FullDataSourceV2)) if (!(obj instanceof FullDataSourceV2 other))
{ {
return false; return false;
} }
FullDataSourceV2 other = (FullDataSourceV2) obj;
if (other.pos != this.pos) if (other.pos != this.pos)
{ {
@@ -25,7 +25,7 @@ import com.seibel.distanthorizons.core.dataObjects.transformers.FullDataToRender
import com.seibel.distanthorizons.core.file.DataSourcePool; import com.seibel.distanthorizons.core.file.DataSourcePool;
import com.seibel.distanthorizons.core.file.IDataSource; import com.seibel.distanthorizons.core.file.IDataSource;
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.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
@@ -182,7 +182,7 @@ public class ColumnRenderSource implements IDataSource<IDhClientLevel>
EDhApiWorldGenerationStep worldGenStep = inputFullDataSource.getWorldGenStepAtRelativePos(x, z); EDhApiWorldGenerationStep worldGenStep = inputFullDataSource.getWorldGenStepAtRelativePos(x, z);
if (dataColumn != null && worldGenStep != EDhApiWorldGenerationStep.EMPTY) if (dataColumn != null && worldGenStep != EDhApiWorldGenerationStep.EMPTY)
{ {
FullDataToRenderDataTransformer.updateOrReplaceRenderDataViewColumnWithFullDataColumn( FullDataToRenderDataTransformer.updateRenderDataViewWithFullDataColumn(
level, inputFullDataSource.mapping, level, inputFullDataSource.mapping,
minBlockPos.x + x, minBlockPos.x + x,
minBlockPos.z + z, minBlockPos.z + z,
@@ -288,7 +288,7 @@ public class ColumnRenderSource implements IDataSource<IDhClientLevel>
String SUBDATA_DELIMITER = ","; String SUBDATA_DELIMITER = ",";
StringBuilder stringBuilder = new StringBuilder(); StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(DhSectionPos.toString(this.pos)); stringBuilder.append(this.pos);
stringBuilder.append(LINE_DELIMITER); stringBuilder.append(LINE_DELIMITER);
int size = 1; int size = 1;
@@ -99,33 +99,23 @@ public final class BufferQuad
if (compareDirection == BufferMergeDirectionEnum.EastWest) if (compareDirection == BufferMergeDirectionEnum.EastWest)
{ {
switch (this.direction.getAxis()) return switch (this.direction.getAxis())
{ {
case X: case X -> threeDimensionalCompare(this.x, this.y, this.z, quad.x, quad.y, quad.z);
return threeDimensionalCompare(this.x, this.y, this.z, quad.x, quad.y, quad.z); case Y -> threeDimensionalCompare(this.y, this.z, this.x, quad.y, quad.z, quad.x);
case Y: case Z -> threeDimensionalCompare(this.z, this.y, this.x, quad.z, quad.y, quad.x);
return threeDimensionalCompare(this.y, this.z, this.x, quad.y, quad.z, quad.x); default -> throw new IllegalArgumentException("Invalid Axis enum: " + this.direction.getAxis());
case Z: };
return threeDimensionalCompare(this.z, this.y, this.x, quad.z, quad.y, quad.x);
default:
throw new IllegalArgumentException("Invalid Axis enum: " + this.direction.getAxis());
}
} }
else else
{ {
switch (this.direction.getAxis()) return switch (this.direction.getAxis())
{ {
case X: case X -> threeDimensionalCompare(this.x, this.z, this.y, quad.x, quad.z, quad.y);
return threeDimensionalCompare(this.x, this.z, this.y, quad.x, quad.z, quad.y); case Y -> threeDimensionalCompare(this.y, this.x, this.z, quad.y, quad.x, quad.z);
case Y: case Z -> threeDimensionalCompare(this.z, this.x, this.y, quad.z, quad.x, quad.y);
return threeDimensionalCompare(this.y, this.x, this.z, quad.y, quad.x, quad.z); default -> throw new IllegalArgumentException("Invalid Axis enum: " + this.direction.getAxis());
case Z: };
return threeDimensionalCompare(this.z, this.x, this.y, quad.z, quad.x, quad.y);
default:
throw new IllegalArgumentException("Invalid Axis enum: " + this.direction.getAxis());
}
} }
} }
/** /**
@@ -19,60 +19,29 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding; package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
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.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.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer; import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.coreapi.util.MathUtil; import com.seibel.distanthorizons.coreapi.util.MathUtil;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
public class ColumnBox public class ColumnBox
{ {
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
/**
* if the skylight has this value that means
* no data is expected
*/
private static final byte SKYLIGHT_EMPTY = -1;
/**
* if the skylight has this value that means
* that block position is covered/occuled by an adjacent block/column.
*/
private static final byte SKYLIGHT_COVERED = -2;
private static final ThreadLocal<byte[]> THREAD_LOCAL_SKY_LIGHT_ARRAY = ThreadLocal.withInitial(() ->
{
byte[] array = new byte[RenderDataPointUtil.MAX_WORLD_Y_SIZE];
Arrays.fill(array, SKYLIGHT_EMPTY);
return array;
});
//=========//
// builder //
//=========//
public static void addBoxQuadsToBuilder( public static void addBoxQuadsToBuilder(
LodQuadBuilder builder, IDhClientLevel clientLevel, LodQuadBuilder builder,
short xSize, short ySize, short zSize, short xSize, short ySize, short zSize,
short x, short minY, short z, short x, short minY, short z,
int color, byte irisBlockMaterialId, byte skyLight, byte blockLight, int color, byte irisBlockMaterialId, byte skyLight, byte blockLight,
long topData, long bottomData, ColumnArrayView[] adjData, boolean[] isAdjDataSameDetailLevel) long topData, long bottomData, ColumnArrayView[][] adjData)
{ {
//================//
// variable setup //
//================//
short maxX = (short) (x + xSize); short maxX = (short) (x + xSize);
short maxY = (short) (minY + ySize); short maxY = (short) (minY + ySize);
short maxZ = (short) (z + zSize); short maxZ = (short) (z + zSize);
@@ -84,24 +53,33 @@ public class ColumnBox
boolean isTopTransparent = RenderDataPointUtil.getAlpha(topData) < 255 && LodRenderer.transparencyEnabled; boolean isTopTransparent = RenderDataPointUtil.getAlpha(topData) < 255 && LodRenderer.transparencyEnabled;
boolean isBottomTransparent = RenderDataPointUtil.getAlpha(bottomData) < 255 && LodRenderer.transparencyEnabled; boolean isBottomTransparent = RenderDataPointUtil.getAlpha(bottomData) < 255 && LodRenderer.transparencyEnabled;
// defaulting to a value far below what we can normally render means we
// don't need to have an additional "is cave culling enabled" check
int caveCullingMaxY = Integer.MIN_VALUE;
if (Config.Client.Advanced.Graphics.AdvancedGraphics.enableCaveCulling.get())
{
caveCullingMaxY = Config.Client.Advanced.Graphics.AdvancedGraphics.caveCullingHeight.get() - clientLevel.getMinY();
}
// if there isn't any data below this LOD, make this LOD's color opaque to prevent seeing void through transparent blocks // if there isn't any data below this LOD, make this LOD's color opaque to prevent seeing void through transparent blocks
// Note: this LOD should still be considered transparent for this method's checks, otherwise rendering bugs may occur // Note: this LOD should still be considered transparent for this method's checks, otherwise rendering bugs may occur
// FIXME this transparency change should be applied before this point since this could affect other areas
// This may also be better than handling the LOD as transparent, but that is TBD
if (!RenderDataPointUtil.doesDataPointExist(bottomData)) if (!RenderDataPointUtil.doesDataPointExist(bottomData))
{ {
color = ColorUtil.setAlpha(color, 255); color = ColorUtil.setAlpha(color, 255);
} }
// cave culling prevention
// prevents certain faces from being culled underground that should be allowed
if (builder.skipQuadsWithZeroSkylight
&& 0 == skyLight
&& builder.skyLightCullingBelow > maxY
&& (
(RenderDataPointUtil.getAlpha(topData) < 255 && RenderDataPointUtil.getYMax(topData) >= builder.skyLightCullingBelow)
|| (RenderDataPointUtil.getYMin(topData) >= builder.skyLightCullingBelow)
|| !RenderDataPointUtil.doesDataPointExist(topData)
)
)
{
maxY = builder.skyLightCullingBelow;
}
// fake ocean transparency // fake ocean transparency
if (LodRenderer.transparencyEnabled && LodRenderer.fakeOceanFloor) if (LodRenderer.transparencyEnabled && LodRenderer.fakeOceanFloor)
{ {
@@ -121,9 +99,7 @@ public class ColumnBox
//==========================// // add top and bottom faces if requested //
// add top and bottom faces //
//==========================//
boolean skipTop = RenderDataPointUtil.doesDataPointExist(topData) && (RenderDataPointUtil.getYMin(topData) == maxY) && !isTopTransparent; boolean skipTop = RenderDataPointUtil.doesDataPointExist(topData) && (RenderDataPointUtil.getYMin(topData) == maxY) && !isTopTransparent;
if (!skipTop) if (!skipTop)
@@ -138,291 +114,409 @@ public class ColumnBox
} }
// add North, south, east, and west faces if requested //
//========================================// // TODO merge duplicate code
// add North, south, east, and west faces // //NORTH face vertex creation
//========================================//
// NORTH face
{ {
ColumnArrayView adjCol = adjData[EDhDirection.NORTH.ordinal() - 2]; // TODO can we use something other than ordinal-2? ColumnArrayView[] adjDataNorth = adjData[EDhDirection.NORTH.ordinal() - 2]; // TODO can we use something other than ordinal-2?
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.NORTH.ordinal() - 2]; int adjOverlapNorth = ColorUtil.INVISIBLE;
// if the adjacent column is null that generally means the adjacent area hasn't been generated yet if (adjDataNorth == null)
if (adjCol == null)
{ {
// Add an adjacent face if this is opaque face or transparent over the void. // add an adjacent face if this is opaque face or transparent over the void
if (!isTransparent || overVoid) if (!isTransparent || overVoid)
{ {
builder.addQuadAdj(EDhDirection.NORTH, x, minY, z, xSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); builder.addQuadAdj(EDhDirection.NORTH, x, minY, z, xSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
} }
} }
else if (adjDataNorth.length == 1)
{
makeAdjVerticalQuad(builder, adjDataNorth[0], EDhDirection.NORTH, x, minY, z, xSize, ySize,
color, adjOverlapNorth, irisBlockMaterialId, skyLightTop, blockLight,
topData, bottomData);
}
else else
{ {
makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.NORTH, x, minY, z, xSize, ySize, makeAdjVerticalQuad(builder, adjDataNorth[0], EDhDirection.NORTH, x, minY, z, (short) (xSize / 2), ySize,
color, irisBlockMaterialId, blockLight); color, adjOverlapNorth, irisBlockMaterialId, skyLightTop, blockLight,
topData, bottomData);
makeAdjVerticalQuad(builder, adjDataNorth[1], EDhDirection.NORTH, (short) (x + xSize / 2), minY, z, (short) (xSize / 2), ySize,
color, adjOverlapNorth, irisBlockMaterialId, skyLightTop, blockLight,
topData, bottomData);
} }
} }
// SOUTH face //SOUTH face vertex creation
{ {
ColumnArrayView adjCol = adjData[EDhDirection.SOUTH.ordinal() - 2]; ColumnArrayView[] adjDataSouth = adjData[EDhDirection.SOUTH.ordinal() - 2];
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.SOUTH.ordinal() - 2]; int adjOverlapSouth = ColorUtil.INVISIBLE;
if (adjCol == null) if (adjDataSouth == null)
{ {
if (!isTransparent || overVoid) if (!isTransparent || overVoid)
{
builder.addQuadAdj(EDhDirection.SOUTH, x, minY, maxZ, xSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); builder.addQuadAdj(EDhDirection.SOUTH, x, minY, maxZ, xSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
} }
else if (adjDataSouth.length == 1)
{
makeAdjVerticalQuad(builder, adjDataSouth[0], EDhDirection.SOUTH, x, minY, maxZ, xSize, ySize,
color, adjOverlapSouth, irisBlockMaterialId, skyLightTop, blockLight,
topData, bottomData);
} }
else else
{ {
makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.SOUTH, x, minY, maxZ, xSize, ySize, makeAdjVerticalQuad(builder, adjDataSouth[0], EDhDirection.SOUTH, x, minY, maxZ, (short) (xSize / 2), ySize,
color, irisBlockMaterialId, blockLight); color, adjOverlapSouth, irisBlockMaterialId, skyLightTop, blockLight,
topData, bottomData);
makeAdjVerticalQuad(builder, adjDataSouth[1], EDhDirection.SOUTH, (short) (x + xSize / 2), minY, maxZ, (short) (xSize / 2), ySize,
color, adjOverlapSouth, irisBlockMaterialId, skyLightTop, blockLight,
topData, bottomData);
} }
} }
// WEST face //WEST face vertex creation
{ {
ColumnArrayView adjCol = adjData[EDhDirection.WEST.ordinal() - 2]; ColumnArrayView[] adjDataWest = adjData[EDhDirection.WEST.ordinal() - 2];
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.WEST.ordinal() - 2]; int adjOverlapWest = ColorUtil.INVISIBLE;
if (adjCol == null) if (adjDataWest == null)
{ {
if (!isTransparent || overVoid) if (!isTransparent || overVoid)
{
builder.addQuadAdj(EDhDirection.WEST, x, minY, z, zSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); builder.addQuadAdj(EDhDirection.WEST, x, minY, z, zSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
} }
else if (adjDataWest.length == 1)
{
makeAdjVerticalQuad(builder, adjDataWest[0], EDhDirection.WEST, x, minY, z, zSize, ySize,
color, adjOverlapWest, irisBlockMaterialId, skyLightTop, blockLight,
topData, bottomData);
} }
else else
{ {
makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.WEST, x, minY, z, zSize, ySize, makeAdjVerticalQuad(builder, adjDataWest[0], EDhDirection.WEST, x, minY, z, (short) (zSize / 2), ySize,
color, irisBlockMaterialId, blockLight); color, adjOverlapWest, irisBlockMaterialId, skyLightTop, blockLight,
topData, bottomData);
makeAdjVerticalQuad(builder, adjDataWest[1], EDhDirection.WEST, x, minY, (short) (z + zSize / 2), (short) (zSize / 2), ySize,
color, adjOverlapWest, irisBlockMaterialId, skyLightTop, blockLight,
topData, bottomData);
} }
} }
// EAST face //EAST face vertex creation
{ {
ColumnArrayView adjCol = adjData[EDhDirection.EAST.ordinal() - 2]; ColumnArrayView[] adjDataEast = adjData[EDhDirection.EAST.ordinal() - 2];
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.EAST.ordinal() - 2]; int adjOverlapEast = ColorUtil.INVISIBLE;
if (adjCol == null) if (adjData[EDhDirection.EAST.ordinal() - 2] == null)
{ {
if (!isTransparent || overVoid) if (!isTransparent || overVoid)
{
builder.addQuadAdj(EDhDirection.EAST, maxX, minY, z, zSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); builder.addQuadAdj(EDhDirection.EAST, maxX, minY, z, zSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
} }
else if (adjDataEast.length == 1)
{
makeAdjVerticalQuad(builder, adjDataEast[0], EDhDirection.EAST, maxX, minY, z, zSize, ySize,
color, adjOverlapEast, irisBlockMaterialId, skyLightTop, blockLight,
topData, bottomData);
} }
else else
{ {
makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.EAST, maxX, minY, z, zSize, ySize, makeAdjVerticalQuad(builder, adjDataEast[0], EDhDirection.EAST, maxX, minY, z, (short) (zSize / 2), ySize,
color, irisBlockMaterialId, blockLight); color, adjOverlapEast, irisBlockMaterialId, skyLightTop, blockLight,
topData, bottomData);
makeAdjVerticalQuad(builder, adjDataEast[1], EDhDirection.EAST, maxX, minY, (short) (z + zSize / 2), (short) (zSize / 2), ySize,
color, adjOverlapEast, irisBlockMaterialId, skyLightTop, blockLight,
topData, bottomData);
} }
} }
} }
// the overlap color can be used to see faces that shouldn't be rendered
private static void makeAdjVerticalQuad( private static void makeAdjVerticalQuad(
LodQuadBuilder builder, @NotNull ColumnArrayView adjColumnView, boolean adjacentIsSameDetailLevel, int caveCullingMaxY, EDhDirection direction, LodQuadBuilder builder, ColumnArrayView adjColumnView, 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, int debugOverlapColor, byte irisBlockMaterialId, byte skyLightTop, byte blockLight,
long topData, long bottomData)
{ {
//==================//
// create face with //
// no adjacent data //
//==================//
color = ColorUtil.applyShade(color, MC.getShade(direction)); color = ColorUtil.applyShade(color, MC.getShade(direction));
// if there isn't any data adjacent to this LOD, if (adjColumnView == null || adjColumnView.size == 0 || RenderDataPointUtil.isVoid(adjColumnView.get(0)))
// just add the full vertical quad
if (adjColumnView.size == 0 || RenderDataPointUtil.isVoid(adjColumnView.get(0)))
{ {
// there isn't any data adjacent to this LOD, add the vertical quad
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
return; return;
} }
int yMax = yMin + ySize;
//===========================// int adjIndex;
// Determine face visibility // boolean firstFace = true;
// based on it's neighbors // boolean inputAboveAdjLods = true;
//===========================// short previousAdjDepth = -1;
byte nextTopSkyLight = skyLightTop;
boolean inputTransparent = ColorUtil.getAlpha(color) < 255 && LodRenderer.transparencyEnabled;
boolean lastAdjWasTransparent = false;
short yMax = (short) (yMin + ySize); // min is inclusive, max is exclusive
byte[] skyLightAtInputPos = THREAD_LOCAL_SKY_LIGHT_ARRAY.get();
try
if (!RenderDataPointUtil.doesDataPointExist(bottomData))
{ {
// set the initial sky-lights for this face, // there isn't anything under this LOD,
// if nothing overlaps or overhangs the face should have max sky light // to prevent seeing through the world, make it opaque
Arrays.fill(skyLightAtInputPos, yMin, yMax, LodUtil.MAX_MC_LIGHT); color = ColorUtil.setAlpha(color, 255);
}
// Add adjacent faces if this LOD is surrounded by transparent LODs
// (prevents invisible sides underwater)
int adjCount = adjColumnView.size();
for (adjIndex = 0; // iterates top down
adjIndex < adjCount
&& RenderDataPointUtil.doesDataPointExist(adjColumnView.get(adjIndex))
&& !RenderDataPointUtil.isVoid(adjColumnView.get(adjIndex));
adjIndex++)
{
long adjPoint = adjColumnView.get(adjIndex);
// iterate top down // if the adjacent data point is over the void
int adjCount = adjColumnView.size(); // don't consider it as transparent
for (int adjIndex = 0; adjIndex < adjCount; adjIndex++) // FIXME this transparency change should be applied before this point since this could affect other areas
boolean adjOverVoid = false;
if (adjIndex > 0)
{ {
long adjPoint = adjColumnView.get(adjIndex); long adjBellowPoint = adjColumnView.get(adjIndex-1);
short adjMinY = RenderDataPointUtil.getYMin(adjPoint); adjOverVoid = !RenderDataPointUtil.doesDataPointExist(adjBellowPoint);
short adjMaxY = RenderDataPointUtil.getYMax(adjPoint); }
boolean adjTransparent = !adjOverVoid && RenderDataPointUtil.getAlpha(adjPoint) < 255 && LodRenderer.transparencyEnabled;
// continue if this data point is transparent or the adjacent point is not
if (inputTransparent || !adjTransparent) // TODO inputIsTransparent may be unnecessary
{
short adjYMin = RenderDataPointUtil.getYMin(adjPoint);
short adjYMax = RenderDataPointUtil.getYMax(adjPoint);
// skip empty adjacent datapoints
if (!RenderDataPointUtil.doesDataPointExist(adjPoint) // if fake transparency is enabled, allow for 1 block of transparency,
|| RenderDataPointUtil.isVoid(adjPoint)) // everything under that should be opaque
if (LodRenderer.transparencyEnabled && LodRenderer.fakeOceanFloor)
{ {
continue; if (lastAdjWasTransparent && !adjTransparent)
}
// skip this adjacent datapoint if it's above the input datapoint (since it can't affect the input data point)
if (yMax <= adjMinY)
{
continue;
}
long adjAbovePoint = (adjIndex != 0) ? adjColumnView.get(adjIndex - 1) : RenderDataPointUtil.EMPTY_DATA;
long adjBelowPoint = (adjIndex + 1 < adjCount) ? adjColumnView.get(adjIndex + 1) : RenderDataPointUtil.EMPTY_DATA;
// if the adjacent data point is over the void
// don't consider it as transparent
boolean adjOverVoid = !RenderDataPointUtil.doesDataPointExist(adjBelowPoint);
boolean adjTransparent = !adjOverVoid && RenderDataPointUtil.getAlpha(adjPoint) < 255 && LodRenderer.transparencyEnabled;
//=================================//
// set sky light based on adjacent //
//=================================//
// set light based on overlapping adjacent
if (!adjTransparent)
{
// adj opaque
// mark positions adjacent is covering
byte adjSkyLight = RenderDataPointUtil.getLightSky(adjPoint);
for (int i = adjMinY; i < adjMaxY; i++)
{ {
byte skyLightAtPos = skyLightAtInputPos[i]; adjYMax = (short) (RenderDataPointUtil.getYMax(adjColumnView.get(adjIndex - 1)) - 1);
// if the adjacent is a different detail level, we want to render adjacent opaque
// faces to try and reduce the chance of holes on detail level borders
boolean adjacentCoversThis =
// if the adjacent is the same detail level, no special handling is necessary
!adjacentIsSameDetailLevel
// if the adjacent face is underground we probably don't need it
&& RenderDataPointUtil.getYMax(adjPoint) >= caveCullingMaxY
// check if this face is on a border
&&
(
(x == 0 && direction == EDhDirection.WEST)
|| (z == 0 && direction == EDhDirection.NORTH)
// TODO why does 256 represent a border? aren't LODs only 64 datapoints wide?
|| (x == 256 && direction == EDhDirection.EAST)
|| (z == 256 && direction == EDhDirection.SOUTH)
);
byte newSkyLightAtPos = adjacentCoversThis ? adjSkyLight : SKYLIGHT_COVERED;
skyLightAtInputPos[i] = (byte) Math.min(newSkyLightAtPos, skyLightAtPos);
} }
else if (adjTransparent && (adjIndex + 1) < adjCount)
{
if (RenderDataPointUtil.getAlpha(adjColumnView.get(adjIndex + 1)) == 255)
{
adjYMin = (short) (adjYMax - 1);
}
}
}
if (yMax <= adjYMin)
{
// the adjacent LOD is above the input LOD and won't affect its rendering,
// skip to the next adjacent
continue;
}
inputAboveAdjLods = false;
if (adjYMax < yMin)
{
// the adjacent LOD is below the input LOD
// getting the skylight is more complicated
// since LODs can be adjacent to water, which changes how skylight works
byte skyLight;
if (adjIndex == 0)
{
// this adj LOD is at the highest position,
// its sky lighting won't be affected by anything above it
skyLight = RenderDataPointUtil.getLightSky(adjPoint);
}
else
{
// TODO improve the comments here, this is a bit confusing
long aboveAdjPoint = adjColumnView.get(adjIndex - 1);
if (RenderDataPointUtil.getAlpha(aboveAdjPoint) != 255)
{
// above adjacent LOD is transparent...
boolean inputMaxHigherThanAboveAdj = yMax > RenderDataPointUtil.getYMax(aboveAdjPoint);
if (inputMaxHigherThanAboveAdj)
{
// ...and higher than the input yMax,
// use its sky light
skyLight = RenderDataPointUtil.getLightSky(aboveAdjPoint);
}
else
{
// ...and at or below the input yMax,
skyLight = RenderDataPointUtil.getLightSky(adjPoint);
}
}
else
{
// LOD above adjacent is opaque, use the adj LOD's skylight
skyLight = RenderDataPointUtil.getLightSky(adjPoint);
}
}
if (firstFace)
{
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, color, irisBlockMaterialId, skyLight, blockLight);
}
else
{
// Now: adjMaxHeight < y < previousAdjDepth < yMax
if (previousAdjDepth == -1)
{
// TODO why is this an error?
throw new RuntimeException("Loop error");
}
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, (short) (previousAdjDepth - yMin), color, irisBlockMaterialId, skyLight, blockLight);
previousAdjDepth = -1;
}
// TODO why break here?
break;
}
if (adjYMin <= yMin)
{
// the adjacent LOD's base is at or below the input's base
if (yMax <= adjYMax)
{
// The input face is completely inside the adj's face, don't render it
if (debugOverlapColor != 0)
{
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, debugOverlapColor, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, LodUtil.MAX_MC_LIGHT);
}
}
else
{
// the adj data intersects the lower part of the input data, don't render below the intersection
if (adjYMax > yMin && debugOverlapColor != 0)
{
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, (short) (adjYMax - yMin), debugOverlapColor, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, LodUtil.MAX_MC_LIGHT);
}
// if this is the only face, use the yMax and break,
// if there was another face finish the last one and then break
if (firstFace)
{
builder.addQuadAdj(direction, x, adjYMax, z, horizontalWidth, (short) (yMax - adjYMax), color, irisBlockMaterialId,
RenderDataPointUtil.getLightSky(adjPoint), blockLight);
}
else
{
// Now: depth <= y <= height <= previousAdjDepth < yMax
if (previousAdjDepth == -1)
{
// TODO why is this an error?
throw new RuntimeException("Loop error");
}
if (previousAdjDepth > adjYMax)
{
builder.addQuadAdj(direction, x, adjYMax, z, horizontalWidth, (short) (previousAdjDepth - adjYMax), color, irisBlockMaterialId,
RenderDataPointUtil.getLightSky(adjPoint), blockLight);
}
previousAdjDepth = -1;
}
}
// we don't need to check any other adjacent LODs
// since this one completely covers the input
break;
}
// In here always true: y < adjYMin < yMax
// _________________&&: y < ________ (height and yMax)
if (adjYMax >= yMax)
{
// Basically: y _______ < yMax <= height
// _______&&: y < depth < yMax
// the adj data intersects the higher part of the current data
if (debugOverlapColor != 0)
{
builder.addQuadAdj(direction, x, adjYMin, z, horizontalWidth, (short) (yMax - adjYMin), debugOverlapColor, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, LodUtil.MAX_MC_LIGHT);
}
// we start the creation of a new face
} }
else else
{ {
// adjacent is transparent, // Otherwise: y < _____ height < yMax
// use datapoint below adjacent for lighting // _______&&: y < depth ______ < yMax
byte belowSkyLight = RenderDataPointUtil.getLightSky(adjBelowPoint); if (debugOverlapColor != 0)
for (int i = adjMinY; i < adjMaxY; i++)
{ {
byte skyLightAtPos = skyLightAtInputPos[i]; builder.addQuadAdj(direction, x, adjYMin, z, horizontalWidth, (short) (adjYMax - adjYMin), debugOverlapColor, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, LodUtil.MAX_MC_LIGHT);
skyLightAtInputPos[i] = (byte) Math.min(belowSkyLight, skyLightAtPos); }
if (firstFace)
{
builder.addQuadAdj(direction, x, adjYMax, z, horizontalWidth, (short) (yMax - adjYMax), color, irisBlockMaterialId,
RenderDataPointUtil.getLightSky(adjPoint), blockLight);
}
else
{
// Now: y < depth < height <= previousAdjDepth < yMax
if (previousAdjDepth == -1)
throw new RuntimeException("Loop error");
if (previousAdjDepth > adjYMax)
{
if (irisBlockMaterialId == EDhApiBlockMaterial.GRASS.index)
{
// this LOD is underneath another, grass will never show here
irisBlockMaterialId = EDhApiBlockMaterial.DIRT.index;
}
builder.addQuadAdj(direction, x, adjYMax, z, horizontalWidth, (short) (previousAdjDepth - adjYMax), color, irisBlockMaterialId,
RenderDataPointUtil.getLightSky(adjPoint), blockLight);
}
previousAdjDepth = -1;
} }
} }
// fill in sky light up to the next DP, // set next top as current depth
// this is done to handle overhangs previousAdjDepth = adjYMin;
byte adjSkyLight = RenderDataPointUtil.getLightSky(adjPoint); firstFace = false;
int adjAboveMinY = RenderDataPointUtil.getYMin(adjAbovePoint); nextTopSkyLight = skyLightTop;
for (int i = adjMaxY; i < adjAboveMinY; i++)
if (adjIndex + 1 < adjColumnView.size() && RenderDataPointUtil.doesDataPointExist(adjColumnView.get(adjIndex + 1)))
{ {
byte skyLightAtPos = skyLightAtInputPos[i]; nextTopSkyLight = RenderDataPointUtil.getLightSky(adjColumnView.get(adjIndex + 1));
skyLightAtInputPos[i] = (byte) Math.min(adjSkyLight, skyLightAtPos);
}
}
//=======================//
// create vertical faces //
//=======================//
boolean inputTransparent = ColorUtil.getAlpha(color) < 255 && LodRenderer.transparencyEnabled;
byte lastSkyLight = skyLightAtInputPos[yMin];
int quadBottomY = yMin;
int quadTopY = -1;
// walk up the sky lights and create a new face
// whenever the light changes to different valid value
for (int i = yMin; i < yMax; i++)
{
byte skyLight = skyLightAtInputPos[i];
if (skyLight != lastSkyLight)
{
// the sky light changed, create the in-progress face
tryAddVerticalFaceWithSkyLightToBuilder(
builder, direction,
x, z, horizontalWidth,
color, irisBlockMaterialId, blockLight,
lastSkyLight, inputTransparent, quadTopY, quadBottomY
);
lastSkyLight = skyLight;
quadBottomY = i;
} }
quadTopY = (i + 1); lastAdjWasTransparent = adjTransparent;
}
// add the in-progress face if present
if (quadTopY != -1)
{
tryAddVerticalFaceWithSkyLightToBuilder(
builder, direction,
x, z, horizontalWidth,
color, irisBlockMaterialId, blockLight,
lastSkyLight, inputTransparent, quadTopY, quadBottomY
);
} }
} }
finally
if (inputAboveAdjLods)
{ {
// clean up the array before the next thread uses it // the input LOD is above all adjacent LODs and won't be affected
// (may be unnecessary since we only work between the yMin-yMax anyway, but is helpful for debugging) // by them, add the vertical quad using the input's lighting and height
Arrays.fill(skyLightAtInputPos, yMin, yMax, SKYLIGHT_EMPTY); builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, color, irisBlockMaterialId, skyLightTop, blockLight);
}
else if (previousAdjDepth != -1)
{
// We need to finish the last quad.
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, (short) (previousAdjDepth - yMin), color, irisBlockMaterialId, nextTopSkyLight, blockLight);
} }
} }
private static void tryAddVerticalFaceWithSkyLightToBuilder(
LodQuadBuilder builder, EDhDirection direction,
short x, short z, short horizontalWidth,
int color, byte irisBlockMaterialId, byte blockLight,
byte lastSkyLight, boolean inputTransparent, int quadTopY, int quadBottomY
)
{
// invalid positions will have a negative skylight
if (lastSkyLight >= 0)
{
// Don't add transparent vertical faces
// unless the adjacent position is empty.
// This is done to prevent walls between water blocks in the ocean.
if (!inputTransparent
|| (lastSkyLight == LodUtil.MAX_MC_LIGHT))
{
// don't add negative/empty height faces
short height = (short) (quadTopY - quadBottomY);
if (height > 0)
{
builder.addQuadAdj(direction, x, (short) quadBottomY, z, horizontalWidth, height, color, irisBlockMaterialId, lastSkyLight, blockLight);
}
}
}
}
} }
@@ -19,23 +19,23 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding; package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam; import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
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.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.render.glObject.GLProxy; import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer; import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer; import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.StatsMap; import com.seibel.distanthorizons.core.util.objects.StatsMap;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod; import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
import com.seibel.distanthorizons.core.util.*;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.Iterator;
import java.util.concurrent.*; import java.util.concurrent.*;
/** /**
@@ -91,7 +91,7 @@ public class ColumnRenderBuffer implements AutoCloseable
/** Should be run on a DH thread. */ /** Should be run on a DH thread. */
public void uploadBuffer(LodQuadBuilder builder, EDhApiGpuUploadMethod gpuUploadMethod) throws InterruptedException public void uploadBuffer(LodQuadBuilder builder, EDhApiGpuUploadMethod gpuUploadMethod) throws InterruptedException
{ {
LodUtil.assertTrue(DhApi.isDhThread(), "Buffer uploading needs to be done on a DH thread to prevent locking up any MC threads."); LodUtil.assertTrue(Thread.currentThread().getName().startsWith(ThreadUtil.THREAD_NAME_PREFIX), "Buffer uploading needs to be done on a DH thread to prevent locking up any MC threads.");
// upload on MC's render thread // upload on MC's render thread
@@ -100,7 +100,7 @@ public class ColumnRenderBuffer implements AutoCloseable
{ {
try try
{ {
this.uploadBuffers(builder, gpuUploadMethod); this.uploadBuffersUsingUploadMethod(builder, gpuUploadMethod);
uploadFuture.complete(null); uploadFuture.complete(null);
} }
catch (InterruptedException e) catch (InterruptedException e)
@@ -126,46 +126,72 @@ public class ColumnRenderBuffer implements AutoCloseable
//LOGGER.warn("Error uploading builder ["+builder+"] synchronously. Error: "+e.getMessage(), e); //LOGGER.warn("Error uploading builder ["+builder+"] synchronously. Error: "+e.getMessage(), e);
} }
} }
private void uploadBuffers(LodQuadBuilder builder, EDhApiGpuUploadMethod method) throws InterruptedException private void uploadBuffersUsingUploadMethod(LodQuadBuilder builder, EDhApiGpuUploadMethod gpuUploadMethod) throws InterruptedException
{ {
// uploading mapped buffers used to be done here, if (gpuUploadMethod.useEarlyMapping)
// however due to a memory leak and complication with the previous code, {
// now we only allow direct uploading. this.uploadBuffersMapped(builder, gpuUploadMethod);
// (There's also insufficient data to state whether mapped buffers are necessary }
// for DH to upload without stuttering the main thread) else
{
this.vbos = makeAndUploadBuffers(builder, method, this.vbos, builder.makeOpaqueVertexBuffers()); this.uploadBuffersDirect(builder, gpuUploadMethod);
this.vbosTransparent = makeAndUploadBuffers(builder, method, this.vbosTransparent, builder.makeTransparentVertexBuffers()); }
this.buffersUploaded = true; this.buffersUploaded = true;
} }
/** This resizes and returns the vbo array if necessary based on the amount of data needed for this area. */
private static GLVertexBuffer[] makeAndUploadBuffers(LodQuadBuilder builder, EDhApiGpuUploadMethod method, GLVertexBuffer[] vbos, ArrayList<ByteBuffer> buffers) throws InterruptedException
private void uploadBuffersMapped(LodQuadBuilder builder, EDhApiGpuUploadMethod method)
{ {
try // opaque vbos //
this.vbos = ColumnRenderBufferBuilder.resizeBuffer(this.vbos, builder.getCurrentNeededOpaqueVertexBufferCount());
for (int i = 0; i < this.vbos.length; i++)
{ {
vbos = resizeBuffer(vbos, buffers.size()); if (this.vbos[i] == null)
uploadBuffersDirect(vbos, buffers, method);
}
finally
{
// all the buffers must be manually freed to prevent memory leaks
if (buffers != null)
{ {
for (ByteBuffer buffer : buffers) this.vbos[i] = new GLVertexBuffer(method.useBufferStorage);
{
MemoryUtil.memFree(buffer);
}
} }
} }
LodQuadBuilder.BufferFiller func = builder.makeOpaqueBufferFiller(method);
for (GLVertexBuffer vbo : this.vbos)
{
func.fill(vbo);
}
// return the array in case it was resized
return vbos; // transparent vbos //
this.vbosTransparent = ColumnRenderBufferBuilder.resizeBuffer(this.vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount());
for (int i = 0; i < this.vbosTransparent.length; i++)
{
if (this.vbosTransparent[i] == null)
{
this.vbosTransparent[i] = new GLVertexBuffer(method.useBufferStorage);
}
}
LodQuadBuilder.BufferFiller transparentFillerFunc = builder.makeTransparentBufferFiller(method);
for (GLVertexBuffer vbo : this.vbosTransparent)
{
transparentFillerFunc.fill(vbo);
}
} }
private static void uploadBuffersDirect(GLVertexBuffer[] vbos, ArrayList<ByteBuffer> byteBuffers, EDhApiGpuUploadMethod method) throws InterruptedException
private void uploadBuffersDirect(LodQuadBuilder builder, EDhApiGpuUploadMethod method) throws InterruptedException
{ {
this.vbos = ColumnRenderBufferBuilder.resizeBuffer(this.vbos, builder.getCurrentNeededOpaqueVertexBufferCount());
uploadBuffersDirect(this.vbos, builder.makeOpaqueVertexBuffers(), method);
this.vbosTransparent = ColumnRenderBufferBuilder.resizeBuffer(this.vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount());
uploadBuffersDirect(this.vbosTransparent, builder.makeTransparentVertexBuffers(), method);
}
private static void uploadBuffersDirect(GLVertexBuffer[] vbos, Iterator<ByteBuffer> iter, EDhApiGpuUploadMethod method) throws InterruptedException
{
long remainingMS = 0;
long MBPerMS = Config.Client.Advanced.GpuBuffers.gpuUploadPerMegabyteInMilliseconds.get();
int vboIndex = 0; int vboIndex = 0;
for (int i = 0; i < byteBuffers.size(); i++) while (iter.hasNext())
{ {
if (vboIndex >= vbos.length) if (vboIndex >= vbos.length)
{ {
@@ -181,13 +207,13 @@ public class ColumnRenderBuffer implements AutoCloseable
GLVertexBuffer vbo = vbos[vboIndex]; GLVertexBuffer vbo = vbos[vboIndex];
ByteBuffer buffer = byteBuffers.get(i); ByteBuffer bb = iter.next();
int size = buffer.limit() - buffer.position(); int size = bb.limit() - bb.position();
try try
{ {
vbo.bind(); vbo.bind();
vbo.uploadBuffer(buffer, size / LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, FULL_SIZED_BUFFER); vbo.uploadBuffer(bb, size / LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, FULL_SIZED_BUFFER);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -196,6 +222,24 @@ public class ColumnRenderBuffer implements AutoCloseable
LOGGER.error("Failed to upload buffer: ", e); LOGGER.error("Failed to upload buffer: ", e);
} }
if (MBPerMS > 0)
{
// upload buffers over an extended period of time
// to hopefully prevent stuttering.
remainingMS += size * MBPerMS;
if (remainingMS >= TimeUnit.NANOSECONDS.convert(1000 / 60, TimeUnit.MILLISECONDS))
{
if (remainingMS > MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS)
{
remainingMS = MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS;
}
Thread.sleep(remainingMS / 1000000, (int) (remainingMS % 1000000));
remainingMS = 0;
}
}
vboIndex++; vboIndex++;
} }
@@ -274,9 +318,9 @@ public class ColumnRenderBuffer implements AutoCloseable
//================// //==============//
// helper methods // // misc methods //
//================// //==============//
/** can be used when debugging */ /** can be used when debugging */
public boolean hasNonNullVbos() { return this.vbos != null || this.vbosTransparent != null; } public boolean hasNonNullVbos() { return this.vbos != null || this.vbosTransparent != null; }
@@ -322,35 +366,6 @@ public class ColumnRenderBuffer implements AutoCloseable
} }
} }
public static GLVertexBuffer[] resizeBuffer(GLVertexBuffer[] vbos, int newSize)
{
if (vbos.length == newSize)
{
return vbos;
}
GLVertexBuffer[] newVbos = new GLVertexBuffer[newSize];
System.arraycopy(vbos, 0, newVbos, 0, Math.min(vbos.length, newSize));
if (newSize < vbos.length)
{
for (int i = newSize; i < vbos.length; i++)
{
if (vbos[i] != null)
{
vbos[i].close();
}
}
}
return newVbos;
}
//================//
// base overrides //
//================//
/** /**
* This method is called when object is no longer in use. * This method is called when object is no longer in use.
* Called either after uploadBuffers() returned false (On buffer Upload * Called either after uploadBuffers() returned false (On buffer Upload
@@ -19,7 +19,6 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding; package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering; import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.core.enums.EDhDirection; import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
@@ -27,16 +26,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.ConfigBasedLogger; import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
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.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.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.util.ColorUtil; 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.util.RenderDataPointUtil; import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException; import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@@ -61,21 +59,21 @@ public class ColumnRenderBufferBuilder
// vbo building // // vbo building //
//==============// //==============//
public static CompletableFuture<LodQuadBuilder> buildBuffersAsync( public static CompletableFuture<ColumnRenderBuffer> buildAndUploadBuffersAsync(
IDhClientLevel clientLevel, IDhClientLevel clientLevel,
ColumnRenderSource renderSource, ColumnRenderSource[] adjData, boolean[] isSameDetailLevel ColumnRenderSource renderSource, ColumnRenderSource[] adjData)
)
{ {
ThreadPoolExecutor bufferBuilderExecutor = ThreadPoolUtil.getBufferBuilderExecutor(); ThreadPoolExecutor bufferBuilderExecutor = ThreadPoolUtil.getBufferBuilderExecutor();
if (bufferBuilderExecutor == null || bufferBuilderExecutor.isTerminated()) ThreadPoolExecutor bufferUploaderExecutor = ThreadPoolUtil.getBufferUploaderExecutor();
if ((bufferBuilderExecutor == null || bufferBuilderExecutor.isTerminated()) ||
(bufferUploaderExecutor == null || bufferUploaderExecutor.isTerminated()))
{ {
// one or more of the thread pools has been shut down // one or more of the thread pools has been shut down
CompletableFuture<LodQuadBuilder> future = new CompletableFuture<>(); CompletableFuture<ColumnRenderBuffer> future = new CompletableFuture<>();
future.cancel(true); future.cancel(true);
return future; return future;
} }
try try
{ {
return CompletableFuture.supplyAsync(() -> return CompletableFuture.supplyAsync(() ->
@@ -83,8 +81,16 @@ public class ColumnRenderBufferBuilder
try try
{ {
boolean enableTransparency = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled; boolean enableTransparency = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled;
long builderStartTime = System.currentTimeMillis();
LodQuadBuilder builder = new LodQuadBuilder(enableTransparency, clientLevel.getClientLevelWrapper()); LodQuadBuilder builder = new LodQuadBuilder(enableTransparency, clientLevel.getClientLevelWrapper());
makeLodRenderData(builder, renderSource, clientLevel, adjData, isSameDetailLevel); makeLodRenderData(builder, renderSource, adjData);
long builderEndTime = System.currentTimeMillis();
long buildMs = builderEndTime - builderStartTime;
//LOGGER.debug("RenderRegion end QuadBuild @ " + renderSource.pos + " took: " + buildMs);
return builder; return builder;
} }
catch (UncheckedInterruptedException e) catch (UncheckedInterruptedException e)
@@ -93,42 +99,11 @@ public class ColumnRenderBufferBuilder
} }
catch (Throwable e3) catch (Throwable e3)
{ {
LOGGER.error("LodNodeBufferBuilder was unable to build quads for pos ["+DhSectionPos.toString(renderSource.pos)+"], error: ["+ e3.getMessage()+"].", e3); LOGGER.error("\"LodNodeBufferBuilder\" was unable to build quads: ", e3);
throw e3; throw e3;
} }
}, bufferBuilderExecutor); }, bufferBuilderExecutor)
} .thenApplyAsync((quadBuilder) ->
catch (RejectedExecutionException ignore)
{
// the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back
CompletableFuture<LodQuadBuilder> future = new CompletableFuture<>();
future.cancel(true);
return future;
}
}
/** @link adjData should be null for adjacent sections that cross detail level boundaries */
public static CompletableFuture<ColumnRenderBuffer> uploadBuffersAsync(
IDhClientLevel clientLevel,
ColumnRenderSource renderSource,
LodQuadBuilder quadBuilder
)
{
// TODO put into a single future/thread so it can be easily canceled
ThreadPoolExecutor bufferUploaderExecutor = ThreadPoolUtil.getBufferUploaderExecutor();
if (bufferUploaderExecutor == null || bufferUploaderExecutor.isTerminated())
{
// one or more of the thread pools has been shut down
CompletableFuture<ColumnRenderBuffer> future = new CompletableFuture<>();
future.cancel(true);
return future;
}
try
{
return CompletableFuture.supplyAsync(() ->
{ {
try try
{ {
@@ -136,15 +111,8 @@ public class ColumnRenderBufferBuilder
try try
{ {
buffer.uploadBuffer(quadBuilder, GLProxy.getInstance().getGpuUploadMethod()); buffer.uploadBuffer(quadBuilder, GLProxy.getInstance().getGpuUploadMethod());
if (buffer.buffersUploaded) LodUtil.assertTrue(buffer.buffersUploaded);
{ return buffer;
return buffer;
}
else
{
buffer.close();
return null;
}
} }
catch (Exception e) catch (Exception e)
{ {
@@ -158,38 +126,35 @@ public class ColumnRenderBufferBuilder
} }
catch (Throwable e3) catch (Throwable e3)
{ {
LOGGER.error("LodNodeBufferBuilder was unable to upload buffer for pos ["+DhSectionPos.toString(renderSource.pos)+"], error: [" + e3.getMessage() + "].", e3); LOGGER.error("LodNodeBufferBuilder was unable to upload buffer: " + e3.getMessage(), e3);
throw e3; throw e3;
} }
}, bufferUploaderExecutor); }, bufferUploaderExecutor);
} }
catch (RejectedExecutionException ignore) catch (RejectedExecutionException ignore)
{ {
// shouldn't happen, but just in case // the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back
CompletableFuture<ColumnRenderBuffer> future = new CompletableFuture<>(); CompletableFuture<ColumnRenderBuffer> future = new CompletableFuture<>();
future.cancel(true); future.cancel(true);
return future; return future;
} }
} }
private static void makeLodRenderData( private static void makeLodRenderData(LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, ColumnRenderSource[] adjRegions)
LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, IDhClientLevel clientLevel,
ColumnRenderSource[] adjRegions, boolean[] isSameDetailLevel)
{ {
//=============// // Variable initialization
// debug check // EDhApiDebugRendering debugMode = Config.Client.Advanced.Debugging.debugRendering.get();
//=============//
// can be used to limit which section positions are build and thus, rendered // can be used to limit which section positions are build and thus, rendered
// useful when debugging a specific section // useful when debugging a specific section
boolean columnBuilderDebugEnabled = Config.Client.Advanced.Debugging.columnBuilderDebugEnable.get(); boolean enableColumnBufferLimit = Config.Client.Advanced.Debugging.columnBuilderDebugEnable.get();
if (columnBuilderDebugEnabled) if (enableColumnBufferLimit)
{ {
if (DhSectionPos.getDetailLevel(renderSource.pos) == Config.Client.Advanced.Debugging.columnBuilderDebugDetailLevel.get() if (DhSectionPos.getDetailLevel(renderSource.pos) == Config.Client.Advanced.Debugging.columnBuilderDebugDetailLevel.get()
&& DhSectionPos.getX(renderSource.pos) == Config.Client.Advanced.Debugging.columnBuilderDebugXPos.get() && DhSectionPos.getX(renderSource.pos) == Config.Client.Advanced.Debugging.columnBuilderDebugXPos.get()
&& DhSectionPos.getZ(renderSource.pos) == Config.Client.Advanced.Debugging.columnBuilderDebugZPos.get()) && DhSectionPos.getZ(renderSource.pos) == Config.Client.Advanced.Debugging.columnBuilderDebugZPos.get())
{ {
int breakpoint = 0; int test = 0;
} }
else else
{ {
@@ -197,22 +162,24 @@ public class ColumnRenderBufferBuilder
} }
} }
byte detailLevel = renderSource.getDataDetailLevel();
for (int x = 0; x < ColumnRenderSource.SECTION_SIZE; x++)
//===================//
// build each column //
//===================//
byte thisDetailLevel = renderSource.getDataDetailLevel();
for (int relX = 0; relX < ColumnRenderSource.SECTION_SIZE; relX++)
{ {
for (int relZ = 0; relZ < ColumnRenderSource.SECTION_SIZE; relZ++) for (int z = 0; z < ColumnRenderSource.SECTION_SIZE; z++)
{ {
// stop the builder if requested // TODO make a config for this
// can be uncommented to limit the buffer building to a specific
// relative position in this section.
// useful for debugging a single column's rendering
// if (x != 0 || (z != 0 && z != 1))
// {
// continue;
// }
UncheckedInterruptedException.throwIfInterrupted(); UncheckedInterruptedException.throwIfInterrupted();
// ignore empty/null columns ColumnArrayView columnRenderData = renderSource.getVerticalDataPointView(x, z);
ColumnArrayView columnRenderData = renderSource.getVerticalDataPointView(relX, relZ);
if (columnRenderData.size() == 0 if (columnRenderData.size() == 0
|| !RenderDataPointUtil.doesDataPointExist(columnRenderData.get(0)) || !RenderDataPointUtil.doesDataPointExist(columnRenderData.get(0))
|| RenderDataPointUtil.isVoid(columnRenderData.get(0))) || RenderDataPointUtil.isVoid(columnRenderData.get(0)))
@@ -220,66 +187,43 @@ public class ColumnRenderBufferBuilder
continue; continue;
} }
ColumnRenderSource.DebugSourceFlag debugSourceFlag = renderSource.debugGetFlag(x, z);
ColumnArrayView[][] adjColumnViews = new ColumnArrayView[4][];
// We extract the adj data in the four cardinal direction
// we first reset the adjShadeDisabled. This is used to disable the shade on the
// border when we have transparent block like water or glass
// to avoid having a "darker border" underground
// Arrays.fill(adjShadeDisabled, false);
//=============// // We check every adj block in each direction
// debug limit //
//=============//
// can be used to limit the buffer building to a specific relative position. // If the adj block is rendered in the same region and with same detail
// useful for debugging a single column // and is positioned in a place that is not going to be rendered by vanilla game
if (columnBuilderDebugEnabled) // then we can set this position as adj
{ // We avoid cases where the adjPosition is in player chunk while the position is
int wantedX = Config.Client.Advanced.Debugging.columnBuilderDebugXRow.get(); // not
if (wantedX >= 0 && relX != wantedX) // to always have a wall underwater
{
continue;
}
int wantedZ = Config.Client.Advanced.Debugging.columnBuilderDebugZRow.get();
if (wantedZ >= 0 && relZ != wantedZ)
{
continue;
}
}
//==================================//
// get adjacent render data columns //
//==================================//
ColumnArrayView[] adjColumnViews = new ColumnArrayView[EDhDirection.ADJ_DIRECTIONS.length];
for (EDhDirection lodDirection : EDhDirection.ADJ_DIRECTIONS) for (EDhDirection lodDirection : EDhDirection.ADJ_DIRECTIONS)
{ {
try try
{ {
int xAdj = relX + lodDirection.getNormal().x; int xAdj = x + lodDirection.getNormal().x;
int zAdj = relZ + lodDirection.getNormal().z; int zAdj = z + lodDirection.getNormal().z;
boolean isCrossRenderSourceBoundary = boolean isCrossRegionBoundary =
(xAdj < 0 || xAdj >= ColumnRenderSource.SECTION_SIZE) || (xAdj < 0 || xAdj >= ColumnRenderSource.SECTION_SIZE) ||
(zAdj < 0 || zAdj >= ColumnRenderSource.SECTION_SIZE); (zAdj < 0 || zAdj >= ColumnRenderSource.SECTION_SIZE);
ColumnRenderSource adjRenderSource; ColumnRenderSource adjRenderSource;
byte adjDetailLevel; byte adjDetailLevel;
//we check if the detail of the adjPos is equal to the correct one (region border fix)
//or if the detail is wrong by 1 value (region+circle border fix)
//=========================// if (isCrossRegionBoundary)
// get the adjacent render //
// source if present //
//=========================//
if (!isCrossRenderSourceBoundary)
{ {
// the adjacent position is inside this same render source //we compute at which detail that position should be rendered
adjRenderSource = renderSource;
adjDetailLevel = thisDetailLevel;
}
else
{
// the adjacent position is outside this render source
// skip empty sections
adjRenderSource = adjRegions[lodDirection.ordinal() - 2]; adjRenderSource = adjRegions[lodDirection.ordinal() - 2];
if (adjRenderSource == null) if (adjRenderSource == null)
{ {
@@ -287,70 +231,67 @@ public class ColumnRenderBufferBuilder
} }
adjDetailLevel = adjRenderSource.getDataDetailLevel(); adjDetailLevel = adjRenderSource.getDataDetailLevel();
if (adjDetailLevel == thisDetailLevel) if (adjDetailLevel != detailLevel)
{
//TODO: Implement this
}
else
{ {
// if the adjacent position is outside this render source,
// wrap the position around so it's inside the adjacent source
if (xAdj < 0) if (xAdj < 0)
{
xAdj += ColumnRenderSource.SECTION_SIZE; xAdj += ColumnRenderSource.SECTION_SIZE;
}
if (xAdj >= ColumnRenderSource.SECTION_SIZE)
{
xAdj -= ColumnRenderSource.SECTION_SIZE;
}
if (zAdj < 0) if (zAdj < 0)
{
zAdj += ColumnRenderSource.SECTION_SIZE; zAdj += ColumnRenderSource.SECTION_SIZE;
}
if (xAdj >= ColumnRenderSource.SECTION_SIZE)
xAdj -= ColumnRenderSource.SECTION_SIZE;
if (zAdj >= ColumnRenderSource.SECTION_SIZE) if (zAdj >= ColumnRenderSource.SECTION_SIZE)
{
zAdj -= ColumnRenderSource.SECTION_SIZE; zAdj -= ColumnRenderSource.SECTION_SIZE;
}
} }
} }
else
{
adjRenderSource = renderSource;
adjDetailLevel = detailLevel;
}
if (adjDetailLevel < detailLevel - 1 || adjDetailLevel > detailLevel + 1)
{
continue;
}
if (adjDetailLevel == detailLevel || adjDetailLevel > detailLevel)
//========================// {
// get the adjacent views // adjColumnViews[lodDirection.ordinal() - 2] = new ColumnArrayView[1];
//========================// adjColumnViews[lodDirection.ordinal() - 2][0] = adjRenderSource.getVerticalDataPointView(xAdj, zAdj);
}
// the old logic handled additional cases, but they never appeared to fire, else
// so just these two cases should be fine {
LodUtil.assertTrue(adjDetailLevel == thisDetailLevel || adjDetailLevel > thisDetailLevel); adjColumnViews[lodDirection.ordinal() - 2] = new ColumnArrayView[2];
adjColumnViews[lodDirection.ordinal() - 2][0] = adjRenderSource.getVerticalDataPointView(xAdj, zAdj);
adjColumnViews[lodDirection.ordinal() - 2] = adjRenderSource.getVerticalDataPointView(xAdj, zAdj); adjColumnViews[lodDirection.ordinal() - 2][1] = adjRenderSource.getVerticalDataPointView(
xAdj + (lodDirection.getAxis() == EDhDirection.Axis.X ? 0 : 1),
zAdj + (lodDirection.getAxis() == EDhDirection.Axis.Z ? 0 : 1));
}
} }
catch (RuntimeException e) catch (RuntimeException e)
{ {
EVENT_LOGGER.warn("Failed to get adj data for relative pos: [" + thisDetailLevel + ":" + relX + "," + relZ + "] at [" + lodDirection + "], Error: "+e.getMessage(), e); EVENT_LOGGER.warn("Failed to get adj data for [" + detailLevel + ":" + x + "," + z + "] at [" + lodDirection + "], Error: "+e.getMessage(), e);
} }
} // for adjacent directions } // for adjacent directions
//==========================//
// build this render column //
//==========================//
ColumnRenderSource.DebugSourceFlag debugSourceFlag = renderSource.debugGetFlag(relX, relZ);
// We render every vertical lod present in this position // We render every vertical lod present in this position
// We only stop when we find a block that is void or non-existing block // We only stop when we find a block that is void or non-existing block
for (int i = 0; i < columnRenderData.size(); i++) for (int i = 0; i < columnRenderData.size(); i++)
{ {
// TODO make a config for this
// 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.columnBuilderDebugEnable.get()) // if (i != 0)
{ // {
int wantedColumnIndex = Config.Client.Advanced.Debugging.columnBuilderDebugColumnIndex.get(); // continue;
if (wantedColumnIndex >= 0 && i != wantedColumnIndex) // }
{
continue;
}
}
long data = columnRenderData.get(i); long data = columnRenderData.get(i);
// 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
@@ -363,12 +304,8 @@ public class ColumnRenderBufferBuilder
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;
addLodToBuffer( CubicLodTemplate.addLodToBuffer(data, topDataPoint, bottomDataPoint, adjColumnViews, detailLevel,
clientLevel, x, z, quadBuilder, debugMode, debugSourceFlag);
data, topDataPoint, bottomDataPoint,
adjColumnViews, isSameDetailLevel,
thisDetailLevel, relX, relZ,
quadBuilder, debugSourceFlag);
} }
}// for z }// for z
@@ -376,147 +313,33 @@ public class ColumnRenderBufferBuilder
quadBuilder.finalizeData(); quadBuilder.finalizeData();
} }
private static void addLodToBuffer(
IDhClientLevel clientLevel,
long data, long topData, long bottomData,
ColumnArrayView[] adjColumnViews, boolean[] isSameDetailLevel, //=================//
byte detailLevel, int renderSourceOffsetPosX, int renderSourceOffsetPosZ, // vbo interaction //
LodQuadBuilder quadBuilder, ColumnRenderSource.DebugSourceFlag debugSource) //=================//
public static GLVertexBuffer[] resizeBuffer(GLVertexBuffer[] vbos, int newSize)
{ {
long sectionPos = DhSectionPos.encode(detailLevel, renderSourceOffsetPosX, renderSourceOffsetPosZ); if (vbos.length == newSize)
short width = (short) BitShiftUtil.powerOfTwo(detailLevel);
short x = (short) DhSectionPos.getMinCornerBlockX(sectionPos);
short yMin = RenderDataPointUtil.getYMin(data);
short z = (short) DhSectionPos.getMinCornerBlockZ(sectionPos);
short ySize = (short) (RenderDataPointUtil.getYMax(data) - yMin);
if (ySize == 0)
{ {
return; return vbos;
}
else if (ySize < 0)
{
throw new IllegalArgumentException("Negative y size for the data! Data: [" + RenderDataPointUtil.toString(data) + "].");
} }
byte blockMaterialId = RenderDataPointUtil.getBlockMaterialId(data); GLVertexBuffer[] newVbos = new GLVertexBuffer[newSize];
System.arraycopy(vbos, 0, newVbos, 0, Math.min(vbos.length, newSize));
if (newSize < vbos.length)
int color;
boolean fullBright = false;
EDhApiDebugRendering debugging = Config.Client.Advanced.Debugging.debugRendering.get();
switch (debugging)
{ {
case OFF: for (int i = newSize; i < vbos.length; i++)
{ {
float saturationMultiplier = Config.Client.Advanced.Graphics.AdvancedGraphics.saturationMultiplier.get().floatValue(); if (vbos[i] != null)
float brightnessMultiplier = Config.Client.Advanced.Graphics.AdvancedGraphics.brightnessMultiplier.get().floatValue();
if (saturationMultiplier == 1.0 && brightnessMultiplier == 1.0)
{ {
color = RenderDataPointUtil.getColor(data); vbos[i].close();
} }
else
{
float[] ahsv = ColorUtil.argbToAhsv(RenderDataPointUtil.getColor(data));
color = ColorUtil.ahsvToArgb(ahsv[0], ahsv[1], ahsv[2] * saturationMultiplier, ahsv[3] * brightnessMultiplier);
}
break;
} }
case SHOW_DETAIL:
{
color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detailLevel];
fullBright = true;
break;
}
case SHOW_BLOCK_MATERIAL:
{
switch (EDhApiBlockMaterial.getFromIndex(blockMaterialId))
{
case UNKNOWN:
case AIR: // shouldn't normally be rendered, but just in case
color = ColorUtil.HOT_PINK;
break;
case LEAVES:
color = ColorUtil.DARK_GREEN;
break;
case STONE:
color = ColorUtil.GRAY;
break;
case WOOD:
color = ColorUtil.BROWN;
break;
case METAL:
color = ColorUtil.DARK_GRAY;
break;
case DIRT:
color = ColorUtil.LIGHT_BROWN;
break;
case LAVA:
color = ColorUtil.ORANGE;
break;
case DEEPSLATE:
color = ColorUtil.BLACK;
break;
case SNOW:
color = ColorUtil.WHITE;
break;
case SAND:
color = ColorUtil.TAN;
break;
case TERRACOTTA:
color = ColorUtil.DARK_ORANGE;
break;
case NETHER_STONE:
color = ColorUtil.DARK_RED;
break;
case WATER:
color = ColorUtil.BLUE;
break;
case GRASS:
color = ColorUtil.GREEN;
break;
case ILLUMINATED:
color = ColorUtil.YELLOW;
break;
default:
// undefined color
color = ColorUtil.CYAN;
break;
}
fullBright = true;
break;
}
case SHOW_OVERLAPPING_QUADS:
{
color = ColorUtil.WHITE;
fullBright = true;
break;
}
case SHOW_RENDER_SOURCE_FLAG:
{
color = debugSource == null ? ColorUtil.RED : debugSource.color;
fullBright = true;
break;
}
default:
throw new IllegalArgumentException("Unknown debug mode: " + debugging);
} }
return newVbos;
ColumnBox.addBoxQuadsToBuilder(
quadBuilder, clientLevel,
width, ySize, width,
x, yMin, z,
color,
blockMaterialId,
RenderDataPointUtil.getLightSky(data),
fullBright ? 15 : RenderDataPointUtil.getLightBlock(data),
topData, bottomData, adjColumnViews, isSameDetailLevel);
} }
} }
@@ -0,0 +1,153 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.bufferBuilding;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
/**
* Builds LODs as rectangular prisms.
*
* @author James Seibel
* @version 2022-1-2
*/
public class CubicLodTemplate
{
public static void addLodToBuffer(
long data, long topData, long bottomData, ColumnArrayView[][] adjColumnViews,
byte detailLevel, int offsetPosX, int offsetOosZ, LodQuadBuilder quadBuilder,
EDhApiDebugRendering debugging, ColumnRenderSource.DebugSourceFlag debugSource)
{
DhLodPos blockOffsetPos = new DhLodPos(detailLevel, offsetPosX, offsetOosZ).convertToDetailLevel(LodUtil.BLOCK_DETAIL_LEVEL);
short width = (short) BitShiftUtil.powerOfTwo(detailLevel);
short x = (short) blockOffsetPos.x;
short yMin = RenderDataPointUtil.getYMin(data);
short z = (short) (short) blockOffsetPos.z;
short ySize = (short) (RenderDataPointUtil.getYMax(data) - yMin);
if (ySize == 0)
{
return;
}
else if (ySize < 0)
{
throw new IllegalArgumentException("Negative y size for the data! Data: " + RenderDataPointUtil.toString(data));
}
byte blockMaterialId = RenderDataPointUtil.getBlockMaterialId(data);
int color;
boolean fullBright = false;
switch (debugging)
{
case OFF:
{
float saturationMultiplier = Config.Client.Advanced.Graphics.AdvancedGraphics.saturationMultiplier.get().floatValue();
float brightnessMultiplier = Config.Client.Advanced.Graphics.AdvancedGraphics.brightnessMultiplier.get().floatValue();
if (saturationMultiplier == 1.0 && brightnessMultiplier == 1.0)
{
color = RenderDataPointUtil.getColor(data);
}
else
{
float[] ahsv = ColorUtil.argbToAhsv(RenderDataPointUtil.getColor(data));
color = ColorUtil.ahsvToArgb(ahsv[0], ahsv[1], ahsv[2] * saturationMultiplier, ahsv[3] * brightnessMultiplier);
//ApiShared.LOGGER.info("Raw color:[{}], AHSV:{}, Out color:[{}]",
// ColorUtil.toString(DataPointUtil.getColor(data)),
// ahsv, ColorUtil.toString(color));
}
break;
}
case SHOW_DETAIL:
{
color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detailLevel];
fullBright = true;
break;
}
case SHOW_BLOCK_MATERIAL:
{
color = switch (EDhApiBlockMaterial.getFromIndex(blockMaterialId))
{
case UNKNOWN, AIR -> // shouldn't normally be rendered, but just in case
ColorUtil.HOT_PINK;
case LEAVES -> ColorUtil.DARK_GREEN;
case STONE -> ColorUtil.GRAY;
case WOOD -> ColorUtil.BROWN;
case METAL -> ColorUtil.DARK_GRAY;
case DIRT -> ColorUtil.LIGHT_BROWN;
case LAVA -> ColorUtil.ORANGE;
case DEEPSLATE -> ColorUtil.BLACK;
case SNOW -> ColorUtil.WHITE;
case SAND -> ColorUtil.TAN;
case TERRACOTTA -> ColorUtil.DARK_ORANGE;
case NETHER_STONE -> ColorUtil.DARK_RED;
case WATER -> ColorUtil.BLUE;
case GRASS -> ColorUtil.GREEN;
case ILLUMINATED -> ColorUtil.YELLOW;
default ->
// undefined color
ColorUtil.CYAN;
};
fullBright = true;
break;
}
case SHOW_OVERLAPPING_QUADS:
{
color = ColorUtil.WHITE;
fullBright = true;
break;
}
case SHOW_RENDER_SOURCE_FLAG:
{
color = debugSource == null ? ColorUtil.RED : debugSource.color;
fullBright = true;
break;
}
default:
throw new IllegalArgumentException("Unknown debug mode: " + debugging);
}
ColumnBox.addBoxQuadsToBuilder(
quadBuilder, // buffer
width, ySize, width, // setWidth
x, yMin, z, // setOffset
color, // setColor
blockMaterialId, // irisBlockMaterialId
RenderDataPointUtil.getLightSky(data), // setSkyLights
fullBright ? 15 : RenderDataPointUtil.getLightBlock(data), // setBlockLights
topData, bottomData, adjColumnViews); // setAdjData
}
}
@@ -38,7 +38,8 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftCli
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.util.MathUtil; import com.seibel.distanthorizons.coreapi.util.MathUtil;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.lwjgl.system.MemoryUtil;
//TODO: Recheck this class for refactoring
/** /**
* Used to create the quads before they are converted to render-able buffers. <br><br> * Used to create the quads before they are converted to render-able buffers. <br><br>
@@ -50,6 +51,11 @@ public class LodQuadBuilder
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
@Deprecated
public final boolean skipQuadsWithZeroSkylight;
@Deprecated
public final short skyLightCullingBelow;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private final ArrayList<BufferQuad>[] opaqueQuads = (ArrayList<BufferQuad>[]) new ArrayList[6]; private final ArrayList<BufferQuad>[] opaqueQuads = (ArrayList<BufferQuad>[]) new ArrayList[6];
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -128,6 +134,8 @@ public class LodQuadBuilder
this.transparentQuads[i] = new ArrayList<>(); this.transparentQuads[i] = new ArrayList<>();
} }
this.skipQuadsWithZeroSkylight = false;
this.skyLightCullingBelow = 0;
this.clientLevelWrapper = clientLevelWrapper; this.clientLevelWrapper = clientLevelWrapper;
this.debugRenderingMode = Config.Client.Advanced.Debugging.debugRendering.get(); this.debugRenderingMode = Config.Client.Advanced.Debugging.debugRendering.get();
@@ -151,12 +159,17 @@ public class LodQuadBuilder
throw new IllegalArgumentException("addQuadAdj() is only for adj direction! Not UP or Down!"); throw new IllegalArgumentException("addQuadAdj() is only for adj direction! Not UP or Down!");
} }
if (this.skipQuadsWithZeroSkylight && skyLight == 0 && y + widthNorthSouthOrUpDown < this.skyLightCullingBelow)
{
return;
}
BufferQuad quad = new BufferQuad(x, y, z, widthEastWest, widthNorthSouthOrUpDown, color, irisBlockMaterialId, skyLight, blockLight, dir); BufferQuad quad = new BufferQuad(x, y, z, widthEastWest, widthNorthSouthOrUpDown, color, irisBlockMaterialId, skyLight, blockLight, dir);
ArrayList<BufferQuad> quadList = (this.doTransparency && ColorUtil.getAlpha(color) < 255) ? this.transparentQuads[dir.ordinal()] : this.opaqueQuads[dir.ordinal()]; ArrayList<BufferQuad> quadList = (this.doTransparency && ColorUtil.getAlpha(color) < 255) ? this.transparentQuads[dir.ordinal()] : this.opaqueQuads[dir.ordinal()];
if (!quadList.isEmpty() && if (!quadList.isEmpty() &&
( (
quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.EastWest) quadList.getLast().tryMerge(quad, BufferMergeDirectionEnum.EastWest)
|| quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.NorthSouthOrUpDown)) || quadList.getLast().tryMerge(quad, BufferMergeDirectionEnum.NorthSouthOrUpDown))
) )
{ {
this.premergeCount++; this.premergeCount++;
@@ -169,6 +182,12 @@ public class LodQuadBuilder
// XZ // XZ
public void addQuadUp(short x, short maxY, short z, short widthEastWest, short widthNorthSouthOrUpDown, int color, byte irisBlockMaterialId, byte skylight, byte blocklight) // TODO argument names are wrong public void addQuadUp(short x, short maxY, short z, short widthEastWest, short widthNorthSouthOrUpDown, int color, byte irisBlockMaterialId, byte skylight, byte blocklight) // TODO argument names are wrong
{ {
// cave culling
if (this.skipQuadsWithZeroSkylight && skylight == 0 && maxY < this.skyLightCullingBelow)
{
return;
}
BufferQuad quad = new BufferQuad(x, maxY, z, widthEastWest, widthNorthSouthOrUpDown, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.UP); BufferQuad quad = new BufferQuad(x, maxY, z, widthEastWest, widthNorthSouthOrUpDown, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.UP);
boolean isTransparent = (this.doTransparency && ColorUtil.getAlpha(color) < 255); boolean isTransparent = (this.doTransparency && ColorUtil.getAlpha(color) < 255);
ArrayList<BufferQuad> quadList = isTransparent ? this.transparentQuads[EDhDirection.UP.ordinal()] : this.opaqueQuads[EDhDirection.UP.ordinal()]; ArrayList<BufferQuad> quadList = isTransparent ? this.transparentQuads[EDhDirection.UP.ordinal()] : this.opaqueQuads[EDhDirection.UP.ordinal()];
@@ -177,8 +196,8 @@ public class LodQuadBuilder
// attempt to merge this quad with adjacent ones // attempt to merge this quad with adjacent ones
if (!quadList.isEmpty() && if (!quadList.isEmpty() &&
( (
quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.EastWest) quadList.getLast().tryMerge(quad, BufferMergeDirectionEnum.EastWest)
|| quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.NorthSouthOrUpDown)) || quadList.getLast().tryMerge(quad, BufferMergeDirectionEnum.NorthSouthOrUpDown))
) )
{ {
this.premergeCount++; this.premergeCount++;
@@ -190,13 +209,15 @@ public class LodQuadBuilder
public void addQuadDown(short x, short y, short z, short width, short wz, 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)
{ {
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
BufferQuad quad = new BufferQuad(x, y, z, width, wz, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.DOWN); BufferQuad quad = new BufferQuad(x, y, z, width, wz, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.DOWN);
ArrayList<BufferQuad> qs = (doTransparency && ColorUtil.getAlpha(color) < 255) ArrayList<BufferQuad> qs = (doTransparency && ColorUtil.getAlpha(color) < 255)
? transparentQuads[EDhDirection.DOWN.ordinal()] : opaqueQuads[EDhDirection.DOWN.ordinal()]; ? transparentQuads[EDhDirection.DOWN.ordinal()] : opaqueQuads[EDhDirection.DOWN.ordinal()];
if (!qs.isEmpty() if (!qs.isEmpty() &&
&& (qs.get(qs.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.EastWest) (qs.getLast().tryMerge(quad, BufferMergeDirectionEnum.EastWest)
|| qs.get(qs.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.NorthSouthOrUpDown)) || qs.getLast().tryMerge(quad, BufferMergeDirectionEnum.NorthSouthOrUpDown))
) )
{ {
premergeCount++; premergeCount++;
return; return;
@@ -206,124 +227,10 @@ public class LodQuadBuilder
//=================//
// data finalizing //
//=================//
/** runs any final data cleanup, merging, etc. */
public void finalizeData() { this.mergeQuads(); }
/** Uses Greedy meshing to merge this builder's Quads. */
public void mergeQuads()
{
long mergeCount = 0; // can be used for debugging
long preQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount();
if (preQuadsCount <= 1)
{
return;
}
for (int directionIndex = 0; directionIndex < 6; directionIndex++)
{
mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.EastWest);
if (this.doTransparency)
{
mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.EastWest);
}
// only run the second merge if the face is the top or bottom
if (directionIndex == EDhDirection.UP.ordinal() || directionIndex == EDhDirection.DOWN.ordinal())
{
mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
if (this.doTransparency)
{
mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
}
}
}
//long postQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount();
//LOGGER.trace("Merged "+mergeCount+"/"+preQuadsCount+"("+(mergeCount / (double) preQuadsCount)+") quads");
}
/** Merges all of this builder's quads for the given directionIndex (up, down, left, etc.) in the given direction */
private static long mergeQuadsInternal(ArrayList<BufferQuad>[] list, int directionIndex, BufferMergeDirectionEnum mergeDirection)
{
if (list[directionIndex].size() <= 1)
return 0;
list[directionIndex].sort((objOne, objTwo) -> objOne.compare(objTwo, mergeDirection));
long mergeCount = 0;
ListIterator<BufferQuad> iter = list[directionIndex].listIterator();
BufferQuad currentQuad = iter.next();
while (iter.hasNext())
{
BufferQuad nextQuad = iter.next();
if (currentQuad.tryMerge(nextQuad, mergeDirection))
{
// merge successful, attempt to merge the next quad
mergeCount++;
iter.set(null);
}
else
{
// merge fail, move on to the next quad
currentQuad = nextQuad;
}
}
list[directionIndex].removeIf(Objects::isNull);
return mergeCount;
}
//==============// //==============//
// buffer setup // // add vertices //
//==============// //==============//
public ArrayList<ByteBuffer> makeOpaqueVertexBuffers() { return this.makeVertexBuffers(this.opaqueQuads); }
public ArrayList<ByteBuffer> makeTransparentVertexBuffers() { return this.makeVertexBuffers(this.transparentQuads); }
private ArrayList<ByteBuffer> makeVertexBuffers(ArrayList<BufferQuad>[] quadList)
{
ArrayList<ByteBuffer> byteBufferList = new ArrayList<>(3);
ByteBuffer buffer = null;
for (int directionIndex = 0; directionIndex < 6; directionIndex++)
{
// ignore empty directions
if (quadList[directionIndex].isEmpty())
{
continue;
}
// put all the quads in this direction into the buffer
for (int quadIndex = 0; quadIndex < quadList[directionIndex].size(); quadIndex++)
{
// if this is the first iteration or the buffer is full,
// create a new buffer
if (buffer == null || !buffer.hasRemaining())
{
buffer = MemoryUtil.memAlloc(ColumnRenderBuffer.FULL_SIZED_BUFFER);
byteBufferList.add(buffer);
}
this.putQuad(buffer, quadList[directionIndex].get(quadIndex));
}
}
// rewind all the buffers so they can be read from
for (int i = 0; i < byteBufferList.size(); i++)
{
buffer = byteBufferList.get(i);
buffer.limit(buffer.position());
buffer.rewind();
}
return byteBufferList;
}
private void putQuad(ByteBuffer bb, BufferQuad quad) private void putQuad(ByteBuffer bb, BufferQuad quad)
{ {
int[][] quadBase = DIRECTION_VERTEX_IBO_QUAD[quad.direction.ordinal()]; int[][] quadBase = DIRECTION_VERTEX_IBO_QUAD[quad.direction.ordinal()];
@@ -381,10 +288,10 @@ public class LodQuadBuilder
if (quad.direction.getAxis().isHorizontal() || quad.direction == EDhDirection.DOWN) if (quad.direction.getAxis().isHorizontal() || quad.direction == EDhDirection.DOWN)
{ {
if (this.grassSideRenderingMode == EDhApiGrassSideRendering.AS_DIRT if (this.grassSideRenderingMode == EDhApiGrassSideRendering.AS_DIRT
// if we want the color to fade, only apply the dirt color to the bottom vertices // if we want the color to fade, only apply the dirt color to the bottom vertices
|| (this.grassSideRenderingMode == EDhApiGrassSideRendering.FADE_TO_DIRT && quadBase[i][1] == 0) || (this.grassSideRenderingMode == EDhApiGrassSideRendering.FADE_TO_DIRT && quadBase[i][1] == 0)
// always render the bottom as dirt // always render the bottom as dirt
|| quad.direction == EDhDirection.DOWN) || quad.direction == EDhDirection.DOWN)
{ {
// for horizontal and bottom faces of grass blocks, use the dirt color to // for horizontal and bottom faces of grass blocks, use the dirt color to
// prevent green cliff walls // prevent green cliff walls
@@ -392,7 +299,7 @@ public class LodQuadBuilder
color = ColorUtil.applyShade(color, MC.getShade(quad.direction)); color = ColorUtil.applyShade(color, MC.getShade(quad.direction));
} }
} }
} }
} }
} }
@@ -406,6 +313,7 @@ public class LodQuadBuilder
mx, my, mz); mx, my, mz);
} }
} }
private void putVertex(ByteBuffer bb, short x, short y, short z, int color, byte normalIndex, byte irisBlockMaterialId, byte skylight, byte blocklight, int mx, int my, int mz) private void putVertex(ByteBuffer bb, short x, short y, short z, int color, byte normalIndex, byte irisBlockMaterialId, byte skylight, byte blocklight, int mx, int my, int mz)
{ {
skylight %= 16; skylight %= 16;
@@ -446,6 +354,389 @@ public class LodQuadBuilder
//=================//
// data finalizing //
//=================//
/** runs any final data cleanup, merging, etc. */
public void finalizeData() { this.mergeQuads(); }
/** Uses Greedy meshing to merge this builder's Quads. */
public void mergeQuads()
{
long mergeCount = 0;
long preQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount();
if (preQuadsCount <= 1)
{
return;
}
for (int directionIndex = 0; directionIndex < 6; directionIndex++)
{
mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.EastWest);
if (this.doTransparency)
{
mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.EastWest);
}
// only run the second merge if the face is the top or bottom
if (directionIndex == EDhDirection.UP.ordinal() || directionIndex == EDhDirection.DOWN.ordinal())
{
mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
if (this.doTransparency)
{
mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
}
}
}
long postQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount();
LOGGER.debug("Merged "+mergeCount+"/"+preQuadsCount+"("+(mergeCount / (double) preQuadsCount)+") quads");
}
/** Merges all of this builder's quads for the given directionIndex (up, down, left, etc.) in the given direction */
private static long mergeQuadsInternal(ArrayList<BufferQuad>[] list, int directionIndex, BufferMergeDirectionEnum mergeDirection)
{
if (list[directionIndex].size() <= 1)
return 0;
list[directionIndex].sort((objOne, objTwo) -> objOne.compare(objTwo, mergeDirection));
long mergeCount = 0;
ListIterator<BufferQuad> iter = list[directionIndex].listIterator();
BufferQuad currentQuad = iter.next();
while (iter.hasNext())
{
BufferQuad nextQuad = iter.next();
if (currentQuad.tryMerge(nextQuad, mergeDirection))
{
// merge successful, attempt to merge the next quad
mergeCount++;
iter.set(null);
}
else
{
// merge fail, move on to the next quad
currentQuad = nextQuad;
}
}
list[directionIndex].removeIf(Objects::isNull);
return mergeCount;
}
//==============//
// buffer setup //
//==============//
public Iterator<ByteBuffer> makeOpaqueVertexBuffers()
{
return new Iterator<>()
{
final ByteBuffer bb = ByteBuffer.allocateDirect(ColumnRenderBuffer.FULL_SIZED_BUFFER)
.order(ByteOrder.nativeOrder());
int dir = skipEmpty(0);
int quad = 0;
private int skipEmpty(int d)
{
while (d < 6 && opaqueQuads[d].isEmpty())
{
d++;
}
return d;
}
@Override
public boolean hasNext()
{
return dir < 6;
}
@Override
public ByteBuffer next()
{
if (dir >= 6)
{
return null;
}
bb.clear();
bb.limit(ColumnRenderBuffer.FULL_SIZED_BUFFER);
while (bb.hasRemaining() && dir < 6)
{
writeData();
}
bb.limit(bb.position());
bb.rewind();
return bb;
}
private void writeData()
{
int i = quad;
for (; i < opaqueQuads[dir].size(); i++)
{
if (!bb.hasRemaining())
{
break;
}
putQuad(bb, opaqueQuads[dir].get(i));
}
if (i >= opaqueQuads[dir].size())
{
quad = 0;
dir++;
dir = skipEmpty(dir);
}
else
{
quad = i;
}
}
};
}
public Iterator<ByteBuffer> makeTransparentVertexBuffers()
{
return new Iterator<>()
{
final ByteBuffer bb = ByteBuffer.allocateDirect(ColumnRenderBuffer.FULL_SIZED_BUFFER)
.order(ByteOrder.nativeOrder());
int directionIndex = this.skipEmptyDirectionIndices(0);
int quad = 0;
private int skipEmptyDirectionIndices(int directionIndex)
{
while (directionIndex < 6 &&
(LodQuadBuilder.this.transparentQuads[directionIndex] == null
|| LodQuadBuilder.this.transparentQuads[directionIndex].isEmpty()))
{
directionIndex++;
}
return directionIndex;
}
@Override
public boolean hasNext() { return this.directionIndex < 6; }
@Override
public ByteBuffer next()
{
if (this.directionIndex >= 6)
{
return null;
}
this.bb.clear();
this.bb.limit(ColumnRenderBuffer.FULL_SIZED_BUFFER);
while (this.bb.hasRemaining() && this.directionIndex < 6)
{
this.writeData();
}
this.bb.limit(this.bb.position());
this.bb.rewind();
return this.bb;
}
private void writeData()
{
int i = this.quad;
for (; i < LodQuadBuilder.this.transparentQuads[this.directionIndex].size(); i++)
{
if (!this.bb.hasRemaining())
{
break;
}
putQuad(this.bb, LodQuadBuilder.this.transparentQuads[this.directionIndex].get(i));
}
if (i >= LodQuadBuilder.this.transparentQuads[this.directionIndex].size())
{
this.quad = 0;
this.directionIndex++;
this.directionIndex = this.skipEmptyDirectionIndices(this.directionIndex);
}
else
{
this.quad = i;
}
}
};
}
public interface BufferFiller
{
/** If true: more data needs to be filled */
boolean fill(GLVertexBuffer vbo);
}
public BufferFiller makeOpaqueBufferFiller(EDhApiGpuUploadMethod method)
{
return new BufferFiller()
{
int dir = 0;
int quad = 0;
public boolean fill(GLVertexBuffer vbo)
{
if (dir >= 6)
{
vbo.setVertexCount(0);
return false;
}
int numOfQuads = _countRemainingQuads();
if (numOfQuads > ColumnRenderBuffer.MAX_QUADS_PER_BUFFER)
numOfQuads = ColumnRenderBuffer.MAX_QUADS_PER_BUFFER;
if (numOfQuads == 0)
{
vbo.setVertexCount(0);
return false;
}
ByteBuffer bb = vbo.mapBuffer(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE, method,
ColumnRenderBuffer.FULL_SIZED_BUFFER);
if (bb == null)
throw new NullPointerException("mapBuffer returned null");
bb.clear();
bb.limit(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE);
while (bb.hasRemaining() && dir < 6)
{
writeData(bb);
}
bb.rewind();
vbo.unmapBuffer();
vbo.setVertexCount(numOfQuads * 4);
return dir < 6;
}
private int _countRemainingQuads()
{
int a = opaqueQuads[dir].size() - quad;
for (int i = dir + 1; i < opaqueQuads.length; i++)
{
a += opaqueQuads[i].size();
}
return a;
}
private void writeData(ByteBuffer bb)
{
int startQ = quad;
int i = startQ;
for (i = startQ; i < opaqueQuads[dir].size(); i++)
{
if (!bb.hasRemaining())
{
break;
}
putQuad(bb, opaqueQuads[dir].get(i));
}
if (i >= opaqueQuads[dir].size())
{
quad = 0;
dir++;
while (dir < 6 && opaqueQuads[dir].isEmpty())
{
dir++;
}
}
else
{
quad = i;
}
}
};
}
public BufferFiller makeTransparentBufferFiller(EDhApiGpuUploadMethod method)
{
return new BufferFiller()
{
int dir = 0;
int quad = 0;
public boolean fill(GLVertexBuffer vbo)
{
if (dir >= 6)
{
vbo.setVertexCount(0);
return false;
}
int numOfQuads = _countRemainingQuads();
if (numOfQuads > ColumnRenderBuffer.MAX_QUADS_PER_BUFFER)
numOfQuads = ColumnRenderBuffer.MAX_QUADS_PER_BUFFER;
if (numOfQuads == 0)
{
vbo.setVertexCount(0);
return false;
}
ByteBuffer bb = vbo.mapBuffer(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE, method,
ColumnRenderBuffer.FULL_SIZED_BUFFER);
if (bb == null)
throw new NullPointerException("mapBuffer returned null");
bb.clear();
bb.limit(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE);
while (bb.hasRemaining() && dir < 6)
{
writeData(bb);
}
bb.rewind();
vbo.unmapBuffer();
vbo.setVertexCount(numOfQuads * 4);
return dir < 6;
}
private int _countRemainingQuads()
{
int a = transparentQuads[dir].size() - quad;
for (int i = dir + 1; i < transparentQuads.length; i++)
{
a += transparentQuads[i].size();
}
return a;
}
private void writeData(ByteBuffer bb)
{
int startQ = quad;
int i = startQ;
for (i = startQ; i < transparentQuads[dir].size(); i++)
{
if (!bb.hasRemaining())
{
break;
}
putQuad(bb, transparentQuads[dir].get(i));
}
if (i >= transparentQuads[dir].size())
{
quad = 0;
dir++;
while (dir < 6 && transparentQuads[dir].isEmpty())
{
dir++;
}
}
else
{
quad = i;
}
}
};
}
//=========// //=========//
// getters // // getters //
//=========// //=========//
@@ -20,7 +20,6 @@
package com.seibel.distanthorizons.core.dataObjects.render.columnViews; package com.seibel.distanthorizons.core.dataObjects.render.columnViews;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
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;
@@ -29,60 +28,41 @@ import java.util.Arrays;
public final class ColumnArrayView implements IColumnDataView public final class ColumnArrayView implements IColumnDataView
{ {
public final LongArrayList data; 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; public final int size;
/** public final int offset; // offset in longs
* Vertical size in data points. <Br> /** can be 0 if this column was created for an empty data source */
* Can be 0 if this column was created for an empty data source. public final int vertSize; // vertical size in longs
*/
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;
//=============// public ColumnArrayView(LongArrayList data, int size, int offset, int vertSize)
// constructor //
//=============//
public ColumnArrayView(LongArrayList data, int size, int offset, int verticalSize)
{ {
this.data = data; this.data = data;
this.size = size; this.size = size;
this.offset = offset; this.offset = offset;
this.verticalSize = verticalSize; this.vertSize = vertSize;
} }
//=====================//
// getters and setters //
//=====================//
@Override @Override
public long get(int index) { return data.getLong(index + offset); } public long get(int index) { return data.getLong(index + offset); }
public void set(int index, long value) { data.set(index + offset, value); } public void set(int index, long value) { data.set(index + offset, value); }
@Override @Override
public int size() { return size; } public int size() { return size; }
@Override
public int verticalSize() { return verticalSize; }
@Override @Override
public int dataCount() { return (this.verticalSize != 0) ? (this.size / this.verticalSize) : 0; } // TODO what does the divide by mean? public int verticalSize() { return vertSize; }
@Override
public int dataCount() { return (this.vertSize != 0) ? (this.size / this.vertSize) : 0; }
@Override @Override
public ColumnArrayView subView(int dataIndexStart, int dataCount) public ColumnArrayView subView(int dataIndexStart, int dataCount)
{ {
return new ColumnArrayView(data, dataCount * verticalSize, offset + dataIndexStart * verticalSize, verticalSize); return new ColumnArrayView(data, dataCount * vertSize, offset + dataIndexStart * vertSize, vertSize);
} }
public void fill(long value) { Arrays.fill(data.elements(), offset, offset + size, value); } public void fill(long value) { Arrays.fill(data.elements(), offset, offset + size, value); }
@@ -90,7 +70,7 @@ public final class ColumnArrayView implements IColumnDataView
public void copyFrom(IColumnDataView source) { copyFrom(source, 0); } public void copyFrom(IColumnDataView source) { copyFrom(source, 0); }
public void copyFrom(IColumnDataView source, int outputDataIndexOffset) public void copyFrom(IColumnDataView source, int outputDataIndexOffset)
{ {
if (source.verticalSize() > verticalSize) if (source.verticalSize() > vertSize)
{ {
throw new IllegalArgumentException("source verticalSize must be <= self's verticalSize to copy"); throw new IllegalArgumentException("source verticalSize must be <= self's verticalSize to copy");
} }
@@ -98,19 +78,19 @@ public final class ColumnArrayView implements IColumnDataView
{ {
throw new IllegalArgumentException("dataIndexStart + source.dataCount() must be <= self.dataCount() to copy"); throw new IllegalArgumentException("dataIndexStart + source.dataCount() must be <= self.dataCount() to copy");
} }
else if (source.verticalSize() != verticalSize) else if (source.verticalSize() != vertSize)
{ {
for (int i = 0; i < source.dataCount(); i++) for (int i = 0; i < source.dataCount(); i++)
{ {
int outputOffset = offset + outputDataIndexOffset * verticalSize + i * verticalSize; int outputOffset = offset + outputDataIndexOffset * vertSize + i * vertSize;
source.subView(i, 1).copyTo(data.elements(), outputOffset, source.verticalSize()); source.subView(i, 1).copyTo(data.elements(), outputOffset, source.verticalSize());
Arrays.fill(data.elements(), outputOffset + source.verticalSize(), Arrays.fill(data.elements(), outputOffset + source.verticalSize(),
outputOffset + verticalSize, 0); outputOffset + vertSize, 0);
} }
} }
else else
{ {
source.copyTo(data.elements(), offset + outputDataIndexOffset * verticalSize, source.size()); source.copyTo(data.elements(), offset + outputDataIndexOffset * vertSize, source.size());
} }
} }
@@ -123,19 +103,19 @@ public final class ColumnArrayView implements IColumnDataView
{ {
throw new IllegalArgumentException("Cannot merge views of different sizes"); throw new IllegalArgumentException("Cannot merge views of different sizes");
} }
if (verticalSize != source.verticalSize) if (vertSize != source.vertSize)
{ {
throw new IllegalArgumentException("Cannot merge views of different vertical sizes"); throw new IllegalArgumentException("Cannot merge views of different vertical sizes");
} }
boolean anyChange = false; boolean anyChange = false;
for (int o = 0; o < (source.size() * verticalSize); o += verticalSize) for (int o = 0; o < (source.size() * vertSize); o += vertSize)
{ {
if (override) if (override)
{ {
if (RenderDataPointUtil.compareDatapointPriority(source.get(o), get(o)) >= 0) if (RenderDataPointUtil.compareDatapointPriority(source.get(o), get(o)) >= 0)
{ {
anyChange = true; anyChange = true;
System.arraycopy(source.data, source.offset + o, data, offset + o, verticalSize); System.arraycopy(source.data, source.offset + o, data, offset + o, vertSize);
} }
} }
else else
@@ -143,7 +123,7 @@ public final class ColumnArrayView implements IColumnDataView
if (RenderDataPointUtil.compareDatapointPriority(source.get(o), get(o)) > 0) if (RenderDataPointUtil.compareDatapointPriority(source.get(o), get(o)) > 0)
{ {
anyChange = true; anyChange = true;
System.arraycopy(source.data, source.offset + o, data, offset + o, verticalSize); System.arraycopy(source.data, source.offset + o, data, offset + o, vertSize);
} }
} }
} }
@@ -157,7 +137,7 @@ public final class ColumnArrayView implements IColumnDataView
throw new IllegalArgumentException("Cannot copy and resize to views with different dataCounts"); throw new IllegalArgumentException("Cannot copy and resize to views with different dataCounts");
} }
if (this.verticalSize >= source.verticalSize()) if (this.vertSize >= source.verticalSize())
{ {
this.copyFrom(source); this.copyFrom(source);
} }
@@ -180,18 +160,12 @@ public final class ColumnArrayView implements IColumnDataView
RenderDataPointUtil.mergeMultiData(source, this); RenderDataPointUtil.mergeMultiData(source, this);
} }
//================//
// base overrides //
//================//
@Override @Override
public String toString() public String toString()
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("S:").append(size); sb.append("S:").append(size);
sb.append(" V:").append(verticalSize); sb.append(" V:").append(vertSize);
sb.append(" O:").append(offset); sb.append(" O:").append(offset);
sb.append(" ["); sb.append(" [");
@@ -208,7 +182,6 @@ public final class ColumnArrayView implements IColumnDataView
return sb.toString(); return sb.toString();
} }
public int getDataHash() public int getDataHash()
{ {
return arrayHash(data, offset, size); return arrayHash(data, offset, size);
@@ -226,7 +199,7 @@ public final class ColumnArrayView implements IColumnDataView
for (int i = offset; i < end; i++) for (int i = offset; i < end; i++)
{ {
long element = a.getLong(i); long element = a.getLong(i);
int elementHash = (int) (element ^ (element >>> 32)); int elementHash = Long.hashCode(element);
result = 31 * result + elementHash; result = 31 * result + elementHash;
} }
return result; return result;
@@ -0,0 +1,253 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.transformers;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import org.apache.logging.log4j.LogManager;
public class ChunkToLodBuilder implements AutoCloseable
{
public static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(), () -> Config.Client.Advanced.Logging.logLodBuilderEvent.get());
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
public static final long MAX_TICK_TIME_NS = 1000000000L / 20L;
private final ConcurrentHashMap<DhChunkPos, IChunkWrapper> concurrentChunkToBuildByChunkPos = new ConcurrentHashMap<>();
private final ConcurrentLinkedDeque<Task> concurrentTaskToBuildList = new ConcurrentLinkedDeque<>();
private final AtomicInteger runningCount = new AtomicInteger(0);
//==============//
// constructors //
//==============//
public ChunkToLodBuilder() { }
//=================//
// data generation //
//=================//
public CompletableFuture<FullDataSourceV2> tryGenerateData(IChunkWrapper chunkWrapper)
{
if (chunkWrapper == null)
{
throw new NullPointerException("ChunkWrapper cannot be null!");
}
IChunkWrapper oldChunk = this.concurrentChunkToBuildByChunkPos.put(chunkWrapper.getChunkPos(), chunkWrapper); // an Exchange operation
// If there's old chunk, that means we just replaced an unprocessed old request on generating data on this pos.
// if so, we can just return null to signal this, as the old request's future will instead be the proper one
// that will return the latest generated data.
if (oldChunk != null)
{
return null;
}
// Otherwise, it means we're the first to do so. Let's submit our task to this entry.
CompletableFuture<FullDataSourceV2> future = new CompletableFuture<>();
this.concurrentTaskToBuildList.addLast(new Task(chunkWrapper.getChunkPos(), future));
return future;
}
// TODO why on tick?
public void tick()
{
int threadCount = ThreadPoolUtil.getWorkerThreadCount();
if (this.runningCount.get() >= threadCount)
{
return;
}
else if (this.concurrentTaskToBuildList.isEmpty())
{
return;
}
else if (MC == null || !MC.playerExists())
{
// TODO handle server side properly
// MC hasn't finished loading (or is currently unloaded)
// can be uncommented if tasks aren't being cleared correctly
//this.clearCurrentTasks();
return;
}
ThreadPoolExecutor lodBuilderExecutor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
if (lodBuilderExecutor == null)
{
return;
}
for (int i = 0; i < threadCount; i++)
{
this.runningCount.incrementAndGet();
try
{
CompletableFuture.runAsync(() ->
{
try
{
this.tickThreadTask();
}
finally
{
this.runningCount.decrementAndGet();
}
}, lodBuilderExecutor);
}
catch (RejectedExecutionException ignore) { /* the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back */ }
}
}
private void tickThreadTask()
{
long time = System.nanoTime();
int count = 0;
boolean allDone = false;
while (true)
{
// run until we either run out of time, or all tasks are complete
if (System.nanoTime() - time > MAX_TICK_TIME_NS && !this.concurrentTaskToBuildList.isEmpty())
{
break;
}
Task task = this.concurrentTaskToBuildList.pollFirst();
if (task == null)
{
allDone = true;
break;
}
count++;
IChunkWrapper latestChunk = this.concurrentChunkToBuildByChunkPos.remove(task.chunkPos); // Basically an Exchange operation
if (latestChunk == null)
{
LOGGER.error("Somehow Task at " + task.chunkPos + " has latestChunk as null. Skipping task.");
task.future.complete(null);
continue;
}
try
{
if (LodDataBuilder.canGenerateLodFromChunk(latestChunk))
{
FullDataSourceV2 dataSource = LodDataBuilder.createGeneratedDataSource(latestChunk);
if (dataSource != null)
{
task.future.complete(dataSource);
continue;
}
}
else if (task.generationAttemptExpirationTimeMs < System.currentTimeMillis())
{
// this task won't be re-queued
//LOGGER.trace("removed chunk "+task.chunkPos);
continue;
}
}
catch (Exception ex)
{
LOGGER.error("Error while processing Task at " + task.chunkPos, ex);
}
// Failed to build due to chunk not meeting requirement,
// re-add it to the queue so it can be tested next time
IChunkWrapper casChunk = this.concurrentChunkToBuildByChunkPos.putIfAbsent(task.chunkPos, latestChunk); // CAS operation with expected=null
if (casChunk == null || latestChunk.isStillValid()) // That means CAS have been successful
{
this.concurrentTaskToBuildList.addLast(task); // Then add back the same old task.
}
else // Else, it means someone managed to sneak in a new gen request in this pos. Then lets drop this old task.
{
task.future.complete(null);
}
count--;
}
long time2 = System.nanoTime();
if (!allDone)
{
//LOGGER.info("Completed {} tasks in {} in this tick", count, Duration.ofNanos(time2 - time));
}
else if (count > 0)
{
//LOGGER.info("Completed all {} tasks in {}", count, Duration.ofNanos(time2 - time));
}
}
/**
* should be called whenever changing levels/worlds
* to prevent trying to generate LODs for chunk(s) that are no longer loaded
* (which can cause exceptions)
*/
public void clearCurrentTasks()
{
this.concurrentTaskToBuildList.clear();
this.concurrentChunkToBuildByChunkPos.clear();
}
//==============//
// base methods //
//==============//
@Override
public void close() { this.clearCurrentTasks(); }
//================//
// helper classes //
//================//
private static class Task
{
public final DhChunkPos chunkPos;
public final CompletableFuture<FullDataSourceV2> future;
/** This is tracked so impossible tasks can be removed from the queue */
public long generationAttemptExpirationTimeMs = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(10);
Task(DhChunkPos chunkPos, CompletableFuture<FullDataSourceV2> future)
{
this.chunkPos = chunkPos;
this.future = future;
}
}
}
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core.dataObjects.transformers; package com.seibel.distanthorizons.core.dataObjects.transformers;
import com.seibel.distanthorizons.api.enums.config.EDhApiBlocksToAvoid; import com.seibel.distanthorizons.api.enums.config.EDhApiBlocksToAvoid;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.core.config.Config; 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;
@@ -28,7 +29,7 @@ import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArra
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IDhClientLevel; import com.seibel.distanthorizons.core.level.IDhClientLevel;
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.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.ColorUtil; import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.FullDataPointUtil; import com.seibel.distanthorizons.core.util.FullDataPointUtil;
@@ -38,7 +39,6 @@ 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.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
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 org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@@ -122,12 +122,7 @@ public class FullDataToRenderDataTransformer
ColumnArrayView columnArrayView = columnSource.getVerticalDataPointView(x, z); ColumnArrayView columnArrayView = columnSource.getVerticalDataPointView(x, z);
LongArrayList dataColumn = fullDataSource.get(x, z); LongArrayList dataColumn = fullDataSource.get(x, z);
updateRenderDataViewWithFullDataColumn(level, fullDataSource.mapping, baseX + x, baseZ + z, columnArrayView, dataColumn);
updateOrReplaceRenderDataViewColumnWithFullDataColumn(
level, fullDataSource.mapping,
// bitshift is to account for LODs with a detail level greater than 0 so the block pos is correct
baseX + BitShiftUtil.pow(x,dataDetail), baseZ + BitShiftUtil.pow(z,dataDetail),
columnArrayView, dataColumn);
} }
} }
@@ -137,48 +132,40 @@ public class FullDataToRenderDataTransformer
} }
/** Updates the given {@link ColumnArrayView} 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 updateRenderDataViewWithFullDataColumn(
IDhClientLevel level, IDhClientLevel level,
FullDataPointIdMap fullDataMapping, int blockX, int blockZ, FullDataPointIdMap fullDataMapping, int blockX, int blockZ,
ColumnArrayView columnArrayView, ColumnArrayView columnArrayView,
LongArrayList fullDataColumn) LongArrayList fullDataColumn)
{ {
// we can't do anything if the full data is missing or empty
if (fullDataColumn == null || fullDataColumn.size() == 0) if (fullDataColumn == null || fullDataColumn.size() == 0)
{ {
return; return;
} }
int fullDataLength = fullDataColumn.size(); int dataTotalLength = fullDataColumn.size();
if (fullDataLength <= columnArrayView.verticalSize()) if (dataTotalLength > columnArrayView.verticalSize())
{ {
// Directly use the arrayView since it fits. ColumnArrayView totalColumnData = new ColumnArrayView(new LongArrayList(new long[dataTotalLength]), dataTotalLength, 0, dataTotalLength);
setRenderColumnView(level, fullDataMapping, blockX, blockZ, columnArrayView, fullDataColumn); iterateAndConvert(level, fullDataMapping, blockX, blockZ, totalColumnData, fullDataColumn);
columnArrayView.changeVerticalSizeFrom(totalColumnData);
} }
else else
{ {
// expand the ColumnArrayView to fit the new larger max vertical size iterateAndConvert(level, fullDataMapping, blockX, blockZ, columnArrayView, fullDataColumn); //Directly use the arrayView since it fits.
ColumnArrayView newColumnArrayView = new ColumnArrayView(new LongArrayList(new long[fullDataLength]), fullDataLength, 0, fullDataLength);
setRenderColumnView(level, fullDataMapping, blockX, blockZ, newColumnArrayView, fullDataColumn);
columnArrayView.changeVerticalSizeFrom(newColumnArrayView);
} }
} }
private static void setRenderColumnView( private static void iterateAndConvert(
IDhClientLevel level, FullDataPointIdMap fullDataMapping, IDhClientLevel level, FullDataPointIdMap fullDataMapping,
int blockX, int blockZ, int blockX, int blockZ,
ColumnArrayView renderColumnData, LongArrayList fullColumnData) ColumnArrayView renderColumnData, LongArrayList fullColumnData)
{ {
//===============//
// config values //
//===============//
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();
HashSet<IBlockStateWrapper> blockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredBlocks(level.getLevelWrapper()); HashSet<IBlockStateWrapper> blockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredBlocks(level.getLevelWrapper());
HashSet<IBlockStateWrapper> caveBlockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredCaveBlocks(level.getLevelWrapper()); HashSet<IBlockStateWrapper> caveBlockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredCaveBlocks(level.getLevelWrapper());
int caveCullingMaxY = Config.Client.Advanced.Graphics.AdvancedGraphics.caveCullingHeight.get() - level.getMinY();
boolean caveCullingEnabled = boolean caveCullingEnabled =
Config.Client.Advanced.Graphics.AdvancedGraphics.enableCaveCulling.get() Config.Client.Advanced.Graphics.AdvancedGraphics.enableCaveCulling.get()
&& ( && (
@@ -189,36 +176,30 @@ public class FullDataToRenderDataTransformer
&& !level.getLevelWrapper().getDimensionType().isTheEnd() && !level.getLevelWrapper().getDimensionType().isTheEnd()
); );
boolean isColumnVoid = true; boolean isVoid = true;
int colorToApplyToNextBlock = -1; int colorToApplyToNextBlock = -1;
int lastColor = 0; int lastColor = 0;
int lastBottom = -10_000; int lastBottom = -10000;
int skylightToApplyToNextBlock = -1; int skylightToApplyToNextBlock = -1;
int blocklightToApplyToNextBlock = -1; int blocklightToApplyToNextBlock = -1;
int renderDataIndex = 0; int columnOffset = 0;
IBiomeWrapper biome = null;
IBlockStateWrapper block = null;
//==================================//
// convert full data to render data //
//==================================//
// goes from the top down // goes from the top down
for (int fullDataIndex = 0; fullDataIndex < fullColumnData.size(); fullDataIndex++) for (int i = 0; i < fullColumnData.size(); i++)
{ {
long fullData = fullColumnData.getLong(fullDataIndex); long fullData = fullColumnData.getLong(i);
int bottomY = FullDataPointUtil.getBottomY(fullData); int bottomY = FullDataPointUtil.getBottomY(fullData);
int blockHeight = FullDataPointUtil.getHeight(fullData); int blockHeight = FullDataPointUtil.getHeight(fullData);
int topY = bottomY + blockHeight;
int id = FullDataPointUtil.getId(fullData); int id = FullDataPointUtil.getId(fullData);
int blockLight = FullDataPointUtil.getBlockLight(fullData); int blockLight = FullDataPointUtil.getBlockLight(fullData);
int skyLight = FullDataPointUtil.getSkyLight(fullData); int skyLight = FullDataPointUtil.getSkyLight(fullData);
IBiomeWrapper biome;
IBlockStateWrapper block;
try try
{ {
biome = fullDataMapping.getBiomeWrapper(id); biome = fullDataMapping.getBiomeWrapper(id);
@@ -226,6 +207,7 @@ public class FullDataToRenderDataTransformer
} }
catch (IndexOutOfBoundsException e) catch (IndexOutOfBoundsException e)
{ {
// FIXME sometimes the data map has a length of 0
if (!brokenPos.contains(fullDataMapping.getPos())) if (!brokenPos.contains(fullDataMapping.getPos()))
{ {
brokenPos.add(fullDataMapping.getPos()); brokenPos.add(fullDataMapping.getPos());
@@ -237,12 +219,11 @@ public class FullDataToRenderDataTransformer
"Further errors for this position won't be logged."); "Further errors for this position won't be logged.");
} }
// don't render broken data // skip rendering broken data
continue; continue;
} }
//====================// //====================//
// ignored block and // // ignored block and //
// cave culling check // // cave culling check //
@@ -254,26 +235,24 @@ public class FullDataToRenderDataTransformer
{ {
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
&& 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 && columnOffset != 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()) && columnOffset != 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.
long nextFullData = fullColumnData.getLong(fullDataIndex+1); long nextFullData = fullColumnData.getLong(i+1);
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(columnOffset - 1);
columnData = RenderDataPointUtil.setYMin(columnData, bottomY); columnData = RenderDataPointUtil.setYMin(columnData, bottomY);
renderColumnData.set(renderDataIndex - 1, columnData); renderColumnData.set(columnOffset - 1, columnData);
} }
continue; continue;
@@ -293,28 +272,27 @@ public class FullDataToRenderDataTransformer
} }
//===================//
// solid block check //
//===================//
//=======================// if (ignoreNonCollidingBlocks && !block.isSolid() && !block.isLiquid() && block.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE)
// non-solid block check //
//=======================//
if (ignoreNonCollidingBlocks
&& !block.isSolid() && !block.isLiquid() && block.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE)
{ {
if (colorBelowWithAvoidedBlocks) if (colorBelowWithAvoidedBlocks)
{ {
int tempColor = level.computeBaseColor(new DhBlockPos(blockX, bottomY + level.getMinY(), blockZ), biome, block); int tempColor = level.computeBaseColor(new DhBlockPos(blockX, bottomY + level.getMinY(), blockZ), biome, 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
if (ColorUtil.getAlpha(tempColor) != 0) if (ColorUtil.getAlpha(tempColor) != 0)
{ {
// don't transfer alpha if for some reason grass is semi transparent
colorToApplyToNextBlock = ColorUtil.setAlpha(tempColor,255); colorToApplyToNextBlock = ColorUtil.setAlpha(tempColor,255);
skylightToApplyToNextBlock = skyLight; skylightToApplyToNextBlock = skyLight;
blocklightToApplyToNextBlock = blockLight; blocklightToApplyToNextBlock = blockLight;
} }
} }
// skip this non-colliding block // don't add this block
continue; continue;
} }
@@ -334,36 +312,31 @@ public class FullDataToRenderDataTransformer
blockLight = blocklightToApplyToNextBlock; blockLight = blocklightToApplyToNextBlock;
} }
//check if they share a top-bottom face and if they have same color
if (color == lastColor && bottomY + blockHeight == lastBottom && columnOffset > 0)
//=============================//
// merge same-colored adjacent //
//=============================//
// check if they share a top-bottom face and if they have same color
if (color == lastColor && bottomY + blockHeight == lastBottom && renderDataIndex > 0)
{ {
//replace the previous block with new bottom //replace the previous block with new bottom
long columnData = renderColumnData.get(renderDataIndex - 1); long columnData = renderColumnData.get(columnOffset - 1);
columnData = RenderDataPointUtil.setYMin(columnData, bottomY); columnData = RenderDataPointUtil.setYMin(columnData, bottomY);
renderColumnData.set(renderDataIndex - 1, columnData); renderColumnData.set(columnOffset - 1, columnData);
} }
else else
{ {
// add the block // add the block
isColumnVoid = false; isVoid = false;
long columnData = RenderDataPointUtil.createDataPoint(bottomY + blockHeight, bottomY, color, skyLight, blockLight, block.getMaterialId()); long columnData = RenderDataPointUtil.createDataPoint(bottomY + blockHeight, bottomY, color, skyLight, blockLight, block.getMaterialId());
renderColumnData.set(renderDataIndex, columnData); renderColumnData.set(columnOffset, columnData);
renderDataIndex++; columnOffset++;
} }
lastBottom = bottomY; lastBottom = bottomY;
lastColor = color; lastColor = color;
} }
if (isColumnVoid) if (isVoid)
{ {
renderColumnData.set(0, RenderDataPointUtil.EMPTY_DATA); renderColumnData.set(0, RenderDataPointUtil.createVoidDataPoint());
} }
} }
@@ -19,7 +19,6 @@
package com.seibel.distanthorizons.core.dataObjects.transformers; package com.seibel.distanthorizons.core.dataObjects.transformers;
import java.util.Collections;
import java.util.List; import java.util.List;
import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode; import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode;
@@ -31,12 +30,10 @@ 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.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.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPosMutable;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
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.util.RenderDataPointUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException; import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
@@ -67,8 +64,8 @@ public class LodDataBuilder
int sectionPosX = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().getX()); int sectionPosX = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().x);
int sectionPosZ = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().getZ()); int sectionPosZ = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().z);
long pos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ); long pos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
FullDataSourceV2 dataSource = FullDataSourceV2.createEmpty(pos); FullDataSourceV2 dataSource = FullDataSourceV2.createEmpty(pos);
@@ -78,8 +75,8 @@ public class LodDataBuilder
// compute the chunk dataSource offset // compute the chunk dataSource offset
// this offset is used to determine where in the dataSource this chunk's data should go // this offset is used to determine where in the dataSource this chunk's data should go
int chunkOffsetX = chunkWrapper.getChunkPos().getX(); int chunkOffsetX = chunkWrapper.getChunkPos().x;
if (chunkWrapper.getChunkPos().getX() < 0) if (chunkWrapper.getChunkPos().x < 0)
{ {
// expected offset positions: // expected offset positions:
// chunkPos -> offset // chunkPos -> offset
@@ -106,8 +103,8 @@ public class LodDataBuilder
} }
chunkOffsetX *= LodUtil.CHUNK_WIDTH; chunkOffsetX *= LodUtil.CHUNK_WIDTH;
int chunkOffsetZ = chunkWrapper.getChunkPos().getZ(); int chunkOffsetZ = chunkWrapper.getChunkPos().z;
if (chunkWrapper.getChunkPos().getZ() < 0) if (chunkWrapper.getChunkPos().z < 0)
{ {
chunkOffsetZ = ((chunkOffsetZ) % FullDataSourceV2.NUMB_OF_CHUNKS_WIDE); chunkOffsetZ = ((chunkOffsetZ) % FullDataSourceV2.NUMB_OF_CHUNKS_WIDE);
if (chunkOffsetZ != 0) if (chunkOffsetZ != 0)
@@ -155,8 +152,8 @@ public class LodDataBuilder
else else
{ {
//we are at the height limit. There are no torches here, and sky is not obscured. //we are at the height limit. There are no torches here, and sky is not obscured.
blockLight = LodUtil.MIN_MC_LIGHT; blockLight = 0;
skyLight = LodUtil.MAX_MC_LIGHT; skyLight = 15;
} }
@@ -238,7 +235,7 @@ public class LodDataBuilder
private static boolean blockVisible(IChunkWrapper chunkWrapper, int relBlockX, int blockY, int relBlockZ) private static boolean blockVisible(IChunkWrapper chunkWrapper, int relBlockX, int blockY, int relBlockZ)
{ {
DhBlockPos originalBlockPos = new DhBlockPos(relBlockX,blockY,relBlockZ); DhBlockPos originalBlockPos = new DhBlockPos(relBlockX,blockY,relBlockZ);
final DhBlockPosMutable testBlockPos = new DhBlockPosMutable(relBlockX,blockY,relBlockZ); DhBlockPos testBlockPos = new DhBlockPos(relBlockX,blockY,relBlockZ);
// up/down // up/down
if (blockInDirectionVisible(chunkWrapper, EDhDirection.UP, originalBlockPos, testBlockPos)) if (blockInDirectionVisible(chunkWrapper, EDhDirection.UP, originalBlockPos, testBlockPos))
@@ -273,20 +270,20 @@ public class LodDataBuilder
return false; return false;
} }
private static boolean blockInDirectionVisible(IChunkWrapper chunkWrapper, EDhDirection direction, DhBlockPos originalBlockPos, DhBlockPosMutable testBlockPos) private static boolean blockInDirectionVisible(IChunkWrapper chunkWrapper, EDhDirection direction, DhBlockPos originalBlockPos, DhBlockPos testBlockPos)
{ {
originalBlockPos.mutateOffset(direction, testBlockPos); originalBlockPos.mutateOffset(direction, testBlockPos);
// if the block is next to the border of a chunk, assume it's visible // if the block is next to the border of a chunk, assume it's visible
if (testBlockPos.getX() < 0 || testBlockPos.getX() >= LodUtil.CHUNK_WIDTH) if (testBlockPos.x < 0 || testBlockPos.x >= LodUtil.CHUNK_WIDTH)
{ {
return true; return true;
} }
if (testBlockPos.getZ() < 0 || testBlockPos.getZ() >= LodUtil.CHUNK_WIDTH) if (testBlockPos.z < 0 || testBlockPos.z >= LodUtil.CHUNK_WIDTH)
{ {
return true; return true;
} }
if (testBlockPos.getY() < chunkWrapper.getMinBuildHeight() || testBlockPos.getY() > chunkWrapper.getMaxBuildHeight()) if (testBlockPos.y < chunkWrapper.getMinBuildHeight() || testBlockPos.y > chunkWrapper.getMaxBuildHeight())
{ {
return true; return true;
} }
@@ -298,7 +295,7 @@ public class LodDataBuilder
/** @throws ClassCastException if an API user returns the wrong object type(s) */ /** @throws ClassCastException if an API user returns the wrong object type(s) */
public static FullDataSourceV2 createFromApiChunkData(DhApiChunk apiChunk, boolean runAdditionalValidation) throws ClassCastException, DataCorruptedException, IllegalArgumentException public static FullDataSourceV2 createFromApiChunkData(DhApiChunk apiChunk) throws ClassCastException, DataCorruptedException
{ {
// get the section position // get the section position
int sectionPosX = getXOrZSectionPosFromChunkPos(apiChunk.chunkPosX); int sectionPosX = getXOrZSectionPosFromChunkPos(apiChunk.chunkPosX);
@@ -315,10 +312,6 @@ public class LodDataBuilder
for (int relBlockX = 0; relBlockX < LodUtil.CHUNK_WIDTH; relBlockX++) for (int relBlockX = 0; relBlockX < LodUtil.CHUNK_WIDTH; relBlockX++)
{ {
List<DhApiTerrainDataPoint> columnDataPoints = apiChunk.getDataPoints(relBlockX, relBlockZ); List<DhApiTerrainDataPoint> columnDataPoints = apiChunk.getDataPoints(relBlockX, relBlockZ);
if (runAdditionalValidation)
{
validateOrThrowDataColumn(columnDataPoints);
}
// this null check does 2 nice things at the same time: // this null check does 2 nice things at the same time:
@@ -327,8 +320,6 @@ public class LodDataBuilder
// AND the below loop won't run. // AND the below loop won't run.
int size = (columnDataPoints != null) ? columnDataPoints.size() : 0; int size = (columnDataPoints != null) ? columnDataPoints.size() : 0;
// TODO make missing air LODs
// TODO merge duplicate datapoints
LongArrayList packedDataPoints = new LongArrayList(new long[size]); LongArrayList packedDataPoints = new LongArrayList(new long[size]);
for (int index = 0; index < size; index++) for (int index = 0; index < size; index++)
{ {
@@ -359,75 +350,6 @@ public class LodDataBuilder
} }
return dataSource; return dataSource;
} }
private static void validateOrThrowDataColumn(List<DhApiTerrainDataPoint> dataPoints) throws IllegalArgumentException
{
// order doesn't need to be checked if there is 0 or 1 items
if (dataPoints.size() > 1)
{
// DH expects datapoints to be in a top-down order
DhApiTerrainDataPoint first = dataPoints.get(0);
DhApiTerrainDataPoint last = dataPoints.get(dataPoints.size() - 1);
if (first.bottomYBlockPos < last.bottomYBlockPos)
{
// flip the array if it's in bottom-up order
Collections.reverse(dataPoints);
}
}
// check that each datapoint is valid
int lastBottomYPos = Integer.MIN_VALUE;
for (int i = 0; i < dataPoints.size(); i++) // standard for-loop used instead of an enhanced for-loop to slightly reduce GC overhead due to iterator allocation
{
DhApiTerrainDataPoint dataPoint = dataPoints.get(i);
if (dataPoint == null)
{
throw new IllegalArgumentException("Datapoint: ["+i+"] is null DhApiTerrainDataPoints are not allowed. If you want to represent empty terrain, please use AIR.");
}
if (dataPoint.detailLevel != 0)
{
throw new IllegalArgumentException("Datapoint: ["+i+"] has the wrong detail level ["+dataPoint.detailLevel+"], all data points must be block sized; IE their detail level must be [0].");
}
int bottomYPos = dataPoint.bottomYBlockPos;
int topYPos = dataPoint.topYBlockPos;
int height = (dataPoint.topYBlockPos - dataPoint.bottomYBlockPos);
// is the datapoint right side up?
if (bottomYPos > topYPos)
{
throw new IllegalArgumentException("Datapoint: ["+i+"] is upside down. Top Pos: ["+topYPos+"], bottom pos: ["+bottomYPos+"].");
}
// valid height?
if (height <= 0 || height >= RenderDataPointUtil.MAX_WORLD_Y_SIZE)
{
throw new IllegalArgumentException("Datapoint: ["+i+"] has invalid height. Height must be in the range [1 - "+RenderDataPointUtil.MAX_WORLD_Y_SIZE+"] (inclusive).");
}
// is this datapoint overlapping the last one?
if (lastBottomYPos > topYPos)
{
throw new IllegalArgumentException("DhApiTerrainDataPoint ["+i+"] is overlapping with the last datapoint, this top Y: ["+topYPos+"], lastBottomYPos: ["+lastBottomYPos+"].");
}
// is there a gap between the last datapoint?
if (topYPos != lastBottomYPos
&& lastBottomYPos != Integer.MIN_VALUE)
{
throw new IllegalArgumentException("DhApiTerrainDataPoint ["+i+"] has a gap between it and index ["+(i-1)+"]. Empty spaces should be filled by air, otherwise DH's downsampling won't calculate lighting correctly.");
}
lastBottomYPos = bottomYPos;
}
}
@@ -98,9 +98,7 @@ public enum EDhDirection
private static final EDhDirection[] VALUES = values(); private static final EDhDirection[] VALUES = values();
private static final Map<String, EDhDirection> BY_NAME = Arrays.stream(VALUES).collect(Collectors.toMap(EDhDirection::getName, (p_199787_0_) -> private static final Map<String, EDhDirection> BY_NAME = Arrays.stream(VALUES).collect(Collectors.toMap(EDhDirection::getName, (p_199787_0_) ->
{ p_199787_0_));
return p_199787_0_;
}));
// private static final LodDirection[] BY_3D_DATA = Arrays.stream(VALUES).sorted(Comparator.comparingInt((p_199790_0_) -> // private static final LodDirection[] BY_3D_DATA = Arrays.stream(VALUES).sorted(Comparator.comparingInt((p_199790_0_) ->
// { // {
@@ -247,36 +245,26 @@ public enum EDhDirection
public EDhDirection getClockWise() public EDhDirection getClockWise()
{ {
switch (this) return switch (this)
{ {
case NORTH: case NORTH -> EAST;
return EAST; case SOUTH -> WEST;
case SOUTH: case WEST -> NORTH;
return WEST; case EAST -> SOUTH;
case WEST: default -> throw new IllegalStateException("Unable to get Y-rotated facing of " + this);
return NORTH; };
case EAST:
return SOUTH;
default:
throw new IllegalStateException("Unable to get Y-rotated facing of " + this);
}
} }
public EDhDirection getCounterClockWise() public EDhDirection getCounterClockWise()
{ {
switch (this) return switch (this)
{ {
case NORTH: case NORTH -> WEST;
return WEST; case SOUTH -> EAST;
case SOUTH: case WEST -> SOUTH;
return EAST; case EAST -> NORTH;
case WEST: default -> throw new IllegalStateException("Unable to get CCW facing of " + this);
return SOUTH; };
case EAST:
return NORTH;
default:
throw new IllegalStateException("Unable to get CCW facing of " + this);
}
} }
public String getName() public String getName()
@@ -317,16 +305,11 @@ public enum EDhDirection
public static EDhDirection fromAxisAndDirection(EDhDirection.Axis p_211699_0_, EDhDirection.AxisDirection p_211699_1_) public static EDhDirection fromAxisAndDirection(EDhDirection.Axis p_211699_0_, EDhDirection.AxisDirection p_211699_1_)
{ {
switch (p_211699_0_) return switch (p_211699_0_) {
{ case X -> p_211699_1_ == AxisDirection.POSITIVE ? EAST : WEST;
case X: case Y -> p_211699_1_ == AxisDirection.POSITIVE ? UP : DOWN;
return p_211699_1_ == EDhDirection.AxisDirection.POSITIVE ? EAST : WEST; default -> p_211699_1_ == AxisDirection.POSITIVE ? SOUTH : NORTH;
case Y: };
return p_211699_1_ == EDhDirection.AxisDirection.POSITIVE ? UP : DOWN;
case Z:
default:
return p_211699_1_ == EDhDirection.AxisDirection.POSITIVE ? SOUTH : NORTH;
}
} }
// public float toYRot() // public float toYRot()
@@ -436,9 +419,7 @@ public enum EDhDirection
private static final EDhDirection.Axis[] VALUES = values(); private static final EDhDirection.Axis[] VALUES = values();
private static final Map<String, EDhDirection.Axis> BY_NAME = Arrays.stream(VALUES).collect(Collectors.toMap(EDhDirection.Axis::getName, (p_199785_0_) -> private static final Map<String, EDhDirection.Axis> BY_NAME = Arrays.stream(VALUES).collect(Collectors.toMap(EDhDirection.Axis::getName, (p_199785_0_) ->
{ p_199785_0_));
return p_199785_0_;
}));
private final String name; private final String name;
Axis(String name) Axis(String name)
@@ -125,10 +125,10 @@ public class FullDataSourceProviderV2
// start migrating any legacy data sources present in the background // start migrating any legacy data sources present in the background
this.migrationThreadPool = ThreadUtil.makeRateLimitedThreadPool(1, MIGRATION_THREAD_NAME_PREFIX +"["+dimensionName+"]", Config.Client.Advanced.MultiThreading.runTimeRatioForUpdatePropagatorThreads.get(), Thread.MIN_PRIORITY, (Semaphore)null); this.migrationThreadPool = ThreadUtil.makeRateLimitedThreadPool(1, MIGRATION_THREAD_NAME_PREFIX +"["+dimensionName+"]", Config.Client.Advanced.MultiThreading.runTimeRatioForUpdatePropagatorThreads.get(), Thread.MIN_PRIORITY, (Semaphore)null);
this.migrationThreadPool.execute(() -> this.convertLegacyDataSources()); this.migrationThreadPool.execute(this::convertLegacyDataSources);
this.updateQueueProcessor = ThreadUtil.makeSingleThreadPool("Parent Update Queue ["+dimensionName+"]"); this.updateQueueProcessor = ThreadUtil.makeSingleThreadPool("Parent Update Queue ["+dimensionName+"]");
this.updateQueueProcessor.execute(() -> this.runUpdateQueue()); this.updateQueueProcessor.execute(this::runUpdateQueue);
} }
@@ -604,12 +604,12 @@ public class FullDataSourceProviderV2
public void debugRender(DebugRenderer renderer) public void debugRender(DebugRenderer renderer)
{ {
this.lockedPosSet this.lockedPosSet
.forEach((pos) -> { renderer.renderBox(new DebugRenderer.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 DebugRenderer.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)));
this.parentUpdatingPosSet this.parentUpdatingPosSet
.forEach((pos) -> { renderer.renderBox(new DebugRenderer.Box(pos, -32f, 80f, 0.20f, Color.MAGENTA)); }); .forEach((pos) -> renderer.renderBox(new DebugRenderer.Box(pos, -32f, 80f, 0.20f, Color.MAGENTA)));
} }
@Override @Override
@@ -111,7 +111,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
// if the generation task was split up into smaller positions, add the on-complete event to them // if the generation task was split up into smaller positions, add the on-complete event to them
for (CompletableFuture<WorldGenResult> siblingFuture : genTaskResult.childFutures) for (CompletableFuture<WorldGenResult> siblingFuture : genTaskResult.childFutures)
{ {
siblingFuture.whenComplete((siblingGenTaskResult, siblingEx) -> this.onWorldGenTaskComplete(siblingGenTaskResult, siblingEx)); siblingFuture.whenComplete(this::onWorldGenTaskComplete);
} }
} }
@@ -215,7 +215,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
GenTask genTask = new GenTask(genPos); GenTask genTask = new GenTask(genPos);
CompletableFuture<WorldGenResult> worldGenFuture = worldGenQueue.submitGenTask(genPos, (byte) (DhSectionPos.getDetailLevel(genPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL), genTask); CompletableFuture<WorldGenResult> worldGenFuture = worldGenQueue.submitGenTask(genPos, (byte) (DhSectionPos.getDetailLevel(genPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL), genTask);
worldGenFuture.whenComplete((genTaskResult, ex) -> this.onWorldGenTaskComplete(genTaskResult, ex)); worldGenFuture.whenComplete(this::onWorldGenTaskComplete);
return true; return true;
} }
@@ -362,7 +362,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
super.debugRender(renderer); super.debugRender(renderer);
this.delayedFullDataSourceSaveCache.dataSourceByPosition this.delayedFullDataSourceSaveCache.dataSourceByPosition
.forEach((pos, dataSource) -> { renderer.renderBox(new DebugRenderer.Box(pos, -32f, 80f, 0.20f, Color.green.darker())); }); .forEach((pos, dataSource) -> renderer.renderBox(new DebugRenderer.Box(pos, -32f, 80f, 0.20f, Color.green.darker())));
} }
@@ -387,10 +387,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
@Override @Override
public Consumer<FullDataSourceV2> getChunkDataConsumer() public Consumer<FullDataSourceV2> getChunkDataConsumer()
{ {
return (chunkSizedFullDataSource) -> return GeneratedFullDataSourceProvider.this.delayedFullDataSourceSaveCache::queueDataSourceForUpdateAndSave;
{
GeneratedFullDataSourceProvider.this.delayedFullDataSourceSaveCache.queueDataSourceForUpdateAndSave(chunkSizedFullDataSource);
};
} }
} }
private void onDataSourceSave(FullDataSourceV2 fullDataSource) private void onDataSourceSave(FullDataSourceV2 fullDataSource)
@@ -26,12 +26,12 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.api.enums.config.EDhApiServerFolderNameMode; import com.seibel.distanthorizons.api.enums.config.EDhApiServerFolderNameMode;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel; import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import com.seibel.distanthorizons.core.util.objects.ParsedIp; import com.seibel.distanthorizons.core.util.objects.ParsedIp;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper; 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.IDimensionTypeWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IDimensionTypeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.util.StringUtil;
import java.io.File; import java.io.File;
import java.util.*; import java.util.*;
@@ -85,9 +85,8 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure
return this.levelWrapperToFileMap.computeIfAbsent(levelWrapper, (newLevelWrapper) -> return this.levelWrapperToFileMap.computeIfAbsent(levelWrapper, (newLevelWrapper) ->
{ {
// Use the server provided key if one was provided // Use the server provided key if one was provided
if (newLevelWrapper instanceof IServerKeyedClientLevel) if (newLevelWrapper instanceof IServerKeyedClientLevel keyedClientLevel)
{ {
IServerKeyedClientLevel keyedClientLevel = (IServerKeyedClientLevel) newLevelWrapper;
LOGGER.info("Loading level " + newLevelWrapper.getDimensionType().getDimensionName() + " with key: " + keyedClientLevel.getServerLevelKey()); LOGGER.info("Loading level " + newLevelWrapper.getDimensionType().getDimensionName() + " with key: " + keyedClientLevel.getServerLevelKey());
// This world was identified by the server directly, so we can know for sure which folder to use. // This world was identified by the server directly, so we can know for sure which folder to use.
return new File(getSaveStructureFolderPath() + File.separatorChar + keyedClientLevel.getServerLevelKey()); return new File(getSaveStructureFolderPath() + File.separatorChar + keyedClientLevel.getServerLevelKey());
@@ -95,9 +94,8 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure
// use multiverse matching if enabled and in multiplayer (the server should already know where the player is) // use multiverse matching if enabled and in multiplayer (the server should already know where the player is)
if (newLevelWrapper instanceof IClientLevelWrapper && Config.Client.Advanced.Multiplayer.multiverseSimilarityRequiredPercent.get() != 0) if (newLevelWrapper instanceof IClientLevelWrapper newClientLevelWrapper && Config.Client.Advanced.Multiplayer.multiverseSimilarityRequiredPercent.get() != 0)
{ {
IClientLevelWrapper newClientLevelWrapper = (IClientLevelWrapper) newLevelWrapper;
// create the matcher if one doesn't exist // create the matcher if one doesn't exist
if (this.subDimMatcher == null || !this.subDimMatcher.isFindingLevel(newClientLevelWrapper)) if (this.subDimMatcher == null || !this.subDimMatcher.isFindingLevel(newClientLevelWrapper))
@@ -134,12 +132,12 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure
private File getLevelFolderWithoutSimilarityMatching(ILevelWrapper level) private File getLevelFolderWithoutSimilarityMatching(ILevelWrapper level)
{ {
List<File> folders = this.getDhDataFoldersForDimension(level.getDimensionType()); List<File> folders = this.getDhDataFoldersForDimension(level.getDimensionType());
if (!folders.isEmpty() && folders.get(0) != null) if (!folders.isEmpty() && folders.getFirst() != null)
{ {
// use the first existing sub-dimension // use the first existing sub-dimension
String folderName = folders.get(0).getName(); String folderName = folders.getFirst().getName();
LOGGER.info("Default Sub Dimension set to: [" + StringUtil.shortenString(folderName, 8) + "...]"); LOGGER.info("Default Sub Dimension set to: [" + LodUtil.shortenString(folderName, 8) + "...]");
return folders.get(0); return folders.getFirst();
} }
else else
{ {
@@ -270,27 +268,14 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure
// generate the folder name // generate the folder name
String folderName; String folderName = switch (folderNameMode)
switch (folderNameMode)
{ {
default: default -> serverName;
case NAME_ONLY: case IP_ONLY -> serverIpCleaned;
folderName = serverName; case NAME_IP -> serverName + ", IP " + serverIpCleaned;
break; case NAME_IP_PORT -> serverName + ", IP " + serverIpCleaned + (serverPortCleaned.length() != 0 ? ("-" + serverPortCleaned) : "");
case IP_ONLY: case NAME_IP_PORT_MC_VERSION -> serverName + ", IP " + serverIpCleaned + (serverPortCleaned.length() != 0 ? ("-" + serverPortCleaned) : "") + ", GameVersion " + serverMcVersion;
folderName = serverIpCleaned; };
break;
case NAME_IP:
folderName = serverName + ", IP " + serverIpCleaned;
break;
case NAME_IP_PORT:
folderName = serverName + ", IP " + serverIpCleaned + (serverPortCleaned.length() != 0 ? ("-" + serverPortCleaned) : "");
break;
case NAME_IP_PORT_MC_VERSION:
folderName = serverName + ", IP " + serverIpCleaned + (serverPortCleaned.length() != 0 ? ("-" + serverPortCleaned) : "") + ", GameVersion " + serverMcVersion;
break;
}
// PercentEscaper makes the characters all part of the standard alphameric character set // PercentEscaper makes the characters all part of the standard alphameric character set
// This fixes some issues when the server is named something in other languages // This fixes some issues when the server is named something in other languages
@@ -40,7 +40,6 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftCli
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
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.util.StringUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@@ -93,7 +92,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable
if (potentialLevelFolders.size() == 0) if (potentialLevelFolders.size() == 0)
{ {
String newId = UUID.randomUUID().toString(); String newId = UUID.randomUUID().toString();
LOGGER.info("No potential level files found. Creating a new sub dimension with the ID ["+ StringUtil.shortenString(newId, 8)+"]..."); LOGGER.info("No potential level files found. Creating a new sub dimension with the ID ["+LodUtil.shortenString(newId, 8)+"]...");
this.foundLevelFile = this.CreateSubDimFolder(newId); this.foundLevelFile = this.CreateSubDimFolder(newId);
} }
} }
@@ -202,13 +201,13 @@ public class SubDimensionLevelMatcher implements AutoCloseable
// log the start of this attempt // log the start of this attempt
LOGGER.info("Attempting to determine sub-dimension for [" + MC_CLIENT.getWrappedClientLevel().getDimensionType().getDimensionName() + "]"); LOGGER.info("Attempting to determine sub-dimension for [" + MC_CLIENT.getWrappedClientLevel().getDimensionType().getDimensionName() + "]");
LOGGER.info("Player block pos in dimension: [" + this.playerData.playerBlockPos.getX() + "," + this.playerData.playerBlockPos.getY() + "," + this.playerData.playerBlockPos.getZ() + "]"); LOGGER.info("Player block pos in dimension: [" + this.playerData.playerBlockPos.x + "," + this.playerData.playerBlockPos.y + "," + this.playerData.playerBlockPos.z + "]");
LOGGER.info("Potential Sub Dimension folders: [" + this.potentialLevelFolders.size() + "]"); LOGGER.info("Potential Sub Dimension folders: [" + this.potentialLevelFolders.size() + "]");
SubDimCompare mostSimilarSubDim = null; SubDimCompare mostSimilarSubDim = null;
for (File testLevelFolder : this.potentialLevelFolders) for (File testLevelFolder : this.potentialLevelFolders)
{ {
LOGGER.info("Testing level folder: [" + StringUtil.shortenString(testLevelFolder.getName(), 8) + "]"); LOGGER.info("Testing level folder: [" + LodUtil.shortenString(testLevelFolder.getName(), 8) + "]");
FullDataSourceV2 testFullDataSource = null; FullDataSourceV2 testFullDataSource = null;
try try
@@ -315,7 +314,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable
// get the player data for this dimension folder // get the player data for this dimension folder
SubDimensionPlayerData testPlayerData = new SubDimensionPlayerData(testLevelFolder); SubDimensionPlayerData testPlayerData = new SubDimensionPlayerData(testLevelFolder);
LOGGER.info("Last known player pos: [" + testPlayerData.playerBlockPos.getX() + "," + testPlayerData.playerBlockPos.getY() + "," + testPlayerData.playerBlockPos.getZ() + "]"); LOGGER.info("Last known player pos: [" + testPlayerData.playerBlockPos.x + "," + testPlayerData.playerBlockPos.y + "," + testPlayerData.playerBlockPos.z + "]");
// check if the block positions are close // check if the block positions are close
int playerBlockDist = testPlayerData.playerBlockPos.getManhattanDistance(this.playerData.playerBlockPos); int playerBlockDist = testPlayerData.playerBlockPos.getManhattanDistance(this.playerData.playerBlockPos);
@@ -329,8 +328,8 @@ public class SubDimensionLevelMatcher implements AutoCloseable
} }
String subDimShortName = StringUtil.shortenString(testLevelFolder.getName(), 8); // variables are separated out for easier debugging String subDimShortName = LodUtil.shortenString(testLevelFolder.getName(), 8); // variables are separated out for easier debugging
String equalPercent = StringUtil.shortenString(mostSimilarSubDim.getPercentEqual()+"", 5); String equalPercent = LodUtil.shortenString(mostSimilarSubDim.getPercentEqual()+"", 5);
LOGGER.info("Sub dimension ["+subDimShortName+"...] is current dimension probability: "+equalPercent+" ("+equalDataPoints+"/"+totalDataPointCount+")"); LOGGER.info("Sub dimension ["+subDimShortName+"...] is current dimension probability: "+equalPercent+" ("+equalDataPoints+"/"+totalDataPointCount+")");
} }
catch (Exception e) catch (Exception e)
@@ -360,7 +359,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable
{ {
// we found a sub dim folder that is similar, use it // we found a sub dim folder that is similar, use it
LOGGER.info("Sub Dimension set to: [" + StringUtil.shortenString(mostSimilarSubDim.folder.getName(), 8) + "...] with an equality of [" + mostSimilarSubDim.getPercentEqual() + "]"); LOGGER.info("Sub Dimension set to: [" + LodUtil.shortenString(mostSimilarSubDim.folder.getName(), 8) + "...] with an equality of [" + mostSimilarSubDim.getPercentEqual() + "]");
return mostSimilarSubDim.folder; return mostSimilarSubDim.folder;
} }
else else
@@ -370,7 +369,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable
String newId = UUID.randomUUID().toString(); String newId = UUID.randomUUID().toString();
double highestEqualityPercent = mostSimilarSubDim != null ? mostSimilarSubDim.getPercentEqual() : 0; double highestEqualityPercent = mostSimilarSubDim != null ? mostSimilarSubDim.getPercentEqual() : 0;
String message = "No suitable sub dimension found. The highest equality was [" + StringUtil.shortenString(highestEqualityPercent + "", 5) + "]. Creating a new sub dimension with ID: " + StringUtil.shortenString(newId, 8) + "..."; String message = "No suitable sub dimension found. The highest equality was [" + LodUtil.shortenString(highestEqualityPercent + "", 5) + "]. Creating a new sub dimension with ID: " + LodUtil.shortenString(newId, 8) + "...";
LOGGER.info(message); LOGGER.info(message);
File folder = this.CreateSubDimFolder(newId); File folder = this.CreateSubDimFolder(newId);
@@ -22,7 +22,7 @@ package com.seibel.distanthorizons.core.file.subDimMatching;
import com.electronwill.nightconfig.core.file.CommentedFileConfig; import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
@@ -127,9 +127,9 @@ public class SubDimensionPlayerData
public void toTomlFile(CommentedFileConfig toml) public void toTomlFile(CommentedFileConfig toml)
{ {
// player block pos // player block pos
toml.add(PLAYER_BLOCK_POS_X_PATH, this.playerBlockPos.getX()); toml.add(PLAYER_BLOCK_POS_X_PATH, this.playerBlockPos.x);
toml.add(PLAYER_BLOCK_POS_Y_PATH, this.playerBlockPos.getY()); toml.add(PLAYER_BLOCK_POS_Y_PATH, this.playerBlockPos.y);
toml.add(PLAYER_BLOCK_POS_Z_PATH, this.playerBlockPos.getZ()); toml.add(PLAYER_BLOCK_POS_Z_PATH, this.playerBlockPos.z);
toml.save(); toml.save();
} }
@@ -138,7 +138,7 @@ public class SubDimensionPlayerData
@Override @Override
public String toString() public String toString()
{ {
return "PlayerBlockPos: [" + this.playerBlockPos.getX() + "," + this.playerBlockPos.getY() + "," + this.playerBlockPos.getZ() + "]"; return "PlayerBlockPos: [" + this.playerBlockPos.x + "," + this.playerBlockPos.y + "," + this.playerBlockPos.z + "]";
} }
} }
@@ -33,7 +33,7 @@ public class AdjacentChunkHolder
{ {
for (int zOffset = -1; zOffset <= 1; zOffset++) for (int zOffset = -1; zOffset <= 1; zOffset++)
{ {
DhChunkPos adjacentPos = new DhChunkPos(centerChunkPos.getX() + xOffset, centerChunkPos.getZ() + zOffset); DhChunkPos adjacentPos = new DhChunkPos(centerChunkPos.x + xOffset, centerChunkPos.z + zOffset);
requestedAdjacentPositions.add(adjacentPos); requestedAdjacentPositions.add(adjacentPos);
} }
} }
@@ -69,13 +69,13 @@ public class AdjacentChunkHolder
DhChunkPos centerPos = this.chunkArray[4].getChunkPos(); DhChunkPos centerPos = this.chunkArray[4].getChunkPos();
DhChunkPos offsetPos = centerWrapper.getChunkPos(); DhChunkPos offsetPos = centerWrapper.getChunkPos();
int offsetX = offsetPos.getX() - centerPos.getX(); int offsetX = offsetPos.x - centerPos.x;
if (offsetX < -1 || offsetX > 1) if (offsetX < -1 || offsetX > 1)
{ {
return; return;
} }
int offsetZ = offsetPos.getZ() - centerPos.getZ(); int offsetZ = offsetPos.z - centerPos.z;
if (offsetZ < -1 || offsetZ > 1) if (offsetZ < -1 || offsetZ > 1)
{ {
return; return;
@@ -91,18 +91,18 @@ public class AdjacentChunkHolder
int chunkZ = BitShiftUtil.divideByPowerOfTwo(blockZ, 4); int chunkZ = BitShiftUtil.divideByPowerOfTwo(blockZ, 4);
IChunkWrapper centerChunk = this.chunkArray[4]; IChunkWrapper centerChunk = this.chunkArray[4];
DhChunkPos centerPos = centerChunk.getChunkPos(); DhChunkPos centerPos = centerChunk.getChunkPos();
if (centerPos.getX() == chunkX && centerPos.getZ() == chunkZ) if (centerPos.x == chunkX && centerPos.z == chunkZ)
{ {
return centerChunk; return centerChunk;
} }
int offsetX = chunkX - centerPos.getX(); int offsetX = chunkX - centerPos.x;
if (offsetX < -1 || offsetX > 1) if (offsetX < -1 || offsetX > 1)
{ {
return null; return null;
} }
int offsetZ = chunkZ - centerPos.getZ(); int offsetZ = chunkZ - centerPos.z;
if (offsetZ < -1 || offsetZ > 1) if (offsetZ < -1 || offsetZ > 1)
{ {
return null; return null;
@@ -47,6 +47,15 @@ public class BatchGenerator implements IDhApiWorldGenerator
private static final IWrapperFactory FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class); private static final IWrapperFactory FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final Logger LOGGER = DhLoggerBuilder.getLogger();
/**
* Defines how many tasks can be queued per thread. <br><br>
*
* TODO the multiplier here should change dynamically based on how fast the generator is vs the queuing thread,
* if this is too high it may cause issues when moving,
* but if it is too low the generator threads won't have enough tasks to work on
*/
private static final int MAX_QUEUED_TASKS_PER_THREAD = 3;
public AbstractBatchGenerationEnvironmentWrapper generationEnvironment; public AbstractBatchGenerationEnvironmentWrapper generationEnvironment;
public IDhLevel targetDhLevel; public IDhLevel targetDhLevel;
@@ -100,23 +109,17 @@ public class BatchGenerator implements IDhApiWorldGenerator
int chunkPosMinX, int chunkPosMinZ, byte granularity, byte targetDataDetail, EDhApiDistantGeneratorMode generatorMode, int chunkPosMinX, int chunkPosMinZ, byte granularity, byte targetDataDetail, EDhApiDistantGeneratorMode generatorMode,
ExecutorService worldGeneratorThreadPool, Consumer<Object[]> resultConsumer) ExecutorService worldGeneratorThreadPool, Consumer<Object[]> resultConsumer)
{ {
EDhApiWorldGenerationStep targetStep = null; EDhApiWorldGenerationStep targetStep = switch (generatorMode)
switch (generatorMode)
{ {
case PRE_EXISTING_ONLY: // Only load in existing chunks. Note: this requires the biome generation step in order for biomes to be properly initialized. case PRE_EXISTING_ONLY -> // Only load in existing chunks. Note: this requires the biome generation step in order for biomes to be properly initialized.
//case BIOME_ONLY: // No blocks. Require fake height in LodBuilder //case BIOME_ONLY: // No blocks. Require fake height in LodBuilder
targetStep = EDhApiWorldGenerationStep.BIOMES; EDhApiWorldGenerationStep.BIOMES;
break;
//case BIOME_ONLY_SIMULATE_HEIGHT: //case BIOME_ONLY_SIMULATE_HEIGHT:
// targetStep = EDhApiWorldGenerationStep.NOISE; // Stone only. Requires a fake surface // targetStep = EDhApiWorldGenerationStep.NOISE; // Stone only. Requires a fake surface
// break; // break;
case SURFACE: case SURFACE -> EDhApiWorldGenerationStep.SURFACE;
targetStep = EDhApiWorldGenerationStep.SURFACE; case FEATURES -> EDhApiWorldGenerationStep.FEATURES;
break; };
case FEATURES:
targetStep = EDhApiWorldGenerationStep.FEATURES;
break;
}
int genChunkSize = BitShiftUtil.powerOfTwo(granularity - 4); // minus 4 is equal to dividing by 16 to convert to chunk scale int genChunkSize = BitShiftUtil.powerOfTwo(granularity - 4); // minus 4 is equal to dividing by 16 to convert to chunk scale
@@ -138,6 +141,14 @@ public class BatchGenerator implements IDhApiWorldGenerator
@Override @Override
public void preGeneratorTaskStart() { this.generationEnvironment.updateAllFutures(); } public void preGeneratorTaskStart() { this.generationEnvironment.updateAllFutures(); }
@Override
public boolean isBusy()
{
int worldGenThreadCount = Math.max(Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads.get(), 1);
int maxWorldGenTaskCount = worldGenThreadCount * MAX_QUEUED_TASKS_PER_THREAD;
return this.generationEnvironment.getEventCount() > maxWorldGenTaskCount;
}
//=========// //=========//
@@ -21,17 +21,13 @@ package com.seibel.distanthorizons.core.generation;
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.DhBlockPos;
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.render.renderer.DebugRenderer;
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;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import java.awt.*;
import java.util.*; import java.util.*;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@@ -53,28 +49,15 @@ public class DhLightingEngine
* 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
* only create as many of these {@link DhBlockPos} as necessary. * only create as many of these {@link DhBlockPos} as necessary.
*/ */
private static final ThreadLocal<DhBlockPosMutable> PRIMARY_BLOCK_POS_REF = ThreadLocal.withInitial(() -> new DhBlockPosMutable()); private static final ThreadLocal<DhBlockPos> PRIMARY_BLOCK_POS_REF = ThreadLocal.withInitial(DhBlockPos::new);
private static final ThreadLocal<DhBlockPosMutable> SECONDARY_BLOCK_POS_REF = ThreadLocal.withInitial(() -> new DhBlockPosMutable()); private static final ThreadLocal<DhBlockPos> SECONDARY_BLOCK_POS_REF = ThreadLocal.withInitial(DhBlockPos::new);
/** if enabled will render each block light value when the lighting engine is run */
private static final boolean RENDER_BLOCK_LIGHT_WIREFRAME = false;
/** if enabled will render each sky light value when the lighting engine is run */
private static final boolean RENDER_SKY_LIGHT_WIREFRAME = false;
//=============//
// constructor //
//=============//
private DhLightingEngine() { } private DhLightingEngine() { }
//=========//
// methods //
//=========//
/** /**
* Note: depending on the implementation of {@link IChunkWrapper#setDhBlockLight(int, int, int, int)} and {@link IChunkWrapper#setDhSkyLight(int, int, int, int)} * Note: depending on the implementation of {@link IChunkWrapper#setDhBlockLight(int, int, int, int)} and {@link IChunkWrapper#setDhSkyLight(int, int, int, int)}
* the light values may be stored in the wrapper itself instead of the wrapped chunk object. * the light values may be stored in the wrapper itself instead of the wrapped chunk object.
@@ -93,12 +76,12 @@ public class DhLightingEngine
// try-finally to handle the stableArray resources // try-finally to handle the stableArray resources
StableLightPosStack blockLightWorldPosQueue = null; StableLightPosStack blockLightPosQueue = null;
StableLightPosStack skyLightWorldPosQueue = null; StableLightPosStack skyLightPosQueue = null;
try try
{ {
blockLightWorldPosQueue = StableLightPosStack.borrowStableLightPosArray(); blockLightPosQueue = StableLightPosStack.borrowStableLightPosArray();
skyLightWorldPosQueue = StableLightPosStack.borrowStableLightPosArray(); skyLightPosQueue = StableLightPosStack.borrowStableLightPosArray();
@@ -109,7 +92,7 @@ public class DhLightingEngine
{ {
for (int zOffset = -1; zOffset <= 1; zOffset++) for (int zOffset = -1; zOffset <= 1; zOffset++)
{ {
DhChunkPos adjacentPos = new DhChunkPos(centerChunkPos.getX() + xOffset, centerChunkPos.getZ() + zOffset); DhChunkPos adjacentPos = new DhChunkPos(centerChunkPos.x + xOffset, centerChunkPos.z + zOffset);
requestedAdjacentPositions.add(adjacentPos); requestedAdjacentPositions.add(adjacentPos);
} }
} }
@@ -131,14 +114,11 @@ public class DhLightingEngine
//==================//
// set block lights //
//==================//
// get and set the adjacent chunk's initial block lights // get and set the adjacent chunk's initial block lights
final DhBlockPosMutable relLightBlockPos = PRIMARY_BLOCK_POS_REF.get(); final DhBlockPos relLightBlockPos = PRIMARY_BLOCK_POS_REF.get();
final DhBlockPos relBlockPos = SECONDARY_BLOCK_POS_REF.get();
ArrayList<DhBlockPos> blockLightPosList = chunk.getWorldBlockLightPosList(); ArrayList<DhBlockPos> blockLightPosList = chunk.getBlockLightPosList();
for (int blockLightIndex = 0; blockLightIndex < blockLightPosList.size(); blockLightIndex++) // using iterators in high traffic areas can cause GC issues due to allocating a bunch of iterators, use an indexed for-loop instead for (int blockLightIndex = 0; blockLightIndex < blockLightPosList.size(); blockLightIndex++) // using iterators in high traffic areas can cause GC issues due to allocating a bunch of iterators, use an indexed for-loop instead
{ {
DhBlockPos blockLightPos = blockLightPosList.get(blockLightIndex); DhBlockPos blockLightPos = blockLightPosList.get(blockLightIndex);
@@ -147,18 +127,14 @@ public class DhLightingEngine
// get the light // get the light
IBlockStateWrapper blockState = chunk.getBlockState(relLightBlockPos); IBlockStateWrapper blockState = chunk.getBlockState(relLightBlockPos);
int lightValue = blockState.getLightEmission(); int lightValue = blockState.getLightEmission();
blockLightWorldPosQueue.push(blockLightPos.getX(), blockLightPos.getY(), blockLightPos.getZ(), lightValue); blockLightPosQueue.push(blockLightPos.x, blockLightPos.y, blockLightPos.z, lightValue);
// set the light // set the light
chunk.setDhBlockLight(relLightBlockPos.getX(), relLightBlockPos.getY(), relLightBlockPos.getZ(), lightValue); blockLightPos.mutateToChunkRelativePos(relBlockPos);
chunk.setDhBlockLight(relBlockPos.x, relBlockPos.y, relBlockPos.z, lightValue);
} }
//================//
// set sky lights //
//================//
// get and set the adjacent chunk's initial skylights, // get and set the adjacent chunk's initial skylights,
// if the dimension has skylights // if the dimension has skylights
if (maxSkyLight > 0) if (maxSkyLight > 0)
@@ -171,7 +147,7 @@ public class DhLightingEngine
{ {
for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++) for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++)
{ {
// set each pos' sky light all the way down until an opaque block is hit // set each pos' sky light all the way down until a opaque block is hit
for (int y = maxY; y >= minY; y--) for (int y = maxY; y >= minY; y--)
{ {
IBlockStateWrapper block = chunk.getBlockState(relX, y, relZ); IBlockStateWrapper block = chunk.getBlockState(relX, y, relZ);
@@ -184,11 +160,11 @@ public class DhLightingEngine
// add sky light to the queue // add sky light to the queue
DhBlockPos skyLightPos = new DhBlockPos(chunk.getMinBlockX() + relX, y, chunk.getMinBlockZ() + relZ); DhBlockPos skyLightPos = new DhBlockPos(chunk.getMinBlockX() + relX, y, chunk.getMinBlockZ() + relZ);
skyLightWorldPosQueue.push(skyLightPos.getX(), skyLightPos.getY(), skyLightPos.getZ(), maxSkyLight); skyLightPosQueue.push(skyLightPos.x, skyLightPos.y, skyLightPos.z, maxSkyLight);
// set the chunk's sky light // set the chunk's sky light
skyLightPos.mutateToChunkRelativePos(relLightBlockPos); skyLightPos.mutateToChunkRelativePos(relBlockPos);
chunk.setDhSkyLight(relLightBlockPos.getX(), relLightBlockPos.getY(), relLightBlockPos.getZ(), maxSkyLight); chunk.setDhSkyLight(relBlockPos.x, relBlockPos.y, relBlockPos.z, maxSkyLight);
} }
} }
} }
@@ -204,16 +180,14 @@ public class DhLightingEngine
} }
// block light // block light
this.propagateLightPosList(blockLightWorldPosQueue, adjacentChunkHolder, this.propagateLightPosList(blockLightPosQueue, adjacentChunkHolder,
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()), (neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.x, relBlockPos.y, relBlockPos.z),
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue), (neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.x, relBlockPos.y, relBlockPos.z, newLightValue));
true);
// sky light // sky light
this.propagateLightPosList(skyLightWorldPosQueue, adjacentChunkHolder, this.propagateLightPosList(skyLightPosQueue, adjacentChunkHolder,
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()), (neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.x, relBlockPos.y, relBlockPos.z),
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue), (neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.x, relBlockPos.y, relBlockPos.z, newLightValue));
false);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -221,8 +195,8 @@ public class DhLightingEngine
} }
finally finally
{ {
StableLightPosStack.returnStableLightPosArray(blockLightWorldPosQueue); StableLightPosStack.returnStableLightPosArray(blockLightPosQueue);
StableLightPosStack.returnStableLightPosArray(skyLightWorldPosQueue); StableLightPosStack.returnStableLightPosArray(skyLightPosQueue);
} }
@@ -238,14 +212,13 @@ public class DhLightingEngine
/** Applies each {@link LightPos} from the queue to the given set of {@link IChunkWrapper}'s. */ /** Applies each {@link LightPos} from the queue to the given set of {@link IChunkWrapper}'s. */
private void propagateLightPosList( private void propagateLightPosList(
StableLightPosStack lightPosQueue, AdjacentChunkHolder adjacentChunkHolder, StableLightPosStack lightPosQueue, AdjacentChunkHolder adjacentChunkHolder,
IGetLightFunc getLightFunc, ISetLightFunc setLightFunc, IGetLightFunc getLightFunc, ISetLightFunc setLightFunc)
boolean propagatingBlockLights)
{ {
// these objects are saved so they can be mutated throughout the method, // these objects are saved so they can be mutated throughout the method,
// this reduces the number of allocations necessary, reducing GC pressure // this reduces the number of allocations necessary, reducing GC pressure
final LightPos lightPos = new LightPos(0, 0, 0, 0); final LightPos lightPos = new LightPos(0, 0, 0, 0);
final DhBlockPosMutable neighbourBlockPos = PRIMARY_BLOCK_POS_REF.get(); final DhBlockPos neighbourBlockPos = PRIMARY_BLOCK_POS_REF.get();
final DhBlockPosMutable relNeighbourBlockPos = SECONDARY_BLOCK_POS_REF.get(); final DhBlockPos relNeighbourBlockPos = SECONDARY_BLOCK_POS_REF.get();
// update each light position // update each light position
@@ -266,14 +239,14 @@ public class DhLightingEngine
// only continue if the light position is inside one of our chunks // only continue if the light position is inside one of our chunks
IChunkWrapper neighbourChunk = adjacentChunkHolder.getByBlockPos(neighbourBlockPos.getX(), neighbourBlockPos.getZ()); IChunkWrapper neighbourChunk = adjacentChunkHolder.getByBlockPos(neighbourBlockPos.x, neighbourBlockPos.z);
if (neighbourChunk == null) if (neighbourChunk == null)
{ {
// the light pos is outside our generator's range, ignore it // the light pos is outside our generator's range, ignore it
continue; continue;
} }
if (relNeighbourBlockPos.getY() < neighbourChunk.getMinNonEmptyHeight() || relNeighbourBlockPos.getY() > neighbourChunk.getMaxBuildHeight()) if (relNeighbourBlockPos.y < neighbourChunk.getMinNonEmptyHeight() || relNeighbourBlockPos.y > neighbourChunk.getMaxBuildHeight())
{ {
// the light pos is outside the chunk's min/max height, // the light pos is outside the chunk's min/max height,
// this can happen if given a chunk that hasn't finished generating // this can happen if given a chunk that hasn't finished generating
@@ -300,103 +273,16 @@ public class DhLightingEngine
// now that light has been propagated to this blockPos // now that light has been propagated to this blockPos
// we need to queue it up so its neighbours can be propagated as well // we need to queue it up so its neighbours can be propagated as well
lightPosQueue.push(neighbourBlockPos.getX(), neighbourBlockPos.getY(), neighbourBlockPos.getZ(), targetLevel); lightPosQueue.push(neighbourBlockPos.x, neighbourBlockPos.y, neighbourBlockPos.z, targetLevel);
} }
} }
} }
// can be enable if troubleshooting lighting issues
if (RENDER_BLOCK_LIGHT_WIREFRAME && propagatingBlockLights)
{
RenderDhLightValuesAsWireframe(adjacentChunkHolder, true);
}
else if (RENDER_SKY_LIGHT_WIREFRAME && !propagatingBlockLights)
{
RenderDhLightValuesAsWireframe(adjacentChunkHolder, false);
}
// propagation complete // propagation complete
} }
//===========//
// debugging //
//===========//
/** Draw a wireframe representing each block's light value */
private static void RenderDhLightValuesAsWireframe(AdjacentChunkHolder adjacentChunkHolder, boolean renderBlockLights)
{
for (IChunkWrapper chunk : adjacentChunkHolder.chunkArray)
{
if (chunk == null)
{
continue;
}
int chunkMinX = chunk.getMinBlockX();
int chunkMinZ = chunk.getMinBlockZ();
int minY = chunk.getMinNonEmptyHeight();
int maxY = chunk.getMaxNonEmptyHeight();
// check each position's light
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
{
for (int y = minY; y < maxY; y++)
{
int lightValue = renderBlockLights? chunk.getDhBlockLight(x, y, z) : chunk.getDhSkyLight(x, y, z);
if (lightValue != LodUtil.MIN_MC_LIGHT)
{
// hotter colors for more intense light
Color color;
if (lightValue >= 14)
{
color = Color.WHITE;
}
else if (lightValue >= 10)
{
color = Color.PINK;
}
else if (lightValue >= 6)
{
color = Color.YELLOW;
}
else if (lightValue >= 4)
{
color = Color.ORANGE;
}
else
{
color = Color.RED;
}
// a color can be set to null if you only want to troubleshoot up to a certain light level
if (color != null)
{
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(DhSectionPos.encode((byte) 0, chunkMinX + x, chunkMinZ + z), y, y + 1, 0.2f, color),
10.0, 0f
)
);
}
}
}
}
}
}
}
//================// //================//
// helper classes // // helper classes //
//================// //================//
@@ -407,7 +293,7 @@ public class DhLightingEngine
@FunctionalInterface @FunctionalInterface
interface ISetLightFunc { void setLight(IChunkWrapper chunk, DhBlockPos pos, int lightValue); } interface ISetLightFunc { void setLight(IChunkWrapper chunk, DhBlockPos pos, int lightValue); }
private static class LightPos extends DhBlockPosMutable private static class LightPos extends DhBlockPos
{ {
public int lightValue; public int lightValue;
@@ -417,11 +303,6 @@ public class DhLightingEngine
this.lightValue = lightValue; this.lightValue = lightValue;
} }
@Override
public String toString() { return this.lightValue+" - ["+ this.x +", "+ this.y +", "+ this.z +"]"; }
} }
/** /**
@@ -521,9 +402,9 @@ public class DhLightingEngine
{ {
int subIndex = this.index * INTS_PER_LIGHT_POS; int subIndex = this.index * INTS_PER_LIGHT_POS;
pos.setX(this.lightPositions.getInt(subIndex)); pos.x = this.lightPositions.getInt(subIndex);
pos.setY(this.lightPositions.getInt(subIndex + 1)); pos.y = this.lightPositions.getInt(subIndex + 1);
pos.setZ(this.lightPositions.getInt(subIndex + 2)); pos.z = this.lightPositions.getInt(subIndex + 2);
pos.lightValue = this.lightPositions.getInt(subIndex + 3); pos.lightValue = this.lightPositions.getInt(subIndex + 3);
this.index--; this.index--;
@@ -21,7 +21,7 @@ package com.seibel.distanthorizons.core.generation;
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker; import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult; import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.LodQuadTree; import com.seibel.distanthorizons.core.render.LodQuadTree;
@@ -27,7 +27,7 @@ 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.generation.tasks.*; import com.seibel.distanthorizons.core.generation.tasks.*;
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.DhBlockPos2D;
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.config.Config; import com.seibel.distanthorizons.core.config.Config;
@@ -55,16 +55,6 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class); private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
/**
* Defines how many tasks can be queued per thread. <br><br>
*
* TODO the multiplier here should change dynamically based on how fast the generator is vs the queuing thread,
* if this is too high it may cause issues when moving,
* but if it is too low the generator threads won't have enough tasks to work on
*/
private static final int MAX_QUEUED_TASKS_PER_THREAD = 3;
private final IDhApiWorldGenerator generator; private final IDhApiWorldGenerator generator;
/** contains the positions that need to be generated */ /** contains the positions that need to be generated */
@@ -216,7 +206,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
// queue generation tasks until the generator is full, or there are no more tasks to generate // queue generation tasks until the generator is full, or there are no more tasks to generate
boolean taskStarted = true; boolean taskStarted = true;
while (!this.isGeneratorBusy() && taskStarted) while (!this.generator.isBusy() && taskStarted)
{ {
taskStarted = this.startNextWorldGenTask(this.generationTargetPos); taskStarted = this.startNextWorldGenTask(this.generationTargetPos);
if (!taskStarted) if (!taskStarted)
@@ -243,19 +233,6 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
} }
}); });
} }
public boolean isGeneratorBusy()
{
ThreadPoolExecutor executor = ThreadPoolUtil.getWorldGenExecutor();
if (executor == null)
{
// shouldn't happen, but just in case, don't queue more tasks
return true;
}
int worldGenThreadCount = Math.max(Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads.get(), 1);
int maxWorldGenTaskCount = worldGenThreadCount * MAX_QUEUED_TASKS_PER_THREAD;
return executor.getQueue().size() > maxWorldGenTaskCount;
}
/** /**
* @param targetPos the position to center the generation around * @param targetPos the position to center the generation around
@@ -451,18 +428,16 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
{ {
EDhApiDistantGeneratorMode generatorMode = Config.Client.Advanced.WorldGenerator.distantGeneratorMode.get(); EDhApiDistantGeneratorMode generatorMode = Config.Client.Advanced.WorldGenerator.distantGeneratorMode.get();
EDhApiWorldGeneratorReturnType returnType = this.generator.getReturnType(); EDhApiWorldGeneratorReturnType returnType = this.generator.getReturnType();
switch (returnType) return switch (returnType)
{ {
case VANILLA_CHUNKS: case VANILLA_CHUNKS -> this.generator.generateChunks(
{ chunkPosMin.x,
return this.generator.generateChunks( chunkPosMin.z,
chunkPosMin.getX(),
chunkPosMin.getZ(),
granularity, granularity,
targetDataDetail, targetDataDetail,
generatorMode, generatorMode,
ThreadPoolUtil.getWorldGenExecutor(), ThreadPoolUtil.getWorldGenExecutor(),
(Object[] generatedObjectArray) -> (Object[] generatedObjectArray) ->
{ {
try try
{ {
@@ -477,13 +452,10 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
Config.Client.Advanced.WorldGenerator.enableDistantGeneration.set(false); Config.Client.Advanced.WorldGenerator.enableDistantGeneration.set(false);
} }
} }
); );
} case API_CHUNKS -> this.generator.generateApiChunks(
case API_CHUNKS: chunkPosMin.x,
{ chunkPosMin.z,
return this.generator.generateApiChunks(
chunkPosMin.getX(),
chunkPosMin.getZ(),
granularity, granularity,
targetDataDetail, targetDataDetail,
generatorMode, generatorMode,
@@ -492,10 +464,10 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
{ {
try try
{ {
FullDataSourceV2 dataSource = LodDataBuilder.createFromApiChunkData(dataPoints, this.generator.runApiChunkValidation()); FullDataSourceV2 dataSource = LodDataBuilder.createFromApiChunkData(dataPoints);
chunkDataConsumer.accept(dataSource); chunkDataConsumer.accept(dataSource);
} }
catch (DataCorruptedException | IllegalArgumentException e) catch (DataCorruptedException e)
{ {
LOGGER.error("World generator returned a corrupt chunk. Error: [" + e.getMessage() + "]. World generator disabled.", e); LOGGER.error("World generator returned a corrupt chunk. Error: [" + e.getMessage() + "]. World generator disabled.", e);
Config.Client.Advanced.WorldGenerator.enableDistantGeneration.set(false); Config.Client.Advanced.WorldGenerator.enableDistantGeneration.set(false);
@@ -506,14 +478,13 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
Config.Client.Advanced.WorldGenerator.enableDistantGeneration.set(false); Config.Client.Advanced.WorldGenerator.enableDistantGeneration.set(false);
} }
} }
); );
} default ->
default:
{ {
Config.Client.Advanced.WorldGenerator.enableDistantGeneration.set(false); Config.Client.Advanced.WorldGenerator.enableDistantGeneration.set(false);
throw new AssertFailureException("Unknown return type: " + returnType); throw new AssertFailureException("Unknown return type: " + returnType);
} }
} };
} }
@@ -639,8 +610,8 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
@Override @Override
public void debugRender(DebugRenderer renderer) public void debugRender(DebugRenderer renderer)
{ {
this.waitingTasks.keySet().forEach((pos) -> { renderer.renderBox(new DebugRenderer.Box(pos, -32f, 64f, 0.05f, Color.blue)); }); this.waitingTasks.keySet().forEach((pos) -> renderer.renderBox(new DebugRenderer.Box(pos, -32f, 64f, 0.05f, Color.blue)));
this.inProgressGenTasksByLodPos.forEach((pos, t) -> { renderer.renderBox(new DebugRenderer.Box(pos, -32f, 64f, 0.05f, Color.red)); }); this.inProgressGenTasksByLodPos.forEach((pos, t) -> renderer.renderBox(new DebugRenderer.Box(pos, -32f, 64f, 0.05f, Color.red)));
} }
@@ -40,20 +40,14 @@ public class DarkModeDetector
public static boolean isDarkMode() public static boolean isDarkMode()
{ {
switch (EPlatform.get()) return switch (EPlatform.get())
{ {
case WINDOWS: case WINDOWS -> isWindowsDarkMode();
return isWindowsDarkMode(); case MACOS -> isMacOsDarkMode();
case MACOS: // Most Unix(-like) distros also use a lot of the same things as Linux (like desktop environments and window managers)
return isMacOsDarkMode(); case LINUX, BSD, UNIX -> checkLinuxDark();
case LINUX: default -> false;
// Most Unix(-like) distros also use a lot of the same things as Linux (like desktop environments and window managers) };
case BSD:
case UNIX:
return checkLinuxDark();
default:
return false;
}
} }
// Needs checking as I dont use Mac // Needs checking as I dont use Mac
@@ -131,7 +131,7 @@ public class JarMain
// Selected download // Selected download
AtomicReference<String> downloadID = new AtomicReference<String>(""); AtomicReference<String> downloadID = new AtomicReference<>("");
// This is for the panel to show the update description // This is for the panel to show the update description
@@ -175,17 +175,12 @@ public class JarUtils
@Deprecated @Deprecated
public static OperatingSystem getOperatingSystem() public static OperatingSystem getOperatingSystem()
{ // Get the os and turn it into that enum { // Get the os and turn it into that enum
switch (EPlatform.get()) return switch (EPlatform.get()) {
{ case WINDOWS -> OperatingSystem.WINDOWS;
case WINDOWS: case LINUX -> OperatingSystem.LINUX;
return OperatingSystem.WINDOWS; case MACOS -> OperatingSystem.MACOS;
case LINUX: default -> OperatingSystem.NONE;
return OperatingSystem.LINUX; };
case MACOS:
return OperatingSystem.MACOS;
default:
return OperatingSystem.NONE;
}
} }
} }
@@ -89,7 +89,7 @@ public class BaseJFrame extends JFrame
final BufferedReader br = new BufferedReader(isr) final BufferedReader br = new BufferedReader(isr)
) )
{ {
List<Object> col = Collections.unmodifiableList(new ArrayList<>(Arrays.asList(br.lines().toArray()))); List<Object> col = List.of(br.lines().toArray());
for (Object obj : col) for (Object obj : col)
{ {
langsToChoose.add(((String) obj).replaceAll("\\.json", "")); langsToChoose.add(((String) obj).replaceAll("\\.json", ""));
@@ -101,7 +101,7 @@ public class BaseJFrame extends JFrame
} }
// Creates the box // Creates the box
JComboBox<String> languageBox = new JComboBox(new DefaultComboBoxModel(langsToChoose.toArray())); JComboBox<String> languageBox = new JComboBox<>(new DefaultComboBoxModel(langsToChoose.toArray()));
languageBox.setSelectedIndex(langsToChoose.indexOf(Locale.getDefault().toString().toLowerCase())); languageBox.setSelectedIndex(langsToChoose.indexOf(Locale.getDefault().toString().toLowerCase()));
languageBox.addActionListener(e -> { languageBox.addActionListener(e -> {
Locale.setDefault(Locale.forLanguageTag(languageBox.getSelectedItem().toString())); // Change lang on update Locale.setDefault(Locale.forLanguageTag(languageBox.getSelectedItem().toString())); // Change lang on update
@@ -133,7 +133,7 @@ public class GitlabGetter
public static void main(String[] args) { public static void main(String[] args) {
GitlabGetter gitlabGetter = new GitlabGetter(); GitlabGetter gitlabGetter = new GitlabGetter();
System.out.println(gitlabGetter.getDownloads(gitlabGetter.projectPipelines.get(0).get("id"))); System.out.println(gitlabGetter.getDownloads(gitlabGetter.projectPipelines.getFirst().get("id")));
} }
@@ -72,8 +72,8 @@ public class ModrinthGetter
downloadUrl.put(workingID, downloadUrl.put(workingID,
new URL( new URL(
((Config) ((Config)
((ArrayList) currentRelease.get("files")) ((ArrayList<?>) currentRelease.get("files"))
.get(0)) .getFirst())
.get("url") .get("url")
.toString() .toString()
)); ));
@@ -112,7 +112,7 @@ public class ModrinthGetter
{ {
try try
{ {
return mcVerToReleaseID.get(mcVer).get(0); return mcVerToReleaseID.get(mcVer).getFirst();
} }
catch (Exception e) catch (Exception e)
{ {
@@ -121,17 +121,17 @@ public class ModrinthGetter
} }
public static String getLatestNameForVersion(String mcVer) public static String getLatestNameForVersion(String mcVer)
{ {
return releaseNames.get(mcVerToReleaseID.get(mcVer).get(0)); return releaseNames.get(mcVerToReleaseID.get(mcVer).getFirst());
} }
public static URL getLatestDownloadForVersion(String mcVer) public static URL getLatestDownloadForVersion(String mcVer)
{ {
return downloadUrl.get(mcVerToReleaseID.get(mcVer).get(0)); return downloadUrl.get(mcVerToReleaseID.get(mcVer).getFirst());
} }
public static String getLatestShaForVersion(String mcVer) public static String getLatestShaForVersion(String mcVer)
{ {
return (((ArrayList<Config>) idToJson.get( return (((ArrayList<Config>) idToJson.get(
mcVerToReleaseID.get(mcVer).get(0) mcVerToReleaseID.get(mcVer).getFirst()
).get("files")).get(0).get("hashes.sha1") ).get("files")).getFirst().get("hashes.sha1")
.toString()); .toString());
} }
@@ -136,7 +136,7 @@ public class SelfUpdater
{ {
if (GitlabGetter.INSTANCE.projectPipelines.size() == 0) if (GitlabGetter.INSTANCE.projectPipelines.size() == 0)
return false; return false;
com.electronwill.nightconfig.core.Config pipeline = GitlabGetter.INSTANCE.projectPipelines.get(0); com.electronwill.nightconfig.core.Config pipeline = GitlabGetter.INSTANCE.projectPipelines.getFirst();
if (!pipeline.get("ref").equals(ModJarInfo.Git_Branch)) if (!pipeline.get("ref").equals(ModJarInfo.Git_Branch))
{ {
@@ -186,16 +186,13 @@ public class SelfUpdater
} }
public static boolean updateMod(String minecraftVersion, File file) public static boolean updateMod(String minecraftVersion, File file)
{ {
boolean returnValue = false; boolean returnValue = switch (Config.Client.Advanced.AutoUpdater.updateBranch.get())
switch (Config.Client.Advanced.AutoUpdater.updateBranch.get())
{ {
case STABLE: case STABLE -> updateStableMod(minecraftVersion, file);
returnValue = updateStableMod(minecraftVersion, file); case NIGHTLY -> updateNightlyMod(minecraftVersion, file);
break; default -> false;
case NIGHTLY:
returnValue = updateNightlyMod(minecraftVersion, file);
break;
}; };
;
return returnValue; return returnValue;
} }
@@ -253,7 +250,7 @@ public class SelfUpdater
File mergedZip = file.getParentFile().toPath().resolve("merged.zip").toFile(); File mergedZip = file.getParentFile().toPath().resolve("merged.zip").toFile();
WebDownloader.downloadAsFile(GitlabGetter.INSTANCE.getDownloads(GitlabGetter.INSTANCE.projectPipelines.get(0).get("id")).get(minecraftVersion), mergedZip); WebDownloader.downloadAsFile(GitlabGetter.INSTANCE.getDownloads(GitlabGetter.INSTANCE.projectPipelines.getFirst().get("id")).get(minecraftVersion), mergedZip);
ZipInputStream zis = new ZipInputStream(new FileInputStream(mergedZip)); ZipInputStream zis = new ZipInputStream(new FileInputStream(mergedZip));
ZipEntry zipEntry = zis.getNextEntry(); ZipEntry zipEntry = zis.getNextEntry();
@@ -301,7 +298,7 @@ public class SelfUpdater
} }
catch (Exception e) catch (Exception e)
{ {
LOGGER.warn("Failed to update " + ModInfo.READABLE_NAME + " to version " + GitlabGetter.INSTANCE.projectPipelines.get(0).get("sha")); LOGGER.warn("Failed to update " + ModInfo.READABLE_NAME + " to version " + GitlabGetter.INSTANCE.projectPipelines.getFirst().get("sha"));
e.printStackTrace(); e.printStackTrace();
return false; return false;
} }
@@ -19,19 +19,28 @@
package com.seibel.distanthorizons.core.level; package com.seibel.distanthorizons.core.level;
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.objects.math.DhApiVec3f;
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.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dataObjects.transformers.ChunkToLodBuilder;
import com.seibel.distanthorizons.core.file.fullDatafile.DelayedFullDataSourceSaveCache; import com.seibel.distanthorizons.core.file.fullDatafile.DelayedFullDataSourceSaveCache;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
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.generic.BeaconRenderHandler; import com.seibel.distanthorizons.core.render.renderer.generic.BeaconRenderHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.CloudRenderHandler; 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.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.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.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector; import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@@ -40,14 +49,18 @@ import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class AbstractDhLevel implements IDhLevel public abstract class AbstractDhLevel implements IDhLevel
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public final ChunkToLodBuilder chunkToLodBuilder;
/** if this is null then the other handler is probably null too, but just in case */ /** if this is null then the other handler is probably null too, but just in case */
@Nullable @Nullable
public ChunkHashRepo chunkHashRepo; public ChunkHashRepo chunkHashRepo;
@@ -55,10 +68,9 @@ public abstract class AbstractDhLevel implements IDhLevel
@Nullable @Nullable
public BeaconBeamRepo beaconBeamRepo; public BeaconBeamRepo beaconBeamRepo;
protected final DelayedFullDataSourceSaveCache delayedFullDataSourceSaveCache = new DelayedFullDataSourceSaveCache(this::onDataSourceSave, 500); protected final DelayedFullDataSourceSaveCache delayedFullDataSourceSaveCache = new DelayedFullDataSourceSaveCache(this::onDataSourceSave, 2_000);
/** contains the {@link DhChunkPos} for each {@link DhSectionPos} that are queued to save via {@link AbstractDhLevel#delayedFullDataSourceSaveCache} */ /** contains the {@link DhChunkPos} for each {@link DhSectionPos} that are queued to save via {@link AbstractDhLevel#delayedFullDataSourceSaveCache} */
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<>();
/** Will be null if clouds shouldn't be rendered for this level. */ /** Will be null if clouds shouldn't be rendered for this level. */
@Nullable @Nullable
@@ -71,7 +83,10 @@ public abstract class AbstractDhLevel implements IDhLevel
// constructor // // constructor //
//=============// //=============//
protected AbstractDhLevel() { } protected AbstractDhLevel()
{
this.chunkToLodBuilder = new ChunkToLodBuilder();
}
/** /**
* Creating the repos requires access to the level file, which isn't * Creating the repos requires access to the level file, which isn't
@@ -111,15 +126,11 @@ public abstract class AbstractDhLevel implements IDhLevel
GenericObjectRenderer genericRenderer = this.getGenericRenderer(); GenericObjectRenderer genericRenderer = this.getGenericRenderer();
if (genericRenderer != null) if (genericRenderer != null)
{ {
// only client levels can render clouds // only add clouds for certain dimension types
if (this instanceof IDhClientLevel) if (!this.getLevelWrapper().hasCeiling()
&& !this.getLevelWrapper().getDimensionType().isTheEnd())
{ {
// only add clouds for certain dimension types this.cloudRenderHandler = new CloudRenderHandler(this, genericRenderer);
if (!this.getLevelWrapper().hasCeiling()
&& !this.getLevelWrapper().getDimensionType().isTheEnd())
{
this.cloudRenderHandler = new CloudRenderHandler((IDhClientLevel)this, genericRenderer);
}
} }
@@ -141,7 +152,7 @@ public abstract class AbstractDhLevel implements IDhLevel
public int getUnsavedDataSourceCount() { return this.delayedFullDataSourceSaveCache.getUnsavedCount(); } public int getUnsavedDataSourceCount() { return this.delayedFullDataSourceSaveCache.getUnsavedCount(); }
@Override @Override
public void updateChunkAsync(IChunkWrapper chunkWrapper, int chunkHash) public void updateChunkAsync(IChunkWrapper chunkWrapper)
{ {
FullDataSourceV2 dataSource = FullDataSourceV2.createFromChunk(chunkWrapper); FullDataSourceV2 dataSource = FullDataSourceV2.createFromChunk(chunkWrapper);
if (dataSource == null) if (dataSource == null)
@@ -160,7 +171,6 @@ public abstract class AbstractDhLevel implements IDhLevel
chunkPosSet.add(chunkWrapper.getChunkPos()); chunkPosSet.add(chunkWrapper.getChunkPos());
return chunkPosSet; return chunkPosSet;
}); });
this.updatedChunkHashesByChunkPos.put(chunkWrapper.getChunkPos(), chunkHash);
// batch updates to reduce overhead when flying around or breaking/placing a lot of blocks in an area // batch updates to reduce overhead when flying around or breaking/placing a lot of blocks in an area
this.delayedFullDataSourceSaveCache.queueDataSourceForUpdateAndSave(dataSource); this.delayedFullDataSourceSaveCache.queueDataSourceForUpdateAndSave(dataSource);
@@ -175,16 +185,9 @@ public abstract class AbstractDhLevel implements IDhLevel
{ {
for (DhChunkPos chunkPos : updatedChunkPosSet) for (DhChunkPos chunkPos : updatedChunkPosSet)
{ {
// save after the data source has been updated to prevent saving the hash without the associated datasource
Integer chunkHash = this.updatedChunkHashesByChunkPos.remove(chunkPos);
if (this.chunkHashRepo != null && chunkHash != null)
{
this.chunkHashRepo.save(new ChunkHashDTO(chunkPos, chunkHash));
}
ApiEventInjector.INSTANCE.fireAllEvents( ApiEventInjector.INSTANCE.fireAllEvents(
DhApiChunkModifiedEvent.class, DhApiChunkModifiedEvent.class,
new DhApiChunkModifiedEvent.EventParam(this.getLevelWrapper(), chunkPos.getX(), chunkPos.getZ())); new DhApiChunkModifiedEvent.EventParam(this.getLevelWrapper(), chunkPos.x, chunkPos.z));
} }
} }
}); });
@@ -209,6 +212,14 @@ public abstract class AbstractDhLevel implements IDhLevel
ChunkHashDTO dto = this.chunkHashRepo.getByKey(pos); ChunkHashDTO dto = this.chunkHashRepo.getByKey(pos);
return (dto != null) ? dto.chunkHash : 0; return (dto != null) ? dto.chunkHash : 0;
} }
@Override
public void setChunkHash(DhChunkPos pos, int chunkHash)
{
if (this.chunkHashRepo != null)
{
this.chunkHashRepo.save(new ChunkHashDTO(pos, chunkHash));
}
}
@@ -217,12 +228,11 @@ public abstract class AbstractDhLevel implements IDhLevel
//=================// //=================//
@Override @Override
public void updateBeaconBeamsForChunk(IChunkWrapper chunkToUpdate, ArrayList<IChunkWrapper> nearbyChunkList) public void setBeaconBeamsForChunk(DhChunkPos chunkPos, List<BeaconBeamDTO> newBeamList)
{ {
if (this.beaconRenderHandler != null) if (this.beaconRenderHandler != null)
{ {
List<BeaconBeamDTO> activeBeamList = chunkToUpdate.getAllActiveBeacons(nearbyChunkList); this.beaconRenderHandler.setBeaconBeamsForChunk(chunkPos, newBeamList);
this.beaconRenderHandler.setBeaconBeamsForChunk(chunkToUpdate.getChunkPos(), activeBeamList);
} }
} }
@@ -252,6 +262,8 @@ public abstract class AbstractDhLevel implements IDhLevel
@Override @Override
public void close() public void close()
{ {
this.chunkToLodBuilder.close();
if (this.chunkHashRepo != null) if (this.chunkHashRepo != null)
{ {
this.chunkHashRepo.close(); this.chunkHashRepo.close();
@@ -27,7 +27,7 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.file.AbstractDataSourceHandler; import com.seibel.distanthorizons.core.file.AbstractDataSourceHandler;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2; import com.seibel.distanthorizons.core.file.fullDatafile.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.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.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.render.renderer.generic.GenericObjectRenderer;
@@ -258,8 +258,6 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
public void clearRenderCache() public void clearRenderCache()
{ {
this.clientLevel.getClientLevelWrapper().clearBlockColorCache();
ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState != null && ClientRenderState.quadtree != null) if (ClientRenderState != null && ClientRenderState.quadtree != null)
{ {
@@ -25,7 +25,7 @@ import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV
import com.seibel.distanthorizons.core.file.fullDatafile.RemoteFullDataSourceProvider; import com.seibel.distanthorizons.core.file.fullDatafile.RemoteFullDataSourceProvider;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure; import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
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.DhBlockPos;
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.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
@@ -86,6 +86,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
{ {
try try
{ {
this.chunkToLodBuilder.tick();
this.clientside.clientTick(); this.clientside.clientTick();
} }
catch (Exception e) catch (Exception e)
@@ -109,7 +110,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
//================// //================//
@Override @Override
public int computeBaseColor(DhBlockPos pos, IBiomeWrapper biome, IBlockStateWrapper block) { return this.levelWrapper.getBlockColor(pos, biome, block); } public int computeBaseColor(DhBlockPos pos, IBiomeWrapper biome, IBlockStateWrapper block) { return this.levelWrapper.computeBaseColor(pos, biome, block); }
@Override @Override
public IClientLevelWrapper getClientLevelWrapper() { return this.levelWrapper; } public IClientLevelWrapper getClientLevelWrapper() { return this.levelWrapper; }
@@ -28,8 +28,8 @@ import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure; import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
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.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer; import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
@@ -95,7 +95,7 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
{ this.clientside.renderDeferred(renderEventParam, profiler); } { this.clientside.renderDeferred(renderEventParam, profiler); }
@Override @Override
public void serverTick() { } public void serverTick() { this.chunkToLodBuilder.tick(); }
@Override @Override
public void doWorldGen() public void doWorldGen()
@@ -146,7 +146,7 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
} }
else else
{ {
return clientLevel.getBlockColor(pos, biome, block); return clientLevel.computeBaseColor(pos, biome, block);
} }
} }
@@ -154,7 +154,10 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
public IClientLevelWrapper getClientLevelWrapper() { return MC_CLIENT.getWrappedClientLevel(); } public IClientLevelWrapper getClientLevelWrapper() { return MC_CLIENT.getWrappedClientLevel(); }
@Override @Override
public void clearRenderCache() { this.clientside.clearRenderCache(); } public void clearRenderCache()
{
clientside.clearRenderCache();
}
@Override @Override
public IServerLevelWrapper getServerLevelWrapper() { return serverLevelWrapper; } public IServerLevelWrapper getServerLevelWrapper() { return serverLevelWrapper; }
@@ -22,7 +22,7 @@ package com.seibel.distanthorizons.core.level;
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.FullDataSourceProviderV2; import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure; import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
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.render.renderer.generic.GenericObjectRenderer;
@@ -65,7 +65,7 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
// methods // // methods //
//=========// //=========//
public void serverTick() { } public void serverTick() { this.chunkToLodBuilder.tick(); }
@Override @Override
public CompletableFuture<Void> updateDataSourcesAsync(FullDataSourceV2 data) { return this.getFullDataProvider().updateDataSourceAsync(data); } public CompletableFuture<Void> updateDataSourcesAsync(FullDataSourceV2 data) { return this.getFullDataProvider().updateDataSourceAsync(data); }
@@ -20,7 +20,7 @@
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.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
@@ -25,11 +25,11 @@ import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
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.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; 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 org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -45,10 +45,11 @@ public interface IDhLevel extends AutoCloseable
/** @return 0 if no hash is known */ /** @return 0 if no hash is known */
int getChunkHash(DhChunkPos pos); int getChunkHash(DhChunkPos pos);
void updateChunkAsync(IChunkWrapper chunk, int newChunkHash); void setChunkHash(DhChunkPos pos, int chunkHash);
void updateChunkAsync(IChunkWrapper chunk);
void loadBeaconBeamsInPos(long pos); void loadBeaconBeamsInPos(long pos);
void updateBeaconBeamsForChunk(IChunkWrapper chunkToUpdate, ArrayList<IChunkWrapper> nearbyChunkList); void setBeaconBeamsForChunk(DhChunkPos chunkPos, List<BeaconBeamDTO> beamList);
void unloadBeaconBeamsInPos(long pos); void unloadBeaconBeamsInPos(long pos);
FullDataSourceProviderV2 getFullDataProvider(); FullDataSourceProviderV2 getFullDataProvider();
@@ -23,7 +23,7 @@ import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSource
import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue; import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue;
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.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import java.io.Closeable; import java.io.Closeable;
@@ -38,7 +38,7 @@ public class ConfigBasedLogger
public static final List<WeakReference<ConfigBasedLogger>> loggers public static final List<WeakReference<ConfigBasedLogger>> loggers
= Collections.synchronizedList(new LinkedList<WeakReference<ConfigBasedLogger>>()); = Collections.synchronizedList(new LinkedList<>());
public static synchronized void updateAll() public static synchronized void updateAll()
{ {
@@ -38,7 +38,7 @@ public class ConfigBasedSpamLogger
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
public static final List<WeakReference<ConfigBasedSpamLogger>> loggers public static final List<WeakReference<ConfigBasedSpamLogger>> loggers
= Collections.synchronizedList(new LinkedList<WeakReference<ConfigBasedSpamLogger>>()); = Collections.synchronizedList(new LinkedList<>());
public static synchronized void updateAll(boolean flush) public static synchronized void updateAll(boolean flush)
{ {
@@ -35,7 +35,7 @@ public class SpamReducedLogger
private static final Logger LOGGER = LogManager.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); private static final Logger LOGGER = LogManager.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
public static final List<WeakReference<SpamReducedLogger>> loggers public static final List<WeakReference<SpamReducedLogger>> loggers
= Collections.synchronizedList(new LinkedList<WeakReference<SpamReducedLogger>>()); = Collections.synchronizedList(new LinkedList<>());
public static synchronized void flushAll() public static synchronized void flushAll()
{ {
@@ -53,7 +53,7 @@ public class SpamReducedLogger
public SpamReducedLogger(int maxLogPerSec) public SpamReducedLogger(int maxLogPerSec)
{ {
maxLogCount = maxLogPerSec; maxLogCount = maxLogPerSec;
loggers.add(new WeakReference<SpamReducedLogger>(this)); loggers.add(new WeakReference<>(this));
} }
public void reset() public void reset()
@@ -72,9 +72,6 @@ public class F3Screen
ThreadPoolExecutor worldGenPool = ThreadPoolUtil.getWorldGenExecutor(); ThreadPoolExecutor worldGenPool = ThreadPoolUtil.getWorldGenExecutor();
ThreadPoolExecutor fileHandlerPool = ThreadPoolUtil.getFileHandlerExecutor(); ThreadPoolExecutor fileHandlerPool = ThreadPoolUtil.getFileHandlerExecutor();
ThreadPoolExecutor updatePool = ThreadPoolUtil.getUpdatePropagatorExecutor(); ThreadPoolExecutor updatePool = ThreadPoolUtil.getUpdatePropagatorExecutor();
ThreadPoolExecutor lodBuilderPool = ThreadPoolUtil.getChunkToLodBuilderExecutor();
ThreadPoolExecutor bufferBuilderPool = ThreadPoolUtil.getBufferBuilderExecutor();
ThreadPoolExecutor bufferUploaderPool = ThreadPoolUtil.getBufferUploaderExecutor();
AbstractDhWorld world = SharedApi.getAbstractDhWorld(); AbstractDhWorld world = SharedApi.getAbstractDhWorld();
Iterable<? extends IDhLevel> levelIterator = world.getAllLoadedLevels(); Iterable<? extends IDhLevel> levelIterator = world.getAllLoadedLevels();
@@ -87,9 +84,6 @@ public class F3Screen
messageList.add(getThreadPoolStatString("World Gen", worldGenPool));//"World Gen Tasks: 40/5304, (in progress: 7)"); messageList.add(getThreadPoolStatString("World Gen", worldGenPool));//"World Gen Tasks: 40/5304, (in progress: 7)");
messageList.add(getThreadPoolStatString("File Handler", fileHandlerPool)); messageList.add(getThreadPoolStatString("File Handler", fileHandlerPool));
messageList.add(getThreadPoolStatString("Update Propagator", updatePool)); messageList.add(getThreadPoolStatString("Update Propagator", updatePool));
messageList.add(getThreadPoolStatString("LOD Builder", lodBuilderPool));
messageList.add(getThreadPoolStatString("Buffer Builder", bufferBuilderPool));
messageList.add(getThreadPoolStatString("Buffer Uploader", bufferUploaderPool));
messageList.add(""); messageList.add("");
// chunk updates // chunk updates
messageList.add(SharedApi.INSTANCE.getDebugMenuString()); messageList.add(SharedApi.INSTANCE.getDebugMenuString());
@@ -0,0 +1,231 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.pos;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.util.LodUtil;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
public class DhBlockPos
{
public static final boolean DO_CHECKS = false;
// 26 bits wide as that just encompasses the maximum possible value
// of +- 30,000,000 blocks in each direction. Yes this packing method
// is how Minecraft packs it.
// NOTE: Remember to ALWAYS check that DHBlockPos packing is EXACTLY
// the same as Minecraft's!!!!
public static final int PACKED_X_LENGTH = 26;
public static final int PACKED_Z_LENGTH = 26;
public static final int PACKED_Y_LENGTH = 12;
public static final long PACKED_X_MASK = (1L << PACKED_X_LENGTH) - 1L;
public static final long PACKED_Y_MASK = (1L << PACKED_Y_LENGTH) - 1L;
public static final long PACKED_Z_MASK = (1L << PACKED_Z_LENGTH) - 1L;
public static final int PACKED_Y_OFFSET = 0;
public static final int PACKED_Z_OFFSET = PACKED_Y_LENGTH;
public static final int PACKED_X_OFFSET = PACKED_Y_LENGTH + PACKED_Z_LENGTH;
/** Useful for methods that need a position passed in but won't actually be used */
public static final DhBlockPos ZERO = new DhBlockPos(0, 0, 0);
public int x;
public int y;
public int z;
public DhBlockPos(int x, int y, int z)
{
this.x = x;
this.y = y;
this.z = z;
}
public DhBlockPos()
{
this(0, 0, 0);
}
public DhBlockPos(DhBlockPos pos)
{
this(pos.x, pos.y, pos.z);
}
public DhBlockPos(DhBlockPos2D pos, int y)
{
this(pos.x, y, pos.z);
}
public static long asLong(int x, int y, int z)
{
if (DO_CHECKS)
{
if ((x & ~PACKED_X_MASK) != 0)
{
throw new IllegalArgumentException("x is out of range: " + x);
}
if ((y & ~PACKED_Y_MASK) != 0)
{
throw new IllegalArgumentException("y is out of range: " + y);
}
if ((z & ~PACKED_Z_MASK) != 0)
{
throw new IllegalArgumentException("z is out of range: " + z);
}
}
return ((long) x & PACKED_X_MASK) << PACKED_X_OFFSET |
((long) y & PACKED_Y_MASK) << PACKED_Y_OFFSET |
((long) z & PACKED_Z_MASK) << PACKED_Z_OFFSET;
}
public static int getX(long packed)
{ // X is at the top
return (int) (packed << (64 - PACKED_X_OFFSET - PACKED_X_LENGTH) >> (64 - PACKED_X_LENGTH));
}
public static int getY(long packed)
{ // Y is at the bottom
return (int) (packed << (64 - PACKED_Y_OFFSET - PACKED_Y_LENGTH) >> (64 - PACKED_Y_LENGTH));
}
public static int getZ(long packed)
{ // Z is at the middle
return (int) (packed << (64 - PACKED_Z_OFFSET - PACKED_Z_LENGTH) >> (64 - PACKED_Z_LENGTH));
}
public DhBlockPos(long packed)
{
this(getX(packed), getY(packed), getZ(packed));
}
public long asLong()
{
return asLong(x, y, z);
}
/** creates a new {@link DhBlockPos} with the given offset from the current pos. */
public DhBlockPos offset(EDhDirection direction) { return this.mutateOffset(direction, null); }
/** if not null, mutates "mutablePos" so it matches the current pos after being offset. Otherwise creates a new {@link DhBlockPos}. */
public DhBlockPos mutateOffset(EDhDirection direction, @Nullable DhBlockPos mutablePos) { return this.mutateOffset(direction.getNormal().x, direction.getNormal().y, direction.getNormal().z, mutablePos); }
public DhBlockPos offset(int x, int y, int z) { return this.mutateOffset(x,y,z, null); }
public DhBlockPos mutateOffset(int x, int y, int z, @Nullable DhBlockPos mutablePos)
{
int newX = this.x + x;
int newY = this.y + y;
int newZ = this.z + z;
if (mutablePos != null)
{
mutablePos.x = newX;
mutablePos.y = newY;
mutablePos.z = newZ;
return mutablePos;
}
else
{
return new DhBlockPos(newX, newY, newZ);
}
}
/** Returns a new {@link DhBlockPos} limits to a value between 0 and 15 (inclusive) */
public DhBlockPos convertToChunkRelativePos() { return this.mutateToChunkRelativePos(null); }
/**
* Limits the block position to a value between 0 and 15 (inclusive)
* If not null, mutates "mutableBlockPos"
*/
public DhBlockPos mutateToChunkRelativePos(@Nullable DhBlockPos mutableBlockPos)
{
// move the position into the range -15 and +15
int relX = (this.x % LodUtil.CHUNK_WIDTH);
// if the position is negative move it into the range 0 and 15
relX = (relX < 0) ? (relX + LodUtil.CHUNK_WIDTH) : relX;
int relZ = (this.z % LodUtil.CHUNK_WIDTH);
relZ = (relZ < 0) ? (relZ + LodUtil.CHUNK_WIDTH) : relZ;
// the y value shouldn't need to be changed
if (mutableBlockPos != null)
{
mutableBlockPos.x = relX;
mutableBlockPos.y = this.y;
mutableBlockPos.z = relZ;
return mutableBlockPos;
}
else
{
return new DhBlockPos(relX, this.y, relZ);
}
}
/**
* Can be used to quickly determine the rough distance between two points<Br>
* or determine the taxi cab (manhattan) distance between two points. <Br><Br>
*
* Manhattan distance is equivalent to determining the distance between two street intersections,
* where you can only drive along each street, instead of directly to the other point.
*/
public int getManhattanDistance(DhBlockPos otherPos)
{
return Math.abs(this.x - otherPos.x) + Math.abs(this.y - otherPos.y) + Math.abs(this.z - otherPos.z);
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DhBlockPos that = (DhBlockPos) o;
return x == that.x && y == that.y && z == that.z;
}
@Override
public int hashCode()
{
return Objects.hash(x, y, z);
}
@Override
public String toString()
{
return "DHBlockPos[" +
"" + x +
", " + y +
", " + z +
']';
}
public static void _DebugCheckPacker(int x, int y, int z, long expected)
{
long packed = asLong(x, y, z);
if (packed != expected)
{
throw new IllegalArgumentException("Packed values don't match: " + packed + " != " + expected);
}
DhBlockPos pos = new DhBlockPos(packed);
if (pos.x != x || pos.y != y || pos.z != z)
{
throw new IllegalArgumentException("Values after decode don't match: " + pos + " != " + x + ", " + y + ", " + z);
}
}
}
@@ -17,12 +17,10 @@
* 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.pos.blockPos; package com.seibel.distanthorizons.core.pos;
import com.seibel.distanthorizons.core.pos.Pos2D;
import com.seibel.distanthorizons.coreapi.util.MathUtil; import com.seibel.distanthorizons.coreapi.util.MathUtil;
/** immutable */
public class DhBlockPos2D public class DhBlockPos2D
{ {
public static final DhBlockPos2D ZERO = new DhBlockPos2D(0, 0); public static final DhBlockPos2D ZERO = new DhBlockPos2D(0, 0);
@@ -43,11 +41,11 @@ public class DhBlockPos2D
public DhBlockPos2D(DhBlockPos blockPos) public DhBlockPos2D(DhBlockPos blockPos)
{ {
this.x = blockPos.getX(); this.x = blockPos.x;
this.z = blockPos.getZ(); this.z = blockPos.z;
} }
public static DhBlockPos2D fromPos2D(Pos2D pos) { return new DhBlockPos2D(pos.getX(), pos.getY()); } public static DhBlockPos2D fromPos2D(Pos2D pos) { return new DhBlockPos2D(pos.x, pos.y); }
@@ -87,9 +85,8 @@ public class DhBlockPos2D
@Override @Override
public boolean equals(Object obj) public boolean equals(Object obj)
{ {
if (obj instanceof DhBlockPos2D) if (obj instanceof DhBlockPos2D other)
{ {
DhBlockPos2D other = (DhBlockPos2D) obj;
return this.x == other.x && this.z == other.z; return this.x == other.x && this.z == other.z;
} }
@@ -19,23 +19,12 @@
package com.seibel.distanthorizons.core.pos; package com.seibel.distanthorizons.core.pos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
/**
* immutable <br><br>
*
* Dev note: if for some reason we want to store these as longs check the old commits. <br>
* That logic was removed since it wasn't needed at the time
*/
public class DhChunkPos public class DhChunkPos
{ {
private final int x; public final int x; // Low 32 bits
public int getX() { return x; } public final int z; // High 32 bits
private final int z;
public int getZ() { return z; }
/** cached to improve hashing speed */ /** cached to improve hashing speed */
public final int hashCode; public final int hashCode;
@@ -56,14 +45,15 @@ public class DhChunkPos
} }
public DhChunkPos(DhBlockPos blockPos) public DhChunkPos(DhBlockPos blockPos)
{ {
// >> 4 is the Same as divide by 16 // >> 4 is the Same as div 16
this(blockPos.getX() >> 4, blockPos.getZ() >> 4); this(blockPos.x >> 4, blockPos.z >> 4);
} }
public DhChunkPos(DhBlockPos2D blockPos) public DhChunkPos(DhBlockPos2D blockPos)
{ {
// >> 4 is the Same as div 16 // >> 4 is the Same as div 16
this(blockPos.x >> 4, blockPos.z >> 4); this(blockPos.x >> 4, blockPos.z >> 4);
} }
public DhChunkPos(long packed) { this(getXFromPackedLong(packed), getZFromPackedLong(packed)); }
@@ -71,8 +61,13 @@ public class DhChunkPos
// methods // // methods //
//=========// //=========//
public DhBlockPos centerBlockPos() { return new DhBlockPos(8 + this.x << 4, 0, 8 + this.z << 4); } public DhBlockPos center() { return new DhBlockPos(8 + this.x << 4, 0, 8 + this.z << 4); }
public DhBlockPos minCornerBlockPos() { return new DhBlockPos(this.x << 4, 0, this.z << 4); } public DhBlockPos corner() { return new DhBlockPos(this.x << 4, 0, this.z << 4); }
public static long toLong(int x, int z) { return ((long) x & 0xFFFFFFFFL) << 32 | (long) z & 0xFFFFFFFFL; }
private static int getXFromPackedLong(long chunkPos) { return (int) (chunkPos >> 32); }
private static int getZFromPackedLong(long chunkPos) { return (int) (chunkPos & 0xFFFFFFFFL); }
public int getMinBlockX() { return this.x << 4; } public int getMinBlockX() { return this.x << 4; }
public int getMinBlockZ() { return this.z << 4; } public int getMinBlockZ() { return this.z << 4; }
@@ -86,10 +81,11 @@ public class DhChunkPos
int maxBlockX = minBlockX + LodUtil.CHUNK_WIDTH; int maxBlockX = minBlockX + LodUtil.CHUNK_WIDTH;
int maxBlockZ = minBlockZ + LodUtil.CHUNK_WIDTH; int maxBlockZ = minBlockZ + LodUtil.CHUNK_WIDTH;
return minBlockX <= pos.getX() && pos.getX() < maxBlockX return minBlockX <= pos.x && pos.x <= maxBlockX
&& minBlockZ <= pos.getZ() && pos.getZ() < maxBlockZ; && minBlockZ <= pos.z && pos.z <= maxBlockZ;
} }
public long getLong() { return toLong(this.x, this.z); }
//================// //================//
@@ -120,4 +116,39 @@ public class DhChunkPos
@Override @Override
public String toString() { return "C[" + this.x + "," + this.z + "]"; } public String toString() { return "C[" + this.x + "," + this.z + "]"; }
//=======================//
// static helper methods //
//=======================//
public static void _DebugCheckPacker(int x, int z, long expected)
{
long packed = toLong(x, z);
if (packed != expected)
{
throw new IllegalArgumentException("Packed values don't match: " + packed + " != " + expected);
}
DhChunkPos pos = new DhChunkPos(packed);
if (pos.x != x || pos.z != z)
{
throw new IllegalArgumentException("Values after decode don't match: " + pos + " != " + x + ", " + z);
}
}
/** @return true if testPos is within the area defined by the min and max positions. */
public static boolean isChunkPosBetween(DhChunkPos minChunkPos, DhChunkPos testPos, DhChunkPos maxChunkPos)
{
int minChunkX = Math.min(minChunkPos.x, maxChunkPos.x);
int minChunkZ = Math.min(minChunkPos.z, maxChunkPos.z);
int maxChunkX = Math.max(minChunkPos.x, maxChunkPos.x);
int maxChunkZ = Math.max(minChunkPos.z, maxChunkPos.z);
return minChunkX <= testPos.x && testPos.x <= maxChunkX &&
minChunkZ <= testPos.z && testPos.z <= maxChunkZ;
}
} }
@@ -20,7 +20,6 @@
package com.seibel.distanthorizons.core.pos; package com.seibel.distanthorizons.core.pos;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil; import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -48,10 +47,6 @@ public class DhLodPos implements Comparable<DhLodPos>
//==============//
// constructors //
//==============//
public DhLodPos(byte detailLevel, int x, int z) public DhLodPos(byte detailLevel, int x, int z)
{ {
this.detailLevel = detailLevel; this.detailLevel = detailLevel;
@@ -36,10 +36,6 @@ public class DhLodUnit
//==============//
// constructors //
//==============//
public DhLodUnit(byte detailLevel, int numberOfLodSectionsWide) public DhLodUnit(byte detailLevel, int numberOfLodSectionsWide)
{ {
this.detailLevel = detailLevel; this.detailLevel = detailLevel;
@@ -47,11 +43,6 @@ public class DhLodUnit
} }
//=========//
// methods //
//=========//
/** @return the size of this LOD unit in Minecraft blocks */ /** @return the size of this LOD unit in Minecraft blocks */
public int toBlockWidth() { return BitShiftUtil.pow(this.numberOfLodSectionsWide, this.detailLevel); } public int toBlockWidth() { return BitShiftUtil.pow(this.numberOfLodSectionsWide, this.detailLevel); }
/** @return the LOD Unit relative to the given block width and detail level */ /** @return the LOD Unit relative to the given block width and detail level */
@@ -21,8 +21,6 @@ package com.seibel.distanthorizons.core.pos;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.enums.EDhDirection; import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil; import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
@@ -36,11 +34,11 @@ import java.util.function.LongConsumer;
* For the specifics of how they compare can be viewed in the constants {@link #SECTION_BLOCK_DETAIL_LEVEL}, * For the specifics of how they compare can be viewed in the constants {@link #SECTION_BLOCK_DETAIL_LEVEL},
* {@link #SECTION_CHUNK_DETAIL_LEVEL}, and {@link #SECTION_REGION_DETAIL_LEVEL}).<br><br> * {@link #SECTION_CHUNK_DETAIL_LEVEL}, and {@link #SECTION_REGION_DETAIL_LEVEL}).<br><br>
* *
* <strong>Why does the smallest render section represent 4x4 MC chunks (section detail level 6)? </strong> <br> * <strong>Why does the smallest render section represent 2x2 MC chunks (section detail level 6)? </strong> <br>
* A section defines what unit the quad tree works in, because of that we don't want that unit to be too big or too small. <br> * A section defines what unit the quad tree works in, because of that we don't want that unit to be too big or too small. <br>
* <strong>Too small</strong>, and we'll have 1,000s of sections running around, all needing individual files and render buffers.<br> * <strong>Too small</strong>, and we'll have 1,000s of sections running around, all needing individual files and render buffers.<br>
* <strong>Too big</strong>, and the LOD dropoff will be very noticeable.<br> * <strong>Too big</strong>, and the LOD dropoff will be very noticeable.<br>
* With those thoughts in mind we decided on a smallest section size of 64 data points square (IE 4x4 chunks). * With those thoughts in mind we decided on a smallest section size of 32 data points square (IE 2x2 chunks).
* *
* @author Leetom * @author Leetom
*/ */
@@ -107,16 +105,16 @@ public class DhSectionPos
/** Returns the section pos at the requested detail level containing the given BlockPos */ /** Returns the section pos at the requested detail level containing the given BlockPos */
public static long encodeContaining(byte outputSectionDetailLevel, DhBlockPos pos) public static long encodeContaining(byte outputSectionDetailLevel, DhBlockPos pos)
{ {
int sectionPosX = getXOrZSectionPosFromChunkOrBlockPos(pos.getX(), false); int sectionPosX = getXOrZSectionPosFromChunkOrBlockPos(pos.x, false);
int sectionPosZ = getXOrZSectionPosFromChunkOrBlockPos(pos.getZ(), false); int sectionPosZ = getXOrZSectionPosFromChunkOrBlockPos(pos.z, false);
long blockPos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ); long blockPos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
return convertToDetailLevel(blockPos, outputSectionDetailLevel); return convertToDetailLevel(blockPos, outputSectionDetailLevel);
} }
/** Returns the section pos at the requested detail level containing the given ChunkPos */ /** Returns the section pos at the requested detail level containing the given ChunkPos */
public static long encodeContaining(byte outputSectionDetailLevel, DhChunkPos pos) public static long encodeContaining(byte outputSectionDetailLevel, DhChunkPos pos)
{ {
int sectionPosX = getXOrZSectionPosFromChunkOrBlockPos(pos.getX(), true); int sectionPosX = getXOrZSectionPosFromChunkOrBlockPos(pos.x, true);
int sectionPosZ = getXOrZSectionPosFromChunkOrBlockPos(pos.getZ(), true); int sectionPosZ = getXOrZSectionPosFromChunkOrBlockPos(pos.z, true);
long blockPos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ); long blockPos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
return convertToDetailLevel(blockPos, outputSectionDetailLevel); return convertToDetailLevel(blockPos, outputSectionDetailLevel);
} }
@@ -23,23 +23,15 @@ import com.seibel.distanthorizons.coreapi.util.MathUtil;
import java.util.Objects; import java.util.Objects;
/** immutable */
public class Pos2D public class Pos2D
{ {
public static final Pos2D ZERO = new Pos2D(0, 0); public static final Pos2D ZERO = new Pos2D(0, 0);
private final int x; public final int x;
public int getX() { return this.x; } public final int y;
private final int y;
public int getY() { return this.y; }
//==============//
// constructors //
//==============//
public Pos2D(int x, int y) public Pos2D(int x, int y)
{ {
this.x = x; this.x = x;
@@ -48,11 +40,6 @@ public class Pos2D
//======//
// math //
//======//
public Pos2D add(Pos2D other) { return new Pos2D(this.x + other.x, this.y + other.y); } public Pos2D add(Pos2D other) { return new Pos2D(this.x + other.x, this.y + other.y); }
public Pos2D subtract(Pos2D other) { return new Pos2D(this.x - other.x, this.y - other.y); } public Pos2D subtract(Pos2D other) { return new Pos2D(this.x - other.x, this.y - other.y); }
public Pos2D subtract(int value) { return new Pos2D(this.x - value, this.y - value); } public Pos2D subtract(int value) { return new Pos2D(this.x - value, this.y - value); }
@@ -85,24 +72,16 @@ public class Pos2D
//================//
// base overrides //
//================//
@Override
public int hashCode() { return Objects.hash(this.x, this.y); } public int hashCode() { return Objects.hash(this.x, this.y); }
@Override
public String toString() { return "[" + this.x + ", " + this.y + "]"; } public String toString() { return "[" + this.x + ", " + this.y + "]"; }
@Override
public boolean equals(Object otherObj) public boolean equals(Object otherObj)
{ {
if (otherObj == this) if (otherObj == this)
return true; return true;
if (otherObj instanceof Pos2D) if (otherObj instanceof Pos2D otherPos)
{ {
Pos2D otherPos = (Pos2D) otherObj;
return this.x == otherPos.x && this.y == otherPos.y; return this.x == otherPos.x && this.y == otherPos.y;
} }
return false; return false;
@@ -1,194 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.pos.blockPos;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.util.LodUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
/**
* immutable <br><br>
*
* Dev note: if for some reason we want to store these as longs check the old commits. <br>
* That logic was removed since it wasn't needed at the time.
*
* @see DhBlockPosMutable
*/
public class DhBlockPos
{
/** Useful for methods that need a position passed in but won't actually be used */
public static final DhBlockPos ZERO = new DhBlockPos(0, 0, 0);
protected int x;
public int getX() { return this.x; }
protected int y;
public int getY() { return this.y; }
protected int z;
public int getZ() { return this.z; }
//==============//
// constructors //
//==============//
public DhBlockPos(int x, int y, int z)
{
this.x = x;
this.y = y;
this.z = z;
}
public DhBlockPos() { this(0, 0, 0); }
public DhBlockPos(DhBlockPos pos) { this(pos.x, pos.y, pos.z); }
public DhBlockPos(DhBlockPos2D pos, int y) { this(pos.x, y, pos.z); }
//========//
// offset //
//========//
/** creates a new {@link DhBlockPos} with the given offset from the current pos. */
public DhBlockPos createOffset(EDhDirection direction) { return this.mutateOrCreateOffset(direction.getNormal().x, direction.getNormal().y, direction.getNormal().z, null); }
/** if not null, mutates "mutablePos" so it matches the current pos after being offset. Otherwise creates a new {@link DhBlockPos}. */
public void mutateOffset(EDhDirection direction, @NotNull DhBlockPosMutable mutablePos) { this.mutateOrCreateOffset(direction.getNormal().x, direction.getNormal().y, direction.getNormal().z, mutablePos); }
public DhBlockPos createOffset(int x, int y, int z) { return this.mutateOrCreateOffset(x,y,z, null); }
public void mutateOffset(int x, int y, int z, @NotNull DhBlockPosMutable mutablePos) { this.mutateOrCreateOffset(x, y, z, mutablePos); }
protected DhBlockPos mutateOrCreateOffset(int x, int y, int z, @Nullable DhBlockPosMutable mutablePos)
{
int newX = this.x + x;
int newY = this.y + y;
int newZ = this.z + z;
if (mutablePos != null)
{
mutablePos.x = newX;
mutablePos.y = newY;
mutablePos.z = newZ;
return mutablePos;
}
else
{
return new DhBlockPos(newX, newY, newZ);
}
}
//================//
// chunk relative //
//================//
/** Returns a new {@link DhBlockPos} limited to a value between 0 and 15 (inclusive) */
public DhBlockPos createChunkRelativePos() { return this.mutateOrCreateChunkRelativePos(null); }
/** Limits the input {@link DhBlockPos} to a value between 0 and 15 (inclusive) */
public void mutateToChunkRelativePos(DhBlockPosMutable mutableBlockPos) { this.mutateOrCreateChunkRelativePos(mutableBlockPos); }
/**
* Limits the block position to a value between 0 and 15 (inclusive)
* If not null, mutates "mutableBlockPos"
*
* @return the mutated or created {@link DhBlockPos}
*/
protected DhBlockPos mutateOrCreateChunkRelativePos(@Nullable DhBlockPosMutable mutableBlockPos)
{
int relX = convertWorldPosToChunkRelative(this.x);
// the y value shouldn't need to be changed
int relZ = convertWorldPosToChunkRelative(this.z);
if (mutableBlockPos != null)
{
mutableBlockPos.x = relX;
mutableBlockPos.y = this.y;
mutableBlockPos.z = relZ;
return mutableBlockPos;
}
else
{
return new DhBlockPos(relX, this.y, relZ);
}
}
protected static int convertWorldPosToChunkRelative(int xOrZ)
{
// move the position into the range -15 and +15
int relPos = (xOrZ % LodUtil.CHUNK_WIDTH);
// if the position is negative move it into the range 0 and 15
relPos = (relPos < 0) ? (relPos + LodUtil.CHUNK_WIDTH) : relPos;
return relPos;
}
//==========//
// distance //
//==========//
/**
* Can be used to quickly determine the rough distance between two points<Br>
* or determine the taxi cab (manhattan) distance between two points. <Br><Br>
*
* Manhattan distance is equivalent to determining the distance between two street intersections,
* where you can only drive along each street, instead of directly to the other point.
*/
public int getManhattanDistance(DhBlockPos otherPos)
{ return Math.abs(this.x - otherPos.x) + Math.abs(this.y - otherPos.y) + Math.abs(this.z - otherPos.z); }
//================//
// base overrides //
//================//
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
else if (obj == null || this.getClass() != obj.getClass())
{
return false;
}
else
{
DhBlockPos that = (DhBlockPos) obj;
return this.x == that.x && this.y == that.y && this.z == that.z;
}
}
@Override
public int hashCode() { return Objects.hash(this.x, this.y, this.z); }
@Override
public String toString() { return "DHBlockPos["+ this.x +", "+ this.y +", "+ this.z +"]"; }
}
@@ -1,66 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.pos.blockPos;
import com.seibel.distanthorizons.core.enums.EDhDirection;
public class DhBlockPosMutable extends DhBlockPos
{
/** Useful for methods that need a position passed in but won't actually be used */
public static final DhBlockPosMutable ZERO = new DhBlockPosMutable(0, 0, 0);
public void setX(int x) { this.x = x; }
public void setY(int y) { this.y = y; }
public void setZ(int z) { this.z = z; }
//==============//
// constructors //
//==============//
public DhBlockPosMutable(int x, int y, int z) { super(x,y,z); }
public DhBlockPosMutable() { super(0, 0, 0); }
public DhBlockPosMutable(DhBlockPos pos) { super(pos); }
public DhBlockPosMutable(DhBlockPos2D pos, int y) { super(pos.x, y, pos.z); }
//========//
// offset //
//========//
/** @see DhBlockPos#createOffset(EDhDirection) */
public DhBlockPosMutable createOffset(EDhDirection direction) { return new DhBlockPosMutable(super.mutateOrCreateOffset(direction.getNormal().x, direction.getNormal().y, direction.getNormal().z, null)); }
/** @see DhBlockPos#createOffset(int, int, int) */
public DhBlockPosMutable createOffset(int x, int y, int z) { return new DhBlockPosMutable(this.mutateOrCreateOffset(x,y,z, null)); }
//================//
// chunk relative //
//================//
public DhBlockPosMutable createChunkRelativePos() { return new DhBlockPosMutable(this.mutateOrCreateChunkRelativePos(null)); }
}
@@ -54,6 +54,10 @@ public class DhFrustumBounds implements IDhApiCullingFrustum
Vector3f lodMin = new Vector3f(lodBlockPosMinX, this.worldMinY, lodBlockPosMinZ); Vector3f lodMin = new Vector3f(lodBlockPosMinX, this.worldMinY, lodBlockPosMinZ);
Vector3f lodMax = new Vector3f(lodBlockPosMinX + lodBlockWidth, this.worldMaxY, lodBlockPosMinZ + lodBlockWidth); Vector3f lodMax = new Vector3f(lodBlockPosMinX + lodBlockWidth, this.worldMaxY, lodBlockPosMinZ + lodBlockWidth);
if (lodMax.x < this.boundsMin.x || lodMin.x > this.boundsMax.x) return false;
if (lodMax.z < this.boundsMin.z || lodMin.z > this.boundsMax.z) return false;
if (this.worldMaxY < this.boundsMin.y || this.worldMinY > this.boundsMax.y) return false;
return this.frustum.testAab(lodMin, lodMax); return this.frustum.testAab(lodMin, lodMax);
} }
@@ -19,16 +19,19 @@
package com.seibel.distanthorizons.core.render; package com.seibel.distanthorizons.core.render;
import com.seibel.distanthorizons.api.enums.config.EDhApiHorizontalQuality;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource; import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.enums.EDhDirection; import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2; import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.level.IDhClientLevel; import com.seibel.distanthorizons.core.level.IDhClientLevel;
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.DhBlockPos2D;
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.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.ThreadUtil; import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode; import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode;
@@ -71,6 +74,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
*/ */
private final ConcurrentLinkedQueue<Long> sectionsToReload = new ConcurrentLinkedQueue<>(); private final ConcurrentLinkedQueue<Long> sectionsToReload = new ConcurrentLinkedQueue<>();
private final IDhClientLevel level; //FIXME: Proper hierarchy to remove this reference! private final IDhClientLevel level; //FIXME: Proper hierarchy to remove this reference!
private final ConfigChangeListener<EDhApiHorizontalQuality> horizontalScaleChangeListener;
private final ReentrantLock treeReadWriteLock = new ReentrantLock(); private final ReentrantLock treeReadWriteLock = new ReentrantLock();
private final AtomicBoolean fullDataRetrievalQueueRunning = new AtomicBoolean(false); private final AtomicBoolean fullDataRetrievalQueueRunning = new AtomicBoolean(false);
@@ -106,6 +110,8 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
this.level = level; this.level = level;
this.fullDataSourceProvider = fullDataSourceProvider; this.fullDataSourceProvider = fullDataSourceProvider;
this.blockRenderDistanceDiameter = viewDiameterInBlocks; this.blockRenderDistanceDiameter = viewDiameterInBlocks;
this.horizontalScaleChangeListener = new ConfigChangeListener<>(Config.Client.Advanced.Graphics.Quality.horizontalQuality, (newHorizontalScale) -> this.onHorizontalQualityChange());
} }
@@ -145,7 +151,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
} }
catch (Exception e) catch (Exception e)
{ {
LOGGER.error("Quad Tree tick exception for dimension: " + this.level.getLevelWrapper().getDimensionType().getDimensionName() + ", exception: " + e.getMessage(), e); LOGGER.error("Quad Tree tick exception for dimension: " + this.level.getClientLevelWrapper().getDimensionType().getDimensionName() + ", exception: " + e.getMessage(), e);
} }
finally finally
{ {
@@ -186,7 +192,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
try try
{ {
LodRenderSection renderSection = this.getValue(pos); LodRenderSection renderSection = this.getValue(pos);
if (renderSection != null && renderSection.getRenderingEnabled()) if (renderSection != null && renderSection.renderingEnabled)
{ {
renderSection.uploadRenderDataToGpuAsync(); renderSection.uploadRenderDataToGpuAsync();
} }
@@ -250,12 +256,11 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
ArrayList<LodRenderSection> nodesNeedingRetrieval, ArrayList<LodRenderSection> nodesNeedingRetrieval,
ArrayList<LodRenderSection> nodesNeedingLoading) ArrayList<LodRenderSection> nodesNeedingLoading)
{ {
//=====================// //===============================//
// get/create the node // // node and render section setup //
// and render section // //===============================//
//=====================//
// create the node // make sure the node is created
if (quadNode == null && this.isSectionPosInBounds(sectionPos)) // the position bounds should only fail when at the edge of the user's render distance if (quadNode == null && this.isSectionPosInBounds(sectionPos)) // the position bounds should only fail when at the edge of the user's render distance
{ {
rootNode.setValue(sectionPos, new LodRenderSection(sectionPos, this, this.level, this.fullDataSourceProvider)); rootNode.setValue(sectionPos, new LodRenderSection(sectionPos, this, this.level, this.fullDataSourceProvider));
@@ -267,12 +272,15 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
return false; return false;
} }
// make sure the render section is created (shouldn't be necessary, but just in case) // make sure the render section is created
LodRenderSection renderSection = quadNode.value; LodRenderSection renderSection = quadNode.value;
// create a new render section if missing
if (renderSection == null) if (renderSection == null)
{ {
renderSection = new LodRenderSection(sectionPos, this, this.level, this.fullDataSourceProvider); LodRenderSection newRenderSection = new LodRenderSection(sectionPos, this, this.level, this.fullDataSourceProvider);
rootNode.setValue(sectionPos, renderSection); rootNode.setValue(sectionPos, newRenderSection);
renderSection = newRenderSection; // TODO this never seemed to be called, is it necessary?
} }
@@ -281,22 +289,20 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
// handle enabling, loading, // // handle enabling, loading, //
// and disabling render sections // // and disabling render sections //
//===============================// //===============================//
//byte expectedDetailLevel = DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL + 3; // can be used instead of the following logic for testing //byte expectedDetailLevel = DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL + 3; // can be used instead of the following logic for testing
byte expectedDetailLevel = this.calculateExpectedDetailLevel(playerPos, sectionPos); byte expectedDetailLevel = this.calculateExpectedDetailLevel(playerPos, sectionPos);
expectedDetailLevel = (byte) Math.min(expectedDetailLevel, this.minRenderDetailLevel); expectedDetailLevel = (byte) Math.min(expectedDetailLevel, this.minRenderDetailLevel);
expectedDetailLevel += DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL; expectedDetailLevel += DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL;
if (DhSectionPos.getDetailLevel(sectionPos) > expectedDetailLevel) if (DhSectionPos.getDetailLevel(sectionPos) > expectedDetailLevel)
{ {
//=======================// // section detail level too high //
// detail level too high // boolean thisPosIsRendering = renderSection.renderingEnabled;
//=======================//
boolean thisPosIsRendering = renderSection.getRenderingEnabled();
boolean allChildrenSectionsAreLoaded = true; boolean allChildrenSectionsAreLoaded = true;
// recursively update each child render section // recursively update all child render sections
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i); QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i);
@@ -304,7 +310,6 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
allChildrenSectionsAreLoaded = childSectionLoaded && allChildrenSectionsAreLoaded; allChildrenSectionsAreLoaded = childSectionLoaded && allChildrenSectionsAreLoaded;
} }
if (!allChildrenSectionsAreLoaded) if (!allChildrenSectionsAreLoaded)
{ {
// not all child positions are loaded yet, or this section is out of render range // not all child positions are loaded yet, or this section is out of render range
@@ -312,90 +317,111 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
} }
else else
{ {
// onRenderingDisabled() needs to be fired before the children are enabled so beacons render correctly if (renderSection.renderingEnabled
if (renderSection.getRenderingEnabled()) && Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus.get())
{ {
renderSection.onRenderingDisabled(); // show that this position has just been disabled
DebugRenderer.makeParticle(
// this position's rendering has been disabled due to children being rendered new DebugRenderer.BoxParticle(
DebugRenderer.makeParticle(new DebugRenderer.BoxParticle(new DebugRenderer.Box(renderSection.pos, 128f, 156f, 0.09f, Color.WHITE), 0.2, 32f)); new DebugRenderer.Box(renderSection.pos, 128f, 156f, 0.09f, Color.CYAN.darker()),
0.2, 32f
)
);
} }
// all child positions are loaded, disable this section and enable its children.
if (renderSection.renderingEnabled)
{
this.level.unloadBeaconBeamsInPos(renderSection.pos);
}
renderSection.renderingEnabled = false;
// walk back down the tree and enable each child section // walk back down the tree and enable the child sections //TODO there are probably more efficient ways of doing this, but this will work for now
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i); QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i);
this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, DhSectionPos.getChildByIndex(sectionPos, i), parentSectionIsRendering, nodesNeedingRetrieval, nodesNeedingLoading); boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, DhSectionPos.getChildByIndex(sectionPos, i), parentSectionIsRendering, nodesNeedingRetrieval, nodesNeedingLoading);
allChildrenSectionsAreLoaded = childSectionLoaded && allChildrenSectionsAreLoaded;
}
if (!allChildrenSectionsAreLoaded)
{
// FIXME having world generation enabled in a pre-generated world that doesn't have any DH data can cause this to happen
// surprisingly reloadPos() doesn't appear to be the culprit, maybe there is an issue with reloading/changing the full data source?
//LOGGER.warn("Potential QuadTree concurrency issue. All child sections should be enabled and ready to render for pos: "+DhSectionPos.toString(sectionPos));
} }
// disabling rendering must be done after the children are enabled
// otherwise holes may appear in the world, overlaps are less noticeable
renderSection.setRenderingEnabled(false);
// this section is now being rendered via its children // this section is now being rendered via its children
return true; return allChildrenSectionsAreLoaded;
} }
} }
// TODO this should only equal the expected detail level, the (expectedDetailLevel-1) is a temporary fix to prevent corners from being cut out // TODO this should only equal the expected detail level, the (expectedDetailLevel-1) is a temporary fix to prevent corners from being cut out
else if (DhSectionPos.getDetailLevel(sectionPos) == expectedDetailLevel || DhSectionPos.getDetailLevel(sectionPos) == expectedDetailLevel - 1) else if (DhSectionPos.getDetailLevel(sectionPos) == expectedDetailLevel || DhSectionPos.getDetailLevel(sectionPos) == expectedDetailLevel - 1)
{ {
//======================// // this is the detail level we want to render //
// desired detail level //
//======================//
// prepare this section for rendering /* Can be uncommented to easily debug a single render section. */
if (!renderSection.gpuUploadInProgress() && renderSection.renderBuffer == null) /* Don't forget the disableRendering() at the bottom though. */
//if (sectionPos.getDetailLevel() == 10
// &&
// (
// sectionPos.getX() == 0 &&
// sectionPos.getZ() == -4
// ))
{ {
nodesNeedingLoading.add(renderSection); // prepare this section for rendering
} // TODO this should fire for the lowest detail level first to improve loading speed
if (!renderSection.gpuUploadInProgress() && renderSection.renderBuffer == null)
// queue world gen if needed
if (!renderSection.isFullyGenerated())
{
nodesNeedingRetrieval.add(renderSection);
}
// update debug if needed
if (Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus.get())
{
this.debugRenderSections.add(renderSection);
}
// wait for the parent to disable before enabling this section, so we don't have a hole
if (!parentSectionIsRendering && renderSection.canRender())
{
// if rendering is already enabled we don't have to re-enable it
if (!renderSection.getRenderingEnabled())
{ {
renderSection.setRenderingEnabled(true); nodesNeedingLoading.add(renderSection);
}
// disabling rendering must be done after the parent is enabled
// otherwise holes may appear in the world, overlaps are less noticeable if (Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus.get())
quadNode.deleteAllChildren((childRenderSection) -> {
this.debugRenderSections.add(renderSection);
}
// wait for the parent to disable before enabling this section, so we don't overdraw/overlap render sections
if (!parentSectionIsRendering && renderSection.canRender())
{
// if rendering is already enabled we don't have to re-enable it
if (!renderSection.renderingEnabled)
{ {
if (childRenderSection != null) renderSection.renderingEnabled = true;
this.level.loadBeaconBeamsInPos(renderSection.pos);
// delete/disable children, all of them will be a lower detail level than requested
quadNode.deleteAllChildren((childRenderSection) ->
{ {
if (childRenderSection.getRenderingEnabled()) if (childRenderSection != null)
{ {
// this position's rendering has been disabled due to a parent rendering if (childRenderSection.renderingEnabled)
DebugRenderer.makeParticle(new DebugRenderer.BoxParticle(new DebugRenderer.Box(childRenderSection.pos, 128f, 156f, 0.09f, Color.MAGENTA),0.2, 32f)); {
// show that this position's rendering has been disabled due to a parent rendering
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(childRenderSection.pos, 128f, 156f, 0.09f, Color.MAGENTA.darker()),
0.2, 32f
)
);
}
childRenderSection.renderingEnabled = false;
childRenderSection.close();
} }
});
childRenderSection.setRenderingEnabled(false); }
childRenderSection.onRenderingDisabled(); }
childRenderSection.close();
} if (!renderSection.isFullyGenerated())
}); {
nodesNeedingRetrieval.add(renderSection);
// onRenderingEnabled() needs to be fired after the children are disabled so beacons render correctly
renderSection.onRenderingEnabled();
} }
} }
//else
//{
// renderSection.disableRendering();
//}
return renderSection.canRender(); return renderSection.canRender();
} }
@@ -585,6 +611,12 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
} }
//==================//
// config listeners //
//==================//
private void onHorizontalQualityChange() { this.clearRenderDataCache(); }
//===========// //===========//
// debugging // // debugging //
@@ -646,6 +678,8 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
{ {
LOGGER.info("Shutting down " + LodQuadTree.class.getSimpleName() + "..."); LOGGER.info("Shutting down " + LodQuadTree.class.getSimpleName() + "...");
this.horizontalScaleChangeListener.close();
DebugRenderer.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus); DebugRenderer.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus);
Iterator<QuadNode<LodRenderSection>> nodeIterator = this.nodeIterator(); Iterator<QuadNode<LodRenderSection>> nodeIterator = this.nodeIterator();

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