Compare commits
112 Commits
blazeRender
...
Vulkan
| Author | SHA1 | Date | |
|---|---|---|---|
| 269f2c30fd | |||
| b674f49600 | |||
| b592012ba8 | |||
| 5d1e8a44fd | |||
| 40b27335ea | |||
| d0b07a5d2f | |||
| cb0fee9780 | |||
| 895e9276cd | |||
| 9ee0af8b01 | |||
| 69941fb7f8 | |||
| 36862a968f | |||
| 27204336b2 | |||
| 4846cf5019 | |||
| f7f3c1146f | |||
| aaa5e958f0 | |||
| 726da953bd | |||
| c4f4935fdd | |||
| 3ef8bd7e20 | |||
| ec72762067 | |||
| 4d0ed2a6dc | |||
| 7b252b173b | |||
| 7b0c66e3ae | |||
| 1b066327a8 | |||
| 43d0a971f7 | |||
| 9e60c698de | |||
| bf2affa6d1 | |||
| 98f6cea86a | |||
| 9ae01dc1f8 | |||
| 40efc5cbf3 | |||
| 66bba1c80a | |||
| d9f3b31cc5 | |||
| e465ef5325 | |||
| 225385a43f | |||
| 7d7d07416b | |||
| 5ef308cbee | |||
| d61b601c14 | |||
| 246c679a97 | |||
| 4b317a8e00 | |||
| 1debd4b875 | |||
| 5dcda31990 | |||
| ae16ed2341 | |||
| 2c266d2495 | |||
| 7e40546bc5 | |||
| 5d391c83ea | |||
| 0895bf53e3 | |||
| a7203f8f33 | |||
| 22efbb211a | |||
| 95c4459d8a | |||
| 0ef11caaf2 | |||
| 2e3dfab6c3 | |||
| 42be139e94 | |||
| f866e7f8e3 | |||
| 53fcce9d7c | |||
| 7b8b22fd5a | |||
| 6f54cfacb5 | |||
| 61eaf43ba0 | |||
| 1d368e3adc | |||
| 9e65e2dd4c | |||
| 2d878338cb | |||
| d62d21776d | |||
| a44c5d562d | |||
| 1d9bffe64e | |||
| b4e0687e2a | |||
| 89804f1ba1 | |||
| 9dbc5ef525 | |||
| e5dcb0999d | |||
| 50e0e940d1 | |||
| cb3e42fac4 | |||
| 34cdaf02eb | |||
| 71e6b9b58e | |||
| 1ec342928f | |||
| d45a1379bd | |||
| ecb3dce963 | |||
| 2b8cddd424 | |||
| 13895fec51 | |||
| 88c7245be6 | |||
| 6aad156a32 | |||
| 94535a213e | |||
| 63d6d42356 | |||
| acecbede8e | |||
| e2a8953e4c | |||
| 32eae23963 | |||
| 480c3b3ec5 | |||
| 6c0736a2a0 | |||
| c89abd414b | |||
| da2454b249 | |||
| 5933ef8245 | |||
| b9984c7723 | |||
| 2de50475f1 | |||
| bb838328a7 | |||
| 1980e64b2f | |||
| c3df26f5cb | |||
| 17df533fa6 | |||
| 2393bdd8e8 | |||
| 082b364f52 | |||
| 8a39610b8c | |||
| aa53835772 | |||
| cd5a3ce52b | |||
| 38e104a9fc | |||
| e27cee1f71 | |||
| 15d1d78954 | |||
| e2421c97ed | |||
| 8b72920652 | |||
| 212031a05f | |||
| 804293e291 | |||
| cfdd464f30 | |||
| 4c9d703e15 | |||
| 0eba376e70 | |||
| 89e6355d73 | |||
| 40149bc1d1 | |||
| b0e7c31964 | |||
| 2e906b57c4 |
+59
-23
@@ -2,48 +2,63 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "java"
|
id "java"
|
||||||
|
id "com.gradleup.shadow"
|
||||||
id "com.github.johnrengelman.shadow" version '8.1.1' apply false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
shadowJar {
|
mavenCentral()
|
||||||
// required for basic shadowJar setup
|
|
||||||
configurations = [project.configurations.shadow]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
task addSourcesToCompiledJar(type: ShadowJar) {
|
tasks.withType(JavaCompile).configureEach {
|
||||||
|
options.release = 8
|
||||||
|
options.encoding = "UTF-8"
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
testImplementation.extendsFrom compileOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly "org.apache.logging.log4j:log4j-api:${rootProject.log4j_version}"
|
||||||
|
testImplementation "junit:junit:4.13"
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
withSourcesJar()
|
||||||
|
}
|
||||||
|
|
||||||
|
task createReleaseApiJar(type: ShadowJar) {
|
||||||
|
|
||||||
|
mustRunAfter sourcesJar
|
||||||
|
dependsOn shadowJar
|
||||||
|
|
||||||
def sourceJarPath = "build/libs/DistantHorizons-api-${rootProject.versionStr}-sources.jar"
|
// the compiled "-all" jar is used since it's available at the time this task is run
|
||||||
def secondJarFile = file(sourceJarPath)
|
def compiledJarPath = "build/libs/DistantHorizonsApi-${rootProject.api_version}-all.jar"
|
||||||
|
def compiledJarFile = file(compiledJarPath)
|
||||||
|
|
||||||
// doFirst is so these only run when the task is actually executed
|
// doFirst is so these only run when the task is actually executed
|
||||||
doFirst {
|
doFirst {
|
||||||
System.out.println("Adding source files from: \n" +
|
System.out.println("Adding class files from: \n" +
|
||||||
"[" + sourceJarPath + "] to compiled API jar: \n" +
|
"[" + compiledJarPath + "] to source API jar: \n" +
|
||||||
"[" + shadowJar.archivePath + "]")
|
"[" + shadowJar.archiveFile.get().asFile + "]")
|
||||||
|
|
||||||
// Validate the input JAR file
|
// Validate the input JAR file
|
||||||
if (!secondJarFile.exists()) {
|
if (!compiledJarFile.exists()) {
|
||||||
throw new GradleException("Second JAR file not found: [${secondJarFile}]")
|
throw new GradleException("Compiled JAR file not found: [${compiledJarFile}]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the name of the combined JAR file
|
archiveFileName.set("DistantHorizonsApi-${rootProject.api_version}-combined.jar") // jar name
|
||||||
archiveFileName.set("DistantHorizonsApi-${rootProject.api_version}.jar")
|
destinationDirectory = file('build/libs/') // jar location
|
||||||
|
|
||||||
// Set the destination directory for the combined JAR file
|
|
||||||
destinationDirectory = file('build/libs/merged/')
|
|
||||||
|
|
||||||
// Set the input JAR files to be combined
|
// Set the input JAR files to be combined
|
||||||
from sourceSets.main.allJava
|
from sourceSets.main.allJava
|
||||||
from {
|
from {
|
||||||
configurations.shadow.collect { it.isDirectory() ? it : zipTree(it) }
|
project.configurations.shadow.collect { it.isDirectory() ? it : zipTree(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the jars to merge
|
// add the class files
|
||||||
from shadowJar.archivePath
|
from zipTree(compiledJarFile)
|
||||||
from secondJarFile
|
|
||||||
|
|
||||||
// alternative method to Include the source files in the combined JAR
|
// alternative method to Include the source files in the combined JAR
|
||||||
// and/or see which files are being included
|
// and/or see which files are being included
|
||||||
@@ -77,6 +92,13 @@ task addSourcesToCompiledJar(type: ShadowJar) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
// always create a combined jar for easy deployment
|
||||||
|
assemble.dependsOn(createReleaseApiJar)
|
||||||
|
|
||||||
|
shadowJar {
|
||||||
|
// required for basic shadowJar setup
|
||||||
|
configurations = [project.configurations.shadow]
|
||||||
|
}
|
||||||
|
|
||||||
javadoc {
|
javadoc {
|
||||||
options {
|
options {
|
||||||
@@ -87,3 +109,17 @@ javadoc {
|
|||||||
addStringOption('Xdoclint:all,-missing', '-quiet')
|
addStringOption('Xdoclint:all,-missing', '-quiet')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set the jar name
|
||||||
|
def configureJar = { task ->
|
||||||
|
// outputs in the format:
|
||||||
|
// "DistantHorizonsApi-6.0.0.jar"
|
||||||
|
// "DistantHorizonsApi-6.0.0-sources.jar"
|
||||||
|
task.archiveBaseName = rootProject.api_name
|
||||||
|
task.archiveVersion = rootProject.api_version
|
||||||
|
}
|
||||||
|
configureJar(tasks.named("jar").get())
|
||||||
|
configureJar(tasks.named("sourcesJar").get())
|
||||||
|
configureJar(tasks.named("shadowJar").get())
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+9
-9
@@ -20,17 +20,17 @@
|
|||||||
package com.seibel.distanthorizons.api.enums.rendering;
|
package com.seibel.distanthorizons.api.enums.rendering;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default <br>
|
* DEFAULT <br>
|
||||||
* Debug <br>
|
* DEBUG_TRIANGLE <br>
|
||||||
* Disabled <br>
|
* DISABLED <br>
|
||||||
*
|
*
|
||||||
* @since API 2.0.0
|
* @since API 2.0.0
|
||||||
* @version 2024-4-6
|
* @version 2026-03-23
|
||||||
*/
|
*/
|
||||||
public enum EDhApiRendererMode
|
public enum EDhApiRendererMode
|
||||||
{
|
{
|
||||||
DEFAULT,
|
DEFAULT,
|
||||||
DEBUG,
|
DEBUG_TRIANGLE,
|
||||||
DISABLED;
|
DISABLED;
|
||||||
|
|
||||||
|
|
||||||
@@ -40,8 +40,8 @@ public enum EDhApiRendererMode
|
|||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case DEFAULT:
|
case DEFAULT:
|
||||||
return DEBUG;
|
return DEBUG_TRIANGLE;
|
||||||
case DEBUG:
|
case DEBUG_TRIANGLE:
|
||||||
return DISABLED;
|
return DISABLED;
|
||||||
default:
|
default:
|
||||||
return DEFAULT;
|
return DEFAULT;
|
||||||
@@ -55,10 +55,10 @@ public enum EDhApiRendererMode
|
|||||||
{
|
{
|
||||||
case DEFAULT:
|
case DEFAULT:
|
||||||
return DISABLED;
|
return DISABLED;
|
||||||
case DEBUG:
|
case DEBUG_TRIANGLE:
|
||||||
return DEFAULT;
|
return DEFAULT;
|
||||||
default:
|
default:
|
||||||
return DEBUG;
|
return DEBUG_TRIANGLE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-1
@@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.api.interfaces;
|
package com.seibel.distanthorizons.api.interfaces;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implemented by wrappers so developers can
|
* Implemented by wrappers so developers can
|
||||||
* access the underlying Minecraft object(s).
|
* access the underlying Minecraft object(s).
|
||||||
@@ -38,7 +40,11 @@ public interface IDhApiUnsafeWrapper
|
|||||||
* In order to cast this object to something usable, you may want
|
* In order to cast this object to something usable, you may want
|
||||||
* to use <code>obj.getClass()</code> when in your IDE
|
* to use <code>obj.getClass()</code> when in your IDE
|
||||||
* in order to determine what object this method returns for
|
* in order to determine what object this method returns for
|
||||||
* the specific version of Minecraft you are developing for.
|
* the specific version of Minecraft you are developing for. <br><br>
|
||||||
|
*
|
||||||
|
* Note:<br>
|
||||||
|
* This method may return null in some cases, IE {@link IDhApiBlockStateWrapper}
|
||||||
|
* when it is wrapping air.
|
||||||
*/
|
*/
|
||||||
Object getWrappedMcObject();
|
Object getWrappedMcObject();
|
||||||
|
|
||||||
|
|||||||
+10
-1
@@ -23,7 +23,10 @@ import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
|
|||||||
import com.seibel.distanthorizons.api.interfaces.IDhApiUnsafeWrapper;
|
import com.seibel.distanthorizons.api.interfaces.IDhApiUnsafeWrapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Minecraft version independent way of handling Blocks.
|
* A Minecraft version independent way of handling Blocks. <br><br>
|
||||||
|
*
|
||||||
|
* Note: the wrapped object (IE the object returned by {@link IDhApiUnsafeWrapper#getWrappedMcObject})
|
||||||
|
* will be null if this object is wrapping air.
|
||||||
*
|
*
|
||||||
* @author James Seibel
|
* @author James Seibel
|
||||||
* @version 2023-6-11
|
* @version 2023-6-11
|
||||||
@@ -39,6 +42,12 @@ public interface IDhApiBlockStateWrapper extends IDhApiUnsafeWrapper
|
|||||||
/** @since API 1.0.0 */
|
/** @since API 1.0.0 */
|
||||||
boolean isLiquid();
|
boolean isLiquid();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a value between 0 (fully transparent) and 16 (fully opaque).
|
||||||
|
* @since 6.1.0
|
||||||
|
*/
|
||||||
|
int getOpacity();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the full serialized form of the given block
|
* Returns the full serialized form of the given block
|
||||||
* as defined by DH's serialization methods.
|
* as defined by DH's serialization methods.
|
||||||
|
|||||||
+6
@@ -24,4 +24,10 @@ public interface IDhApiTerrainDataCache extends AutoCloseable
|
|||||||
*/
|
*/
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
|
// override without an exception
|
||||||
|
@Override
|
||||||
|
void close();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-4
@@ -51,14 +51,16 @@ public interface IDhApiRenderProxy
|
|||||||
//=======================//
|
//=======================//
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the name of Distant Horizons' depth texture. <br>
|
* Returns the OpenGL name of Distant Horizons' depth texture. <br>
|
||||||
* Will return {@link DhApiResult#success} = false and {@link DhApiResult#payload} = -1 if the texture hasn't been created yet.
|
* Will return {@link DhApiResult#success} = false and {@link DhApiResult#payload} = -1 if the texture hasn't been created yet
|
||||||
|
* or a rendering API other than OpenGL is in use.
|
||||||
*/
|
*/
|
||||||
DhApiResult<Integer> getDhDepthTextureId();
|
DhApiResult<Integer> getDhDepthTextureId();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the name of Distant Horizons' color texture. <br>
|
* Returns the OpenGL name of Distant Horizons' color texture. <br>
|
||||||
* Will return {@link DhApiResult#success} = false and {@link DhApiResult#payload} = -1 if the texture hasn't been created yet.
|
* Will return {@link DhApiResult#success} = false and {@link DhApiResult#payload} = -1 if the texture hasn't been created yet
|
||||||
|
* or a rendering API other than OpenGL is in use
|
||||||
*/
|
*/
|
||||||
DhApiResult<Integer> getDhColorTextureId();
|
DhApiResult<Integer> getDhColorTextureId();
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Called before Distant Horizons starts rendering a buffer. <br>
|
* Called before Distant Horizons starts rendering a buffer. <br>
|
||||||
* This event cannot be cancelled, use {@link DhApiBeforeRenderEvent} if you want to cancel rendering.
|
* This event cannot be canceled, use {@link DhApiBeforeRenderEvent} if you want to cancel rendering.
|
||||||
*
|
*
|
||||||
* @author James Seibel
|
* @author James Seibel
|
||||||
* @version 2023-1-31
|
* @version 2023-1-31
|
||||||
|
|||||||
+3
@@ -31,7 +31,10 @@ import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhAp
|
|||||||
* @author James Seibel
|
* @author James Seibel
|
||||||
* @version 2025-6-9
|
* @version 2025-6-9
|
||||||
* @since API 4.1.0
|
* @since API 4.1.0
|
||||||
|
* @deprecated Only used for the legacy OpenGL renderer <Br>
|
||||||
|
* Using {@link DhApiAfterColorDepthTextureCreatedEvent} instead is recommended.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public abstract class DhApiBeforeColorDepthTextureCreatedEvent implements IDhApiEvent<DhApiTextureCreatedParam>
|
public abstract class DhApiBeforeColorDepthTextureCreatedEvent implements IDhApiEvent<DhApiTextureCreatedParam>
|
||||||
{
|
{
|
||||||
/** Fired before Distant Horizons creates. */
|
/** Fired before Distant Horizons creates. */
|
||||||
|
|||||||
+156
@@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the Distant Horizons mod
|
||||||
|
* licensed under the GNU LGPL v3 License.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 James Seibel
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.seibel.distanthorizons.api.methods.events.abstractEvents;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
|
||||||
|
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper;
|
||||||
|
import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper;
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent;
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam;
|
||||||
|
import com.seibel.distanthorizons.coreapi.util.ColorUtil;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performance note: this event will be fired thousands of times on concurrent threads,
|
||||||
|
* make it thread safe and as fast as possible. <Br><Br>
|
||||||
|
*
|
||||||
|
* This event is fired when DH needs to convert a {@link IDhApiBlockStateWrapper}
|
||||||
|
* into a color for rendering. This event is fired after DH attempts to determine
|
||||||
|
* the color itself (using the base color, tinting, etc.). <Br>
|
||||||
|
* Using this event will override the tinting config for this block
|
||||||
|
* unless you re-implement that logic yourself.
|
||||||
|
* <Br><Br>
|
||||||
|
*
|
||||||
|
* This event will only trigger for {@link IDhApiBlockStateWrapper}s that have been registered
|
||||||
|
* via {@link DhApiBlockStateWrapperCreatedEvent.EventParam#setAllowApiColorOverride(boolean)}.
|
||||||
|
*
|
||||||
|
* @author James Seibel
|
||||||
|
* @version 2026-04-14
|
||||||
|
* @since API 6.0.0
|
||||||
|
* @see IDhApiBlockStateWrapper
|
||||||
|
*/
|
||||||
|
public abstract class DhApiBlockColorOverrideEvent implements IDhApiEvent<DhApiBlockColorOverrideEvent.EventParam>
|
||||||
|
{
|
||||||
|
public abstract void blockStateWrapperCreated(DhApiEventParam<EventParam> event);
|
||||||
|
|
||||||
|
|
||||||
|
//=========================//
|
||||||
|
// internal DH API methods //
|
||||||
|
//=========================//
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void fireEvent(DhApiEventParam<EventParam> event) { this.blockStateWrapperCreated(event); }
|
||||||
|
|
||||||
|
|
||||||
|
//==================//
|
||||||
|
// parameter object //
|
||||||
|
//==================//
|
||||||
|
|
||||||
|
public static class EventParam implements IDhApiEventParam
|
||||||
|
{
|
||||||
|
private IDhApiLevelWrapper levelWrapper;
|
||||||
|
private IDhApiBlockStateWrapper blockStateWrapper = null;
|
||||||
|
private int colorAsInt = -1;
|
||||||
|
private int blockPosX = 0, blockPosY = 0, blockPosZ = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=============//
|
||||||
|
// constructor //
|
||||||
|
//=============//
|
||||||
|
|
||||||
|
public EventParam() {}
|
||||||
|
|
||||||
|
public void update(
|
||||||
|
IDhApiLevelWrapper levelWrapper,
|
||||||
|
IDhApiBlockStateWrapper blockStateWrapper,
|
||||||
|
int colorAsInt,
|
||||||
|
int blockPosX, int blockPosY, int blockPosZ)
|
||||||
|
{
|
||||||
|
this.levelWrapper = levelWrapper;
|
||||||
|
this.blockStateWrapper = blockStateWrapper;
|
||||||
|
this.colorAsInt = colorAsInt;
|
||||||
|
|
||||||
|
this.blockPosX = blockPosX;
|
||||||
|
this.blockPosY = blockPosY;
|
||||||
|
this.blockPosZ = blockPosZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=================//
|
||||||
|
// getters/setters //
|
||||||
|
//=================//
|
||||||
|
|
||||||
|
public IDhApiBlockStateWrapper getBlockStateWrapper() { return this.blockStateWrapper; }
|
||||||
|
|
||||||
|
public IDhApiLevelWrapper getLevelWrapper() { return levelWrapper; }
|
||||||
|
|
||||||
|
public int getColorAsInt() { return this.colorAsInt; }
|
||||||
|
public int getAlpha() { return ColorUtil.getAlpha(this.colorAsInt); }
|
||||||
|
public int getRed() { return ColorUtil.getRed(this.colorAsInt); }
|
||||||
|
public int getGreen() { return ColorUtil.getGreen(this.colorAsInt); }
|
||||||
|
public int getBlue() { return ColorUtil.getBlue(this.colorAsInt); }
|
||||||
|
public void setColor(int red, int green, int blue) throws IllegalArgumentException { this.setColor(this.getAlpha(), red, green, blue); }
|
||||||
|
/**
|
||||||
|
* Note: when if you set a partially transparent alpha channel the underlying {@link IDhApiBlockStateWrapper#getOpacity()}
|
||||||
|
* method should also return a non-opaque value.
|
||||||
|
* Otherwise LODs may behave incorrectly.
|
||||||
|
*/
|
||||||
|
public void setColor(int alpha, int red, int green, int blue) throws IllegalArgumentException
|
||||||
|
{
|
||||||
|
ColorUtil.throwIfColorValueOutOfIntRange("alpha", alpha);
|
||||||
|
ColorUtil.throwIfColorValueOutOfIntRange("red", red);
|
||||||
|
ColorUtil.throwIfColorValueOutOfIntRange("green", green);
|
||||||
|
ColorUtil.throwIfColorValueOutOfIntRange("blue", blue);
|
||||||
|
|
||||||
|
this.colorAsInt = ColorUtil.argbToInt(alpha, red, green, blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return the block's X value in the world */
|
||||||
|
public int getBlockPosX() { return blockPosX; }
|
||||||
|
/** @return the block's Y value in the world */
|
||||||
|
public int getBlockPosY() { return blockPosY; }
|
||||||
|
/** @return the block's Z value in the world */
|
||||||
|
public int getBlockPosZ() { return blockPosZ; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the same instance of this event.
|
||||||
|
* Copying this event isn't supported
|
||||||
|
* since the internal parameters must be mutated
|
||||||
|
* by API users in order to be tracked by DH's internal
|
||||||
|
* logic.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public EventParam copy() { return this; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getCopyBeforeFire() { return false; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+131
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the Distant Horizons mod
|
||||||
|
* licensed under the GNU LGPL v3 License.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 James Seibel
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.seibel.distanthorizons.api.methods.events.abstractEvents;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
|
||||||
|
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper;
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent;
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be used to modify {@link IDhApiBlockStateWrapper}'s as they're created.
|
||||||
|
* This can be helpful for modded blocks that are mis-categorized by DH's base logic. <br><br>
|
||||||
|
*
|
||||||
|
* Note: this is only fired once per {@link IDhApiBlockStateWrapper} that is created
|
||||||
|
* and those {@link IDhApiBlockStateWrapper} will only be created once per JVM session.
|
||||||
|
*
|
||||||
|
* @author James Seibel
|
||||||
|
* @version 2026-04-14
|
||||||
|
* @since API 6.0.0
|
||||||
|
* @see IDhApiBlockStateWrapper
|
||||||
|
*/
|
||||||
|
public abstract class DhApiBlockStateWrapperCreatedEvent implements IDhApiEvent<DhApiBlockStateWrapperCreatedEvent.EventParam>
|
||||||
|
{
|
||||||
|
public abstract void blockStateWrapperCreated(DhApiEventParam<EventParam> event);
|
||||||
|
|
||||||
|
|
||||||
|
//=========================//
|
||||||
|
// internal DH API methods //
|
||||||
|
//=========================//
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void fireEvent(DhApiEventParam<EventParam> event) { this.blockStateWrapperCreated(event); }
|
||||||
|
|
||||||
|
|
||||||
|
//==================//
|
||||||
|
// parameter object //
|
||||||
|
//==================//
|
||||||
|
|
||||||
|
public static class EventParam implements IDhApiEventParam
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* A copy of the wrapper that will be created. <Br>
|
||||||
|
* Note: modifying this object won't change anything
|
||||||
|
* a new wrapper will be created after this event finishes.
|
||||||
|
*/
|
||||||
|
private final IDhApiBlockStateWrapper blockStateWrapper;
|
||||||
|
|
||||||
|
private boolean overridesSet = false;
|
||||||
|
private EDhApiBlockMaterial blockMaterial = null;
|
||||||
|
private Integer opacity = null;
|
||||||
|
private Boolean allowApiColorOverride = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=============//
|
||||||
|
// constructor //
|
||||||
|
//=============//
|
||||||
|
|
||||||
|
public EventParam(IDhApiBlockStateWrapper blockStateWrapper) { this.blockStateWrapper = blockStateWrapper; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=================//
|
||||||
|
// getters/setters //
|
||||||
|
//=================//
|
||||||
|
|
||||||
|
public IDhApiBlockStateWrapper getBlockStateWrapper() { return this.blockStateWrapper; }
|
||||||
|
|
||||||
|
/** if set this will override the value currently set in the given {@link IDhApiBlockStateWrapper} */
|
||||||
|
public void setBlockMaterial(EDhApiBlockMaterial blockMaterial)
|
||||||
|
{
|
||||||
|
this.blockMaterial = blockMaterial;
|
||||||
|
this.overridesSet = true;
|
||||||
|
}
|
||||||
|
public EDhApiBlockMaterial getBlockMaterial() { return this.blockMaterial; }
|
||||||
|
|
||||||
|
/** if set this will override the value currently set in the given {@link IDhApiBlockStateWrapper} */
|
||||||
|
public void setOpacity(int opacity)
|
||||||
|
{
|
||||||
|
this.opacity = opacity;
|
||||||
|
this.overridesSet = true;
|
||||||
|
}
|
||||||
|
public Integer getOpacity() { return this.opacity; }
|
||||||
|
|
||||||
|
/** if set to true this {@link IDhApiBlockStateWrapper} will trigger {@link DhApiBlockColorOverrideEvent} */
|
||||||
|
public void setAllowApiColorOverride(boolean allowApiColorOverride)
|
||||||
|
{
|
||||||
|
this.allowApiColorOverride = allowApiColorOverride;
|
||||||
|
this.overridesSet = true;
|
||||||
|
}
|
||||||
|
public Boolean getAllowApiColorOverride() { return this.allowApiColorOverride; }
|
||||||
|
|
||||||
|
/** If true then one or more options for this block were set to be changed */
|
||||||
|
public boolean getOverridesSet() { return this.overridesSet; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the same instance of this event.
|
||||||
|
* Copying this event isn't supported
|
||||||
|
* since the internal parameters must be mutated
|
||||||
|
* by API users in order to be tracked by DH's internal
|
||||||
|
* logic.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public EventParam copy() { return this; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getCopyBeforeFire() { return false; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+3
-1
@@ -31,7 +31,9 @@ import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhAp
|
|||||||
* @author James Seibel
|
* @author James Seibel
|
||||||
* @version 2024-3-2
|
* @version 2024-3-2
|
||||||
* @since API 2.0.0
|
* @since API 2.0.0
|
||||||
* @deprecated Replaced by {@link DhApiBeforeColorDepthTextureCreatedEvent} since this event's name isn't obvious when it fires.
|
* @deprecated Only used for the legacy OpenGL renderer <Br>
|
||||||
|
* Replaced by {@link DhApiBeforeColorDepthTextureCreatedEvent} since this event's name isn't obvious when it fires.
|
||||||
|
* Using {@link DhApiAfterColorDepthTextureCreatedEvent} instead is recommended
|
||||||
*/
|
*/
|
||||||
@Deprecated // internal notes: this method must be kept around due to Iris using it and we don't want to break old Iris support
|
@Deprecated // internal notes: this method must be kept around due to Iris using it and we don't want to break old Iris support
|
||||||
public abstract class DhApiColorDepthTextureCreatedEvent implements IDhApiEvent<DhApiColorDepthTextureCreatedEvent.EventParam>
|
public abstract class DhApiColorDepthTextureCreatedEvent implements IDhApiEvent<DhApiColorDepthTextureCreatedEvent.EventParam>
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public class DependencyInjector<BindableType extends IBindable> implements IDependencyInjector<BindableType> // Note to self: Don't try adding a generic type to IDhApiEvent, the constructor won't accept it
|
public class DependencyInjector<BindableType extends IBindable> implements IDependencyInjector<BindableType> // Note to self: Don't try adding a generic type to IDhApiEvent, the constructor won't accept it
|
||||||
{
|
{
|
||||||
protected final Map<Class<? extends BindableType>, ArrayList<BindableType>> dependencies = new HashMap<>();
|
protected final HashMap<Class<? extends BindableType>, ArrayList<BindableType>> dependencies = new HashMap<>();
|
||||||
|
|
||||||
/** Internal class reference to BindableType since we can't get it any other way. */
|
/** Internal class reference to BindableType since we can't get it any other way. */
|
||||||
protected final Class<? extends BindableType> bindableInterface;
|
protected final Class<? extends BindableType> bindableInterface;
|
||||||
|
|||||||
@@ -32,22 +32,27 @@ public final class ModInfo
|
|||||||
|
|
||||||
/** Incremented every time any packets are added, changed or removed, with a few exceptions. */
|
/** Incremented every time any packets are added, changed or removed, with a few exceptions. */
|
||||||
public static final int PROTOCOL_VERSION = 13;
|
public static final int PROTOCOL_VERSION = 13;
|
||||||
public static final String WRAPPER_PACKET_PATH = "message";
|
|
||||||
|
/**
|
||||||
|
* The full plugin channel name (RESOURCE_NAMESPACE:WRAPPER_PACKET_PATH)
|
||||||
|
* must be 20 characters or fewer for compatibility with MC 1.13 and older.
|
||||||
|
*/
|
||||||
|
public static final String WRAPPER_PACKET_PATH = "msg";
|
||||||
|
|
||||||
/** The internal mod name */
|
/** The internal mod name */
|
||||||
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.4.6-b-dev";
|
public static final String VERSION = "3.0.4-b-dev";
|
||||||
/** Returns true if the current build is an unstable developer build, false otherwise. */
|
/** Returns true if the current build is an unstable developer build, false otherwise. */
|
||||||
public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
|
public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
|
||||||
|
|
||||||
/** This version should only be updated when breaking changes are introduced to the DH API */
|
/** This version should only be updated when breaking changes are introduced to the DH API */
|
||||||
public static final int API_MAJOR_VERSION = 6;
|
public static final int API_MAJOR_VERSION = 6;
|
||||||
/** This version should be updated whenever new methods are added to the DH API */
|
/** This version should be updated whenever new methods are added to the DH API */
|
||||||
public static final int API_MINOR_VERSION = 0;
|
public static final int API_MINOR_VERSION = 1;
|
||||||
/** This version should be updated whenever non-breaking fixes are added to the DH API */
|
/** This version should be updated whenever non-breaking fixes are added to the DH API */
|
||||||
public static final int API_PATCH_VERSION = 0;
|
public static final int API_PATCH_VERSION = 1;
|
||||||
|
|
||||||
/** If the config file has an older version it'll be re-created from scratch. */
|
/** If the config file has an older version it'll be re-created from scratch. */
|
||||||
public static final int CONFIG_FILE_VERSION = 4;
|
public static final int CONFIG_FILE_VERSION = 4;
|
||||||
|
|||||||
+11
-1
@@ -17,7 +17,7 @@
|
|||||||
* 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.util;
|
package com.seibel.distanthorizons.coreapi.util;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
|
||||||
@@ -89,6 +89,16 @@ public class ColorUtil
|
|||||||
/** @param newBlue should be a value between 0 and 255 */
|
/** @param newBlue should be a value between 0 and 255 */
|
||||||
public static int setBlue(int color, int newBlue) { return (getAlpha(color) << 24) | (getRed(color) << 16) | (getGreen(color) << 8) | newBlue; }
|
public static int setBlue(int color, int newBlue) { return (getAlpha(color) << 24) | (getRed(color) << 16) | (getGreen(color) << 8) | newBlue; }
|
||||||
|
|
||||||
|
/** @throws IllegalArgumentException if the given int value is out of the range 0 - 255 (exclusive) */
|
||||||
|
public static void throwIfColorValueOutOfIntRange(String colorName, int value) throws IllegalArgumentException
|
||||||
|
{
|
||||||
|
if (value < 0
|
||||||
|
|| value > 255)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("["+colorName+"] with the value ["+value+"] is out of the expected range 0 - 255 (exclusive).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static int applyShade(int color, int shade)
|
public static int applyShade(int color, int shade)
|
||||||
+44
-32
@@ -1,18 +1,23 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "java"
|
id "java"
|
||||||
id "com.github.johnrengelman.shadow" version '8.1.1' apply false // Set this to true if you're using the standalone Core project
|
id "com.gradleup.shadow"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: "application"
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven { url "https://repo.enonic.com/public/" }
|
||||||
|
}
|
||||||
|
|
||||||
application {
|
tasks.withType(JavaCompile).configureEach {
|
||||||
mainClass.set("com.seibel.distanthorizons.core.jar.JarMain")
|
options.release = 8
|
||||||
|
options.encoding = "UTF-8"
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
shadowedArtifact // Used by DH to specify that we want to implement the shadowed core JAR file instead of the regular JAR file
|
shadowedArtifact // Used by DH to specify that we want to implement the shadowed core JAR file instead of the regular JAR file
|
||||||
shade
|
shade
|
||||||
implementation.extendsFrom shade
|
implementation.extendsFrom shade
|
||||||
|
testImplementation.extendsFrom compileOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem;
|
OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem;
|
||||||
@@ -20,38 +25,45 @@ OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePl
|
|||||||
// Set the OS lwjgl is using to the current os
|
// Set the OS lwjgl is using to the current os
|
||||||
project.ext.lwjglNatives = "natives-" + os.toFamilyName()
|
project.ext.lwjglNatives = "natives-" + os.toFamilyName()
|
||||||
|
|
||||||
dependencies { // All of these dependencies are in Vanilla Minecraft, but we need to depend on it as we arent importing Minecraft in the core
|
dependencies {
|
||||||
// Imports most of lwjgl's libraries (well, only the ones that we need)
|
// API project dependency
|
||||||
implementation platform("org.lwjgl:lwjgl-bom:${rootProject.lwjgl_version}")
|
implementation project(":api")
|
||||||
|
|
||||||
// REMEMBER: Dont shadow stuff here, these are just the libs that are included in Minecraft so that the core can use
|
// MC-provided libraries (available at runtime via Minecraft)
|
||||||
implementation "org.lwjgl:lwjgl"
|
compileOnly platform("org.lwjgl:lwjgl-bom:${rootProject.lwjgl_version}")
|
||||||
implementation "org.lwjgl:lwjgl-assimp"
|
compileOnly "org.lwjgl:lwjgl"
|
||||||
implementation "org.lwjgl:lwjgl-glfw"
|
compileOnly "org.lwjgl:lwjgl-assimp"
|
||||||
// OpenGL is removed since DH now handles rendering in the "Common" project
|
compileOnly "org.lwjgl:lwjgl-glfw"
|
||||||
// so we can use OpenGL for old MC versions and Blaze3D (IE Vulkan) for newer ones
|
compileOnly "org.lwjgl:lwjgl-stb"
|
||||||
// implementation "org.lwjgl:lwjgl-openal"
|
compileOnly "org.lwjgl:lwjgl-tinyfd"
|
||||||
// implementation "org.lwjgl:lwjgl-opengl"
|
testRuntimeOnly platform("org.lwjgl:lwjgl-bom:${rootProject.lwjgl_version}")
|
||||||
implementation "org.lwjgl:lwjgl-stb"
|
testRuntimeOnly "org.lwjgl:lwjgl::$lwjglNatives"
|
||||||
implementation "org.lwjgl:lwjgl-tinyfd"
|
testRuntimeOnly "org.lwjgl:lwjgl-assimp::$lwjglNatives"
|
||||||
runtimeOnly "org.lwjgl:lwjgl::$lwjglNatives"
|
testRuntimeOnly "org.lwjgl:lwjgl-glfw::$lwjglNatives"
|
||||||
runtimeOnly "org.lwjgl:lwjgl-assimp::$lwjglNatives"
|
testRuntimeOnly "org.lwjgl:lwjgl-stb::$lwjglNatives"
|
||||||
runtimeOnly "org.lwjgl:lwjgl-glfw::$lwjglNatives"
|
testRuntimeOnly "org.lwjgl:lwjgl-tinyfd::$lwjglNatives"
|
||||||
// runtimeOnly "org.lwjgl:lwjgl-openal::$lwjglNatives"
|
|
||||||
// runtimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives"
|
|
||||||
runtimeOnly "org.lwjgl:lwjgl-stb::$lwjglNatives"
|
|
||||||
runtimeOnly "org.lwjgl:lwjgl-tinyfd::$lwjglNatives"
|
|
||||||
|
|
||||||
// FIXME for some reason this line doesn't actually shade in the library
|
compileOnly("org.apache.logging.log4j:log4j-api:${rootProject.log4j_version}")
|
||||||
// shade "it.unimi.dsi:fastutil:${rootProject.fastutil_version}" // Add our own fastutil version
|
compileOnly("org.apache.logging.log4j:log4j-core:${rootProject.log4j_version}")
|
||||||
|
compileOnly("it.unimi.dsi:fastutil:${rootProject.fastutil_version}")
|
||||||
|
compileOnly("org.joml:joml:${rootProject.joml_version}")
|
||||||
|
compileOnly("io.netty:netty-buffer:${rootProject.netty_version}")
|
||||||
|
compileOnly("org.jetbrains:annotations:16.0.2")
|
||||||
|
compileOnly("com.google.code.findbugs:jsr305:3.0.2")
|
||||||
|
compileOnly("com.google.guava:guava:31.1-jre")
|
||||||
|
|
||||||
|
// DH's bundled libraries (shadowed + relocated in loader jars)
|
||||||
|
implementation("com.github.luben:zstd-jni:${rootProject.zstd_version}")
|
||||||
|
implementation("org.lz4:lz4-java:${rootProject.lz4_version}")
|
||||||
|
implementation("org.tukaani:xz:${rootProject.xz_version}")
|
||||||
|
implementation("org.xerial:sqlite-jdbc:${rootProject.sqlite_jdbc_version}")
|
||||||
|
implementation("com.electronwill.night-config:toml:${rootProject.nightconfig_version}")
|
||||||
|
implementation("com.electronwill.night-config:json:${rootProject.nightconfig_version}")
|
||||||
|
|
||||||
// Some other dependencies
|
// JUnit (core tests only)
|
||||||
implementation("org.jetbrains:annotations:16.0.2")
|
compileOnly("junit:junit:4.13")
|
||||||
implementation("com.google.code.findbugs:jsr305:3.0.2")
|
compileOnly("org.junit.jupiter:junit-jupiter:5.8.2")
|
||||||
implementation("com.google.common:google-collect:0.5")
|
compileOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2")
|
||||||
implementation("com.google.guava:guava:31.1-jre")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
artifacts {
|
artifacts {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public class Initializer
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static void init()
|
public static void preConfigInit()
|
||||||
{
|
{
|
||||||
//============================//
|
//============================//
|
||||||
// check referenced libraries //
|
// check referenced libraries //
|
||||||
@@ -177,6 +177,11 @@ public class Initializer
|
|||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/** fired after DH's config has been set up */
|
||||||
|
public static void postConfigInit()
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
//==============================//
|
//==============================//
|
||||||
@@ -238,8 +243,7 @@ public class Initializer
|
|||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -35,6 +35,6 @@ 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.enableSsao); }
|
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.enableSsao); }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -34,7 +34,7 @@ public class DhApiDebuggingConfig implements IDhApiDebuggingConfig
|
|||||||
|
|
||||||
|
|
||||||
public IDhApiConfigValue<EDhApiDebugRendering> debugRendering()
|
public IDhApiConfigValue<EDhApiDebugRendering> debugRendering()
|
||||||
{ return new DhApiConfigValue<EDhApiDebugRendering, EDhApiDebugRendering>(Config.Client.Advanced.Debugging.debugRendering); }
|
{ return new DhApiConfigValue<EDhApiDebugRendering, EDhApiDebugRendering>(Config.Client.Advanced.Debugging.debugRenderingColors); }
|
||||||
|
|
||||||
public IDhApiConfigValue<Boolean> debugKeybindings()
|
public IDhApiConfigValue<Boolean> debugKeybindings()
|
||||||
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Debugging.enableDebugKeybindings); }
|
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Debugging.enableDebugKeybindings); }
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode;
|
|||||||
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
|
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
|
||||||
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
|
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
|
||||||
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
|
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
|
||||||
|
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
|
||||||
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
||||||
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
|
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
@@ -40,8 +41,8 @@ import com.seibel.distanthorizons.core.util.objects.Pair;
|
|||||||
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
|
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
|
||||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccessor;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhMetaRenderer;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhMetaRenderer;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhTerrainRenderer;
|
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhVanillaFadeRenderer;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhVanillaFadeRenderer;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhTestTriangleRenderer;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhTestTriangleRenderer;
|
||||||
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
|
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
|
||||||
@@ -64,6 +65,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
@@ -146,6 +148,16 @@ public class ClientApi
|
|||||||
private Vec3d lastCameraPosForSpeedCheck = new Vec3d();
|
private Vec3d lastCameraPosForSpeedCheck = new Vec3d();
|
||||||
private long msSinceLastSpeedCheck = 0L;
|
private long msSinceLastSpeedCheck = 0L;
|
||||||
|
|
||||||
|
public static long firstRenderTimeMs = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* keeping track of this is necessary to fix
|
||||||
|
* out-of-date LODs from rendering when the shading
|
||||||
|
* is changed by Iris, causing LODs to often
|
||||||
|
* lack the side shading, which looks pretty bad
|
||||||
|
* when shaders are disabled.
|
||||||
|
*/
|
||||||
|
private boolean irisShadersEnabledLastFrame = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -160,7 +172,7 @@ public class ClientApi
|
|||||||
//==============//
|
//==============//
|
||||||
// world events //
|
// world events //
|
||||||
//==============//
|
//==============//
|
||||||
///region
|
//region world events
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* May be fired slightly before or after the associated
|
* May be fired slightly before or after the associated
|
||||||
@@ -238,14 +250,14 @@ public class ClientApi
|
|||||||
this.waitingClientLevels.clear();
|
this.waitingClientLevels.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
///endregion
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//==============//
|
//==============//
|
||||||
// level events //
|
// level events //
|
||||||
//==============//
|
//==============//
|
||||||
///region
|
//region level events
|
||||||
|
|
||||||
public void clientLevelUnloadEvent(IClientLevelWrapper level)
|
public void clientLevelUnloadEvent(IClientLevelWrapper level)
|
||||||
{
|
{
|
||||||
@@ -357,14 +369,14 @@ public class ClientApi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///endregion
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//============//
|
//============//
|
||||||
// networking //
|
// networking //
|
||||||
//============//
|
//============//
|
||||||
///region
|
//region networking
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Forwards a decoded message into the registered handlers.
|
* Forwards a decoded message into the registered handlers.
|
||||||
@@ -397,14 +409,14 @@ public class ClientApi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///endregion
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//===============//
|
//===============//
|
||||||
// LOD rendering //
|
// LOD rendering //
|
||||||
//===============//
|
//===============//
|
||||||
///region
|
//region lod rendering
|
||||||
|
|
||||||
/** Should be called before {@link ClientApi#renderDeferredLodsForShaders} */
|
/** Should be called before {@link ClientApi#renderDeferredLodsForShaders} */
|
||||||
public void renderLods() { this.renderLodLayer(false); }
|
public void renderLods() { this.renderLodLayer(false); }
|
||||||
@@ -415,261 +427,284 @@ public class ClientApi
|
|||||||
*/
|
*/
|
||||||
public void renderDeferredLodsForShaders() { this.renderLodLayer(true); }
|
public void renderDeferredLodsForShaders() { this.renderLodLayer(true); }
|
||||||
|
|
||||||
public static long firstRenderTimeMs = 0;
|
|
||||||
|
|
||||||
private void renderLodLayer(boolean renderingDeferredLayer)
|
private void renderLodLayer(boolean renderingDeferredLayer)
|
||||||
{
|
{
|
||||||
IProfilerWrapper profiler = MC_CLIENT.getProfiler();
|
IProfilerWrapper profiler = MC_CLIENT.getProfiler();
|
||||||
profiler.push("DH-RenderLevel");
|
try (IProfilerWrapper.IProfileBlock dhRender_profile = profiler.push("DH-RenderLevel"))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//===========//
|
|
||||||
// debugging //
|
|
||||||
//===========//
|
|
||||||
//region
|
|
||||||
|
|
||||||
//DhApiTerrainDataRepo.asyncDebugMethod(
|
|
||||||
// RENDER_STATE.clientLevelWrapper,
|
|
||||||
// MC_CLIENT.getPlayerBlockPos().getX(),
|
|
||||||
// MC_CLIENT.getPlayerBlockPos().getY(),
|
|
||||||
// MC_CLIENT.getPlayerBlockPos().getZ()
|
|
||||||
//);
|
|
||||||
|
|
||||||
//endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=====================//
|
|
||||||
// render thread tasks //
|
|
||||||
//=====================//
|
|
||||||
///region
|
|
||||||
|
|
||||||
// only run these tasks once per frame
|
|
||||||
if (!renderingDeferredLayer)
|
|
||||||
{
|
{
|
||||||
profiler.push("DH render thread tasks");
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//===============//
|
//===========//
|
||||||
// chat messages //
|
// debugging //
|
||||||
//===============//
|
//===========//
|
||||||
|
//region
|
||||||
|
|
||||||
this.sendQueuedChatMessages();
|
// only run these tasks once per frame
|
||||||
|
if (!renderingDeferredLayer)
|
||||||
|
|
||||||
|
|
||||||
//======================//
|
|
||||||
// GL Proxy queued jobs //
|
|
||||||
//======================//
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
// these tasks always need to be called, regardless of whether the renderer is enabled or not to prevent memory leaks
|
//DhApiTerrainDataRepo.asyncDebugMethod(
|
||||||
RenderThreadTaskHandler.INSTANCE.runRenderThreadTasks();
|
// RENDER_STATE.clientLevelWrapper,
|
||||||
}
|
// MC_CLIENT.getPlayerBlockPos().getX(),
|
||||||
catch (Exception e)
|
// MC_CLIENT.getPlayerBlockPos().getY(),
|
||||||
{
|
// MC_CLIENT.getPlayerBlockPos().getZ()
|
||||||
LOGGER.error("Unexpected issue running render thread tasks, error: [" + e.getMessage() + "].", e);
|
//);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
//==============//
|
|
||||||
// camera speed //
|
|
||||||
//==============//
|
|
||||||
|
|
||||||
long nowMs = System.currentTimeMillis();
|
//=====================//
|
||||||
if (this.msSinceLastSpeedCheck + MIN_MS_BETWEEN_SPEED_CHECKS < nowMs)
|
// render thread tasks //
|
||||||
|
//=====================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
// only run these tasks once per frame
|
||||||
|
if (!renderingDeferredLayer)
|
||||||
{
|
{
|
||||||
// calc time since last check
|
try (IProfilerWrapper.IProfileBlock renderTask_profile = profiler.push("DH render thread tasks"))
|
||||||
double secSinceLastCheck = (nowMs - this.msSinceLastSpeedCheck) / 1_000.0;
|
{
|
||||||
this.msSinceLastSpeedCheck = nowMs;
|
//===============//
|
||||||
|
// chat messages //
|
||||||
// get the distance traveled since last frame
|
//===============//
|
||||||
Vec3d camPos = MC_RENDER.getCameraExactPosition();
|
|
||||||
double distanceInBlocks = camPos.getDistance(this.lastCameraPosForSpeedCheck);
|
this.sendQueuedChatMessages();
|
||||||
double speed = distanceInBlocks / secSinceLastCheck;
|
|
||||||
|
|
||||||
// record new values for next check
|
|
||||||
this.cameraSpeedRollingAverage.add(speed);
|
//======================//
|
||||||
this.lastCameraPosForSpeedCheck = camPos;
|
// GL Proxy queued jobs //
|
||||||
|
//======================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// these tasks always need to be called, regardless of whether the renderer is enabled or not to prevent memory leaks
|
||||||
|
RenderThreadTaskHandler.INSTANCE.runRenderThreadTasks();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LOGGER.error("Unexpected issue running render thread tasks, error: [" + e.getMessage() + "].", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==============//
|
||||||
|
// camera speed //
|
||||||
|
//==============//
|
||||||
|
//region
|
||||||
|
|
||||||
|
long nowMs = System.currentTimeMillis();
|
||||||
|
if (this.msSinceLastSpeedCheck + MIN_MS_BETWEEN_SPEED_CHECKS < nowMs)
|
||||||
|
{
|
||||||
|
// calc time since last check
|
||||||
|
double secSinceLastCheck = (nowMs - this.msSinceLastSpeedCheck) / 1_000.0;
|
||||||
|
this.msSinceLastSpeedCheck = nowMs;
|
||||||
|
|
||||||
|
// get the distance traveled since last frame
|
||||||
|
Vec3d camPos = MC_RENDER.getCameraExactPosition();
|
||||||
|
double distanceInBlocks = camPos.getDistance(this.lastCameraPosForSpeedCheck);
|
||||||
|
double speed = distanceInBlocks / secSinceLastCheck;
|
||||||
|
|
||||||
|
// record new values for next check
|
||||||
|
this.cameraSpeedRollingAverage.add(speed);
|
||||||
|
this.lastCameraPosForSpeedCheck = camPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//====================//
|
||||||
|
// Iris data re-build //
|
||||||
|
//====================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
// delayed getter since ClientApi is created before this accessor is bound
|
||||||
|
IIrisAccessor irisAccessor = ModAccessorInjector.INSTANCE.get(IIrisAccessor.class);
|
||||||
|
if (irisAccessor != null)
|
||||||
|
{
|
||||||
|
boolean shadersActive = irisAccessor.isShaderPackInUse();
|
||||||
|
if (this.irisShadersEnabledLastFrame != shadersActive)
|
||||||
|
{
|
||||||
|
this.irisShadersEnabledLastFrame = shadersActive;
|
||||||
|
DhApi.Delayed.renderProxy.clearRenderDataCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
profiler.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
///endregion
|
//=================//
|
||||||
|
// parameter setup //
|
||||||
|
//=================//
|
||||||
|
//region
|
||||||
|
|
||||||
//=================//
|
EDhApiRenderPass renderPass;
|
||||||
// parameter setup //
|
if (DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
|
||||||
//=================//
|
|
||||||
///region
|
|
||||||
|
|
||||||
EDhApiRenderPass renderPass;
|
|
||||||
if (DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
|
|
||||||
{
|
|
||||||
if (renderingDeferredLayer)
|
|
||||||
{
|
{
|
||||||
renderPass = EDhApiRenderPass.TRANSPARENT;
|
if (renderingDeferredLayer)
|
||||||
|
{
|
||||||
|
renderPass = EDhApiRenderPass.TRANSPARENT;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
renderPass = EDhApiRenderPass.OPAQUE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
renderPass = EDhApiRenderPass.OPAQUE;
|
renderPass = EDhApiRenderPass.OPAQUE_AND_TRANSPARENT;
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
renderPass = EDhApiRenderPass.OPAQUE_AND_TRANSPARENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A global render state variable is used since MC has split up their
|
|
||||||
// render prep and actual rendering into different threads/methods
|
|
||||||
// this is annoying since it's possible to start a render with only
|
|
||||||
// partially complete info, but there isn't a better option at the moment
|
|
||||||
RenderParams renderParams = new RenderParams(renderPass, RENDER_STATE);
|
|
||||||
|
|
||||||
///endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//============//
|
|
||||||
// validation //
|
|
||||||
//============//
|
|
||||||
///region
|
|
||||||
|
|
||||||
if (firstRenderTimeMs == 0)
|
|
||||||
{
|
|
||||||
firstRenderTimeMs = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
String validationMessage = renderParams.getValidationErrorMessage(firstRenderTimeMs);
|
|
||||||
if (validationMessage != null)
|
|
||||||
{
|
|
||||||
// store the error message so it can be seen on the F3 screen
|
|
||||||
this.lastRenderParamValidationMessage = validationMessage;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.lastRenderParamValidationMessage = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
// A global render state variable is used since MC has split up their
|
||||||
}
|
// render prep and actual rendering into different threads/methods
|
||||||
|
// this is annoying since it's possible to start a render with only
|
||||||
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DISABLED)
|
// partially complete info, but there isn't a better option at the moment
|
||||||
{
|
RenderParams renderParams = new RenderParams(renderPass, RENDER_STATE);
|
||||||
return;
|
|
||||||
}
|
//endregion
|
||||||
|
|
||||||
///endregion
|
|
||||||
|
|
||||||
|
//============//
|
||||||
|
// validation //
|
||||||
//===========//
|
//============//
|
||||||
// rendering //
|
//region
|
||||||
//===========//
|
|
||||||
///region
|
if (firstRenderTimeMs == 0)
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// render pass //
|
|
||||||
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT)
|
|
||||||
{
|
{
|
||||||
if (!renderingDeferredLayer)
|
firstRenderTimeMs = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
String validationMessage = renderParams.getValidationErrorMessage(firstRenderTimeMs);
|
||||||
|
if (validationMessage != null)
|
||||||
|
{
|
||||||
|
// store the error message so it can be seen on the F3 screen
|
||||||
|
this.lastRenderParamValidationMessage = validationMessage;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.lastRenderParamValidationMessage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.rendererDisabledBecauseOfExceptions)
|
||||||
|
{
|
||||||
|
// re-enable rendering if the user toggles DH rendering
|
||||||
|
if (!Config.Client.quickEnableRendering.get())
|
||||||
{
|
{
|
||||||
boolean renderingCancelled = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderEvent.class, renderParams);
|
LOGGER.info("DH Renderer re-enabled after exception. Some rendering issues may occur. Please reboot Minecraft if you see any rendering issues.");
|
||||||
if (!renderingCancelled)
|
this.rendererDisabledBecauseOfExceptions = false;
|
||||||
|
Config.Client.quickEnableRendering.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DISABLED)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//===========//
|
||||||
|
// rendering //
|
||||||
|
//===========//
|
||||||
|
//region
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// render pass //
|
||||||
|
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT)
|
||||||
|
{
|
||||||
|
if (!renderingDeferredLayer)
|
||||||
{
|
{
|
||||||
LodRenderer.INSTANCE.render(renderParams, profiler);
|
boolean renderingCancelled = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderEvent.class, renderParams);
|
||||||
|
if (!renderingCancelled)
|
||||||
|
{
|
||||||
|
LodRenderer.INSTANCE.render(renderParams, profiler);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!DhApi.Delayed.renderProxy.getDeferTransparentRendering())
|
||||||
|
{
|
||||||
|
ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterRenderEvent.class, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (!DhApi.Delayed.renderProxy.getDeferTransparentRendering())
|
|
||||||
{
|
{
|
||||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterRenderEvent.class, null);
|
boolean renderingCancelled = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeDeferredRenderEvent.class, renderParams);
|
||||||
|
if (!renderingCancelled)
|
||||||
|
{
|
||||||
|
LodRenderer.INSTANCE.renderDeferred(renderParams, profiler);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (DhApi.Delayed.renderProxy.getDeferTransparentRendering())
|
||||||
|
{
|
||||||
|
ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterRenderEvent.class, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
boolean renderingCancelled = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeDeferredRenderEvent.class, renderParams);
|
if (!renderingDeferredLayer)
|
||||||
if (!renderingCancelled)
|
|
||||||
{
|
{
|
||||||
LodRenderer.INSTANCE.renderDeferred(renderParams, profiler);
|
IDhMetaRenderer metaRenderer = SingletonInjector.INSTANCE.get(IDhMetaRenderer.class);
|
||||||
}
|
IDhTestTriangleRenderer testRenderer = SingletonInjector.INSTANCE.get(IDhTestTriangleRenderer.class);
|
||||||
|
if (testRenderer != null
|
||||||
|
&& metaRenderer != null)
|
||||||
if (DhApi.Delayed.renderProxy.getDeferTransparentRendering())
|
{
|
||||||
{
|
// meta renderer needed for render state/texture
|
||||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterRenderEvent.class, null);
|
// for setup on some APIs (IE openGL)
|
||||||
|
metaRenderer.runRenderPassSetup(renderParams);
|
||||||
|
|
||||||
|
testRenderer.render(renderParams);
|
||||||
|
|
||||||
|
metaRenderer.runRenderPassCleanup(renderParams);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RATE_LIMITED_LOGGER.warn("Unable to find singleton [" + IDhTestTriangleRenderer.class.getSimpleName() + "]");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
if (!renderingDeferredLayer)
|
this.rendererDisabledBecauseOfExceptions = true;
|
||||||
{
|
LOGGER.error("Unexpected Renderer error in render pass [" + renderPass + "]. Error: " + e.getMessage(), e);
|
||||||
IDhMetaRenderer metaRenderer = SingletonInjector.INSTANCE.get(IDhMetaRenderer.class);
|
|
||||||
IDhTestTriangleRenderer testRenderer = SingletonInjector.INSTANCE.get(IDhTestTriangleRenderer.class);
|
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "" + MinecraftTextFormat.BOLD + "ERROR: Distant Horizons renderer has encountered an exception!" + MinecraftTextFormat.CLEAR_FORMATTING);
|
||||||
if (testRenderer != null
|
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Renderer disabled to try preventing GL state corruption." + MinecraftTextFormat.CLEAR_FORMATTING);
|
||||||
&& metaRenderer != null)
|
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Toggle DH rendering via the config UI to re-activate DH rendering." + MinecraftTextFormat.CLEAR_FORMATTING);
|
||||||
{
|
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Error: " + MinecraftTextFormat.CLEAR_FORMATTING + e);
|
||||||
// meta renderer needed for render state/texture
|
|
||||||
// for setup on some APIs (IE openGL)
|
|
||||||
metaRenderer.runRenderPassSetup(renderParams);
|
|
||||||
|
|
||||||
testRenderer.render(renderParams);
|
|
||||||
|
|
||||||
metaRenderer.runRenderPassCleanup(renderParams);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RATE_LIMITED_LOGGER.warn("Unable to find singleton [" + IDhTestTriangleRenderer.class.getSimpleName() + "]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
this.rendererDisabledBecauseOfExceptions = true;
|
|
||||||
LOGGER.error("Unexpected Renderer error in render pass [" + renderPass + "]. Error: " + e.getMessage(), e);
|
|
||||||
|
|
||||||
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "" + MinecraftTextFormat.BOLD + "ERROR: Distant Horizons renderer has encountered an exception!" + MinecraftTextFormat.CLEAR_FORMATTING);
|
//endregion
|
||||||
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Renderer disabled to try preventing GL state corruption." + MinecraftTextFormat.CLEAR_FORMATTING);
|
|
||||||
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Toggle DH rendering via the config UI to re-activate DH rendering." + MinecraftTextFormat.CLEAR_FORMATTING);
|
|
||||||
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Error: " + MinecraftTextFormat.CLEAR_FORMATTING + e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
profiler.pop(); // end LOD
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///endregion
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//================//
|
//================//
|
||||||
// fade rendering //
|
// fade rendering //
|
||||||
//================//
|
//================//
|
||||||
///region
|
//region fade rendering
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The first fade pass.
|
* The first fade pass.
|
||||||
@@ -732,14 +767,14 @@ public class ClientApi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///endregion
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//==========//
|
//==========//
|
||||||
// keyboard //
|
// keyboard //
|
||||||
//==========//
|
//==========//
|
||||||
///region
|
//region keyboard
|
||||||
|
|
||||||
/** Trigger once on key press, with CLIENT PLAYER. */
|
/** Trigger once on key press, with CLIENT PLAYER. */
|
||||||
public void keyPressedEvent(int glfwKey)
|
public void keyPressedEvent(int glfwKey)
|
||||||
@@ -763,19 +798,19 @@ public class ClientApi
|
|||||||
}
|
}
|
||||||
else if (glfwKey == GLFW.GLFW_KEY_F8)
|
else if (glfwKey == GLFW.GLFW_KEY_F8)
|
||||||
{
|
{
|
||||||
Config.Client.Advanced.Debugging.debugRendering.set(EDhApiDebugRendering.next(Config.Client.Advanced.Debugging.debugRendering.get()));
|
Config.Client.Advanced.Debugging.debugRenderingColors.set(EDhApiDebugRendering.next(Config.Client.Advanced.Debugging.debugRenderingColors.get()));
|
||||||
MC_CLIENT.sendChatMessage("F8: Set debug mode to " + Config.Client.Advanced.Debugging.debugRendering.get());
|
MC_CLIENT.sendChatMessage("F8: Set debug mode to " + Config.Client.Advanced.Debugging.debugRenderingColors.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///endregion
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//======//
|
//======//
|
||||||
// chat //
|
// chat //
|
||||||
//======//
|
//======//
|
||||||
///region
|
//region chat
|
||||||
|
|
||||||
private void sendQueuedChatMessages()
|
private void sendQueuedChatMessages()
|
||||||
{
|
{
|
||||||
@@ -909,7 +944,7 @@ public class ClientApi
|
|||||||
*/
|
*/
|
||||||
public void showOverlayMessageNextFrame(String message) { this.overlayMessageQueueForNextFrame.add(message); }
|
public void showOverlayMessageNextFrame(String message) { this.overlayMessageQueueForNextFrame.add(message); }
|
||||||
|
|
||||||
///endregion
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -90,7 +90,7 @@ public class ClientPluginChannelApi
|
|||||||
|
|
||||||
LOGGER.info("Server level key received: [" + msg.levelKey + "].");
|
LOGGER.info("Server level key received: [" + msg.levelKey + "].");
|
||||||
|
|
||||||
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread(() ->
|
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("ClientPluginChannelApi onLevelInitMessage", () ->
|
||||||
{
|
{
|
||||||
IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true);
|
IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true);
|
||||||
IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel();
|
IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel();
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ public class ServerApi
|
|||||||
|
|
||||||
public void serverPlayerJoinEvent(IServerPlayerWrapper player)
|
public void serverPlayerJoinEvent(IServerPlayerWrapper player)
|
||||||
{
|
{
|
||||||
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly())
|
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@ public class ServerApi
|
|||||||
}
|
}
|
||||||
public void serverPlayerDisconnectEvent(IServerPlayerWrapper player)
|
public void serverPlayerDisconnectEvent(IServerPlayerWrapper player)
|
||||||
{
|
{
|
||||||
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly())
|
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -150,7 +150,7 @@ public class ServerApi
|
|||||||
}
|
}
|
||||||
public void serverPlayerLevelChangeEvent(IServerPlayerWrapper player, IServerLevelWrapper originLevel, IServerLevelWrapper destinationLevel)
|
public void serverPlayerLevelChangeEvent(IServerPlayerWrapper player, IServerLevelWrapper originLevel, IServerLevelWrapper destinationLevel)
|
||||||
{
|
{
|
||||||
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly())
|
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -170,7 +170,7 @@ public class ServerApi
|
|||||||
*/
|
*/
|
||||||
public void pluginMessageReceived(IServerPlayerWrapper player, @NotNull AbstractNetworkMessage message)
|
public void pluginMessageReceived(IServerPlayerWrapper player, @NotNull AbstractNetworkMessage message)
|
||||||
{
|
{
|
||||||
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly())
|
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import com.seibel.distanthorizons.core.level.IDhLevel;
|
|||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||||
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
||||||
|
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer;
|
import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer;
|
||||||
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
|
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
|
||||||
import com.seibel.distanthorizons.core.util.objects.Pair;
|
import com.seibel.distanthorizons.core.util.objects.Pair;
|
||||||
@@ -63,6 +64,7 @@ public class SharedApi
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static AbstractDhWorld currentWorld;
|
private static AbstractDhWorld currentWorld;
|
||||||
|
private static final Object worldLockObject = new Object();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -72,7 +74,6 @@ public class SharedApi
|
|||||||
//region
|
//region
|
||||||
|
|
||||||
private SharedApi() { }
|
private SharedApi() { }
|
||||||
public static void init() { Initializer.init(); }
|
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
@@ -87,46 +88,51 @@ public class SharedApi
|
|||||||
|
|
||||||
public static void setDhWorld(AbstractDhWorld newWorld)
|
public static void setDhWorld(AbstractDhWorld newWorld)
|
||||||
{
|
{
|
||||||
AbstractDhWorld oldWorld = currentWorld;
|
synchronized (worldLockObject)
|
||||||
if (oldWorld != null)
|
|
||||||
{
|
{
|
||||||
oldWorld.close();
|
AbstractDhWorld oldWorld = currentWorld;
|
||||||
}
|
if (oldWorld != null)
|
||||||
currentWorld = newWorld;
|
|
||||||
|
|
||||||
// starting and stopping the DataRenderTransformer is necessary to prevent attempting to
|
|
||||||
// access the MC level at inappropriate times, which can cause exceptions
|
|
||||||
if (currentWorld != null)
|
|
||||||
{
|
|
||||||
ThreadPoolUtil.setupThreadPools();
|
|
||||||
|
|
||||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiWorldLoadEvent.class, new DhApiWorldLoadEvent.EventParam());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ThreadPoolUtil.shutdownThreadPools();
|
|
||||||
|
|
||||||
// delayed get because SharedApi will be created before the singleton has been bound
|
|
||||||
AbstractDebugWireframeRenderer debugWireframeRenderer = SingletonInjector.INSTANCE.get(AbstractDebugWireframeRenderer.class);
|
|
||||||
debugWireframeRenderer.clearRenderables();
|
|
||||||
|
|
||||||
if (MC_RENDER != null)
|
|
||||||
{
|
{
|
||||||
MC_RENDER.clearTargetFrameBuffer();
|
oldWorld.close();
|
||||||
}
|
}
|
||||||
|
currentWorld = newWorld;
|
||||||
|
|
||||||
// shouldn't be necessary, but if we missed closing one of the connections this should make sure they're all closed
|
// starting and stopping the DataRenderTransformer is necessary to prevent attempting to
|
||||||
AbstractDhRepo.closeAllConnections();
|
// access the MC level at inappropriate times, which can cause exceptions
|
||||||
// needs to be closed on world shutdown to clear out un-processed chunks
|
if (currentWorld != null)
|
||||||
WORLD_CHUNK_UPDATE_MANAGER.clear();
|
{
|
||||||
|
ThreadPoolUtil.setupThreadPools();
|
||||||
// recommend that the garbage collector cleans up any objects from the old world and thread pools
|
|
||||||
System.gc();
|
ApiEventInjector.INSTANCE.fireAllEvents(DhApiWorldLoadEvent.class, new DhApiWorldLoadEvent.EventParam());
|
||||||
|
}
|
||||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiWorldUnloadEvent.class, new DhApiWorldUnloadEvent.EventParam());
|
else
|
||||||
|
{
|
||||||
// fired after the unload event so API users can't change the read-only for any new worlds
|
ThreadPoolUtil.shutdownThreadPools();
|
||||||
DhApiWorldProxy.INSTANCE.setReadOnly(false, false);
|
|
||||||
|
// delayed get because SharedApi will be created before the singleton has been bound
|
||||||
|
AbstractDebugWireframeRenderer debugWireframeRenderer = SingletonInjector.INSTANCE.get(AbstractDebugWireframeRenderer.class);
|
||||||
|
debugWireframeRenderer.clearRenderables();
|
||||||
|
|
||||||
|
if (MC_RENDER != null)
|
||||||
|
{
|
||||||
|
MC_RENDER.clearTargetFrameBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldn't be necessary, but if we missed closing one of the connections this should make sure they're all closed
|
||||||
|
AbstractDhRepo.closeAllConnections();
|
||||||
|
// needs to be closed on world shutdown to clear out un-processed chunks
|
||||||
|
WORLD_CHUNK_UPDATE_MANAGER.clear();
|
||||||
|
|
||||||
|
RenderThreadTaskHandler.INSTANCE.clearDebugStats();
|
||||||
|
|
||||||
|
// recommend that the garbage collector cleans up any objects from the old world and thread pools
|
||||||
|
System.gc();
|
||||||
|
|
||||||
|
ApiEventInjector.INSTANCE.fireAllEvents(DhApiWorldUnloadEvent.class, new DhApiWorldUnloadEvent.EventParam());
|
||||||
|
|
||||||
|
// fired after the unload event so API users can't change the read-only for any new worlds
|
||||||
|
DhApiWorldProxy.INSTANCE.setReadOnly(false, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,14 +204,14 @@ public class SharedApi
|
|||||||
// If the client world isn't loaded yet, keep track of which chunks were loaded so we can use them later.
|
// If the client world isn't loaded yet, keep track of which chunks were loaded so we can use them later.
|
||||||
// This may happen if the client world and client level load events happen out of order
|
// This may happen if the client world and client level load events happen out of order
|
||||||
IClientLevelWrapper clientLevel = (IClientLevelWrapper) levelWrapper;
|
IClientLevelWrapper clientLevel = (IClientLevelWrapper) levelWrapper;
|
||||||
ClientApi.INSTANCE.waitingChunkByClientLevelAndPos.replace(new Pair<>(clientLevel, chunkWrapper.getChunkPos()), chunkWrapper);
|
ClientApi.INSTANCE.waitingChunkByClientLevelAndPos.put(new Pair<>(clientLevel, chunkWrapper.getChunkPos()), chunkWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore updates if the world is read-only
|
// ignore updates if the world is read-only
|
||||||
if (DhApiWorldProxy.INSTANCE.getReadOnly())
|
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -218,7 +224,7 @@ public class SharedApi
|
|||||||
{
|
{
|
||||||
// the client level isn't loaded yet
|
// the client level isn't loaded yet
|
||||||
IClientLevelWrapper clientLevel = (IClientLevelWrapper) levelWrapper;
|
IClientLevelWrapper clientLevel = (IClientLevelWrapper) levelWrapper;
|
||||||
ClientApi.INSTANCE.waitingChunkByClientLevelAndPos.replace(new Pair<>(clientLevel, chunkWrapper.getChunkPos()), chunkWrapper);
|
ClientApi.INSTANCE.waitingChunkByClientLevelAndPos.put(new Pair<>(clientLevel, chunkWrapper.getChunkPos()), chunkWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
+10
@@ -11,6 +11,8 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapp
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
@@ -28,6 +30,9 @@ public class WorldChunkUpdateManager
|
|||||||
/** singleton since we only expect to have one world loaded at a time */
|
/** singleton since we only expect to have one world loaded at a time */
|
||||||
public static final WorldChunkUpdateManager INSTANCE = new WorldChunkUpdateManager();
|
public static final WorldChunkUpdateManager INSTANCE = new WorldChunkUpdateManager();
|
||||||
|
|
||||||
|
public static final Set<String> LOGGED_GET_ERROR_MESSAGES = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queues are only removed during world shutdown.
|
* Queues are only removed during world shutdown.
|
||||||
* The assumption is that there will be a limited number of {@link ILevelWrapper}'s
|
* The assumption is that there will be a limited number of {@link ILevelWrapper}'s
|
||||||
@@ -37,6 +42,7 @@ public class WorldChunkUpdateManager
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=============//
|
//=============//
|
||||||
// constructor //
|
// constructor //
|
||||||
//=============//
|
//=============//
|
||||||
@@ -62,6 +68,7 @@ public class WorldChunkUpdateManager
|
|||||||
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
|
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
|
||||||
if (world == null)
|
if (world == null)
|
||||||
{
|
{
|
||||||
|
// world isn't loaded, no warnings need to be logged
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +80,8 @@ public class WorldChunkUpdateManager
|
|||||||
// but this check confirms it
|
// but this check confirms it
|
||||||
&& !(levelWrapper instanceof IClientLevelWrapper))
|
&& !(levelWrapper instanceof IClientLevelWrapper))
|
||||||
{
|
{
|
||||||
|
// how did we get a server level wrapper on the client?
|
||||||
|
// this shouldn't happen, but just in case
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
else if (
|
else if (
|
||||||
@@ -81,6 +90,7 @@ public class WorldChunkUpdateManager
|
|||||||
// when hosting a server we only care about the server wrappers
|
// when hosting a server we only care about the server wrappers
|
||||||
&& !(levelWrapper instanceof IServerLevelWrapper))
|
&& !(levelWrapper instanceof IServerLevelWrapper))
|
||||||
{
|
{
|
||||||
|
// ignore client updates on the server
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.core.config;
|
package com.seibel.distanthorizons.core.config;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.api.DhApi;
|
|
||||||
import com.seibel.distanthorizons.api.enums.config.*;
|
import com.seibel.distanthorizons.api.enums.config.*;
|
||||||
import com.seibel.distanthorizons.api.enums.config.quickOptions.*;
|
import com.seibel.distanthorizons.api.enums.config.quickOptions.*;
|
||||||
import com.seibel.distanthorizons.api.enums.rendering.*;
|
import com.seibel.distanthorizons.api.enums.rendering.*;
|
||||||
@@ -27,14 +26,12 @@ import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGenerat
|
|||||||
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorProgressDisplayLocation;
|
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorProgressDisplayLocation;
|
||||||
import com.seibel.distanthorizons.core.config.eventHandlers.*;
|
import com.seibel.distanthorizons.core.config.eventHandlers.*;
|
||||||
import com.seibel.distanthorizons.core.config.eventHandlers.presets.*;
|
import com.seibel.distanthorizons.core.config.eventHandlers.presets.*;
|
||||||
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
|
|
||||||
import com.seibel.distanthorizons.core.config.types.*;
|
import com.seibel.distanthorizons.core.config.types.*;
|
||||||
import com.seibel.distanthorizons.core.config.types.enums.*;
|
import com.seibel.distanthorizons.core.config.types.enums.*;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
import com.seibel.distanthorizons.core.util.NativeDialogUtil;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
|
||||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
@@ -146,8 +143,11 @@ public class Config
|
|||||||
public static ConfigCategory quality = new ConfigCategory.Builder().set(Quality.class).build();
|
public static ConfigCategory quality = new ConfigCategory.Builder().set(Quality.class).build();
|
||||||
public static ConfigUISpacer qualitySpacer = new ConfigUISpacer.Builder().build();
|
public static ConfigUISpacer qualitySpacer = new ConfigUISpacer.Builder().build();
|
||||||
|
|
||||||
public static ConfigUiLinkedEntry quickEnableSsao = new ConfigUiLinkedEntry(Ssao.enableSsao);
|
|
||||||
public static ConfigCategory ssao = new ConfigCategory.Builder().set(Ssao.class).build();
|
public static ConfigEntry<Boolean> enableSsao = new ConfigEntry.Builder<Boolean>()
|
||||||
|
.set(true)
|
||||||
|
.comment("Enable Screen Space Ambient Occlusion")
|
||||||
|
.build();
|
||||||
public static ConfigUISpacer ssaoSpacer = new ConfigUISpacer.Builder().build();
|
public static ConfigUISpacer ssaoSpacer = new ConfigUISpacer.Builder().build();
|
||||||
|
|
||||||
|
|
||||||
@@ -358,17 +358,6 @@ public class Config
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Ssao
|
|
||||||
{
|
|
||||||
public static ConfigUIComment ssaoHeader = new ConfigUIComment.Builder().setParentConfigClass(Ssao.class).build();
|
|
||||||
|
|
||||||
public static ConfigEntry<Boolean> enableSsao = new ConfigEntry.Builder<Boolean>()
|
|
||||||
.set(true)
|
|
||||||
.comment("Enable Screen Space Ambient Occlusion")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class GenericRendering
|
public static class GenericRendering
|
||||||
{
|
{
|
||||||
public static ConfigUIComment genericRendererHeader = new ConfigUIComment.Builder().setParentConfigClass(GenericRendering.class).build();
|
public static ConfigUIComment genericRendererHeader = new ConfigUIComment.Builder().setParentConfigClass(GenericRendering.class).build();
|
||||||
@@ -424,14 +413,6 @@ public class Config
|
|||||||
+ "Changes will only be seen when the world is re-loaded.\n"
|
+ "Changes will only be seen when the world is re-loaded.\n"
|
||||||
+ "")
|
+ "")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<Boolean> enableInstancedRendering = new ConfigEntry.Builder<Boolean>()
|
|
||||||
.set(true)
|
|
||||||
.comment(""
|
|
||||||
+ "Can be disabled to use much slower but more compatible direct rendering. \n"
|
|
||||||
+ "Disabling this can be used to fix some crashes on Mac. \n"
|
|
||||||
+ "")
|
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Fog
|
public static class Fog
|
||||||
@@ -565,7 +546,7 @@ public class Config
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<Float> heightFogBaseHeight = new ConfigEntry.Builder<Float>()
|
public static ConfigEntry<Float> heightFogBaseHeight = new ConfigEntry.Builder<Float>()
|
||||||
.setMinDefaultMax(-4096.0f, 80.0f, 4096.0f)
|
.setMinDefaultMax(-3_000_000.0f, 80.0f, 3_000_000.0f)
|
||||||
.comment("If the height fog is calculated around a set height, what is that height position?")
|
.comment("If the height fog is calculated around a set height, what is that height position?")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -745,57 +726,56 @@ public class Config
|
|||||||
+ "Disable this if shadows render incorrectly.")
|
+ "Disable this if shadows render incorrectly.")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
public static ConfigUISpacer ignoreCsvStartSpacer = new ConfigUISpacer.Builder().build();
|
||||||
|
|
||||||
public static ConfigEntry<String> ignoredRenderBlockCsv = new ConfigEntry.Builder<String>()
|
public static ConfigEntry<String> ignoredRenderBlockCsv = new ConfigEntry.Builder<String>()
|
||||||
.set("minecraft:barrier,minecraft:structure_void,minecraft:light,minecraft:tripwire,minecraft:brown_mushroom")
|
.set("minecraft:barrier,minecraft:structure_void,minecraft:light,minecraft:tripwire,minecraft:brown_mushroom")
|
||||||
.setAppearance(EConfigEntryAppearance.ALL)
|
.setAppearance(EConfigEntryAppearance.ALL)
|
||||||
|
.addListener(RenderBlockCacheCsvHandler.INSTANCE)
|
||||||
.comment(""
|
.comment(""
|
||||||
+ "A comma separated list of block resource locations that won't be rendered by DH. \n"
|
+ "A comma separated list of block resource locations that won't be rendered by DH. \n"
|
||||||
+ "Air is always included in this list. \n"
|
+ "Air is always included in this list. \n"
|
||||||
+ "Requires a restart to change. \n"
|
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "Note:\n"
|
+ "Note:\n"
|
||||||
+ "If you see gaps, or holes you may have to change\n"
|
+ "If you see gaps, or holes you may have to change\n"
|
||||||
+ "worldCompression to ["+EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS+"] and re-generate the LODs.\n"
|
+ "worldCompression to ["+EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS+"] and re-generate the LODs.\n"
|
||||||
+ "Black spots may happen occur to block lighting being zero for covered blocks.\n"
|
|
||||||
+ "")
|
+ "")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<String> ignoredRenderCaveBlockCsv = new ConfigEntry.Builder<String>()
|
public static ConfigEntry<String> ignoredRenderCaveBlockCsv = new ConfigEntry.Builder<String>()
|
||||||
.set("") // config is empty since most cave blocks will be automatically ignored due to being: transparent, non-solid, or liquids, but new blocks can be added here if needed
|
.set("")
|
||||||
.setAppearance(EConfigEntryAppearance.ALL)
|
.setAppearance(EConfigEntryAppearance.ALL)
|
||||||
|
.addListener(RenderBlockCacheCsvHandler.INSTANCE)
|
||||||
.comment(""
|
.comment(""
|
||||||
+ "A comma separated list of block resource locations that shouldn't be rendered \n"
|
+ "A comma separated list of block resource locations that shouldn't be rendered \n"
|
||||||
+ "if they are in a 0 sky light underground area. \n"
|
+ "if they are in a 0 sky light underground area. \n"
|
||||||
+ "Air is always included in this list. \n"
|
+ "Air is always included in this list. \n"
|
||||||
+ "Requires a restart to change. \n"
|
+ "\n"
|
||||||
|
+ "Defaults to an empty list since most cave blocks will be automatically ignored due to being: \n"
|
||||||
|
+ "transparent, non-solid, or liquids, but new blocks can be added here if needed.\n"
|
||||||
+ "")
|
+ "")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
public static ConfigEntry<String> waterSubSurfaceBlockReplacementCsv = new ConfigEntry.Builder<String>()
|
||||||
|
.set("minecraft:kelp,minecraft:tall_seagrass,minecraft:seagrass")
|
||||||
|
.setAppearance(EConfigEntryAppearance.ALL)
|
||||||
|
.addListener(RenderBlockCacheCsvHandler.INSTANCE)
|
||||||
|
.comment(""
|
||||||
|
+ "A comma separated list of block resource locations that will be replaced by water \n"
|
||||||
|
+ "if they're visible on the water's surface. \n"
|
||||||
|
+ "")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
public static ConfigEntry<String> waterSurfaceBlockReplacementCsv = new ConfigEntry.Builder<String>()
|
||||||
|
.set("minecraft:lily_pad")
|
||||||
|
.setAppearance(EConfigEntryAppearance.ALL)
|
||||||
|
.addListener(RenderBlockCacheCsvHandler.INSTANCE)
|
||||||
|
.comment(""
|
||||||
|
+ "A comma separated list of block resource locations that will be removed \n"
|
||||||
|
+ "when on top of water. \n"
|
||||||
|
+ "")
|
||||||
|
.build();
|
||||||
|
|
||||||
static
|
|
||||||
{
|
|
||||||
ignoredRenderBlockCsv.addListener(new ConfigChangeListener<String>(ignoredRenderBlockCsv,
|
|
||||||
(blockCsv) ->
|
|
||||||
{
|
|
||||||
IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
|
|
||||||
if (wrapperFactory != null)
|
|
||||||
{
|
|
||||||
wrapperFactory.resetRendererIgnoredBlocksSet();
|
|
||||||
DhApi.Delayed.renderProxy.clearRenderDataCache();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
ignoredRenderCaveBlockCsv.addListener(new ConfigChangeListener<String>(ignoredRenderCaveBlockCsv,
|
|
||||||
(blockCsv) ->
|
|
||||||
{
|
|
||||||
IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
|
|
||||||
if (wrapperFactory != null)
|
|
||||||
{
|
|
||||||
wrapperFactory.resetRendererIgnoredCaveBlocks();
|
|
||||||
DhApi.Delayed.renderProxy.clearRenderDataCache();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Experimental
|
public static class Experimental
|
||||||
@@ -834,8 +814,7 @@ public class Config
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<EDhApiRenderApi> renderingApi = new ConfigEntry.Builder<EDhApiRenderApi>()
|
public static ConfigEntry<EDhApiRenderApi> renderingApi = new ConfigEntry.Builder<EDhApiRenderApi>()
|
||||||
.set(EDhApiRenderApi.AUTO)
|
.set(EDhApiRenderApi.AUTO)
|
||||||
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE) // very experimental option and only supported
|
|
||||||
.comment(""
|
.comment(""
|
||||||
+ "Requires a restart to change. \n"
|
+ "Requires a restart to change. \n"
|
||||||
+ " \n"
|
+ " \n"
|
||||||
@@ -908,11 +887,11 @@ public class Config
|
|||||||
+ "What renderer is active? \n"
|
+ "What renderer is active? \n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ EDhApiRendererMode.DEFAULT + ": Default lod renderer \n"
|
+ EDhApiRendererMode.DEFAULT + ": Default lod renderer \n"
|
||||||
+ EDhApiRendererMode.DEBUG + ": Debug testing renderer \n"
|
+ EDhApiRendererMode.DEBUG_TRIANGLE + ": Debug testing renderer \n"
|
||||||
+ EDhApiRendererMode.DISABLED + ": Disable rendering")
|
+ EDhApiRendererMode.DISABLED + ": Disable rendering")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<EDhApiDebugRendering> debugRendering = new ConfigEntry.Builder<EDhApiDebugRendering>()
|
public static ConfigEntry<EDhApiDebugRendering> debugRenderingColors = new ConfigEntry.Builder<EDhApiDebugRendering>()
|
||||||
.set(EDhApiDebugRendering.OFF)
|
.set(EDhApiDebugRendering.OFF)
|
||||||
.comment(""
|
.comment(""
|
||||||
+ "Should specialized colors/rendering modes be used? \n"
|
+ "Should specialized colors/rendering modes be used? \n"
|
||||||
@@ -925,6 +904,13 @@ public class Config
|
|||||||
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
|
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
public static ConfigEntry<Boolean> enableWhiteWorld = new ConfigEntry.Builder<Boolean>()
|
||||||
|
.set(false)
|
||||||
|
.comment(""
|
||||||
|
+ "Stops vertex colors from being passed. \n"
|
||||||
|
+ "Useful for debugging shaders")
|
||||||
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<Boolean> lodOnlyMode = new ConfigEntry.Builder<Boolean>()
|
public static ConfigEntry<Boolean> lodOnlyMode = new ConfigEntry.Builder<Boolean>()
|
||||||
.set(false)
|
.set(false)
|
||||||
.comment(""
|
.comment(""
|
||||||
@@ -952,13 +938,6 @@ public class Config
|
|||||||
+ "")
|
+ "")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<Boolean> enableWhiteWorld = new ConfigEntry.Builder<Boolean>()
|
|
||||||
.set(false)
|
|
||||||
.comment(""
|
|
||||||
+ "Stops vertex colors from being passed. \n"
|
|
||||||
+ "Useful for debugging shaders")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
public static ConfigEntry<Boolean> showOverlappingQuadErrors = new ConfigEntry.Builder<Boolean>()
|
public static ConfigEntry<Boolean> showOverlappingQuadErrors = new ConfigEntry.Builder<Boolean>()
|
||||||
.set(false)
|
.set(false)
|
||||||
.comment(""
|
.comment(""
|
||||||
@@ -985,6 +964,7 @@ public class Config
|
|||||||
public static ConfigCategory debugWireframe = new ConfigCategory.Builder().set(DebugWireframe.class).build();
|
public static ConfigCategory debugWireframe = new ConfigCategory.Builder().set(DebugWireframe.class).build();
|
||||||
public static ConfigCategory openGl = new ConfigCategory.Builder().set(OpenGl.class).build();
|
public static ConfigCategory openGl = new ConfigCategory.Builder().set(OpenGl.class).build();
|
||||||
public static ConfigCategory columnBuilderDebugging = new ConfigCategory.Builder().set(ColumnBuilderDebugging.class).build();
|
public static ConfigCategory columnBuilderDebugging = new ConfigCategory.Builder().set(ColumnBuilderDebugging.class).build();
|
||||||
|
public static ConfigCategory positionFinderDebugging = new ConfigCategory.Builder().set(PositionFinder.class).build();
|
||||||
public static ConfigCategory f3Screen = new ConfigCategory.Builder().set(F3Screen.class).build();
|
public static ConfigCategory f3Screen = new ConfigCategory.Builder().set(F3Screen.class).build();
|
||||||
public static ConfigCategory exampleConfigScreen = new ConfigCategory.Builder().set(ExampleConfigScreen.class).build();
|
public static ConfigCategory exampleConfigScreen = new ConfigCategory.Builder().set(ExampleConfigScreen.class).build();
|
||||||
|
|
||||||
@@ -1019,15 +999,6 @@ public class Config
|
|||||||
.set(false)
|
.set(false)
|
||||||
.comment("Render LOD section status?")
|
.comment("Render LOD section status?")
|
||||||
.build();
|
.build();
|
||||||
public static ConfigEntry<Boolean> showRenderSectionToggling = new ConfigEntry.Builder<Boolean>()
|
|
||||||
.set(false)
|
|
||||||
.comment("" +
|
|
||||||
"A white box will be drawn when an LOD starts rendering \n" +
|
|
||||||
"and a purple box when an LOD stops rendering. \n" +
|
|
||||||
"\n" +
|
|
||||||
"This can be used to debug Quad Tree holes.\n" +
|
|
||||||
"")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
public static ConfigEntry<Boolean> showQuadTreeRenderStatus = new ConfigEntry.Builder<Boolean>()
|
public static ConfigEntry<Boolean> showQuadTreeRenderStatus = new ConfigEntry.Builder<Boolean>()
|
||||||
.set(false)
|
.set(false)
|
||||||
@@ -1080,13 +1051,6 @@ public class Config
|
|||||||
+ "")
|
+ "")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<EDhApiGpuUploadMethod> glUploadMode = new ConfigEntry.Builder<EDhApiGpuUploadMethod>()
|
|
||||||
.set(EDhApiGpuUploadMethod.AUTO)
|
|
||||||
.comment(""
|
|
||||||
+ "\n"
|
|
||||||
+ "")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ColumnBuilderDebugging
|
public static class ColumnBuilderDebugging
|
||||||
@@ -1132,6 +1096,36 @@ public class Config
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class PositionFinder
|
||||||
|
{
|
||||||
|
//public static ConfigUIComment positionFinderHeader = new ConfigUIComment.Builder().setParentConfigClass(ColumnBuilderDebugging.class).build();
|
||||||
|
|
||||||
|
public static ConfigEntry<Boolean> positionFinderEnable = new ConfigEntry.Builder<Boolean>()
|
||||||
|
.set(false)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
public static ConfigEntry<Integer> positionFinderDetailLevel = new ConfigEntry.Builder<Integer>()
|
||||||
|
.set((int) DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
|
||||||
|
.build();
|
||||||
|
public static ConfigEntry<Integer> positionFinderXPos = new ConfigEntry.Builder<Integer>()
|
||||||
|
.set(0)
|
||||||
|
.build();
|
||||||
|
public static ConfigEntry<Integer> positionFinderZPos = new ConfigEntry.Builder<Integer>()
|
||||||
|
.set(0)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
public static ConfigEntry<Integer> positionFinderMinBlockY = new ConfigEntry.Builder<Integer>()
|
||||||
|
.set(-64)
|
||||||
|
.build();
|
||||||
|
public static ConfigEntry<Integer> positionFinderMaxBlockY = new ConfigEntry.Builder<Integer>()
|
||||||
|
.set(125)
|
||||||
|
.build();
|
||||||
|
public static ConfigEntry<Float> positionFinderMarginPercent = new ConfigEntry.Builder<Float>()
|
||||||
|
.set(0.0f)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public static class F3Screen
|
public static class F3Screen
|
||||||
{
|
{
|
||||||
public static ConfigUIComment f3ScreenHeader = new ConfigUIComment.Builder().setParentConfigClass(F3Screen.class).build();
|
public static ConfigUIComment f3ScreenHeader = new ConfigUIComment.Builder().setParentConfigClass(F3Screen.class).build();
|
||||||
@@ -1153,6 +1147,11 @@ public class Config
|
|||||||
.comment("Shows info about each thread pool.")
|
.comment("Shows info about each thread pool.")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
public static ConfigEntry<Boolean> showRenderThreadTasks = new ConfigEntry.Builder<Boolean>()
|
||||||
|
.set(false)
|
||||||
|
.comment("Shows info about the render thread tasks.")
|
||||||
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<Boolean> showCombinedObjectPools = new ConfigEntry.Builder<Boolean>()
|
public static ConfigEntry<Boolean> showCombinedObjectPools = new ConfigEntry.Builder<Boolean>()
|
||||||
.set(false)
|
.set(false)
|
||||||
.comment("Shows the combined memory use and array counts for all DH pooled objects.")
|
.comment("Shows the combined memory use and array counts for all DH pooled objects.")
|
||||||
@@ -1164,7 +1163,7 @@ public class Config
|
|||||||
|
|
||||||
public static ConfigEntry<Boolean> showQueuedChunkUpdateCount = new ConfigEntry.Builder<Boolean>()
|
public static ConfigEntry<Boolean> showQueuedChunkUpdateCount = new ConfigEntry.Builder<Boolean>()
|
||||||
.set(true)
|
.set(true)
|
||||||
.comment("Shows how many chunks are queud for processing and the max count that can be queued.")
|
.comment("Shows how many chunks are queued for processing and the max count that can be queued.")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<Boolean> showLevelStatus = new ConfigEntry.Builder<Boolean>()
|
public static ConfigEntry<Boolean> showLevelStatus = new ConfigEntry.Builder<Boolean>()
|
||||||
@@ -1242,9 +1241,11 @@ public class Config
|
|||||||
});
|
});
|
||||||
public static void onButtonPressed()
|
public static void onButtonPressed()
|
||||||
{
|
{
|
||||||
|
IMinecraftClientWrapper mcClient = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||||
|
|
||||||
LOGGER.info("Attempting to show tinyfd message box...");
|
LOGGER.info("Attempting to show tinyfd message box...");
|
||||||
boolean buttonPress = NativeDialogUtil.showDialog("Button pressed!", "UITester dialog", "ok", "info");
|
mcClient.showDialog("Button pressed!", "UITester dialog", "ok", "info");
|
||||||
LOGGER.info("dialog returned with ["+(buttonPress ? "TRUE" : "FALSE")+"]");
|
LOGGER.info("dialog closed");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ConfigCategory categoryTest = new ConfigCategory.Builder().set(CategoryTest.class).build();
|
public static ConfigCategory categoryTest = new ConfigCategory.Builder().set(CategoryTest.class).build();
|
||||||
|
|||||||
+84
@@ -0,0 +1,84 @@
|
|||||||
|
package com.seibel.distanthorizons.core.config.eventHandlers;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
|
||||||
|
import com.seibel.distanthorizons.core.util.TimerUtil;
|
||||||
|
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
|
public abstract class AbstractDelayedConfigEventHandler implements IConfigListener
|
||||||
|
{
|
||||||
|
public static final long DEFAULT_TIMEOUT_IN_MS = 2_000L;
|
||||||
|
|
||||||
|
/** how long to wait in milliseconds before applying the config changes */
|
||||||
|
private final long timeoutInMs;
|
||||||
|
private Timer timer;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=============//
|
||||||
|
// constructor //
|
||||||
|
//=============//
|
||||||
|
//region
|
||||||
|
|
||||||
|
public AbstractDelayedConfigEventHandler(long timeoutInMs) { this.timeoutInMs = timeoutInMs; }
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==================//
|
||||||
|
// abstract methods //
|
||||||
|
//==================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
public abstract void onConfigTimeout();
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//========//
|
||||||
|
// events //
|
||||||
|
//========//
|
||||||
|
//region
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigValueSet()
|
||||||
|
{
|
||||||
|
if (this.timeoutInMs > 0)
|
||||||
|
{
|
||||||
|
this.refreshRenderDataAfterTimeout();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.onConfigTimeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Calling this method multiple times will reset the timer */
|
||||||
|
private synchronized void refreshRenderDataAfterTimeout() // synchronized to prevent potential threading issues when adding/removing the timer
|
||||||
|
{
|
||||||
|
// stop the previous timer if one exists
|
||||||
|
if (this.timer != null)
|
||||||
|
{
|
||||||
|
this.timer.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new timer task
|
||||||
|
TimerTask timerTask = new TimerTask()
|
||||||
|
{
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
AbstractDelayedConfigEventHandler.this.onConfigTimeout();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.timer = TimerUtil.CreateTimer("AbstractDelayedConfigTimer");
|
||||||
|
this.timer.schedule(timerTask, this.timeoutInMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+11
-44
@@ -27,72 +27,36 @@ import com.seibel.distanthorizons.core.util.TimerUtil;
|
|||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
|
|
||||||
public class ReloadLodsConfigEventHandler implements IConfigListener
|
public class ReloadLodsConfigEventHandler extends AbstractDelayedConfigEventHandler
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* should be used for user facing UI options
|
* should be used for user facing UI options
|
||||||
* this allows the user a second to click through options before they're applied
|
* this allows the user a second to click through options before they're applied
|
||||||
*/
|
*/
|
||||||
public static ReloadLodsConfigEventHandler DELAYED_INSTANCE = new ReloadLodsConfigEventHandler(2_000L);
|
public static ReloadLodsConfigEventHandler DELAYED_INSTANCE = new ReloadLodsConfigEventHandler(AbstractDelayedConfigEventHandler.DEFAULT_TIMEOUT_IN_MS);
|
||||||
/** should be used for debug options so their change can be seen instantly */
|
/** should be used for debug options so their change can be seen instantly */
|
||||||
public static ReloadLodsConfigEventHandler INSTANT_INSTANCE = new ReloadLodsConfigEventHandler(0);
|
public static ReloadLodsConfigEventHandler INSTANT_INSTANCE = new ReloadLodsConfigEventHandler(0);
|
||||||
|
|
||||||
/** how long to wait in milliseconds before applying the config changes */
|
|
||||||
private final long timeoutInMs;
|
|
||||||
private Timer cacheClearingTimer;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=============//
|
//=============//
|
||||||
// constructor //
|
// constructor //
|
||||||
//=============//
|
//=============//
|
||||||
|
//region
|
||||||
|
|
||||||
public ReloadLodsConfigEventHandler(long timeoutInMs)
|
public ReloadLodsConfigEventHandler(long timeoutInMs) { super(timeoutInMs); }
|
||||||
{
|
|
||||||
this.timeoutInMs = timeoutInMs;
|
//endregion
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//========//
|
//========//
|
||||||
// events //
|
// events //
|
||||||
//========//
|
//========//
|
||||||
|
//region
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConfigValueSet()
|
public void onConfigTimeout()
|
||||||
{
|
|
||||||
if (this.timeoutInMs > 0)
|
|
||||||
{
|
|
||||||
this.refreshRenderDataAfterTimeout();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
clearRenderDataCache();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Calling this method multiple times will reset the timer */
|
|
||||||
private synchronized void refreshRenderDataAfterTimeout() // synchronized to prevent potential threading issues when adding/removing the timer
|
|
||||||
{
|
|
||||||
// stop the previous timer if one exists
|
|
||||||
if (this.cacheClearingTimer != null)
|
|
||||||
{
|
|
||||||
this.cacheClearingTimer.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a new timer task
|
|
||||||
TimerTask timerTask = new TimerTask()
|
|
||||||
{
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
clearRenderDataCache();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.cacheClearingTimer = TimerUtil.CreateTimer("RenderCacheClearConfigTimer");
|
|
||||||
this.cacheClearingTimer.schedule(timerTask, this.timeoutInMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void clearRenderDataCache()
|
|
||||||
{
|
{
|
||||||
IDhApiRenderProxy renderProxy = DhApi.Delayed.renderProxy;
|
IDhApiRenderProxy renderProxy = DhApi.Delayed.renderProxy;
|
||||||
if (renderProxy != null)
|
if (renderProxy != null)
|
||||||
@@ -101,5 +65,8 @@ public class ReloadLodsConfigEventHandler implements IConfigListener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+77
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the Distant Horizons mod
|
||||||
|
* licensed under the GNU LGPL v3 License.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 James Seibel
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.seibel.distanthorizons.core.config.eventHandlers;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.api.DhApi;
|
||||||
|
import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode;
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBeforeRenderEvent;
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiCancelableEventParam;
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
|
||||||
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
|
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
|
||||||
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
||||||
|
import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
||||||
|
|
||||||
|
import java.util.Timer;
|
||||||
|
|
||||||
|
public class RenderBlockCacheCsvHandler extends AbstractDelayedConfigEventHandler
|
||||||
|
{
|
||||||
|
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
||||||
|
|
||||||
|
public static RenderBlockCacheCsvHandler INSTANCE = new RenderBlockCacheCsvHandler();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=============//
|
||||||
|
// constructor //
|
||||||
|
//=============//
|
||||||
|
//region
|
||||||
|
|
||||||
|
/** private since we only ever need one handler at a time */
|
||||||
|
private RenderBlockCacheCsvHandler() { super(AbstractDelayedConfigEventHandler.DEFAULT_TIMEOUT_IN_MS); }
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=================//
|
||||||
|
// config handling //
|
||||||
|
//=================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigTimeout()
|
||||||
|
{
|
||||||
|
IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
|
||||||
|
if (wrapperFactory != null)
|
||||||
|
{
|
||||||
|
wrapperFactory.resetCachedIgnoredBlocksSets();
|
||||||
|
DhApi.Delayed.renderProxy.clearRenderDataCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+1
-1
@@ -78,7 +78,7 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
|
|||||||
this.put(EDhApiQualityPreset.HIGH, EDhApiTransparency.COMPLETE);
|
this.put(EDhApiQualityPreset.HIGH, EDhApiTransparency.COMPLETE);
|
||||||
this.put(EDhApiQualityPreset.EXTREME, EDhApiTransparency.COMPLETE);
|
this.put(EDhApiQualityPreset.EXTREME, EDhApiTransparency.COMPLETE);
|
||||||
}});
|
}});
|
||||||
private final ConfigPresetOptions<EDhApiQualityPreset, Boolean> ssaoEnabled = new ConfigPresetOptions<>(Config.Client.Advanced.Graphics.Ssao.enableSsao,
|
private final ConfigPresetOptions<EDhApiQualityPreset, Boolean> ssaoEnabled = new ConfigPresetOptions<>(Config.Client.Advanced.Graphics.enableSsao,
|
||||||
new HashMap<EDhApiQualityPreset, Boolean>()
|
new HashMap<EDhApiQualityPreset, Boolean>()
|
||||||
{{
|
{{
|
||||||
this.put(EDhApiQualityPreset.MINIMUM, false);
|
this.put(EDhApiQualityPreset.MINIMUM, false);
|
||||||
|
|||||||
+51
-51
@@ -59,9 +59,19 @@ public final class BufferQuad
|
|||||||
|
|
||||||
public boolean hasError = false;
|
public boolean hasError = false;
|
||||||
|
|
||||||
|
// Pre-computed sort keys to avoid recomputing on every comparison
|
||||||
|
// Slight increase in memory for reduction in cpu usage
|
||||||
|
public final long sortKeyEastWest;
|
||||||
|
public final long sortKeyNorthSouth;
|
||||||
|
|
||||||
|
|
||||||
BufferQuad(
|
|
||||||
|
//=============//
|
||||||
|
// constructor //
|
||||||
|
//=============//
|
||||||
|
//region
|
||||||
|
|
||||||
|
public BufferQuad(
|
||||||
short x, short y, short z, short widthEastWest, short widthNorthSouthOrHeight,
|
short x, short y, short z, short widthEastWest, short widthNorthSouthOrHeight,
|
||||||
int color, byte irisBlockMaterialId, byte skylight, byte blockLight,
|
int color, byte irisBlockMaterialId, byte skylight, byte blockLight,
|
||||||
EDhDirection direction)
|
EDhDirection direction)
|
||||||
@@ -85,64 +95,46 @@ public final class BufferQuad
|
|||||||
this.skyLight = skylight;
|
this.skyLight = skylight;
|
||||||
this.blockLight = blockLight;
|
this.blockLight = blockLight;
|
||||||
this.direction = direction;
|
this.direction = direction;
|
||||||
|
this.sortKeyEastWest = computeSortKey(direction, true);
|
||||||
|
this.sortKeyNorthSouth = computeSortKey(direction, false);
|
||||||
}
|
}
|
||||||
|
private long computeSortKey(EDhDirection dir, boolean eastWest)
|
||||||
|
|
||||||
|
|
||||||
/** a rough but fast calculation */
|
|
||||||
double calculateDistance(double relativeX, double relativeY, double relativeZ)
|
|
||||||
{
|
{
|
||||||
return Math.pow(relativeX - this.x, 2) + Math.pow(relativeY - this.y, 2) + Math.pow(relativeZ - this.z, 2);
|
if (eastWest)
|
||||||
|
{
|
||||||
|
switch (dir.axis)
|
||||||
|
{
|
||||||
|
case X: return (long) x << 48 | (long) y << 32 | (long) z << 16;
|
||||||
|
case Y: return (long) y << 48 | (long) z << 32 | (long) x << 16;
|
||||||
|
case Z: return (long) z << 48 | (long) y << 32 | (long) x << 16;
|
||||||
|
default: throw new IllegalArgumentException("Invalid Axis enum: [" + dir.axis + "].");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (dir.axis)
|
||||||
|
{
|
||||||
|
case X: return (long) x << 48 | (long) z << 32 | (long) y << 16;
|
||||||
|
case Y: return (long) y << 48 | (long) x << 32 | (long) z << 16;
|
||||||
|
case Z: return (long) z << 48 | (long) x << 32 | (long) y << 16;
|
||||||
|
default: throw new IllegalArgumentException("Invalid Axis enum: [" + dir.axis + "].");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** compares this quad's position to the given quad */
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** compares this quad's position to the given quad using pre-computed sort keys */
|
||||||
public int compare(BufferQuad quad, BufferMergeDirectionEnum compareDirection)
|
public int compare(BufferQuad quad, BufferMergeDirectionEnum compareDirection)
|
||||||
{
|
{
|
||||||
if (this.direction != quad.direction)
|
if (this.direction != quad.direction)
|
||||||
throw new IllegalArgumentException("The other quad is not in the same direction: " + quad.direction + " vs " + this.direction);
|
throw new IllegalArgumentException("The other quad is not in the same direction: " + quad.direction + " vs " + this.direction);
|
||||||
|
|
||||||
if (compareDirection == BufferMergeDirectionEnum.EastWest)
|
return compareDirection == BufferMergeDirectionEnum.EastWest
|
||||||
{
|
? Long.compare(this.sortKeyEastWest, quad.sortKeyEastWest)
|
||||||
switch (this.direction.axis)
|
: Long.compare(this.sortKeyNorthSouth, quad.sortKeyNorthSouth);
|
||||||
{
|
|
||||||
case X:
|
|
||||||
return threeDimensionalCompare(this.x, this.y, this.z, quad.x, quad.y, quad.z);
|
|
||||||
case Y:
|
|
||||||
return threeDimensionalCompare(this.y, this.z, this.x, quad.y, quad.z, quad.x);
|
|
||||||
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.axis + "].");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
switch (this.direction.axis)
|
|
||||||
{
|
|
||||||
case X:
|
|
||||||
return threeDimensionalCompare(this.x, this.z, this.y, quad.x, quad.z, quad.y);
|
|
||||||
case Y:
|
|
||||||
return threeDimensionalCompare(this.y, this.x, this.z, quad.y, quad.x, quad.z);
|
|
||||||
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.axis + "].");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Compares two 3D points A and B. <br>
|
|
||||||
* The X, Y, and Z coordinates can be passed into parameters 0, 1, and 2 in any order
|
|
||||||
* provided they are in the same order for both A and B. <br>
|
|
||||||
* With the 0th parameter being the most significant when comparing.
|
|
||||||
*/
|
|
||||||
private static int threeDimensionalCompare(short a0, short a1, short a2, short b0, short b1, short b2)
|
|
||||||
{
|
|
||||||
long a = (long) a0 << 48 | (long) a1 << 32 | (long) a2 << 16;
|
|
||||||
long b = (long) b0 << 48 | (long) b1 << 32 | (long) b2 << 16;
|
|
||||||
return Long.compare(a, b);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -154,11 +146,15 @@ public final class BufferQuad
|
|||||||
public boolean tryMerge(BufferQuad quad, BufferMergeDirectionEnum mergeDirection)
|
public boolean tryMerge(BufferQuad quad, BufferMergeDirectionEnum mergeDirection)
|
||||||
{
|
{
|
||||||
if (quad.hasError || this.hasError)
|
if (quad.hasError || this.hasError)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// only merge quads that are in the same direction
|
// only merge quads that are in the same direction
|
||||||
if (this.direction != quad.direction)
|
if (this.direction != quad.direction)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// make sure these quads share the same perpendicular axis
|
// make sure these quads share the same perpendicular axis
|
||||||
if ((mergeDirection == BufferMergeDirectionEnum.EastWest && this.y != quad.y)
|
if ((mergeDirection == BufferMergeDirectionEnum.EastWest && this.y != quad.y)
|
||||||
@@ -175,7 +171,6 @@ public final class BufferQuad
|
|||||||
short otherParallelCompareStartPos;
|
short otherParallelCompareStartPos;
|
||||||
switch (this.direction.axis)
|
switch (this.direction.axis)
|
||||||
{
|
{
|
||||||
default: // shouldn't normally happen, just here to make the compiler happy
|
|
||||||
case X:
|
case X:
|
||||||
if (mergeDirection == BufferMergeDirectionEnum.EastWest)
|
if (mergeDirection == BufferMergeDirectionEnum.EastWest)
|
||||||
{
|
{
|
||||||
@@ -232,6 +227,9 @@ public final class BufferQuad
|
|||||||
otherParallelCompareStartPos = quad.z;
|
otherParallelCompareStartPos = quad.z;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
default: // shouldn't normally happen, just here to make the compiler happy
|
||||||
|
throw new IllegalArgumentException("Unsupported axis: ["+this.direction.axis+"]");
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the width of this quad in the relevant axis
|
// get the width of this quad in the relevant axis
|
||||||
@@ -333,4 +331,6 @@ public final class BufferQuad
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-7
@@ -24,7 +24,7 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
|||||||
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
||||||
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
||||||
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListCheckout;
|
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListCheckout;
|
||||||
import com.seibel.distanthorizons.core.util.ColorUtil;
|
import com.seibel.distanthorizons.coreapi.util.ColorUtil;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||||
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
|
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
|
||||||
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnRenderView;
|
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnRenderView;
|
||||||
@@ -341,13 +341,24 @@ public class ColumnBox
|
|||||||
|
|
||||||
|
|
||||||
// Apply light to the range [adjMinY, adjMaxY)
|
// Apply light to the range [adjMinY, adjMaxY)
|
||||||
applyLightToRange(segments, newSegments, adjMinY, adjMaxY, lightToApply);
|
applyLightToRangeAndPopulateNewSgements(segments, newSegments, adjMinY, adjMaxY, lightToApply);
|
||||||
|
{
|
||||||
|
// swap references so we can use the newly populated segments
|
||||||
|
LongArrayList temp = segments;
|
||||||
|
segments = newSegments;
|
||||||
|
newSegments = temp;
|
||||||
|
}
|
||||||
|
|
||||||
// Fill overhang area [adjMaxY, adjAboveMinY) with adjSkyLight
|
// Fill overhang area [adjMaxY, adjAboveMinY) with adjSkyLight
|
||||||
short adjAboveMinY = RenderDataPointUtil.getYMin(adjAbovePoint);
|
short adjAboveMinY = RenderDataPointUtil.getYMin(adjAbovePoint);
|
||||||
if (adjMaxY < adjAboveMinY)
|
if (adjMaxY < adjAboveMinY)
|
||||||
{
|
{
|
||||||
applyLightToRange(segments, newSegments, adjMaxY, adjAboveMinY, adjSkyLight);
|
applyLightToRangeAndPopulateNewSgements(segments, newSegments, adjMaxY, adjAboveMinY, adjSkyLight);
|
||||||
|
{
|
||||||
|
LongArrayList temp = segments;
|
||||||
|
segments = newSegments;
|
||||||
|
newSegments = temp;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,10 +384,11 @@ public class ColumnBox
|
|||||||
/**
|
/**
|
||||||
* Apply the new light value over the given y range,
|
* Apply the new light value over the given y range,
|
||||||
* splitting segments as needed
|
* splitting segments as needed
|
||||||
|
* and putting the new segments into "newSegments"
|
||||||
* <p>
|
* <p>
|
||||||
* source: claude.ai
|
* source: claude.ai
|
||||||
*/
|
*/
|
||||||
private static void applyLightToRange(
|
private static void applyLightToRangeAndPopulateNewSgements(
|
||||||
LongArrayList segments, LongArrayList newSegments,
|
LongArrayList segments, LongArrayList newSegments,
|
||||||
short rangeStart, short rangeEnd,
|
short rangeStart, short rangeEnd,
|
||||||
byte newLight)
|
byte newLight)
|
||||||
@@ -419,9 +431,6 @@ public class ColumnBox
|
|||||||
newSegments.add(YSegmentUtil.encode(rangeEnd, endY, skyLight));
|
newSegments.add(YSegmentUtil.encode(rangeEnd, endY, skyLight));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
segments.clear();
|
|
||||||
segments.addAll(newSegments);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void tryAddVerticalFaceWithSkyLightToBuilder(
|
private static void tryAddVerticalFaceWithSkyLightToBuilder(
|
||||||
|
|||||||
+2
-22
@@ -31,7 +31,7 @@ import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListChec
|
|||||||
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListPool;
|
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListPool;
|
||||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
import com.seibel.distanthorizons.core.util.ColorUtil;
|
import com.seibel.distanthorizons.coreapi.util.ColorUtil;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||||
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
|
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
|
||||||
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnRenderView;
|
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnRenderView;
|
||||||
@@ -55,26 +55,6 @@ public class ColumnRenderBufferBuilder
|
|||||||
// vbo building //
|
// vbo building //
|
||||||
//==============//
|
//==============//
|
||||||
|
|
||||||
/** @link adjData should be null for adjacent sections that cross detail level boundaries */
|
|
||||||
public static CompletableFuture<LodBufferContainer> uploadBuffersAsync(
|
|
||||||
IDhClientLevel clientLevel,
|
|
||||||
long pos,
|
|
||||||
LodQuadBuilder quadBuilder
|
|
||||||
)
|
|
||||||
{
|
|
||||||
DhBlockPos minBlockPos = new DhBlockPos(DhSectionPos.getMinCornerBlockX(pos), clientLevel.getLevelWrapper().getMinHeight(), DhSectionPos.getMinCornerBlockZ(pos));
|
|
||||||
LodBufferContainer bufferContainer = new LodBufferContainer(pos, minBlockPos);
|
|
||||||
CompletableFuture<LodBufferContainer> uploadFuture = bufferContainer.makeAndUploadBuffersAsync(quadBuilder);
|
|
||||||
uploadFuture.whenComplete((uploadedBuffer, exception) ->
|
|
||||||
{
|
|
||||||
// clean up if not uploaded
|
|
||||||
if (uploadedBuffer != null && !uploadedBuffer.buffersUploaded)
|
|
||||||
{
|
|
||||||
uploadedBuffer.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return uploadFuture;
|
|
||||||
}
|
|
||||||
public static void makeLodRenderData(
|
public static void makeLodRenderData(
|
||||||
LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, IDhClientLevel clientLevel,
|
LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, IDhClientLevel clientLevel,
|
||||||
ColumnRenderSource[] adjRegions, boolean[] isSameDetailLevel)
|
ColumnRenderSource[] adjRegions, boolean[] isSameDetailLevel)
|
||||||
@@ -328,7 +308,7 @@ public class ColumnRenderBufferBuilder
|
|||||||
|
|
||||||
int color;
|
int color;
|
||||||
boolean fullBright = false;
|
boolean fullBright = false;
|
||||||
EDhApiDebugRendering debugging = Config.Client.Advanced.Debugging.debugRendering.get();
|
EDhApiDebugRendering debugging = Config.Client.Advanced.Debugging.debugRenderingColors.get();
|
||||||
switch (debugging)
|
switch (debugging)
|
||||||
{
|
{
|
||||||
case OFF:
|
case OFF:
|
||||||
|
|||||||
+92
@@ -0,0 +1,92 @@
|
|||||||
|
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
|
||||||
|
|
||||||
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
public class IndexBufferBuilder
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
//==========//
|
||||||
|
// building //
|
||||||
|
//==========//
|
||||||
|
//region
|
||||||
|
|
||||||
|
/** Buffer should be freed by {@link MemoryUtil#memFree} */
|
||||||
|
public static ByteBuffer createBuffer(int quadCount)
|
||||||
|
{
|
||||||
|
int indexCount = quadCount * 6; // 2 triangles per quad
|
||||||
|
ByteBuffer buffer = MemoryUtil.memAlloc(indexCount * Integer.BYTES);
|
||||||
|
buffer.order(ByteOrder.nativeOrder());
|
||||||
|
buildBufferInt(quadCount, buffer);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
private static void buildBufferByte(int quadCount, ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < quadCount; i++)
|
||||||
|
{
|
||||||
|
int vIndex = i * 4;
|
||||||
|
// First triangle
|
||||||
|
buffer.put((byte) (vIndex));
|
||||||
|
buffer.put((byte) (vIndex + 1));
|
||||||
|
buffer.put((byte) (vIndex + 2));
|
||||||
|
// Second triangle
|
||||||
|
buffer.put((byte) (vIndex + 2));
|
||||||
|
buffer.put((byte) (vIndex + 3));
|
||||||
|
buffer.put((byte) (vIndex));
|
||||||
|
}
|
||||||
|
if (buffer.hasRemaining())
|
||||||
|
{
|
||||||
|
throw new IllegalStateException("QuadElementBuffer is not full somehow after building");
|
||||||
|
}
|
||||||
|
buffer.rewind();
|
||||||
|
}
|
||||||
|
private static void buildBufferShort(int quadCount, ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < quadCount; i++)
|
||||||
|
{
|
||||||
|
int vIndex = i * 4;
|
||||||
|
// First triangle
|
||||||
|
buffer.putShort((short) (vIndex));
|
||||||
|
buffer.putShort((short) (vIndex + 1));
|
||||||
|
buffer.putShort((short) (vIndex + 2));
|
||||||
|
// Second triangle
|
||||||
|
buffer.putShort((short) (vIndex + 2));
|
||||||
|
buffer.putShort((short) (vIndex + 3));
|
||||||
|
buffer.putShort((short) (vIndex));
|
||||||
|
}
|
||||||
|
if (buffer.hasRemaining())
|
||||||
|
{
|
||||||
|
throw new IllegalStateException("QuadElementBuffer is not full somehow after building");
|
||||||
|
}
|
||||||
|
buffer.rewind();
|
||||||
|
}
|
||||||
|
private static void buildBufferInt(int quadCount, ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < quadCount; i++)
|
||||||
|
{
|
||||||
|
int vIndex = i * 4;
|
||||||
|
// First triangle
|
||||||
|
buffer.putInt(vIndex);
|
||||||
|
buffer.putInt(vIndex + 1);
|
||||||
|
buffer.putInt(vIndex + 2);
|
||||||
|
// Second triangle
|
||||||
|
buffer.putInt(vIndex + 2);
|
||||||
|
buffer.putInt(vIndex + 3);
|
||||||
|
buffer.putInt(vIndex);
|
||||||
|
}
|
||||||
|
if (buffer.hasRemaining())
|
||||||
|
{
|
||||||
|
throw new IllegalStateException("QuadElementBuffer is not full somehow after building");
|
||||||
|
}
|
||||||
|
buffer.rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+302
-111
@@ -20,22 +20,23 @@
|
|||||||
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
|
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
|
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
|
||||||
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
|
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
import com.seibel.distanthorizons.core.util.ExceptionUtil;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.render.AbstractDhRenderApiDefinition;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.ILodContainerUniformBufferWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.ILodContainerUniformBufferWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhTerrainRenderer;
|
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.IVertexBufferWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.IVertexBufferWrapper;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.lwjgl.system.MemoryUtil;
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Java representation of one or more OpenGL buffers for rendering.
|
* Java representation of one or more OpenGL buffers for rendering.
|
||||||
@@ -47,6 +48,7 @@ public class LodBufferContainer implements AutoCloseable
|
|||||||
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
||||||
|
|
||||||
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
|
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
|
||||||
|
private static final AbstractDhRenderApiDefinition RENDER_DEF = SingletonInjector.INSTANCE.get(AbstractDhRenderApiDefinition.class);
|
||||||
|
|
||||||
|
|
||||||
/** the position closest to minimum X/Z infinity and the level's lowest Y */
|
/** the position closest to minimum X/Z infinity and the level's lowest Y */
|
||||||
@@ -55,13 +57,11 @@ public class LodBufferContainer implements AutoCloseable
|
|||||||
|
|
||||||
public boolean buffersUploaded = false;
|
public boolean buffersUploaded = false;
|
||||||
|
|
||||||
public IVertexBufferWrapper[] vbos;
|
public IVertexBufferWrapper[] vboOpaqueWrappers;
|
||||||
public IVertexBufferWrapper[] vbosTransparent;
|
public IVertexBufferWrapper[] vboTransparentWrappers;
|
||||||
|
|
||||||
public ILodContainerUniformBufferWrapper uniformContainer = WRAPPER_FACTORY.createLodContainerUniformWrapper();
|
public ILodContainerUniformBufferWrapper uniformContainer = WRAPPER_FACTORY.createLodContainerUniformWrapper();
|
||||||
|
|
||||||
private final AtomicReference<CompletableFuture<LodBufferContainer>> uploadFutureRef = new AtomicReference<>(null);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//==============//
|
//==============//
|
||||||
@@ -69,12 +69,12 @@ public class LodBufferContainer implements AutoCloseable
|
|||||||
//==============//
|
//==============//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
public LodBufferContainer(long pos, DhBlockPos minCornerBlockPos)
|
private LodBufferContainer(long pos, DhBlockPos minCornerBlockPos)
|
||||||
{
|
{
|
||||||
this.pos = pos;
|
this.pos = pos;
|
||||||
this.minCornerBlockPos = minCornerBlockPos;
|
this.minCornerBlockPos = minCornerBlockPos;
|
||||||
this.vbos = new IVertexBufferWrapper[0];
|
this.vboOpaqueWrappers = new IVertexBufferWrapper[0];
|
||||||
this.vbosTransparent = new IVertexBufferWrapper[0];
|
this.vboTransparentWrappers = new IVertexBufferWrapper[0];
|
||||||
|
|
||||||
this.uniformContainer.createUniformData(this);
|
this.uniformContainer.createUniformData(this);
|
||||||
}
|
}
|
||||||
@@ -89,94 +89,182 @@ public class LodBufferContainer implements AutoCloseable
|
|||||||
//region
|
//region
|
||||||
|
|
||||||
/** Should be run on a DH thread. */
|
/** Should be run on a DH thread. */
|
||||||
public synchronized CompletableFuture<LodBufferContainer> makeAndUploadBuffersAsync(LodQuadBuilder builder)
|
public static CompletableFuture<LodBufferContainer> tryMakeAndUploadBuffersAsync(
|
||||||
|
long pos, IDhClientLevel clientLevel,
|
||||||
|
LodQuadBuilder builder)
|
||||||
{
|
{
|
||||||
// separate variable to prevent race condition when checking null
|
|
||||||
CompletableFuture<LodBufferContainer> oldFuture = this.uploadFutureRef.get();
|
|
||||||
if (oldFuture != null)
|
|
||||||
{
|
|
||||||
// upload already in process
|
|
||||||
return oldFuture;
|
|
||||||
}
|
|
||||||
|
|
||||||
// new upload needed
|
// new upload needed
|
||||||
CompletableFuture<LodBufferContainer> future = new CompletableFuture<>();
|
CompletableFuture<LodBufferContainer> future = new CompletableFuture<>();
|
||||||
future.handle((lodBufferContainer, throwable) ->
|
|
||||||
{
|
|
||||||
if (!this.uploadFutureRef.compareAndSet(future, null))
|
|
||||||
{
|
|
||||||
LOGGER.warn("upload future ref changed for pos ["+DhSectionPos.toString(this.pos)+"].");
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!this.uploadFutureRef.compareAndSet(null, future))
|
|
||||||
{
|
|
||||||
oldFuture = this.uploadFutureRef.get();
|
|
||||||
LodUtil.assertTrue(oldFuture != null, "Concurrency error");
|
|
||||||
return oldFuture;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// make the buffers
|
//================//
|
||||||
|
// create buffers //
|
||||||
|
//================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
DhBlockPos minCornerBlockPos = new DhBlockPos(
|
||||||
|
DhSectionPos.getMinCornerBlockX(pos),
|
||||||
|
clientLevel.getLevelWrapper().getMinHeight(),
|
||||||
|
DhSectionPos.getMinCornerBlockZ(pos));
|
||||||
|
LodBufferContainer bufferContainer = new LodBufferContainer(pos, minCornerBlockPos);
|
||||||
|
|
||||||
|
// create CPU vertex buffers
|
||||||
ArrayList<ByteBuffer> opaqueBuffers = builder.makeOpaqueVertexBuffers();
|
ArrayList<ByteBuffer> opaqueBuffers = builder.makeOpaqueVertexBuffers();
|
||||||
ArrayList<ByteBuffer> transparentBuffers = builder.makeTransparentVertexBuffers();
|
ArrayList<ByteBuffer> transparentBuffers = builder.makeTransparentVertexBuffers();
|
||||||
|
|
||||||
this.vbos = resizeBufferArray(this.vbos, opaqueBuffers.size());
|
// update arrays to contain buffers
|
||||||
this.vbosTransparent = resizeBufferArray(this.vbosTransparent, transparentBuffers.size());
|
bufferContainer.vboOpaqueWrappers = resizeWrapperArray(bufferContainer.vboOpaqueWrappers, opaqueBuffers.size());
|
||||||
|
bufferContainer.vboTransparentWrappers = resizeWrapperArray(bufferContainer.vboTransparentWrappers, transparentBuffers.size());
|
||||||
|
|
||||||
|
// create CPU index buffers if needed.
|
||||||
|
// Mac requires separate IBO objects for each VBO when using OpenGL,
|
||||||
|
// all other OS's can share a single IBO for quicker loading times
|
||||||
|
boolean useSingleIbo = RENDER_DEF.useSingleIbo();
|
||||||
|
@Nullable ArrayList<ByteBuffer> opaqueIndexBuffers = useSingleIbo ? null : bufferContainer.createIndexBuffers(opaqueBuffers);
|
||||||
|
@Nullable ArrayList<ByteBuffer> transparentIndexBuffers = useSingleIbo ? null : bufferContainer.createIndexBuffers(transparentBuffers);
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
// upload on MC's render thread
|
|
||||||
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread(() ->
|
//=============//
|
||||||
|
// create VBOs //
|
||||||
|
//=============//
|
||||||
|
//region
|
||||||
|
|
||||||
|
CompletableFuture<Void> createFuture = new CompletableFuture<Void>();
|
||||||
|
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer Setup", () ->
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// skip this event if requested
|
// skip this event if requested
|
||||||
if (Thread.interrupted()
|
if (Thread.interrupted()
|
||||||
|| future.isCancelled())
|
|| future.isCancelled())
|
||||||
{
|
{
|
||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// upload on the render thread
|
createBufferWrappers(bufferContainer.vboOpaqueWrappers, opaqueBuffers);
|
||||||
uploadBuffers(this.vbos, opaqueBuffers);
|
createBufferWrappers(bufferContainer.vboTransparentWrappers, transparentBuffers);
|
||||||
uploadBuffers(this.vbosTransparent, transparentBuffers);
|
|
||||||
this.buffersUploaded = true;
|
|
||||||
|
|
||||||
// success
|
createFuture.complete(null);
|
||||||
future.complete(this);
|
|
||||||
}
|
|
||||||
catch (InterruptedException ignore)
|
|
||||||
{
|
|
||||||
future.complete(this);
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
LOGGER.error("Unexpected issue uploading buffer ["+this.minCornerBlockPos +"], error: ["+e.getMessage()+"].", e);
|
if (!ExceptionUtil.isShutdownException(e))
|
||||||
|
|
||||||
future.completeExceptionally(e);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
// all the buffers must be manually freed to prevent memory leaks
|
|
||||||
|
|
||||||
for (ByteBuffer buffer : opaqueBuffers)
|
|
||||||
{
|
{
|
||||||
MemoryUtil.memFree(buffer);
|
LOGGER.error("Unexpected issue creating buffers for pos: ["+DhSectionPos.toString(bufferContainer.pos)+"], error: ["+e.getMessage()+"].", e);
|
||||||
}
|
|
||||||
|
|
||||||
for (ByteBuffer buffer : transparentBuffers)
|
|
||||||
{
|
|
||||||
MemoryUtil.memFree(buffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bufferContainer.close();
|
||||||
|
createFuture.completeExceptionally(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//====================//
|
||||||
|
// upload VBOs to GPU //
|
||||||
|
//====================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
createFuture.exceptionally((Throwable e) ->
|
||||||
|
{
|
||||||
|
// create VBOs failed //
|
||||||
|
if (!ExceptionUtil.isShutdownException(e))
|
||||||
|
{
|
||||||
|
LOGGER.error("Unexpected issue creating buffer [" + bufferContainer.minCornerBlockPos + "], error: [" + e.getMessage() + "].", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferContainer.close();
|
||||||
|
future.completeExceptionally(e);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
createFuture.thenRun(() ->
|
||||||
|
{
|
||||||
|
CompletableFuture<Void> opaqueFuture = uploadBuffersAsync(future, bufferContainer.vboOpaqueWrappers, opaqueBuffers, opaqueIndexBuffers);
|
||||||
|
CompletableFuture<Void> transparentFuture = uploadBuffersAsync(future, bufferContainer.vboTransparentWrappers, transparentBuffers, transparentIndexBuffers);
|
||||||
|
CompletableFuture<Void> uploadFuture = CompletableFuture.allOf(opaqueFuture, transparentFuture);
|
||||||
|
uploadFuture.exceptionally((Throwable e) ->
|
||||||
|
{
|
||||||
|
// upload failed //
|
||||||
|
if (!ExceptionUtil.isShutdownException(e))
|
||||||
|
{
|
||||||
|
LOGGER.error("Unexpected issue uploading buffer [" + bufferContainer.minCornerBlockPos + "], error: [" + e.getMessage() + "].", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferContainer.close();
|
||||||
|
future.completeExceptionally(e);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
uploadFuture.thenRun(() ->
|
||||||
|
{
|
||||||
|
// upload success //
|
||||||
|
bufferContainer.buffersUploaded = true;
|
||||||
|
future.complete(bufferContainer);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//====================//
|
||||||
|
// CPU Buffer cleanup //
|
||||||
|
//====================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
future.whenComplete((LodBufferContainer lodBufferContainer, Throwable throwable) ->
|
||||||
|
{
|
||||||
|
// all the buffers must be manually freed to prevent memory leaks
|
||||||
|
|
||||||
|
tryFreeByteBufferList(opaqueBuffers);
|
||||||
|
tryFreeByteBufferList(transparentBuffers);
|
||||||
|
|
||||||
|
tryFreeByteBufferList(opaqueIndexBuffers);
|
||||||
|
tryFreeByteBufferList(transparentIndexBuffers);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
private static IVertexBufferWrapper[] resizeBufferArray(IVertexBufferWrapper[] vbos, int newSize)
|
private static void tryFreeByteBufferList(@Nullable ArrayList<ByteBuffer> list)
|
||||||
|
{
|
||||||
|
if (list != null)
|
||||||
|
{
|
||||||
|
for (ByteBuffer buffer : list)
|
||||||
|
{
|
||||||
|
MemoryUtil.memFree(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private ArrayList<ByteBuffer> createIndexBuffers(ArrayList<ByteBuffer> vertexBuffers)
|
||||||
|
{
|
||||||
|
ArrayList<ByteBuffer> indexBuffers = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < vertexBuffers.size(); i++)
|
||||||
|
{
|
||||||
|
ByteBuffer buffer = vertexBuffers.get(i);
|
||||||
|
int size = buffer.limit() - buffer.position();
|
||||||
|
int maxVertexCount = size / LodQuadBuilder.BYTES_PER_VERTEX;
|
||||||
|
int quadCount = (maxVertexCount / 4);
|
||||||
|
ByteBuffer indexBuffer = IndexBufferBuilder.createBuffer(quadCount);
|
||||||
|
indexBuffers.add(indexBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return indexBuffers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IVertexBufferWrapper[] resizeWrapperArray(IVertexBufferWrapper[] vbos, int newSize)
|
||||||
{
|
{
|
||||||
if (vbos.length == newSize)
|
if (vbos.length == newSize)
|
||||||
{
|
{
|
||||||
@@ -197,46 +285,141 @@ public class LodBufferContainer implements AutoCloseable
|
|||||||
}
|
}
|
||||||
return newVbos;
|
return newVbos;
|
||||||
}
|
}
|
||||||
private static void uploadBuffers(IVertexBufferWrapper[] vbos, ArrayList<ByteBuffer> byteBuffers) throws InterruptedException
|
|
||||||
|
private static void createBufferWrappers(IVertexBufferWrapper[] vboWrappers, ArrayList<ByteBuffer> vertexBuffers)
|
||||||
{
|
{
|
||||||
int vboIndex = 0;
|
for (int i = 0; i < vertexBuffers.size(); i++)
|
||||||
for (int i = 0; i < byteBuffers.size(); i++)
|
|
||||||
{
|
{
|
||||||
if (vboIndex >= vbos.length)
|
if (i >= vboWrappers.length)
|
||||||
|
{
|
||||||
|
throw new RuntimeException("Too many vertex buffers!!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vboWrappers[i] == null)
|
||||||
|
{
|
||||||
|
vboWrappers[i] = WRAPPER_FACTORY.createVboWrapper("distantHorizons:McLodRenderer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Index buffers should be null if {@link AbstractDhRenderApiDefinition#useSingleIbo()} returns true. */
|
||||||
|
private static CompletableFuture<Void> uploadBuffersAsync(
|
||||||
|
CompletableFuture<LodBufferContainer> parentFuture,
|
||||||
|
IVertexBufferWrapper[] vboWrappers,
|
||||||
|
ArrayList<ByteBuffer> vertexBuffers, @Nullable ArrayList<ByteBuffer> indexBuffers
|
||||||
|
)
|
||||||
|
{
|
||||||
|
ArrayList<CompletableFuture<Void>> uploadFutureList = new ArrayList<>();
|
||||||
|
int vboIndex = 0;
|
||||||
|
for (int i = 0; i < vertexBuffers.size(); i++)
|
||||||
|
{
|
||||||
|
if (vboIndex >= vboWrappers.length)
|
||||||
{
|
{
|
||||||
throw new RuntimeException("Too many vertex buffers!!");
|
throw new RuntimeException("Too many vertex buffers!!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// get or create the VBO
|
|
||||||
if (vbos[vboIndex] == null)
|
|
||||||
{
|
|
||||||
vbos[vboIndex] = SingletonInjector.INSTANCE.get(IWrapperFactory.class).createVboWrapper("distantHorizons:McLodRenderer");
|
|
||||||
}
|
|
||||||
IVertexBufferWrapper vbo = vbos[vboIndex];
|
|
||||||
|
|
||||||
ByteBuffer buffer = byteBuffers.get(i);
|
// final variables for use in lambdas //
|
||||||
int size = buffer.limit() - buffer.position();
|
|
||||||
int vertexCount = size / LodQuadBuilder.BYTES_PER_VERTEX;
|
|
||||||
|
|
||||||
try
|
final IVertexBufferWrapper finalVboWrapper = vboWrappers[vboIndex];
|
||||||
|
|
||||||
|
final ByteBuffer finalVertexBuffer = vertexBuffers.get(vboIndex);
|
||||||
|
// index buffers are optional
|
||||||
|
@Nullable final ByteBuffer finalIndexBuffer = (indexBuffers != null) ? indexBuffers.get(vboIndex) : null;
|
||||||
|
|
||||||
|
final int finalVertexCount = vertexByteBufferToVertexCount(finalVertexBuffer);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//===============//
|
||||||
|
// vertex upload //
|
||||||
|
//===============//
|
||||||
|
//region
|
||||||
|
|
||||||
|
CompletableFuture<Void> vertexUploadFuture = new CompletableFuture<>();
|
||||||
|
uploadFutureList.add(vertexUploadFuture);
|
||||||
|
|
||||||
|
|
||||||
|
final StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
|
||||||
|
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer VBO Upload", () ->
|
||||||
{
|
{
|
||||||
vbo.upload(buffer, vertexCount);
|
try
|
||||||
}
|
{
|
||||||
catch (Exception e)
|
// skip this event if requested
|
||||||
|
if (Thread.interrupted()
|
||||||
|
|| parentFuture.isCancelled())
|
||||||
|
{
|
||||||
|
throw new InterruptedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
finalVboWrapper.uploadVertexBuffer(finalVertexBuffer, finalVertexCount);
|
||||||
|
vertexUploadFuture.complete(null);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LOGGER.error("Failed to upload buffer. Error: [" + e.getMessage() + "].", e);
|
||||||
|
vertexUploadFuture.completeExceptionally(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==============//
|
||||||
|
// index upload //
|
||||||
|
//==============//
|
||||||
|
//region
|
||||||
|
|
||||||
|
if (finalIndexBuffer != null)
|
||||||
{
|
{
|
||||||
vbos[vboIndex] = null;
|
CompletableFuture<Void> indexUploadFuture = new CompletableFuture<>();
|
||||||
vbo.close();
|
uploadFutureList.add(indexUploadFuture);
|
||||||
LOGGER.error("Failed to upload buffer. Error: ["+e.getMessage()+"].", e);
|
|
||||||
|
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer IBO Upload", () ->
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// skip this event if requested
|
||||||
|
if (Thread.interrupted()
|
||||||
|
|| parentFuture.isCancelled())
|
||||||
|
{
|
||||||
|
throw new InterruptedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
finalVboWrapper.uploadIndexBuffer(finalIndexBuffer, finalVertexCount);
|
||||||
|
indexUploadFuture.complete(null);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
finalVboWrapper.close();
|
||||||
|
indexUploadFuture.completeExceptionally(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
vboIndex++;
|
vboIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vboIndex < vbos.length)
|
if (vboIndex < vboWrappers.length)
|
||||||
{
|
{
|
||||||
throw new RuntimeException("Too few vertex buffers!!");
|
throw new RuntimeException("Too few vertex buffers!!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// merge futures //
|
||||||
|
|
||||||
|
CompletableFuture<?>[] futureArray = new CompletableFuture[uploadFutureList.size()];
|
||||||
|
for (int i = 0; i < uploadFutureList.size(); i++)
|
||||||
|
{
|
||||||
|
futureArray[i] = uploadFutureList.get(i);
|
||||||
|
}
|
||||||
|
return CompletableFuture.allOf(futureArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
@@ -248,29 +431,34 @@ public class LodBufferContainer implements AutoCloseable
|
|||||||
//================//
|
//================//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
|
private static int vertexByteBufferToVertexCount(ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
int size = buffer.limit() - buffer.position();
|
||||||
|
int vertexCount = size / LodQuadBuilder.BYTES_PER_VERTEX;
|
||||||
|
return vertexCount;
|
||||||
|
}
|
||||||
|
|
||||||
/** can be used when debugging */
|
/** can be used when debugging */
|
||||||
public boolean hasNonNullVbos() { return this.vbos != null || this.vbosTransparent != null; }
|
public boolean hasNonNullVbos() { return this.vboOpaqueWrappers != null || this.vboTransparentWrappers != null; }
|
||||||
|
|
||||||
/** can be used when debugging */
|
/** can be used when debugging */
|
||||||
public int vboBufferCount()
|
public int vboBufferCount()
|
||||||
{
|
{
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
if (this.vbos != null)
|
if (this.vboOpaqueWrappers != null)
|
||||||
{
|
{
|
||||||
count += this.vbos.length;
|
count += this.vboOpaqueWrappers.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.vbosTransparent != null)
|
if (this.vboTransparentWrappers != null)
|
||||||
{
|
{
|
||||||
count += this.vbosTransparent.length;
|
count += this.vboTransparentWrappers.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean uploadInProgress() { return this.uploadFutureRef.get() != null; }
|
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
@@ -291,28 +479,31 @@ public class LodBufferContainer implements AutoCloseable
|
|||||||
{
|
{
|
||||||
this.buffersUploaded = false;
|
this.buffersUploaded = false;
|
||||||
|
|
||||||
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread(() ->
|
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer Close", () ->
|
||||||
{
|
{
|
||||||
for (IVertexBufferWrapper buffer : this.vbos)
|
tryCloseBufferWrapperArray(this.vboOpaqueWrappers);
|
||||||
{
|
tryCloseBufferWrapperArray(this.vboTransparentWrappers);
|
||||||
if (buffer != null)
|
|
||||||
{
|
|
||||||
buffer.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (IVertexBufferWrapper buffer : this.vbosTransparent)
|
|
||||||
{
|
|
||||||
if (buffer != null)
|
|
||||||
{
|
|
||||||
buffer.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.uniformContainer.close();
|
this.uniformContainer.close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void tryCloseBufferWrapperArray(@Nullable IVertexBufferWrapper[] bufferWrappers)
|
||||||
|
{
|
||||||
|
if (bufferWrappers != null)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < bufferWrappers.length; i++)
|
||||||
|
{
|
||||||
|
IVertexBufferWrapper buffer = bufferWrappers[i];
|
||||||
|
bufferWrappers[i] = null;
|
||||||
|
if (buffer != null)
|
||||||
|
{
|
||||||
|
buffer.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+19
-16
@@ -30,9 +30,8 @@ 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.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.util.ColorUtil;
|
import com.seibel.distanthorizons.coreapi.util.ColorUtil;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhTerrainRenderer;
|
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||||
import org.lwjgl.system.MemoryUtil;
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
@@ -57,11 +56,12 @@ public class LodQuadBuilder
|
|||||||
private final EDhApiDebugRendering debugRenderingMode;
|
private final EDhApiDebugRendering debugRenderingMode;
|
||||||
private final EDhApiGrassSideRendering grassSideRenderingMode;
|
private final EDhApiGrassSideRendering grassSideRenderingMode;
|
||||||
|
|
||||||
/** the number of bytes for */
|
/** the number of bytes for a single vertex */
|
||||||
public static final int BYTES_PER_VERTEX = 14;
|
public static final int BYTES_PER_VERTEX = 16;
|
||||||
|
public static final int BYTES_PER_QUAD = BYTES_PER_VERTEX * 4;
|
||||||
|
|
||||||
public static final int[][][] DIRECTION_VERTEX_IBO_QUAD = new int[][][]
|
public static final int[][][] DIRECTION_VERTEX_IBO_QUAD = new int[][][]
|
||||||
///region
|
//region
|
||||||
{
|
{
|
||||||
// X,Z //
|
// X,Z //
|
||||||
{ // UP
|
{ // UP
|
||||||
@@ -109,7 +109,7 @@ public class LodQuadBuilder
|
|||||||
{0, 0}, // 3
|
{0, 0}, // 3
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
///endregion
|
//endregion
|
||||||
|
|
||||||
private int premergeCount = 0;
|
private int premergeCount = 0;
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ public class LodQuadBuilder
|
|||||||
|
|
||||||
this.clientLevelWrapper = clientLevelWrapper;
|
this.clientLevelWrapper = clientLevelWrapper;
|
||||||
|
|
||||||
this.debugRenderingMode = Config.Client.Advanced.Debugging.debugRendering.get();
|
this.debugRenderingMode = Config.Client.Advanced.Debugging.debugRenderingColors.get();
|
||||||
this.grassSideRenderingMode = Config.Client.Advanced.Graphics.Quality.grassSideRendering.get();
|
this.grassSideRenderingMode = Config.Client.Advanced.Graphics.Quality.grassSideRendering.get();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -308,7 +308,8 @@ public class LodQuadBuilder
|
|||||||
{
|
{
|
||||||
// if this is the first iteration or the buffer is full,
|
// if this is the first iteration or the buffer is full,
|
||||||
// create a new buffer
|
// create a new buffer
|
||||||
if (buffer == null || !buffer.hasRemaining())
|
if (buffer == null
|
||||||
|
|| buffer.remaining() < BYTES_PER_QUAD)
|
||||||
{
|
{
|
||||||
buffer = MemoryUtil.memAlloc(getMaxBufferByteSize());
|
buffer = MemoryUtil.memAlloc(getMaxBufferByteSize());
|
||||||
byteBufferList.add(buffer);
|
byteBufferList.add(buffer);
|
||||||
@@ -498,16 +499,18 @@ public class LodQuadBuilder
|
|||||||
return maxBufferByteSize;
|
return maxBufferByteSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
// number of bytes a single quad takes
|
// 2 MB
|
||||||
int QUADS_BYTE_SIZE = BYTES_PER_VERTEX * 4;
|
// note: this is relatively small (10 MB was the previous max) to reduce stuttering
|
||||||
// how big a single VBO can be in bytes
|
// during the upload process by having smaller upload steps
|
||||||
int MAX_VBO_BYTE_SIZE = 10 * 1024 * 1024; // 10 MB
|
int maxVboByteSize = 2 * 1024 * 1024;
|
||||||
int MAX_QUADS_PER_BUFFER = MAX_VBO_BYTE_SIZE / QUADS_BYTE_SIZE;
|
|
||||||
int FULL_SIZED_BUFFER = MAX_QUADS_PER_BUFFER * QUADS_BYTE_SIZE;
|
|
||||||
|
|
||||||
maxBufferByteSize = FULL_SIZED_BUFFER;
|
int maxQuadsPerBuffer = maxVboByteSize / BYTES_PER_QUAD;
|
||||||
|
// integer truncation to remove decimal component
|
||||||
|
int fullSizedBuffer = maxQuadsPerBuffer * BYTES_PER_QUAD;
|
||||||
|
|
||||||
return FULL_SIZED_BUFFER;
|
maxBufferByteSize = fullSizedBuffer;
|
||||||
|
|
||||||
|
return fullSizedBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
///endregion
|
///endregion
|
||||||
|
|||||||
+61
-18
@@ -37,6 +37,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrappe
|
|||||||
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.coreapi.util.BitShiftUtil;
|
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
|
||||||
|
import com.seibel.distanthorizons.coreapi.util.ColorUtil;
|
||||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
@@ -128,6 +129,7 @@ public class FullDataToRenderDataTransformer
|
|||||||
ColumnRenderView tempExpandingColumnView = ColumnRenderView.getPooled();
|
ColumnRenderView tempExpandingColumnView = ColumnRenderView.getPooled();
|
||||||
RenderDataPointReducingList reducingList = new RenderDataPointReducingList())
|
RenderDataPointReducingList reducingList = new RenderDataPointReducingList())
|
||||||
{
|
{
|
||||||
|
DhBlockPosMutable mutableBlockPos = new DhBlockPosMutable();
|
||||||
for (int x = 0; x < FullDataSourceV2.WIDTH; x++)
|
for (int x = 0; x < FullDataSourceV2.WIDTH; x++)
|
||||||
{
|
{
|
||||||
for (int z = 0; z < FullDataSourceV2.WIDTH; z++)
|
for (int z = 0; z < FullDataSourceV2.WIDTH; z++)
|
||||||
@@ -141,7 +143,7 @@ public class FullDataToRenderDataTransformer
|
|||||||
baseX + BitShiftUtil.pow(x, dataDetail), baseZ + BitShiftUtil.pow(z, dataDetail),
|
baseX + BitShiftUtil.pow(x, dataDetail), baseZ + BitShiftUtil.pow(z, dataDetail),
|
||||||
columnArrayView, dataColumn,
|
columnArrayView, dataColumn,
|
||||||
// pooled references so we don't need to re-allocate/get them 4000 times per render source
|
// pooled references so we don't need to re-allocate/get them 4000 times per render source
|
||||||
phantomCheckout, tempExpandingColumnView, reducingList);
|
phantomCheckout, tempExpandingColumnView, reducingList, mutableBlockPos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,7 +158,7 @@ public class FullDataToRenderDataTransformer
|
|||||||
ColumnRenderView columnArrayView,
|
ColumnRenderView columnArrayView,
|
||||||
LongArrayList fullDataColumn,
|
LongArrayList fullDataColumn,
|
||||||
// pooled references
|
// pooled references
|
||||||
PhantomArrayListCheckout phantomCheckout, ColumnRenderView tempExpandingColumnView, RenderDataPointReducingList reducingList)
|
PhantomArrayListCheckout phantomCheckout, ColumnRenderView tempExpandingColumnView, RenderDataPointReducingList reducingList, DhBlockPosMutable mutableBlockPos)
|
||||||
{
|
{
|
||||||
// we can't do anything if the full data is missing or empty
|
// we can't do anything if the full data is missing or empty
|
||||||
if (fullDataColumn == null
|
if (fullDataColumn == null
|
||||||
@@ -169,7 +171,7 @@ public class FullDataToRenderDataTransformer
|
|||||||
if (fullDataLength <= columnArrayView.maxVerticalSliceCount)
|
if (fullDataLength <= columnArrayView.maxVerticalSliceCount)
|
||||||
{
|
{
|
||||||
// Directly use the arrayView since it fits.
|
// Directly use the arrayView since it fits.
|
||||||
setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, columnArrayView, fullDataColumn);
|
setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, columnArrayView, fullDataColumn, mutableBlockPos);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -177,7 +179,7 @@ public class FullDataToRenderDataTransformer
|
|||||||
|
|
||||||
// expand the ColumnArrayView to fit the new larger max vertical size
|
// expand the ColumnArrayView to fit the new larger max vertical size
|
||||||
tempExpandingColumnView.populate(dataArrayList, fullDataLength, 0, fullDataLength);
|
tempExpandingColumnView.populate(dataArrayList, fullDataLength, 0, fullDataLength);
|
||||||
setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, tempExpandingColumnView, fullDataColumn);
|
setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, tempExpandingColumnView, fullDataColumn, mutableBlockPos);
|
||||||
|
|
||||||
columnArrayView.changeVerticalSizeFrom(tempExpandingColumnView, reducingList);
|
columnArrayView.changeVerticalSizeFrom(tempExpandingColumnView, reducingList);
|
||||||
}
|
}
|
||||||
@@ -185,7 +187,7 @@ public class FullDataToRenderDataTransformer
|
|||||||
private static void setRenderColumnView(
|
private static void setRenderColumnView(
|
||||||
IClientLevelWrapper levelWrapper, FullDataSourceV2 fullDataSource,
|
IClientLevelWrapper levelWrapper, FullDataSourceV2 fullDataSource,
|
||||||
int blockX, int blockZ,
|
int blockX, int blockZ,
|
||||||
ColumnRenderView renderColumnData, LongArrayList fullColumnData)
|
ColumnRenderView renderColumnData, LongArrayList fullColumnData, DhBlockPosMutable mutableBlockPos)
|
||||||
{
|
{
|
||||||
//===============//
|
//===============//
|
||||||
// config values //
|
// config values //
|
||||||
@@ -194,8 +196,12 @@ public class FullDataToRenderDataTransformer
|
|||||||
boolean ignoreNonCollidingBlocks = (Config.Client.Advanced.Graphics.Quality.blocksToIgnore.get() == EDhApiBlocksToAvoid.NON_COLLIDING);
|
boolean ignoreNonCollidingBlocks = (Config.Client.Advanced.Graphics.Quality.blocksToIgnore.get() == EDhApiBlocksToAvoid.NON_COLLIDING);
|
||||||
boolean colorBelowWithAvoidedBlocks = Config.Client.Advanced.Graphics.Quality.tintWithAvoidedBlocks.get();
|
boolean colorBelowWithAvoidedBlocks = Config.Client.Advanced.Graphics.Quality.tintWithAvoidedBlocks.get();
|
||||||
|
|
||||||
ObjectOpenHashSet<IBlockStateWrapper> blockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredBlocks(levelWrapper);
|
final ObjectOpenHashSet<IBlockStateWrapper> blockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredBlocks(levelWrapper);
|
||||||
ObjectOpenHashSet<IBlockStateWrapper> caveBlockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredCaveBlocks(levelWrapper);
|
final ObjectOpenHashSet<IBlockStateWrapper> caveBlockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredCaveBlocks(levelWrapper);
|
||||||
|
final ObjectOpenHashSet<IBlockStateWrapper> waterSubsurfaceReplacementBlocks = WRAPPER_FACTORY.getWaterSubsurfaceReplacementBlocks(levelWrapper);
|
||||||
|
final ObjectOpenHashSet<IBlockStateWrapper> waterSurfaceReplacementBlocks = WRAPPER_FACTORY.getWaterSurfaceReplacementBlocks(levelWrapper);
|
||||||
|
final IBlockStateWrapper water = WRAPPER_FACTORY.getWaterBlockStateWrapper(levelWrapper);
|
||||||
|
|
||||||
|
|
||||||
// build snow block cache if needed
|
// build snow block cache if needed
|
||||||
if (snowLayerBlockStates == null)
|
if (snowLayerBlockStates == null)
|
||||||
@@ -223,6 +229,7 @@ public class FullDataToRenderDataTransformer
|
|||||||
int colorToApplyToNextBlock = -1;
|
int colorToApplyToNextBlock = -1;
|
||||||
int lastColor = 0;
|
int lastColor = 0;
|
||||||
int lastBottom = -10_000;
|
int lastBottom = -10_000;
|
||||||
|
IBlockStateWrapper lastBlock = null;
|
||||||
|
|
||||||
int skylightToApplyToNextBlock = -1;
|
int skylightToApplyToNextBlock = -1;
|
||||||
int blocklightToApplyToNextBlock = -1;
|
int blocklightToApplyToNextBlock = -1;
|
||||||
@@ -236,7 +243,8 @@ public class FullDataToRenderDataTransformer
|
|||||||
|
|
||||||
FullDataPointIdMap fullDataMapping = fullDataSource.mapping;
|
FullDataPointIdMap fullDataMapping = fullDataSource.mapping;
|
||||||
|
|
||||||
DhBlockPosMutable mutableBlockPos = new DhBlockPosMutable(blockX, 0, blockZ);
|
mutableBlockPos.setX(blockX);
|
||||||
|
mutableBlockPos.setZ(blockZ);
|
||||||
|
|
||||||
// goes from the top down
|
// goes from the top down
|
||||||
for (int fullDataIndex = 0; fullDataIndex < fullColumnData.size(); fullDataIndex++)
|
for (int fullDataIndex = 0; fullDataIndex < fullColumnData.size(); fullDataIndex++)
|
||||||
@@ -283,6 +291,12 @@ public class FullDataToRenderDataTransformer
|
|||||||
// cave culling check //
|
// cave culling check //
|
||||||
//====================//
|
//====================//
|
||||||
|
|
||||||
|
if (waterSubsurfaceReplacementBlocks.contains(block)
|
||||||
|
&& (lastBlock == null || lastBlock.isAir()))
|
||||||
|
{
|
||||||
|
block = water;
|
||||||
|
}
|
||||||
|
|
||||||
boolean ignoreBlock = blockStatesToIgnore.contains(block);
|
boolean ignoreBlock = blockStatesToIgnore.contains(block);
|
||||||
boolean caveBlock = caveBlockStatesToIgnore.contains(block);
|
boolean caveBlock = caveBlockStatesToIgnore.contains(block);
|
||||||
if (caveBlock
|
if (caveBlock
|
||||||
@@ -328,6 +342,9 @@ public class FullDataToRenderDataTransformer
|
|||||||
else if (ignoreBlock)
|
else if (ignoreBlock)
|
||||||
{
|
{
|
||||||
// this is an ignored block, but shouldn't be merged like a cave block
|
// this is an ignored block, but shouldn't be merged like a cave block
|
||||||
|
|
||||||
|
// applying this sky light to the next block should prevent black spots for opaque covering blocks
|
||||||
|
skylightToApplyToNextBlock = skyLight;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,21 +360,37 @@ public class FullDataToRenderDataTransformer
|
|||||||
&& !block.isLiquid()
|
&& !block.isLiquid()
|
||||||
&& block.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE;
|
&& block.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE;
|
||||||
|
|
||||||
// merge snow into the block below it
|
// handle height reduction
|
||||||
if (snowLayerBlockStates.contains(block))
|
boolean isSnowLayer = snowLayerBlockStates.contains(block);
|
||||||
|
boolean isWaterSurfaceReplacement = waterSurfaceReplacementBlocks.contains(block);
|
||||||
|
if (isSnowLayer || isWaterSurfaceReplacement)
|
||||||
{
|
{
|
||||||
// sometimes a snow datapoint will be multiple blocks tall,
|
if (isWaterSurfaceReplacement)
|
||||||
|
{
|
||||||
|
// replace the block with water
|
||||||
|
block = WRAPPER_FACTORY.getWaterBlockStateWrapper(levelWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
// sometimes a datapoint will be multiple blocks tall,
|
||||||
// in that case we just want to drop the top by 1
|
// in that case we just want to drop the top by 1
|
||||||
blockHeight -= 1;
|
blockHeight -= 1;
|
||||||
if (blockHeight == 0)
|
if (blockHeight == 0)
|
||||||
{
|
{
|
||||||
// this snow block was entirely removed, just color the block below it
|
// this block was entirely removed, just color the block below it
|
||||||
ignoreNonSolidBlock = true;
|
ignoreNonSolidBlock = true;
|
||||||
|
|
||||||
// snow is a special case where it should always tint the block
|
|
||||||
// below it, if not done grass will appear as gray
|
if (isSnowLayer)
|
||||||
int snowColor = levelWrapper.getBlockColor(mutableBlockPos, biome, fullDataSource, block);
|
{
|
||||||
colorToApplyToNextBlock = ColorUtil.setAlpha(snowColor, 255);
|
// snow is a special case where it should always tint the block
|
||||||
|
// below it, if not done grass will appear as gray
|
||||||
|
int snowColor = levelWrapper.getBlockColor(mutableBlockPos, biome, fullDataSource, block);
|
||||||
|
colorToApplyToNextBlock = ColorUtil.setAlpha(snowColor, 255);
|
||||||
|
}
|
||||||
|
else //if (isWaterSurfaceReplacement)
|
||||||
|
{
|
||||||
|
colorToApplyToNextBlock = levelWrapper.getBlockColor(mutableBlockPos, biome, fullDataSource, block);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,8 +452,17 @@ public class FullDataToRenderDataTransformer
|
|||||||
// use the previous block's color
|
// use the previous block's color
|
||||||
color = colorToApplyToNextBlock;
|
color = colorToApplyToNextBlock;
|
||||||
colorToApplyToNextBlock = -1;
|
colorToApplyToNextBlock = -1;
|
||||||
skyLight = skylightToApplyToNextBlock;
|
|
||||||
blockLight = blocklightToApplyToNextBlock;
|
// use the skylight override if present
|
||||||
|
if (skylightToApplyToNextBlock != -1)
|
||||||
|
{
|
||||||
|
skyLight = skylightToApplyToNextBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blocklightToApplyToNextBlock != -1)
|
||||||
|
{
|
||||||
|
blockLight = blocklightToApplyToNextBlock;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -449,6 +491,7 @@ public class FullDataToRenderDataTransformer
|
|||||||
}
|
}
|
||||||
lastBottom = bottomY;
|
lastBottom = bottomY;
|
||||||
lastColor = color;
|
lastColor = color;
|
||||||
|
lastBlock = block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProvid
|
|||||||
import com.seibel.distanthorizons.core.util.delayedSaveCache.DelayedDataSourceSaveCache;
|
import com.seibel.distanthorizons.core.util.delayedSaveCache.DelayedDataSourceSaveCache;
|
||||||
import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
|
import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
|
||||||
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
|
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
|
||||||
import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue;
|
import com.seibel.distanthorizons.core.generation.queues.IFullDataSourceRetrievalQueue;
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
|
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
|
||||||
import com.seibel.distanthorizons.core.level.IDhLevel;
|
import com.seibel.distanthorizons.core.level.IDhLevel;
|
||||||
|
|||||||
+2
-2
@@ -22,10 +22,10 @@ package com.seibel.distanthorizons.core.file.fullDatafile;
|
|||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||||
import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
|
import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
|
||||||
import com.seibel.distanthorizons.core.generation.RemoteWorldRetrievalQueue;
|
import com.seibel.distanthorizons.core.generation.queues.RemoteWorldRetrievalQueue;
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
||||||
import com.seibel.distanthorizons.core.level.IDhLevel;
|
import com.seibel.distanthorizons.core.level.IDhLevel;
|
||||||
import com.seibel.distanthorizons.core.level.LodRequestModule;
|
import com.seibel.distanthorizons.core.generation.queues.LodRequestModule;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
|
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
|
||||||
import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue;
|
import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue;
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
|||||||
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider;
|
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider;
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
|
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||||
import com.seibel.distanthorizons.core.util.FormatUtil;
|
import com.seibel.distanthorizons.core.util.FormatUtil;
|
||||||
@@ -93,7 +92,8 @@ public class PregenManager
|
|||||||
private final AtomicInteger nextSectionSpiralIndex = new AtomicInteger(0);
|
private final AtomicInteger nextSectionSpiralIndex = new AtomicInteger(0);
|
||||||
|
|
||||||
private final AtomicLong lastTaskFinishTime = new AtomicLong(System.currentTimeMillis());
|
private final AtomicLong lastTaskFinishTime = new AtomicLong(System.currentTimeMillis());
|
||||||
private final RollingAverage averageTaskCompletionIntervalMs = new RollingAverage(1000);
|
private RollingAverage averageTaskCompletionIntervalMs = new RollingAverage(1000);
|
||||||
|
private final RollingAverage averageTaskCompletionIntervalMsShort = new RollingAverage(50);
|
||||||
|
|
||||||
private final AtomicLong lastLogTime = new AtomicLong();
|
private final AtomicLong lastLogTime = new AtomicLong();
|
||||||
|
|
||||||
@@ -112,6 +112,7 @@ public class PregenManager
|
|||||||
|
|
||||||
long timeSincePreviousTaskFinish = System.currentTimeMillis() - this.lastTaskFinishTime.getAndSet(System.currentTimeMillis());
|
long timeSincePreviousTaskFinish = System.currentTimeMillis() - this.lastTaskFinishTime.getAndSet(System.currentTimeMillis());
|
||||||
this.averageTaskCompletionIntervalMs.add(timeSincePreviousTaskFinish);
|
this.averageTaskCompletionIntervalMs.add(timeSincePreviousTaskFinish);
|
||||||
|
this.averageTaskCompletionIntervalMsShort.add(timeSincePreviousTaskFinish);
|
||||||
|
|
||||||
PregenState.this.fillPendingQueue();
|
PregenState.this.fillPendingQueue();
|
||||||
})
|
})
|
||||||
@@ -181,7 +182,14 @@ public class PregenManager
|
|||||||
double chunksToGenerate = Math.ceil(Math.sqrt(this.sectionsToGenerate) / 2 * 4 * 10) / 10; // ceil to nearest 0.1
|
double chunksToGenerate = Math.ceil(Math.sqrt(this.sectionsToGenerate) / 2 * 4 * 10) / 10; // ceil to nearest 0.1
|
||||||
int chunkRatePerSecond = (int) (1000 / this.averageTaskCompletionIntervalMs.getAverage() * 4 * 4);
|
int chunkRatePerSecond = (int) (1000 / this.averageTaskCompletionIntervalMs.getAverage() * 4 * 4);
|
||||||
double etaMs = this.averageTaskCompletionIntervalMs.getAverage() * (this.sectionsToGenerate - this.nextSectionSpiralIndex.get());
|
double etaMs = this.averageTaskCompletionIntervalMs.getAverage() * (this.sectionsToGenerate - this.nextSectionSpiralIndex.get());
|
||||||
|
|
||||||
|
// Reset long rolling average if short average diverged too much (<0.5 / >2.0)
|
||||||
|
double averageRatio = this.averageTaskCompletionIntervalMsShort.getAverage() / this.averageTaskCompletionIntervalMs.getAverage();
|
||||||
|
if (averageRatio < 0.5 || averageRatio > 2.0)
|
||||||
|
{
|
||||||
|
this.averageTaskCompletionIntervalMs = new RollingAverage(1000);
|
||||||
|
}
|
||||||
|
|
||||||
return MessageFormat.format("Generated radius: {0,number,#.###} / {1,number,#.#} chunks ({2} cps, {3,number,#.###%}), ETA: {4}",
|
return MessageFormat.format("Generated radius: {0,number,#.###} / {1,number,#.#} chunks ({2} cps, {3,number,#.###%}), ETA: {4}",
|
||||||
this.generatedRadius.getValue(),
|
this.generatedRadius.getValue(),
|
||||||
chunksToGenerate,
|
chunksToGenerate,
|
||||||
|
|||||||
+227
@@ -0,0 +1,227 @@
|
|||||||
|
package com.seibel.distanthorizons.core.generation.queues;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorProgressDisplayLocation;
|
||||||
|
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||||
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
|
import com.seibel.distanthorizons.core.level.DhServerLevel;
|
||||||
|
import com.seibel.distanthorizons.core.level.IDhLevel;
|
||||||
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
|
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
|
||||||
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||||
|
import com.seibel.distanthorizons.core.util.FormatUtil;
|
||||||
|
import com.seibel.distanthorizons.core.util.ThreadUtil;
|
||||||
|
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
|
||||||
|
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
|
||||||
|
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the {@link IFullDataSourceRetrievalQueue} and any other necessary world gen information.
|
||||||
|
*
|
||||||
|
* @see LodRequestModule
|
||||||
|
* @see IFullDataSourceRetrievalQueue
|
||||||
|
*/
|
||||||
|
public abstract class AbstractLodRequestState
|
||||||
|
{
|
||||||
|
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
||||||
|
|
||||||
|
/** static so we only send the disable message once per session */
|
||||||
|
private static long firstProgressMessageSentMs = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public final IDhLevel dhLevel;
|
||||||
|
public final IFullDataSourceRetrievalQueue retrievalQueue;
|
||||||
|
|
||||||
|
private final ThreadPoolExecutor progressUpdaterThread = ThreadUtil.makeSingleDaemonThreadPool("World Gen Progress Updater");
|
||||||
|
private boolean progressUpdateThreadRunning = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//===========================//
|
||||||
|
// request queue and logging //
|
||||||
|
//===========================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
public AbstractLodRequestState(IDhLevel dhLevel, IFullDataSourceRetrievalQueue retrievalQueue)
|
||||||
|
{
|
||||||
|
this.dhLevel = dhLevel;
|
||||||
|
this.retrievalQueue = retrievalQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//===========================//
|
||||||
|
// request queue and logging //
|
||||||
|
//===========================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
/** @param targetPosForRequest the position that world generation should be centered around */
|
||||||
|
public void startRequestQueueAndSetTargetPos(DhBlockPos2D targetPosForRequest)
|
||||||
|
{
|
||||||
|
this.retrievalQueue.startAndSetTargetPos(targetPosForRequest);
|
||||||
|
this.startProgressUpdateThread();
|
||||||
|
}
|
||||||
|
private void startProgressUpdateThread()
|
||||||
|
{
|
||||||
|
// only start the thread once
|
||||||
|
if (!this.progressUpdateThreadRunning)
|
||||||
|
{
|
||||||
|
this.progressUpdateThreadRunning = true;
|
||||||
|
|
||||||
|
progressUpdaterThread.execute(() ->
|
||||||
|
{
|
||||||
|
while (this.progressUpdateThreadRunning)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.sendRetrievalProgress();
|
||||||
|
|
||||||
|
// sleep so we only see an update once in a while
|
||||||
|
int sleepTimeInSec = Config.Common.WorldGenerator.generationProgressDisplayIntervalInSeconds.get();
|
||||||
|
Thread.sleep(sleepTimeInSec * 1_000L);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LOGGER.error("Unexpected issue displaying chunk retrieval progress [" + e.getMessage() + "].", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void sendRetrievalProgress()
|
||||||
|
{
|
||||||
|
// format the remaining chunks
|
||||||
|
int remainingChunkCount = this.retrievalQueue.getRetrievalEstimatedRemainingChunkCount();
|
||||||
|
remainingChunkCount += this.retrievalQueue.getQueuedChunkCount();
|
||||||
|
String remainingChunkCountStr = F3Screen.NUMBER_FORMAT.format(remainingChunkCount);
|
||||||
|
|
||||||
|
String message = "DH is generating chunks. ";
|
||||||
|
if (this.dhLevel.getClass() == DhServerLevel.class)
|
||||||
|
{
|
||||||
|
// server levels can have multiple world generators running at once,
|
||||||
|
// this helps us track of which queue is which
|
||||||
|
message += "For " + this.dhLevel.getLevelWrapper().getDimensionName() + " ";
|
||||||
|
}
|
||||||
|
message += remainingChunkCountStr + " left.";
|
||||||
|
|
||||||
|
// show a message about how to disable progress logging if requested
|
||||||
|
int msToShowDisableInstructions = Config.Common.WorldGenerator.generationProgressDisableMessageDisplayTimeInSeconds.get() * 1_000;
|
||||||
|
if (msToShowDisableInstructions > 0)
|
||||||
|
{
|
||||||
|
long timeSinceFirstMessageInMs = (System.currentTimeMillis() - firstProgressMessageSentMs);
|
||||||
|
// always show this message for the first tick
|
||||||
|
if (firstProgressMessageSentMs == 0
|
||||||
|
// show this message if there is still time
|
||||||
|
|| timeSinceFirstMessageInMs < msToShowDisableInstructions)
|
||||||
|
{
|
||||||
|
// append to the current message
|
||||||
|
message += " This message can be hidden in the DH config.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the remaining time estimate if available
|
||||||
|
double chunksPerSec = this.getEstimatedChunksPerSecond();
|
||||||
|
if (chunksPerSec > 0)
|
||||||
|
{
|
||||||
|
long estimatedRemainingTime = (long) (remainingChunkCount / chunksPerSec);
|
||||||
|
message += " ETA: " + FormatUtil.formatEta(Duration.ofSeconds(estimatedRemainingTime));
|
||||||
|
|
||||||
|
if (Config.Common.WorldGenerator.generationProgressIncludeChunksPerSecond.get())
|
||||||
|
{
|
||||||
|
message += " at " + F3Screen.NUMBER_FORMAT.format(chunksPerSec) + " chunks/sec";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// only log if there are chunks needing to be generated
|
||||||
|
if (remainingChunkCount != 0)
|
||||||
|
{
|
||||||
|
// determine where to log
|
||||||
|
EDhApiDistantGeneratorProgressDisplayLocation displayLocation = Config.Common.WorldGenerator.showGenerationProgress.get();
|
||||||
|
if (displayLocation == EDhApiDistantGeneratorProgressDisplayLocation.OVERLAY)
|
||||||
|
{
|
||||||
|
ClientApi.INSTANCE.showOverlayMessageNextFrame(message);
|
||||||
|
}
|
||||||
|
else if (displayLocation == EDhApiDistantGeneratorProgressDisplayLocation.CHAT)
|
||||||
|
{
|
||||||
|
ClientApi.INSTANCE.showChatMessageNextFrame(message);
|
||||||
|
}
|
||||||
|
else if (displayLocation == EDhApiDistantGeneratorProgressDisplayLocation.LOG)
|
||||||
|
{
|
||||||
|
LOGGER.info(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// mark when the first message was sent
|
||||||
|
if (firstProgressMessageSentMs == 0)
|
||||||
|
{
|
||||||
|
firstProgressMessageSentMs = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return -1 if this method isn't supported or available */
|
||||||
|
public double getEstimatedChunksPerSecond()
|
||||||
|
{
|
||||||
|
RollingAverage avg = this.retrievalQueue.getRollingAverageChunkGenTimeInMs();
|
||||||
|
if (avg == null)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
PriorityTaskPicker.Executor executor = ThreadPoolUtil.getWorldGenExecutor();
|
||||||
|
int threadCount = 1;
|
||||||
|
if (executor != null)
|
||||||
|
{
|
||||||
|
threadCount = executor.getPoolSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert chunk generation time in milliseconds to chunks per second
|
||||||
|
double chunksPerSecond = (1 / avg.getAverage()) * 1_000;
|
||||||
|
// estimate the number of chunks that can be processed per second by all threads
|
||||||
|
// Note: this is probably higher than the actual number, we might want to drop this by 1 or 2 to give a more realistic estimate
|
||||||
|
chunksPerSecond = threadCount * chunksPerSecond;
|
||||||
|
|
||||||
|
return chunksPerSecond;
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// base overrides //
|
||||||
|
//================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
public CompletableFuture<Void> closeAsync(boolean doInterrupt)
|
||||||
|
{
|
||||||
|
// this should stop the updater thread
|
||||||
|
this.progressUpdateThreadRunning = false;
|
||||||
|
|
||||||
|
return this.retrievalQueue.startClosingAsync(true, doInterrupt)
|
||||||
|
.exceptionally(e ->
|
||||||
|
{
|
||||||
|
LOGGER.error("Error during first stage of generation queue shutdown, Error: [" + e.getMessage() + "].", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
).thenRun(this.retrievalQueue::close)
|
||||||
|
.exceptionally(e ->
|
||||||
|
{
|
||||||
|
LOGGER.error("Error during second stage of generation queue shutdown, Error: [" + e.getMessage() + "].", e);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+3
-1
@@ -17,7 +17,7 @@
|
|||||||
* 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.generation;
|
package com.seibel.distanthorizons.core.generation.queues;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
||||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||||
@@ -42,6 +42,8 @@ import java.util.concurrent.CompletableFuture;
|
|||||||
* Used by both world gen and server networking.
|
* Used by both world gen and server networking.
|
||||||
*
|
*
|
||||||
* @see LodQuadTree
|
* @see LodQuadTree
|
||||||
|
* @see AbstractLodRequestState
|
||||||
|
* @see LodRequestModule
|
||||||
*/
|
*/
|
||||||
public interface IFullDataSourceRetrievalQueue extends Closeable
|
public interface IFullDataSourceRetrievalQueue extends Closeable
|
||||||
{
|
{
|
||||||
+61
-181
@@ -17,39 +17,35 @@
|
|||||||
* 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.level;
|
package com.seibel.distanthorizons.core.generation.queues;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorProgressDisplayLocation;
|
|
||||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
|
||||||
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider;
|
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider;
|
||||||
import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue;
|
import com.seibel.distanthorizons.core.level.*;
|
||||||
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.blockPos.DhBlockPos2D;
|
||||||
import com.seibel.distanthorizons.core.util.FormatUtil;
|
|
||||||
import com.seibel.distanthorizons.core.util.ThreadUtil;
|
import com.seibel.distanthorizons.core.util.ThreadUtil;
|
||||||
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
|
|
||||||
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
|
|
||||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
|
||||||
import com.seibel.distanthorizons.core.world.DhApiWorldProxy;
|
import com.seibel.distanthorizons.core.world.DhApiWorldProxy;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles both single-player/server-side world gen and client side LOD requests.
|
* Handles both single-player/server-side world gen and client side LOD requests.
|
||||||
|
*
|
||||||
|
* @see AbstractLodRequestState
|
||||||
|
* @see IFullDataSourceRetrievalQueue
|
||||||
*/
|
*/
|
||||||
public class LodRequestModule implements Closeable
|
public class LodRequestModule implements Closeable
|
||||||
{
|
{
|
||||||
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
||||||
|
|
||||||
|
|
||||||
|
private final IDhLevel level;
|
||||||
private final GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener onWorldGenCompleteListener;
|
private final GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener onWorldGenCompleteListener;
|
||||||
private final ThreadPoolExecutor tickerThread;
|
private final ThreadPoolExecutor tickerThread;
|
||||||
|
|
||||||
@@ -63,6 +59,7 @@ public class LodRequestModule implements Closeable
|
|||||||
//=============//
|
//=============//
|
||||||
// constructor //
|
// constructor //
|
||||||
//=============//
|
//=============//
|
||||||
|
//region
|
||||||
|
|
||||||
public LodRequestModule(
|
public LodRequestModule(
|
||||||
IDhLevel level,
|
IDhLevel level,
|
||||||
@@ -71,29 +68,50 @@ public class LodRequestModule implements Closeable
|
|||||||
Supplier<? extends AbstractLodRequestState> worldGenStateSupplier
|
Supplier<? extends AbstractLodRequestState> worldGenStateSupplier
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
this.level = level;
|
||||||
this.onWorldGenCompleteListener = onWorldGenCompleteListener;
|
this.onWorldGenCompleteListener = onWorldGenCompleteListener;
|
||||||
this.dataSourceProvider = dataSourceProvider;
|
this.dataSourceProvider = dataSourceProvider;
|
||||||
this.worldGenStateSupplier = worldGenStateSupplier;
|
this.worldGenStateSupplier = worldGenStateSupplier;
|
||||||
|
|
||||||
String levelId = level.getLevelWrapper().getDhIdentifier();
|
String levelId = this.level.getLevelWrapper().getDhIdentifier();
|
||||||
this.tickerThread = ThreadUtil.makeSingleDaemonThreadPool("Request Module Ticker ["+levelId+"]");
|
this.tickerThread = ThreadUtil.makeSingleDaemonThreadPool("Request Module Ticker ["+levelId+"]");
|
||||||
this.tickerThread.execute(this::tickLoop);
|
this.tickerThread.execute(this::tickLoop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=========//
|
//=========//
|
||||||
// ticking //
|
// ticking //
|
||||||
//=========//
|
//=========//
|
||||||
|
//region
|
||||||
|
|
||||||
private void tickLoop()
|
private void tickLoop()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Initial wait is to prevent an issue
|
||||||
|
// where this starts before the child object's constructor finishes,
|
||||||
|
// causing null pointers on final non-null references.
|
||||||
|
// The try-catch in the while loop should also handle this
|
||||||
|
// but this way we shouldn't have error logs.
|
||||||
|
Thread.sleep(500);
|
||||||
|
|
||||||
|
// run until the threadpool is shut down
|
||||||
while (!Thread.interrupted())
|
while (!Thread.interrupted())
|
||||||
{
|
{
|
||||||
Thread.sleep(20);
|
try
|
||||||
this.tick();
|
{
|
||||||
|
Thread.sleep(20);
|
||||||
|
|
||||||
|
this.tick();
|
||||||
|
}
|
||||||
|
catch (InterruptedException e) { throw e; }
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LOGGER.error("Unexpected error in [" + LodRequestModule.class.getSimpleName() + "] tick loop, error: [" + e.getMessage() + "].", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (InterruptedException ignore) { }
|
catch (InterruptedException ignore) { }
|
||||||
@@ -101,8 +119,22 @@ public class LodRequestModule implements Closeable
|
|||||||
private void tick()
|
private void tick()
|
||||||
{
|
{
|
||||||
boolean shouldDoWorldGen = this.onWorldGenCompleteListener.shouldDoWorldGen();
|
boolean shouldDoWorldGen = this.onWorldGenCompleteListener.shouldDoWorldGen();
|
||||||
|
|
||||||
// if the world is read only don't generate anything
|
// if the world is read only don't generate anything
|
||||||
shouldDoWorldGen &= !DhApiWorldProxy.INSTANCE.getReadOnly();
|
shouldDoWorldGen &= !DhApiWorldProxy.INSTANCE.tryGetReadOnly();
|
||||||
|
|
||||||
|
// don't generate chunks for client levels that aren't being rendered
|
||||||
|
// (this can happen when moving between dimensions)
|
||||||
|
if (this.level instanceof IDhClientLevel)
|
||||||
|
{
|
||||||
|
boolean isRendering = ((IDhClientLevel) this.level).isRendering();
|
||||||
|
if (!isRendering)
|
||||||
|
{
|
||||||
|
shouldDoWorldGen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
boolean isWorldGenRunning = this.isWorldGenRunning();
|
boolean isWorldGenRunning = this.isWorldGenRunning();
|
||||||
if (shouldDoWorldGen && !isWorldGenRunning)
|
if (shouldDoWorldGen && !isWorldGenRunning)
|
||||||
@@ -130,11 +162,14 @@ public class LodRequestModule implements Closeable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//===================//
|
//===================//
|
||||||
// world gen control //
|
// world gen control //
|
||||||
//===================//
|
//===================//
|
||||||
|
//region
|
||||||
|
|
||||||
public void startWorldGen(GeneratedFullDataSourceProvider dataFileHandler, AbstractLodRequestState newWgs)
|
public void startWorldGen(GeneratedFullDataSourceProvider dataFileHandler, AbstractLodRequestState newWgs)
|
||||||
{
|
{
|
||||||
@@ -168,15 +203,19 @@ public class LodRequestModule implements Closeable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
dataFileHandler.clearRetrievalQueue();
|
dataFileHandler.clearRetrievalQueue();
|
||||||
worldGenState.closeAsync(true).join(); //TODO: Make it async.
|
// synchronized shutdown necessary to make sure the tasks are all handled correctly
|
||||||
|
worldGenState.closeAsync(true).join();
|
||||||
dataFileHandler.removeWorldGenCompleteListener(this.onWorldGenCompleteListener);
|
dataFileHandler.removeWorldGenCompleteListener(this.onWorldGenCompleteListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=======================//
|
//=======================//
|
||||||
// base method overrides //
|
// base method overrides //
|
||||||
//=======================//
|
//=======================//
|
||||||
|
//region
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close()
|
public void close()
|
||||||
@@ -198,16 +237,20 @@ public class LodRequestModule implements Closeable
|
|||||||
|
|
||||||
if (worldGenState != null)
|
if (worldGenState != null)
|
||||||
{
|
{
|
||||||
worldGenState.closeAsync(true).join(); //TODO: Make it async.
|
// synchronized shutdown necessary to make sure the tasks are all handled correctly
|
||||||
|
worldGenState.closeAsync(true).join();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=========//
|
//=========//
|
||||||
// getters //
|
// getters //
|
||||||
//=========//
|
//=========//
|
||||||
|
//region
|
||||||
|
|
||||||
public boolean isWorldGenRunning() { return this.lodRequestStateRef.get() != null; }
|
public boolean isWorldGenRunning() { return this.lodRequestStateRef.get() != null; }
|
||||||
|
|
||||||
@@ -239,170 +282,7 @@ public class LodRequestModule implements Closeable
|
|||||||
worldGenState.retrievalQueue.addDebugMenuStringsToList(messageList);
|
worldGenState.retrievalQueue.addDebugMenuStringsToList(messageList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
//================//
|
|
||||||
// helper classes //
|
|
||||||
//================//
|
|
||||||
|
|
||||||
/** Handles the {@link IFullDataSourceRetrievalQueue} and any other necessary world gen information. */
|
|
||||||
public static abstract class AbstractLodRequestState
|
|
||||||
{
|
|
||||||
/** static so we only send the disable message once per session */
|
|
||||||
private static long firstProgressMessageSentMs = 0;
|
|
||||||
|
|
||||||
public IFullDataSourceRetrievalQueue retrievalQueue;
|
|
||||||
|
|
||||||
private static final ThreadPoolExecutor PROGRESS_UPDATER_THREAD = ThreadUtil.makeSingleDaemonThreadPool("World Gen Progress Updater");
|
|
||||||
private boolean progressUpdateThreadRunning = false;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** @param targetPosForRequest the position that world generation should be centered around */
|
|
||||||
public void startRequestQueueAndSetTargetPos(DhBlockPos2D targetPosForRequest)
|
|
||||||
{
|
|
||||||
this.retrievalQueue.startAndSetTargetPos(targetPosForRequest);
|
|
||||||
this.startProgressUpdateThread();
|
|
||||||
}
|
|
||||||
private void startProgressUpdateThread()
|
|
||||||
{
|
|
||||||
// only start the thread once
|
|
||||||
if (!this.progressUpdateThreadRunning)
|
|
||||||
{
|
|
||||||
this.progressUpdateThreadRunning = true;
|
|
||||||
|
|
||||||
PROGRESS_UPDATER_THREAD.execute(() ->
|
|
||||||
{
|
|
||||||
while (this.progressUpdateThreadRunning)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.sendRetrievalProgress();
|
|
||||||
|
|
||||||
// sleep so we only see an update once in a while
|
|
||||||
int sleepTimeInSec = Config.Common.WorldGenerator.generationProgressDisplayIntervalInSeconds.get();
|
|
||||||
Thread.sleep(sleepTimeInSec * 1_000L);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
LOGGER.error("Unexpected issue displaying chunk retrieval progress [" + e.getMessage() + "].", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void sendRetrievalProgress()
|
|
||||||
{
|
|
||||||
// format the remaining chunks
|
|
||||||
int remainingChunkCount = this.retrievalQueue.getRetrievalEstimatedRemainingChunkCount();
|
|
||||||
remainingChunkCount += this.retrievalQueue.getQueuedChunkCount();
|
|
||||||
String remainingChunkCountStr = F3Screen.NUMBER_FORMAT.format(remainingChunkCount);
|
|
||||||
|
|
||||||
String message = "DH is generating chunks. " + remainingChunkCountStr + " left."; // TODO getting stuck at 32 chunks remaining
|
|
||||||
|
|
||||||
// show a message about how to disable progress logging if requested
|
|
||||||
int msToShowDisableInstructions = Config.Common.WorldGenerator.generationProgressDisableMessageDisplayTimeInSeconds.get() * 1_000;
|
|
||||||
if (msToShowDisableInstructions > 0)
|
|
||||||
{
|
|
||||||
long timeSinceFirstMessageInMs = (System.currentTimeMillis() - firstProgressMessageSentMs);
|
|
||||||
// always show this message for the first tick
|
|
||||||
if (firstProgressMessageSentMs == 0
|
|
||||||
// show this message if there is still time
|
|
||||||
|| timeSinceFirstMessageInMs < msToShowDisableInstructions)
|
|
||||||
{
|
|
||||||
// append to the current message
|
|
||||||
message += " This message can be hidden in the DH config.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add the remaining time estimate if available
|
|
||||||
double chunksPerSec = this.getEstimatedChunksPerSecond();
|
|
||||||
if (chunksPerSec > 0)
|
|
||||||
{
|
|
||||||
long estimatedRemainingTime = (long) (remainingChunkCount / chunksPerSec);
|
|
||||||
message += " ETA: " + FormatUtil.formatEta(Duration.ofSeconds(estimatedRemainingTime));
|
|
||||||
|
|
||||||
if (Config.Common.WorldGenerator.generationProgressIncludeChunksPerSecond.get())
|
|
||||||
{
|
|
||||||
message += " at " + F3Screen.NUMBER_FORMAT.format(chunksPerSec) + " chunks/sec";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// only log if there are chunks needing to be generated
|
|
||||||
if (remainingChunkCount != 0)
|
|
||||||
{
|
|
||||||
// determine where to log
|
|
||||||
EDhApiDistantGeneratorProgressDisplayLocation displayLocation = Config.Common.WorldGenerator.showGenerationProgress.get();
|
|
||||||
if (displayLocation == EDhApiDistantGeneratorProgressDisplayLocation.OVERLAY)
|
|
||||||
{
|
|
||||||
ClientApi.INSTANCE.showOverlayMessageNextFrame(message);
|
|
||||||
}
|
|
||||||
else if (displayLocation == EDhApiDistantGeneratorProgressDisplayLocation.CHAT)
|
|
||||||
{
|
|
||||||
ClientApi.INSTANCE.showChatMessageNextFrame(message);
|
|
||||||
}
|
|
||||||
else if (displayLocation == EDhApiDistantGeneratorProgressDisplayLocation.LOG)
|
|
||||||
{
|
|
||||||
LOGGER.info(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// mark when the first message was sent
|
|
||||||
if (firstProgressMessageSentMs == 0)
|
|
||||||
{
|
|
||||||
firstProgressMessageSentMs = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return -1 if this method isn't supported or available */
|
|
||||||
public double getEstimatedChunksPerSecond()
|
|
||||||
{
|
|
||||||
RollingAverage avg = this.retrievalQueue.getRollingAverageChunkGenTimeInMs();
|
|
||||||
if (avg == null)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
PriorityTaskPicker.Executor executor = ThreadPoolUtil.getWorldGenExecutor();
|
|
||||||
int threadCount = 1;
|
|
||||||
if (executor != null)
|
|
||||||
{
|
|
||||||
threadCount = executor.getPoolSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert chunk generation time in milliseconds to chunks per second
|
|
||||||
double chunksPerSecond = (1 / avg.getAverage()) * 1_000;
|
|
||||||
// estimate the number of chunks that can be processed per second by all threads
|
|
||||||
// Note: this is probably higher than the actual number, we might want to drop this by 1 or 2 to give a more realistic estimate
|
|
||||||
chunksPerSecond = threadCount * chunksPerSecond;
|
|
||||||
|
|
||||||
return chunksPerSecond;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
CompletableFuture<Void> closeAsync(boolean doInterrupt)
|
|
||||||
{
|
|
||||||
// this should stop the updater thread
|
|
||||||
this.progressUpdateThreadRunning = false;
|
|
||||||
|
|
||||||
return this.retrievalQueue.startClosingAsync(true, doInterrupt)
|
|
||||||
.exceptionally(e ->
|
|
||||||
{
|
|
||||||
LOGGER.error("Error during first stage of generation queue shutdown, Error: ["+e.getMessage()+"].", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
).thenRun(this.retrievalQueue::close)
|
|
||||||
.exceptionally(e ->
|
|
||||||
{
|
|
||||||
LOGGER.error("Error during second stage of generation queue shutdown, Error: ["+e.getMessage()+"].", e);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+1
-2
@@ -1,4 +1,4 @@
|
|||||||
package com.seibel.distanthorizons.core.generation;
|
package com.seibel.distanthorizons.core.generation.queues;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
|
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
|
||||||
@@ -15,7 +15,6 @@ import com.seibel.distanthorizons.core.util.LodUtil;
|
|||||||
import com.seibel.distanthorizons.core.util.WorldGenUtil;
|
import com.seibel.distanthorizons.core.util.WorldGenUtil;
|
||||||
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
|
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
|
||||||
|
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
+3
-2
@@ -17,7 +17,7 @@
|
|||||||
* 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.generation;
|
package com.seibel.distanthorizons.core.generation.queues;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
|
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
|
||||||
import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGenerator;
|
import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGenerator;
|
||||||
@@ -27,6 +27,7 @@ import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource;
|
|||||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
|
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
|
||||||
|
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalTask;
|
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalTask;
|
||||||
import com.seibel.distanthorizons.core.level.IDhServerLevel;
|
import com.seibel.distanthorizons.core.level.IDhServerLevel;
|
||||||
@@ -201,7 +202,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
private synchronized void tryQueueNewWorldGenRequestsAsync()
|
private synchronized void tryQueueNewWorldGenRequestsAsync()
|
||||||
{
|
{
|
||||||
if (!DhApiWorldProxy.INSTANCE.worldLoaded()
|
if (!DhApiWorldProxy.INSTANCE.worldLoaded()
|
||||||
|| DhApiWorldProxy.INSTANCE.getReadOnly())
|
|| DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,10 @@ public class GitlabGetter
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
pipelineInfo.put(pipeline, WebDownloader.parseWebJsonList(this.GitProjID + "pipelines/" + pipeline + "/jobs"));
|
pipelineInfo.put(pipeline, WebDownloader.parseWebJsonList(this.GitProjID + "pipelines/" + pipeline + "/jobs?per_page=100"));
|
||||||
|
// max page size is 100,
|
||||||
|
// if we ever support more than 100 MC versions this logic will need to handle pagination
|
||||||
|
// https://docs.gitlab.com/api/rest/#pagination
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,16 +28,13 @@ import com.seibel.distanthorizons.core.jar.installer.GitlabGetter;
|
|||||||
import com.seibel.distanthorizons.core.jar.installer.ModrinthGetter;
|
import com.seibel.distanthorizons.core.jar.installer.ModrinthGetter;
|
||||||
import com.seibel.distanthorizons.core.jar.installer.WebDownloader;
|
import com.seibel.distanthorizons.core.jar.installer.WebDownloader;
|
||||||
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.util.NativeDialogUtil;
|
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.IVersionConstants;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.IVersionConstants;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||||
import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
||||||
import com.seibel.distanthorizons.coreapi.util.jar.DeleteOnUnlock;
|
import com.seibel.distanthorizons.coreapi.util.jar.DeleteOnUnlock;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.awt.*;
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@@ -59,6 +56,8 @@ public class SelfUpdater
|
|||||||
{
|
{
|
||||||
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
||||||
|
|
||||||
|
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||||
|
|
||||||
/** As we cannot delete(or replace) the jar while the mod is running, we just have this to delete it once the game closes */
|
/** As we cannot delete(or replace) the jar while the mod is running, we just have this to delete it once the game closes */
|
||||||
public static boolean deleteOldJarOnJvmShutdown = false;
|
public static boolean deleteOldJarOnJvmShutdown = false;
|
||||||
|
|
||||||
@@ -264,7 +263,7 @@ public class SelfUpdater
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, successMessage, "ok", "info");
|
MC_CLIENT.showDialog(ModInfo.READABLE_NAME, successMessage, "ok", "info");
|
||||||
}
|
}
|
||||||
catch (Exception ignore) { }
|
catch (Exception ignore) { }
|
||||||
}).start();
|
}).start();
|
||||||
@@ -287,7 +286,7 @@ public class SelfUpdater
|
|||||||
LOGGER.error(failMessage, e);
|
LOGGER.error(failMessage, e);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, failMessage, "ok", "error");
|
MC_CLIENT.showDialog(ModInfo.READABLE_NAME, failMessage, "ok", "error");
|
||||||
}
|
}
|
||||||
catch (Exception ignore) { }
|
catch (Exception ignore) { }
|
||||||
|
|
||||||
@@ -386,7 +385,7 @@ public class SelfUpdater
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, successMessage, "ok", "info");
|
MC_CLIENT.showDialog(ModInfo.READABLE_NAME, successMessage, "ok", "info");
|
||||||
}
|
}
|
||||||
catch (Exception ignore) { }
|
catch (Exception ignore) { }
|
||||||
}).start();
|
}).start();
|
||||||
@@ -424,7 +423,7 @@ public class SelfUpdater
|
|||||||
LOGGER.error(failMessage, e);
|
LOGGER.error(failMessage, e);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, failMessage, "ok", "error");
|
MC_CLIENT.showDialog(ModInfo.READABLE_NAME, failMessage, "ok", "error");
|
||||||
}
|
}
|
||||||
catch (Exception ignore) { }
|
catch (Exception ignore) { }
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ public abstract class AbstractDhLevel implements IDhLevel
|
|||||||
//=============//
|
//=============//
|
||||||
// constructor //
|
// constructor //
|
||||||
//=============//
|
//=============//
|
||||||
|
//region
|
||||||
|
|
||||||
protected AbstractDhLevel() { }
|
protected AbstractDhLevel() { }
|
||||||
|
|
||||||
@@ -135,11 +136,14 @@ public abstract class AbstractDhLevel implements IDhLevel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=================//
|
//=================//
|
||||||
// default methods //
|
// default methods //
|
||||||
//=================//
|
//=================//
|
||||||
|
//region
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateChunkAsync(IChunkWrapper chunkWrapper, int chunkHash)
|
public void updateChunkAsync(IChunkWrapper chunkWrapper, int chunkHash)
|
||||||
@@ -208,11 +212,14 @@ public abstract class AbstractDhLevel implements IDhLevel
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=======//
|
//=======//
|
||||||
// repos //
|
// repos //
|
||||||
//=======//
|
//=======//
|
||||||
|
//region
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getChunkHash(DhChunkPos pos)
|
public int getChunkHash(DhChunkPos pos)
|
||||||
@@ -226,11 +233,14 @@ public abstract class AbstractDhLevel implements IDhLevel
|
|||||||
return (dto != null) ? dto.chunkHash : 0;
|
return (dto != null) ? dto.chunkHash : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=================//
|
//=================//
|
||||||
// beacon handling //
|
// beacon handling //
|
||||||
//=================//
|
//=================//
|
||||||
|
//region
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateBeaconBeamsForSectionPos(long sectionPos, List<BeaconBeamDTO> activeBeamList)
|
public void updateBeaconBeamsForSectionPos(long sectionPos, List<BeaconBeamDTO> activeBeamList)
|
||||||
@@ -362,6 +372,8 @@ public abstract class AbstractDhLevel implements IDhLevel
|
|||||||
@Nullable
|
@Nullable
|
||||||
public BeaconBeamRepo getBeaconBeamRepo() { return this.beaconBeamRepo; }
|
public BeaconBeamRepo getBeaconBeamRepo() { return this.beaconBeamRepo; }
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//================//
|
//================//
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldDoWorldGen()
|
public boolean shouldDoWorldGen()
|
||||||
{ return Config.Common.WorldGenerator.enableDistantGeneration.get() && !this.worldGenPlayerCenteringQueue.isEmpty(); }
|
{ return Config.Common.WorldGenerator.enableDistantGeneration.get(); }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DhBlockPos2D getTargetPosForGeneration()
|
public DhBlockPos2D getTargetPosForGeneration()
|
||||||
|
|||||||
@@ -29,8 +29,10 @@ import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
|||||||
import com.seibel.distanthorizons.core.render.QuadTree.LodQuadTree;
|
import com.seibel.distanthorizons.core.render.QuadTree.LodQuadTree;
|
||||||
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
|
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||||
|
import com.seibel.distanthorizons.core.util.math.Vec3d;
|
||||||
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;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
@@ -44,6 +46,7 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
|
|||||||
{
|
{
|
||||||
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
||||||
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||||
|
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
|
||||||
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
|
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
|
||||||
|
|
||||||
private final IDhClientLevel clientLevel;
|
private final IDhClientLevel clientLevel;
|
||||||
@@ -106,7 +109,10 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
|
|||||||
this.ClientRenderStateRef.set(clientRenderState);
|
this.ClientRenderStateRef.set(clientRenderState);
|
||||||
}
|
}
|
||||||
|
|
||||||
clientRenderState.quadtree.tryTick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
|
// use camera position instead of player pos so free cam mods work better
|
||||||
|
Vec3d cameraDoublePos = MC_RENDER.getCameraExactPosition();
|
||||||
|
DhBlockPos2D cameraBlockPos = new DhBlockPos2D((int)cameraDoublePos.x, (int)cameraDoublePos.z);
|
||||||
|
clientRenderState.quadtree.tryTick(cameraBlockPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -170,6 +176,7 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.fullDataSourceProvider.removeDataSourceUpdateListener(this);
|
this.fullDataSourceProvider.removeDataSourceUpdateListener(this);
|
||||||
|
this.genericRenderer.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -259,7 +266,7 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
|
|||||||
@Override
|
@Override
|
||||||
public void close()
|
public void close()
|
||||||
{
|
{
|
||||||
LOGGER.info("Shutting down " + ClientRenderState.class.getSimpleName());
|
//LOGGER.info("Shutting down " + ClientRenderState.class.getSimpleName());
|
||||||
this.quadtree.close();
|
this.quadtree.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
|||||||
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
|
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
|
||||||
import com.seibel.distanthorizons.core.file.fullDatafile.RemoteFullDataSourceProvider;
|
import com.seibel.distanthorizons.core.file.fullDatafile.RemoteFullDataSourceProvider;
|
||||||
import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
|
import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
|
||||||
import com.seibel.distanthorizons.core.generation.RemoteWorldRetrievalQueue;
|
import com.seibel.distanthorizons.core.generation.queues.RemoteWorldRetrievalQueue;
|
||||||
|
import com.seibel.distanthorizons.core.generation.queues.AbstractLodRequestState;
|
||||||
|
import com.seibel.distanthorizons.core.generation.queues.LodRequestModule;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState;
|
import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState;
|
||||||
@@ -182,7 +184,6 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// TODO this has a lock which can cause stuttering/lag issues
|
|
||||||
this.updateBeaconBeamsForSectionPos(dataSourceDto.pos, message.payload.beaconBeams);
|
this.updateBeaconBeamsForSectionPos(dataSourceDto.pos, message.payload.beaconBeams);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -397,12 +398,10 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
|
|||||||
//================//
|
//================//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
private static class LodRequestState extends LodRequestModule.AbstractLodRequestState
|
private static class LodRequestState extends AbstractLodRequestState
|
||||||
{
|
{
|
||||||
LodRequestState(DhClientLevel clientLevel, ClientNetworkState networkState)
|
LodRequestState(DhClientLevel clientLevel, ClientNetworkState networkState)
|
||||||
{
|
{ super(clientLevel, new RemoteWorldRetrievalQueue(networkState, clientLevel)); }
|
||||||
this.retrievalQueue = new RemoteWorldRetrievalQueue(networkState, clientLevel);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|||||||
@@ -23,7 +23,10 @@ import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiW
|
|||||||
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider;
|
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider;
|
||||||
import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
|
import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
|
||||||
import com.seibel.distanthorizons.core.generation.BatchGenerator;
|
import com.seibel.distanthorizons.core.generation.BatchGenerator;
|
||||||
import com.seibel.distanthorizons.core.generation.WorldGenerationQueue;
|
import com.seibel.distanthorizons.core.generation.queues.IFullDataSourceRetrievalQueue;
|
||||||
|
import com.seibel.distanthorizons.core.generation.queues.WorldGenerationQueue;
|
||||||
|
import com.seibel.distanthorizons.core.generation.queues.AbstractLodRequestState;
|
||||||
|
import com.seibel.distanthorizons.core.generation.queues.LodRequestModule;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.coreapi.DependencyInjection.WorldGeneratorInjector;
|
import com.seibel.distanthorizons.coreapi.DependencyInjection.WorldGeneratorInjector;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
@@ -75,9 +78,11 @@ public class ServerLevelModule implements AutoCloseable
|
|||||||
// helper classes //
|
// helper classes //
|
||||||
//================//
|
//================//
|
||||||
|
|
||||||
public static class LodRequestState extends LodRequestModule.AbstractLodRequestState
|
public static class LodRequestState extends AbstractLodRequestState
|
||||||
{
|
{
|
||||||
LodRequestState(IDhServerLevel level)
|
LodRequestState(IDhServerLevel level)
|
||||||
|
{ super(level, createRetrievalQueue(level)); }
|
||||||
|
private static IFullDataSourceRetrievalQueue createRetrievalQueue(IDhServerLevel level)
|
||||||
{
|
{
|
||||||
IDhApiWorldGenerator worldGenerator = WorldGeneratorInjector.INSTANCE.get(level.getLevelWrapper());
|
IDhApiWorldGenerator worldGenerator = WorldGeneratorInjector.INSTANCE.get(level.getLevelWrapper());
|
||||||
if (worldGenerator == null)
|
if (worldGenerator == null)
|
||||||
@@ -88,9 +93,11 @@ public class ServerLevelModule implements AutoCloseable
|
|||||||
// since core world generator's should have the lowest override priority
|
// since core world generator's should have the lowest override priority
|
||||||
WorldGeneratorInjector.INSTANCE.bind(level.getLevelWrapper(), worldGenerator);
|
WorldGeneratorInjector.INSTANCE.bind(level.getLevelWrapper(), worldGenerator);
|
||||||
}
|
}
|
||||||
this.retrievalQueue = new WorldGenerationQueue(worldGenerator, level);
|
return new WorldGenerationQueue(worldGenerator, level);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import com.seibel.distanthorizons.core.jar.ModJarInfo;
|
|||||||
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
||||||
import com.seibel.distanthorizons.core.level.IDhLevel;
|
import com.seibel.distanthorizons.core.level.IDhLevel;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
|
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
|
||||||
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListPool;
|
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListPool;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
|
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
|
||||||
@@ -149,6 +150,13 @@ public class F3Screen
|
|||||||
messageList.add("");
|
messageList.add("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// render thread tasks
|
||||||
|
if (Config.Client.Advanced.Debugging.F3Screen.showRenderThreadTasks.get())
|
||||||
|
{
|
||||||
|
RenderThreadTaskHandler.INSTANCE.addDebugMenuStringsToList(messageList);
|
||||||
|
messageList.add("");
|
||||||
|
}
|
||||||
|
|
||||||
// combined object pools
|
// combined object pools
|
||||||
if (Config.Client.Advanced.Debugging.F3Screen.showCombinedObjectPools.get())
|
if (Config.Client.Advanced.Debugging.F3Screen.showCombinedObjectPools.get())
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-2
@@ -151,8 +151,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
|||||||
|
|
||||||
public synchronized boolean tick(DhBlockPos2D targetPos)
|
public synchronized boolean tick(DhBlockPos2D targetPos)
|
||||||
{
|
{
|
||||||
if (DhApiWorldProxy.INSTANCE.worldLoaded()
|
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||||
&& DhApiWorldProxy.INSTANCE.getReadOnly())
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
package com.seibel.distanthorizons.core.multiplayer.client;
|
package com.seibel.distanthorizons.core.multiplayer.client;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
import com.seibel.distanthorizons.core.generation.RemoteWorldRetrievalQueue;
|
import com.seibel.distanthorizons.core.generation.queues.RemoteWorldRetrievalQueue;
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
||||||
import com.seibel.distanthorizons.core.level.DhClientLevel;
|
import com.seibel.distanthorizons.core.level.DhClientLevel;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
|
|||||||
+3
-3
@@ -7,10 +7,10 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
|||||||
import com.seibel.distanthorizons.core.network.INetworkObject;
|
import com.seibel.distanthorizons.core.network.INetworkObject;
|
||||||
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage;
|
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage;
|
||||||
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
|
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
|
||||||
import io.netty.buffer.CompositeByteBuf;
|
import io.netty.buffer.CompositeByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
private final ConcurrentMap<Integer, CompositeByteBuf> buffersById = CacheBuilder.newBuilder()
|
private final ConcurrentMap<Integer, CompositeByteBuf> buffersById = CacheBuilder.newBuilder()
|
||||||
.expireAfterAccess(10, TimeUnit.SECONDS)
|
.expireAfterAccess(30, TimeUnit.SECONDS)
|
||||||
.<Integer, CompositeByteBuf>build().asMap();
|
.<Integer, CompositeByteBuf>build().asMap();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -56,7 +56,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
|
|||||||
public FullDataSourceV2DTO decodeDataSource(FullDataPayload payload)
|
public FullDataSourceV2DTO decodeDataSource(FullDataPayload payload)
|
||||||
{
|
{
|
||||||
CompositeByteBuf compositeByteBuffer = this.buffersById.get(payload.dtoBufferId);
|
CompositeByteBuf compositeByteBuffer = this.buffersById.get(payload.dtoBufferId);
|
||||||
LodUtil.assertTrue(compositeByteBuffer != null, "decoded data source missing byte buffer");
|
Objects.requireNonNull(compositeByteBuffer, "Unable to get a complete buffer for a received payload. Ignore this if it doesn't spam similar errors");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
+331
-110
@@ -21,6 +21,7 @@ package com.seibel.distanthorizons.core.render.QuadTree;
|
|||||||
|
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
|
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
|
||||||
|
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
||||||
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
|
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
|
||||||
@@ -34,9 +35,12 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
|||||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
|
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
|
||||||
|
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer;
|
import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.BeaconRenderHandler;
|
import com.seibel.distanthorizons.core.render.renderer.BeaconRenderHandler;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
|
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
|
||||||
|
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
|
||||||
|
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.WorldGenUtil;
|
import com.seibel.distanthorizons.core.util.WorldGenUtil;
|
||||||
@@ -54,10 +58,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import javax.annotation.WillNotClose;
|
import javax.annotation.WillNotClose;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
@@ -96,8 +97,19 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
private final AtomicBoolean queueThreadRunningRef = new AtomicBoolean(false);
|
private final AtomicBoolean queueThreadRunningRef = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* contains the list of beacons currently being rendered in this section
|
||||||
|
* if this list is modified the {@link LodQuadTree#beaconRenderHandler} should be updated to match.
|
||||||
|
*/
|
||||||
|
private final ArrayList<BeaconRenderHandler.BeaconBeamWithWidth> beaconList = new ArrayList<>();
|
||||||
@Nullable
|
@Nullable
|
||||||
public final BeaconRenderHandler beaconRenderHandler;
|
private final BeaconRenderHandler beaconRenderHandler;
|
||||||
|
@Nullable
|
||||||
|
private final BeaconBeamRepo beaconBeamRepo;
|
||||||
|
/** used to prevent updating the beacons concurrently */
|
||||||
|
@NotNull
|
||||||
|
private CompletableFuture<Void> beaconUpdateFuture = CompletableFuture.completedFuture(null);
|
||||||
|
|
||||||
|
|
||||||
/** the smallest numerical detail level number that can be rendered */
|
/** the smallest numerical detail level number that can be rendered */
|
||||||
private byte maxLeafRenderDetailLevel;
|
private byte maxLeafRenderDetailLevel;
|
||||||
@@ -137,7 +149,8 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
int initialPlayerBlockX, int initialPlayerBlockZ,
|
int initialPlayerBlockX, int initialPlayerBlockZ,
|
||||||
FullDataSourceProviderV2 fullDataSourceProvider)
|
FullDataSourceProviderV2 fullDataSourceProvider)
|
||||||
{
|
{
|
||||||
super(viewDiameterInBlocks, new DhBlockPos2D(initialPlayerBlockX, initialPlayerBlockZ), DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
|
super(viewDiameterInBlocks, FullDataSourceV2.WIDTH,
|
||||||
|
new DhBlockPos2D(initialPlayerBlockX, initialPlayerBlockZ), DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
|
||||||
|
|
||||||
DEBUG_RENDERER.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus);
|
DEBUG_RENDERER.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus);
|
||||||
|
|
||||||
@@ -148,6 +161,8 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
IDhGenericRenderer genericObjectRenderer = this.level.getGenericRenderer();
|
IDhGenericRenderer genericObjectRenderer = this.level.getGenericRenderer();
|
||||||
this.beaconRenderHandler = (genericObjectRenderer != null) ? new BeaconRenderHandler(genericObjectRenderer) : null;
|
this.beaconRenderHandler = (genericObjectRenderer != null) ? new BeaconRenderHandler(genericObjectRenderer) : null;
|
||||||
|
|
||||||
|
this.beaconBeamRepo = this.level.getBeaconBeamRepo();
|
||||||
|
|
||||||
Config.Common.WorldGenerator.enableDistantGeneration.addListener(this);
|
Config.Common.WorldGenerator.enableDistantGeneration.addListener(this);
|
||||||
Config.Server.enableServerGeneration.addListener(this);
|
Config.Server.enableServerGeneration.addListener(this);
|
||||||
|
|
||||||
@@ -232,17 +247,38 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
//===================//
|
//===================//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
this.setCenterBlockPos(playerPos, (renderSection) ->
|
// remove out of bound sections
|
||||||
{
|
this.setCenterBlockPos(playerPos,
|
||||||
// removing out of bounds sections
|
// remove completely out of bound nodes
|
||||||
if (renderSection != null)
|
// (the root node is no longer in bounds)
|
||||||
|
(renderSection) ->
|
||||||
{
|
{
|
||||||
this.fullDataSourceProvider.removeRetrievalRequestIf((long genPos) -> DhSectionPos.contains(renderSection.pos, genPos));
|
if (renderSection != null)
|
||||||
this.missingGenerationPosSet.remove(renderSection.pos);
|
{
|
||||||
this.queuedGenerationPosSet.remove(renderSection.pos);
|
this.fullDataSourceProvider.removeRetrievalRequestIf((long genPos) -> DhSectionPos.contains(renderSection.pos, genPos));
|
||||||
renderSection.close();
|
|
||||||
|
// unfortunately we have to fully go through each set
|
||||||
|
// since a removed position may be larger than the multiple generated positions
|
||||||
|
// it contains
|
||||||
|
this.missingGenerationPosSet.removeIf((Long genPos) -> DhSectionPos.contains(renderSection.pos, genPos));
|
||||||
|
this.queuedGenerationPosSet.removeIf((Long genPos) -> DhSectionPos.contains(renderSection.pos, genPos));
|
||||||
|
|
||||||
|
renderSection.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// mutate partially out of bound nodes
|
||||||
|
// (the root node is still in bounds, but this individual child node isn't)
|
||||||
|
(renderSection) ->
|
||||||
|
{
|
||||||
|
if (renderSection != null)
|
||||||
|
{
|
||||||
|
// when this node comes back into render distance
|
||||||
|
// we'll need to re-load it since the full data
|
||||||
|
// may have been modified while it was out of bounds
|
||||||
|
renderSection.renderDataDirty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
@@ -291,7 +327,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
node.value.retreivedMissingSectionsForRetreival = false;
|
node.value.queuedMissingSectionsForRetrieval = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,28 +395,35 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
//=========================//
|
//=========================//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
// also handles disabling beacons
|
|
||||||
|
|
||||||
for (QuadNode<LodRenderSection> node : this.tickNodeHolder.getDisableNodes())
|
for (QuadNode<LodRenderSection> node : this.tickNodeHolder.getDisableNodes())
|
||||||
{
|
{
|
||||||
if (node == null || node.value == null) { continue; }
|
if (node == null || node.value == null) { continue; }
|
||||||
|
|
||||||
node.value.setRenderingEnabled(false);
|
node.value.setRenderingEnabled(false);
|
||||||
node.value.tryDisableBeacons();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (QuadNode<LodRenderSection> node : this.tickNodeHolder.getEnableDeleteChildrenNodes())
|
for (QuadNode<LodRenderSection> node : this.tickNodeHolder.getEnableDeleteChildrenNodes())
|
||||||
{
|
{
|
||||||
if (node == null || node.value == null) { continue; }
|
if (node == null
|
||||||
|
|| node.value == null
|
||||||
node.deleteAllChildren((childRenderSection) ->
|
// only clear the children if there are children to clear
|
||||||
|
|| node.getDirectChildCount() == 0)
|
||||||
{
|
{
|
||||||
if (childRenderSection != null)
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// run this on the render thread to hopefully prevent
|
||||||
|
// closing render data while rendering is happening
|
||||||
|
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodQuadTree delayed child cleanup", () ->
|
||||||
|
{
|
||||||
|
node.deleteAllChildren((childRenderSection) ->
|
||||||
{
|
{
|
||||||
childRenderSection.setRenderingEnabled(false);
|
if (childRenderSection != null)
|
||||||
childRenderSection.tryDisableBeacons();
|
{
|
||||||
childRenderSection.close();
|
childRenderSection.setRenderingEnabled(false);
|
||||||
}
|
childRenderSection.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,21 +436,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
//=================//
|
//=================//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
// must be handled after beacon disabling
|
this.tryRefreshRenderingBeaconsAsync(playerPos);
|
||||||
// otherwise the beacons will be missing
|
|
||||||
|
|
||||||
for (QuadNode<LodRenderSection> node : this.tickNodeHolder.getEnabledNodes())
|
|
||||||
{
|
|
||||||
if (node == null || node.value == null) { continue; }
|
|
||||||
|
|
||||||
node.value.tryEnableBeacons();
|
|
||||||
}
|
|
||||||
for (QuadNode<LodRenderSection> node : this.tickNodeHolder.getEnableDeleteChildrenNodes())
|
|
||||||
{
|
|
||||||
if (node == null || node.value == null) { continue; }
|
|
||||||
|
|
||||||
node.value.tryEnableBeacons();
|
|
||||||
}
|
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
@@ -441,9 +470,9 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
|
|
||||||
// since this section wants to render
|
// since this section wants to render
|
||||||
// check if it needs any generation to do so
|
// check if it needs any generation to do so
|
||||||
if (!node.value.retreivedMissingSectionsForRetreival)
|
if (!node.value.queuedMissingSectionsForRetrieval)
|
||||||
{
|
{
|
||||||
node.value.retreivedMissingSectionsForRetreival = true;
|
node.value.queuedMissingSectionsForRetrieval = true;
|
||||||
this.tryQueuePosForRetrieval(node.value.pos); // can be quite slow
|
this.tryQueuePosForRetrieval(node.value.pos); // can be quite slow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -468,9 +497,10 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
//=========================//
|
//=========================//
|
||||||
// tick - recursive update //
|
// tick - recursive update //
|
||||||
//=========================//
|
//=========================//
|
||||||
///region
|
//region
|
||||||
|
|
||||||
private void recursivelyUpdateRenderSectionNode(
|
/** @return true if the node at this position has uploaded its render data */
|
||||||
|
private boolean recursivelyUpdateRenderSectionNode(
|
||||||
@NotNull DhBlockPos2D playerPos,
|
@NotNull DhBlockPos2D playerPos,
|
||||||
@NotNull QuadNode<LodRenderSection> rootNode,
|
@NotNull QuadNode<LodRenderSection> rootNode,
|
||||||
@Nullable QuadNode<LodRenderSection> parentNode,
|
@Nullable QuadNode<LodRenderSection> parentNode,
|
||||||
@@ -482,19 +512,22 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
// get/create the node //
|
// get/create the node //
|
||||||
// and render section //
|
// and render section //
|
||||||
//=====================//
|
//=====================//
|
||||||
///region
|
//region
|
||||||
|
|
||||||
// create the node
|
quadNode = this.tryAddNodeToTree(rootNode, quadNode, sectionPos);
|
||||||
if (quadNode == null)
|
|
||||||
{
|
|
||||||
rootNode.setValue(sectionPos, new LodRenderSection(sectionPos, this, this.level, this.fullDataSourceProvider));
|
// Skip sections that are out-of-bounds.
|
||||||
quadNode = rootNode.getNode(sectionPos);
|
// If not done some sections will appear and/or generate
|
||||||
}
|
// outside the desired render distance
|
||||||
if (quadNode == null)
|
if (!this.isSectionPosInBounds(quadNode.sectionPos))
|
||||||
{
|
{
|
||||||
LodUtil.assertNotReach("Unable to add node with pos ["+DhSectionPos.toString(sectionPos)+"] to tree root ["+rootNode+"].");
|
this.tickNodeHolder.addDisableNode(quadNode);
|
||||||
|
this.recursivelyDisableChildNodes(quadNode);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// make sure the render section is created (shouldn't be necessary, but just in case)
|
// make sure the render section is created (shouldn't be necessary, but just in case)
|
||||||
LodRenderSection renderSection = quadNode.value;
|
LodRenderSection renderSection = quadNode.value;
|
||||||
if (renderSection == null)
|
if (renderSection == null)
|
||||||
@@ -503,7 +536,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
quadNode.setValue(sectionPos, renderSection);
|
quadNode.setValue(sectionPos, renderSection);
|
||||||
}
|
}
|
||||||
|
|
||||||
///endregion
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -511,7 +544,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
// handle enabling, loading, //
|
// handle enabling, loading, //
|
||||||
// and disabling render sections //
|
// and disabling render sections //
|
||||||
//===============================//
|
//===============================//
|
||||||
///region
|
//region
|
||||||
|
|
||||||
// load every node for rendering
|
// load every node for rendering
|
||||||
if (!renderSection.gpuUploadInProgress()
|
if (!renderSection.gpuUploadInProgress()
|
||||||
@@ -527,94 +560,153 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
|
|
||||||
if (DhSectionPos.getDetailLevel(quadNode.sectionPos) > expectedDetailLevel)
|
if (DhSectionPos.getDetailLevel(quadNode.sectionPos) > expectedDetailLevel)
|
||||||
{
|
{
|
||||||
this.onDetailLevelTooHigh(playerPos, rootNode, quadNode);
|
return this.onDetailLevelTooLow(playerPos, rootNode, quadNode);
|
||||||
}
|
}
|
||||||
// the (expectedDetailLevel-1) fixes corners being cut out due to distance calculations using the LOD center
|
// the (expectedDetailLevel-1) fixes corners being cut out due to distance calculations using the LOD center
|
||||||
else if (DhSectionPos.getDetailLevel(quadNode.sectionPos) == expectedDetailLevel
|
else if (DhSectionPos.getDetailLevel(quadNode.sectionPos) == expectedDetailLevel
|
||||||
|| DhSectionPos.getDetailLevel(quadNode.sectionPos) == expectedDetailLevel - 1)
|
|| DhSectionPos.getDetailLevel(quadNode.sectionPos) == expectedDetailLevel - 1)
|
||||||
{
|
{
|
||||||
this.onDesiredDetailLevel(quadNode, parentNode);
|
return this.onDesiredDetailLevel(quadNode, parentNode);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new IllegalStateException("LodQuadTree shouldn't be updating renderSections below the expected detail level: [" + expectedDetailLevel + "].");
|
throw new IllegalStateException("LodQuadTree shouldn't be updating renderSections below the expected detail level: [" + expectedDetailLevel + "].");
|
||||||
}
|
}
|
||||||
|
|
||||||
///endregion
|
//endregion
|
||||||
}
|
}
|
||||||
private void onDetailLevelTooHigh(
|
/** @return true if the node at this position has uploaded its render data */
|
||||||
|
private boolean onDetailLevelTooLow(
|
||||||
@NotNull DhBlockPos2D playerPos,
|
@NotNull DhBlockPos2D playerPos,
|
||||||
@NotNull QuadNode<LodRenderSection> rootNode, @NotNull QuadNode<LodRenderSection> quadNode)
|
@NotNull QuadNode<LodRenderSection> rootNode,
|
||||||
|
@NotNull QuadNode<LodRenderSection> quadNode)
|
||||||
{
|
{
|
||||||
// recursively update each child node
|
// recursively update each child node
|
||||||
boolean allChildNodesCanRender = true;
|
int childNodeRenderCount = 0;
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i);
|
|
||||||
long childPos = DhSectionPos.getChildByIndex(quadNode.sectionPos, i);
|
long childPos = DhSectionPos.getChildByIndex(quadNode.sectionPos, i);
|
||||||
this.recursivelyUpdateRenderSectionNode(
|
QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i);
|
||||||
playerPos,
|
|
||||||
rootNode, quadNode, childNode, childPos);
|
|
||||||
childNode = quadNode.getChildByIndex(i); // needs to be gotten again in case a new node was added to the tree (this will often happen when moving into new areas where the children were deleted)
|
|
||||||
|
|
||||||
// nodes shouldn't be null, but just in case
|
boolean childCanRender = this.recursivelyUpdateRenderSectionNode(
|
||||||
if (childNode != null
|
playerPos,
|
||||||
&& childNode.value != null
|
rootNode, quadNode, childNode, childPos);
|
||||||
&& !childNode.value.gpuUploadComplete())
|
if (childCanRender)
|
||||||
{
|
{
|
||||||
// the node is present but not uploaded yet
|
// node can be rendered
|
||||||
allChildNodesCanRender = false;
|
childNodeRenderCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isRootNode = (quadNode == rootNode);
|
||||||
if (allChildNodesCanRender)
|
if (isRootNode)
|
||||||
{
|
{
|
||||||
// all child nodes can render, this node isn't needed
|
// Never render the root node.
|
||||||
|
// This is done to prevent flashing when moving across root node
|
||||||
|
// boundaries.
|
||||||
|
// Otherwise, when moving, new empty nodes will be added at the edge of the tree
|
||||||
|
// which will require the root node to render to cover the "empty" area.
|
||||||
this.tickNodeHolder.addDisableNode(quadNode);
|
this.tickNodeHolder.addDisableNode(quadNode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (childNodeRenderCount >= 4)
|
||||||
|
{
|
||||||
|
this.tickNodeHolder.addDisableNode(quadNode);
|
||||||
|
|
||||||
|
// all children can render,
|
||||||
|
// the area will be filled when rendering
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// not all child positions are loaded yet, this one should be rendered instead
|
boolean nodeCanRender = quadNode.value != null
|
||||||
this.tickNodeHolder.addEnableNode(quadNode);
|
&& quadNode.value.canRender();
|
||||||
|
if (nodeCanRender)
|
||||||
|
{
|
||||||
|
// not all child positions are loaded yet, this one should be rendered instead
|
||||||
|
this.tickNodeHolder.addEnableNode(quadNode);
|
||||||
|
this.recursivelyDisableChildNodes(quadNode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.tickNodeHolder.addDisableNode(quadNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodeCanRender;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void onDesiredDetailLevel(
|
|
||||||
@NotNull QuadNode<LodRenderSection> quadNode, @Nullable QuadNode<LodRenderSection> parentNode)
|
/** @return true if the node at this position has uploaded its render data */
|
||||||
|
private boolean onDesiredDetailLevel(
|
||||||
|
@NotNull QuadNode<LodRenderSection> quadNode,
|
||||||
|
@Nullable QuadNode<LodRenderSection> parentNode)
|
||||||
{
|
{
|
||||||
boolean allAdjNodesCanRender = true;
|
// Skip sections that are out-of-bounds.
|
||||||
|
// If not done some sections will appear and/or generate
|
||||||
// if the parent node is null, that means we're at the root node
|
// outside the desired render distance
|
||||||
// and we should always render
|
if (!this.isSectionPosInBounds(quadNode.sectionPos))
|
||||||
if (parentNode != null)
|
|
||||||
{
|
{
|
||||||
// check if all adjacent nodes are ready to render
|
return true;
|
||||||
// this check is done to prevent some overlapping due to the parent node
|
}
|
||||||
// still being active
|
|
||||||
for (int i = 0; i < 4; i++)
|
if (quadNode.value != null
|
||||||
|
&& quadNode.value.canRender())
|
||||||
|
{
|
||||||
|
if (!this.tickNodeHolder.getEnabledNodes().contains(parentNode))
|
||||||
{
|
{
|
||||||
QuadNode<LodRenderSection> adjNode = parentNode.getChildByIndex(i);
|
this.tickNodeHolder.addEnableDeleteChildrenNode(quadNode);
|
||||||
// nodes shouldn't be null, but just in case there's an issue
|
return true;
|
||||||
if (adjNode != null
|
}
|
||||||
&& adjNode.value != null
|
else
|
||||||
&& !adjNode.value.gpuUploadComplete())
|
{
|
||||||
{
|
this.tickNodeHolder.addDisableNode(quadNode);
|
||||||
// the node is present but not uploaded yet
|
return false;
|
||||||
allAdjNodesCanRender = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (allAdjNodesCanRender
|
|
||||||
&& quadNode.value != null
|
|
||||||
&& quadNode.value.gpuUploadComplete())
|
|
||||||
{
|
{
|
||||||
this.tickNodeHolder.addEnableDeleteChildrenNode(quadNode);
|
this.tickNodeHolder.addDisableNode(quadNode);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///endregion
|
@NotNull
|
||||||
|
private QuadNode<LodRenderSection> tryAddNodeToTree(
|
||||||
|
@NotNull QuadNode<LodRenderSection> rootNode,
|
||||||
|
@Nullable QuadNode<LodRenderSection> quadNode,
|
||||||
|
long sectionPos // section pos is needed here since the quad node may be null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// create the node
|
||||||
|
if (quadNode == null)
|
||||||
|
{
|
||||||
|
rootNode.setValue(sectionPos, new LodRenderSection(sectionPos, this, this.level, this.fullDataSourceProvider));
|
||||||
|
quadNode = rootNode.getNode(sectionPos);
|
||||||
|
}
|
||||||
|
if (quadNode == null)
|
||||||
|
{
|
||||||
|
LodUtil.assertNotReach("Unable to add node with pos ["+DhSectionPos.toString(sectionPos)+"] to tree root ["+rootNode+"].");
|
||||||
|
}
|
||||||
|
|
||||||
|
return quadNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recursivelyDisableChildNodes(@NotNull QuadNode<LodRenderSection> quadNode)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i);
|
||||||
|
this.tickNodeHolder.removeEnableAndDisableNode(childNode);
|
||||||
|
|
||||||
|
if (childNode != null)
|
||||||
|
{
|
||||||
|
this.recursivelyDisableChildNodes(childNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
//=====================//
|
//=====================//
|
||||||
// tick - work queuing //
|
// tick - work queuing //
|
||||||
@@ -860,6 +952,133 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=================//
|
||||||
|
// beacon handling //
|
||||||
|
//=================//
|
||||||
|
//region beacon handling
|
||||||
|
|
||||||
|
/** gets the active beacon list and stops/starts beacon rendering as necessary */
|
||||||
|
private void tryRefreshRenderingBeaconsAsync(DhBlockPos2D playerPos)
|
||||||
|
{
|
||||||
|
// do nothing if beacon rendering or repos are unavailable
|
||||||
|
if (this.beaconBeamRepo == null
|
||||||
|
|| this.beaconRenderHandler == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractExecutorService executor = ThreadPoolUtil.getFileHandlerExecutor();
|
||||||
|
if (executor == null)
|
||||||
|
{
|
||||||
|
// shouldn't normally happen, but just in case
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.beaconUpdateFuture.isDone())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||||
|
this.beaconUpdateFuture = future;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
executor.execute(() ->
|
||||||
|
{
|
||||||
|
this.refreshRenderingBeacons(playerPos);
|
||||||
|
|
||||||
|
try { Thread.sleep(2_000); } catch (InterruptedException ignore) { }
|
||||||
|
future.complete(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (RejectedExecutionException e)
|
||||||
|
{
|
||||||
|
// the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back
|
||||||
|
future.completeExceptionally(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void refreshRenderingBeacons(DhBlockPos2D playerPos)
|
||||||
|
{
|
||||||
|
if (this.beaconBeamRepo == null
|
||||||
|
|| this.beaconRenderHandler == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Synchronized to prevent two threads for accessing the array at once
|
||||||
|
synchronized (this.beaconList)
|
||||||
|
{
|
||||||
|
// get beacons //
|
||||||
|
|
||||||
|
int blockDistanceRadius = (this.blockRenderDistanceDiameter / 2);
|
||||||
|
int minBlockPosX = playerPos.x - blockDistanceRadius;
|
||||||
|
int minBlockPosZ = playerPos.z - blockDistanceRadius;
|
||||||
|
int maxBlockPosX = playerPos.x + blockDistanceRadius;
|
||||||
|
int maxBlockPosZ = playerPos.z + blockDistanceRadius;
|
||||||
|
|
||||||
|
ArrayList<BeaconBeamDTO> dbBeacons = this.beaconBeamRepo.getAllBeamsInBlockPosRange(
|
||||||
|
minBlockPosX, maxBlockPosX,
|
||||||
|
minBlockPosZ, maxBlockPosZ
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// convert DB beacons //
|
||||||
|
|
||||||
|
ArrayList<BeaconRenderHandler.BeaconBeamWithWidth> newBeaconList = new ArrayList<>(this.beaconList.size());
|
||||||
|
for (BeaconBeamDTO beaconBeam : dbBeacons)
|
||||||
|
{
|
||||||
|
byte beaconDetailLevel = this.calcExpectedDetailLevel(playerPos, beaconBeam.blockPos.getX(), beaconBeam.blockPos.getZ());
|
||||||
|
newBeaconList.add(new BeaconRenderHandler.BeaconBeamWithWidth(beaconBeam, beaconDetailLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
boolean replaceBeacons = false;
|
||||||
|
if (this.beaconList.size() != newBeaconList.size())
|
||||||
|
{
|
||||||
|
// lists are different sizes
|
||||||
|
replaceBeacons = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// sort the beacons so they can be compared
|
||||||
|
this.beaconList.sort(BeaconRenderHandler.NegativeInfiniteBlockPosComparator.INSTANCE);
|
||||||
|
newBeaconList.sort(BeaconRenderHandler.NegativeInfiniteBlockPosComparator.INSTANCE);
|
||||||
|
|
||||||
|
for (int i = 0; i < this.beaconList.size(); i++)
|
||||||
|
{
|
||||||
|
BeaconRenderHandler.BeaconBeamWithWidth oldBeam = this.beaconList.get(i);
|
||||||
|
BeaconRenderHandler.BeaconBeamWithWidth newBeam = newBeaconList.get(i);
|
||||||
|
if (!oldBeam.equals(newBeam))
|
||||||
|
{
|
||||||
|
replaceBeacons = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// only replace the beacons if something changed
|
||||||
|
// this is done to prevent constantly re-uploading the render data
|
||||||
|
if (replaceBeacons)
|
||||||
|
{
|
||||||
|
this.beaconList.clear();
|
||||||
|
this.beaconList.addAll(newBeaconList);
|
||||||
|
this.beaconRenderHandler.replaceRenderingBeacons(this.beaconList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LOGGER.error("Unexpected issue updating beacons, error: ["+e.getMessage()+"].", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion beacon handling
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//====================//
|
//====================//
|
||||||
// detail level logic //
|
// detail level logic //
|
||||||
//====================//
|
//====================//
|
||||||
@@ -873,9 +1092,11 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
* @return detail level of this section pos
|
* @return detail level of this section pos
|
||||||
*/
|
*/
|
||||||
public byte calcExpectedDetailLevel(DhBlockPos2D playerPos, long sectionPos)
|
public byte calcExpectedDetailLevel(DhBlockPos2D playerPos, long sectionPos)
|
||||||
|
{ return this.calcExpectedDetailLevel(playerPos, DhSectionPos.getCenterBlockPosX(sectionPos), DhSectionPos.getCenterBlockPosZ(sectionPos)); }
|
||||||
|
public byte calcExpectedDetailLevel(DhBlockPos2D playerPos, int targetBlockPosX, int targetBlockPosZ)
|
||||||
{
|
{
|
||||||
double blockDistance = playerPos.dist(DhSectionPos.getCenterBlockPosX(sectionPos), DhSectionPos.getCenterBlockPosZ(sectionPos));
|
double blockDistance = playerPos.dist(targetBlockPosX, targetBlockPosZ);
|
||||||
return this.calcDetailLevelFromDistance(blockDistance);
|
return this.calcDetailLevelFromDistance(blockDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateDetailLevelVariables()
|
private void updateDetailLevelVariables()
|
||||||
@@ -990,7 +1211,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
{
|
{
|
||||||
color = Color.ORANGE;
|
color = Color.ORANGE;
|
||||||
}
|
}
|
||||||
else if (!renderSection.gpuUploadComplete())
|
else if (!renderSection.canRender())
|
||||||
{
|
{
|
||||||
// uploaded but the buffer is missing
|
// uploaded but the buffer is missing
|
||||||
color = Color.PINK;
|
color = Color.PINK;
|
||||||
@@ -1032,7 +1253,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
@Override
|
@Override
|
||||||
public void close()
|
public void close()
|
||||||
{
|
{
|
||||||
LOGGER.info("Shutting down LodQuadTree...");
|
//LOGGER.info("Shutting down LodQuadTree...");
|
||||||
|
|
||||||
DEBUG_RENDERER.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus);
|
DEBUG_RENDERER.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus);
|
||||||
Config.Common.WorldGenerator.enableDistantGeneration.removeListener(this);
|
Config.Common.WorldGenerator.enableDistantGeneration.removeListener(this);
|
||||||
@@ -1067,7 +1288,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
LOGGER.info("Finished shutting down LodQuadTree");
|
//LOGGER.info("Finished shutting down LodQuadTree");
|
||||||
}
|
}
|
||||||
|
|
||||||
//endregion base methods
|
//endregion base methods
|
||||||
|
|||||||
+100
-219
@@ -34,11 +34,9 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
|||||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer;
|
import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.BeaconRenderHandler;
|
|
||||||
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
|
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
|
||||||
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodBufferContainer;
|
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodBufferContainer;
|
||||||
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
|
import com.seibel.distanthorizons.core.util.ExceptionUtil;
|
||||||
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.threading.PriorityTaskPicker;
|
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
|
||||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||||
@@ -48,10 +46,8 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
import javax.annotation.WillNotClose;
|
import javax.annotation.WillNotClose;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A render section represents an area that could be rendered.
|
* A render section represents an area that could be rendered.
|
||||||
@@ -67,31 +63,24 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
|
|
||||||
public final long pos;
|
public final long pos;
|
||||||
|
|
||||||
private final IDhClientLevel level;
|
private final IDhClientLevel clientLevel;
|
||||||
private final IClientLevelWrapper levelWrapper;
|
private final IClientLevelWrapper levelWrapper;
|
||||||
@WillNotClose
|
@WillNotClose
|
||||||
private final FullDataSourceProviderV2 fullDataSourceProvider;
|
private final FullDataSourceProviderV2 fullDataSourceProvider;
|
||||||
private final LodQuadTree quadTree;
|
private final LodQuadTree quadTree;
|
||||||
|
|
||||||
/**
|
|
||||||
* contains the list of beacons currently being rendered in this section
|
|
||||||
* if this list is modified the {@link LodRenderSection#beaconRenderHandler} should be updated to match.
|
|
||||||
*/
|
|
||||||
private final ArrayList<BeaconBeamDTO> activeBeaconList = new ArrayList<>();
|
|
||||||
@Nullable
|
|
||||||
public final BeaconRenderHandler beaconRenderHandler;
|
|
||||||
@Nullable
|
|
||||||
public final BeaconBeamRepo beaconBeamRepo;
|
|
||||||
/**
|
|
||||||
* locking is necessary to prevent some weird threading issues
|
|
||||||
* causing beacons to appear/disappear at the wrong times.
|
|
||||||
*/
|
|
||||||
private final ReentrantLock beaconRenderHandlingLock = new ReentrantLock();
|
|
||||||
|
|
||||||
|
|
||||||
private boolean renderingEnabled = false;
|
private boolean renderingEnabled = false;
|
||||||
private boolean beaconsRendering = false;
|
/**
|
||||||
public boolean retreivedMissingSectionsForRetreival = false;
|
* Used when a node goes out of render distance
|
||||||
|
* but isn't removed from the underlying quad tree structure. <br><br>
|
||||||
|
*
|
||||||
|
* In those cases we should act as if the node was removed
|
||||||
|
* for cached render data caching purposes, but not
|
||||||
|
* for re-creating missing nodes.
|
||||||
|
*/
|
||||||
|
public boolean renderDataDirty = false;
|
||||||
|
public boolean queuedMissingSectionsForRetrieval = false;
|
||||||
|
|
||||||
/** this reference is necessary so we can determine what VBO to render */
|
/** this reference is necessary so we can determine what VBO to render */
|
||||||
public LodBufferContainer renderBufferContainer;
|
public LodBufferContainer renderBufferContainer;
|
||||||
@@ -109,13 +98,6 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
*/
|
*/
|
||||||
private Runnable getAndBuildRenderDataRunnable = null;
|
private Runnable getAndBuildRenderDataRunnable = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents just uploading the {@link LodQuadBuilder} to the GPU. <br>
|
|
||||||
* Separate from {@link LodRenderSection#getAndBuildRenderDataFutureRef} because they run on
|
|
||||||
* different threads (buffer uploading is on the MC render thread) and need to be canceled separately.
|
|
||||||
*/
|
|
||||||
private final AtomicReference<CompletableFuture<LodBufferContainer>> bufferUploadFutureRef = new AtomicReference<>(null);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=============//
|
//=============//
|
||||||
@@ -126,17 +108,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
public LodRenderSection(
|
public LodRenderSection(
|
||||||
long pos,
|
long pos,
|
||||||
LodQuadTree quadTree,
|
LodQuadTree quadTree,
|
||||||
IDhClientLevel level, FullDataSourceProviderV2 fullDataSourceProvider)
|
IDhClientLevel clientLevel, FullDataSourceProviderV2 fullDataSourceProvider)
|
||||||
{
|
{
|
||||||
this.pos = pos;
|
this.pos = pos;
|
||||||
this.quadTree = quadTree;
|
this.quadTree = quadTree;
|
||||||
this.level = level;
|
this.clientLevel = clientLevel;
|
||||||
this.levelWrapper = level.getClientLevelWrapper();
|
this.levelWrapper = clientLevel.getClientLevelWrapper();
|
||||||
this.fullDataSourceProvider = fullDataSourceProvider;
|
this.fullDataSourceProvider = fullDataSourceProvider;
|
||||||
|
|
||||||
this.beaconRenderHandler = this.quadTree.beaconRenderHandler;
|
|
||||||
this.beaconBeamRepo = this.level.getBeaconBeamRepo();
|
|
||||||
|
|
||||||
DEBUG_RENDERER.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus);
|
DEBUG_RENDERER.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,6 +155,8 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// shouldn't happen since this method is synchronized, but just in case
|
||||||
|
// make sure we only ever start one upload task
|
||||||
if (!this.getAndBuildRenderDataFutureRef.compareAndSet(null, future))
|
if (!this.getAndBuildRenderDataFutureRef.compareAndSet(null, future))
|
||||||
{
|
{
|
||||||
CompletableFuture<Void> oldFuture = this.getAndBuildRenderDataFutureRef.get();
|
CompletableFuture<Void> oldFuture = this.getAndBuildRenderDataFutureRef.get();
|
||||||
@@ -188,8 +169,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.refreshActiveBeaconList();
|
// build LOD data on a DH thread
|
||||||
|
|
||||||
LodQuadBuilder lodQuadBuilder = this.getAndBuildRenderData();
|
LodQuadBuilder lodQuadBuilder = this.getAndBuildRenderData();
|
||||||
if (lodQuadBuilder == null)
|
if (lodQuadBuilder == null)
|
||||||
{
|
{
|
||||||
@@ -197,7 +177,8 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.uploadToGpuAsync(lodQuadBuilder)
|
// uploading will primarily happen on the render thread
|
||||||
|
this.uploadToGpuAsync(future, lodQuadBuilder)
|
||||||
.thenRun(() ->
|
.thenRun(() ->
|
||||||
{
|
{
|
||||||
// the future is passed in separately (IE not using the local var) to prevent any possible race condition null pointers
|
// the future is passed in separately (IE not using the local var) to prevent any possible race condition null pointers
|
||||||
@@ -207,7 +188,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
LOGGER.error("Unexpected issue creating render data for pos: ["+DhSectionPos.toString(this.pos)+"], error: ["+e.getMessage()+"].", e);
|
LOGGER.error("Unexpected issue creating render data for pos: ["+DhSectionPos.toString(this.pos)+"], error: ["+e.getMessage()+"].", e);
|
||||||
future.complete(null);
|
future.completeExceptionally(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
executor.execute(this.getAndBuildRenderDataRunnable);
|
executor.execute(this.getAndBuildRenderDataRunnable);
|
||||||
@@ -222,6 +203,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//=======================//
|
||||||
|
// Get LOD ID data //
|
||||||
|
// and build render data //
|
||||||
|
//=======================//
|
||||||
|
//region
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private synchronized LodQuadBuilder getAndBuildRenderData()
|
private synchronized LodQuadBuilder getAndBuildRenderData()
|
||||||
{
|
{
|
||||||
@@ -235,7 +224,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
|
|
||||||
|
|
||||||
boolean enableTransparency = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled;
|
boolean enableTransparency = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled;
|
||||||
LodQuadBuilder lodQuadBuilder = new LodQuadBuilder(enableTransparency, this.level.getClientLevelWrapper());
|
LodQuadBuilder lodQuadBuilder = new LodQuadBuilder(enableTransparency, this.clientLevel.getClientLevelWrapper());
|
||||||
|
|
||||||
|
|
||||||
// get the adjacent positions
|
// get the adjacent positions
|
||||||
@@ -258,7 +247,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
|
|
||||||
// the render sources are only needed by this synchronous method,
|
// the render sources are only needed by this synchronous method,
|
||||||
// then they can be closed
|
// then they can be closed
|
||||||
ColumnRenderBufferBuilder.makeLodRenderData(lodQuadBuilder, thisRenderSource, this.level, adjacentRenderSections, adjIsSameDetailLevel);
|
ColumnRenderBufferBuilder.makeLodRenderData(lodQuadBuilder, thisRenderSource, this.clientLevel, adjacentRenderSections, adjIsSameDetailLevel);
|
||||||
return lodQuadBuilder;
|
return lodQuadBuilder;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -308,52 +297,63 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
detailLevel += DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
|
detailLevel += DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
|
||||||
return detailLevel == DhSectionPos.getDetailLevel(this.pos);
|
return detailLevel == DhSectionPos.getDetailLevel(this.pos);
|
||||||
}
|
}
|
||||||
private synchronized CompletableFuture<LodBufferContainer> uploadToGpuAsync(LodQuadBuilder lodQuadBuilder)
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
private synchronized CompletableFuture<LodBufferContainer> uploadToGpuAsync(
|
||||||
|
CompletableFuture<Void> parentFuture,
|
||||||
|
LodQuadBuilder lodQuadBuilder)
|
||||||
{
|
{
|
||||||
CompletableFuture<LodBufferContainer> oldFuture = this.bufferUploadFutureRef.getAndSet(null);
|
CompletableFuture<LodBufferContainer> uploadFuture = LodBufferContainer.tryMakeAndUploadBuffersAsync(this.pos, this.clientLevel, lodQuadBuilder);
|
||||||
if (oldFuture != null)
|
uploadFuture.whenComplete((bufferContainer, e) ->
|
||||||
{
|
{
|
||||||
// canceling the previous future
|
try
|
||||||
// prevents the CPU from working on something that won't be used
|
|
||||||
oldFuture.cancel(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
CompletableFuture<LodBufferContainer> future = ColumnRenderBufferBuilder.uploadBuffersAsync(this.level, this.pos, lodQuadBuilder);
|
|
||||||
future.handle((lodBufferContainer, throwable) ->
|
|
||||||
{
|
|
||||||
if (!this.bufferUploadFutureRef.compareAndSet(future, null)
|
|
||||||
// if the old future is canceled then the future ref will be different and that's expected
|
|
||||||
&& !future.isCancelled()
|
|
||||||
// if the old future is already done, then we don't care about the ref being swapped
|
|
||||||
&& !future.isDone())
|
|
||||||
{
|
{
|
||||||
LOGGER.warn("Buffer upload future ref changed for pos: ["+DhSectionPos.toString(this.pos)+"].");
|
// handle errors and early shutdown
|
||||||
|
if (e != null)
|
||||||
|
{
|
||||||
|
if (!ExceptionUtil.isShutdownException(e))
|
||||||
|
{
|
||||||
|
LOGGER.error("Unexpected issue uploading buffers for pos: [" + DhSectionPos.toString(this.pos) + "], error: [" + e.getMessage() + "].", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bufferContainer != null)
|
||||||
|
{
|
||||||
|
// shouldn't happen, but just in case
|
||||||
|
bufferContainer.close();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// close the old container
|
||||||
|
LodBufferContainer oldContainer = this.renderBufferContainer;
|
||||||
|
this.renderBufferContainer = bufferContainer.buffersUploaded ? bufferContainer : null;
|
||||||
|
if (oldContainer != null)
|
||||||
|
{
|
||||||
|
oldContainer.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// upload complete
|
||||||
|
this.renderDataDirty = false;
|
||||||
|
|
||||||
|
|
||||||
|
if (parentFuture.isCancelled())
|
||||||
|
{
|
||||||
|
// if the parent future was canceled that likely means
|
||||||
|
// this LodRenderSection was closed before this point,
|
||||||
|
// meaning this buffer will become homeless,
|
||||||
|
// so we need to clean it up here
|
||||||
|
bufferContainer.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception finishEx)
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
future.thenAccept((LodBufferContainer buffer) ->
|
|
||||||
{
|
|
||||||
// needed to clean up the old data
|
|
||||||
LodBufferContainer previousContainer = this.renderBufferContainer;
|
|
||||||
|
|
||||||
// upload complete
|
|
||||||
this.renderBufferContainer = buffer.buffersUploaded ? buffer : null;
|
|
||||||
|
|
||||||
if (previousContainer != null)
|
|
||||||
{
|
{
|
||||||
previousContainer.close();
|
LOGGER.error("Unexpected buffer finish exception: ["+finishEx.getMessage()+"]", finishEx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return uploadFuture;
|
||||||
if (!this.bufferUploadFutureRef.compareAndSet(null, future))
|
|
||||||
{
|
|
||||||
LodUtil.assertNotReach("Buffer upload future ref couldn't be set due to concurrency error, pos: ["+DhSectionPos.toString(this.pos)+"].");
|
|
||||||
}
|
|
||||||
|
|
||||||
return future;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//endregion render data uploading
|
//endregion render data uploading
|
||||||
@@ -365,7 +365,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
//=================//
|
//=================//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
public boolean gpuUploadComplete() { return this.renderBufferContainer != null; }
|
public boolean canRender() { return this.renderBufferContainer != null; }
|
||||||
|
public boolean gpuUploadComplete()
|
||||||
|
{
|
||||||
|
return this.renderBufferContainer != null
|
||||||
|
// render dirty is here so we can trigger new GPU uploads
|
||||||
|
// even if the render data is present
|
||||||
|
&& !this.renderDataDirty;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean getRenderingEnabled() { return this.renderingEnabled; }
|
public boolean getRenderingEnabled() { return this.renderingEnabled; }
|
||||||
public void setRenderingEnabled(boolean enabled) { this.renderingEnabled = enabled;}
|
public void setRenderingEnabled(boolean enabled) { this.renderingEnabled = enabled;}
|
||||||
@@ -376,130 +383,6 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=================//
|
|
||||||
// beacon handling //
|
|
||||||
//=================//
|
|
||||||
//region beacon handling
|
|
||||||
|
|
||||||
/** gets the active beacon list and stops/starts beacon rendering as necessary */
|
|
||||||
private void refreshActiveBeaconList()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.beaconRenderHandlingLock.lock();
|
|
||||||
|
|
||||||
// do nothing if beacon rendering or repos are unavailable
|
|
||||||
if (this.beaconBeamRepo == null
|
|
||||||
|| this.beaconRenderHandler == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Synchronized to prevent two threads for accessing the array at once
|
|
||||||
synchronized (this.activeBeaconList)
|
|
||||||
{
|
|
||||||
ArrayList<BeaconBeamDTO> activeBeacons = this.beaconBeamRepo.getAllBeamsForPos(this.pos);
|
|
||||||
|
|
||||||
// swap old and new active beacon list
|
|
||||||
this.activeBeaconList.clear();
|
|
||||||
this.activeBeaconList.addAll(activeBeacons);
|
|
||||||
|
|
||||||
// if the beacons are currently rendering,
|
|
||||||
// re-create them so we can see any potential changes
|
|
||||||
if (this.beaconsRendering)
|
|
||||||
{
|
|
||||||
this.tryDisableBeacons();
|
|
||||||
this.tryEnableBeacons();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
this.beaconRenderHandlingLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void tryDisableBeacons()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.beaconRenderHandlingLock.lock();
|
|
||||||
|
|
||||||
|
|
||||||
// do nothing if beacon rendering is unavailable
|
|
||||||
if (this.beaconRenderHandler == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.beaconsRendering)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.beaconsRendering = false;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus.get())
|
|
||||||
{
|
|
||||||
// show that this position has just been disabled
|
|
||||||
DEBUG_RENDERER.makeParticle(
|
|
||||||
new AbstractDebugWireframeRenderer.BoxParticle(
|
|
||||||
new AbstractDebugWireframeRenderer.Box(this.pos, 128f, 156f, 0.09f, Color.CYAN.darker()),
|
|
||||||
0.2, 32f
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized (this.activeBeaconList)
|
|
||||||
{
|
|
||||||
this.beaconRenderHandler.stopRenderingBeaconsInRange(this.pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
this.beaconRenderHandlingLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void tryEnableBeacons()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.beaconRenderHandlingLock.lock();
|
|
||||||
|
|
||||||
|
|
||||||
// do nothing if beacon rendering is unavailable
|
|
||||||
if (this.beaconRenderHandler == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.beaconsRendering)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.beaconsRendering = true;
|
|
||||||
|
|
||||||
|
|
||||||
synchronized (this.activeBeaconList)
|
|
||||||
{
|
|
||||||
byte absoluteDetailLevel = (byte)(DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
|
|
||||||
this.beaconRenderHandler.startRenderingBeacons(this.activeBeaconList, absoluteDetailLevel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
this.beaconRenderHandlingLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//endregion beacon handling
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//==============//
|
//==============//
|
||||||
// base methods //
|
// base methods //
|
||||||
//==============//
|
//==============//
|
||||||
@@ -518,14 +401,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
{
|
{
|
||||||
color = Color.yellow;
|
color = Color.yellow;
|
||||||
}
|
}
|
||||||
else if (this.gpuUploadComplete())
|
else if (this.canRender())
|
||||||
{
|
{
|
||||||
//color = Color.cyan;
|
//color = Color.cyan;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int levelMinY = this.level.getLevelWrapper().getMinHeight();
|
int levelMinY = this.clientLevel.getLevelWrapper().getMinHeight();
|
||||||
int levelMaxY = this.level.getLevelWrapper().getMaxHeight();
|
int levelMaxY = this.clientLevel.getLevelWrapper().getMaxHeight();
|
||||||
|
|
||||||
// show the wireframe a bit lower than world max height,
|
// show the wireframe a bit lower than world max height,
|
||||||
// since most worlds don't render all the way up to the max height
|
// since most worlds don't render all the way up to the max height
|
||||||
@@ -562,14 +445,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.tryDisableBeacons();
|
// render loading is no longer needed
|
||||||
|
|
||||||
if (this.renderBufferContainer != null)
|
|
||||||
{
|
|
||||||
this.renderBufferContainer.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// removes any in-progress futures since they aren't needed any more
|
|
||||||
CompletableFuture<Void> buildFuture = this.getAndBuildRenderDataFutureRef.get();
|
CompletableFuture<Void> buildFuture = this.getAndBuildRenderDataFutureRef.get();
|
||||||
if (buildFuture != null)
|
if (buildFuture != null)
|
||||||
{
|
{
|
||||||
@@ -585,12 +461,17 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
renderLoaderExecutor.remove(runnable);
|
renderLoaderExecutor.remove(runnable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cancel the future after removing the runnable
|
||||||
|
// to make sure the runnable is properly removed
|
||||||
|
buildFuture.cancel(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
CompletableFuture<LodBufferContainer> uploadFuture = this.bufferUploadFutureRef.get();
|
|
||||||
if (uploadFuture != null)
|
this.setRenderingEnabled(false);
|
||||||
|
if (this.renderBufferContainer != null)
|
||||||
{
|
{
|
||||||
uploadFuture.cancel(true);
|
this.renderBufferContainer.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-9
@@ -63,21 +63,39 @@ public class QuadTreeTickNodeHolder
|
|||||||
{
|
{
|
||||||
if(this.presentNodes.add(node))
|
if(this.presentNodes.add(node))
|
||||||
{
|
{
|
||||||
// not a big fan of having to check every node to prevent overlaps, but it does work
|
// in James testing as of 4-21-2026
|
||||||
this.nodesToEnable.removeIf((QuadNode<LodRenderSection> checkNode) ->
|
// this should no longer be needed to prevent overlaps,
|
||||||
|
// however I'm keeping it here as a quick fix solution if
|
||||||
|
// the problem comes up again
|
||||||
|
if (false)
|
||||||
{
|
{
|
||||||
boolean contained = DhSectionPos.contains(node.sectionPos, checkNode.sectionPos);
|
// not a big fan of having to check every node to prevent overlaps, but it does work
|
||||||
if (contained)
|
this.nodesToEnable.removeIf((QuadNode<LodRenderSection> checkNode) ->
|
||||||
{
|
{
|
||||||
this.nodesToDisable.add(checkNode);
|
boolean contained = DhSectionPos.contains(node.sectionPos, checkNode.sectionPos);
|
||||||
}
|
if (contained)
|
||||||
|
{
|
||||||
return contained;
|
this.nodesToDisable.add(checkNode);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
return contained;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.nodesToEnable.add(node);
|
this.nodesToEnable.add(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** */
|
||||||
|
public void removeEnableAndDisableNode(QuadNode<LodRenderSection> node)
|
||||||
|
{
|
||||||
|
this.nodesToEnable.remove(node);
|
||||||
|
this.nodesToEnableDeleteChildrenList.remove(node);
|
||||||
|
|
||||||
|
this.presentNodes.add(node); // should already be present, but re-added just in case
|
||||||
|
this.nodesToDisable.add(node); // node shouldn't be rendered since it's being disabled by a parent
|
||||||
|
}
|
||||||
|
|
||||||
public HashSet<QuadNode<LodRenderSection>> getEnabledNodes() { return this.nodesToEnable; }
|
public HashSet<QuadNode<LodRenderSection>> getEnabledNodes() { return this.nodesToEnable; }
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
|
|||||||
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
|
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
|
||||||
import com.seibel.distanthorizons.core.api.internal.SharedApi;
|
import com.seibel.distanthorizons.core.api.internal.SharedApi;
|
||||||
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
|
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
|
||||||
|
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.jar.EPlatform;
|
import com.seibel.distanthorizons.core.jar.EPlatform;
|
||||||
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
||||||
@@ -17,6 +18,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftCli
|
|||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.ILightMapWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.ILightMapWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.AbstractOptifineAccessor;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.AbstractOptifineAccessor;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IOptifineAccessor;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||||
|
|
||||||
@@ -30,6 +32,8 @@ public class RenderParams extends DhApiRenderParam
|
|||||||
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||||
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
|
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
|
||||||
|
|
||||||
|
private static final IOptifineAccessor OPTIFINE_ACCESSOR = ModAccessorInjector.INSTANCE.get(IOptifineAccessor.class);
|
||||||
|
|
||||||
private static final long TIME_FOR_MAC_TO_FINISH_COMPILING_IN_MS = 10_000;
|
private static final long TIME_FOR_MAC_TO_FINISH_COMPILING_IN_MS = 10_000;
|
||||||
private static boolean initialLoadingComplete = false;
|
private static boolean initialLoadingComplete = false;
|
||||||
|
|
||||||
@@ -164,7 +168,7 @@ public class RenderParams extends DhApiRenderParam
|
|||||||
return "No MVM or Proj Matrix Given";
|
return "No MVM or Proj Matrix Given";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AbstractOptifineAccessor.optifinePresent()
|
if (OPTIFINE_ACCESSOR != null
|
||||||
&& MC_RENDER.getTargetFramebuffer() == -1)
|
&& MC_RENDER.getTargetFramebuffer() == -1)
|
||||||
{
|
{
|
||||||
// wait for MC to finish setting up their renderer
|
// wait for MC to finish setting up their renderer
|
||||||
@@ -172,45 +176,6 @@ public class RenderParams extends DhApiRenderParam
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// potential fix for a segfault when
|
|
||||||
// Sodium and DH are running together
|
|
||||||
if (EPlatform.get() == EPlatform.MACOS
|
|
||||||
&& !initialLoadingComplete)
|
|
||||||
{
|
|
||||||
// Once MC starts rendering, wait a few seconds so
|
|
||||||
// MC/Sodium can finish their shader compiling before DH does its own.
|
|
||||||
// This will allow DH to compile its own shaders after Sodium finishes
|
|
||||||
// compiling its own.
|
|
||||||
long nowMs = System.currentTimeMillis();
|
|
||||||
long firstAllowedRenderTimeMs = firstRenderTimeMs + TIME_FOR_MAC_TO_FINISH_COMPILING_IN_MS;
|
|
||||||
if (nowMs < firstAllowedRenderTimeMs)
|
|
||||||
{
|
|
||||||
return "Waiting for initial MC compile...";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// null shouldn't happen, but just in case
|
|
||||||
PriorityTaskPicker.Executor renderLoadExecutor = ThreadPoolUtil.getRenderLoadingExecutor();
|
|
||||||
if (renderLoadExecutor == null)
|
|
||||||
{
|
|
||||||
return "Waiting for DH Threadpool...";
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait for DH to finish loading, by the time that's done
|
|
||||||
// java should have finished all of DH's JIT compiling,
|
|
||||||
// which will hopefully mean less concurrency and thus a lower
|
|
||||||
// chance of breaking
|
|
||||||
// (plus this gives Sodium/vanill a bit longer to finish their setup)
|
|
||||||
int taskCount = renderLoadExecutor.getQueueSize();
|
|
||||||
if (taskCount > 0)
|
|
||||||
{
|
|
||||||
return "Waiting for DH JIT compiling...";
|
|
||||||
}
|
|
||||||
|
|
||||||
initialLoadingComplete = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+298
-36
@@ -2,32 +2,62 @@ package com.seibel.distanthorizons.core.render;
|
|||||||
|
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
|
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
|
import com.seibel.distanthorizons.core.util.ExceptionUtil;
|
||||||
import com.seibel.distanthorizons.core.util.TimerUtil;
|
import com.seibel.distanthorizons.core.util.TimerUtil;
|
||||||
|
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
|
||||||
|
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.LongAdder;
|
||||||
|
|
||||||
public class RenderThreadTaskHandler
|
public class RenderThreadTaskHandler
|
||||||
{
|
{
|
||||||
public static final DhLogger LOGGER = new DhLoggerBuilder()
|
private static final DhLogger LOGGER = new DhLoggerBuilder()
|
||||||
.fileLevelConfig(Config.Common.Logging.logRendererEventToFile)
|
.fileLevelConfig(Config.Common.Logging.logRendererEventToFile)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
private static final ConcurrentLinkedQueue<Runnable> RENDER_THREAD_RUNNABLE_QUEUE = new ConcurrentLinkedQueue<>();
|
private static final DhLogger RATE_LIMITED_LOGGER = new DhLoggerBuilder()
|
||||||
|
.fileLevelConfig(Config.Common.Logging.logRendererEventToFile)
|
||||||
|
.maxCountPerSecond(4)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private static final ConcurrentLinkedQueue<QueuedRunnable> RENDER_THREAD_RUNNABLE_QUEUE = new ConcurrentLinkedQueue<>();
|
||||||
|
|
||||||
|
private static final ConcurrentHashMap<String, RollingAverage> AVERAGE_NANO_RUN_TIME_BY_TASK_NAME = new ConcurrentHashMap<>();
|
||||||
|
private static final LongAdder COMPLETED_TASK_COUNTER = new LongAdder();
|
||||||
|
private static final NumberFormat DECIMAL_NUMBER_FORMAT = NumberFormat.getNumberInstance();
|
||||||
|
private static final NumberFormat INT_NUMBER_FORMAT = NumberFormat.getIntegerInstance();
|
||||||
|
private static final boolean LOG_SLOW_TASKS = false;
|
||||||
|
|
||||||
private static final Timer TIMER = TimerUtil.CreateTimer("Cleanup timer");
|
private static final Timer TIMER = TimerUtil.CreateTimer("Cleanup timer");
|
||||||
private static final long MS_BETWEEN_CLEANUP_TICKS = 1_000L;
|
private static final long MS_BETWEEN_CLEANUP_TICKS = 1_000L;
|
||||||
private static final long MS_BEFORE_RUN_CLEANUP_TIMER = 1_000L;
|
private static final long NANOS_BEFORE_RUN_CLEANUP_TIMER = TimeUnit.NANOSECONDS.convert(1_000L, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
|
||||||
public static final RenderThreadTaskHandler INSTANCE = new RenderThreadTaskHandler();
|
public static final RenderThreadTaskHandler INSTANCE = new RenderThreadTaskHandler();
|
||||||
|
|
||||||
|
|
||||||
private long msSinceGlTasksRun = System.currentTimeMillis();
|
private long nanoSinceTasksRun = System.nanoTime();
|
||||||
|
private final boolean running;
|
||||||
|
|
||||||
|
private Thread renderThread;
|
||||||
|
/**
|
||||||
|
* the currently running {@link QueuedRunnable}
|
||||||
|
* will be null if nothing is running.
|
||||||
|
*/
|
||||||
|
private volatile @Nullable QueuedRunnable currentQueuedRunnable;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -36,7 +66,22 @@ public class RenderThreadTaskHandler
|
|||||||
//=============//
|
//=============//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
private RenderThreadTaskHandler() { TIMER.scheduleAtFixedRate(TimerUtil.createTimerTask(this::manualCleanupTick), MS_BETWEEN_CLEANUP_TICKS, MS_BETWEEN_CLEANUP_TICKS); }
|
private RenderThreadTaskHandler()
|
||||||
|
{
|
||||||
|
// we only want to run this when the client is available
|
||||||
|
IMinecraftSharedWrapper mcShared = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
|
||||||
|
if (!mcShared.isDedicatedServer())
|
||||||
|
{
|
||||||
|
LOGGER.debug("Starting ["+RenderThreadTaskHandler.class.getSimpleName()+"]...");
|
||||||
|
this.running = true;
|
||||||
|
TIMER.scheduleAtFixedRate(TimerUtil.createTimerTask(this::manualCleanupTick), MS_BETWEEN_CLEANUP_TICKS, MS_BETWEEN_CLEANUP_TICKS);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.running = false;
|
||||||
|
LOGGER.debug("Skipping ["+RenderThreadTaskHandler.class.getSimpleName()+"] startup due to running on a dedicated server.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
@@ -47,23 +92,24 @@ public class RenderThreadTaskHandler
|
|||||||
//==============//
|
//==============//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
public void queueRunningOnRenderThread(Runnable renderCall)
|
public void queueRunningOnRenderThread(String name, Runnable renderCall)
|
||||||
{
|
{
|
||||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
// don't queuing tasks if they'll never be run
|
||||||
RENDER_THREAD_RUNNABLE_QUEUE.add(() -> this.createRenderThreadRunnable(renderCall, stackTrace));
|
if (!this.running)
|
||||||
}
|
|
||||||
private void createRenderThreadRunnable(Runnable renderCall, StackTraceElement[] stackTrace)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
renderCall.run();
|
return;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
|
||||||
|
|
||||||
|
// don't get the stacktrace on release to reduce GC pressure
|
||||||
|
StackTraceElement[] stackTrace = null;
|
||||||
|
if (ModInfo.IS_DEV_BUILD)
|
||||||
{
|
{
|
||||||
RuntimeException error = new RuntimeException("Uncaught Exception during GL call execution:", e);
|
stackTrace = Thread.currentThread().getStackTrace();
|
||||||
error.setStackTrace(stackTrace);
|
|
||||||
LOGGER.error("[" + Thread.currentThread().getName() + "] ran into an unexpected error running a GL call, Error: ["+ e.getMessage() +"].", error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueuedRunnable runnable = new QueuedRunnable(name, renderCall, stackTrace);
|
||||||
|
RENDER_THREAD_RUNNABLE_QUEUE.add(runnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
@@ -83,31 +129,72 @@ public class RenderThreadTaskHandler
|
|||||||
{
|
{
|
||||||
IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
|
IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
|
||||||
|
|
||||||
|
// https://fpstoms.com/
|
||||||
int frameLimit = MC_RENDER.getFrameLimit();
|
int frameLimit = MC_RENDER.getFrameLimit();
|
||||||
if (frameLimit <= 1)
|
if (frameLimit <= 1)
|
||||||
{
|
{
|
||||||
frameLimit = 4; // 240 FPS
|
frameLimit = 240;
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://fpstoms.com/
|
|
||||||
int msPerFrame = 1000 / frameLimit;
|
|
||||||
this.runRenderThreadTasks(msPerFrame);
|
|
||||||
}
|
|
||||||
private void runRenderThreadTasks(long msMaxRunTime)
|
|
||||||
{
|
|
||||||
long startTimeMs = System.currentTimeMillis();
|
|
||||||
this.msSinceGlTasksRun = startTimeMs;
|
|
||||||
|
|
||||||
Runnable runnable = RENDER_THREAD_RUNNABLE_QUEUE.poll();
|
int msPerFrame = 1000 / frameLimit;
|
||||||
|
long nanoPerFrame = msPerFrame * 1_000_000L;
|
||||||
|
nanoPerFrame /= 2; // divide the time in half so we can only impact half of the framerate at worst
|
||||||
|
this.runRenderThreadTasks(nanoPerFrame);
|
||||||
|
}
|
||||||
|
private void runRenderThreadTasks(long nanoMaxRunTime)
|
||||||
|
{
|
||||||
|
long loopStartTimeNano = System.nanoTime();
|
||||||
|
this.nanoSinceTasksRun = loopStartTimeNano;
|
||||||
|
|
||||||
|
|
||||||
|
if (this.renderThread == null)
|
||||||
|
{
|
||||||
|
this.renderThread = Thread.currentThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QueuedRunnable runnable = RENDER_THREAD_RUNNABLE_QUEUE.poll();
|
||||||
while(runnable != null)
|
while(runnable != null)
|
||||||
{
|
{
|
||||||
|
long taskStartNano = System.nanoTime();
|
||||||
|
|
||||||
|
this.currentQueuedRunnable = runnable;
|
||||||
runnable.run();
|
runnable.run();
|
||||||
|
this.currentQueuedRunnable = null;
|
||||||
|
|
||||||
// only try running for a limited amount of time to prevent lag spikes
|
// only try running for a limited amount of time to prevent lag spikes
|
||||||
long currentTimeMs = System.currentTimeMillis();
|
long taskNano = System.nanoTime() - taskStartNano;
|
||||||
long runDuration = currentTimeMs - startTimeMs;
|
long totalLoopNano = System.nanoTime() - loopStartTimeNano;
|
||||||
if (runDuration > msMaxRunTime)
|
|
||||||
|
// stat tracking
|
||||||
|
if (ModInfo.IS_DEV_BUILD)
|
||||||
{
|
{
|
||||||
|
if (!AVERAGE_NANO_RUN_TIME_BY_TASK_NAME.containsKey(runnable.name))
|
||||||
|
{
|
||||||
|
AVERAGE_NANO_RUN_TIME_BY_TASK_NAME.put(runnable.name, new RollingAverage(1_000));
|
||||||
|
}
|
||||||
|
AVERAGE_NANO_RUN_TIME_BY_TASK_NAME.get(runnable.name).add(totalLoopNano);
|
||||||
|
|
||||||
|
COMPLETED_TASK_COUNTER.increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// estimate when our ending nano-time would be once the next task is run
|
||||||
|
long expectedNextTaskNano = totalLoopNano
|
||||||
|
// doubling this task's time gives a rough over-estimate of how long the next task should take
|
||||||
|
+ (taskNano * 2);
|
||||||
|
// If the next task would push us over the max run time, stop now.
|
||||||
|
// This prevents stuttering at the cost of lower throughput.
|
||||||
|
if (expectedNextTaskNano >= nanoMaxRunTime)
|
||||||
|
{
|
||||||
|
if (LOG_SLOW_TASKS
|
||||||
|
&& totalLoopNano > nanoMaxRunTime)
|
||||||
|
{
|
||||||
|
// this task took longer than what we wanted
|
||||||
|
RATE_LIMITED_LOGGER.warn("["+runnable.name+"] slow, actual ["+totalLoopNano+"], allowed ["+nanoMaxRunTime+"].");
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,9 +208,9 @@ public class RenderThreadTaskHandler
|
|||||||
*/
|
*/
|
||||||
private void manualCleanupTick()
|
private void manualCleanupTick()
|
||||||
{
|
{
|
||||||
long nowMs = System.currentTimeMillis();
|
long nowNano = System.nanoTime();
|
||||||
long msSinceLast = nowMs - this.msSinceGlTasksRun;
|
long nanoSinceLast = nowNano - this.nanoSinceTasksRun;
|
||||||
if (msSinceLast > MS_BEFORE_RUN_CLEANUP_TIMER)
|
if (nanoSinceLast < NANOS_BEFORE_RUN_CLEANUP_TIMER)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -132,11 +219,186 @@ public class RenderThreadTaskHandler
|
|||||||
// this means we could have GL jobs building up.
|
// this means we could have GL jobs building up.
|
||||||
// Run the queued tasks on MC's executor (hopefully this should always run,
|
// Run the queued tasks on MC's executor (hopefully this should always run,
|
||||||
// even if DH's render code isn't being hit).
|
// even if DH's render code isn't being hit).
|
||||||
IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
IMinecraftClientWrapper mcClient = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||||
MC.executeOnRenderThread(() -> this.runRenderThreadTasks(1_000));
|
if (mcClient != null)
|
||||||
|
{
|
||||||
|
mcClient.executeOnRenderThread(() -> this.runRenderThreadTasks(500 * 1_000_000L));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// shouldn't happen, but just in case
|
||||||
|
|
||||||
|
// somehow the timer started when there wasn't a client wrapper
|
||||||
|
// this probably means the timer was started on a dedicated server
|
||||||
|
RATE_LIMITED_LOGGER.warn("["+RenderThreadTaskHandler.class.getSimpleName()+"] timer started when ["+IMinecraftClientWrapper.class.getSimpleName()+"] is null. This shouldn't happen but can likely be ignored.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//end region
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//===========//
|
||||||
|
// debugging //
|
||||||
|
//===========//
|
||||||
|
//region
|
||||||
|
|
||||||
|
/**
|
||||||
|
* if tasks are currently queued the debug
|
||||||
|
* stats may not be zero after this method has been called.
|
||||||
|
*/
|
||||||
|
public void clearDebugStats()
|
||||||
|
{
|
||||||
|
AVERAGE_NANO_RUN_TIME_BY_TASK_NAME.clear();
|
||||||
|
COMPLETED_TASK_COUNTER.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDebugMenuStringsToList(List<String> messageList)
|
||||||
|
{
|
||||||
|
if (!ModInfo.IS_DEV_BUILD)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
String o = MinecraftTextFormat.ORANGE;
|
||||||
|
String g = MinecraftTextFormat.GREEN;
|
||||||
|
String b = MinecraftTextFormat.DARK_BLUE;
|
||||||
|
String y = MinecraftTextFormat.YELLOW;
|
||||||
|
String cf = MinecraftTextFormat.CLEAR_FORMATTING;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
String queueSize = DECIMAL_NUMBER_FORMAT.format(RENDER_THREAD_RUNNABLE_QUEUE.size());
|
||||||
|
String completedCount = DECIMAL_NUMBER_FORMAT.format(COMPLETED_TASK_COUNTER.sum());
|
||||||
|
|
||||||
|
String messageHeader = "Render Tasks, Queue: "+o+queueSize+cf+", Done: "+g+completedCount+cf;
|
||||||
|
messageList.add(messageHeader);
|
||||||
|
|
||||||
|
AVERAGE_NANO_RUN_TIME_BY_TASK_NAME.forEach((name, rollingAverage) ->
|
||||||
|
{
|
||||||
|
// thread runtime
|
||||||
|
String runTimeAvgStr;
|
||||||
|
double runTimeAvgInNano = rollingAverage.getAverage();
|
||||||
|
if (!Double.isNaN(runTimeAvgInNano))
|
||||||
|
{
|
||||||
|
double runTimeAvgInMs = runTimeAvgInNano / 1_000_000.0;
|
||||||
|
runTimeAvgStr = DECIMAL_NUMBER_FORMAT.format(runTimeAvgInMs);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
runTimeAvgStr = "<0";
|
||||||
|
}
|
||||||
|
|
||||||
|
String lifetimeCount = INT_NUMBER_FORMAT.format(rollingAverage.getLifetimeCount());
|
||||||
|
|
||||||
|
String message = name+" Avg: "+b+runTimeAvgStr+"ms"+cf+" #: "+y+lifetimeCount+cf;
|
||||||
|
messageList.add(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Returns true if the currently running thread is being run by this handler */
|
||||||
|
public boolean isCurrentThread()
|
||||||
|
{
|
||||||
|
if (this.renderThread != null)
|
||||||
|
{
|
||||||
|
return Thread.currentThread() == this.renderThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldn't normally be needed, but can be used if this
|
||||||
|
// handler hasn't been run yet
|
||||||
|
return Thread.currentThread().getName().equals("Render thread");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only recommended to be used by the task that's currently being run.
|
||||||
|
* Use {@link RenderThreadTaskHandler#isCurrentThread()} to check. <br>
|
||||||
|
* Can be used to get stack traces for render thread tasks while they're being run.
|
||||||
|
*/
|
||||||
|
public @Nullable QueuedRunnable getCurrentlyRunningTask() { return this.currentQueuedRunnable; }
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// helper classes //
|
||||||
|
//================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
public static class QueuedRunnable implements Runnable
|
||||||
|
{
|
||||||
|
/** used to easily track what's being done on the render thread */
|
||||||
|
public final String name;
|
||||||
|
public final Runnable renderCall;
|
||||||
|
/** will be null on release build to reduce GC pressure */
|
||||||
|
@Nullable
|
||||||
|
public final StackTraceElement[] stackTrace;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=============//
|
||||||
|
// constructor //
|
||||||
|
//=============//
|
||||||
|
//region
|
||||||
|
|
||||||
|
public QueuedRunnable(String name, Runnable renderCall, @Nullable StackTraceElement[] stackTrace)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.renderCall = renderCall;
|
||||||
|
this.stackTrace = stackTrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=========//
|
||||||
|
// running //
|
||||||
|
//=========//
|
||||||
|
//region
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.renderCall.run();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
if (ExceptionUtil.isShutdownException(e))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeException error = new RuntimeException("Uncaught Exception during GL call execution. StackTrace: ["+(this.stackTrace != null ? "Present" : "Missing")+"] Error: ["+e.getMessage()+"]", e);
|
||||||
|
if (this.stackTrace != null)
|
||||||
|
{
|
||||||
|
error.setStackTrace(this.stackTrace);
|
||||||
|
}
|
||||||
|
LOGGER.error("[" + Thread.currentThread().getName() + "] ran into an unexpected error running a GL call, Error: ["+ e.getMessage() +"].", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// base overrides //
|
||||||
|
//================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() { return this.name; }
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+128
-126
@@ -46,7 +46,6 @@ import java.util.*;
|
|||||||
import java.util.concurrent.RejectedExecutionException;
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
public class BeaconRenderHandler
|
public class BeaconRenderHandler
|
||||||
{
|
{
|
||||||
@@ -57,8 +56,6 @@ public class BeaconRenderHandler
|
|||||||
/** how often should we check if a beacon should be culled? */
|
/** how often should we check if a beacon should be culled? */
|
||||||
private static final int MAX_CULLING_FREQUENCY_IN_MS = 1_000;
|
private static final int MAX_CULLING_FREQUENCY_IN_MS = 1_000;
|
||||||
|
|
||||||
private static final Comparator<BeaconBeamDTO> NEGATIVE_BLOCKPOS_COMPARATOR = new NegativeInfiniteBlockPosComparator();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private final ReentrantLock updateLock = new ReentrantLock();
|
private final ReentrantLock updateLock = new ReentrantLock();
|
||||||
@@ -67,8 +64,6 @@ public class BeaconRenderHandler
|
|||||||
private final IDhApiRenderableBoxGroup activeBeaconBoxRenderGroup;
|
private final IDhApiRenderableBoxGroup activeBeaconBoxRenderGroup;
|
||||||
/** contains all beacons that could be rendered (including those that are being culled) */
|
/** contains all beacons that could be rendered (including those that are being culled) */
|
||||||
private final ArrayList<DhApiRenderableBox> fullBeaconBoxList = new ArrayList<>();
|
private final ArrayList<DhApiRenderableBox> fullBeaconBoxList = new ArrayList<>();
|
||||||
/** contains all beacons that could be rendered */
|
|
||||||
private final HashSet<DhBlockPos> fullBeaconBlockPosSet = new HashSet<>();
|
|
||||||
|
|
||||||
private boolean cullingThreadRunning = false;
|
private boolean cullingThreadRunning = false;
|
||||||
private boolean updateRenderDataNextFrame = false;
|
private boolean updateRenderDataNextFrame = false;
|
||||||
@@ -96,126 +91,12 @@ public class BeaconRenderHandler
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=================//
|
//===============//
|
||||||
// render handling //
|
// before render //
|
||||||
//=================//
|
//===============//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
public void startRenderingBeacons(ArrayList<BeaconBeamDTO> beaconList, byte detailLevel)
|
private void beforeRender(DhApiRenderParam renderEventParam)
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.updateLock.lock();
|
|
||||||
|
|
||||||
|
|
||||||
// how wide should each beacon be?
|
|
||||||
int beaconBlockWidth = 1;
|
|
||||||
if (Config.Client.Advanced.Graphics.GenericRendering.expandDistantBeacons.get())
|
|
||||||
{
|
|
||||||
beaconBlockWidth = DhSectionPos.getBlockWidth(detailLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ArrayList<BeaconBeamDTO> sortedBeaconList = new ArrayList<>(beaconList);
|
|
||||||
|
|
||||||
// merge distant beams if requested
|
|
||||||
if (Config.Client.Advanced.Graphics.GenericRendering.expandDistantBeacons.get())
|
|
||||||
{
|
|
||||||
// sort beacons from neg inf -> pos inf
|
|
||||||
// so we can consistently merge adjacent beacons
|
|
||||||
sortedBeaconList.sort(NEGATIVE_BLOCKPOS_COMPARATOR);
|
|
||||||
|
|
||||||
// go through each beacon...
|
|
||||||
for (int outerIndex = 0; outerIndex < sortedBeaconList.size(); outerIndex++)
|
|
||||||
{
|
|
||||||
BeaconBeamDTO outerBeacon = sortedBeaconList.get(outerIndex);
|
|
||||||
DhBlockPos outerBlockPos = outerBeacon.blockPos;
|
|
||||||
|
|
||||||
// ...and remove any beacons that are within the block width to prevent overlaps
|
|
||||||
for (int mergeIndex = outerIndex + 1; mergeIndex < sortedBeaconList.size(); mergeIndex++)
|
|
||||||
{
|
|
||||||
BeaconBeamDTO beaconToMerge = sortedBeaconList.get(mergeIndex);
|
|
||||||
DhBlockPos mergeBlockPos = beaconToMerge.blockPos;
|
|
||||||
|
|
||||||
int xDiff = mergeBlockPos.getX() - outerBlockPos.getX();
|
|
||||||
int zDiff = mergeBlockPos.getZ() - outerBlockPos.getZ();
|
|
||||||
|
|
||||||
// merge (remove) this beacon if
|
|
||||||
// it's close to the outer beacon
|
|
||||||
if (xDiff < beaconBlockWidth
|
|
||||||
&& zDiff < beaconBlockWidth)
|
|
||||||
{
|
|
||||||
sortedBeaconList.remove(mergeIndex);
|
|
||||||
mergeIndex--; // minus 1 so we don't go past the end of the array when incrementing in the for loop up top
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//LOGGER.info("startRenderingBeacons ["+sortedBeaconList+"]");
|
|
||||||
|
|
||||||
// add each beacon to the renderer
|
|
||||||
for (int i = 0; i < sortedBeaconList.size(); i++)
|
|
||||||
{
|
|
||||||
BeaconBeamDTO beacon = sortedBeaconList.get(i);
|
|
||||||
if (!this.fullBeaconBlockPosSet.add(beacon.blockPos))
|
|
||||||
{
|
|
||||||
// skip already present beacons
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int maxBeaconBeamHeight = Config.Client.Advanced.Graphics.GenericRendering.beaconRenderHeight.get();
|
|
||||||
DhApiRenderableBox beaconBox = new DhApiRenderableBox(
|
|
||||||
new DhApiVec3d(beacon.blockPos.getX(), beacon.blockPos.getY() + 1, beacon.blockPos.getZ()),
|
|
||||||
new DhApiVec3d(beacon.blockPos.getX() + beaconBlockWidth, maxBeaconBeamHeight, beacon.blockPos.getZ() + beaconBlockWidth),
|
|
||||||
beacon.color,
|
|
||||||
EDhApiBlockMaterial.ILLUMINATED
|
|
||||||
);
|
|
||||||
|
|
||||||
this.activeBeaconBoxRenderGroup.add(beaconBox);
|
|
||||||
this.fullBeaconBoxList.add(beaconBox);
|
|
||||||
this.activeBeaconBoxRenderGroup.triggerBoxChange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
this.updateLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stopRenderingBeaconsInRange(long pos)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.updateLock.lock();
|
|
||||||
|
|
||||||
Predicate<DhApiRenderableBox> removeBoxPredicate = (DhApiRenderableBox box) ->
|
|
||||||
{
|
|
||||||
DhBlockPos blockPos = new DhBlockPos((int)box.minPos.x, (int)box.minPos.y, (int)box.minPos.z);
|
|
||||||
boolean contains = DhSectionPos.contains(pos, blockPos);
|
|
||||||
//if (contains)
|
|
||||||
//{
|
|
||||||
// LOGGER.info("stopRenderingBeaconsInRange ["+DhSectionPos.toString(pos)+"] ["+blockPos+"]");
|
|
||||||
//}
|
|
||||||
return contains;
|
|
||||||
};
|
|
||||||
this.activeBeaconBoxRenderGroup.removeIf(removeBoxPredicate);
|
|
||||||
this.fullBeaconBoxList.removeIf(removeBoxPredicate);
|
|
||||||
|
|
||||||
this.fullBeaconBlockPosSet.removeIf((DhBlockPos blockPos) -> DhSectionPos.contains(pos, blockPos));
|
|
||||||
|
|
||||||
this.activeBeaconBoxRenderGroup.triggerBoxChange();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
this.updateLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void beforeRender(DhApiRenderParam renderEventParam)
|
|
||||||
{
|
{
|
||||||
if (Config.Client.Advanced.Graphics.Culling.disableBeaconDistanceCulling.get())
|
if (Config.Client.Advanced.Graphics.Culling.disableBeaconDistanceCulling.get())
|
||||||
{
|
{
|
||||||
@@ -237,7 +118,7 @@ public class BeaconRenderHandler
|
|||||||
private void tryUpdateBeaconCullingAsync()
|
private void tryUpdateBeaconCullingAsync()
|
||||||
{
|
{
|
||||||
ThreadPoolExecutor executor = ThreadPoolUtil.getBeaconCullingExecutor();
|
ThreadPoolExecutor executor = ThreadPoolUtil.getBeaconCullingExecutor();
|
||||||
if (executor != null
|
if (executor != null
|
||||||
&& !this.cullingThreadRunning)
|
&& !this.cullingThreadRunning)
|
||||||
{
|
{
|
||||||
this.cullingThreadRunning = true;
|
this.cullingThreadRunning = true;
|
||||||
@@ -304,15 +185,136 @@ public class BeaconRenderHandler
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==============//
|
||||||
|
// registration //
|
||||||
|
//==============//
|
||||||
|
//region
|
||||||
|
|
||||||
|
public void replaceRenderingBeacons(ArrayList<BeaconBeamWithWidth> beaconList)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.updateLock.lock();
|
||||||
|
|
||||||
|
ArrayList<BeaconBeamWithWidth> sortedBeaconList = new ArrayList<>(beaconList);
|
||||||
|
|
||||||
|
// merge distant beams if requested
|
||||||
|
if (Config.Client.Advanced.Graphics.GenericRendering.expandDistantBeacons.get())
|
||||||
|
{
|
||||||
|
// sort beacons from neg inf -> pos inf
|
||||||
|
// so we can consistently merge adjacent beacons
|
||||||
|
sortedBeaconList.sort(NegativeInfiniteBlockPosComparator.INSTANCE);
|
||||||
|
|
||||||
|
// go through each beacon...
|
||||||
|
for (int outerIndex = 0; outerIndex < sortedBeaconList.size(); outerIndex++)
|
||||||
|
{
|
||||||
|
BeaconBeamWithWidth outerBeacon = sortedBeaconList.get(outerIndex);
|
||||||
|
DhBlockPos outerBlockPos = outerBeacon.blockPos;
|
||||||
|
|
||||||
|
// ...and remove any beacons that are within the block width to prevent overlaps
|
||||||
|
for (int mergeIndex = outerIndex + 1; mergeIndex < sortedBeaconList.size(); mergeIndex++)
|
||||||
|
{
|
||||||
|
BeaconBeamWithWidth beaconToMerge = sortedBeaconList.get(mergeIndex);
|
||||||
|
DhBlockPos mergeBlockPos = beaconToMerge.blockPos;
|
||||||
|
|
||||||
|
int xDiff = mergeBlockPos.getX() - outerBlockPos.getX();
|
||||||
|
int zDiff = mergeBlockPos.getZ() - outerBlockPos.getZ();
|
||||||
|
|
||||||
|
// merge (remove) this beacon if
|
||||||
|
// it's close to the outer beacon
|
||||||
|
if (xDiff < beaconToMerge.beaconBlockWidth
|
||||||
|
&& zDiff < beaconToMerge.beaconBlockWidth)
|
||||||
|
{
|
||||||
|
sortedBeaconList.remove(mergeIndex);
|
||||||
|
mergeIndex--; // minus 1 so we don't go past the end of the array when incrementing in the for loop up top
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.activeBeaconBoxRenderGroup.clear();
|
||||||
|
this.fullBeaconBoxList.clear();
|
||||||
|
|
||||||
|
// add each beacon to the renderer
|
||||||
|
for (int i = 0; i < sortedBeaconList.size(); i++)
|
||||||
|
{
|
||||||
|
BeaconBeamWithWidth beacon = sortedBeaconList.get(i);
|
||||||
|
int maxBeaconBeamHeight = Config.Client.Advanced.Graphics.GenericRendering.beaconRenderHeight.get();
|
||||||
|
DhApiRenderableBox beaconBox = new DhApiRenderableBox(
|
||||||
|
new DhApiVec3d(beacon.blockPos.getX(), beacon.blockPos.getY() + 1, beacon.blockPos.getZ()),
|
||||||
|
new DhApiVec3d(beacon.blockPos.getX() + beacon.beaconBlockWidth, maxBeaconBeamHeight, beacon.blockPos.getZ() + beacon.beaconBlockWidth),
|
||||||
|
beacon.color,
|
||||||
|
EDhApiBlockMaterial.ILLUMINATED
|
||||||
|
);
|
||||||
|
|
||||||
|
this.activeBeaconBoxRenderGroup.add(beaconBox);
|
||||||
|
this.fullBeaconBoxList.add(beaconBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.activeBeaconBoxRenderGroup.triggerBoxChange();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.updateLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//================//
|
//================//
|
||||||
// helper classes //
|
// helper classes //
|
||||||
//================//
|
//================//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
private static class NegativeInfiniteBlockPosComparator implements Comparator<BeaconBeamDTO>
|
public static class BeaconBeamWithWidth extends BeaconBeamDTO
|
||||||
{
|
{
|
||||||
|
public final int beaconBlockWidth;
|
||||||
|
|
||||||
|
public BeaconBeamWithWidth(BeaconBeamDTO beaconBeamDTO, byte lodDetailLevel)
|
||||||
|
{
|
||||||
|
super(beaconBeamDTO.blockPos, beaconBeamDTO.color);
|
||||||
|
|
||||||
|
|
||||||
|
// how wide should each beacon be?
|
||||||
|
if (Config.Client.Advanced.Graphics.GenericRendering.expandDistantBeacons.get())
|
||||||
|
{
|
||||||
|
this.beaconBlockWidth = DhSectionPos.getBlockWidth(lodDetailLevel);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.beaconBlockWidth = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj)
|
||||||
|
{
|
||||||
|
if (obj == null
|
||||||
|
|| obj.getClass() != this.getClass())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BeaconBeamWithWidth that = (BeaconBeamWithWidth) obj;
|
||||||
|
if (that.beaconBlockWidth != this.beaconBlockWidth)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.equals(that);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NegativeInfiniteBlockPosComparator implements Comparator<BeaconBeamWithWidth>
|
||||||
|
{
|
||||||
|
public static final NegativeInfiniteBlockPosComparator INSTANCE = new NegativeInfiniteBlockPosComparator();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compare(BeaconBeamDTO beacon1, BeaconBeamDTO beacon2)
|
public int compare(BeaconBeamWithWidth beacon1, BeaconBeamWithWidth beacon2)
|
||||||
{
|
{
|
||||||
DhBlockPos blockPos1 = beacon1.blockPos;
|
DhBlockPos blockPos1 = beacon1.blockPos;
|
||||||
DhBlockPos blockPos2 = beacon2.blockPos;
|
DhBlockPos blockPos2 = beacon2.blockPos;
|
||||||
|
|||||||
+1
-15
@@ -96,9 +96,6 @@ public class CloudRenderHandler
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
private boolean disabledWarningLogged = false;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=============//
|
//=============//
|
||||||
// constructor //
|
// constructor //
|
||||||
@@ -287,7 +284,7 @@ public class CloudRenderHandler
|
|||||||
CloudParams cloudParams = new CloudParams(textureWidth, x, z);
|
CloudParams cloudParams = new CloudParams(textureWidth, x, z);
|
||||||
boxGroup.setPreRenderFunc((renderParam) -> this.preRender(renderParam, cloudParams));
|
boxGroup.setPreRenderFunc((renderParam) -> this.preRender(renderParam, cloudParams));
|
||||||
|
|
||||||
renderer.add(boxGroup);
|
this.renderer.add(boxGroup);
|
||||||
this.boxGroupByOffset[x+CLOUD_INSTANCE_RADIUS_COUNT][z+CLOUD_INSTANCE_RADIUS_COUNT] = boxGroup;
|
this.boxGroupByOffset[x+CLOUD_INSTANCE_RADIUS_COUNT][z+CLOUD_INSTANCE_RADIUS_COUNT] = boxGroup;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -319,17 +316,6 @@ public class CloudRenderHandler
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (!this.renderer.getInstancedRenderingAvailable())
|
|
||||||
//{
|
|
||||||
// if (!this.disabledWarningLogged)
|
|
||||||
// {
|
|
||||||
// this.disabledWarningLogged = true;
|
|
||||||
// LOGGER.warn("Instanced rendering unavailable, cloud rendering disabled.");
|
|
||||||
// }
|
|
||||||
// boxGroup.setActive(false);
|
|
||||||
// return;
|
|
||||||
//}
|
|
||||||
|
|
||||||
IClientLevelWrapper clientLevelWrapper = this.level.getClientLevelWrapper();
|
IClientLevelWrapper clientLevelWrapper = this.level.getClientLevelWrapper();
|
||||||
if (clientLevelWrapper == null)
|
if (clientLevelWrapper == null)
|
||||||
{
|
{
|
||||||
|
|||||||
+192
-158
@@ -26,6 +26,7 @@ import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
|
|||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
|
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
|
||||||
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
|
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
|
||||||
import com.seibel.distanthorizons.core.render.RenderParams;
|
import com.seibel.distanthorizons.core.render.RenderParams;
|
||||||
@@ -36,6 +37,8 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccess
|
|||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.*;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.*;
|
||||||
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
|
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is where all the magic happens. <br>
|
* This is where all the magic happens. <br>
|
||||||
* This is where LODs are draw to the world.
|
* This is where LODs are draw to the world.
|
||||||
@@ -101,7 +104,7 @@ public class LodRenderer
|
|||||||
* otherwise it will only render opaque LODs.
|
* otherwise it will only render opaque LODs.
|
||||||
*/
|
*/
|
||||||
public void render(RenderParams renderParams, IProfilerWrapper profiler)
|
public void render(RenderParams renderParams, IProfilerWrapper profiler)
|
||||||
{ this.renderLodPass(renderParams, profiler, false); }
|
{ this.renderTerrain(renderParams, profiler, false); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is designed for Iris to be able
|
* This method is designed for Iris to be able
|
||||||
@@ -110,9 +113,9 @@ public class LodRenderer
|
|||||||
* but shouldn't be activated as per deferWaterRendering.
|
* but shouldn't be activated as per deferWaterRendering.
|
||||||
*/
|
*/
|
||||||
public void renderDeferred(RenderParams renderParams, IProfilerWrapper profiler)
|
public void renderDeferred(RenderParams renderParams, IProfilerWrapper profiler)
|
||||||
{ this.renderLodPass(renderParams, profiler, true); }
|
{ this.renderTerrain(renderParams, profiler, true); }
|
||||||
|
|
||||||
private void renderLodPass(RenderParams renderParams, IProfilerWrapper profiler, boolean runningDeferredPass)
|
private void renderTerrain(RenderParams renderParams, IProfilerWrapper profiler, boolean runningDeferredPass)
|
||||||
{
|
{
|
||||||
//====================//
|
//====================//
|
||||||
// validate rendering //
|
// validate rendering //
|
||||||
@@ -143,179 +146,212 @@ public class LodRenderer
|
|||||||
//region
|
//region
|
||||||
|
|
||||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderSetupEvent.class, renderParams);
|
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderSetupEvent.class, renderParams);
|
||||||
profiler.push("LOD GL setup");
|
try (IProfilerWrapper.IProfileBlock terrainRender_profile = profiler.push("LOD GL setup")) // starts the new profile block for most DH rendering
|
||||||
|
|
||||||
if (!this.renderersBound)
|
|
||||||
{
|
{
|
||||||
this.bindRenderers();
|
|
||||||
this.renderersBound = true;
|
if (!this.renderersBound)
|
||||||
}
|
|
||||||
|
|
||||||
RenderBufferHandler renderBufferHandler = renderParams.renderBufferHandler;
|
|
||||||
IDhGenericRenderer genericRenderer = renderParams.genericRenderer;
|
|
||||||
|
|
||||||
|
|
||||||
this.metaRenderer.runRenderPassSetup(renderParams);
|
|
||||||
|
|
||||||
if (!this.vanillaSettingsOverridden)
|
|
||||||
{
|
|
||||||
// only do this once, that way they can still be reverted if desired
|
|
||||||
if (Config.Client.Advanced.Graphics.overrideVanillaGraphicsSettings.get())
|
|
||||||
{
|
{
|
||||||
LOGGER.info("Overriding vanilla MC settings to better fit Distant Horizons... This behavior can be disabled in the Distant Horizons config.");
|
this.bindRenderers();
|
||||||
|
this.renderersBound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderBufferHandler renderBufferHandler = renderParams.renderBufferHandler;
|
||||||
|
IDhGenericRenderer genericRenderer = renderParams.genericRenderer;
|
||||||
|
|
||||||
|
|
||||||
|
this.metaRenderer.runRenderPassSetup(renderParams);
|
||||||
|
|
||||||
|
if (!this.vanillaSettingsOverridden)
|
||||||
|
{
|
||||||
|
// only do this once, that way they can still be reverted if desired
|
||||||
|
if (Config.Client.Advanced.Graphics.overrideVanillaGraphicsSettings.get())
|
||||||
|
{
|
||||||
|
LOGGER.info("Overriding vanilla MC settings to better fit Distant Horizons... This behavior can be disabled in the Distant Horizons config.");
|
||||||
|
|
||||||
|
MC.disableVanillaClouds();
|
||||||
|
MC.disableVanillaChunkFadeIn();
|
||||||
|
MC.disableFabulousTransparency();
|
||||||
|
}
|
||||||
|
|
||||||
MC.disableVanillaClouds();
|
this.vanillaSettingsOverridden = true;
|
||||||
MC.disableVanillaChunkFadeIn();
|
|
||||||
MC.disableFabulousTransparency();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.vanillaSettingsOverridden = true;
|
if (firstPass)
|
||||||
}
|
|
||||||
|
|
||||||
if (firstPass)
|
|
||||||
{
|
|
||||||
// we only need to sort/cull the LODs at the start of the frame
|
|
||||||
profiler.popPush("LOD build render list");
|
|
||||||
renderBufferHandler.buildRenderList(renderParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
//endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//===========//
|
|
||||||
// rendering //
|
|
||||||
//===========//
|
|
||||||
|
|
||||||
if (!runningDeferredPass)
|
|
||||||
{
|
|
||||||
this.metaRenderer.clearDhDepthAndColorTextures(renderParams);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=========================//
|
|
||||||
// opaque and non-deferred //
|
|
||||||
// transparent rendering //
|
|
||||||
//=========================//
|
|
||||||
|
|
||||||
// opaque LODs
|
|
||||||
profiler.popPush("LOD Opaque");
|
|
||||||
|
|
||||||
this.renderLodPass(this.terrainRenderer, renderBufferHandler, renderParams, /*opaquePass*/ true, profiler);
|
|
||||||
|
|
||||||
// custom objects with SSAO
|
|
||||||
if (Config.Client.Advanced.Graphics.GenericRendering.enableGenericRendering.get())
|
|
||||||
{
|
{
|
||||||
profiler.popPush("Custom Objects");
|
// we only need to sort/cull the LODs at the start of the frame
|
||||||
genericRenderer.render(renderParams, profiler, true);
|
profiler.popPush("LOD build render list");
|
||||||
}
|
renderBufferHandler.buildRenderList(renderParams);
|
||||||
|
|
||||||
// SSAO
|
|
||||||
if (Config.Client.Advanced.Graphics.Ssao.enableSsao.get())
|
|
||||||
{
|
|
||||||
profiler.popPush("LOD SSAO");
|
|
||||||
this.ssaoRenderer.render(renderParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
// custom objects without SSAO
|
|
||||||
if (Config.Client.Advanced.Graphics.GenericRendering.enableGenericRendering.get())
|
|
||||||
{
|
|
||||||
profiler.popPush("Custom Objects");
|
|
||||||
genericRenderer.render(renderParams, profiler, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// combined pass transparent rendering
|
|
||||||
if (!deferTransparentRendering
|
|
||||||
&& Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled)
|
|
||||||
{
|
|
||||||
profiler.popPush("LOD Transparent");
|
|
||||||
this.renderLodPass(this.terrainRenderer, renderBufferHandler, renderParams, /*opaquePass*/ false, profiler);
|
|
||||||
}
|
|
||||||
|
|
||||||
// far plane clip fading
|
|
||||||
if (Config.Client.Advanced.Graphics.Quality.dhFadeFarClipPlane.get()
|
|
||||||
&& IRIS_ACCESSOR == null)
|
|
||||||
{
|
|
||||||
profiler.popPush("Fade Far Clip Fade");
|
|
||||||
this.farFadeRenderer.render(renderParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
// fog
|
|
||||||
if (Config.Client.Advanced.Graphics.Fog.enableDhFog.get()
|
|
||||||
// this is done to fix issues with: underwater fog, blindness effect, etc.
|
|
||||||
|| renderParams.vanillaFogEnabled)
|
|
||||||
{
|
|
||||||
profiler.popPush("LOD Fog");
|
|
||||||
|
|
||||||
this.fogRenderer.render(renderParams);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
boolean renderFog;
|
||||||
//=================//
|
Boolean apiFogOverride = Config.Client.Advanced.Graphics.Fog.enableDhFog.getApiValue();
|
||||||
// debug rendering //
|
if (apiFogOverride != null)
|
||||||
//=================//
|
|
||||||
|
|
||||||
if (Config.Client.Advanced.Debugging.DebugWireframe.enableRendering.get())
|
|
||||||
{
|
{
|
||||||
profiler.popPush("Debug wireframes");
|
// use whatever the API dictates if set
|
||||||
|
// (this could cause issues when underwater if a shader or something
|
||||||
// Note: this can be very slow if a lot of boxes are being rendered
|
// doesn't add their own, but that's relatively unlikely)
|
||||||
this.debugWireframeRenderer.render(renderParams);
|
renderFog = apiFogOverride;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
renderFog = Config.Client.Advanced.Graphics.Fog.enableDhFog.get();
|
||||||
|
// allow enabling fog when: underwater fog, blind, etc.
|
||||||
|
// otherwise LODs won't appear correctly
|
||||||
|
renderFog |= renderParams.vanillaFogEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
//=============================//
|
|
||||||
// Apply to the MC Framebuffer //
|
|
||||||
//=============================//
|
|
||||||
|
|
||||||
boolean cancelApplyShader = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeApplyShaderRenderEvent.class, renderParams);
|
//===========//
|
||||||
if (!cancelApplyShader)
|
// rendering //
|
||||||
|
//===========//
|
||||||
|
|
||||||
|
if (!runningDeferredPass)
|
||||||
{
|
{
|
||||||
profiler.popPush("Apply to MC");
|
// needs to be fired after all the textures have been created/bound
|
||||||
this.metaRenderer.applyToMcTexture(renderParams);
|
boolean clearTextures = !ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeTextureClearEvent.class, renderParams);
|
||||||
}
|
if (clearTextures)
|
||||||
|
{
|
||||||
}
|
this.metaRenderer.clearDhDepthAndColorTextures(renderParams);
|
||||||
else
|
}
|
||||||
{
|
|
||||||
//====================//
|
|
||||||
// deferred rendering //
|
|
||||||
//====================//
|
//=========================//
|
||||||
|
// opaque and non-deferred //
|
||||||
if (Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled)
|
// transparent rendering //
|
||||||
{
|
//=========================//
|
||||||
profiler.popPush("LOD Transparent");
|
|
||||||
this.renderLodPass(this.terrainRenderer, renderBufferHandler, renderParams, /*opaquePass*/ false, profiler);
|
// opaque LODs
|
||||||
|
profiler.popPush("LOD Opaque");
|
||||||
|
|
||||||
if (Config.Client.Advanced.Graphics.Fog.enableDhFog.get()
|
this.renderTerrain(this.terrainRenderer, renderBufferHandler, renderParams, /*opaquePass*/ true, profiler);
|
||||||
// this is done to fix issues with: underwater fog, blindness effect, etc.
|
|
||||||
|| renderParams.vanillaFogEnabled)
|
// custom objects with SSAO
|
||||||
|
if (Config.Client.Advanced.Graphics.GenericRendering.enableGenericRendering.get())
|
||||||
|
{
|
||||||
|
profiler.popPush("Custom Objects");
|
||||||
|
genericRenderer.render(renderParams, profiler, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSAO
|
||||||
|
if (Config.Client.Advanced.Graphics.enableSsao.get())
|
||||||
|
{
|
||||||
|
profiler.popPush("LOD SSAO");
|
||||||
|
this.ssaoRenderer.render(renderParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
// custom objects without SSAO
|
||||||
|
if (Config.Client.Advanced.Graphics.GenericRendering.enableGenericRendering.get())
|
||||||
|
{
|
||||||
|
profiler.popPush("Custom Objects");
|
||||||
|
genericRenderer.render(renderParams, profiler, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// combined pass transparent rendering
|
||||||
|
if (!deferTransparentRendering
|
||||||
|
&& Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled)
|
||||||
|
{
|
||||||
|
profiler.popPush("LOD Transparent");
|
||||||
|
this.renderTerrain(this.terrainRenderer, renderBufferHandler, renderParams, /*opaquePass*/ false, profiler);
|
||||||
|
}
|
||||||
|
|
||||||
|
// far plane clip fading
|
||||||
|
if (Config.Client.Advanced.Graphics.Quality.dhFadeFarClipPlane.get()
|
||||||
|
&& IRIS_ACCESSOR == null)
|
||||||
|
{
|
||||||
|
profiler.popPush("Fade Far Clip Fade");
|
||||||
|
this.farFadeRenderer.render(renderParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fog
|
||||||
|
|
||||||
|
if (renderFog)
|
||||||
{
|
{
|
||||||
profiler.popPush("LOD Fog");
|
profiler.popPush("LOD Fog");
|
||||||
|
|
||||||
this.fogRenderer.render(renderParams);
|
this.fogRenderer.render(renderParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=================//
|
||||||
|
// debug rendering //
|
||||||
|
//=================//
|
||||||
|
|
||||||
|
if (Config.Client.Advanced.Debugging.DebugWireframe.enableRendering.get())
|
||||||
|
{
|
||||||
|
profiler.popPush("Debug wireframes");
|
||||||
|
|
||||||
|
// Note: this can be very slow if a lot of boxes are being rendered
|
||||||
|
this.debugWireframeRenderer.render(renderParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (Config.Client.Advanced.Debugging.PositionFinder.positionFinderEnable.get())
|
||||||
|
{
|
||||||
|
// can be used to find specific positions when debugging
|
||||||
|
this.debugWireframeRenderer.renderBox(new AbstractDebugWireframeRenderer.Box(
|
||||||
|
DhSectionPos.encode(
|
||||||
|
Config.Client.Advanced.Debugging.PositionFinder.positionFinderDetailLevel.get().byteValue(),
|
||||||
|
Config.Client.Advanced.Debugging.PositionFinder.positionFinderXPos.get(),
|
||||||
|
Config.Client.Advanced.Debugging.PositionFinder.positionFinderZPos.get()),
|
||||||
|
Config.Client.Advanced.Debugging.PositionFinder.positionFinderMinBlockY.get(),
|
||||||
|
Config.Client.Advanced.Debugging.PositionFinder.positionFinderMaxBlockY.get(),
|
||||||
|
Config.Client.Advanced.Debugging.PositionFinder.positionFinderMarginPercent.get(),
|
||||||
|
Color.GREEN
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=============================//
|
||||||
|
// Apply to the MC Framebuffer //
|
||||||
|
//=============================//
|
||||||
|
|
||||||
|
boolean cancelApplyShader = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeApplyShaderRenderEvent.class, renderParams);
|
||||||
|
if (!cancelApplyShader)
|
||||||
|
{
|
||||||
|
profiler.popPush("Apply to MC");
|
||||||
|
this.metaRenderer.applyToMcTexture(renderParams);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//====================//
|
||||||
|
// deferred rendering //
|
||||||
|
//====================//
|
||||||
|
|
||||||
|
if (Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled)
|
||||||
|
{
|
||||||
|
profiler.popPush("LOD Transparent");
|
||||||
|
this.renderTerrain(this.terrainRenderer, renderBufferHandler, renderParams, /*opaquePass*/ false, profiler);
|
||||||
|
|
||||||
|
|
||||||
|
if (renderFog)
|
||||||
|
{
|
||||||
|
profiler.popPush("LOD Fog");
|
||||||
|
|
||||||
|
this.fogRenderer.render(renderParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// render cleanup //
|
||||||
|
//================//
|
||||||
|
|
||||||
|
profiler.popPush("LOD cleanup");
|
||||||
|
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderCleanupEvent.class, renderParams);
|
||||||
|
|
||||||
|
this.metaRenderer.runRenderPassCleanup(renderParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//================//
|
|
||||||
// render cleanup //
|
|
||||||
//================//
|
|
||||||
|
|
||||||
profiler.popPush("LOD cleanup");
|
|
||||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderCleanupEvent.class, renderParams);
|
|
||||||
|
|
||||||
this.metaRenderer.runRenderPassCleanup(renderParams);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// end of internal LOD profiling
|
|
||||||
profiler.pop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
@@ -327,18 +363,16 @@ public class LodRenderer
|
|||||||
//===============//
|
//===============//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
private void renderLodPass(IDhTerrainRenderer lodRenderer, RenderBufferHandler lodBufferHandler, RenderParams renderEventParam, boolean opaquePass, IProfilerWrapper profilerWrapper)
|
private void renderTerrain(IDhTerrainRenderer terrainRenderer, RenderBufferHandler lodBufferHandler, RenderParams renderEventParam, boolean opaquePass, IProfilerWrapper profilerWrapper)
|
||||||
{
|
{
|
||||||
//===========//
|
//===========//
|
||||||
// rendering //
|
// rendering //
|
||||||
//===========//
|
//===========//
|
||||||
|
|
||||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderPassEvent.class, renderEventParam);
|
|
||||||
|
|
||||||
SortedArraySet<LodBufferContainer> lodBufferContainer = lodBufferHandler.getColumnRenderBuffers();
|
SortedArraySet<LodBufferContainer> lodBufferContainer = lodBufferHandler.getColumnRenderBuffers();
|
||||||
if (lodBufferContainer != null)
|
if (lodBufferContainer != null)
|
||||||
{
|
{
|
||||||
lodRenderer.render(renderEventParam, opaquePass, lodBufferContainer, profilerWrapper);
|
terrainRenderer.render(renderEventParam, opaquePass, lodBufferContainer, profilerWrapper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -56,7 +56,7 @@ public class RenderableBoxGroup
|
|||||||
|
|
||||||
public boolean active = true;
|
public boolean active = true;
|
||||||
public boolean ssaoEnabled = true;
|
public boolean ssaoEnabled = true;
|
||||||
private boolean vertexDataDirty = true;
|
private boolean vertexDataDirty = false;
|
||||||
|
|
||||||
public byte skyLight = LodUtil.MAX_MC_LIGHT;
|
public byte skyLight = LodUtil.MAX_MC_LIGHT;
|
||||||
public byte blockLight = LodUtil.MIN_MC_LIGHT;
|
public byte blockLight = LodUtil.MIN_MC_LIGHT;
|
||||||
@@ -198,6 +198,7 @@ public class RenderableBoxGroup
|
|||||||
if (this.altVertexBufferContainer.getState() == IDhGenericObjectVertexBufferContainer.EState.READY_TO_UPLOAD)
|
if (this.altVertexBufferContainer.getState() == IDhGenericObjectVertexBufferContainer.EState.READY_TO_UPLOAD)
|
||||||
{
|
{
|
||||||
this.altVertexBufferContainer.uploadDataToGpu();
|
this.altVertexBufferContainer.uploadDataToGpu();
|
||||||
|
this.altVertexBufferContainer.setState(IDhGenericObjectVertexBufferContainer.EState.RENDER);
|
||||||
|
|
||||||
// swap VBO references for rendering
|
// swap VBO references for rendering
|
||||||
IDhGenericObjectVertexBufferContainer temp = this.vertexBufferContainer;
|
IDhGenericObjectVertexBufferContainer temp = this.vertexBufferContainer;
|
||||||
@@ -248,6 +249,7 @@ public class RenderableBoxGroup
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.altVertexBufferContainer.updateVertexData(this.uploadBoxList);
|
this.altVertexBufferContainer.updateVertexData(this.uploadBoxList);
|
||||||
|
this.altVertexBufferContainer.setState(IDhGenericObjectVertexBufferContainer.EState.READY_TO_UPLOAD);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -346,7 +348,7 @@ public class RenderableBoxGroup
|
|||||||
@Override
|
@Override
|
||||||
public void close()
|
public void close()
|
||||||
{
|
{
|
||||||
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread(() ->
|
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("RenderBoxGroup Close", () ->
|
||||||
{
|
{
|
||||||
this.vertexBufferContainer.close();
|
this.vertexBufferContainer.close();
|
||||||
this.altVertexBufferContainer.close();
|
this.altVertexBufferContainer.close();
|
||||||
|
|||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
package com.seibel.distanthorizons.core.render.renderer;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
|
||||||
|
import com.seibel.distanthorizons.core.render.RenderParams;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to prevent attempts to render debug stuff on the server side.
|
||||||
|
*/
|
||||||
|
public class StubDebugWireframeRenderer extends AbstractDebugWireframeRenderer
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void render(RenderParams renderParams) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void renderBox(Box box) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void makeParticle(BoxParticle particle) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void register(IDebugRenderable renderable, ConfigEntry<Boolean> config) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addRenderer(IDebugRenderable renderable, ConfigEntry<Boolean> config) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unregister(IDebugRenderable renderable, ConfigEntry<Boolean> config) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeRenderer(IDebugRenderable renderable, ConfigEntry<Boolean> config) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearRenderables() { }
|
||||||
|
|
||||||
|
}
|
||||||
@@ -72,6 +72,20 @@ public class BeaconBeamDTO implements IBaseDTO<DhBlockPos>, INetworkObject
|
|||||||
@Override
|
@Override
|
||||||
public DhBlockPos getKey() { return this.blockPos; }
|
public DhBlockPos getKey() { return this.blockPos; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj)
|
||||||
|
{
|
||||||
|
if (obj == null
|
||||||
|
|| obj.getClass() != this.getClass())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BeaconBeamDTO that = (BeaconBeamDTO)obj;
|
||||||
|
return this.blockPos.equals(that.blockPos)
|
||||||
|
&& this.color.equals(that.color);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close()
|
public void close()
|
||||||
{ /* no closing needed */ }
|
{ /* no closing needed */ }
|
||||||
|
|||||||
@@ -25,9 +25,11 @@ import com.seibel.distanthorizons.core.sql.DatabaseUpdater;
|
|||||||
import com.seibel.distanthorizons.core.sql.DbConnectionClosedException;
|
import com.seibel.distanthorizons.core.sql.DbConnectionClosedException;
|
||||||
import com.seibel.distanthorizons.core.sql.dto.IBaseDTO;
|
import com.seibel.distanthorizons.core.sql.dto.IBaseDTO;
|
||||||
import com.seibel.distanthorizons.core.sql.repo.phantoms.AutoClosableTrackingWrapper;
|
import com.seibel.distanthorizons.core.sql.repo.phantoms.AutoClosableTrackingWrapper;
|
||||||
|
import com.seibel.distanthorizons.core.util.ExceptionUtil;
|
||||||
import com.seibel.distanthorizons.core.util.KeyedLockContainer;
|
import com.seibel.distanthorizons.core.util.KeyedLockContainer;
|
||||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
|
import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -496,20 +498,12 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
|
|||||||
Connection connection = CONNECTIONS_BY_CONNECTION_STRING.remove(connectionString);
|
Connection connection = CONNECTIONS_BY_CONNECTION_STRING.remove(connectionString);
|
||||||
if (connection != null)
|
if (connection != null)
|
||||||
{
|
{
|
||||||
|
// don't try closing an already closed connection
|
||||||
if (!connection.isClosed())
|
if (!connection.isClosed())
|
||||||
{
|
{
|
||||||
LOGGER.info("Closing database connection: [" + connectionString + "]");
|
LOGGER.info("Closing database connection: [" + connectionString + "]");
|
||||||
connection.close();
|
connection.close();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// these warnings can be ignored in release builds, as long as the connection is closed it doesn't really matter
|
|
||||||
// TODO fix duplicate closes
|
|
||||||
if (ModInfo.IS_DEV_BUILD)
|
|
||||||
{
|
|
||||||
LOGGER.warn("Attempting to close already closed database connection: [" + connectionString + "]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(SQLException e)
|
catch(SQLException e)
|
||||||
@@ -560,22 +554,13 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
|
|||||||
LOGGER.warn(stringBuilder.toString());
|
LOGGER.warn(stringBuilder.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// don't try closing an already closed connection
|
||||||
if (!this.connection.isClosed())
|
if (!this.connection.isClosed())
|
||||||
{
|
{
|
||||||
LOGGER.info("Closing database connection: [" + this.connectionString + "]...");
|
LOGGER.info("Closing database connection: [" + this.connectionString + "]...");
|
||||||
this.connection.close();
|
this.connection.close();
|
||||||
LOGGER.info("Finished closing database connection: [" + this.connectionString + "]");
|
LOGGER.info("Finished closing database connection: [" + this.connectionString + "]");
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// these warnings can be ignored in release builds, as long as the connection is closed it doesn't really matter
|
|
||||||
// TODO fix duplicate closes
|
|
||||||
if (ModInfo.IS_DEV_BUILD)
|
|
||||||
{
|
|
||||||
LOGGER.warn("Attempting to close already closed database connection: [" + this.connectionString + "]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ACTIVE_CONNECTION_STRINGS_BY_REPO.remove(this);
|
ACTIVE_CONNECTION_STRINGS_BY_REPO.remove(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ public class ExceptionUtil
|
|||||||
return throwable instanceof InterruptedException
|
return throwable instanceof InterruptedException
|
||||||
|| throwable instanceof UncheckedInterruptedException
|
|| throwable instanceof UncheckedInterruptedException
|
||||||
|| throwable instanceof RejectedExecutionException
|
|| throwable instanceof RejectedExecutionException
|
||||||
|
|| throwable instanceof CancellationException
|
||||||
|| throwable instanceof ClosedByInterruptException;
|
|| throwable instanceof ClosedByInterruptException;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,8 +38,8 @@ public class ExceptionUtil
|
|||||||
unwrapped instanceof CancellationException;
|
unwrapped instanceof CancellationException;
|
||||||
}
|
}
|
||||||
public static Throwable ensureUnwrap(Throwable t)
|
public static Throwable ensureUnwrap(Throwable t)
|
||||||
{
|
{ return t instanceof CompletionException ? ensureUnwrap(t.getCause()) : t; }
|
||||||
return t instanceof CompletionException ? ensureUnwrap(t.getCause()) : t;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ package com.seibel.distanthorizons.core.util;
|
|||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
|
import com.seibel.distanthorizons.coreapi.util.ColorUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class holds methods and constants that may be used in multiple places.
|
* This class holds methods and constants that may be used in multiple places.
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
package com.seibel.distanthorizons.core.util;
|
|
||||||
|
|
||||||
import org.lwjgl.util.tinyfd.TinyFileDialogs;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Should be used instead of the direct call to {@link TinyFileDialogs}
|
|
||||||
* so we can run additional validation and/or string cleanup.
|
|
||||||
* Otherwise, we may get error messages back.
|
|
||||||
*
|
|
||||||
* @see TinyFileDialogs
|
|
||||||
*/
|
|
||||||
public class NativeDialogUtil
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @param dialogType the dialog type. One of:<br><table><tr><td>"ok"</td><td>"okcancel"</td><td>"yesno"</td><td>"yesnocancel"</td></tr></table>
|
|
||||||
* @param iconType the icon type. One of:<br><table><tr><td>"info"</td><td>"warning"</td><td>"error"</td><td>"question"</td></tr></table>
|
|
||||||
*/
|
|
||||||
public static boolean showDialog(String title, String message, String dialogType, String iconType)
|
|
||||||
{
|
|
||||||
// Tinyfd doesn't support the following characters, attempting to display them will cause the message
|
|
||||||
// to be replaced with an error message
|
|
||||||
String unsafeCharsRegex = "['\"`]";
|
|
||||||
|
|
||||||
title = title.replaceAll(unsafeCharsRegex, "`");
|
|
||||||
message = message.replaceAll(unsafeCharsRegex, "`");
|
|
||||||
|
|
||||||
return TinyFileDialogs.tinyfd_messageBox(title, message, dialogType, iconType, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
+15
@@ -451,6 +451,21 @@ public class RenderDataPointReducingList extends AbstractPhantomArrayList
|
|||||||
this.setBigger(smaller, bigger);
|
this.setBigger(smaller, bigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (writeIndex == 0)
|
||||||
|
{
|
||||||
|
// if every data point in the list is NULL (0) the write index will be 0,
|
||||||
|
// and in order to prevent accessing index -1 below,
|
||||||
|
// setting the write index to 1 is needed.
|
||||||
|
|
||||||
|
// This shouldn't happen normally, however if the lod data is slightly malformed
|
||||||
|
// (which is specifically the case for the commonly shared wyncraft LODs)
|
||||||
|
// this check is needed.
|
||||||
|
// It would probably be best to fix the 6 or so NULL datapoints that are next
|
||||||
|
// to each other in the full data source, but for now this fix works.
|
||||||
|
|
||||||
|
writeIndex = 1;
|
||||||
|
}
|
||||||
|
|
||||||
this.smallest = this.sortingArray.getShort(0);
|
this.smallest = this.sortingArray.getShort(0);
|
||||||
this.biggest = this.sortingArray.getShort(writeIndex - 1);
|
this.biggest = this.sortingArray.getShort(writeIndex - 1);
|
||||||
this.setSmaller(this.getSmallest(), NULL);
|
this.setSmaller(this.getSmallest(), NULL);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
|||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
|
import com.seibel.distanthorizons.coreapi.util.ColorUtil;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -246,10 +247,7 @@ public class RenderDataPointUtil
|
|||||||
{
|
{
|
||||||
return "Y+:" + getYMax(dataPoint) +
|
return "Y+:" + getYMax(dataPoint) +
|
||||||
" Y-:" + getYMin(dataPoint) +
|
" Y-:" + getYMin(dataPoint) +
|
||||||
" argb:" + getAlpha(dataPoint) + " " +
|
" argb:" + getAlpha(dataPoint) + "," + getRed(dataPoint) + "," + getGreen(dataPoint) + "," + getBlue(dataPoint) +
|
||||||
getRed(dataPoint) + " " +
|
|
||||||
getGreen(dataPoint) + " " +
|
|
||||||
getBlue(dataPoint) +
|
|
||||||
" BL:" + getLightBlock(dataPoint) +
|
" BL:" + getLightBlock(dataPoint) +
|
||||||
" SL:" + getLightSky(dataPoint) +
|
" SL:" + getLightSky(dataPoint) +
|
||||||
" MAT:" + getBlockMaterialId(dataPoint) + "["+ EDhApiBlockMaterial.getFromIndex(getBlockMaterialId(dataPoint))+"]";
|
" MAT:" + getBlockMaterialId(dataPoint) + "["+ EDhApiBlockMaterial.getFromIndex(getBlockMaterialId(dataPoint))+"]";
|
||||||
|
|||||||
@@ -19,12 +19,17 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.core.util;
|
package com.seibel.distanthorizons.core.util;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.api.DhApi;
|
||||||
import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
|
import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
|
||||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
|
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccessor;
|
||||||
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 com.seibel.distanthorizons.core.util.math.Mat4f;
|
import com.seibel.distanthorizons.core.util.math.Mat4f;
|
||||||
@@ -35,8 +40,11 @@ import com.seibel.distanthorizons.core.util.math.Mat4f;
|
|||||||
*/
|
*/
|
||||||
public class RenderUtil
|
public class RenderUtil
|
||||||
{
|
{
|
||||||
|
private static final DhLogger LOGGER = new DhLoggerBuilder().maxCountPerSecond(1).build();
|
||||||
|
|
||||||
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||||
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
|
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
|
||||||
|
private static final IIrisAccessor IRIS_ACCESSOR = ModAccessorInjector.INSTANCE.get(IIrisAccessor.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* all speeds are measured in blocks per second
|
* all speeds are measured in blocks per second
|
||||||
@@ -79,7 +87,7 @@ public class RenderUtil
|
|||||||
nearClipDist = Math.min(nearClipDist, 7.5f);
|
nearClipDist = Math.min(nearClipDist, 7.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
float farClipDist = (float) RenderUtil.getFarClipPlaneDistanceInBlocks();
|
float farClipDist = RenderUtil.getFarClipPlaneDistanceInBlocks();
|
||||||
|
|
||||||
// Create a copy of the current matrix, so it won't be modified.
|
// Create a copy of the current matrix, so it won't be modified.
|
||||||
Mat4f lodProj = new Mat4f(mcProjMat);
|
Mat4f lodProj = new Mat4f(mcProjMat);
|
||||||
@@ -115,7 +123,16 @@ public class RenderUtil
|
|||||||
// At low render distances this hides the vanilla RD border
|
// At low render distances this hides the vanilla RD border
|
||||||
|
|
||||||
int chunkRenderDistance = MC_RENDER.getRenderDistance();
|
int chunkRenderDistance = MC_RENDER.getRenderDistance();
|
||||||
if (chunkRenderDistance <= 2)
|
|
||||||
|
if (IRIS_ACCESSOR != null
|
||||||
|
&& IRIS_ACCESSOR.isShaderPackInUse())
|
||||||
|
{
|
||||||
|
// shaders handle the near clip plane/overdraw differently, best to play it
|
||||||
|
// safe and have the plane really close otherwise
|
||||||
|
// there might be cutouts on the screen edges
|
||||||
|
overdraw = 0.2f;
|
||||||
|
}
|
||||||
|
else if (chunkRenderDistance <= 2)
|
||||||
{
|
{
|
||||||
overdraw = 0.2f;
|
overdraw = 0.2f;
|
||||||
}
|
}
|
||||||
@@ -245,12 +262,31 @@ public class RenderUtil
|
|||||||
|
|
||||||
//region
|
//region
|
||||||
|
|
||||||
public static int getFarClipPlaneDistanceInBlocks()
|
public static float getFarClipPlaneDistanceInBlocks()
|
||||||
{
|
{
|
||||||
int lodChunkDist = Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistanceRadius.get();
|
if (IRIS_ACCESSOR != null)
|
||||||
int lodBlockDist = lodChunkDist * LodUtil.CHUNK_WIDTH;
|
{
|
||||||
// * 2 to prevent clipping when high above the world
|
// Iris doesn't use the far clip plane DH generates, instead
|
||||||
return (lodBlockDist + LodUtil.REGION_WIDTH) * 2;
|
// they use a manually generated one, which causes problems.
|
||||||
|
// This is a hack so DH's far clip plane matches up with what Iris thinks it is,
|
||||||
|
// fixing projection/depth mapping.
|
||||||
|
// https://github.com/IrisShaders/Iris/issues/2534
|
||||||
|
|
||||||
|
int lodChunkDist = DhApi.Delayed.configs.graphics().chunkRenderDistance().getValue();
|
||||||
|
int lodBlockDist = lodChunkDist * 16; /* 16 = chunk width in blocks */
|
||||||
|
// sqrt 2 to prevent the corners from being cut off
|
||||||
|
return (float) ((lodBlockDist + 512 /* 512 = region width in blocks */) * Math.sqrt(2));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Current DH logic
|
||||||
|
// uses a farther depth to help when far above the world
|
||||||
|
|
||||||
|
int lodChunkDist = Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistanceRadius.get();
|
||||||
|
int lodBlockDist = lodChunkDist * LodUtil.CHUNK_WIDTH;
|
||||||
|
// * 2 to prevent clipping when high above the world
|
||||||
|
return (lodBlockDist + LodUtil.REGION_WIDTH) * 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|||||||
@@ -17,12 +17,15 @@ public class RollingAverage
|
|||||||
private int index = 0;
|
private int index = 0;
|
||||||
private double sum = 0.0;
|
private double sum = 0.0;
|
||||||
private final Lock arrayLock = new ReentrantLock();
|
private final Lock arrayLock = new ReentrantLock();
|
||||||
|
/** how many items have been added to this average over its lifetime */
|
||||||
|
private long lifetimeCount = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=============//
|
//=============//
|
||||||
// constructor //
|
// constructor //
|
||||||
//=============//
|
//=============//
|
||||||
|
//region
|
||||||
|
|
||||||
public RollingAverage(int size)
|
public RollingAverage(int size)
|
||||||
{
|
{
|
||||||
@@ -35,11 +38,14 @@ public class RollingAverage
|
|||||||
this.values = new double[size];
|
this.values = new double[size];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=======//
|
//=======//
|
||||||
// input //
|
// input //
|
||||||
//=======//
|
//=======//
|
||||||
|
//region
|
||||||
|
|
||||||
public void add(double value)
|
public void add(double value)
|
||||||
{
|
{
|
||||||
@@ -56,6 +62,8 @@ public class RollingAverage
|
|||||||
this.index = (this.index + 1) % this.maxSize;
|
this.index = (this.index + 1) % this.maxSize;
|
||||||
|
|
||||||
this.currentSize = Math.max(this.index+1, this.currentSize);
|
this.currentSize = Math.max(this.index+1, this.currentSize);
|
||||||
|
|
||||||
|
this.lifetimeCount++;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -71,6 +79,7 @@ public class RollingAverage
|
|||||||
this.sum = 0;
|
this.sum = 0;
|
||||||
this.index = 0;
|
this.index = 0;
|
||||||
this.currentSize = 0;
|
this.currentSize = 0;
|
||||||
|
this.lifetimeCount = 0;
|
||||||
Arrays.fill(this.values, 0);
|
Arrays.fill(this.values, 0);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -79,11 +88,14 @@ public class RollingAverage
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//========//
|
//========//
|
||||||
// output //
|
// output //
|
||||||
//========//
|
//========//
|
||||||
|
//region
|
||||||
|
|
||||||
/** Gets the current rolling average. */
|
/** Gets the current rolling average. */
|
||||||
public double getAverage()
|
public double getAverage()
|
||||||
@@ -101,14 +113,23 @@ public class RollingAverage
|
|||||||
/** rounded to two decimals*/
|
/** rounded to two decimals*/
|
||||||
public String getAverageRoundedString() { return String.format("%.2f", this.getAverage()); }
|
public String getAverageRoundedString() { return String.format("%.2f", this.getAverage()); }
|
||||||
|
|
||||||
|
/** how many items have been added to the rolling average since it's last {@link RollingAverage#clear()} */
|
||||||
|
public long getLifetimeCount() { return this.lifetimeCount; }
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//================//
|
//================//
|
||||||
// base overrides //
|
// base overrides //
|
||||||
//================//
|
//================//
|
||||||
|
//region
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() { return "avg: ["+this.getAverageRoundedString()+"], count: ["+this.currentSize+"], max count: ["+this.maxSize+"]."; }
|
public String toString() { return "avg: ["+this.getAverageRoundedString()+"], count: ["+this.currentSize+"], max count: ["+this.maxSize+"]."; }
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+22
-53
@@ -185,24 +185,34 @@ public class PhantomArrayListPool
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// this reference is pointing to null,
|
// this reference is pointing to null,
|
||||||
// the checkout must have been garbage collected,
|
// the checkout was garbage collected
|
||||||
// that means we don't have enough memory
|
|
||||||
if (!lowMemoryWarningLogged)
|
if (!lowMemoryWarningLogged)
|
||||||
{
|
{
|
||||||
lowMemoryWarningLogged = true;
|
// Complain if there isn't much free ram.
|
||||||
|
// Some garbage collectors may remove our soft references unnecessarily
|
||||||
|
// even when there is free space, and this should prevent the warning from
|
||||||
|
// popping up unnecessarily.
|
||||||
|
|
||||||
String message = MinecraftTextFormat.ORANGE + "Distant Horizons: Insufficient memory detected." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
|
long freeMemoryBytes = Runtime.getRuntime().freeMemory();
|
||||||
|
|
||||||
|
long expectedFreeMemoryBytes = 1_073_741_824 /* 1 Gibibyte */ / 8; // 128 MibiBytes
|
||||||
|
if (freeMemoryBytes < expectedFreeMemoryBytes)
|
||||||
|
{
|
||||||
|
lowMemoryWarningLogged = true;
|
||||||
|
|
||||||
|
String message = MinecraftTextFormat.ORANGE + "Distant Horizons: Insufficient memory detected." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
|
||||||
"This may cause stuttering or crashing. \n" +
|
"This may cause stuttering or crashing. \n" +
|
||||||
"Potential causes: \n" +
|
"Potential causes: \n" +
|
||||||
"1. your allocated memory isn't high enough \n" +
|
"1. your allocated memory isn't high enough \n" +
|
||||||
"2. your DH CPU preset is too high \n" +
|
"2. your DH CPU preset is too high \n" +
|
||||||
"3. your DH quality preset is too high \n" +
|
"3. your DH quality preset is too high \n" +
|
||||||
"4. you have other memory hungry mod(s)";
|
"4. you have other memory hungry mod(s)";
|
||||||
|
|
||||||
LOGGER.warn(message);
|
LOGGER.warn(message);
|
||||||
if (Config.Common.Logging.Warning.showPoolInsufficientMemoryWarning.get())
|
if (Config.Common.Logging.Warning.showPoolInsufficientMemoryWarning.get())
|
||||||
{
|
{
|
||||||
ClientApi.INSTANCE.showChatMessageNextFrame(message);
|
ClientApi.INSTANCE.showChatMessageNextFrame(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,9 +329,9 @@ public class PhantomArrayListPool
|
|||||||
pool.returnCheckout(checkout);
|
pool.returnCheckout(checkout);
|
||||||
|
|
||||||
if (pool.logGarbageCollectedStacks
|
if (pool.logGarbageCollectedStacks
|
||||||
&& checkout.allocationStackTrace != null) // stack trace shouldn't be null, but just in case
|
&& checkout.allocationStackTrace != null) // stack trace shouldn't be null, but just in case
|
||||||
{
|
{
|
||||||
putAndIncrementTrackingString(checkout.allocationStackTrace, allocationStackTraceCountPairList);
|
PhantomLoggingHelper.putAndIncrementTrackingString(checkout.allocationStackTrace, allocationStackTraceCountPairList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -353,18 +363,7 @@ public class PhantomArrayListPool
|
|||||||
// log stack traces if present
|
// log stack traces if present
|
||||||
if (pool.logGarbageCollectedStacks)
|
if (pool.logGarbageCollectedStacks)
|
||||||
{
|
{
|
||||||
// high numbers first
|
PhantomLoggingHelper.LogAllocationStackTracePairCounts(LOGGER, allocationStackTraceCountPairList);
|
||||||
allocationStackTraceCountPairList.sort((a, b) -> Integer.compare(b.second.get(), a.second.get()));
|
|
||||||
|
|
||||||
StringBuilder stringBuilder = new StringBuilder();
|
|
||||||
for (int j = 0; j < allocationStackTraceCountPairList.size(); j++)
|
|
||||||
{
|
|
||||||
int count = allocationStackTraceCountPairList.get(j).second.get();
|
|
||||||
String stack = allocationStackTraceCountPairList.get(j).first;
|
|
||||||
|
|
||||||
stringBuilder.append(count).append(". ").append(stack).append("\n");
|
|
||||||
}
|
|
||||||
LOGGER.warn("Stacks: ["+ allocationStackTraceCountPairList.size()+"]\n" + stringBuilder.toString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -379,36 +378,6 @@ public class PhantomArrayListPool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* This was separated out so it could be used for other string pair lists.
|
|
||||||
* James originally had an idea to add a shorter static string
|
|
||||||
* ID to each allocated {@link PhantomArrayListCheckout} as a simpler version of the stack trace,
|
|
||||||
* however it became a bit more difficult and messy than he wanted to deal with, so for now we just
|
|
||||||
* have the stack trace.
|
|
||||||
*/
|
|
||||||
private static void putAndIncrementTrackingString(
|
|
||||||
String key,
|
|
||||||
ArrayList<Pair<String, AtomicInteger>> allocationStackTraceCountPairList)
|
|
||||||
{
|
|
||||||
// sequential search, for the number of elements we're dealing with (less than 20)
|
|
||||||
// this should be sufficiently fast
|
|
||||||
boolean pairFound = false;
|
|
||||||
for (int i = 0; i < allocationStackTraceCountPairList.size(); i++)
|
|
||||||
{
|
|
||||||
Pair<String, AtomicInteger> possiblePair = allocationStackTraceCountPairList.get(i);
|
|
||||||
if (possiblePair.first.equals(key))
|
|
||||||
{
|
|
||||||
possiblePair.second.getAndIncrement();
|
|
||||||
pairFound = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pairFound)
|
|
||||||
{
|
|
||||||
allocationStackTraceCountPairList.add(new Pair<>(key, new AtomicInteger(1)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///endregion
|
///endregion
|
||||||
|
|
||||||
|
|||||||
+232
@@ -0,0 +1,232 @@
|
|||||||
|
package com.seibel.distanthorizons.core.util.objects.pooling;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
|
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
|
||||||
|
import com.seibel.distanthorizons.core.util.ThreadUtil;
|
||||||
|
import com.seibel.distanthorizons.core.util.objects.Pair;
|
||||||
|
|
||||||
|
import java.lang.ref.PhantomReference;
|
||||||
|
import java.lang.ref.Reference;
|
||||||
|
import java.lang.ref.ReferenceQueue;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public class PhantomLoggingHelper
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* This was separated out so it could be used for other string pair lists.
|
||||||
|
* James originally had an idea to add a shorter static string
|
||||||
|
* ID to each allocated {@link PhantomArrayListCheckout} as a simpler version of the stack trace,
|
||||||
|
* however it became a bit more difficult and messy than he wanted to deal with, so for now we just
|
||||||
|
* have the stack trace.
|
||||||
|
*/
|
||||||
|
public static void putAndIncrementTrackingString(
|
||||||
|
String key,
|
||||||
|
ArrayList<Pair<String, AtomicInteger>> allocationStackTraceCountPairList)
|
||||||
|
{
|
||||||
|
// sequential search, for the number of elements we're dealing with (less than 20)
|
||||||
|
// this should be sufficiently fast
|
||||||
|
boolean pairFound = false;
|
||||||
|
for (int i = 0; i < allocationStackTraceCountPairList.size(); i++)
|
||||||
|
{
|
||||||
|
Pair<String, AtomicInteger> possiblePair = allocationStackTraceCountPairList.get(i);
|
||||||
|
if (possiblePair.first.equals(key))
|
||||||
|
{
|
||||||
|
possiblePair.second.getAndIncrement();
|
||||||
|
pairFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pairFound)
|
||||||
|
{
|
||||||
|
allocationStackTraceCountPairList.add(new Pair<>(key, new AtomicInteger(1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LogAllocationStackTracePairCounts(DhLogger logger, ArrayList<Pair<String, AtomicInteger>> allocationStackTraceCountPairList)
|
||||||
|
{
|
||||||
|
// high numbers first
|
||||||
|
allocationStackTraceCountPairList.sort((a, b) -> Integer.compare(b.second.get(), a.second.get()));
|
||||||
|
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
for (int j = 0; j < allocationStackTraceCountPairList.size(); j++)
|
||||||
|
{
|
||||||
|
int count = allocationStackTraceCountPairList.get(j).second.get();
|
||||||
|
String stack = allocationStackTraceCountPairList.get(j).first;
|
||||||
|
|
||||||
|
stringBuilder.append(count).append(". ").append(stack).append("\n");
|
||||||
|
}
|
||||||
|
logger.warn("Stacks: ["+ allocationStackTraceCountPairList.size()+"]\n" + stringBuilder.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// helper classes //
|
||||||
|
//================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be quickly added to a {@link AutoCloseable} implementing
|
||||||
|
* class to confirm it's being properly closed.
|
||||||
|
*/
|
||||||
|
public static class BasicPhantomReference implements AutoCloseable
|
||||||
|
{
|
||||||
|
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
||||||
|
|
||||||
|
/** if enabled the number of GC'ed buffers will be logged */
|
||||||
|
private static final boolean LOG_PHANTOM_RECOVERY = true;
|
||||||
|
/**
|
||||||
|
* If enabled the GC'ed buffers allocation/upload stacks will be logged.
|
||||||
|
* Note: due to how the buffers are often run on the render thread,
|
||||||
|
* these stacks will likely only be of limited use.
|
||||||
|
* For more robust debugging it would likely be best to somehow track
|
||||||
|
* the stacks of where these calls are happening before they're queued
|
||||||
|
* for the render thread.
|
||||||
|
*/
|
||||||
|
private static final boolean LOG_PHANTOM_ALLOCATION_STACKS = true;
|
||||||
|
|
||||||
|
private static final int PHANTOM_REF_CHECK_TIME_IN_MS = 5 * 1000;
|
||||||
|
private static final ReferenceQueue<BasicPhantomReference> PHANTOM_REFERENCE_QUEUE = new ReferenceQueue<>();
|
||||||
|
private static final ConcurrentHashMap<PhantomReference<? extends BasicPhantomReference>, Class<?>> PHANTOM_TO_PARENT_CLASS = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private static final ThreadPoolExecutor CLEANUP_THREAD = ThreadUtil.makeSingleDaemonThreadPool("BasicPhantom Cleanup");
|
||||||
|
|
||||||
|
|
||||||
|
private final Class<?> parentClass;
|
||||||
|
private final PhantomReference<? extends BasicPhantomReference> phantomReference;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==============//
|
||||||
|
// constructors //
|
||||||
|
//==============//
|
||||||
|
//region
|
||||||
|
|
||||||
|
static { CLEANUP_THREAD.execute(() -> runPhantomReferenceCleanupLoop()); }
|
||||||
|
|
||||||
|
public BasicPhantomReference(Class<?> parentClass)
|
||||||
|
{
|
||||||
|
this.parentClass = parentClass;
|
||||||
|
this.phantomReference = new PhantomReference<>(this, PHANTOM_REFERENCE_QUEUE);
|
||||||
|
PHANTOM_TO_PARENT_CLASS.put(this.phantomReference, this.parentClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// base overrides //
|
||||||
|
//================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
this.phantomReference.clear();
|
||||||
|
PHANTOM_TO_PARENT_CLASS.remove(this.phantomReference);
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// static cleanup //
|
||||||
|
//================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
private static void runPhantomReferenceCleanupLoop()
|
||||||
|
{
|
||||||
|
// these arrays are stored here so they don't have to be re-allocated each loop
|
||||||
|
ArrayList<Pair<String, AtomicInteger>> allocationStackTraceCountPairList = new ArrayList<>();
|
||||||
|
ArrayList<Pair<String, AtomicInteger>> parentClassNameCountPairList = new ArrayList<>();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
allocationStackTraceCountPairList.clear();
|
||||||
|
parentClassNameCountPairList.clear();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Thread.sleep(PHANTOM_REF_CHECK_TIME_IN_MS);
|
||||||
|
}
|
||||||
|
catch (InterruptedException ignore) { }
|
||||||
|
|
||||||
|
int collectedCount = 0;
|
||||||
|
|
||||||
|
Reference<? extends BasicPhantomReference> phantomRef = PHANTOM_REFERENCE_QUEUE.poll();
|
||||||
|
while (phantomRef != null)
|
||||||
|
{
|
||||||
|
// destroy the buffer if it hasn't been cleared yet
|
||||||
|
Class<?> parentClass = PHANTOM_TO_PARENT_CLASS.remove((PhantomReference<? extends BasicPhantomReference>)phantomRef); // cast to make IntelliJ happy
|
||||||
|
{
|
||||||
|
String parentClassName = "NULL";
|
||||||
|
if (parentClass != null)
|
||||||
|
{
|
||||||
|
parentClassName = parentClass.getSimpleName();
|
||||||
|
}
|
||||||
|
|
||||||
|
PhantomLoggingHelper.putAndIncrementTrackingString(parentClassName, parentClassNameCountPairList);
|
||||||
|
//LOGGER.info("Phantom collected for class: [" + parentClassName + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//if (LOG_PHANTOM_ALLOCATION_STACKS) // stack trace shouldn't be null, but just in case
|
||||||
|
//{
|
||||||
|
// String stack = BUFFER_ID_TO_ALLOCATION_STRING.get(idRef);
|
||||||
|
// if (stack != null)
|
||||||
|
// {
|
||||||
|
// PhantomLoggingHelper.putAndIncrementTrackingString(stack, allocationStackTraceCountPairList);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
collectedCount++;
|
||||||
|
phantomRef = PHANTOM_REFERENCE_QUEUE.poll();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (LOG_PHANTOM_RECOVERY)
|
||||||
|
{
|
||||||
|
// we only want to log when something has been returned
|
||||||
|
if (collectedCount != 0)
|
||||||
|
{
|
||||||
|
LOGGER.warn("Phantoms collected: ["+ F3Screen.NUMBER_FORMAT.format(collectedCount)+"].");
|
||||||
|
|
||||||
|
PhantomLoggingHelper.LogAllocationStackTracePairCounts(LOGGER, parentClassNameCountPairList);
|
||||||
|
|
||||||
|
//// log stack traces if present
|
||||||
|
//if (LOG_PHANTOM_ALLOCATION_STACKS)
|
||||||
|
//{
|
||||||
|
// PhantomLoggingHelper.LogAllocationStackTracePairCounts(LOGGER, allocationStackTraceCountPairList);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LOGGER.error("Unexpected error in buffer cleanup thread: [" + e.getMessage() + "].", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+12
-1
@@ -86,6 +86,17 @@ public class QuadNode<T>
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** @return the number of non-null direct child nodes */
|
||||||
|
public int getDirectChildCount()
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
if (this.nwChild != null) { count++; }
|
||||||
|
if (this.neChild != null) { count++; }
|
||||||
|
if (this.swChild != null) { count++; }
|
||||||
|
if (this.seChild != null) { count++; }
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use {@link QuadNode#getNonNullChildCount()} if you want the number of non-null child values.
|
* Use {@link QuadNode#getNonNullChildCount()} if you want the number of non-null child values.
|
||||||
*
|
*
|
||||||
@@ -314,7 +325,7 @@ public class QuadNode<T>
|
|||||||
|
|
||||||
public void deleteAllChildren() { this.deleteAllChildren(null); }
|
public void deleteAllChildren() { this.deleteAllChildren(null); }
|
||||||
/** @param removedItemConsumer is only fired for non-null nodes, however the value passed in may be null */
|
/** @param removedItemConsumer is only fired for non-null nodes, however the value passed in may be null */
|
||||||
public void deleteAllChildren(Consumer<? super T> removedItemConsumer)
|
public void deleteAllChildren(@Nullable Consumer<? super T> removedItemConsumer)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
|
|||||||
+120
-35
@@ -19,10 +19,12 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.core.util.objects.quadTree;
|
package com.seibel.distanthorizons.core.util.objects.quadTree;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
|
import com.seibel.distanthorizons.core.render.QuadTree.LodQuadTree;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||||
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
|
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
|
||||||
import com.seibel.distanthorizons.coreapi.util.MathUtil;
|
import com.seibel.distanthorizons.coreapi.util.MathUtil;
|
||||||
@@ -61,6 +63,11 @@ public class QuadTree<T>
|
|||||||
private final MovableGridRingList<QuadNode<T>> topRingList;
|
private final MovableGridRingList<QuadNode<T>> topRingList;
|
||||||
|
|
||||||
private DhBlockPos2D centerBlockPos;
|
private DhBlockPos2D centerBlockPos;
|
||||||
|
/**
|
||||||
|
* defines how many blocks the center needs to move in blocks
|
||||||
|
* before we check for out-of-bound nodes.
|
||||||
|
*/
|
||||||
|
private int blockDistanceForNodeClearing;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -74,10 +81,13 @@ public class QuadTree<T>
|
|||||||
*
|
*
|
||||||
* @param diameterInBlocks equivalent to the distance between the two opposing sides
|
* @param diameterInBlocks equivalent to the distance between the two opposing sides
|
||||||
*/
|
*/
|
||||||
public QuadTree(int diameterInBlocks, DhBlockPos2D centerBlockPos, byte treeLeafDetailLevel)
|
public QuadTree(
|
||||||
|
int diameterInBlocks, int blockDistanceForNodeClearing,
|
||||||
|
DhBlockPos2D centerBlockPos, byte treeLeafDetailLevel)
|
||||||
{
|
{
|
||||||
this.centerBlockPos = centerBlockPos;
|
this.centerBlockPos = centerBlockPos;
|
||||||
this.diameterInBlocks = diameterInBlocks;
|
this.diameterInBlocks = diameterInBlocks;
|
||||||
|
this.blockDistanceForNodeClearing = blockDistanceForNodeClearing;
|
||||||
|
|
||||||
this.treeLeafDetailLevel = treeLeafDetailLevel;
|
this.treeLeafDetailLevel = treeLeafDetailLevel;
|
||||||
// the min detail level must be greater than 0 (to prevent divide by 0 errors) and greater than the maximum detail level
|
// the min detail level must be greater than 0 (to prevent divide by 0 errors) and greater than the maximum detail level
|
||||||
@@ -130,18 +140,22 @@ public class QuadTree<T>
|
|||||||
public int leafNodeCount()
|
public int leafNodeCount()
|
||||||
{
|
{
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (QuadNode<T> node : this.topRingList)
|
for (QuadNode<T> rootNode : this.topRingList)
|
||||||
{
|
{
|
||||||
if (node == null)
|
if (rootNode == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterator<QuadNode<T>> leafNodeIterator = node.getLeafNodeIterator();
|
Iterator<QuadNode<T>> leafNodeIterator = rootNode.getLeafNodeIterator();
|
||||||
while (leafNodeIterator.hasNext())
|
while (leafNodeIterator.hasNext())
|
||||||
{
|
{
|
||||||
leafNodeIterator.next();
|
QuadNode<T> node = leafNodeIterator.next();
|
||||||
count++;
|
if (node != null
|
||||||
|
&& this.isSectionPosInBounds(node.sectionPos))
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,32 +250,32 @@ public class QuadTree<T>
|
|||||||
int ringListPosX = DhSectionPos.getX(rootPos);
|
int ringListPosX = DhSectionPos.getX(rootPos);
|
||||||
int ringListPosZ = DhSectionPos.getZ(rootPos);
|
int ringListPosZ = DhSectionPos.getZ(rootPos);
|
||||||
|
|
||||||
QuadNode<T> topQuadNode = this.topRingList.get(ringListPosX, ringListPosZ);
|
QuadNode<T> rootQuadNode = this.topRingList.get(ringListPosX, ringListPosZ);
|
||||||
if (topQuadNode == null)
|
if (rootQuadNode == null)
|
||||||
{
|
{
|
||||||
if (!setNewValue)
|
if (!setNewValue)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
topQuadNode = new QuadNode<T>(rootPos, this.treeLeafDetailLevel);
|
rootQuadNode = new QuadNode<T>(rootPos, this.treeLeafDetailLevel);
|
||||||
boolean successfullyAdded = this.topRingList.set(ringListPosX, ringListPosZ, topQuadNode);
|
boolean successfullyAdded = this.topRingList.set(ringListPosX, ringListPosZ, rootQuadNode);
|
||||||
if (!successfullyAdded)
|
if (!successfullyAdded)
|
||||||
{
|
{
|
||||||
LodUtil.assertNotReach("Failed to add top quadTree node at position: " + rootPos);
|
LodUtil.assertNotReach("Failed to add root quadTree node at position: ["+DhSectionPos.toString(rootPos)+"]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!DhSectionPos.contains(topQuadNode.sectionPos, pos))
|
if (!DhSectionPos.contains(rootQuadNode.sectionPos, pos))
|
||||||
{
|
{
|
||||||
LodUtil.assertNotReach("failed to get a root node that contains the input position: " + pos + " root node pos: " + topQuadNode.sectionPos);
|
LodUtil.assertNotReach("failed to get a root node that contains the input position: " + pos + " root node pos: " + rootQuadNode.sectionPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QuadNode<T> returnNode = topQuadNode.getNode(pos);
|
QuadNode<T> returnNode = rootQuadNode.getNode(pos);
|
||||||
if (setNewValue)
|
if (setNewValue)
|
||||||
{
|
{
|
||||||
topQuadNode.setValue(pos, newValue);
|
rootQuadNode.setValue(pos, newValue);
|
||||||
}
|
}
|
||||||
return returnNode;
|
return returnNode;
|
||||||
}
|
}
|
||||||
@@ -354,32 +368,103 @@ public class QuadTree<T>
|
|||||||
//================//
|
//================//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
public void setCenterBlockPos(DhBlockPos2D newCenterPos) { this.setCenterBlockPos(newCenterPos, null); }
|
public void setCenterBlockPos(DhBlockPos2D newCenterPos) { this.setCenterBlockPos(newCenterPos, null, null); }
|
||||||
public void setCenterBlockPos(DhBlockPos2D newCenterPos, Consumer<? super T> removedItemConsumer)
|
/**
|
||||||
|
* @param removedConsumer fired when a root node is completely removed from the underlying data structure
|
||||||
|
* @param mutateOutOfBoundConsumer fired when a child node is out of bounds, but not removed from the underlying data structure
|
||||||
|
*/
|
||||||
|
public void setCenterBlockPos(
|
||||||
|
DhBlockPos2D newCenterPos,
|
||||||
|
@Nullable Consumer<? super T> removedConsumer,
|
||||||
|
@Nullable Consumer<? super T> mutateOutOfBoundConsumer)
|
||||||
{
|
{
|
||||||
this.centerBlockPos = newCenterPos;
|
// did we move significantly?
|
||||||
|
boolean ringListMoved = false;
|
||||||
MovableGridRingList.Pos2D expectedCenterPos = new MovableGridRingList.Pos2D(
|
int newCenterPosX = BitShiftUtil.divideByPowerOfTwo(newCenterPos.x, this.treeRootDetailLevel);
|
||||||
BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.x, this.treeRootDetailLevel),
|
int newCenterPosZ = BitShiftUtil.divideByPowerOfTwo(newCenterPos.z, this.treeRootDetailLevel);
|
||||||
BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.z, this.treeRootDetailLevel));
|
if (this.topRingList.getCenter().getX() != newCenterPosX
|
||||||
|
|| this.topRingList.getCenter().getY() != newCenterPosZ)
|
||||||
if (this.topRingList.getCenter().equals(expectedCenterPos))
|
|
||||||
{
|
{
|
||||||
// tree doesn't need to be moved
|
ringListMoved = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// did we move a little bit?
|
||||||
|
boolean recalculateOutOfBoundNodes = false;
|
||||||
|
int centerBlockDistance = this.centerBlockPos.manhattanDist(newCenterPos);
|
||||||
|
if (centerBlockDistance >= this.blockDistanceForNodeClearing)
|
||||||
|
{
|
||||||
|
recalculateOutOfBoundNodes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ringListMoved
|
||||||
|
&& !recalculateOutOfBoundNodes)
|
||||||
|
{
|
||||||
|
// the tree didn't move enough that we need
|
||||||
|
// to re-calculate anything
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// remove out of bounds root nodes
|
|
||||||
this.topRingList.moveTo(expectedCenterPos.getX(), expectedCenterPos.getY(), (quadNode) ->
|
this.centerBlockPos = newCenterPos;
|
||||||
|
|
||||||
|
// remove out of bound root nodes
|
||||||
|
this.topRingList.moveTo(newCenterPosX, newCenterPosZ, (quadNode) ->
|
||||||
{
|
{
|
||||||
if (quadNode != null && removedItemConsumer != null)
|
if (quadNode != null)
|
||||||
{
|
{
|
||||||
quadNode.deleteAllChildren(removedItemConsumer);
|
quadNode.deleteAllChildren(removedConsumer);
|
||||||
|
|
||||||
removedItemConsumer.accept(quadNode.value);
|
if (removedConsumer != null)
|
||||||
|
{
|
||||||
|
removedConsumer.accept(quadNode.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// mutate out of bound child nodes
|
||||||
|
this.topRingList.forEach((rootNode) ->
|
||||||
|
{
|
||||||
|
this.mutateOutOfBoundChildNodes(rootNode, mutateOutOfBoundConsumer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* we don't want to actually remove nodes or node data
|
||||||
|
* since that can cause the {@link LodQuadTree} to
|
||||||
|
* flash low-detail LODs when moving into previously-loaded
|
||||||
|
* LODs, which is really disorienting.
|
||||||
|
*/
|
||||||
|
private void mutateOutOfBoundChildNodes(@Nullable QuadNode<T> quadNode, @Nullable Consumer<? super T> mutateOutOfBoundConsumer)
|
||||||
|
{
|
||||||
|
// nodes shouldn't be null, but just in case
|
||||||
|
if (quadNode == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// go over each child node
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
QuadNode<T> childNode = quadNode.getChildByIndex(i);
|
||||||
|
if (childNode == null
|
||||||
|
|| childNode.value == null)
|
||||||
|
{
|
||||||
|
// no need to go any deeper if this node is already empty
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mutate nodes from the bottom up
|
||||||
|
this.mutateOutOfBoundChildNodes(childNode, mutateOutOfBoundConsumer);
|
||||||
|
|
||||||
|
// mutate this node if out of bounds
|
||||||
|
if (!this.isSectionPosInBounds(childNode.sectionPos))
|
||||||
|
{
|
||||||
|
if (mutateOutOfBoundConsumer != null)
|
||||||
|
{
|
||||||
|
mutateOutOfBoundConsumer.accept(childNode.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final DhBlockPos2D getCenterBlockPos() { return this.centerBlockPos; }
|
public final DhBlockPos2D getCenterBlockPos() { return this.centerBlockPos; }
|
||||||
@@ -484,7 +569,7 @@ public class QuadTree<T>
|
|||||||
|
|
||||||
private class QuadTreeNodeIterator implements Iterator<QuadNode<T>>
|
private class QuadTreeNodeIterator implements Iterator<QuadNode<T>>
|
||||||
{
|
{
|
||||||
private final QuadTreeRootPosIterator rootNodeIterator;
|
private final QuadTreeRootPosIterator rootNodePosIterator;
|
||||||
private Iterator<QuadNode<T>> currentNodeIterator;
|
private Iterator<QuadNode<T>> currentNodeIterator;
|
||||||
|
|
||||||
private QuadNode<T> lastNode = null;
|
private QuadNode<T> lastNode = null;
|
||||||
@@ -497,7 +582,7 @@ public class QuadTree<T>
|
|||||||
|
|
||||||
public QuadTreeNodeIterator(boolean onlyReturnLeaves, @Nullable INodeIteratorStoppingFunc<T> stopIteratingFunc)
|
public QuadTreeNodeIterator(boolean onlyReturnLeaves, @Nullable INodeIteratorStoppingFunc<T> stopIteratingFunc)
|
||||||
{
|
{
|
||||||
this.rootNodeIterator = new QuadTreeRootPosIterator(false, stopIteratingFunc);
|
this.rootNodePosIterator = new QuadTreeRootPosIterator(false, stopIteratingFunc);
|
||||||
this.onlyReturnLeaves = onlyReturnLeaves;
|
this.onlyReturnLeaves = onlyReturnLeaves;
|
||||||
|
|
||||||
this.stopIteratingFunc = stopIteratingFunc;
|
this.stopIteratingFunc = stopIteratingFunc;
|
||||||
@@ -508,7 +593,7 @@ public class QuadTree<T>
|
|||||||
@Override
|
@Override
|
||||||
public boolean hasNext()
|
public boolean hasNext()
|
||||||
{
|
{
|
||||||
if (!this.rootNodeIterator.hasNext()
|
if (!this.rootNodePosIterator.hasNext()
|
||||||
&& this.currentNodeIterator != null
|
&& this.currentNodeIterator != null
|
||||||
&& !this.currentNodeIterator.hasNext())
|
&& !this.currentNodeIterator.hasNext())
|
||||||
{
|
{
|
||||||
@@ -544,9 +629,9 @@ public class QuadTree<T>
|
|||||||
{
|
{
|
||||||
Iterator<QuadNode<T>> nodeIterator = null;
|
Iterator<QuadNode<T>> nodeIterator = null;
|
||||||
while ((nodeIterator == null || !nodeIterator.hasNext())
|
while ((nodeIterator == null || !nodeIterator.hasNext())
|
||||||
&& this.rootNodeIterator.hasNext())
|
&& this.rootNodePosIterator.hasNext())
|
||||||
{
|
{
|
||||||
long sectionPos = this.rootNodeIterator.nextLong();
|
long sectionPos = this.rootNodePosIterator.nextLong();
|
||||||
|
|
||||||
// try-get to prevent concurrency errors if the tree is being moved while we walk through it
|
// try-get to prevent concurrency errors if the tree is being moved while we walk through it
|
||||||
QuadNode<T> rootNode = QuadTree.this.tryGetNode(sectionPos);
|
QuadNode<T> rootNode = QuadTree.this.tryGetNode(sectionPos);
|
||||||
|
|||||||
+8
-1
@@ -73,10 +73,17 @@ public class RateLimitedThreadPoolExecutor extends ThreadPoolExecutor
|
|||||||
{
|
{
|
||||||
super.afterExecute(runnable, throwable);
|
super.afterExecute(runnable, throwable);
|
||||||
|
|
||||||
|
double ratio = this.runTimeRatioConfig.get();
|
||||||
|
if (ratio >= 1.0)
|
||||||
|
{
|
||||||
|
// Avoid sleeping for 0 time
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
long runTime = System.nanoTime() - this.runStartTime.get();
|
long runTime = System.nanoTime() - this.runStartTime.get();
|
||||||
Thread.sleep(TimeUnit.NANOSECONDS.toMillis((long) (runTime / this.runTimeRatioConfig.get() - runTime)));
|
Thread.sleep(TimeUnit.NANOSECONDS.toMillis((long) (runTime / ratio - runTime)));
|
||||||
}
|
}
|
||||||
catch (InterruptedException ignore)
|
catch (InterruptedException ignore)
|
||||||
{
|
{
|
||||||
|
|||||||
+7
-7
@@ -179,13 +179,13 @@ public class ThreadPoolUtil
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
PriorityTaskPicker.Executor executor = getRenderLoadingExecutor();
|
//PriorityTaskPicker.Executor executor = getRenderLoadingExecutor();
|
||||||
if (executor != null
|
//if (executor != null
|
||||||
&& executor.getQueueSize() > 0)
|
// && executor.getQueueSize() > 0)
|
||||||
{
|
//{
|
||||||
// pause if LODs are being loaded for rendering
|
// // pause if LODs are being loaded for rendering
|
||||||
return false;
|
// return false;
|
||||||
}
|
//}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ public abstract class AbstractDhWorld implements IDhWorld, Closeable
|
|||||||
String levelCountStr = F3Screen.NUMBER_FORMAT.format(this.getLoadedLevelCount());
|
String levelCountStr = F3Screen.NUMBER_FORMAT.format(this.getLoadedLevelCount());
|
||||||
|
|
||||||
String readOnlyStr = "";
|
String readOnlyStr = "";
|
||||||
if (DhApiWorldProxy.INSTANCE.getReadOnly())
|
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||||
{
|
{
|
||||||
readOnlyStr += " - ReadOnly";
|
readOnlyStr += " - ReadOnly";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,6 +113,23 @@ public class DhApiWorldProxy implements IDhApiWorldProxy
|
|||||||
return this.isReadOnly;
|
return this.isReadOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns false if no world is loaded.
|
||||||
|
* Can be used in places where the world state might be a bit more questionable
|
||||||
|
* without having to worry about the {@link IllegalStateException} thrown by
|
||||||
|
* {@link DhApiWorldProxy#getReadOnly()}
|
||||||
|
*/
|
||||||
|
public boolean tryGetReadOnly()
|
||||||
|
{
|
||||||
|
if (SharedApi.getAbstractDhWorld() == null)
|
||||||
|
{
|
||||||
|
// no world is loaded, use the default value for the next world
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.isReadOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//================//
|
//================//
|
||||||
|
|||||||
@@ -21,13 +21,20 @@ package com.seibel.distanthorizons.core.world;
|
|||||||
|
|
||||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||||
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
||||||
|
import com.seibel.distanthorizons.core.generation.PregenManager;
|
||||||
import com.seibel.distanthorizons.core.level.DhServerLevel;
|
import com.seibel.distanthorizons.core.level.DhServerLevel;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
public class DhServerWorld extends AbstractDhServerWorld<DhServerLevel>
|
public class DhServerWorld extends AbstractDhServerWorld<DhServerLevel>
|
||||||
{
|
{
|
||||||
|
private final PregenManager pregenManager = new PregenManager();
|
||||||
|
public PregenManager getPregenManager() { return this.pregenManager; }
|
||||||
|
|
||||||
|
|
||||||
//==============//
|
//==============//
|
||||||
// constructors //
|
// constructors //
|
||||||
//==============//
|
//==============//
|
||||||
@@ -88,4 +95,17 @@ public class DhServerWorld extends AbstractDhServerWorld<DhServerLevel>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
CompletableFuture<Void> runningPregen = this.pregenManager.getRunningPregen();
|
||||||
|
if (runningPregen != null)
|
||||||
|
{
|
||||||
|
LOGGER.info("Stopping the running pregen task.");
|
||||||
|
runningPregen.cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.close();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-5
@@ -65,6 +65,7 @@ public interface IWrapperFactory extends IDhApiWrapperFactory, IBindable
|
|||||||
|
|
||||||
IBlockStateWrapper deserializeBlockStateWrapper(String str, ILevelWrapper levelWrapper) throws IOException;
|
IBlockStateWrapper deserializeBlockStateWrapper(String str, ILevelWrapper levelWrapper) throws IOException;
|
||||||
IBlockStateWrapper getAirBlockStateWrapper();
|
IBlockStateWrapper getAirBlockStateWrapper();
|
||||||
|
IBlockStateWrapper getWaterBlockStateWrapper(ILevelWrapper levelWrapper);
|
||||||
default IBlockStateWrapper deserializeBlockStateWrapperOrGetDefault(String str, ILevelWrapper levelWrapper)
|
default IBlockStateWrapper deserializeBlockStateWrapperOrGetDefault(String str, ILevelWrapper levelWrapper)
|
||||||
{
|
{
|
||||||
IBlockStateWrapper blockState;
|
IBlockStateWrapper blockState;
|
||||||
@@ -92,10 +93,10 @@ public interface IWrapperFactory extends IDhApiWrapperFactory, IBindable
|
|||||||
*/
|
*/
|
||||||
ObjectOpenHashSet<IBlockStateWrapper> getRendererIgnoredCaveBlocks(ILevelWrapper levelWrapper);
|
ObjectOpenHashSet<IBlockStateWrapper> getRendererIgnoredCaveBlocks(ILevelWrapper levelWrapper);
|
||||||
|
|
||||||
|
ObjectOpenHashSet<IBlockStateWrapper> getWaterSubsurfaceReplacementBlocks(ILevelWrapper levelWrapper);
|
||||||
|
ObjectOpenHashSet<IBlockStateWrapper> getWaterSurfaceReplacementBlocks(ILevelWrapper levelWrapper);
|
||||||
/** clears the cached values */
|
/** clears the cached values */
|
||||||
void resetRendererIgnoredCaveBlocks();
|
void resetCachedIgnoredBlocksSets();
|
||||||
/** clears the cached values */
|
|
||||||
void resetRendererIgnoredBlocksSet();
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -109,9 +110,7 @@ public interface IWrapperFactory extends IDhApiWrapperFactory, IBindable
|
|||||||
|
|
||||||
IVertexBufferWrapper createVboWrapper(String name);
|
IVertexBufferWrapper createVboWrapper(String name);
|
||||||
ILodContainerUniformBufferWrapper createLodContainerUniformWrapper();
|
ILodContainerUniformBufferWrapper createLodContainerUniformWrapper();
|
||||||
|
|
||||||
IDhGenericObjectVertexBufferContainer createGenericObjectVboContainer();
|
IDhGenericObjectVertexBufferContainer createGenericObjectVboContainer();
|
||||||
|
|
||||||
IDhGenericRenderer createGenericRenderer();
|
IDhGenericRenderer createGenericRenderer();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
@@ -20,6 +20,7 @@
|
|||||||
package com.seibel.distanthorizons.core.wrapperInterfaces.block;
|
package com.seibel.distanthorizons.core.wrapperInterfaces.block;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper;
|
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper;
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBlockColorOverrideEvent;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
@@ -59,6 +60,11 @@ public interface IBlockStateWrapper extends IDhApiBlockStateWrapper
|
|||||||
* IE Iron, diamond, gold, etc.
|
* IE Iron, diamond, gold, etc.
|
||||||
*/
|
*/
|
||||||
boolean isBeaconBaseBlock();
|
boolean isBeaconBaseBlock();
|
||||||
|
/**
|
||||||
|
* if true this block can have its color overridden
|
||||||
|
* by {@link DhApiBlockColorOverrideEvent}
|
||||||
|
*/
|
||||||
|
boolean allowApiColorOverride();
|
||||||
|
|
||||||
Color getMapColor();
|
Color getMapColor();
|
||||||
Color getBeaconTintColor();
|
Color getBeaconTintColor();
|
||||||
|
|||||||
+5
-2
@@ -21,6 +21,7 @@ package com.seibel.distanthorizons.core.wrapperInterfaces.minecraft;
|
|||||||
|
|
||||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
|
||||||
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
||||||
|
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||||
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
|
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
|
||||||
|
|
||||||
@@ -113,17 +114,19 @@ public interface IMinecraftClientWrapper extends IBindable
|
|||||||
void crashMinecraft(String errorMessage, Throwable exception);
|
void crashMinecraft(String errorMessage, Throwable exception);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is only designed to be used internally by {@link GLProxy}
|
* This is only designed to be used internally by {@link RenderThreadTaskHandler}
|
||||||
* since it handles task frame limiting (reducing/preventing stuttering)
|
* since it handles task frame limiting (reducing/preventing stuttering)
|
||||||
* whereas this method causes the task to be run whenever MC decides to
|
* whereas this method causes the task to be run whenever MC decides to
|
||||||
* (likely all at once the next frame). <br><br>
|
* (likely all at once the next frame). <br><br>
|
||||||
*
|
*
|
||||||
* Any tasks submitted here will be run on the render thread.
|
* Any tasks submitted here will be run on the render thread.
|
||||||
*
|
*
|
||||||
* @see GLProxy#queueRunningOnRenderThread(Runnable)
|
* @see RenderThreadTaskHandler
|
||||||
*/
|
*/
|
||||||
void executeOnRenderThread(Runnable runnable);
|
void executeOnRenderThread(Runnable runnable);
|
||||||
|
|
||||||
|
void showDialog(String title, String message, String dialogType, String iconType);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=============//
|
//=============//
|
||||||
|
|||||||
-2
@@ -68,8 +68,6 @@ public interface IMinecraftRenderWrapper extends IBindable
|
|||||||
|
|
||||||
Color getSkyColor();
|
Color getSkyColor();
|
||||||
|
|
||||||
double getFov(float partialTicks);
|
|
||||||
|
|
||||||
/** Measured in chunks */
|
/** Measured in chunks */
|
||||||
int getRenderDistance();
|
int getRenderDistance();
|
||||||
|
|
||||||
|
|||||||
+17
-11
@@ -21,20 +21,26 @@ package com.seibel.distanthorizons.core.wrapperInterfaces.minecraft;
|
|||||||
|
|
||||||
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
|
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
|
||||||
|
|
||||||
/**
|
|
||||||
* @author James Seibel
|
|
||||||
* @version 11-20-2021
|
|
||||||
*/
|
|
||||||
public interface IProfilerWrapper extends IBindable
|
public interface IProfilerWrapper extends IBindable
|
||||||
{
|
{
|
||||||
// Note to self:
|
IProfileBlock push(String newSection);
|
||||||
// if "unspecified" shows up in the pie chart, it is
|
|
||||||
// possibly because the amount of time between sections
|
|
||||||
// is too small for the profiler to measures
|
|
||||||
void push(String newSection);
|
|
||||||
|
|
||||||
void popPush(String newSection);
|
void popPush(String newSection);
|
||||||
|
|
||||||
void pop();
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// helper classes //
|
||||||
|
//================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
/** used to auto-pop blocks to prevent accidentally unevenly pushing/popping */
|
||||||
|
public static interface IProfileBlock extends AutoCloseable
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void close();
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
-2
@@ -70,8 +70,6 @@ public abstract class AbstractOptifineAccessor implements IOptifineAccessor
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean optifinePresent() { return getOptifineFogField() != null; }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//===================//
|
//===================//
|
||||||
|
|||||||
+16
@@ -1,6 +1,7 @@
|
|||||||
package com.seibel.distanthorizons.core.wrapperInterfaces.render;
|
package com.seibel.distanthorizons.core.wrapperInterfaces.render;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
|
import com.seibel.distanthorizons.core.jar.EPlatform;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer;
|
import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.IDhGenericObjectVertexBufferContainer;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.IDhGenericObjectVertexBufferContainer;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.ILodContainerUniformBufferWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.ILodContainerUniformBufferWrapper;
|
||||||
@@ -18,6 +19,16 @@ public abstract class AbstractDhRenderApiDefinition implements IBindable
|
|||||||
/** Used for debugging */
|
/** Used for debugging */
|
||||||
public abstract String getApiName();
|
public abstract String getApiName();
|
||||||
|
|
||||||
|
private final boolean useSingleIbo = (EPlatform.get() != EPlatform.MACOS);
|
||||||
|
/**
|
||||||
|
* Mac has a problem where binding an IBO that's longer than the VBO
|
||||||
|
* can cause OpenGL to render past the end of the VBO, throwing random junk
|
||||||
|
* on the screen. <br>
|
||||||
|
* To fix this we have to use individual IBOs for each VBO, which
|
||||||
|
* is slower due to having to construct new IBOs.
|
||||||
|
*/
|
||||||
|
public boolean useSingleIbo() { return this.useSingleIbo; }
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
@@ -36,6 +47,11 @@ public abstract class AbstractDhRenderApiDefinition implements IBindable
|
|||||||
public abstract IDhVanillaFadeRenderer getVanillaFadeRenderer();
|
public abstract IDhVanillaFadeRenderer getVanillaFadeRenderer();
|
||||||
public abstract IDhTestTriangleRenderer getTestTriangleRenderer();
|
public abstract IDhTestTriangleRenderer getTestTriangleRenderer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this will NOT run on the render thread.
|
||||||
|
* Render thread setup tasks should be handled
|
||||||
|
* during the first rendered frame.
|
||||||
|
*/
|
||||||
public void bindRenderers()
|
public void bindRenderers()
|
||||||
{
|
{
|
||||||
SingletonInjector.INSTANCE.bind(AbstractDhRenderApiDefinition.class, this);
|
SingletonInjector.INSTANCE.bind(AbstractDhRenderApiDefinition.class, this);
|
||||||
|
|||||||
+4
-1
@@ -19,13 +19,16 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.core.wrapperInterfaces.render.objects;
|
package com.seibel.distanthorizons.core.wrapperInterfaces.render.objects;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.render.AbstractDhRenderApiDefinition;
|
||||||
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
|
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
public interface IVertexBufferWrapper extends IBindable, AutoCloseable
|
public interface IVertexBufferWrapper extends IBindable, AutoCloseable
|
||||||
{
|
{
|
||||||
void upload(ByteBuffer buffer, int vertexCount);
|
void uploadVertexBuffer(ByteBuffer buffer, int vertexCount);
|
||||||
|
/** Does nothing if {@link AbstractDhRenderApiDefinition#useSingleIbo()} returns true */
|
||||||
|
void uploadIndexBuffer(ByteBuffer buffer, int vertexCount);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void close();
|
void close();
|
||||||
|
|||||||
+5
-1
@@ -23,10 +23,14 @@ import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderRegist
|
|||||||
import com.seibel.distanthorizons.core.render.RenderParams;
|
import com.seibel.distanthorizons.core.render.RenderParams;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
|
||||||
|
|
||||||
public interface IDhGenericRenderer extends IDhApiCustomRenderRegister
|
public interface IDhGenericRenderer extends IDhApiCustomRenderRegister, AutoCloseable
|
||||||
{
|
{
|
||||||
void render(RenderParams renderEventParam, IProfilerWrapper profiler, boolean renderingWithSsao);
|
void render(RenderParams renderEventParam, IProfilerWrapper profiler, boolean renderingWithSsao);
|
||||||
|
|
||||||
String getVboRenderDebugMenuString();
|
String getVboRenderDebugMenuString();
|
||||||
|
|
||||||
|
@Override void close(); // override to remove "throws exception"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,58 +172,27 @@
|
|||||||
"This is the same as vanilla Biome Blending settings for LOD area. \n\nNote that anything other than '0' will greatly effect Lod building time\nand increase triangle count. The cost on chunk generation speed is also \nquite large if set to too high.\n\n'0' equals to Vanilla Biome Blending of '1x1', \n'1' equals to Vanilla Biome Blending of '3x3', \n'2' equals to Vanilla Biome Blending of '5x5'... \n",
|
"This is the same as vanilla Biome Blending settings for LOD area. \n\nNote that anything other than '0' will greatly effect Lod building time\nand increase triangle count. The cost on chunk generation speed is also \nquite large if set to too high.\n\n'0' equals to Vanilla Biome Blending of '1x1', \n'1' equals to Vanilla Biome Blending of '3x3', \n'2' equals to Vanilla Biome Blending of '5x5'... \n",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"distanthorizons.config.client.advanced.graphics.ssao":
|
|
||||||
"Ambient Occlusion",
|
|
||||||
|
|
||||||
"distanthorizons.config.client.advanced.graphics.ssao.enableSsao":
|
"distanthorizons.config.client.advanced.graphics.enableSsao":
|
||||||
"Enable Ambient Occlusion",
|
"Enable Ambient Occlusion",
|
||||||
"distanthorizons.config.client.advanced.graphics.ssao.enableSsao.@tooltip":
|
"distanthorizons.config.client.advanced.graphics.enableSsao.@tooltip":
|
||||||
"Ambient Occlusion adds depth to the lighting of blocks.",
|
"Ambient Occlusion adds depth to the lighting of blocks.",
|
||||||
"distanthorizons.config.client.advanced.graphics.ssao.sampleCount":
|
|
||||||
"Sample Count",
|
|
||||||
"distanthorizons.config.client.advanced.graphics.ssao.sampleCount.@tooltip":
|
|
||||||
"Determines how many points in space are sampled for the occlusion test. \nHigher numbers will improve quality and reduce banding, but will increase GPU load.",
|
|
||||||
"distanthorizons.config.client.advanced.graphics.ssao.radius":
|
|
||||||
"Radius",
|
|
||||||
"distanthorizons.config.client.advanced.graphics.ssao.radius.@tooltip":
|
|
||||||
"Determines the radius Screen Space Ambient Occlusion is applied, measured in blocks.",
|
|
||||||
"distanthorizons.config.client.advanced.graphics.ssao.strength":
|
|
||||||
"Strength",
|
|
||||||
"distanthorizons.config.client.advanced.graphics.ssao.strength.@tooltip":
|
|
||||||
"Determines how dark the Screen Space Ambient Occlusion effect will be.",
|
|
||||||
"distanthorizons.config.client.advanced.graphics.ssao.bias":
|
|
||||||
"Bias",
|
|
||||||
"distanthorizons.config.client.advanced.graphics.ssao.bias.@tooltip":
|
|
||||||
"Increasing the value can reduce banding at the cost of reducing the strength of the effect.",
|
|
||||||
"distanthorizons.config.client.advanced.graphics.ssao.minLight":
|
|
||||||
"Min Light",
|
|
||||||
"distanthorizons.config.client.advanced.graphics.ssao.minLight.@tooltip":
|
|
||||||
"Determines how dark the occlusion shadows can be. \n0 = totally black at the corners \n1 = no shadow",
|
|
||||||
"distanthorizons.config.client.advanced.graphics.ssao.blurRadius":
|
|
||||||
"Blur Radius",
|
|
||||||
"distanthorizons.config.client.advanced.graphics.ssao.blurRadius.@tooltip":
|
|
||||||
"The radius, measured in pixels, that blurring is calculated for the SSAO. \nHigher numbers will reduce banding at the cost of GPU performance.",
|
|
||||||
"distanthorizons.config.client.advanced.graphics.ssao.fadeDistanceInBlocks":
|
|
||||||
"Fade Distance",
|
|
||||||
"distanthorizons.config.client.advanced.graphics.ssao.fadeDistanceInBlocks.@tooltip":
|
|
||||||
"The distance in blocks from the camera where the SSAO will fade out to. \nThis is done to prevent banding and noise at extreme distances.",
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"distanthorizons.config.client.advanced.graphics.genericRendering":
|
"distanthorizons.config.client.advanced.graphics.genericRendering":
|
||||||
"Generic Object Rendering",
|
"Generic Object Rendering",
|
||||||
"distanthorizons.config.client.advanced.graphics.genericRendering.enableGenericRendering":
|
"distanthorizons.config.client.advanced.graphics.genericRendering.enableGenericRendering":
|
||||||
"Enable Rendering",
|
"Enable Generic Rendering",
|
||||||
"distanthorizons.config.client.advanced.graphics.genericRendering.enableGenericRendering.@tooltip":
|
"distanthorizons.config.client.advanced.graphics.genericRendering.enableGenericRendering.@tooltip":
|
||||||
"If true non terrain objects will be rendered in DH's terrain. \nThis includes beacon beams and clouds.",
|
"If true non terrain objects will be rendered in DH's terrain. \nThis includes beacon beams and clouds.",
|
||||||
"distanthorizons.config.client.advanced.graphics.genericRendering.enableBeaconRendering":
|
"distanthorizons.config.client.advanced.graphics.genericRendering.enableBeaconRendering":
|
||||||
"Enable Beacon Rendering",
|
"Enable Beacon Rendering",
|
||||||
"distanthorizons.config.client.advanced.graphics.genericRendering.beaconRenderHeight":
|
"distanthorizons.config.client.advanced.graphics.genericRendering.beaconRenderHeight":
|
||||||
"Beacon render height",
|
"Beacon render height",
|
||||||
"distanthorizons.config.client.advanced.graphics.genericRendering.beaconRenderHeight.@tooltip":
|
"distanthorizons.config.client.advanced.graphics.genericRendering.beaconRenderHeight.@tooltip":
|
||||||
"Sets the maximum height at which beacons will render. \nThis will only affect new beacons coming into LOD render distance. \nBeacons currently visible in LOD chunks will not be affected.",
|
"Sets the maximum height at which beacons will render. \nThis will only affect new beacons coming into LOD render distance. \nBeacons currently visible in LOD chunks will not be affected.",
|
||||||
"distanthorizons.config.client.advanced.graphics.genericRendering.expandDistantBeacons":
|
"distanthorizons.config.client.advanced.graphics.genericRendering.expandDistantBeacons":
|
||||||
"Expand Distant Beacons",
|
"Expand Distant Beacons",
|
||||||
"distanthorizons.config.client.advanced.graphics.genericRendering.expandDistantBeacons.@tooltip":
|
"distanthorizons.config.client.advanced.graphics.genericRendering.expandDistantBeacons.@tooltip":
|
||||||
"If true LOD beacon beams will be rendered wider at extreme distances, \nmaking them easier to see. \nIf false all LOD beacon beams will only ever be 1 block wide.",
|
"If true LOD beacon beams will be rendered wider at extreme distances, \nmaking them easier to see. \nIf false all LOD beacon beams will only ever be 1 block wide.",
|
||||||
@@ -369,7 +338,7 @@
|
|||||||
"distanthorizons.config.client.advanced.graphics.culling.reduceOverdrawWithFastMovement.@tooltip":
|
"distanthorizons.config.client.advanced.graphics.culling.reduceOverdrawWithFastMovement.@tooltip":
|
||||||
"If set to true the overdraw prevention radius will get closer \nto the camera when flying/moving quickly. \n\nThis helps reduce issues where Minecraft can't load or \ngenerate chunks fast enough to keep up with DH.",
|
"If set to true the overdraw prevention radius will get closer \nto the camera when flying/moving quickly. \n\nThis helps reduce issues where Minecraft can't load or \ngenerate chunks fast enough to keep up with DH.",
|
||||||
"distanthorizons.config.client.advanced.graphics.culling.enableCaveCulling":
|
"distanthorizons.config.client.advanced.graphics.culling.enableCaveCulling":
|
||||||
"Cave Culling",
|
"Enable Cave Culling",
|
||||||
"distanthorizons.config.client.advanced.graphics.culling.enableCaveCulling.@tooltip":
|
"distanthorizons.config.client.advanced.graphics.culling.enableCaveCulling.@tooltip":
|
||||||
"If enabled caves will be culled \n\nAdditional Info: Currently this cull all faces \n with skylight value of 0 under the cave culling height in dimensions that \n do not have a ceiling. \n",
|
"If enabled caves will be culled \n\nAdditional Info: Currently this cull all faces \n with skylight value of 0 under the cave culling height in dimensions that \n do not have a ceiling. \n",
|
||||||
"distanthorizons.config.client.advanced.graphics.culling.caveCullingHeight":
|
"distanthorizons.config.client.advanced.graphics.culling.caveCullingHeight":
|
||||||
@@ -391,12 +360,20 @@
|
|||||||
"distanthorizons.config.client.advanced.graphics.culling.ignoredRenderBlockCsv":
|
"distanthorizons.config.client.advanced.graphics.culling.ignoredRenderBlockCsv":
|
||||||
"Ignored Block CSV",
|
"Ignored Block CSV",
|
||||||
"distanthorizons.config.client.advanced.graphics.culling.ignoredRenderBlockCsv.@tooltip":
|
"distanthorizons.config.client.advanced.graphics.culling.ignoredRenderBlockCsv.@tooltip":
|
||||||
"A comma separated list of block resource locations that won't be rendered by DH. \nNote: air is always included in this list.",
|
"A comma separated list of block resource locations that won't be rendered by DH. \nAir is always included in this list. \n\nNote: \nIf you see gaps, or holes you may have to change \nworldCompression to [MERGE_SAME_BLOCKS] and re-generate the LODs.",
|
||||||
"distanthorizons.config.client.advanced.graphics.culling.ignoredRenderCaveBlockCsv":
|
"distanthorizons.config.client.advanced.graphics.culling.ignoredRenderCaveBlockCsv":
|
||||||
"Ignored Cave Block CSV",
|
"Ignored Cave Block CSV",
|
||||||
"distanthorizons.config.client.advanced.graphics.culling.ignoredRenderCaveBlockCsv.@tooltip":
|
"distanthorizons.config.client.advanced.graphics.culling.ignoredRenderCaveBlockCsv.@tooltip":
|
||||||
"A comma separated list of block resource locations that shouldn't be rendered \nif they are in a 0 sky light underground area. \nNote: air is always included in this list.",
|
"A comma separated list of block resource locations that shouldn't be rendered \nif they are in a 0 sky light underground area. \nAir is always included in this list. \n\nDefaults to an empty list since most cave blocks will be automatically ignored due to being: \ntransparent, non-solid, or liquids, but new blocks can be added here if needed.",
|
||||||
|
"distanthorizons.config.client.advanced.graphics.culling.waterSubSurfaceBlockReplacementCsv":
|
||||||
|
"Water Sub-Surface Replacement",
|
||||||
|
"distanthorizons.config.client.advanced.graphics.culling.waterSubSurfaceBlockReplacementCsv.@tooltip":
|
||||||
|
"A comma separated list of block resource locations that will be replaced by water \nif they're visible on the water's surface.",
|
||||||
|
"distanthorizons.config.client.advanced.graphics.culling.waterSurfaceBlockReplacementCsv":
|
||||||
|
"Water Surface Replacement",
|
||||||
|
"distanthorizons.config.client.advanced.graphics.culling.waterSurfaceBlockReplacementCsv.@tooltip":
|
||||||
|
"A comma separated list of block resource locations that will be removed \nwhen on top of water.",
|
||||||
|
|
||||||
"distanthorizons.config.client.advanced.graphics.overrideVanillaGraphicsSettings":
|
"distanthorizons.config.client.advanced.graphics.overrideVanillaGraphicsSettings":
|
||||||
"Override Vanilla Settings",
|
"Override Vanilla Settings",
|
||||||
"distanthorizons.config.client.advanced.graphics.overrideVanillaGraphicsSettings.@tooltip":
|
"distanthorizons.config.client.advanced.graphics.overrideVanillaGraphicsSettings.@tooltip":
|
||||||
@@ -454,8 +431,8 @@
|
|||||||
|
|
||||||
"distanthorizons.config.client.advanced.debugging.rendererMode":
|
"distanthorizons.config.client.advanced.debugging.rendererMode":
|
||||||
"Renderer Mode",
|
"Renderer Mode",
|
||||||
"distanthorizons.config.client.advanced.debugging.debugRendering":
|
"distanthorizons.config.client.advanced.debugging.debugRenderingColors":
|
||||||
"Debug Rendering",
|
"Debug Rendering Colors",
|
||||||
"distanthorizons.config.client.advanced.debugging.lodOnlyMode":
|
"distanthorizons.config.client.advanced.debugging.lodOnlyMode":
|
||||||
"Only Render LODs",
|
"Only Render LODs",
|
||||||
"distanthorizons.config.client.advanced.debugging.lodOnlyMode.@tooltip":
|
"distanthorizons.config.client.advanced.debugging.lodOnlyMode.@tooltip":
|
||||||
@@ -556,6 +533,26 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"distanthorizons.config.client.advanced.debugging.positionFinderDebugging":
|
||||||
|
"Position Finder",
|
||||||
|
|
||||||
|
"distanthorizons.config.client.advanced.debugging.positionFinderDebugging.positionFinderEnable":
|
||||||
|
"Enable Position Finder",
|
||||||
|
"distanthorizons.config.client.advanced.debugging.positionFinderDebugging.positionFinderDetailLevel":
|
||||||
|
"Absolute Detail Level",
|
||||||
|
"distanthorizons.config.client.advanced.debugging.positionFinderDebugging.positionFinderXPos":
|
||||||
|
"Pos X",
|
||||||
|
"distanthorizons.config.client.advanced.debugging.positionFinderDebugging.positionFinderZPos":
|
||||||
|
"Pos Z",
|
||||||
|
"distanthorizons.config.client.advanced.debugging.positionFinderDebugging.positionFinderMinBlockY":
|
||||||
|
"Min Block Y",
|
||||||
|
"distanthorizons.config.client.advanced.debugging.positionFinderDebugging.positionFinderMaxBlockY":
|
||||||
|
"Max Block Y",
|
||||||
|
"distanthorizons.config.client.advanced.debugging.positionFinderDebugging.positionFinderMarginPercent":
|
||||||
|
"Margin %",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"distanthorizons.config.client.advanced.debugging.f3Screen":
|
"distanthorizons.config.client.advanced.debugging.f3Screen":
|
||||||
"F3 Screen",
|
"F3 Screen",
|
||||||
|
|
||||||
@@ -565,6 +562,8 @@
|
|||||||
"Player Section Pos Detail Level",
|
"Player Section Pos Detail Level",
|
||||||
"distanthorizons.config.client.advanced.debugging.f3Screen.showThreadPools":
|
"distanthorizons.config.client.advanced.debugging.f3Screen.showThreadPools":
|
||||||
"Show Thread Pools",
|
"Show Thread Pools",
|
||||||
|
"distanthorizons.config.client.advanced.debugging.f3Screen.showRenderThreadTasks":
|
||||||
|
"Show Render Thread Tasks",
|
||||||
"distanthorizons.config.client.advanced.debugging.f3Screen.showCombinedObjectPools":
|
"distanthorizons.config.client.advanced.debugging.f3Screen.showCombinedObjectPools":
|
||||||
"Show Combined Object Pools",
|
"Show Combined Object Pools",
|
||||||
"distanthorizons.config.client.advanced.debugging.f3Screen.showSeparatedObjectPools":
|
"distanthorizons.config.client.advanced.debugging.f3Screen.showSeparatedObjectPools":
|
||||||
@@ -1043,7 +1042,7 @@
|
|||||||
|
|
||||||
"distanthorizons.config.enum.EDhApiRendererMode.DEFAULT":
|
"distanthorizons.config.enum.EDhApiRendererMode.DEFAULT":
|
||||||
"Default",
|
"Default",
|
||||||
"distanthorizons.config.enum.EDhApiRendererMode.DEBUG":
|
"distanthorizons.config.enum.EDhApiRendererMode.DEBUG_TRIANGLE":
|
||||||
"Debug Triangle",
|
"Debug Triangle",
|
||||||
"distanthorizons.config.enum.EDhApiRendererMode.DISABLED":
|
"distanthorizons.config.enum.EDhApiRendererMode.DISABLED":
|
||||||
"Disabled",
|
"Disabled",
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ void main()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// the DH texture will have white if nothing was written to that pixel.
|
// ignore anything that DH hasn't drawn to
|
||||||
if (dhColor == vec4(1))
|
if (dhColor.a == 0.0f)
|
||||||
{
|
{
|
||||||
// if not done vanilla clouds will render incorrectly at night
|
// if not done vanilla clouds will render incorrectly at night
|
||||||
dhColor = combinedMcDhColor;
|
dhColor = combinedMcDhColor;
|
||||||
|
|||||||
@@ -28,9 +28,32 @@ layout (std140) uniform fragUniformBlock
|
|||||||
vec3 calcViewPosition(float fragmentDepth, mat4 invMvmProj)
|
vec3 calcViewPosition(float fragmentDepth, mat4 invMvmProj)
|
||||||
{
|
{
|
||||||
// normalized device coordinates
|
// normalized device coordinates
|
||||||
vec4 ndc = vec4(TexCoord.xy, fragmentDepth, 1.0);
|
vec4 ndc = vec4(
|
||||||
|
TexCoord.x, // UV [0,1]
|
||||||
|
TexCoord.y,
|
||||||
|
fragmentDepth, // depth [0,1]
|
||||||
|
1.0
|
||||||
|
);
|
||||||
|
// AKA: remap the [0,1] UV coordinates and depth value
|
||||||
|
// into the [-1,1] positions used by
|
||||||
|
// the NDC cube rendering stage
|
||||||
ndc.xyz = ndc.xyz * 2.0 - 1.0;
|
ndc.xyz = ndc.xyz * 2.0 - 1.0;
|
||||||
|
|
||||||
|
vec4 eyeCoord = invMvmProj * ndc;
|
||||||
|
return eyeCoord.xyz / eyeCoord.w;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO vulkan
|
||||||
|
vec3 calcReversedZViewPosition(float fragmentDepth, mat4 invMvmProj)
|
||||||
|
{
|
||||||
|
// Vulkan NDC: xy in [-1,+1], z already in [0,1] — don't remap z
|
||||||
|
vec4 ndc = vec4(
|
||||||
|
TexCoord.x * 2.0 - 1.0, // UV [0,1] -> NDC [-1,+1]
|
||||||
|
TexCoord.y * 2.0 - 1.0,
|
||||||
|
fragmentDepth, // no remapping needed, this depth is already in the [0,1] range
|
||||||
|
1.0 // w=1 placeholder for matrix multiplication
|
||||||
|
);
|
||||||
|
|
||||||
vec4 eyeCoord = invMvmProj * ndc;
|
vec4 eyeCoord = invMvmProj * ndc;
|
||||||
return eyeCoord.xyz / eyeCoord.w;
|
return eyeCoord.xyz / eyeCoord.w;
|
||||||
}
|
}
|
||||||
@@ -56,8 +79,7 @@ void main()
|
|||||||
|
|
||||||
|
|
||||||
// ignore anything that DH hasn't drawn to
|
// ignore anything that DH hasn't drawn to
|
||||||
// We don't use DH's depth here because it would prevent the fade from running before DH has loaded
|
if (dhColor.a == 0.0f)
|
||||||
if (dhColor == vec4(1))
|
|
||||||
{
|
{
|
||||||
// if not done vanilla clouds will render incorrectly at night
|
// if not done vanilla clouds will render incorrectly at night
|
||||||
dhColor = combinedMcDhColor;
|
dhColor = combinedMcDhColor;
|
||||||
@@ -72,12 +94,13 @@ void main()
|
|||||||
{
|
{
|
||||||
fragColor = vec4(combinedMcDhColor.rgb, 0.0);
|
fragColor = vec4(combinedMcDhColor.rgb, 0.0);
|
||||||
}
|
}
|
||||||
// a fragment depth of "1" means the fragment wasn't drawn to,
|
// // a fragment depth of "1" means the fragment wasn't drawn to,
|
||||||
// we only want to fade vanilla rendered objects, not to the sky or LODs
|
// // we only want to fade vanilla rendered objects, not to the sky or LODs
|
||||||
else if (mcFragmentDepth < 1.0)
|
// else if (mcFragmentDepth < 1.0)
|
||||||
|
else if (mcFragmentDepth > 0)
|
||||||
{
|
{
|
||||||
// fade based on distance from the camera
|
// fade based on distance from the camera
|
||||||
vec3 mcVertexWorldPos = calcViewPosition(mcFragmentDepth, uMcInvMvmProj);
|
vec3 mcVertexWorldPos = calcReversedZViewPosition(mcFragmentDepth, uMcInvMvmProj);
|
||||||
float mcFragmentDistance = length(mcVertexWorldPos.xzy);
|
float mcFragmentDistance = length(mcVertexWorldPos.xzy);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#version 150 core
|
#version 150 core
|
||||||
|
|
||||||
in vec2 vPosition;
|
in vec2 vPosition;
|
||||||
in vec4 vColor;
|
|
||||||
|
|
||||||
out vec4 fColor;
|
out vec4 fColor;
|
||||||
out vec2 TexCoord;
|
out vec2 TexCoord;
|
||||||
|
|||||||
+5
-5
@@ -36,11 +36,11 @@ void main()
|
|||||||
vec4 combinedMcDhColor = texture(uMcColorTexture, TexCoord);
|
vec4 combinedMcDhColor = texture(uMcColorTexture, TexCoord);
|
||||||
// just the DH render pass
|
// just the DH render pass
|
||||||
vec4 dhColor = texture(uDhColorTexture, TexCoord);
|
vec4 dhColor = texture(uDhColorTexture, TexCoord);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// the DH texture will have white if nothing was written to that pixel.
|
// ignore anything that DH hasn't drawn to
|
||||||
if (dhColor == vec4(1))
|
if (dhColor.a == 0.0f)
|
||||||
{
|
{
|
||||||
// if not done vanilla clouds will render incorrectly at night
|
// if not done vanilla clouds will render incorrectly at night
|
||||||
dhColor = combinedMcDhColor;
|
dhColor = combinedMcDhColor;
|
||||||
+1
-2
@@ -52,8 +52,7 @@ void main()
|
|||||||
|
|
||||||
|
|
||||||
// ignore anything that DH hasn't drawn to
|
// ignore anything that DH hasn't drawn to
|
||||||
// We don't use DH's depth here because it would prevent the fade from running before DH has loaded
|
if (dhColor.a == 0.0f)
|
||||||
if (dhColor == vec4(1))
|
|
||||||
{
|
{
|
||||||
// if not done vanilla clouds will render incorrectly at night
|
// if not done vanilla clouds will render incorrectly at night
|
||||||
dhColor = combinedMcDhColor;
|
dhColor = combinedMcDhColor;
|
||||||
@@ -113,12 +113,13 @@ void main()
|
|||||||
float fadeDistance = uFadeDistanceInBlocks;
|
float fadeDistance = uFadeDistanceInBlocks;
|
||||||
if (distanceFromCamera < fadeDistance)
|
if (distanceFromCamera < fadeDistance)
|
||||||
{
|
{
|
||||||
#ifdef GL_ARB_derivative_control
|
// TODO vulkan
|
||||||
// Get higher precision derivatives when available
|
// #ifdef GL_ARB_derivative_control
|
||||||
vec3 viewNormal = cross(dFdxFine(viewPos.xyz), dFdyFine(viewPos.xyz));
|
// // Get higher precision derivatives when available
|
||||||
#else
|
// vec3 viewNormal = cross(dFdxFine(viewPos.xyz), dFdyFine(viewPos.xyz));
|
||||||
|
// #else
|
||||||
vec3 viewNormal = cross(dFdx(viewPos.xyz), dFdy(viewPos.xyz));
|
vec3 viewNormal = cross(dFdx(viewPos.xyz), dFdy(viewPos.xyz));
|
||||||
#endif
|
// #endif
|
||||||
|
|
||||||
viewNormal = normalize(viewNormal);
|
viewNormal = normalize(viewNormal);
|
||||||
occlusion = GetSpiralOcclusion(TexCoord, viewPos, viewNormal);
|
occlusion = GetSpiralOcclusion(TexCoord, viewPos, viewNormal);
|
||||||
|
|||||||
@@ -14,49 +14,22 @@ public class TestBlockStateWrapper implements IBlockStateWrapper
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override public boolean isAir() { return false; }
|
||||||
public boolean isAir()
|
@Override public boolean isSolid() { return true; }
|
||||||
{ return false; }
|
@Override public boolean isLiquid() { return false; }
|
||||||
@Override
|
@Override public String getSerialString() { return this.name; }
|
||||||
public boolean isSolid()
|
@Override public int getOpacity() { return 15; }
|
||||||
{ return true; }
|
@Override public int getLightEmission() { return 0; }
|
||||||
@Override
|
@Override public byte getMaterialId() { return 0; }
|
||||||
public boolean isLiquid()
|
@Override public boolean isBeaconBlock() { return false; }
|
||||||
{ return false; }
|
@Override public boolean isBeaconTintBlock() { return false; }
|
||||||
@Override
|
@Override public boolean allowsBeaconBeamPassage() { return false; }
|
||||||
public String getSerialString()
|
@Override public boolean isBeaconBaseBlock() { return false; }
|
||||||
{ return this.name; }
|
@Override public boolean allowApiColorOverride() { return false; }
|
||||||
@Override
|
@Override public Color getMapColor() { return Color.MAGENTA; }
|
||||||
public int getOpacity()
|
@Override public Color getBeaconTintColor() { return Color.MAGENTA; }
|
||||||
{ return 15; }
|
|
||||||
@Override
|
|
||||||
public int getLightEmission()
|
|
||||||
{ return 0; }
|
|
||||||
@Override
|
|
||||||
public byte getMaterialId()
|
|
||||||
{ return 0; }
|
|
||||||
@Override
|
|
||||||
public boolean isBeaconBlock()
|
|
||||||
{ return false; }
|
|
||||||
@Override
|
|
||||||
public boolean isBeaconTintBlock()
|
|
||||||
{ return false; }
|
|
||||||
@Override
|
|
||||||
public boolean allowsBeaconBeamPassage()
|
|
||||||
{ return false; }
|
|
||||||
@Override
|
|
||||||
public boolean isBeaconBaseBlock()
|
|
||||||
{ return false; }
|
|
||||||
@Override
|
|
||||||
public Color getMapColor()
|
|
||||||
{ return Color.MAGENTA; }
|
|
||||||
@Override
|
|
||||||
public Color getBeaconTintColor()
|
|
||||||
{ return Color.MAGENTA; }
|
|
||||||
|
|
||||||
@Override
|
@Override public Object getWrappedMcObject() { return this; }
|
||||||
public Object getWrappedMcObject()
|
|
||||||
{ return this; }
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user