Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| b0e7c31964 | |||
| 2e906b57c4 |
+39
-19
@@ -23,43 +23,42 @@ dependencies {
|
||||
testImplementation "junit:junit:4.13"
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
// required for basic shadowJar setup
|
||||
configurations = [project.configurations.shadow]
|
||||
java {
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
task addSourcesToCompiledJar(type: ShadowJar) {
|
||||
task createReleaseApiJar(type: ShadowJar) {
|
||||
|
||||
def sourceJarPath = "build/libs/DistantHorizons-api-${rootProject.versionStr}-sources.jar"
|
||||
def secondJarFile = file(sourceJarPath)
|
||||
mustRunAfter sourcesJar
|
||||
dependsOn shadowJar
|
||||
|
||||
// the compiled "-all" jar is used since it's available at the time this task is run
|
||||
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 {
|
||||
System.out.println("Adding source files from: \n" +
|
||||
"[" + sourceJarPath + "] to compiled API jar: \n" +
|
||||
System.out.println("Adding class files from: \n" +
|
||||
"[" + compiledJarPath + "] to source API jar: \n" +
|
||||
"[" + shadowJar.archiveFile.get().asFile + "]")
|
||||
|
||||
// Validate the input JAR file
|
||||
if (!secondJarFile.exists()) {
|
||||
throw new GradleException("Second JAR file not found: [${secondJarFile}]")
|
||||
if (!compiledJarFile.exists()) {
|
||||
throw new GradleException("Compiled JAR file not found: [${compiledJarFile}]")
|
||||
}
|
||||
}
|
||||
|
||||
// Set the name of the combined JAR file
|
||||
archiveFileName.set("DistantHorizonsApi-${rootProject.api_version}.jar")
|
||||
|
||||
// Set the destination directory for the combined JAR file
|
||||
destinationDirectory = file('build/libs/merged/')
|
||||
archiveFileName.set("DistantHorizonsApi-${rootProject.api_version}-combined.jar") // jar name
|
||||
destinationDirectory = file('build/libs/') // jar location
|
||||
|
||||
// Set the input JAR files to be combined
|
||||
from sourceSets.main.allJava
|
||||
from {
|
||||
configurations.shadow.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
project.configurations.shadow.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
}
|
||||
|
||||
// set the jars to merge
|
||||
from shadowJar.archiveFile.get().asFile
|
||||
from secondJarFile
|
||||
// add the class files
|
||||
from zipTree(compiledJarFile)
|
||||
|
||||
// alternative method to Include the source files in the combined JAR
|
||||
// and/or see which files are being included
|
||||
@@ -93,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 {
|
||||
options {
|
||||
@@ -103,3 +109,17 @@ javadoc {
|
||||
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())
|
||||
|
||||
|
||||
|
||||
+7
-1
@@ -19,6 +19,8 @@
|
||||
|
||||
package com.seibel.distanthorizons.api.interfaces;
|
||||
|
||||
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper;
|
||||
|
||||
/**
|
||||
* Implemented by wrappers so developers can
|
||||
* 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
|
||||
* to use <code>obj.getClass()</code> when in your IDE
|
||||
* 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();
|
||||
|
||||
|
||||
+10
-1
@@ -23,7 +23,10 @@ import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
|
||||
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
|
||||
* @version 2023-6-11
|
||||
@@ -39,6 +42,12 @@ public interface IDhApiBlockStateWrapper extends IDhApiUnsafeWrapper
|
||||
/** @since API 1.0.0 */
|
||||
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
|
||||
* as defined by DH's serialization methods.
|
||||
|
||||
+6
@@ -24,4 +24,10 @@ public interface IDhApiTerrainDataCache extends AutoCloseable
|
||||
*/
|
||||
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>
|
||||
* Will return {@link DhApiResult#success} = false and {@link DhApiResult#payload} = -1 if the texture hasn't been created yet.
|
||||
* 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
|
||||
* or a rendering API other than OpenGL is in use.
|
||||
*/
|
||||
DhApiResult<Integer> getDhDepthTextureId();
|
||||
|
||||
/**
|
||||
* Returns the 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.
|
||||
* 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
|
||||
* or a rendering API other than OpenGL is in use
|
||||
*/
|
||||
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>
|
||||
* 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
|
||||
* @version 2023-1-31
|
||||
|
||||
+3
@@ -31,7 +31,10 @@ import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhAp
|
||||
* @author James Seibel
|
||||
* @version 2025-6-9
|
||||
* @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>
|
||||
{
|
||||
/** 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 { setColor(255, 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
|
||||
* @version 2024-3-2
|
||||
* @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
|
||||
public abstract class DhApiColorDepthTextureCreatedEvent implements IDhApiEvent<DhApiColorDepthTextureCreatedEvent.EventParam>
|
||||
|
||||
@@ -32,20 +32,25 @@ public final class ModInfo
|
||||
|
||||
/** Incremented every time any packets are added, changed or removed, with a few exceptions. */
|
||||
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 */
|
||||
public static final String NAME = "DistantHorizons";
|
||||
/** Human-readable version of NAME */
|
||||
public static final String READABLE_NAME = "Distant Horizons";
|
||||
public static final String VERSION = "3.0.0-b-dev";
|
||||
public static final String VERSION = "3.0.2-b";
|
||||
/** Returns true if the current build is an unstable developer build, false otherwise. */
|
||||
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 */
|
||||
public static final int API_MAJOR_VERSION = 6;
|
||||
/** 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 */
|
||||
public static final int API_PATCH_VERSION = 0;
|
||||
|
||||
|
||||
+11
-1
@@ -17,7 +17,7 @@
|
||||
* 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.*;
|
||||
|
||||
@@ -89,6 +89,16 @@ public class ColorUtil
|
||||
/** @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; }
|
||||
|
||||
/** @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)
|
||||
@@ -50,7 +50,6 @@ dependencies {
|
||||
compileOnly("io.netty:netty-buffer:${rootProject.netty_version}")
|
||||
compileOnly("org.jetbrains:annotations:16.0.2")
|
||||
compileOnly("com.google.code.findbugs:jsr305:3.0.2")
|
||||
compileOnly("com.google.common:google-collect:0.5")
|
||||
compileOnly("com.google.guava:guava:31.1-jre")
|
||||
|
||||
// DH's bundled libraries (shadowed + relocated in loader jars)
|
||||
|
||||
@@ -54,7 +54,7 @@ public class Initializer
|
||||
|
||||
|
||||
|
||||
public static void init()
|
||||
public static void preConfigInit()
|
||||
{
|
||||
//============================//
|
||||
// check referenced libraries //
|
||||
@@ -177,6 +177,11 @@ public class Initializer
|
||||
|
||||
//endregion
|
||||
|
||||
}
|
||||
|
||||
/** fired after DH's config has been set up */
|
||||
public static void postConfigInit()
|
||||
{
|
||||
|
||||
|
||||
//==============================//
|
||||
@@ -238,8 +243,7 @@ public class Initializer
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -145,6 +145,7 @@ public class ClientApi
|
||||
private Vec3d lastCameraPosForSpeedCheck = new Vec3d();
|
||||
private long msSinceLastSpeedCheck = 0L;
|
||||
|
||||
public static long firstRenderTimeMs = 0;
|
||||
|
||||
|
||||
|
||||
@@ -159,7 +160,7 @@ public class ClientApi
|
||||
//==============//
|
||||
// world events //
|
||||
//==============//
|
||||
///region
|
||||
//region
|
||||
|
||||
/**
|
||||
* May be fired slightly before or after the associated
|
||||
@@ -237,14 +238,14 @@ public class ClientApi
|
||||
this.waitingClientLevels.clear();
|
||||
}
|
||||
|
||||
///endregion
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// level events //
|
||||
//==============//
|
||||
///region
|
||||
//region
|
||||
|
||||
public void clientLevelUnloadEvent(IClientLevelWrapper level)
|
||||
{
|
||||
@@ -356,14 +357,14 @@ public class ClientApi
|
||||
}
|
||||
}
|
||||
|
||||
///endregion
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
//============//
|
||||
// networking //
|
||||
//============//
|
||||
///region
|
||||
//region
|
||||
|
||||
/**
|
||||
* Forwards a decoded message into the registered handlers.
|
||||
@@ -396,14 +397,14 @@ public class ClientApi
|
||||
}
|
||||
}
|
||||
|
||||
///endregion
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
//===============//
|
||||
// LOD rendering //
|
||||
//===============//
|
||||
///region
|
||||
//region
|
||||
|
||||
/** Should be called before {@link ClientApi#renderDeferredLodsForShaders} */
|
||||
public void renderLods() { this.renderLodLayer(false); }
|
||||
@@ -414,261 +415,259 @@ public class ClientApi
|
||||
*/
|
||||
public void renderDeferredLodsForShaders() { this.renderLodLayer(true); }
|
||||
|
||||
public static long firstRenderTimeMs = 0;
|
||||
|
||||
private void renderLodLayer(boolean renderingDeferredLayer)
|
||||
{
|
||||
IProfilerWrapper profiler = MC_CLIENT.getProfiler();
|
||||
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)
|
||||
try (IProfilerWrapper.IProfileBlock dhRender_profile = profiler.push("DH-RenderLevel"))
|
||||
{
|
||||
profiler.push("DH render thread tasks");
|
||||
|
||||
|
||||
|
||||
//===============//
|
||||
// chat messages //
|
||||
//===============//
|
||||
//===========//
|
||||
// debugging //
|
||||
//===========//
|
||||
//region
|
||||
|
||||
this.sendQueuedChatMessages();
|
||||
//DhApiTerrainDataRepo.asyncDebugMethod(
|
||||
// RENDER_STATE.clientLevelWrapper,
|
||||
// MC_CLIENT.getPlayerBlockPos().getX(),
|
||||
// MC_CLIENT.getPlayerBlockPos().getY(),
|
||||
// MC_CLIENT.getPlayerBlockPos().getZ()
|
||||
//);
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
//======================//
|
||||
// GL Proxy queued jobs //
|
||||
//======================//
|
||||
//=====================//
|
||||
// render thread tasks //
|
||||
//=====================//
|
||||
//region
|
||||
|
||||
try
|
||||
// only run these tasks once per frame
|
||||
if (!renderingDeferredLayer)
|
||||
{
|
||||
// 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);
|
||||
try (IProfilerWrapper.IProfileBlock renderTask_profile = profiler.push("DH render thread tasks"))
|
||||
{
|
||||
//===============//
|
||||
// chat messages //
|
||||
//===============//
|
||||
|
||||
this.sendQueuedChatMessages();
|
||||
|
||||
|
||||
|
||||
//======================//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
//==============//
|
||||
// camera speed //
|
||||
//==============//
|
||||
|
||||
long nowMs = System.currentTimeMillis();
|
||||
if (this.msSinceLastSpeedCheck + MIN_MS_BETWEEN_SPEED_CHECKS < nowMs)
|
||||
|
||||
//=================//
|
||||
// parameter setup //
|
||||
//=================//
|
||||
//region
|
||||
|
||||
EDhApiRenderPass renderPass;
|
||||
if (DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
|
||||
{
|
||||
// calc time since last check
|
||||
double secSinceLastCheck = (nowMs - this.msSinceLastSpeedCheck) / 1_000.0;
|
||||
this.msSinceLastSpeedCheck = nowMs;
|
||||
|
||||
// get the distance traveled since last frame
|
||||
Vec3d camPos = MC_RENDER.getCameraExactPosition();
|
||||
double distanceInBlocks = camPos.getDistance(this.lastCameraPosForSpeedCheck);
|
||||
double speed = distanceInBlocks / secSinceLastCheck;
|
||||
|
||||
// record new values for next check
|
||||
this.cameraSpeedRollingAverage.add(speed);
|
||||
this.lastCameraPosForSpeedCheck = camPos;
|
||||
}
|
||||
|
||||
|
||||
profiler.pop();
|
||||
}
|
||||
|
||||
///endregion
|
||||
|
||||
|
||||
|
||||
|
||||
//=================//
|
||||
// parameter setup //
|
||||
//=================//
|
||||
///region
|
||||
|
||||
EDhApiRenderPass renderPass;
|
||||
if (DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
|
||||
{
|
||||
if (renderingDeferredLayer)
|
||||
{
|
||||
renderPass = EDhApiRenderPass.TRANSPARENT;
|
||||
if (renderingDeferredLayer)
|
||||
{
|
||||
renderPass = EDhApiRenderPass.TRANSPARENT;
|
||||
}
|
||||
else
|
||||
{
|
||||
renderPass = EDhApiRenderPass.OPAQUE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
renderPass = EDhApiRenderPass.OPAQUE;
|
||||
}
|
||||
}
|
||||
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);
|
||||
renderPass = EDhApiRenderPass.OPAQUE_AND_TRANSPARENT;
|
||||
}
|
||||
|
||||
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
|
||||
// partially complete info, but there isn't a better option at the moment
|
||||
RenderParams renderParams = new RenderParams(renderPass, RENDER_STATE);
|
||||
|
||||
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DISABLED)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
///endregion
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
//===========//
|
||||
// rendering //
|
||||
//===========//
|
||||
///region
|
||||
//============//
|
||||
// validation //
|
||||
//============//
|
||||
//region
|
||||
|
||||
try
|
||||
{
|
||||
// render pass //
|
||||
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT)
|
||||
if (firstRenderTimeMs == 0)
|
||||
{
|
||||
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);
|
||||
if (!renderingCancelled)
|
||||
{
|
||||
LodRenderer.INSTANCE.render(renderParams, profiler);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
if (!DhApi.Delayed.renderProxy.getDeferTransparentRendering())
|
||||
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)
|
||||
{
|
||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterRenderEvent.class, null);
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
boolean renderingCancelled = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeDeferredRenderEvent.class, renderParams);
|
||||
if (!renderingCancelled)
|
||||
if (!renderingDeferredLayer)
|
||||
{
|
||||
LodRenderer.INSTANCE.renderDeferred(renderParams, profiler);
|
||||
}
|
||||
IDhMetaRenderer metaRenderer = SingletonInjector.INSTANCE.get(IDhMetaRenderer.class);
|
||||
IDhTestTriangleRenderer testRenderer = SingletonInjector.INSTANCE.get(IDhTestTriangleRenderer.class);
|
||||
if (testRenderer != null
|
||||
&& metaRenderer != null)
|
||||
{
|
||||
// meta renderer needed for render state/texture
|
||||
// for setup on some APIs (IE openGL)
|
||||
metaRenderer.runRenderPassSetup(renderParams);
|
||||
|
||||
testRenderer.render(renderParams);
|
||||
|
||||
if (DhApi.Delayed.renderProxy.getDeferTransparentRendering())
|
||||
{
|
||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterRenderEvent.class, null);
|
||||
metaRenderer.runRenderPassCleanup(renderParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
RATE_LIMITED_LOGGER.warn("Unable to find singleton [" + IDhTestTriangleRenderer.class.getSimpleName() + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception e)
|
||||
{
|
||||
if (!renderingDeferredLayer)
|
||||
{
|
||||
IDhMetaRenderer metaRenderer = SingletonInjector.INSTANCE.get(IDhMetaRenderer.class);
|
||||
IDhTestTriangleRenderer testRenderer = SingletonInjector.INSTANCE.get(IDhTestTriangleRenderer.class);
|
||||
if (testRenderer != null
|
||||
&& metaRenderer != null)
|
||||
{
|
||||
// meta renderer needed for render state/texture
|
||||
// for setup on some APIs (IE openGL)
|
||||
metaRenderer.runRenderPassSetup(renderParams);
|
||||
this.rendererDisabledBecauseOfExceptions = true;
|
||||
LOGGER.error("Unexpected Renderer error in render pass [" + renderPass + "]. Error: " + e.getMessage(), e);
|
||||
|
||||
testRenderer.render(renderParams);
|
||||
|
||||
metaRenderer.runRenderPassCleanup(renderParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
RATE_LIMITED_LOGGER.warn("Unable to find singleton [" + IDhTestTriangleRenderer.class.getSimpleName() + "]");
|
||||
}
|
||||
}
|
||||
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "" + MinecraftTextFormat.BOLD + "ERROR: Distant Horizons renderer has encountered an exception!" + MinecraftTextFormat.CLEAR_FORMATTING);
|
||||
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
|
||||
}
|
||||
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);
|
||||
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 //
|
||||
//================//
|
||||
///region
|
||||
//region
|
||||
|
||||
/**
|
||||
* The first fade pass.
|
||||
@@ -731,14 +730,14 @@ public class ClientApi
|
||||
}
|
||||
}
|
||||
|
||||
///endregion
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
//==========//
|
||||
// keyboard //
|
||||
//==========//
|
||||
///region
|
||||
//region
|
||||
|
||||
/** Trigger once on key press, with CLIENT PLAYER. */
|
||||
public void keyPressedEvent(int glfwKey)
|
||||
@@ -767,14 +766,14 @@ public class ClientApi
|
||||
}
|
||||
}
|
||||
|
||||
///endregion
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
//======//
|
||||
// chat //
|
||||
//======//
|
||||
///region
|
||||
//region
|
||||
|
||||
private void sendQueuedChatMessages()
|
||||
{
|
||||
@@ -908,7 +907,7 @@ public class ClientApi
|
||||
*/
|
||||
public void showOverlayMessageNextFrame(String message) { this.overlayMessageQueueForNextFrame.add(message); }
|
||||
|
||||
///endregion
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ public class ServerApi
|
||||
|
||||
public void serverPlayerJoinEvent(IServerPlayerWrapper player)
|
||||
{
|
||||
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly())
|
||||
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -136,7 +136,7 @@ public class ServerApi
|
||||
}
|
||||
public void serverPlayerDisconnectEvent(IServerPlayerWrapper player)
|
||||
{
|
||||
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly())
|
||||
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -150,7 +150,7 @@ public class ServerApi
|
||||
}
|
||||
public void serverPlayerLevelChangeEvent(IServerPlayerWrapper player, IServerLevelWrapper originLevel, IServerLevelWrapper destinationLevel)
|
||||
{
|
||||
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly())
|
||||
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -170,7 +170,7 @@ public class ServerApi
|
||||
*/
|
||||
public void pluginMessageReceived(IServerPlayerWrapper player, @NotNull AbstractNetworkMessage message)
|
||||
{
|
||||
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly())
|
||||
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -74,7 +74,6 @@ public class SharedApi
|
||||
//region
|
||||
|
||||
private SharedApi() { }
|
||||
public static void init() { Initializer.init(); }
|
||||
|
||||
//endregion
|
||||
|
||||
@@ -205,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.
|
||||
// This may happen if the client world and client level load events happen out of order
|
||||
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;
|
||||
}
|
||||
|
||||
// ignore updates if the world is read-only
|
||||
if (DhApiWorldProxy.INSTANCE.getReadOnly())
|
||||
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -225,7 +224,7 @@ public class SharedApi
|
||||
{
|
||||
// the client level isn't loaded yet
|
||||
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;
|
||||
|
||||
@@ -31,7 +31,7 @@ import com.seibel.distanthorizons.core.config.types.enums.*;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
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.minecraft.IMinecraftSharedWrapper;
|
||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
@@ -1210,9 +1210,11 @@ public class Config
|
||||
});
|
||||
public static void onButtonPressed()
|
||||
{
|
||||
IMinecraftClientWrapper mcClient = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||
|
||||
LOGGER.info("Attempting to show tinyfd message box...");
|
||||
boolean buttonPress = NativeDialogUtil.showDialog("Button pressed!", "UITester dialog", "ok", "info");
|
||||
LOGGER.info("dialog returned with ["+(buttonPress ? "TRUE" : "FALSE")+"]");
|
||||
mcClient.showDialog("Button pressed!", "UITester dialog", "ok", "info");
|
||||
LOGGER.info("dialog closed");
|
||||
}
|
||||
|
||||
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.TimerTask;
|
||||
|
||||
public class ReloadLodsConfigEventHandler implements IConfigListener
|
||||
public class ReloadLodsConfigEventHandler extends AbstractDelayedConfigEventHandler
|
||||
{
|
||||
/**
|
||||
* should be used for user facing UI options
|
||||
* 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 */
|
||||
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 //
|
||||
//=============//
|
||||
//region
|
||||
|
||||
public ReloadLodsConfigEventHandler(long timeoutInMs)
|
||||
{
|
||||
this.timeoutInMs = timeoutInMs;
|
||||
}
|
||||
public ReloadLodsConfigEventHandler(long timeoutInMs) { super(timeoutInMs); }
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
//========//
|
||||
// events //
|
||||
//========//
|
||||
//region
|
||||
|
||||
@Override
|
||||
public void onConfigValueSet()
|
||||
{
|
||||
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()
|
||||
public void onConfigTimeout()
|
||||
{
|
||||
IDhApiRenderProxy renderProxy = DhApi.Delayed.renderProxy;
|
||||
if (renderProxy != null)
|
||||
@@ -101,5 +65,8 @@ public class ReloadLodsConfigEventHandler implements IConfigListener
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
+11
-3
@@ -32,7 +32,9 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
||||
import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
||||
|
||||
public class RenderBlockCacheCsvHandler implements IConfigListener
|
||||
import java.util.Timer;
|
||||
|
||||
public class RenderBlockCacheCsvHandler extends AbstractDelayedConfigEventHandler
|
||||
{
|
||||
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
||||
|
||||
@@ -43,18 +45,22 @@ public class RenderBlockCacheCsvHandler implements IConfigListener
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
//region
|
||||
|
||||
/** private since we only ever need one handler at a time */
|
||||
private RenderBlockCacheCsvHandler() { }
|
||||
private RenderBlockCacheCsvHandler() { super(AbstractDelayedConfigEventHandler.DEFAULT_TIMEOUT_IN_MS); }
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
//=================//
|
||||
// config handling //
|
||||
//=================//
|
||||
//region
|
||||
|
||||
@Override
|
||||
public void onConfigValueSet()
|
||||
public void onConfigTimeout()
|
||||
{
|
||||
IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
|
||||
if (wrapperFactory != null)
|
||||
@@ -64,6 +70,8 @@ public class RenderBlockCacheCsvHandler implements IConfigListener
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
||||
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
||||
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.RenderDataPointUtil;
|
||||
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnRenderView;
|
||||
|
||||
+1
-1
@@ -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.pos.blockPos.DhBlockPos;
|
||||
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.RenderDataPointUtil;
|
||||
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnRenderView;
|
||||
|
||||
+3
-3
@@ -259,9 +259,9 @@ public class LodBufferContainer implements AutoCloseable
|
||||
{
|
||||
ByteBuffer buffer = vertexBuffers.get(i);
|
||||
int size = buffer.limit() - buffer.position();
|
||||
int vertexCount = size / LodQuadBuilder.BYTES_PER_VERTEX;
|
||||
|
||||
ByteBuffer indexBuffer = IndexBufferBuilder.createBuffer(vertexCount);
|
||||
int maxVertexCount = size / LodQuadBuilder.BYTES_PER_VERTEX;
|
||||
int quadCount = (maxVertexCount / 4);
|
||||
ByteBuffer indexBuffer = IndexBufferBuilder.createBuffer(quadCount);
|
||||
indexBuffers.add(indexBuffer);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
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.world.IClientLevelWrapper;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
+1
@@ -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.IClientLevelWrapper;
|
||||
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.LongOpenHashSet;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
|
||||
+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.file.structure.ISaveStructure;
|
||||
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.ERetrievalResultState;
|
||||
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.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||
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.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.generation.tasks.ERetrievalResultState;
|
||||
import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue;
|
||||
|
||||
+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/>.
|
||||
*/
|
||||
|
||||
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.pos.blockPos.DhBlockPos2D;
|
||||
@@ -42,6 +42,8 @@ import java.util.concurrent.CompletableFuture;
|
||||
* Used by both world gen and server networking.
|
||||
*
|
||||
* @see LodQuadTree
|
||||
* @see AbstractLodRequestState
|
||||
* @see LodRequestModule
|
||||
*/
|
||||
public interface IFullDataSourceRetrievalQueue extends Closeable
|
||||
{
|
||||
+57
-179
@@ -17,39 +17,35 @@
|
||||
* 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.generation.IFullDataSourceRetrievalQueue;
|
||||
import com.seibel.distanthorizons.core.level.*;
|
||||
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 com.seibel.distanthorizons.core.world.DhApiWorldProxy;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Handles both single-player/server-side world gen and client side LOD requests.
|
||||
*
|
||||
* @see AbstractLodRequestState
|
||||
* @see IFullDataSourceRetrievalQueue
|
||||
*/
|
||||
public class LodRequestModule implements Closeable
|
||||
{
|
||||
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
||||
|
||||
|
||||
private final IDhLevel level;
|
||||
private final GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener onWorldGenCompleteListener;
|
||||
private final ThreadPoolExecutor tickerThread;
|
||||
|
||||
@@ -63,6 +59,7 @@ public class LodRequestModule implements Closeable
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
//region
|
||||
|
||||
public LodRequestModule(
|
||||
IDhLevel level,
|
||||
@@ -71,29 +68,50 @@ public class LodRequestModule implements Closeable
|
||||
Supplier<? extends AbstractLodRequestState> worldGenStateSupplier
|
||||
)
|
||||
{
|
||||
this.level = level;
|
||||
this.onWorldGenCompleteListener = onWorldGenCompleteListener;
|
||||
this.dataSourceProvider = dataSourceProvider;
|
||||
this.worldGenStateSupplier = worldGenStateSupplier;
|
||||
|
||||
String levelId = level.getLevelWrapper().getDhIdentifier();
|
||||
String levelId = this.level.getLevelWrapper().getDhIdentifier();
|
||||
this.tickerThread = ThreadUtil.makeSingleDaemonThreadPool("Request Module Ticker ["+levelId+"]");
|
||||
this.tickerThread.execute(this::tickLoop);
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
//=========//
|
||||
// ticking //
|
||||
//=========//
|
||||
//region
|
||||
|
||||
private void tickLoop()
|
||||
{
|
||||
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())
|
||||
{
|
||||
Thread.sleep(20);
|
||||
this.tick();
|
||||
try
|
||||
{
|
||||
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) { }
|
||||
@@ -101,8 +119,22 @@ public class LodRequestModule implements Closeable
|
||||
private void tick()
|
||||
{
|
||||
boolean shouldDoWorldGen = this.onWorldGenCompleteListener.shouldDoWorldGen();
|
||||
|
||||
// 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();
|
||||
if (shouldDoWorldGen && !isWorldGenRunning)
|
||||
@@ -130,11 +162,14 @@ public class LodRequestModule implements Closeable
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
//===================//
|
||||
// world gen control //
|
||||
//===================//
|
||||
//region
|
||||
|
||||
public void startWorldGen(GeneratedFullDataSourceProvider dataFileHandler, AbstractLodRequestState newWgs)
|
||||
{
|
||||
@@ -173,11 +208,14 @@ public class LodRequestModule implements Closeable
|
||||
dataFileHandler.removeWorldGenCompleteListener(this.onWorldGenCompleteListener);
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
//=======================//
|
||||
// base method overrides //
|
||||
//=======================//
|
||||
//region
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
@@ -205,11 +243,14 @@ public class LodRequestModule implements Closeable
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
//=========//
|
||||
// getters //
|
||||
//=========//
|
||||
//region
|
||||
|
||||
public boolean isWorldGenRunning() { return this.lodRequestStateRef.get() != null; }
|
||||
|
||||
@@ -241,170 +282,7 @@ public class LodRequestModule implements Closeable
|
||||
worldGenState.retrievalQueue.addDebugMenuStringsToList(messageList);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
+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.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.objects.RollingAverage;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
||||
+3
-2
@@ -17,7 +17,7 @@
|
||||
* 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.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.dependencyInjection.SingletonInjector;
|
||||
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.DataSourceRetrievalTask;
|
||||
import com.seibel.distanthorizons.core.level.IDhServerLevel;
|
||||
@@ -201,7 +202,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
||||
private synchronized void tryQueueNewWorldGenRequestsAsync()
|
||||
{
|
||||
if (!DhApiWorldProxy.INSTANCE.worldLoaded()
|
||||
|| DhApiWorldProxy.INSTANCE.getReadOnly())
|
||||
|| DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -94,7 +94,10 @@ public class GitlabGetter
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -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.WebDownloader;
|
||||
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.minecraft.IMinecraftClientWrapper;
|
||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||
import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
||||
import com.seibel.distanthorizons.coreapi.util.jar.DeleteOnUnlock;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.*;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.file.Files;
|
||||
@@ -59,6 +56,8 @@ public class SelfUpdater
|
||||
{
|
||||
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 */
|
||||
public static boolean deleteOldJarOnJvmShutdown = false;
|
||||
|
||||
@@ -264,7 +263,7 @@ public class SelfUpdater
|
||||
{
|
||||
try
|
||||
{
|
||||
NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, successMessage, "ok", "info");
|
||||
MC_CLIENT.showDialog(ModInfo.READABLE_NAME, successMessage, "ok", "info");
|
||||
}
|
||||
catch (Exception ignore) { }
|
||||
}).start();
|
||||
@@ -287,7 +286,7 @@ public class SelfUpdater
|
||||
LOGGER.error(failMessage, e);
|
||||
try
|
||||
{
|
||||
NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, failMessage, "ok", "error");
|
||||
MC_CLIENT.showDialog(ModInfo.READABLE_NAME, failMessage, "ok", "error");
|
||||
}
|
||||
catch (Exception ignore) { }
|
||||
|
||||
@@ -386,7 +385,7 @@ public class SelfUpdater
|
||||
{
|
||||
try
|
||||
{
|
||||
NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, successMessage, "ok", "info");
|
||||
MC_CLIENT.showDialog(ModInfo.READABLE_NAME, successMessage, "ok", "info");
|
||||
}
|
||||
catch (Exception ignore) { }
|
||||
}).start();
|
||||
@@ -424,7 +423,7 @@ public class SelfUpdater
|
||||
LOGGER.error(failMessage, e);
|
||||
try
|
||||
{
|
||||
NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, failMessage, "ok", "error");
|
||||
MC_CLIENT.showDialog(ModInfo.READABLE_NAME, failMessage, "ok", "error");
|
||||
}
|
||||
catch (Exception ignore) { }
|
||||
|
||||
|
||||
@@ -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.RemoteFullDataSourceProvider;
|
||||
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.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState;
|
||||
@@ -396,12 +398,10 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
|
||||
//================//
|
||||
//region
|
||||
|
||||
private static class LodRequestState extends LodRequestModule.AbstractLodRequestState
|
||||
private static class LodRequestState extends AbstractLodRequestState
|
||||
{
|
||||
LodRequestState(DhClientLevel clientLevel, ClientNetworkState networkState)
|
||||
{
|
||||
this.retrievalQueue = new RemoteWorldRetrievalQueue(networkState, clientLevel);
|
||||
}
|
||||
{ super(clientLevel, new RemoteWorldRetrievalQueue(networkState, clientLevel)); }
|
||||
}
|
||||
|
||||
//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.structure.ISaveStructure;
|
||||
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.coreapi.DependencyInjection.WorldGeneratorInjector;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
@@ -75,9 +78,11 @@ public class ServerLevelModule implements AutoCloseable
|
||||
// helper classes //
|
||||
//================//
|
||||
|
||||
public static class LodRequestState extends LodRequestModule.AbstractLodRequestState
|
||||
public static class LodRequestState extends AbstractLodRequestState
|
||||
{
|
||||
LodRequestState(IDhServerLevel level)
|
||||
{ super(level, createRetrievalQueue(level)); }
|
||||
private static IFullDataSourceRetrievalQueue createRetrievalQueue(IDhServerLevel level)
|
||||
{
|
||||
IDhApiWorldGenerator worldGenerator = WorldGeneratorInjector.INSTANCE.get(level.getLevelWrapper());
|
||||
if (worldGenerator == null)
|
||||
@@ -88,9 +93,11 @@ public class ServerLevelModule implements AutoCloseable
|
||||
// since core world generator's should have the lowest override priority
|
||||
WorldGeneratorInjector.INSTANCE.bind(level.getLevelWrapper(), worldGenerator);
|
||||
}
|
||||
this.retrievalQueue = new WorldGenerationQueue(worldGenerator, level);
|
||||
return new WorldGenerationQueue(worldGenerator, level);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-2
@@ -151,8 +151,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
||||
|
||||
public synchronized boolean tick(DhBlockPos2D targetPos)
|
||||
{
|
||||
if (DhApiWorldProxy.INSTANCE.worldLoaded()
|
||||
&& DhApiWorldProxy.INSTANCE.getReadOnly())
|
||||
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package com.seibel.distanthorizons.core.multiplayer.client;
|
||||
|
||||
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.level.DhClientLevel;
|
||||
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.messages.fullData.FullDataSplitMessage;
|
||||
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import io.netty.buffer.CompositeByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -21,7 +21,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
|
||||
.build();
|
||||
|
||||
private final ConcurrentMap<Integer, CompositeByteBuf> buffersById = CacheBuilder.newBuilder()
|
||||
.expireAfterAccess(10, TimeUnit.SECONDS)
|
||||
.expireAfterAccess(30, TimeUnit.SECONDS)
|
||||
.<Integer, CompositeByteBuf>build().asMap();
|
||||
|
||||
@Override
|
||||
@@ -56,7 +56,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
|
||||
public FullDataSourceV2DTO decodeDataSource(FullDataPayload payload)
|
||||
{
|
||||
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
|
||||
{
|
||||
|
||||
+171
-76
@@ -21,6 +21,7 @@ package com.seibel.distanthorizons.core.render.QuadTree;
|
||||
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
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.enums.EDhDirection;
|
||||
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
|
||||
@@ -148,7 +149,8 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
int initialPlayerBlockX, int initialPlayerBlockZ,
|
||||
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);
|
||||
|
||||
@@ -245,17 +247,38 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
//===================//
|
||||
//region
|
||||
|
||||
this.setCenterBlockPos(playerPos, (renderSection) ->
|
||||
{
|
||||
// removing out of bounds sections
|
||||
if (renderSection != null)
|
||||
// remove out of bound sections
|
||||
this.setCenterBlockPos(playerPos,
|
||||
// remove completely out of bound nodes
|
||||
// (the root node is no longer in bounds)
|
||||
(renderSection) ->
|
||||
{
|
||||
this.fullDataSourceProvider.removeRetrievalRequestIf((long genPos) -> DhSectionPos.contains(renderSection.pos, genPos));
|
||||
this.missingGenerationPosSet.remove(renderSection.pos);
|
||||
this.queuedGenerationPosSet.remove(renderSection.pos);
|
||||
renderSection.close();
|
||||
if (renderSection != null)
|
||||
{
|
||||
this.fullDataSourceProvider.removeRetrievalRequestIf((long genPos) -> DhSectionPos.contains(renderSection.pos, genPos));
|
||||
|
||||
// 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
|
||||
|
||||
@@ -304,7 +327,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
continue;
|
||||
}
|
||||
|
||||
node.value.retreivedMissingSectionsForRetreival = false;
|
||||
node.value.queuedMissingSectionsForRetrieval = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,8 +395,6 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
//=========================//
|
||||
//region
|
||||
|
||||
// also handles disabling beacons
|
||||
|
||||
for (QuadNode<LodRenderSection> node : this.tickNodeHolder.getDisableNodes())
|
||||
{
|
||||
if (node == null || node.value == null) { continue; }
|
||||
@@ -449,9 +470,9 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
|
||||
// since this section wants to render
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -476,9 +497,10 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
//=========================//
|
||||
// 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 QuadNode<LodRenderSection> rootNode,
|
||||
@Nullable QuadNode<LodRenderSection> parentNode,
|
||||
@@ -490,19 +512,22 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
// get/create the node //
|
||||
// and render section //
|
||||
//=====================//
|
||||
///region
|
||||
//region
|
||||
|
||||
// create the node
|
||||
if (quadNode == null)
|
||||
quadNode = this.tryAddNodeToTree(rootNode, quadNode, sectionPos);
|
||||
|
||||
|
||||
// Skip sections that are out-of-bounds.
|
||||
// If not done some sections will appear and/or generate
|
||||
// outside the desired render distance
|
||||
if (!this.isSectionPosInBounds(quadNode.sectionPos))
|
||||
{
|
||||
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+"].");
|
||||
this.tickNodeHolder.addDisableNode(quadNode);
|
||||
this.recursivelyDisableChildNodes(quadNode);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// make sure the render section is created (shouldn't be necessary, but just in case)
|
||||
LodRenderSection renderSection = quadNode.value;
|
||||
if (renderSection == null)
|
||||
@@ -511,7 +536,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
quadNode.setValue(sectionPos, renderSection);
|
||||
}
|
||||
|
||||
///endregion
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
@@ -519,7 +544,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
// handle enabling, loading, //
|
||||
// and disabling render sections //
|
||||
//===============================//
|
||||
///region
|
||||
//region
|
||||
|
||||
// load every node for rendering
|
||||
if (!renderSection.gpuUploadInProgress()
|
||||
@@ -535,94 +560,153 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
|
||||
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
|
||||
else if (DhSectionPos.getDetailLevel(quadNode.sectionPos) == expectedDetailLevel
|
||||
|| DhSectionPos.getDetailLevel(quadNode.sectionPos) == expectedDetailLevel - 1)
|
||||
{
|
||||
this.onDesiredDetailLevel(quadNode, parentNode);
|
||||
return this.onDesiredDetailLevel(quadNode, parentNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
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 QuadNode<LodRenderSection> rootNode, @NotNull QuadNode<LodRenderSection> quadNode)
|
||||
@NotNull QuadNode<LodRenderSection> rootNode,
|
||||
@NotNull QuadNode<LodRenderSection> quadNode)
|
||||
{
|
||||
// recursively update each child node
|
||||
boolean allChildNodesCanRender = true;
|
||||
int childNodeRenderCount = 0;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i);
|
||||
long childPos = DhSectionPos.getChildByIndex(quadNode.sectionPos, i);
|
||||
this.recursivelyUpdateRenderSectionNode(
|
||||
QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i);
|
||||
|
||||
boolean childCanRender = this.recursivelyUpdateRenderSectionNode(
|
||||
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
|
||||
if (childNode != null
|
||||
&& childNode.value != null
|
||||
&& !childNode.value.gpuUploadComplete())
|
||||
if (childCanRender)
|
||||
{
|
||||
// the node is present but not uploaded yet
|
||||
allChildNodesCanRender = false;
|
||||
// node can be rendered
|
||||
childNodeRenderCount++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (allChildNodesCanRender)
|
||||
boolean isRootNode = (quadNode == rootNode);
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
else if (childNodeRenderCount >= 4)
|
||||
{
|
||||
this.tickNodeHolder.addDisableNode(quadNode);
|
||||
|
||||
// all children can render,
|
||||
// the area will be filled when rendering
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// not all child positions are loaded yet, this one should be rendered instead
|
||||
this.tickNodeHolder.addEnableNode(quadNode);
|
||||
boolean nodeCanRender = quadNode.value != null
|
||||
&& 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)
|
||||
{
|
||||
boolean allAdjNodesCanRender = true;
|
||||
|
||||
// if the parent node is null, that means we're at the root node
|
||||
// and we should always render
|
||||
if (parentNode != null)
|
||||
/** @return true if the node at this position has uploaded its render data */
|
||||
private boolean onDesiredDetailLevel(
|
||||
@NotNull QuadNode<LodRenderSection> quadNode,
|
||||
@Nullable QuadNode<LodRenderSection> parentNode)
|
||||
{
|
||||
// Skip sections that are out-of-bounds.
|
||||
// If not done some sections will appear and/or generate
|
||||
// outside the desired render distance
|
||||
if (!this.isSectionPosInBounds(quadNode.sectionPos))
|
||||
{
|
||||
// check if all adjacent nodes are ready to render
|
||||
// this check is done to prevent some overlapping due to the parent node
|
||||
// still being active
|
||||
for (int i = 0; i < 4; i++)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (quadNode.value != null
|
||||
&& quadNode.value.canRender())
|
||||
{
|
||||
if (!this.tickNodeHolder.getEnabledNodes().contains(parentNode))
|
||||
{
|
||||
QuadNode<LodRenderSection> adjNode = parentNode.getChildByIndex(i);
|
||||
// nodes shouldn't be null, but just in case there's an issue
|
||||
if (adjNode != null
|
||||
&& adjNode.value != null
|
||||
&& !adjNode.value.gpuUploadComplete())
|
||||
{
|
||||
// the node is present but not uploaded yet
|
||||
allAdjNodesCanRender = false;
|
||||
}
|
||||
this.tickNodeHolder.addEnableDeleteChildrenNode(quadNode);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.tickNodeHolder.addDisableNode(quadNode);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (allAdjNodesCanRender
|
||||
&& quadNode.value != null
|
||||
&& quadNode.value.gpuUploadComplete())
|
||||
else
|
||||
{
|
||||
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 //
|
||||
@@ -1118,6 +1202,17 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
{
|
||||
this.populateListWithEnabledRenderSections(this.debugNodeList);
|
||||
|
||||
//// can be uncommented for debugging/finding a specific position
|
||||
//debugRenderer.makeParticle(
|
||||
// new AbstractDebugWireframeRenderer.BoxParticle(
|
||||
// new AbstractDebugWireframeRenderer.Box(
|
||||
// DhSectionPos.encode((byte)7, 3,-1)
|
||||
// , -64, 400,
|
||||
// 0.1f,
|
||||
// Color.YELLOW),
|
||||
// 0.5, 0f
|
||||
// ));
|
||||
|
||||
for (int i = 0; i < this.debugNodeList.size(); i++)
|
||||
{
|
||||
LodRenderSection renderSection = this.debugNodeList.get(i);
|
||||
@@ -1127,7 +1222,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
{
|
||||
color = Color.ORANGE;
|
||||
}
|
||||
else if (!renderSection.gpuUploadComplete())
|
||||
else if (!renderSection.canRender())
|
||||
{
|
||||
// uploaded but the buffer is missing
|
||||
color = Color.PINK;
|
||||
|
||||
+21
-9
@@ -34,11 +34,8 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
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.dataObjects.render.bufferBuilding.LodBufferContainer;
|
||||
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.threading.PriorityTaskPicker;
|
||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||
@@ -48,10 +45,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.annotation.WillNotClose;
|
||||
import java.awt.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* A render section represents an area that could be rendered.
|
||||
@@ -75,8 +70,16 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
||||
|
||||
|
||||
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 */
|
||||
public LodBufferContainer renderBufferContainer;
|
||||
@@ -320,6 +323,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
||||
|
||||
// upload complete
|
||||
this.renderBufferContainer = buffer.buffersUploaded ? buffer : null;
|
||||
this.renderDataDirty = false;
|
||||
|
||||
if (previousContainer != null)
|
||||
{
|
||||
@@ -345,7 +349,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
||||
//=================//
|
||||
//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 void setRenderingEnabled(boolean enabled) { this.renderingEnabled = enabled;}
|
||||
@@ -374,7 +385,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
||||
{
|
||||
color = Color.yellow;
|
||||
}
|
||||
else if (this.gpuUploadComplete())
|
||||
else if (this.canRender())
|
||||
{
|
||||
//color = Color.cyan;
|
||||
return;
|
||||
@@ -418,6 +429,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
||||
}
|
||||
|
||||
|
||||
this.setRenderingEnabled(false);
|
||||
if (this.renderBufferContainer != null)
|
||||
{
|
||||
this.renderBufferContainer.close();
|
||||
|
||||
+26
-8
@@ -63,21 +63,39 @@ public class QuadTreeTickNodeHolder
|
||||
{
|
||||
if(this.presentNodes.add(node))
|
||||
{
|
||||
// not a big fan of having to check every node to prevent overlaps, but it does work
|
||||
this.nodesToEnable.removeIf((QuadNode<LodRenderSection> checkNode) ->
|
||||
// in James testing as of 4-21-2026
|
||||
// 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);
|
||||
if (contained)
|
||||
// not a big fan of having to check every node to prevent overlaps, but it does work
|
||||
this.nodesToEnable.removeIf((QuadNode<LodRenderSection> checkNode) ->
|
||||
{
|
||||
this.nodesToDisable.add(checkNode);
|
||||
}
|
||||
boolean contained = DhSectionPos.contains(node.sectionPos, checkNode.sectionPos);
|
||||
if (contained)
|
||||
{
|
||||
this.nodesToDisable.add(checkNode);
|
||||
}
|
||||
|
||||
return contained;
|
||||
});
|
||||
return contained;
|
||||
});
|
||||
}
|
||||
|
||||
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; }
|
||||
|
||||
|
||||
|
||||
@@ -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.core.api.internal.SharedApi;
|
||||
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.jar.EPlatform;
|
||||
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.misc.ILightMapWrapper;
|
||||
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.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 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 boolean initialLoadingComplete = false;
|
||||
|
||||
@@ -164,7 +168,7 @@ public class RenderParams extends DhApiRenderParam
|
||||
return "No MVM or Proj Matrix Given";
|
||||
}
|
||||
|
||||
if (AbstractOptifineAccessor.optifinePresent()
|
||||
if (OPTIFINE_ACCESSOR != null
|
||||
&& MC_RENDER.getTargetFramebuffer() == -1)
|
||||
{
|
||||
// wait for MC to finish setting up their renderer
|
||||
|
||||
+159
-144
@@ -143,179 +143,196 @@ public class LodRenderer
|
||||
//region
|
||||
|
||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderSetupEvent.class, renderParams);
|
||||
profiler.push("LOD GL setup");
|
||||
|
||||
if (!this.renderersBound)
|
||||
try (IProfilerWrapper.IProfileBlock terrainRender_profile = profiler.push("LOD GL setup")) // starts the new profile block for most DH rendering
|
||||
{
|
||||
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())
|
||||
if (!this.renderersBound)
|
||||
{
|
||||
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();
|
||||
this.bindRenderers();
|
||||
this.renderersBound = true;
|
||||
}
|
||||
|
||||
this.vanillaSettingsOverridden = true;
|
||||
}
|
||||
|
||||
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
|
||||
RenderBufferHandler renderBufferHandler = renderParams.renderBufferHandler;
|
||||
IDhGenericRenderer genericRenderer = renderParams.genericRenderer;
|
||||
|
||||
|
||||
this.metaRenderer.runRenderPassSetup(renderParams);
|
||||
|
||||
//===========//
|
||||
// rendering //
|
||||
//===========//
|
||||
|
||||
if (!runningDeferredPass)
|
||||
{
|
||||
this.metaRenderer.clearDhDepthAndColorTextures(renderParams);
|
||||
|
||||
|
||||
|
||||
//=========================//
|
||||
// opaque and non-deferred //
|
||||
// transparent rendering //
|
||||
//=========================//
|
||||
|
||||
// opaque LODs
|
||||
profiler.popPush("LOD Opaque");
|
||||
|
||||
this.renderTerrain(this.terrainRenderer, renderBufferHandler, renderParams, /*opaquePass*/ true, profiler);
|
||||
|
||||
// custom objects with SSAO
|
||||
if (Config.Client.Advanced.Graphics.GenericRendering.enableGenericRendering.get())
|
||||
if (!this.vanillaSettingsOverridden)
|
||||
{
|
||||
profiler.popPush("Custom Objects");
|
||||
genericRenderer.render(renderParams, profiler, true);
|
||||
// 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();
|
||||
}
|
||||
|
||||
this.vanillaSettingsOverridden = true;
|
||||
}
|
||||
|
||||
// SSAO
|
||||
if (Config.Client.Advanced.Graphics.enableSsao.get())
|
||||
if (firstPass)
|
||||
{
|
||||
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 (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);
|
||||
// we only need to sort/cull the LODs at the start of the frame
|
||||
profiler.popPush("LOD build render list");
|
||||
renderBufferHandler.buildRenderList(renderParams);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=================//
|
||||
// debug rendering //
|
||||
//=================//
|
||||
|
||||
if (Config.Client.Advanced.Debugging.DebugWireframe.enableRendering.get())
|
||||
boolean renderFog;
|
||||
Boolean apiFogOverride = Config.Client.Advanced.Graphics.Fog.enableDhFog.getApiValue();
|
||||
if (apiFogOverride != null)
|
||||
{
|
||||
profiler.popPush("Debug wireframes");
|
||||
|
||||
// Note: this can be very slow if a lot of boxes are being rendered
|
||||
this.debugWireframeRenderer.render(renderParams);
|
||||
// use whatever the API dictates if set
|
||||
// (this could cause issues when underwater if a shader or something
|
||||
// doesn't add their own, but that's relatively unlikely)
|
||||
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");
|
||||
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);
|
||||
// needs to be fired after all the textures have been created/bound
|
||||
boolean clearTextures = !ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeTextureClearEvent.class, renderParams);
|
||||
if (clearTextures)
|
||||
{
|
||||
this.metaRenderer.clearDhDepthAndColorTextures(renderParams);
|
||||
}
|
||||
|
||||
|
||||
if (Config.Client.Advanced.Graphics.Fog.enableDhFog.get()
|
||||
// this is done to fix issues with: underwater fog, blindness effect, etc.
|
||||
|| renderParams.vanillaFogEnabled)
|
||||
|
||||
//=========================//
|
||||
// opaque and non-deferred //
|
||||
// transparent rendering //
|
||||
//=========================//
|
||||
|
||||
// opaque LODs
|
||||
profiler.popPush("LOD Opaque");
|
||||
|
||||
this.renderTerrain(this.terrainRenderer, renderBufferHandler, renderParams, /*opaquePass*/ true, profiler);
|
||||
|
||||
// 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");
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=============================//
|
||||
// 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
|
||||
@@ -333,8 +350,6 @@ public class LodRenderer
|
||||
// rendering //
|
||||
//===========//
|
||||
|
||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderPassEvent.class, renderEventParam);
|
||||
|
||||
SortedArraySet<LodBufferContainer> lodBufferContainer = lodBufferHandler.getColumnRenderBuffers();
|
||||
if (lodBufferContainer != null)
|
||||
{
|
||||
|
||||
@@ -22,6 +22,7 @@ package com.seibel.distanthorizons.core.util;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
|
||||
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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||
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) +
|
||||
" Y-:" + getYMin(dataPoint) +
|
||||
" argb:" + getAlpha(dataPoint) + " " +
|
||||
getRed(dataPoint) + " " +
|
||||
getGreen(dataPoint) + " " +
|
||||
getBlue(dataPoint) +
|
||||
" argb:" + getAlpha(dataPoint) + "," + getRed(dataPoint) + "," + getGreen(dataPoint) + "," + getBlue(dataPoint) +
|
||||
" BL:" + getLightBlock(dataPoint) +
|
||||
" SL:" + getLightSky(dataPoint) +
|
||||
" MAT:" + getBlockMaterialId(dataPoint) + "["+ EDhApiBlockMaterial.getFromIndex(getBlockMaterialId(dataPoint))+"]";
|
||||
|
||||
@@ -123,7 +123,16 @@ public class RenderUtil
|
||||
// At low render distances this hides the vanilla RD border
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
+1
-1
@@ -325,7 +325,7 @@ public class QuadNode<T>
|
||||
|
||||
public void deleteAllChildren() { this.deleteAllChildren(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++)
|
||||
{
|
||||
|
||||
+122
-37
@@ -19,10 +19,12 @@
|
||||
|
||||
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.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||
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.coreapi.util.BitShiftUtil;
|
||||
import com.seibel.distanthorizons.coreapi.util.MathUtil;
|
||||
@@ -61,6 +63,11 @@ public class QuadTree<T>
|
||||
private final MovableGridRingList<QuadNode<T>> topRingList;
|
||||
|
||||
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
|
||||
*/
|
||||
public QuadTree(int diameterInBlocks, DhBlockPos2D centerBlockPos, byte treeLeafDetailLevel)
|
||||
public QuadTree(
|
||||
int diameterInBlocks, int blockDistanceForNodeClearing,
|
||||
DhBlockPos2D centerBlockPos, byte treeLeafDetailLevel)
|
||||
{
|
||||
this.centerBlockPos = centerBlockPos;
|
||||
this.diameterInBlocks = diameterInBlocks;
|
||||
this.blockDistanceForNodeClearing = blockDistanceForNodeClearing;
|
||||
|
||||
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
|
||||
@@ -130,18 +140,22 @@ public class QuadTree<T>
|
||||
public int leafNodeCount()
|
||||
{
|
||||
int count = 0;
|
||||
for (QuadNode<T> node : this.topRingList)
|
||||
for (QuadNode<T> rootNode : this.topRingList)
|
||||
{
|
||||
if (node == null)
|
||||
if (rootNode == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Iterator<QuadNode<T>> leafNodeIterator = node.getLeafNodeIterator();
|
||||
Iterator<QuadNode<T>> leafNodeIterator = rootNode.getLeafNodeIterator();
|
||||
while (leafNodeIterator.hasNext())
|
||||
{
|
||||
leafNodeIterator.next();
|
||||
count++;
|
||||
QuadNode<T> node = leafNodeIterator.next();
|
||||
if (node != null
|
||||
&& this.isSectionPosInBounds(node.sectionPos))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,32 +250,32 @@ public class QuadTree<T>
|
||||
int ringListPosX = DhSectionPos.getX(rootPos);
|
||||
int ringListPosZ = DhSectionPos.getZ(rootPos);
|
||||
|
||||
QuadNode<T> topQuadNode = this.topRingList.get(ringListPosX, ringListPosZ);
|
||||
if (topQuadNode == null)
|
||||
QuadNode<T> rootQuadNode = this.topRingList.get(ringListPosX, ringListPosZ);
|
||||
if (rootQuadNode == null)
|
||||
{
|
||||
if (!setNewValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
topQuadNode = new QuadNode<T>(rootPos, this.treeLeafDetailLevel);
|
||||
boolean successfullyAdded = this.topRingList.set(ringListPosX, ringListPosZ, topQuadNode);
|
||||
rootQuadNode = new QuadNode<T>(rootPos, this.treeLeafDetailLevel);
|
||||
boolean successfullyAdded = this.topRingList.set(ringListPosX, ringListPosZ, rootQuadNode);
|
||||
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)
|
||||
{
|
||||
topQuadNode.setValue(pos, newValue);
|
||||
rootQuadNode.setValue(pos, newValue);
|
||||
}
|
||||
return returnNode;
|
||||
}
|
||||
@@ -354,32 +368,103 @@ public class QuadTree<T>
|
||||
//================//
|
||||
//region
|
||||
|
||||
public void setCenterBlockPos(DhBlockPos2D newCenterPos) { this.setCenterBlockPos(newCenterPos, null); }
|
||||
public void setCenterBlockPos(DhBlockPos2D newCenterPos, Consumer<? super T> removedItemConsumer)
|
||||
public void setCenterBlockPos(DhBlockPos2D newCenterPos) { this.setCenterBlockPos(newCenterPos, null, null); }
|
||||
/**
|
||||
* @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;
|
||||
|
||||
MovableGridRingList.Pos2D expectedCenterPos = new MovableGridRingList.Pos2D(
|
||||
BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.x, this.treeRootDetailLevel),
|
||||
BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.z, this.treeRootDetailLevel));
|
||||
|
||||
if (this.topRingList.getCenter().equals(expectedCenterPos))
|
||||
// did we move significantly?
|
||||
boolean ringListMoved = false;
|
||||
int newCenterPosX = BitShiftUtil.divideByPowerOfTwo(newCenterPos.x, this.treeRootDetailLevel);
|
||||
int newCenterPosZ = BitShiftUtil.divideByPowerOfTwo(newCenterPos.z, this.treeRootDetailLevel);
|
||||
if (this.topRingList.getCenter().getX() != newCenterPosX
|
||||
|| this.topRingList.getCenter().getY() != newCenterPosZ)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
// remove out of bounds root nodes
|
||||
this.topRingList.moveTo(expectedCenterPos.getX(), expectedCenterPos.getY(), (quadNode) ->
|
||||
{
|
||||
if (quadNode != null && removedItemConsumer != null)
|
||||
{
|
||||
quadNode.deleteAllChildren(removedItemConsumer);
|
||||
|
||||
removedItemConsumer.accept(quadNode.value);
|
||||
this.centerBlockPos = newCenterPos;
|
||||
|
||||
// remove out of bound root nodes
|
||||
this.topRingList.moveTo(newCenterPosX, newCenterPosZ, (quadNode) ->
|
||||
{
|
||||
if (quadNode != null)
|
||||
{
|
||||
quadNode.deleteAllChildren(removedConsumer);
|
||||
|
||||
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; }
|
||||
@@ -484,7 +569,7 @@ public class QuadTree<T>
|
||||
|
||||
private class QuadTreeNodeIterator implements Iterator<QuadNode<T>>
|
||||
{
|
||||
private final QuadTreeRootPosIterator rootNodeIterator;
|
||||
private final QuadTreeRootPosIterator rootNodePosIterator;
|
||||
private Iterator<QuadNode<T>> currentNodeIterator;
|
||||
|
||||
private QuadNode<T> lastNode = null;
|
||||
@@ -497,7 +582,7 @@ public class QuadTree<T>
|
||||
|
||||
public QuadTreeNodeIterator(boolean onlyReturnLeaves, @Nullable INodeIteratorStoppingFunc<T> stopIteratingFunc)
|
||||
{
|
||||
this.rootNodeIterator = new QuadTreeRootPosIterator(false, stopIteratingFunc);
|
||||
this.rootNodePosIterator = new QuadTreeRootPosIterator(false, stopIteratingFunc);
|
||||
this.onlyReturnLeaves = onlyReturnLeaves;
|
||||
|
||||
this.stopIteratingFunc = stopIteratingFunc;
|
||||
@@ -508,7 +593,7 @@ public class QuadTree<T>
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
if (!this.rootNodeIterator.hasNext()
|
||||
if (!this.rootNodePosIterator.hasNext()
|
||||
&& this.currentNodeIterator != null
|
||||
&& !this.currentNodeIterator.hasNext())
|
||||
{
|
||||
@@ -544,9 +629,9 @@ public class QuadTree<T>
|
||||
{
|
||||
Iterator<QuadNode<T>> nodeIterator = null;
|
||||
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
|
||||
QuadNode<T> rootNode = QuadTree.this.tryGetNode(sectionPos);
|
||||
|
||||
@@ -64,7 +64,7 @@ public abstract class AbstractDhWorld implements IDhWorld, Closeable
|
||||
String levelCountStr = F3Screen.NUMBER_FORMAT.format(this.getLoadedLevelCount());
|
||||
|
||||
String readOnlyStr = "";
|
||||
if (DhApiWorldProxy.INSTANCE.getReadOnly())
|
||||
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||
{
|
||||
readOnlyStr += " - ReadOnly";
|
||||
}
|
||||
|
||||
@@ -113,6 +113,23 @@ public class DhApiWorldProxy implements IDhApiWorldProxy
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
|
||||
+6
@@ -20,6 +20,7 @@
|
||||
package com.seibel.distanthorizons.core.wrapperInterfaces.block;
|
||||
|
||||
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 java.awt.*;
|
||||
@@ -59,6 +60,11 @@ public interface IBlockStateWrapper extends IDhApiBlockStateWrapper
|
||||
* IE Iron, diamond, gold, etc.
|
||||
*/
|
||||
boolean isBeaconBaseBlock();
|
||||
/**
|
||||
* if true this block can have its color overridden
|
||||
* by {@link DhApiBlockColorOverrideEvent}
|
||||
*/
|
||||
boolean allowApiColorOverride();
|
||||
|
||||
Color getMapColor();
|
||||
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.DhChunkPos;
|
||||
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
|
||||
|
||||
@@ -113,17 +114,19 @@ public interface IMinecraftClientWrapper extends IBindable
|
||||
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)
|
||||
* whereas this method causes the task to be run whenever MC decides to
|
||||
* (likely all at once the next frame). <br><br>
|
||||
*
|
||||
* Any tasks submitted here will be run on the render thread.
|
||||
*
|
||||
* @see GLProxy#queueRunningOnRenderThread(Runnable)
|
||||
* @see RenderThreadTaskHandler
|
||||
*/
|
||||
void executeOnRenderThread(Runnable runnable);
|
||||
|
||||
void showDialog(String title, String message, String dialogType, String iconType);
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
|
||||
+17
-11
@@ -21,20 +21,26 @@ package com.seibel.distanthorizons.core.wrapperInterfaces.minecraft;
|
||||
|
||||
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
|
||||
|
||||
/**
|
||||
* @author James Seibel
|
||||
* @version 11-20-2021
|
||||
*/
|
||||
public interface IProfilerWrapper extends IBindable
|
||||
{
|
||||
// Note to self:
|
||||
// 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);
|
||||
|
||||
IProfileBlock push(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;
|
||||
}
|
||||
|
||||
public static boolean optifinePresent() { return getOptifineFogField() != null; }
|
||||
|
||||
|
||||
|
||||
//===================//
|
||||
|
||||
@@ -41,8 +41,8 @@ void main()
|
||||
|
||||
|
||||
|
||||
// the DH texture will have white if nothing was written to that pixel.
|
||||
if (dhColor == vec4(1))
|
||||
// ignore anything that DH hasn't drawn to
|
||||
if (dhColor.a == 0.0f)
|
||||
{
|
||||
// if not done vanilla clouds will render incorrectly at night
|
||||
dhColor = combinedMcDhColor;
|
||||
|
||||
@@ -56,8 +56,7 @@ void main()
|
||||
|
||||
|
||||
// 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 == vec4(1))
|
||||
if (dhColor.a == 0.0f)
|
||||
{
|
||||
// if not done vanilla clouds will render incorrectly at night
|
||||
dhColor = combinedMcDhColor;
|
||||
|
||||
@@ -39,8 +39,8 @@ void main()
|
||||
|
||||
|
||||
|
||||
// the DH texture will have white if nothing was written to that pixel.
|
||||
if (dhColor == vec4(1))
|
||||
// ignore anything that DH hasn't drawn to
|
||||
if (dhColor.a == 0.0f)
|
||||
{
|
||||
// if not done vanilla clouds will render incorrectly at night
|
||||
dhColor = combinedMcDhColor;
|
||||
|
||||
@@ -52,8 +52,7 @@ void main()
|
||||
|
||||
|
||||
// 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 == vec4(1))
|
||||
if (dhColor.a == 0.0f)
|
||||
{
|
||||
// if not done vanilla clouds will render incorrectly at night
|
||||
dhColor = combinedMcDhColor;
|
||||
|
||||
@@ -14,49 +14,22 @@ public class TestBlockStateWrapper implements IBlockStateWrapper
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isAir()
|
||||
{ return false; }
|
||||
@Override
|
||||
public boolean isSolid()
|
||||
{ return true; }
|
||||
@Override
|
||||
public boolean isLiquid()
|
||||
{ return false; }
|
||||
@Override
|
||||
public String getSerialString()
|
||||
{ return this.name; }
|
||||
@Override
|
||||
public int getOpacity()
|
||||
{ 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 public boolean isAir() { return false; }
|
||||
@Override public boolean isSolid() { return true; }
|
||||
@Override public boolean isLiquid() { return false; }
|
||||
@Override public String getSerialString() { return this.name; }
|
||||
@Override public int getOpacity() { 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 boolean allowApiColorOverride() { return false; }
|
||||
@Override public Color getMapColor() { return Color.MAGENTA; }
|
||||
@Override public Color getBeaconTintColor() { return Color.MAGENTA; }
|
||||
|
||||
@Override
|
||||
public Object getWrappedMcObject()
|
||||
{ return this; }
|
||||
@Override public Object getWrappedMcObject() { return this; }
|
||||
|
||||
|
||||
@Override
|
||||
|
||||
@@ -57,7 +57,7 @@ public class QuadTreeTest
|
||||
public void BasicPositiveQuadTreeTest()
|
||||
{
|
||||
AbstractTestTreeParams treeParams = new LargeTestTree();
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
Assert.assertTrue("Tree min/max detail level out of expected bounds: " + tree, tree.treeRootDetailLevel >= 10 && tree.treeLeafDetailLevel <= 10 - 4);
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ public class QuadTreeTest
|
||||
public void BasicNegativeQuadTreeTest()
|
||||
{
|
||||
AbstractTestTreeParams treeParams = new LargeTestTree();
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), DhBlockPos2D.ZERO, LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), DhBlockPos2D.ZERO, LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
|
||||
|
||||
// root node //
|
||||
@@ -128,7 +128,7 @@ public class QuadTreeTest
|
||||
public void OutOfBoundsQuadTreeTest()
|
||||
{
|
||||
AbstractTestTreeParams treeParams = new LargeTestTree();
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), new DhBlockPos2D(0, 0), LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), new DhBlockPos2D(0, 0), LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
Assert.assertEquals("tree diameter incorrect", treeParams.getWidthInBlocks(), tree.diameterInBlocks());
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ public class QuadTreeTest
|
||||
public void outOfBoundsInTreeTest()
|
||||
{
|
||||
// very specific tree parameters to match test results
|
||||
QuadTree<Integer> tree = new QuadTree<>(512, new DhBlockPos2D(125, -516), (byte) 6);
|
||||
QuadTree<Integer> tree = new QuadTree<>(512, 8, new DhBlockPos2D(125, -516), (byte) 6);
|
||||
Assert.assertEquals("Test may need to be re-calculated for different max detail level.", 9, tree.treeRootDetailLevel);
|
||||
|
||||
|
||||
@@ -192,7 +192,7 @@ public class QuadTreeTest
|
||||
public void QuadTreeRootAlignedMovingTest()
|
||||
{
|
||||
AbstractTestTreeParams treeParams = new LargeTestTree();
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
|
||||
int pseudoRootNodeWidthInBlocks = BitShiftUtil.powerOfTwo(10);
|
||||
|
||||
@@ -210,7 +210,7 @@ public class QuadTreeTest
|
||||
testSet(tree, ne, 3);
|
||||
testSet(tree, sw, 4);
|
||||
testSet(tree, se, 5);
|
||||
Assert.assertEquals("incorrect leaf node count", tree.leafNodeCount(), 4);
|
||||
Assert.assertEquals("incorrect leaf node count", 4, tree.leafNodeCount());
|
||||
|
||||
|
||||
// fake move //
|
||||
@@ -237,7 +237,7 @@ public class QuadTreeTest
|
||||
testGet(tree, ne, 3);
|
||||
testGet(tree, sw, 4);
|
||||
testGet(tree, se, 5);
|
||||
Assert.assertEquals("incorrect leaf node count", tree.leafNodeCount(), 4);
|
||||
Assert.assertEquals("incorrect leaf node count", 4, tree.leafNodeCount());
|
||||
|
||||
|
||||
|
||||
@@ -252,7 +252,7 @@ public class QuadTreeTest
|
||||
testGet(tree, sw, null, IndexOutOfBoundsException.class);
|
||||
testGet(tree, se, null, IndexOutOfBoundsException.class);
|
||||
|
||||
Assert.assertEquals("incorrect leaf node count", tree.leafNodeCount(), 0);
|
||||
Assert.assertEquals("incorrect leaf node count", 0, tree.leafNodeCount());
|
||||
|
||||
|
||||
|
||||
@@ -276,7 +276,7 @@ public class QuadTreeTest
|
||||
DhBlockPos2D edgeMoveBlockPos = new DhBlockPos2D(pseudoRootNodeWidthInBlocks - (treeParams.getWidthInRootNodes() * pseudoRootNodeWidthInBlocks), 0);
|
||||
tree.setCenterBlockPos(edgeMoveBlockPos);
|
||||
Assert.assertEquals("Tree center incorrect", edgeMoveBlockPos, tree.getCenterBlockPos());
|
||||
Assert.assertEquals("incorrect leaf node count", 2, tree.leafNodeCount());
|
||||
Assert.assertEquals("incorrect leaf node count", 0, tree.leafNodeCount());
|
||||
|
||||
}
|
||||
|
||||
@@ -284,7 +284,7 @@ public class QuadTreeTest
|
||||
public void QuadTreeIterationTest()
|
||||
{
|
||||
AbstractTestTreeParams treeParams = new LargeTestTree();
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
|
||||
|
||||
// (pseudo) root nodes //
|
||||
@@ -336,7 +336,7 @@ public class QuadTreeTest
|
||||
public void QuadTreeIterationFilterTest()
|
||||
{
|
||||
AbstractTestTreeParams treeParams = new TinyTestTree();
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getPositiveEdgeCenterPos(), (byte)0);
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), treeParams.getPositiveEdgeCenterPos(), (byte)0);
|
||||
|
||||
|
||||
|
||||
@@ -418,7 +418,10 @@ public class QuadTreeTest
|
||||
|
||||
|
||||
}
|
||||
private static <T> void assertFilterCount(QuadTree<T> tree, String message, int expectedNodeCount, @Nullable QuadTree.INodeIteratorStoppingFunc<T> stoppingFilterFunc)
|
||||
private static <T> void assertFilterCount(
|
||||
QuadTree<T> tree, String message,
|
||||
int expectedNodeCount,
|
||||
@Nullable QuadTree.INodeIteratorStoppingFunc<T> stoppingFilterFunc)
|
||||
{
|
||||
ArrayList<String> foundNodePositionStrings = new ArrayList<>();
|
||||
|
||||
@@ -532,7 +535,7 @@ public class QuadTreeTest
|
||||
public void CenteredGridListIterationTest()
|
||||
{
|
||||
AbstractTestTreeParams treeParams = new TinyTestTree();
|
||||
final QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
final QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
testSet(tree, DhSectionPos.encode(tree.treeRootDetailLevel, 0, 0), 0);
|
||||
|
||||
// confirm the root node were added
|
||||
@@ -573,18 +576,18 @@ public class QuadTreeTest
|
||||
AbstractTestTreeParams treeParams = new TinyTestTree();
|
||||
|
||||
// exactly inside (5*0,0)
|
||||
testGridListRootCount(treeParams.getWidthInBlocks(), treeParams.getPositiveEdgeCenterPos(), 1);
|
||||
testGridListRootCount(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), treeParams.getPositiveEdgeCenterPos(), 1);
|
||||
|
||||
// offset across (5*-1,0) and (5*0,0)
|
||||
testGridListRootCount(treeParams.getWidthInBlocks(), new DhBlockPos2D(-treeParams.getWidthInBlocks() / 4, treeParams.getPositiveEdgeCenterPos().z), 2);
|
||||
testGridListRootCount(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), new DhBlockPos2D(-treeParams.getWidthInBlocks() / 4, treeParams.getPositiveEdgeCenterPos().z), 2);
|
||||
|
||||
// offset across the origin: (5*0,0), (5*-1,0), (5*0,-1), and (5*-1,-1)
|
||||
testGridListRootCount(treeParams.getWidthInBlocks(), DhBlockPos2D.ZERO, 4);
|
||||
testGridListRootCount(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), DhBlockPos2D.ZERO, 4);
|
||||
|
||||
}
|
||||
private static void testGridListRootCount(int treeWidth, DhBlockPos2D treeMovePos, int expectedRootNodeCount)
|
||||
private static void testGridListRootCount(int treeWidth, int treeDistanceForNodeClearing, DhBlockPos2D treeMovePos, int expectedRootNodeCount)
|
||||
{
|
||||
final QuadTree<Integer> tree = new QuadTree<>(treeWidth, DhBlockPos2D.ZERO, LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
final QuadTree<Integer> tree = new QuadTree<>(treeWidth, treeDistanceForNodeClearing, DhBlockPos2D.ZERO, LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
Assert.assertEquals("tree creation failed, incorrect initial position", DhBlockPos2D.ZERO, tree.getCenterBlockPos());
|
||||
|
||||
tree.setCenterBlockPos(treeMovePos);
|
||||
@@ -615,7 +618,7 @@ public class QuadTreeTest
|
||||
public void TinyGridAlignedTreeTest()
|
||||
{
|
||||
AbstractTestTreeParams treeParams = new MediumTestTree();
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
// minimum size tree should be 3 root nodes wide
|
||||
Assert.assertEquals("incorrect tree node width", 3, tree.ringListWidth());
|
||||
Assert.assertEquals("incorrect tree width", treeParams.getWidthInBlocks(), tree.diameterInBlocks());
|
||||
@@ -644,7 +647,7 @@ public class QuadTreeTest
|
||||
public void TinyGridOffsetTreeTest()
|
||||
{
|
||||
AbstractTestTreeParams treeParams = new MediumTestTree();
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), new DhBlockPos2D(0, 0), LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), new DhBlockPos2D(0, 0), LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
// minimum size tree should be 3 root nodes wide
|
||||
Assert.assertEquals("incorrect tree node width", 3, tree.ringListWidth());
|
||||
Assert.assertEquals("incorrect tree width", treeParams.getWidthInBlocks(), tree.diameterInBlocks());
|
||||
@@ -683,7 +686,7 @@ public class QuadTreeTest
|
||||
public void TreeDetailLevelLimitTest()
|
||||
{
|
||||
AbstractTestTreeParams treeParams = new MediumTestTree();
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), new DhBlockPos2D(0, 0), (byte) 8);
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), new DhBlockPos2D(0, 0), (byte) 8);
|
||||
Assert.assertEquals("Test detail level's need to be adjusted. This isn't necessarily a failed test.", 10, tree.treeRootDetailLevel);
|
||||
|
||||
// valid detail levels
|
||||
@@ -705,7 +708,7 @@ public class QuadTreeTest
|
||||
public void QuadNodeDetailLimitTest()
|
||||
{
|
||||
AbstractTestTreeParams treeParams = new MediumTestTree();
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), new DhBlockPos2D(0, 0), (byte) 6);
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), new DhBlockPos2D(0, 0), (byte) 6);
|
||||
Assert.assertEquals("Test detail level's need to be adjusted. This isn't necessarily a failed test.", 10, tree.treeRootDetailLevel);
|
||||
|
||||
// create the root node
|
||||
@@ -817,8 +820,7 @@ public class QuadTreeTest
|
||||
@Test
|
||||
public void quadNodeChildPositionOutOfBoundsTest()
|
||||
{
|
||||
int treeWidthInBlocks = 64;
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeWidthInBlocks, new DhBlockPos2D(-2, 0), (byte) 0);
|
||||
QuadTree<Integer> tree = new QuadTree<>(64, 1, new DhBlockPos2D(-2, 0), (byte) 0);
|
||||
|
||||
|
||||
|
||||
@@ -865,7 +867,7 @@ public class QuadTreeTest
|
||||
public void toStringTest()
|
||||
{
|
||||
AbstractTestTreeParams treeParams = new MediumTestTree();
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), new DhBlockPos2D(0, 0), (byte) 6);
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), new DhBlockPos2D(0, 0), (byte) 6);
|
||||
|
||||
String treeString = tree.toString();
|
||||
Assert.assertNotNull(treeString);
|
||||
@@ -929,25 +931,28 @@ public class QuadTreeTest
|
||||
private abstract static class AbstractTestTreeParams
|
||||
{
|
||||
public abstract int getWidthInBlocks();
|
||||
public abstract int getBlockDistanceForNodeClearing();
|
||||
|
||||
/** the tree should be slightly larger than the width in blocks to account for offset centers */
|
||||
public int getWidthInRootNodes() { return MathUtil.log2(this.getWidthInBlocks()) + 2; }
|
||||
/** the top (root) detail level in the tree */
|
||||
public byte getMinDetailLevel() { return (byte) MathUtil.log2(this.getWidthInBlocks()); }
|
||||
public byte getRootDetailLevel() { return (byte) MathUtil.log2(this.getWidthInBlocks()); }
|
||||
/** @return the block pos so that the tree's negative corner lines up with (0,0) */
|
||||
public DhBlockPos2D getPositiveEdgeCenterPos() { return new DhBlockPos2D(BitShiftUtil.powerOfTwo(this.getMinDetailLevel()) / 2, BitShiftUtil.powerOfTwo(this.getMinDetailLevel()) / 2); }
|
||||
public DhBlockPos2D getPositiveEdgeCenterPos() { return new DhBlockPos2D(BitShiftUtil.powerOfTwo(this.getRootDetailLevel()) / 2, BitShiftUtil.powerOfTwo(this.getRootDetailLevel()) / 2); }
|
||||
|
||||
}
|
||||
|
||||
private static class LargeTestTree extends AbstractTestTreeParams
|
||||
{
|
||||
public int getWidthInBlocks() { return 8192; }
|
||||
public int getBlockDistanceForNodeClearing() { return 16; }
|
||||
|
||||
}
|
||||
|
||||
private static class MediumTestTree extends AbstractTestTreeParams
|
||||
{
|
||||
public int getWidthInBlocks() { return 1024; }
|
||||
public int getBlockDistanceForNodeClearing() { return 4; }
|
||||
|
||||
}
|
||||
|
||||
@@ -955,6 +960,7 @@ public class QuadTreeTest
|
||||
{
|
||||
// top detail level = 6
|
||||
public int getWidthInBlocks() { return 32; }
|
||||
public int getBlockDistanceForNodeClearing() { return 1; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user