Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9897570e6c | |||
| 653b86c51a | |||
| 24d2fa90f4 | |||
| 4f6d78189b | |||
| 1f7cf793c6 | |||
| 1908a0ccbf | |||
| fd3a8f7ddf | |||
| 592b050937 | |||
| 9fa9b430f6 | |||
| c720a36f83 | |||
| e3f586da56 | |||
| 552ad226ea | |||
| 775984f651 | |||
| b674f49600 | |||
| b592012ba8 | |||
| 5d1e8a44fd | |||
| 40b27335ea | |||
| d0b07a5d2f | |||
| cb0fee9780 | |||
| 895e9276cd | |||
| 9ee0af8b01 | |||
| fd704bf8e6 | |||
| cc2febcb5c | |||
| 809990f766 | |||
| 69941fb7f8 | |||
| 36862a968f | |||
| 27204336b2 | |||
| 4846cf5019 | |||
| f7f3c1146f | |||
| aaa5e958f0 | |||
| 726da953bd | |||
| c4f4935fdd | |||
| 5cb30ed7ce | |||
| 5f54ad0650 | |||
| 9fc4d840fc | |||
| 52b0acc452 | |||
| 4e647395e8 | |||
| 3ef8bd7e20 | |||
| ec72762067 | |||
| 4d0ed2a6dc | |||
| 7b252b173b | |||
| 25ac1de59b | |||
| 949124f8dc | |||
| c363b7fe4b | |||
| dcb049d4c2 | |||
| ea51b9135d | |||
| da31547cfc | |||
| 00f9fd8e53 | |||
| 7149baf0f6 | |||
| ef3e7763dc | |||
| f5ac5c56b4 | |||
| 77f10bed48 | |||
| 7fe0c9b0e8 | |||
| 3d13ba7645 | |||
| 7b0c66e3ae | |||
| 1b066327a8 | |||
| 43d0a971f7 | |||
| 9e60c698de | |||
| bf2affa6d1 | |||
| 98f6cea86a | |||
| 9ae01dc1f8 | |||
| 40efc5cbf3 | |||
| 66bba1c80a | |||
| d9f3b31cc5 | |||
| e465ef5325 | |||
| 225385a43f | |||
| 7d7d07416b | |||
| 5ef308cbee | |||
| d61b601c14 | |||
| 246c679a97 | |||
| 4b317a8e00 | |||
| 1debd4b875 | |||
| 5dcda31990 | |||
| ae16ed2341 | |||
| 2c266d2495 | |||
| 7e40546bc5 | |||
| 5d391c83ea | |||
| 0895bf53e3 | |||
| a7203f8f33 |
+6
@@ -42,6 +42,12 @@ public interface IDhApiBlockStateWrapper extends IDhApiUnsafeWrapper
|
|||||||
/** @since API 1.0.0 */
|
/** @since API 1.0.0 */
|
||||||
boolean isLiquid();
|
boolean isLiquid();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a value between 0 (fully transparent) and 16 (fully opaque).
|
||||||
|
* @since 6.1.0
|
||||||
|
*/
|
||||||
|
int getOpacity();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the full serialized form of the given block
|
* Returns the full serialized form of the given block
|
||||||
* as defined by DH's serialization methods.
|
* as defined by DH's serialization methods.
|
||||||
|
|||||||
+10
-2
@@ -107,16 +107,24 @@ public abstract class DhApiBlockColorOverrideEvent implements IDhApiEvent<DhApiB
|
|||||||
public IDhApiLevelWrapper getLevelWrapper() { return levelWrapper; }
|
public IDhApiLevelWrapper getLevelWrapper() { return levelWrapper; }
|
||||||
|
|
||||||
public int getColorAsInt() { return this.colorAsInt; }
|
public int getColorAsInt() { return this.colorAsInt; }
|
||||||
|
public int getAlpha() { return ColorUtil.getAlpha(this.colorAsInt); }
|
||||||
public int getRed() { return ColorUtil.getRed(this.colorAsInt); }
|
public int getRed() { return ColorUtil.getRed(this.colorAsInt); }
|
||||||
public int getGreen() { return ColorUtil.getGreen(this.colorAsInt); }
|
public int getGreen() { return ColorUtil.getGreen(this.colorAsInt); }
|
||||||
public int getBlue() { return ColorUtil.getBlue(this.colorAsInt); }
|
public int getBlue() { return ColorUtil.getBlue(this.colorAsInt); }
|
||||||
public void setColor(int red, int green, int blue) throws IllegalArgumentException
|
public void setColor(int red, int green, int blue) throws IllegalArgumentException { this.setColor(this.getAlpha(), red, green, blue); }
|
||||||
|
/**
|
||||||
|
* Note: when if you set a partially transparent alpha channel the underlying {@link IDhApiBlockStateWrapper#getOpacity()}
|
||||||
|
* method should also return a non-opaque value.
|
||||||
|
* Otherwise LODs may behave incorrectly.
|
||||||
|
*/
|
||||||
|
public void setColor(int alpha, int red, int green, int blue) throws IllegalArgumentException
|
||||||
{
|
{
|
||||||
|
ColorUtil.throwIfColorValueOutOfIntRange("alpha", alpha);
|
||||||
ColorUtil.throwIfColorValueOutOfIntRange("red", red);
|
ColorUtil.throwIfColorValueOutOfIntRange("red", red);
|
||||||
ColorUtil.throwIfColorValueOutOfIntRange("green", green);
|
ColorUtil.throwIfColorValueOutOfIntRange("green", green);
|
||||||
ColorUtil.throwIfColorValueOutOfIntRange("blue", blue);
|
ColorUtil.throwIfColorValueOutOfIntRange("blue", blue);
|
||||||
|
|
||||||
this.colorAsInt = ColorUtil.rgbToInt(red, green, blue);
|
this.colorAsInt = ColorUtil.argbToInt(alpha, red, green, blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return the block's X value in the world */
|
/** @return the block's X value in the world */
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public class DependencyInjector<BindableType extends IBindable> implements IDependencyInjector<BindableType> // Note to self: Don't try adding a generic type to IDhApiEvent, the constructor won't accept it
|
public class DependencyInjector<BindableType extends IBindable> implements IDependencyInjector<BindableType> // Note to self: Don't try adding a generic type to IDhApiEvent, the constructor won't accept it
|
||||||
{
|
{
|
||||||
protected final Map<Class<? extends BindableType>, ArrayList<BindableType>> dependencies = new HashMap<>();
|
protected final HashMap<Class<? extends BindableType>, ArrayList<BindableType>> dependencies = new HashMap<>();
|
||||||
|
|
||||||
/** Internal class reference to BindableType since we can't get it any other way. */
|
/** Internal class reference to BindableType since we can't get it any other way. */
|
||||||
protected final Class<? extends BindableType> bindableInterface;
|
protected final Class<? extends BindableType> bindableInterface;
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public final class ModInfo
|
|||||||
public static final String DEDICATED_SERVER_INITIAL_PATH = "dedicated_server_initial";
|
public static final String DEDICATED_SERVER_INITIAL_PATH = "dedicated_server_initial";
|
||||||
|
|
||||||
/** Incremented every time any packets are added, changed or removed, with a few exceptions. */
|
/** Incremented every time any packets are added, changed or removed, with a few exceptions. */
|
||||||
public static final int PROTOCOL_VERSION = 13;
|
public static final int PROTOCOL_VERSION = 14;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The full plugin channel name (RESOURCE_NAMESPACE:WRAPPER_PACKET_PATH)
|
* The full plugin channel name (RESOURCE_NAMESPACE:WRAPPER_PACKET_PATH)
|
||||||
@@ -43,16 +43,16 @@ public final class ModInfo
|
|||||||
public static final String NAME = "DistantHorizons";
|
public static final String NAME = "DistantHorizons";
|
||||||
/** Human-readable version of NAME */
|
/** Human-readable version of NAME */
|
||||||
public static final String READABLE_NAME = "Distant Horizons";
|
public static final String READABLE_NAME = "Distant Horizons";
|
||||||
public static final String VERSION = "3.0.1-b";
|
public static final String VERSION = "3.0.4-b-dev";
|
||||||
/** Returns true if the current build is an unstable developer build, false otherwise. */
|
/** Returns true if the current build is an unstable developer build, false otherwise. */
|
||||||
public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
|
public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
|
||||||
|
|
||||||
/** This version should only be updated when breaking changes are introduced to the DH API */
|
/** This version should only be updated when breaking changes are introduced to the DH API */
|
||||||
public static final int API_MAJOR_VERSION = 6;
|
public static final int API_MAJOR_VERSION = 6;
|
||||||
/** This version should be updated whenever new methods are added to the DH API */
|
/** This version should be updated whenever new methods are added to the DH API */
|
||||||
public static final int API_MINOR_VERSION = 0;
|
public static final int API_MINOR_VERSION = 1;
|
||||||
/** This version should be updated whenever non-breaking fixes are added to the DH API */
|
/** This version should be updated whenever non-breaking fixes are added to the DH API */
|
||||||
public static final int API_PATCH_VERSION = 0;
|
public static final int API_PATCH_VERSION = 1;
|
||||||
|
|
||||||
/** If the config file has an older version it'll be re-created from scratch. */
|
/** If the config file has an older version it'll be re-created from scratch. */
|
||||||
public static final int CONFIG_FILE_VERSION = 4;
|
public static final int CONFIG_FILE_VERSION = 4;
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public class Initializer
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static void init()
|
public static void preConfigInit()
|
||||||
{
|
{
|
||||||
//============================//
|
//============================//
|
||||||
// check referenced libraries //
|
// check referenced libraries //
|
||||||
@@ -177,6 +177,11 @@ public class Initializer
|
|||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/** fired after DH's config has been set up */
|
||||||
|
public static void postConfigInit()
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
//==============================//
|
//==============================//
|
||||||
@@ -238,8 +243,7 @@ public class Initializer
|
|||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode;
|
|||||||
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
|
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
|
||||||
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
|
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
|
||||||
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
|
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
|
||||||
|
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
|
||||||
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
||||||
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
|
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
@@ -40,6 +41,8 @@ import com.seibel.distanthorizons.core.util.objects.Pair;
|
|||||||
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
|
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
|
||||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IImmersivePortalsAccessor;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccessor;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhMetaRenderer;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhMetaRenderer;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhVanillaFadeRenderer;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhVanillaFadeRenderer;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhTestTriangleRenderer;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhTestTriangleRenderer;
|
||||||
@@ -51,7 +54,6 @@ import com.seibel.distanthorizons.coreapi.ModInfo;
|
|||||||
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
|
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
|
||||||
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
|
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
|
|
||||||
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
|
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
|
||||||
import com.seibel.distanthorizons.core.world.DhClientWorld;
|
import com.seibel.distanthorizons.core.world.DhClientWorld;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||||
@@ -63,8 +65,10 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
@@ -84,6 +88,11 @@ public class ClientApi
|
|||||||
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||||
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
|
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
|
||||||
|
|
||||||
|
// Need to classload this field later because otherwise it will be null even when Immersive Portals is present.
|
||||||
|
public static class Late {
|
||||||
|
private static final IImmersivePortalsAccessor IMMERSIVE_PORTALS = ModAccessorInjector.INSTANCE.get(IImmersivePortalsAccessor.class);
|
||||||
|
}
|
||||||
|
|
||||||
/** this includes the is dev build message and low allocated memory warning */
|
/** this includes the is dev build message and low allocated memory warning */
|
||||||
private static final int MS_BETWEEN_STATIC_STARTUP_MESSAGES = 4_000;
|
private static final int MS_BETWEEN_STATIC_STARTUP_MESSAGES = 4_000;
|
||||||
|
|
||||||
@@ -116,7 +125,7 @@ public class ClientApi
|
|||||||
|
|
||||||
public boolean rendererDisabledBecauseOfExceptions = false;
|
public boolean rendererDisabledBecauseOfExceptions = false;
|
||||||
|
|
||||||
private final ClientPluginChannelApi pluginChannelApi = new ClientPluginChannelApi(this::clientLevelLoadEvent, this::clientLevelUnloadEvent);
|
private final ClientPluginChannelApi pluginChannelApi = new ClientPluginChannelApi();
|
||||||
|
|
||||||
/** Delay loading the first level to give the server some time to respond with level to actually load */
|
/** Delay loading the first level to give the server some time to respond with level to actually load */
|
||||||
private Timer firstLevelLoadTimer;
|
private Timer firstLevelLoadTimer;
|
||||||
@@ -124,8 +133,8 @@ public class ClientApi
|
|||||||
|
|
||||||
/** Holds any levels that were loaded before the {@link ClientApi#onClientOnlyConnected} was fired. */
|
/** Holds any levels that were loaded before the {@link ClientApi#onClientOnlyConnected} was fired. */
|
||||||
public final HashSet<IClientLevelWrapper> waitingClientLevels = new HashSet<>();
|
public final HashSet<IClientLevelWrapper> waitingClientLevels = new HashSet<>();
|
||||||
/** Holds any chunks that were loaded before the {@link ClientApi#clientLevelLoadEvent(IClientLevelWrapper)} was fired. */
|
/** Holds any chunks that were found before the client levels are loaded. */
|
||||||
public final HashMap<Pair<IClientLevelWrapper, DhChunkPos>, IChunkWrapper> waitingChunkByClientLevelAndPos = new HashMap<>();
|
public final Map<Pair<IClientLevelWrapper, DhChunkPos>, IChunkWrapper> waitingChunkByClientLevelAndPos = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/** publicly available so {@link F3Screen} can display the error */
|
/** publicly available so {@link F3Screen} can display the error */
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -141,12 +150,25 @@ public class ClientApi
|
|||||||
* tracked should also be to keep the ratio roughly the same.
|
* tracked should also be to keep the ratio roughly the same.
|
||||||
* @see ClientApi#MIN_MS_BETWEEN_SPEED_CHECKS
|
* @see ClientApi#MIN_MS_BETWEEN_SPEED_CHECKS
|
||||||
*/
|
*/
|
||||||
public RollingAverage cameraSpeedRollingAverage = new RollingAverage(40);
|
private RollingAverage cameraSpeedRollingAverage = new RollingAverage(40);
|
||||||
private Vec3d lastCameraPosForSpeedCheck = new Vec3d();
|
private Vec3d lastCameraPosForSpeedCheck = new Vec3d();
|
||||||
private long msSinceLastSpeedCheck = 0L;
|
private long msSinceLastSpeedCheck = 0L;
|
||||||
|
public double getAvgCameraSpeed()
|
||||||
|
{
|
||||||
|
return cameraSpeedRollingAverage.getAverage();
|
||||||
|
}
|
||||||
|
|
||||||
public static long firstRenderTimeMs = 0;
|
public static long firstRenderTimeMs = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* keeping track of this is necessary to fix
|
||||||
|
* out-of-date LODs from rendering when the shading
|
||||||
|
* is changed by Iris, causing LODs to often
|
||||||
|
* lack the side shading, which looks pretty bad
|
||||||
|
* when shaders are disabled.
|
||||||
|
*/
|
||||||
|
private boolean irisShadersEnabledLastFrame = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//==============//
|
//==============//
|
||||||
@@ -160,11 +182,11 @@ public class ClientApi
|
|||||||
//==============//
|
//==============//
|
||||||
// world events //
|
// world events //
|
||||||
//==============//
|
//==============//
|
||||||
//region
|
//region world events
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* May be fired slightly before or after the associated
|
* May be fired slightly before or after the associated
|
||||||
* {@link ClientApi#clientLevelLoadEvent(IClientLevelWrapper)} event
|
* level is loaded
|
||||||
* depending on how the host mod loader functions. <br><br>
|
* depending on how the host mod loader functions. <br><br>
|
||||||
*
|
*
|
||||||
* Synchronized shouldn't be necessary, but is present to match {@see onClientOnlyDisconnected} and prevent any unforeseen issues.
|
* Synchronized shouldn't be necessary, but is present to match {@see onClientOnlyDisconnected} and prevent any unforeseen issues.
|
||||||
@@ -201,14 +223,6 @@ public class ClientApi
|
|||||||
|
|
||||||
this.pluginChannelApi.onJoinServer(world.networkState.getSession());
|
this.pluginChannelApi.onJoinServer(world.networkState.getSession());
|
||||||
world.networkState.sendConfigMessage();
|
world.networkState.sendConfigMessage();
|
||||||
|
|
||||||
LOGGER.info("Loading [" + this.waitingClientLevels.size() + "] waiting client level wrappers.");
|
|
||||||
for (IClientLevelWrapper level : this.waitingClientLevels)
|
|
||||||
{
|
|
||||||
this.clientLevelLoadEvent(level);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.waitingClientLevels.clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +249,55 @@ public class ClientApi
|
|||||||
|
|
||||||
// remove any waiting items
|
// remove any waiting items
|
||||||
this.waitingChunkByClientLevelAndPos.clear();
|
this.waitingChunkByClientLevelAndPos.clear();
|
||||||
this.waitingClientLevels.clear();
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==============//
|
||||||
|
// level events //
|
||||||
|
//==============//
|
||||||
|
//region level events
|
||||||
|
|
||||||
|
/**
|
||||||
|
* used in conjunction with the server networking to
|
||||||
|
* handle level load requests.
|
||||||
|
*/
|
||||||
|
public boolean canLoadClientLevel(IClientLevelWrapper wrapper)
|
||||||
|
{
|
||||||
|
// wait a moment before loading the level to give the server a chance to handle the client's login request
|
||||||
|
if (MC_CLIENT.clientConnectedToDedicatedServer())
|
||||||
|
{
|
||||||
|
if (this.firstLevelLoadTimer == null)
|
||||||
|
{
|
||||||
|
this.firstLevelLoadTimer = TimerUtil.CreateTimer("FirstLevelLoadTimer");
|
||||||
|
this.firstLevelLoadTimer.schedule(new TimerTask()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run() { canLoadClientLevel(wrapper); }
|
||||||
|
}, FIRST_LEVEL_LOAD_DELAY_IN_MS);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.firstLevelLoadTimer.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.pluginChannelApi.allowLevelLoading(wrapper))
|
||||||
|
{
|
||||||
|
LOGGER.debug("Client levels in this connection are managed by the server, skipping auto-load of: ["+wrapper+"]");
|
||||||
|
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
|
||||||
|
if (world == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instead of attempting to load themselves, send the config and wait for a server provided level key.
|
||||||
|
((DhClientWorld) world).networkState.sendLevelInitRequest(wrapper.getDimensionName());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
@@ -247,95 +309,7 @@ public class ClientApi
|
|||||||
//==============//
|
//==============//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
public void clientLevelUnloadEvent(IClientLevelWrapper level)
|
public void loadWaitingChunksForLevel(IClientLevelWrapper level)
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
LOGGER.info("Unloading client level [" + level.getClass().getSimpleName() + "]-[" + level.getDhIdentifier() + "].");
|
|
||||||
|
|
||||||
if (level instanceof IServerKeyedClientLevel)
|
|
||||||
{
|
|
||||||
this.pluginChannelApi.onClientLevelUnload();
|
|
||||||
}
|
|
||||||
|
|
||||||
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
|
|
||||||
if (world != null)
|
|
||||||
{
|
|
||||||
world.unloadLevel(level);
|
|
||||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(level));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.waitingClientLevels.remove(level);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
// handle errors here to prevent blowing up a mixin or API up stream
|
|
||||||
LOGGER.error("Unexpected error in ClientApi.clientLevelUnloadEvent(), error: "+e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clientLevelLoadEvent(@Nullable IClientLevelWrapper levelWrapper)
|
|
||||||
{
|
|
||||||
// can happen if there was an issue during level load
|
|
||||||
if (levelWrapper == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// wait a moment before loading the level to give the server a chance to handle the client's login request
|
|
||||||
if (MC_CLIENT.clientConnectedToDedicatedServer())
|
|
||||||
{
|
|
||||||
if (this.firstLevelLoadTimer == null)
|
|
||||||
{
|
|
||||||
this.firstLevelLoadTimer = TimerUtil.CreateTimer("FirstLevelLoadTimer");
|
|
||||||
this.firstLevelLoadTimer.schedule(new TimerTask()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void run() { ClientApi.this.clientLevelLoadEvent(levelWrapper); }
|
|
||||||
}, FIRST_LEVEL_LOAD_DELAY_IN_MS);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.firstLevelLoadTimer.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
LOGGER.info("Loading client level [" + levelWrapper + "]-[" + levelWrapper.getDhIdentifier() + "].");
|
|
||||||
|
|
||||||
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
|
|
||||||
if (world != null)
|
|
||||||
{
|
|
||||||
if (!this.pluginChannelApi.allowLevelLoading(levelWrapper))
|
|
||||||
{
|
|
||||||
LOGGER.info("Levels in this connection are managed by the server, skipping auto-load.");
|
|
||||||
|
|
||||||
// Instead of attempting to load themselves, send the config and wait for a server provided level key.
|
|
||||||
((DhClientWorld) world).networkState.sendConfigMessage();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
world.getOrLoadLevel(levelWrapper);
|
|
||||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(levelWrapper));
|
|
||||||
|
|
||||||
this.loadWaitingChunksForLevel(levelWrapper);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.waitingClientLevels.add(levelWrapper);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
// handle errors here to prevent blowing up a mixin or API up stream
|
|
||||||
LOGGER.error("Unexpected error in ClientApi.clientLevelLoadEvent(), error: "+e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void loadWaitingChunksForLevel(IClientLevelWrapper level)
|
|
||||||
{
|
{
|
||||||
HashSet<Pair<IClientLevelWrapper, DhChunkPos>> keysToRemove = new HashSet<>();
|
HashSet<Pair<IClientLevelWrapper, DhChunkPos>> keysToRemove = new HashSet<>();
|
||||||
for (Pair<IClientLevelWrapper, DhChunkPos> levelChunkPair : this.waitingChunkByClientLevelAndPos.keySet())
|
for (Pair<IClientLevelWrapper, DhChunkPos> levelChunkPair : this.waitingChunkByClientLevelAndPos.keySet())
|
||||||
@@ -364,7 +338,7 @@ public class ClientApi
|
|||||||
//============//
|
//============//
|
||||||
// networking //
|
// networking //
|
||||||
//============//
|
//============//
|
||||||
//region
|
//region networking
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Forwards a decoded message into the registered handlers.
|
* Forwards a decoded message into the registered handlers.
|
||||||
@@ -404,7 +378,7 @@ public class ClientApi
|
|||||||
//===============//
|
//===============//
|
||||||
// LOD rendering //
|
// LOD rendering //
|
||||||
//===============//
|
//===============//
|
||||||
//region
|
//region lod rendering
|
||||||
|
|
||||||
/** Should be called before {@link ClientApi#renderDeferredLodsForShaders} */
|
/** Should be called before {@link ClientApi#renderDeferredLodsForShaders} */
|
||||||
public void renderLods() { this.renderLodLayer(false); }
|
public void renderLods() { this.renderLodLayer(false); }
|
||||||
@@ -428,12 +402,16 @@ public class ClientApi
|
|||||||
//===========//
|
//===========//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
//DhApiTerrainDataRepo.asyncDebugMethod(
|
// only run these tasks once per frame
|
||||||
// RENDER_STATE.clientLevelWrapper,
|
if (!renderingDeferredLayer)
|
||||||
// MC_CLIENT.getPlayerBlockPos().getX(),
|
{
|
||||||
// MC_CLIENT.getPlayerBlockPos().getY(),
|
//DhApiTerrainDataRepo.asyncDebugMethod(
|
||||||
// MC_CLIENT.getPlayerBlockPos().getZ()
|
// RENDER_STATE.clientLevelWrapper,
|
||||||
//);
|
// MC_CLIENT.getPlayerBlockPos().getX(),
|
||||||
|
// MC_CLIENT.getPlayerBlockPos().getY(),
|
||||||
|
// MC_CLIENT.getPlayerBlockPos().getZ()
|
||||||
|
//);
|
||||||
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
@@ -482,7 +460,7 @@ public class ClientApi
|
|||||||
//region
|
//region
|
||||||
|
|
||||||
long nowMs = System.currentTimeMillis();
|
long nowMs = System.currentTimeMillis();
|
||||||
if (this.msSinceLastSpeedCheck + MIN_MS_BETWEEN_SPEED_CHECKS < nowMs)
|
if (this.msSinceLastSpeedCheck + MIN_MS_BETWEEN_SPEED_CHECKS < nowMs && (Late.IMMERSIVE_PORTALS == null || !Late.IMMERSIVE_PORTALS.isRenderingPortal()))
|
||||||
{
|
{
|
||||||
// calc time since last check
|
// calc time since last check
|
||||||
double secSinceLastCheck = (nowMs - this.msSinceLastSpeedCheck) / 1_000.0;
|
double secSinceLastCheck = (nowMs - this.msSinceLastSpeedCheck) / 1_000.0;
|
||||||
@@ -499,6 +477,27 @@ public class ClientApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//====================//
|
||||||
|
// Iris data re-build //
|
||||||
|
//====================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
// delayed getter since ClientApi is created before this accessor is bound
|
||||||
|
IIrisAccessor irisAccessor = ModAccessorInjector.INSTANCE.get(IIrisAccessor.class);
|
||||||
|
if (irisAccessor != null)
|
||||||
|
{
|
||||||
|
boolean shadersActive = irisAccessor.isShaderPackInUse();
|
||||||
|
if (this.irisShadersEnabledLastFrame != shadersActive)
|
||||||
|
{
|
||||||
|
this.irisShadersEnabledLastFrame = shadersActive;
|
||||||
|
DhApi.Delayed.renderProxy.clearRenderDataCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -667,7 +666,7 @@ public class ClientApi
|
|||||||
//================//
|
//================//
|
||||||
// fade rendering //
|
// fade rendering //
|
||||||
//================//
|
//================//
|
||||||
//region
|
//region fade rendering
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The first fade pass.
|
* The first fade pass.
|
||||||
@@ -690,8 +689,7 @@ public class ClientApi
|
|||||||
// or if LOD-only mode is enabled (fading is used to remove the MC render pass)
|
// or if LOD-only mode is enabled (fading is used to remove the MC render pass)
|
||||||
|| Config.Client.Advanced.Debugging.lodOnlyMode.get()
|
|| Config.Client.Advanced.Debugging.lodOnlyMode.get()
|
||||||
)
|
)
|
||||||
// don't fade when Iris shaders are active, otherwise the rendering can get weird
|
&& shouldRenderFade())
|
||||||
&& !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
|
|
||||||
{
|
{
|
||||||
RenderParams renderParams = new RenderParams(EDhApiRenderPass.OPAQUE, RENDER_STATE);
|
RenderParams renderParams = new RenderParams(EDhApiRenderPass.OPAQUE, RENDER_STATE);
|
||||||
fadeRenderer.render(renderParams);
|
fadeRenderer.render(renderParams);
|
||||||
@@ -720,8 +718,7 @@ public class ClientApi
|
|||||||
// or if LOD-only mode is enabled (fading is used to remove the MC render pass)
|
// or if LOD-only mode is enabled (fading is used to remove the MC render pass)
|
||||||
|| Config.Client.Advanced.Debugging.lodOnlyMode.get()
|
|| Config.Client.Advanced.Debugging.lodOnlyMode.get()
|
||||||
)
|
)
|
||||||
// don't fade when Iris shaders are active, otherwise the rendering can get weird
|
&& shouldRenderFade();
|
||||||
&& !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering();
|
|
||||||
if (renderFade)
|
if (renderFade)
|
||||||
{
|
{
|
||||||
RenderParams renderParams = new RenderParams(EDhApiRenderPass.TRANSPARENT, RENDER_STATE);
|
RenderParams renderParams = new RenderParams(EDhApiRenderPass.TRANSPARENT, RENDER_STATE);
|
||||||
@@ -730,6 +727,25 @@ public class ClientApi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean shouldRenderFade()
|
||||||
|
{
|
||||||
|
// don't fade when Iris shaders are active, otherwise the rendering can get weird
|
||||||
|
if (DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When immersive portals and sodium are combined the fade renders on top of the portal, so turn it off when a portal is on-screen.
|
||||||
|
IImmersivePortalsAccessor immersivePortals = ModAccessorInjector.INSTANCE.get(IImmersivePortalsAccessor.class);
|
||||||
|
if (immersivePortals != null
|
||||||
|
&& immersivePortals.wasPortalRecentlyVisible())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
@@ -737,7 +753,7 @@ public class ClientApi
|
|||||||
//==========//
|
//==========//
|
||||||
// keyboard //
|
// keyboard //
|
||||||
//==========//
|
//==========//
|
||||||
//region
|
//region keyboard
|
||||||
|
|
||||||
/** Trigger once on key press, with CLIENT PLAYER. */
|
/** Trigger once on key press, with CLIENT PLAYER. */
|
||||||
public void keyPressedEvent(int glfwKey)
|
public void keyPressedEvent(int glfwKey)
|
||||||
@@ -773,7 +789,7 @@ public class ClientApi
|
|||||||
//======//
|
//======//
|
||||||
// chat //
|
// chat //
|
||||||
//======//
|
//======//
|
||||||
//region
|
//region chat
|
||||||
|
|
||||||
private void sendQueuedChatMessages()
|
private void sendQueuedChatMessages()
|
||||||
{
|
{
|
||||||
|
|||||||
+7
-25
@@ -10,6 +10,7 @@ import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent
|
|||||||
import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage;
|
import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage;
|
||||||
import com.seibel.distanthorizons.core.network.session.NetworkSession;
|
import com.seibel.distanthorizons.core.network.session.NetworkSession;
|
||||||
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
|
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
|
||||||
|
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -30,9 +31,6 @@ public class ClientPluginChannelApi
|
|||||||
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||||
private static final IKeyedClientLevelManager KEYED_CLIENT_LEVEL_MANAGER = SingletonInjector.INSTANCE.get(IKeyedClientLevelManager.class);
|
private static final IKeyedClientLevelManager KEYED_CLIENT_LEVEL_MANAGER = SingletonInjector.INSTANCE.get(IKeyedClientLevelManager.class);
|
||||||
|
|
||||||
private final Consumer<IServerKeyedClientLevel> levelLoadHandler;
|
|
||||||
private final Consumer<IClientLevelWrapper> levelUnloadHandler;
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public NetworkSession networkSession;
|
public NetworkSession networkSession;
|
||||||
|
|
||||||
@@ -42,10 +40,8 @@ public class ClientPluginChannelApi
|
|||||||
// constructor //
|
// constructor //
|
||||||
//=============//
|
//=============//
|
||||||
|
|
||||||
public ClientPluginChannelApi(Consumer<IServerKeyedClientLevel> levelLoadHandler, Consumer<IClientLevelWrapper> levelUnloadHandler)
|
public ClientPluginChannelApi()
|
||||||
{
|
{
|
||||||
this.levelLoadHandler = levelLoadHandler;
|
|
||||||
this.levelUnloadHandler = levelUnloadHandler;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -94,24 +90,6 @@ public class ClientPluginChannelApi
|
|||||||
{
|
{
|
||||||
IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true);
|
IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true);
|
||||||
IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel();
|
IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel();
|
||||||
|
|
||||||
if (existingKeyedClientLevel != null)
|
|
||||||
{
|
|
||||||
if (!existingKeyedClientLevel.getServerLevelKey().equals(msg.levelKey))
|
|
||||||
{
|
|
||||||
LOGGER.info("Unloading previous level with key: [" + existingKeyedClientLevel.getServerLevelKey() + "].");
|
|
||||||
this.levelUnloadHandler.accept(existingKeyedClientLevel);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LOGGER.info("Level key matches the previous level key, ignoring the message.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LOGGER.info("Unloading non-keyed level: [" + clientLevel.getDhIdentifier() + "].");
|
|
||||||
this.levelUnloadHandler.accept(clientLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingKeyedClientLevel == null
|
if (existingKeyedClientLevel == null
|
||||||
|| !existingKeyedClientLevel.getServerKey().equals(msg.serverKey)
|
|| !existingKeyedClientLevel.getServerKey().equals(msg.serverKey)
|
||||||
@@ -119,7 +97,11 @@ public class ClientPluginChannelApi
|
|||||||
{
|
{
|
||||||
LOGGER.info("Loading level with key: [" + msg.levelKey + "].");
|
LOGGER.info("Loading level with key: [" + msg.levelKey + "].");
|
||||||
IServerKeyedClientLevel keyedLevel = KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel, msg.serverKey, msg.levelKey);
|
IServerKeyedClientLevel keyedLevel = KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel, msg.serverKey, msg.levelKey);
|
||||||
this.levelLoadHandler.accept(keyedLevel);
|
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
|
||||||
|
if (world != null)
|
||||||
|
{
|
||||||
|
world.getOrLoadLevel(keyedLevel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,10 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.core.api.internal;
|
package com.seibel.distanthorizons.core.api.internal;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelLoadEvent;
|
|
||||||
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelUnloadEvent;
|
|
||||||
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
|
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
|
||||||
import com.seibel.distanthorizons.core.network.messages.MessageRegistry;
|
import com.seibel.distanthorizons.core.network.messages.MessageRegistry;
|
||||||
import com.seibel.distanthorizons.core.world.*;
|
import com.seibel.distanthorizons.core.world.*;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
|
||||||
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
|
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||||
@@ -77,7 +74,6 @@ public class ServerApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//==============//
|
//==============//
|
||||||
// level events //
|
// level events //
|
||||||
//==============//
|
//==============//
|
||||||
@@ -90,7 +86,6 @@ public class ServerApi
|
|||||||
if (serverWorld != null)
|
if (serverWorld != null)
|
||||||
{
|
{
|
||||||
serverWorld.getOrLoadLevel(levelWrapper);
|
serverWorld.getOrLoadLevel(levelWrapper);
|
||||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(levelWrapper));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public void serverLevelUnloadEvent(IServerLevelWrapper level)
|
public void serverLevelUnloadEvent(IServerLevelWrapper level)
|
||||||
@@ -101,12 +96,10 @@ public class ServerApi
|
|||||||
if (serverWorld != null)
|
if (serverWorld != null)
|
||||||
{
|
{
|
||||||
serverWorld.unloadLevel(level);
|
serverWorld.unloadLevel(level);
|
||||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(level));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=======================//
|
//=======================//
|
||||||
// chunk modified events //
|
// chunk modified events //
|
||||||
//=======================//
|
//=======================//
|
||||||
@@ -122,7 +115,7 @@ public class ServerApi
|
|||||||
|
|
||||||
public void serverPlayerJoinEvent(IServerPlayerWrapper player)
|
public void serverPlayerJoinEvent(IServerPlayerWrapper player)
|
||||||
{
|
{
|
||||||
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly())
|
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -136,7 +129,7 @@ public class ServerApi
|
|||||||
}
|
}
|
||||||
public void serverPlayerDisconnectEvent(IServerPlayerWrapper player)
|
public void serverPlayerDisconnectEvent(IServerPlayerWrapper player)
|
||||||
{
|
{
|
||||||
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly())
|
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -150,7 +143,7 @@ public class ServerApi
|
|||||||
}
|
}
|
||||||
public void serverPlayerLevelChangeEvent(IServerPlayerWrapper player, IServerLevelWrapper originLevel, IServerLevelWrapper destinationLevel)
|
public void serverPlayerLevelChangeEvent(IServerPlayerWrapper player, IServerLevelWrapper originLevel, IServerLevelWrapper destinationLevel)
|
||||||
{
|
{
|
||||||
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly())
|
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -170,7 +163,7 @@ public class ServerApi
|
|||||||
*/
|
*/
|
||||||
public void pluginMessageReceived(IServerPlayerWrapper player, @NotNull AbstractNetworkMessage message)
|
public void pluginMessageReceived(IServerPlayerWrapper player, @NotNull AbstractNetworkMessage message)
|
||||||
{
|
{
|
||||||
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly())
|
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ public class SharedApi
|
|||||||
//region
|
//region
|
||||||
|
|
||||||
private SharedApi() { }
|
private SharedApi() { }
|
||||||
public static void init() { Initializer.init(); }
|
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
@@ -212,7 +211,7 @@ public class SharedApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ignore updates if the world is read-only
|
// ignore updates if the world is read-only
|
||||||
if (DhApiWorldProxy.INSTANCE.getReadOnly())
|
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -964,6 +964,7 @@ public class Config
|
|||||||
public static ConfigCategory debugWireframe = new ConfigCategory.Builder().set(DebugWireframe.class).build();
|
public static ConfigCategory debugWireframe = new ConfigCategory.Builder().set(DebugWireframe.class).build();
|
||||||
public static ConfigCategory openGl = new ConfigCategory.Builder().set(OpenGl.class).build();
|
public static ConfigCategory openGl = new ConfigCategory.Builder().set(OpenGl.class).build();
|
||||||
public static ConfigCategory columnBuilderDebugging = new ConfigCategory.Builder().set(ColumnBuilderDebugging.class).build();
|
public static ConfigCategory columnBuilderDebugging = new ConfigCategory.Builder().set(ColumnBuilderDebugging.class).build();
|
||||||
|
public static ConfigCategory positionFinderDebugging = new ConfigCategory.Builder().set(PositionFinder.class).build();
|
||||||
public static ConfigCategory f3Screen = new ConfigCategory.Builder().set(F3Screen.class).build();
|
public static ConfigCategory f3Screen = new ConfigCategory.Builder().set(F3Screen.class).build();
|
||||||
public static ConfigCategory exampleConfigScreen = new ConfigCategory.Builder().set(ExampleConfigScreen.class).build();
|
public static ConfigCategory exampleConfigScreen = new ConfigCategory.Builder().set(ExampleConfigScreen.class).build();
|
||||||
|
|
||||||
@@ -1095,6 +1096,36 @@ public class Config
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class PositionFinder
|
||||||
|
{
|
||||||
|
//public static ConfigUIComment positionFinderHeader = new ConfigUIComment.Builder().setParentConfigClass(ColumnBuilderDebugging.class).build();
|
||||||
|
|
||||||
|
public static ConfigEntry<Boolean> positionFinderEnable = new ConfigEntry.Builder<Boolean>()
|
||||||
|
.set(false)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
public static ConfigEntry<Integer> positionFinderDetailLevel = new ConfigEntry.Builder<Integer>()
|
||||||
|
.set((int) DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
|
||||||
|
.build();
|
||||||
|
public static ConfigEntry<Integer> positionFinderXPos = new ConfigEntry.Builder<Integer>()
|
||||||
|
.set(0)
|
||||||
|
.build();
|
||||||
|
public static ConfigEntry<Integer> positionFinderZPos = new ConfigEntry.Builder<Integer>()
|
||||||
|
.set(0)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
public static ConfigEntry<Integer> positionFinderMinBlockY = new ConfigEntry.Builder<Integer>()
|
||||||
|
.set(-64)
|
||||||
|
.build();
|
||||||
|
public static ConfigEntry<Integer> positionFinderMaxBlockY = new ConfigEntry.Builder<Integer>()
|
||||||
|
.set(125)
|
||||||
|
.build();
|
||||||
|
public static ConfigEntry<Float> positionFinderMarginPercent = new ConfigEntry.Builder<Float>()
|
||||||
|
.set(0.0f)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public static class F3Screen
|
public static class F3Screen
|
||||||
{
|
{
|
||||||
public static ConfigUIComment f3ScreenHeader = new ConfigUIComment.Builder().setParentConfigClass(F3Screen.class).build();
|
public static ConfigUIComment f3ScreenHeader = new ConfigUIComment.Builder().setParentConfigClass(F3Screen.class).build();
|
||||||
|
|||||||
+84
@@ -0,0 +1,84 @@
|
|||||||
|
package com.seibel.distanthorizons.core.config.eventHandlers;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
|
||||||
|
import com.seibel.distanthorizons.core.util.TimerUtil;
|
||||||
|
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
|
public abstract class AbstractDelayedConfigEventHandler implements IConfigListener
|
||||||
|
{
|
||||||
|
public static final long DEFAULT_TIMEOUT_IN_MS = 2_000L;
|
||||||
|
|
||||||
|
/** how long to wait in milliseconds before applying the config changes */
|
||||||
|
private final long timeoutInMs;
|
||||||
|
private Timer timer;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=============//
|
||||||
|
// constructor //
|
||||||
|
//=============//
|
||||||
|
//region
|
||||||
|
|
||||||
|
public AbstractDelayedConfigEventHandler(long timeoutInMs) { this.timeoutInMs = timeoutInMs; }
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==================//
|
||||||
|
// abstract methods //
|
||||||
|
//==================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
public abstract void onConfigTimeout();
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//========//
|
||||||
|
// events //
|
||||||
|
//========//
|
||||||
|
//region
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigValueSet()
|
||||||
|
{
|
||||||
|
if (this.timeoutInMs > 0)
|
||||||
|
{
|
||||||
|
this.refreshRenderDataAfterTimeout();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.onConfigTimeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Calling this method multiple times will reset the timer */
|
||||||
|
private synchronized void refreshRenderDataAfterTimeout() // synchronized to prevent potential threading issues when adding/removing the timer
|
||||||
|
{
|
||||||
|
// stop the previous timer if one exists
|
||||||
|
if (this.timer != null)
|
||||||
|
{
|
||||||
|
this.timer.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new timer task
|
||||||
|
TimerTask timerTask = new TimerTask()
|
||||||
|
{
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
AbstractDelayedConfigEventHandler.this.onConfigTimeout();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.timer = TimerUtil.CreateTimer("AbstractDelayedConfigTimer");
|
||||||
|
this.timer.schedule(timerTask, this.timeoutInMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+11
-44
@@ -27,72 +27,36 @@ import com.seibel.distanthorizons.core.util.TimerUtil;
|
|||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
|
|
||||||
public class ReloadLodsConfigEventHandler implements IConfigListener
|
public class ReloadLodsConfigEventHandler extends AbstractDelayedConfigEventHandler
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* should be used for user facing UI options
|
* should be used for user facing UI options
|
||||||
* this allows the user a second to click through options before they're applied
|
* this allows the user a second to click through options before they're applied
|
||||||
*/
|
*/
|
||||||
public static ReloadLodsConfigEventHandler DELAYED_INSTANCE = new ReloadLodsConfigEventHandler(2_000L);
|
public static ReloadLodsConfigEventHandler DELAYED_INSTANCE = new ReloadLodsConfigEventHandler(AbstractDelayedConfigEventHandler.DEFAULT_TIMEOUT_IN_MS);
|
||||||
/** should be used for debug options so their change can be seen instantly */
|
/** should be used for debug options so their change can be seen instantly */
|
||||||
public static ReloadLodsConfigEventHandler INSTANT_INSTANCE = new ReloadLodsConfigEventHandler(0);
|
public static ReloadLodsConfigEventHandler INSTANT_INSTANCE = new ReloadLodsConfigEventHandler(0);
|
||||||
|
|
||||||
/** how long to wait in milliseconds before applying the config changes */
|
|
||||||
private final long timeoutInMs;
|
|
||||||
private Timer cacheClearingTimer;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=============//
|
//=============//
|
||||||
// constructor //
|
// constructor //
|
||||||
//=============//
|
//=============//
|
||||||
|
//region
|
||||||
|
|
||||||
public ReloadLodsConfigEventHandler(long timeoutInMs)
|
public ReloadLodsConfigEventHandler(long timeoutInMs) { super(timeoutInMs); }
|
||||||
{
|
|
||||||
this.timeoutInMs = timeoutInMs;
|
//endregion
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//========//
|
//========//
|
||||||
// events //
|
// events //
|
||||||
//========//
|
//========//
|
||||||
|
//region
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConfigValueSet()
|
public void onConfigTimeout()
|
||||||
{
|
|
||||||
if (this.timeoutInMs > 0)
|
|
||||||
{
|
|
||||||
this.refreshRenderDataAfterTimeout();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
clearRenderDataCache();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Calling this method multiple times will reset the timer */
|
|
||||||
private synchronized void refreshRenderDataAfterTimeout() // synchronized to prevent potential threading issues when adding/removing the timer
|
|
||||||
{
|
|
||||||
// stop the previous timer if one exists
|
|
||||||
if (this.cacheClearingTimer != null)
|
|
||||||
{
|
|
||||||
this.cacheClearingTimer.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a new timer task
|
|
||||||
TimerTask timerTask = new TimerTask()
|
|
||||||
{
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
clearRenderDataCache();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.cacheClearingTimer = TimerUtil.CreateTimer("RenderCacheClearConfigTimer");
|
|
||||||
this.cacheClearingTimer.schedule(timerTask, this.timeoutInMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void clearRenderDataCache()
|
|
||||||
{
|
{
|
||||||
IDhApiRenderProxy renderProxy = DhApi.Delayed.renderProxy;
|
IDhApiRenderProxy renderProxy = DhApi.Delayed.renderProxy;
|
||||||
if (renderProxy != null)
|
if (renderProxy != null)
|
||||||
@@ -101,5 +65,8 @@ public class ReloadLodsConfigEventHandler implements IConfigListener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-3
@@ -32,7 +32,9 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
|||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
||||||
import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
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();
|
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
||||||
|
|
||||||
@@ -43,18 +45,22 @@ public class RenderBlockCacheCsvHandler implements IConfigListener
|
|||||||
//=============//
|
//=============//
|
||||||
// constructor //
|
// constructor //
|
||||||
//=============//
|
//=============//
|
||||||
|
//region
|
||||||
|
|
||||||
/** private since we only ever need one handler at a time */
|
/** private since we only ever need one handler at a time */
|
||||||
private RenderBlockCacheCsvHandler() { }
|
private RenderBlockCacheCsvHandler() { super(AbstractDelayedConfigEventHandler.DEFAULT_TIMEOUT_IN_MS); }
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=================//
|
//=================//
|
||||||
// config handling //
|
// config handling //
|
||||||
//=================//
|
//=================//
|
||||||
|
//region
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConfigValueSet()
|
public void onConfigTimeout()
|
||||||
{
|
{
|
||||||
IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
|
IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
|
||||||
if (wrapperFactory != null)
|
if (wrapperFactory != null)
|
||||||
@@ -64,6 +70,8 @@ public class RenderBlockCacheCsvHandler implements IConfigListener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,16 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
|
|||||||
@Nullable
|
@Nullable
|
||||||
private T apiValue;
|
private T apiValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will be null if un-set. <br> <br>
|
||||||
|
*
|
||||||
|
* Some options aren't supported on all Minecraft versions,
|
||||||
|
* in those cases this value will be set to override the
|
||||||
|
* config file option.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private T mcVersionOverrideValue;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=============//
|
//=============//
|
||||||
@@ -127,7 +137,14 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
|
|||||||
return this.allowApiOverride
|
return this.allowApiOverride
|
||||||
&& this.apiValue != null;
|
&& this.apiValue != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** setting to null will allow the config to be used normally */
|
||||||
|
public void setMcVersionOverrideValue(@Nullable T value)
|
||||||
|
{ this.mcVersionOverrideValue = value; }
|
||||||
|
|
||||||
|
public boolean mcVersionOverridePresent()
|
||||||
|
{ return this.mcVersionOverrideValue != null; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should only be used when loading the config from file. <Br>
|
* Should only be used when loading the config from file. <Br>
|
||||||
* Sets the value without informing the rest of the code (ie, it doesn't call listeners, or saving the value to file).
|
* Sets the value without informing the rest of the code (ie, it doesn't call listeners, or saving the value to file).
|
||||||
@@ -183,6 +200,12 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
|
|||||||
@Override
|
@Override
|
||||||
public T get()
|
public T get()
|
||||||
{
|
{
|
||||||
|
// always use the MC version specific option if defined
|
||||||
|
if (this.mcVersionOverrideValue != null)
|
||||||
|
{
|
||||||
|
return this.mcVersionOverrideValue;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.allowApiOverride
|
if (this.allowApiOverride
|
||||||
&& this.apiValue != null)
|
&& this.apiValue != null)
|
||||||
{
|
{
|
||||||
|
|||||||
+51
-51
@@ -59,9 +59,19 @@ public final class BufferQuad
|
|||||||
|
|
||||||
public boolean hasError = false;
|
public boolean hasError = false;
|
||||||
|
|
||||||
|
// Pre-computed sort keys to avoid recomputing on every comparison
|
||||||
|
// Slight increase in memory for reduction in cpu usage
|
||||||
|
public final long sortKeyEastWest;
|
||||||
|
public final long sortKeyNorthSouth;
|
||||||
|
|
||||||
|
|
||||||
BufferQuad(
|
|
||||||
|
//=============//
|
||||||
|
// constructor //
|
||||||
|
//=============//
|
||||||
|
//region
|
||||||
|
|
||||||
|
public BufferQuad(
|
||||||
short x, short y, short z, short widthEastWest, short widthNorthSouthOrHeight,
|
short x, short y, short z, short widthEastWest, short widthNorthSouthOrHeight,
|
||||||
int color, byte irisBlockMaterialId, byte skylight, byte blockLight,
|
int color, byte irisBlockMaterialId, byte skylight, byte blockLight,
|
||||||
EDhDirection direction)
|
EDhDirection direction)
|
||||||
@@ -85,64 +95,46 @@ public final class BufferQuad
|
|||||||
this.skyLight = skylight;
|
this.skyLight = skylight;
|
||||||
this.blockLight = blockLight;
|
this.blockLight = blockLight;
|
||||||
this.direction = direction;
|
this.direction = direction;
|
||||||
|
this.sortKeyEastWest = computeSortKey(direction, true);
|
||||||
|
this.sortKeyNorthSouth = computeSortKey(direction, false);
|
||||||
}
|
}
|
||||||
|
private long computeSortKey(EDhDirection dir, boolean eastWest)
|
||||||
|
|
||||||
|
|
||||||
/** a rough but fast calculation */
|
|
||||||
double calculateDistance(double relativeX, double relativeY, double relativeZ)
|
|
||||||
{
|
{
|
||||||
return Math.pow(relativeX - this.x, 2) + Math.pow(relativeY - this.y, 2) + Math.pow(relativeZ - this.z, 2);
|
if (eastWest)
|
||||||
|
{
|
||||||
|
switch (dir.axis)
|
||||||
|
{
|
||||||
|
case X: return (long) x << 48 | (long) y << 32 | (long) z << 16;
|
||||||
|
case Y: return (long) y << 48 | (long) z << 32 | (long) x << 16;
|
||||||
|
case Z: return (long) z << 48 | (long) y << 32 | (long) x << 16;
|
||||||
|
default: throw new IllegalArgumentException("Invalid Axis enum: [" + dir.axis + "].");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (dir.axis)
|
||||||
|
{
|
||||||
|
case X: return (long) x << 48 | (long) z << 32 | (long) y << 16;
|
||||||
|
case Y: return (long) y << 48 | (long) x << 32 | (long) z << 16;
|
||||||
|
case Z: return (long) z << 48 | (long) x << 32 | (long) y << 16;
|
||||||
|
default: throw new IllegalArgumentException("Invalid Axis enum: [" + dir.axis + "].");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** compares this quad's position to the given quad */
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** compares this quad's position to the given quad using pre-computed sort keys */
|
||||||
public int compare(BufferQuad quad, BufferMergeDirectionEnum compareDirection)
|
public int compare(BufferQuad quad, BufferMergeDirectionEnum compareDirection)
|
||||||
{
|
{
|
||||||
if (this.direction != quad.direction)
|
if (this.direction != quad.direction)
|
||||||
throw new IllegalArgumentException("The other quad is not in the same direction: " + quad.direction + " vs " + this.direction);
|
throw new IllegalArgumentException("The other quad is not in the same direction: " + quad.direction + " vs " + this.direction);
|
||||||
|
|
||||||
if (compareDirection == BufferMergeDirectionEnum.EastWest)
|
return compareDirection == BufferMergeDirectionEnum.EastWest
|
||||||
{
|
? Long.compare(this.sortKeyEastWest, quad.sortKeyEastWest)
|
||||||
switch (this.direction.axis)
|
: Long.compare(this.sortKeyNorthSouth, quad.sortKeyNorthSouth);
|
||||||
{
|
|
||||||
case X:
|
|
||||||
return threeDimensionalCompare(this.x, this.y, this.z, quad.x, quad.y, quad.z);
|
|
||||||
case Y:
|
|
||||||
return threeDimensionalCompare(this.y, this.z, this.x, quad.y, quad.z, quad.x);
|
|
||||||
case Z:
|
|
||||||
return threeDimensionalCompare(this.z, this.y, this.x, quad.z, quad.y, quad.x);
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Invalid Axis enum: [" + this.direction.axis + "].");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
switch (this.direction.axis)
|
|
||||||
{
|
|
||||||
case X:
|
|
||||||
return threeDimensionalCompare(this.x, this.z, this.y, quad.x, quad.z, quad.y);
|
|
||||||
case Y:
|
|
||||||
return threeDimensionalCompare(this.y, this.x, this.z, quad.y, quad.x, quad.z);
|
|
||||||
case Z:
|
|
||||||
return threeDimensionalCompare(this.z, this.x, this.y, quad.z, quad.x, quad.y);
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Invalid Axis enum: [" + this.direction.axis + "].");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Compares two 3D points A and B. <br>
|
|
||||||
* The X, Y, and Z coordinates can be passed into parameters 0, 1, and 2 in any order
|
|
||||||
* provided they are in the same order for both A and B. <br>
|
|
||||||
* With the 0th parameter being the most significant when comparing.
|
|
||||||
*/
|
|
||||||
private static int threeDimensionalCompare(short a0, short a1, short a2, short b0, short b1, short b2)
|
|
||||||
{
|
|
||||||
long a = (long) a0 << 48 | (long) a1 << 32 | (long) a2 << 16;
|
|
||||||
long b = (long) b0 << 48 | (long) b1 << 32 | (long) b2 << 16;
|
|
||||||
return Long.compare(a, b);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -154,11 +146,15 @@ public final class BufferQuad
|
|||||||
public boolean tryMerge(BufferQuad quad, BufferMergeDirectionEnum mergeDirection)
|
public boolean tryMerge(BufferQuad quad, BufferMergeDirectionEnum mergeDirection)
|
||||||
{
|
{
|
||||||
if (quad.hasError || this.hasError)
|
if (quad.hasError || this.hasError)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// only merge quads that are in the same direction
|
// only merge quads that are in the same direction
|
||||||
if (this.direction != quad.direction)
|
if (this.direction != quad.direction)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// make sure these quads share the same perpendicular axis
|
// make sure these quads share the same perpendicular axis
|
||||||
if ((mergeDirection == BufferMergeDirectionEnum.EastWest && this.y != quad.y)
|
if ((mergeDirection == BufferMergeDirectionEnum.EastWest && this.y != quad.y)
|
||||||
@@ -175,7 +171,6 @@ public final class BufferQuad
|
|||||||
short otherParallelCompareStartPos;
|
short otherParallelCompareStartPos;
|
||||||
switch (this.direction.axis)
|
switch (this.direction.axis)
|
||||||
{
|
{
|
||||||
default: // shouldn't normally happen, just here to make the compiler happy
|
|
||||||
case X:
|
case X:
|
||||||
if (mergeDirection == BufferMergeDirectionEnum.EastWest)
|
if (mergeDirection == BufferMergeDirectionEnum.EastWest)
|
||||||
{
|
{
|
||||||
@@ -232,6 +227,9 @@ public final class BufferQuad
|
|||||||
otherParallelCompareStartPos = quad.z;
|
otherParallelCompareStartPos = quad.z;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
default: // shouldn't normally happen, just here to make the compiler happy
|
||||||
|
throw new IllegalArgumentException("Unsupported axis: ["+this.direction.axis+"]");
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the width of this quad in the relevant axis
|
// get the width of this quad in the relevant axis
|
||||||
@@ -333,4 +331,6 @@ public final class BufferQuad
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-14
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
|||||||
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
||||||
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
||||||
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListCheckout;
|
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListCheckout;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||||
import com.seibel.distanthorizons.coreapi.util.ColorUtil;
|
import com.seibel.distanthorizons.coreapi.util.ColorUtil;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||||
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
|
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
|
||||||
@@ -60,6 +61,12 @@ public class ColumnBox
|
|||||||
// variable setup //
|
// variable setup //
|
||||||
//================//
|
//================//
|
||||||
|
|
||||||
|
IClientLevelWrapper clientLevelWrapper = clientLevel.getClientLevelWrapper();
|
||||||
|
if (clientLevelWrapper == null)
|
||||||
|
{
|
||||||
|
LodUtil.assertNotReach("addBoxQuadsToBuilder getClientLevelWrapper should always succeed");
|
||||||
|
}
|
||||||
|
|
||||||
short maxX = (short) (minX + width);
|
short maxX = (short) (minX + width);
|
||||||
short maxY = (short) (minY + yHeight);
|
short maxY = (short) (minY + yHeight);
|
||||||
short maxZ = (short) (minZ + width);
|
short maxZ = (short) (minZ + width);
|
||||||
@@ -122,7 +129,7 @@ public class ColumnBox
|
|||||||
&& !isTopTransparent;
|
&& !isTopTransparent;
|
||||||
if (!skipTop)
|
if (!skipTop)
|
||||||
{
|
{
|
||||||
builder.addQuadUp(minX, maxY, minZ, width, ColorUtil.applyShade(color, MC_RENDER.getShade(EDhDirection.UP)), irisBlockMaterialId, skyLightTop, blockLight);
|
builder.addQuadUp(minX, maxY, minZ, width, ColorUtil.applyShade(color, clientLevelWrapper.getShade(EDhDirection.UP)), irisBlockMaterialId, skyLightTop, blockLight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +140,7 @@ public class ColumnBox
|
|||||||
&& !isBottomTransparent;
|
&& !isBottomTransparent;
|
||||||
if (!skipBottom)
|
if (!skipBottom)
|
||||||
{
|
{
|
||||||
builder.addQuadDown(minX, minY, minZ, width, ColorUtil.applyShade(color, MC_RENDER.getShade(EDhDirection.DOWN)), irisBlockMaterialId, skyLightBot, blockLight);
|
builder.addQuadDown(minX, minY, minZ, width, ColorUtil.applyShade(color, clientLevelWrapper.getShade(EDhDirection.DOWN)), irisBlockMaterialId, skyLightBot, blockLight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +170,7 @@ public class ColumnBox
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
makeAdjVerticalQuad(
|
makeAdjVerticalQuad(
|
||||||
builder, phantomArrayCheckout,
|
builder, phantomArrayCheckout, clientLevelWrapper,
|
||||||
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.NORTH,
|
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.NORTH,
|
||||||
minX, minY, minZ, width, yHeight,
|
minX, minY, minZ, width, yHeight,
|
||||||
color, irisBlockMaterialId, blockLight);
|
color, irisBlockMaterialId, blockLight);
|
||||||
@@ -188,7 +195,7 @@ public class ColumnBox
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
makeAdjVerticalQuad(
|
makeAdjVerticalQuad(
|
||||||
builder, phantomArrayCheckout,
|
builder, phantomArrayCheckout, clientLevelWrapper,
|
||||||
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.SOUTH,
|
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.SOUTH,
|
||||||
minX, minY, maxZ, width, yHeight,
|
minX, minY, maxZ, width, yHeight,
|
||||||
color, irisBlockMaterialId, blockLight);
|
color, irisBlockMaterialId, blockLight);
|
||||||
@@ -213,7 +220,7 @@ public class ColumnBox
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
makeAdjVerticalQuad(
|
makeAdjVerticalQuad(
|
||||||
builder, phantomArrayCheckout,
|
builder, phantomArrayCheckout, clientLevelWrapper,
|
||||||
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.WEST,
|
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.WEST,
|
||||||
minX, minY, minZ, width, yHeight,
|
minX, minY, minZ, width, yHeight,
|
||||||
color, irisBlockMaterialId, blockLight);
|
color, irisBlockMaterialId, blockLight);
|
||||||
@@ -238,7 +245,7 @@ public class ColumnBox
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
makeAdjVerticalQuad(
|
makeAdjVerticalQuad(
|
||||||
builder, phantomArrayCheckout,
|
builder, phantomArrayCheckout, clientLevelWrapper,
|
||||||
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.EAST,
|
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.EAST,
|
||||||
maxX, minY, minZ, width, yHeight,
|
maxX, minY, minZ, width, yHeight,
|
||||||
color, irisBlockMaterialId, blockLight);
|
color, irisBlockMaterialId, blockLight);
|
||||||
@@ -247,7 +254,7 @@ public class ColumnBox
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void makeAdjVerticalQuad(
|
private static void makeAdjVerticalQuad(
|
||||||
LodQuadBuilder builder, PhantomArrayListCheckout phantomArrayCheckout,
|
LodQuadBuilder builder, PhantomArrayListCheckout phantomArrayCheckout, IClientLevelWrapper clientLevelWrapper,
|
||||||
@NotNull ColumnRenderView adjColumnView, boolean adjacentIsSameDetailLevel, int caveCullingMaxY, EDhDirection direction,
|
@NotNull ColumnRenderView adjColumnView, boolean adjacentIsSameDetailLevel, int caveCullingMaxY, EDhDirection direction,
|
||||||
short x, short yMin, short z, short horizontalWidth, short ySize,
|
short x, short yMin, short z, short horizontalWidth, short ySize,
|
||||||
int color, byte irisBlockMaterialId, byte blockLight)
|
int color, byte irisBlockMaterialId, byte blockLight)
|
||||||
@@ -263,7 +270,7 @@ public class ColumnBox
|
|||||||
// no adjacent data //
|
// no adjacent data //
|
||||||
//==================//
|
//==================//
|
||||||
|
|
||||||
color = ColorUtil.applyShade(color, MC_RENDER.getShade(direction));
|
color = ColorUtil.applyShade(color, clientLevelWrapper.getShade(direction));
|
||||||
|
|
||||||
if (adjColumnView.size == 0
|
if (adjColumnView.size == 0
|
||||||
|| RenderDataPointUtil.hasZeroHeight(adjColumnView.get(0)))
|
|| RenderDataPointUtil.hasZeroHeight(adjColumnView.get(0)))
|
||||||
@@ -341,13 +348,24 @@ public class ColumnBox
|
|||||||
|
|
||||||
|
|
||||||
// Apply light to the range [adjMinY, adjMaxY)
|
// Apply light to the range [adjMinY, adjMaxY)
|
||||||
applyLightToRange(segments, newSegments, adjMinY, adjMaxY, lightToApply);
|
applyLightToRangeAndPopulateNewSgements(segments, newSegments, adjMinY, adjMaxY, lightToApply);
|
||||||
|
{
|
||||||
|
// swap references so we can use the newly populated segments
|
||||||
|
LongArrayList temp = segments;
|
||||||
|
segments = newSegments;
|
||||||
|
newSegments = temp;
|
||||||
|
}
|
||||||
|
|
||||||
// Fill overhang area [adjMaxY, adjAboveMinY) with adjSkyLight
|
// Fill overhang area [adjMaxY, adjAboveMinY) with adjSkyLight
|
||||||
short adjAboveMinY = RenderDataPointUtil.getYMin(adjAbovePoint);
|
short adjAboveMinY = RenderDataPointUtil.getYMin(adjAbovePoint);
|
||||||
if (adjMaxY < adjAboveMinY)
|
if (adjMaxY < adjAboveMinY)
|
||||||
{
|
{
|
||||||
applyLightToRange(segments, newSegments, adjMaxY, adjAboveMinY, adjSkyLight);
|
applyLightToRangeAndPopulateNewSgements(segments, newSegments, adjMaxY, adjAboveMinY, adjSkyLight);
|
||||||
|
{
|
||||||
|
LongArrayList temp = segments;
|
||||||
|
segments = newSegments;
|
||||||
|
newSegments = temp;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,10 +391,11 @@ public class ColumnBox
|
|||||||
/**
|
/**
|
||||||
* Apply the new light value over the given y range,
|
* Apply the new light value over the given y range,
|
||||||
* splitting segments as needed
|
* splitting segments as needed
|
||||||
|
* and putting the new segments into "newSegments"
|
||||||
* <p>
|
* <p>
|
||||||
* source: claude.ai
|
* source: claude.ai
|
||||||
*/
|
*/
|
||||||
private static void applyLightToRange(
|
private static void applyLightToRangeAndPopulateNewSgements(
|
||||||
LongArrayList segments, LongArrayList newSegments,
|
LongArrayList segments, LongArrayList newSegments,
|
||||||
short rangeStart, short rangeEnd,
|
short rangeStart, short rangeEnd,
|
||||||
byte newLight)
|
byte newLight)
|
||||||
@@ -419,9 +438,6 @@ public class ColumnBox
|
|||||||
newSegments.add(YSegmentUtil.encode(rangeEnd, endY, skyLight));
|
newSegments.add(YSegmentUtil.encode(rangeEnd, endY, skyLight));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
segments.clear();
|
|
||||||
segments.addAll(newSegments);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void tryAddVerticalFaceWithSkyLightToBuilder(
|
private static void tryAddVerticalFaceWithSkyLightToBuilder(
|
||||||
|
|||||||
-20
@@ -55,26 +55,6 @@ public class ColumnRenderBufferBuilder
|
|||||||
// vbo building //
|
// vbo building //
|
||||||
//==============//
|
//==============//
|
||||||
|
|
||||||
/** @link adjData should be null for adjacent sections that cross detail level boundaries */
|
|
||||||
public static CompletableFuture<LodBufferContainer> uploadBuffersAsync(
|
|
||||||
IDhClientLevel clientLevel,
|
|
||||||
long pos,
|
|
||||||
LodQuadBuilder quadBuilder
|
|
||||||
)
|
|
||||||
{
|
|
||||||
DhBlockPos minBlockPos = new DhBlockPos(DhSectionPos.getMinCornerBlockX(pos), clientLevel.getLevelWrapper().getMinHeight(), DhSectionPos.getMinCornerBlockZ(pos));
|
|
||||||
LodBufferContainer bufferContainer = new LodBufferContainer(pos, minBlockPos);
|
|
||||||
CompletableFuture<LodBufferContainer> uploadFuture = bufferContainer.tryMakeAndUploadBuffersAsync(quadBuilder);
|
|
||||||
uploadFuture.whenComplete((uploadedBuffer, exception) ->
|
|
||||||
{
|
|
||||||
// clean up if not uploaded
|
|
||||||
if (uploadedBuffer != null && !uploadedBuffer.buffersUploaded)
|
|
||||||
{
|
|
||||||
uploadedBuffer.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return uploadFuture;
|
|
||||||
}
|
|
||||||
public static void makeLodRenderData(
|
public static void makeLodRenderData(
|
||||||
LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, IDhClientLevel clientLevel,
|
LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, IDhClientLevel clientLevel,
|
||||||
ColumnRenderSource[] adjRegions, boolean[] isSameDetailLevel)
|
ColumnRenderSource[] adjRegions, boolean[] isSameDetailLevel)
|
||||||
|
|||||||
+119
-167
@@ -20,13 +20,13 @@
|
|||||||
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
|
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
|
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
|
||||||
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
|
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
|
||||||
import com.seibel.distanthorizons.core.util.ExceptionUtil;
|
import com.seibel.distanthorizons.core.util.ExceptionUtil;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.render.AbstractDhRenderApiDefinition;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.render.AbstractDhRenderApiDefinition;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.ILodContainerUniformBufferWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.ILodContainerUniformBufferWrapper;
|
||||||
@@ -37,7 +37,6 @@ import org.lwjgl.system.MemoryUtil;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Java representation of one or more OpenGL buffers for rendering.
|
* Java representation of one or more OpenGL buffers for rendering.
|
||||||
@@ -63,8 +62,6 @@ public class LodBufferContainer implements AutoCloseable
|
|||||||
|
|
||||||
public ILodContainerUniformBufferWrapper uniformContainer = WRAPPER_FACTORY.createLodContainerUniformWrapper();
|
public ILodContainerUniformBufferWrapper uniformContainer = WRAPPER_FACTORY.createLodContainerUniformWrapper();
|
||||||
|
|
||||||
private final AtomicReference<CompletableFuture<LodBufferContainer>> uploadFutureRef = new AtomicReference<>(null);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//==============//
|
//==============//
|
||||||
@@ -72,7 +69,7 @@ public class LodBufferContainer implements AutoCloseable
|
|||||||
//==============//
|
//==============//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
public LodBufferContainer(long pos, DhBlockPos minCornerBlockPos)
|
private LodBufferContainer(long pos, DhBlockPos minCornerBlockPos)
|
||||||
{
|
{
|
||||||
this.pos = pos;
|
this.pos = pos;
|
||||||
this.minCornerBlockPos = minCornerBlockPos;
|
this.minCornerBlockPos = minCornerBlockPos;
|
||||||
@@ -92,41 +89,12 @@ public class LodBufferContainer implements AutoCloseable
|
|||||||
//region
|
//region
|
||||||
|
|
||||||
/** Should be run on a DH thread. */
|
/** Should be run on a DH thread. */
|
||||||
public synchronized CompletableFuture<LodBufferContainer> tryMakeAndUploadBuffersAsync(LodQuadBuilder builder)
|
public static CompletableFuture<LodBufferContainer> tryMakeAndUploadBuffersAsync(
|
||||||
|
long pos, IDhClientLevel clientLevel,
|
||||||
|
LodQuadBuilder builder)
|
||||||
{
|
{
|
||||||
//================//
|
|
||||||
// handle futures //
|
|
||||||
//================//
|
|
||||||
//region
|
|
||||||
|
|
||||||
// separate variable to prevent race condition when checking null
|
|
||||||
CompletableFuture<LodBufferContainer> oldFuture = this.uploadFutureRef.get();
|
|
||||||
if (oldFuture != null)
|
|
||||||
{
|
|
||||||
// upload already in process
|
|
||||||
return oldFuture;
|
|
||||||
}
|
|
||||||
|
|
||||||
// new upload needed
|
// new upload needed
|
||||||
CompletableFuture<LodBufferContainer> future = new CompletableFuture<>();
|
CompletableFuture<LodBufferContainer> future = new CompletableFuture<>();
|
||||||
future.handle((lodBufferContainer, throwable) ->
|
|
||||||
{
|
|
||||||
if (!this.uploadFutureRef.compareAndSet(future, null))
|
|
||||||
{
|
|
||||||
LOGGER.warn("upload future ref changed for pos ["+DhSectionPos.toString(this.pos)+"].");
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!this.uploadFutureRef.compareAndSet(null, future))
|
|
||||||
{
|
|
||||||
oldFuture = this.uploadFutureRef.get();
|
|
||||||
LodUtil.assertTrue(oldFuture != null, "Concurrency error");
|
|
||||||
return oldFuture;
|
|
||||||
}
|
|
||||||
|
|
||||||
//endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -135,91 +103,119 @@ public class LodBufferContainer implements AutoCloseable
|
|||||||
//================//
|
//================//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
|
DhBlockPos minCornerBlockPos = new DhBlockPos(
|
||||||
|
DhSectionPos.getMinCornerBlockX(pos),
|
||||||
|
clientLevel.getLevelWrapper().getMinHeight(),
|
||||||
|
DhSectionPos.getMinCornerBlockZ(pos));
|
||||||
|
LodBufferContainer bufferContainer = new LodBufferContainer(pos, minCornerBlockPos);
|
||||||
|
|
||||||
|
// create CPU vertex buffers
|
||||||
ArrayList<ByteBuffer> opaqueBuffers = builder.makeOpaqueVertexBuffers();
|
ArrayList<ByteBuffer> opaqueBuffers = builder.makeOpaqueVertexBuffers();
|
||||||
ArrayList<ByteBuffer> transparentBuffers = builder.makeTransparentVertexBuffers();
|
ArrayList<ByteBuffer> transparentBuffers = builder.makeTransparentVertexBuffers();
|
||||||
|
|
||||||
this.vboOpaqueWrappers = resizeWrapperArray(this.vboOpaqueWrappers, opaqueBuffers.size());
|
// update arrays to contain buffers
|
||||||
this.vboTransparentWrappers = resizeWrapperArray(this.vboTransparentWrappers, transparentBuffers.size());
|
bufferContainer.vboOpaqueWrappers = resizeWrapperArray(bufferContainer.vboOpaqueWrappers, opaqueBuffers.size());
|
||||||
|
bufferContainer.vboTransparentWrappers = resizeWrapperArray(bufferContainer.vboTransparentWrappers, transparentBuffers.size());
|
||||||
|
|
||||||
// mac requires separate IBO objects for each VBO when using OpenGL,
|
// create CPU index buffers if needed.
|
||||||
|
// Mac requires separate IBO objects for each VBO when using OpenGL,
|
||||||
// all other OS's can share a single IBO for quicker loading times
|
// all other OS's can share a single IBO for quicker loading times
|
||||||
boolean useSingleIbo = RENDER_DEF.useSingleIbo();
|
boolean useSingleIbo = RENDER_DEF.useSingleIbo();
|
||||||
@Nullable ArrayList<ByteBuffer> opaqueIndexBuffers = useSingleIbo ? null : this.createIndexBuffers(opaqueBuffers);
|
@Nullable ArrayList<ByteBuffer> opaqueIndexBuffers = useSingleIbo ? null : bufferContainer.createIndexBuffers(opaqueBuffers);
|
||||||
@Nullable ArrayList<ByteBuffer> transparentIndexBuffers = useSingleIbo ? null : this.createIndexBuffers(transparentBuffers);
|
@Nullable ArrayList<ByteBuffer> transparentIndexBuffers = useSingleIbo ? null : bufferContainer.createIndexBuffers(transparentBuffers);
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//================//
|
//=============//
|
||||||
// upload buffers //
|
// create VBOs //
|
||||||
//================//
|
//=============//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
try
|
CompletableFuture<Void> createFuture = new CompletableFuture<Void>();
|
||||||
|
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer Setup", () ->
|
||||||
{
|
{
|
||||||
//=============//
|
try
|
||||||
// create VBOs //
|
|
||||||
//=============//
|
|
||||||
|
|
||||||
CompletableFuture<Void> createOpaqueFuture = createBufferWrappersAsync(future, this.vboOpaqueWrappers, opaqueBuffers);
|
|
||||||
CompletableFuture<Void> createTransparentFuture = createBufferWrappersAsync(future, this.vboTransparentWrappers, transparentBuffers);
|
|
||||||
|
|
||||||
CompletableFuture<Void> createFuture = CompletableFuture.allOf(createOpaqueFuture, createTransparentFuture);
|
|
||||||
createFuture.exceptionally((Throwable e) ->
|
|
||||||
{
|
{
|
||||||
// create VBOs failed //
|
// skip this event if requested
|
||||||
|
if (Thread.interrupted()
|
||||||
|
|| future.isCancelled())
|
||||||
|
{
|
||||||
|
throw new InterruptedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
createBufferWrappers(bufferContainer.vboOpaqueWrappers, opaqueBuffers);
|
||||||
|
createBufferWrappers(bufferContainer.vboTransparentWrappers, transparentBuffers);
|
||||||
|
|
||||||
|
createFuture.complete(null);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
if (!ExceptionUtil.isShutdownException(e))
|
if (!ExceptionUtil.isShutdownException(e))
|
||||||
{
|
{
|
||||||
LOGGER.error("Unexpected issue creating buffer [" + this.minCornerBlockPos + "], error: [" + e.getMessage() + "].", e);
|
LOGGER.error("Unexpected issue creating buffers for pos: ["+DhSectionPos.toString(bufferContainer.pos)+"], error: ["+e.getMessage()+"].", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bufferContainer.close();
|
||||||
|
createFuture.completeExceptionally(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//====================//
|
||||||
|
// upload VBOs to GPU //
|
||||||
|
//====================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
createFuture.exceptionally((Throwable e) ->
|
||||||
|
{
|
||||||
|
// create VBOs failed //
|
||||||
|
if (!ExceptionUtil.isShutdownException(e))
|
||||||
|
{
|
||||||
|
LOGGER.error("Unexpected issue creating buffer [" + bufferContainer.minCornerBlockPos + "], error: [" + e.getMessage() + "].", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferContainer.close();
|
||||||
|
future.completeExceptionally(e);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
createFuture.thenRun(() ->
|
||||||
|
{
|
||||||
|
CompletableFuture<Void> opaqueFuture = uploadBuffersAsync(future, bufferContainer.vboOpaqueWrappers, opaqueBuffers, opaqueIndexBuffers);
|
||||||
|
CompletableFuture<Void> transparentFuture = uploadBuffersAsync(future, bufferContainer.vboTransparentWrappers, transparentBuffers, transparentIndexBuffers);
|
||||||
|
CompletableFuture<Void> uploadFuture = CompletableFuture.allOf(opaqueFuture, transparentFuture);
|
||||||
|
uploadFuture.exceptionally((Throwable e) ->
|
||||||
|
{
|
||||||
|
// upload failed //
|
||||||
|
if (!ExceptionUtil.isShutdownException(e))
|
||||||
|
{
|
||||||
|
LOGGER.error("Unexpected issue uploading buffer [" + bufferContainer.minCornerBlockPos + "], error: [" + e.getMessage() + "].", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferContainer.close();
|
||||||
future.completeExceptionally(e);
|
future.completeExceptionally(e);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
createFuture.thenRun(() ->
|
uploadFuture.thenRun(() ->
|
||||||
{
|
{
|
||||||
//=============//
|
// upload success //
|
||||||
// upload VBOs //
|
bufferContainer.buffersUploaded = true;
|
||||||
//=============//
|
future.complete(bufferContainer);
|
||||||
|
|
||||||
CompletableFuture<Void> opaqueFuture = uploadBuffersAsync(future, this.vboOpaqueWrappers, opaqueBuffers, opaqueIndexBuffers);
|
|
||||||
CompletableFuture<Void> transparentFuture = uploadBuffersAsync(future, this.vboTransparentWrappers, transparentBuffers, transparentIndexBuffers);
|
|
||||||
|
|
||||||
CompletableFuture<Void> uploadFuture = CompletableFuture.allOf(opaqueFuture, transparentFuture);
|
|
||||||
uploadFuture.exceptionally((Throwable e) ->
|
|
||||||
{
|
|
||||||
// upload failed //
|
|
||||||
|
|
||||||
if (!ExceptionUtil.isShutdownException(e))
|
|
||||||
{
|
|
||||||
LOGGER.error("Unexpected issue uploading buffer [" + this.minCornerBlockPos + "], error: [" + e.getMessage() + "].", e);
|
|
||||||
}
|
|
||||||
future.completeExceptionally(e);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
uploadFuture.thenRun(() ->
|
|
||||||
{
|
|
||||||
// upload success /
|
|
||||||
|
|
||||||
this.buffersUploaded = true;
|
|
||||||
future.complete(this);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
catch (Exception e)
|
|
||||||
{
|
//endregion
|
||||||
if (!ExceptionUtil.isShutdownException(e))
|
|
||||||
{
|
|
||||||
LOGGER.error("Unexpected issue prepping buffer uploading [" + this.minCornerBlockPos + "], error: [" + e.getMessage() + "].", e);
|
|
||||||
}
|
|
||||||
future.completeExceptionally(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//================//
|
|
||||||
// buffer cleanup //
|
//====================//
|
||||||
//================//
|
// CPU Buffer cleanup //
|
||||||
|
//====================//
|
||||||
|
//region
|
||||||
|
|
||||||
future.whenComplete((LodBufferContainer lodBufferContainer, Throwable throwable) ->
|
future.whenComplete((LodBufferContainer lodBufferContainer, Throwable throwable) ->
|
||||||
{
|
{
|
||||||
@@ -290,11 +286,8 @@ public class LodBufferContainer implements AutoCloseable
|
|||||||
return newVbos;
|
return newVbos;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CompletableFuture<Void> createBufferWrappersAsync(
|
private static void createBufferWrappers(IVertexBufferWrapper[] vboWrappers, ArrayList<ByteBuffer> vertexBuffers)
|
||||||
CompletableFuture<LodBufferContainer> parentFuture,
|
|
||||||
IVertexBufferWrapper[] vboWrappers, ArrayList<ByteBuffer> vertexBuffers)
|
|
||||||
{
|
{
|
||||||
ArrayList<CompletableFuture<Void>> createVboFutureList = new ArrayList<>();
|
|
||||||
for (int i = 0; i < vertexBuffers.size(); i++)
|
for (int i = 0; i < vertexBuffers.size(); i++)
|
||||||
{
|
{
|
||||||
if (i >= vboWrappers.length)
|
if (i >= vboWrappers.length)
|
||||||
@@ -304,45 +297,9 @@ public class LodBufferContainer implements AutoCloseable
|
|||||||
|
|
||||||
if (vboWrappers[i] == null)
|
if (vboWrappers[i] == null)
|
||||||
{
|
{
|
||||||
final int finalVboIndex = i;
|
vboWrappers[i] = WRAPPER_FACTORY.createVboWrapper("distantHorizons:McLodRenderer");
|
||||||
|
|
||||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
|
||||||
createVboFutureList.add(future);
|
|
||||||
|
|
||||||
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer Setup", () ->
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// skip this event if requested
|
|
||||||
if (Thread.interrupted()
|
|
||||||
|| parentFuture.isCancelled())
|
|
||||||
{
|
|
||||||
throw new InterruptedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
vboWrappers[finalVboIndex] = WRAPPER_FACTORY.createVboWrapper("distantHorizons:McLodRenderer");
|
|
||||||
future.complete(null);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
future.completeExceptionally(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (createVboFutureList.size() == 0)
|
|
||||||
{
|
|
||||||
return CompletableFuture.completedFuture(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
CompletableFuture<?>[] futureArray = new CompletableFuture[createVboFutureList.size()];
|
|
||||||
for (int i = 0; i < createVboFutureList.size(); i++)
|
|
||||||
{
|
|
||||||
futureArray[i] = createVboFutureList.get(i);
|
|
||||||
}
|
|
||||||
return CompletableFuture.allOf(futureArray);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Index buffers should be null if {@link AbstractDhRenderApiDefinition#useSingleIbo()} returns true. */
|
/** Index buffers should be null if {@link AbstractDhRenderApiDefinition#useSingleIbo()} returns true. */
|
||||||
@@ -365,8 +322,6 @@ public class LodBufferContainer implements AutoCloseable
|
|||||||
|
|
||||||
// final variables for use in lambdas //
|
// final variables for use in lambdas //
|
||||||
|
|
||||||
final int finalVboIndex = vboIndex;
|
|
||||||
|
|
||||||
final IVertexBufferWrapper finalVboWrapper = vboWrappers[vboIndex];
|
final IVertexBufferWrapper finalVboWrapper = vboWrappers[vboIndex];
|
||||||
|
|
||||||
final ByteBuffer finalVertexBuffer = vertexBuffers.get(vboIndex);
|
final ByteBuffer finalVertexBuffer = vertexBuffers.get(vboIndex);
|
||||||
@@ -385,6 +340,8 @@ public class LodBufferContainer implements AutoCloseable
|
|||||||
CompletableFuture<Void> vertexUploadFuture = new CompletableFuture<>();
|
CompletableFuture<Void> vertexUploadFuture = new CompletableFuture<>();
|
||||||
uploadFutureList.add(vertexUploadFuture);
|
uploadFutureList.add(vertexUploadFuture);
|
||||||
|
|
||||||
|
|
||||||
|
final StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
|
||||||
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer VBO Upload", () ->
|
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer VBO Upload", () ->
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -396,21 +353,12 @@ public class LodBufferContainer implements AutoCloseable
|
|||||||
throw new InterruptedException();
|
throw new InterruptedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finalVboWrapper.uploadVertexBuffer(finalVertexBuffer, finalVertexCount);
|
||||||
try
|
vertexUploadFuture.complete(null);
|
||||||
{
|
|
||||||
finalVboWrapper.uploadVertexBuffer(finalVertexBuffer, finalVertexCount);
|
|
||||||
vertexUploadFuture.complete(null);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
vboWrappers[finalVboIndex] = null;
|
|
||||||
finalVboWrapper.close();
|
|
||||||
LOGGER.error("Failed to upload buffer. Error: [" + e.getMessage() + "].", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
LOGGER.error("Failed to upload buffer. Error: [" + e.getMessage() + "].", e);
|
||||||
vertexUploadFuture.completeExceptionally(e);
|
vertexUploadFuture.completeExceptionally(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -445,6 +393,7 @@ public class LodBufferContainer implements AutoCloseable
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
finalVboWrapper.close();
|
||||||
indexUploadFuture.completeExceptionally(e);
|
indexUploadFuture.completeExceptionally(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -532,26 +481,29 @@ public class LodBufferContainer implements AutoCloseable
|
|||||||
|
|
||||||
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer Close", () ->
|
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer Close", () ->
|
||||||
{
|
{
|
||||||
for (IVertexBufferWrapper buffer : this.vboOpaqueWrappers)
|
tryCloseBufferWrapperArray(this.vboOpaqueWrappers);
|
||||||
{
|
tryCloseBufferWrapperArray(this.vboTransparentWrappers);
|
||||||
if (buffer != null)
|
|
||||||
{
|
|
||||||
buffer.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (IVertexBufferWrapper buffer : this.vboTransparentWrappers)
|
|
||||||
{
|
|
||||||
if (buffer != null)
|
|
||||||
{
|
|
||||||
buffer.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.uniformContainer.close();
|
this.uniformContainer.close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void tryCloseBufferWrapperArray(@Nullable IVertexBufferWrapper[] bufferWrappers)
|
||||||
|
{
|
||||||
|
if (bufferWrappers != null)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < bufferWrappers.length; i++)
|
||||||
|
{
|
||||||
|
IVertexBufferWrapper buffer = bufferWrappers[i];
|
||||||
|
bufferWrappers[i] = null;
|
||||||
|
if (buffer != null)
|
||||||
|
{
|
||||||
|
buffer.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -61,7 +61,7 @@ public class LodQuadBuilder
|
|||||||
public static final int BYTES_PER_QUAD = BYTES_PER_VERTEX * 4;
|
public static final int BYTES_PER_QUAD = BYTES_PER_VERTEX * 4;
|
||||||
|
|
||||||
public static final int[][][] DIRECTION_VERTEX_IBO_QUAD = new int[][][]
|
public static final int[][][] DIRECTION_VERTEX_IBO_QUAD = new int[][][]
|
||||||
///region
|
//region
|
||||||
{
|
{
|
||||||
// X,Z //
|
// X,Z //
|
||||||
{ // UP
|
{ // UP
|
||||||
@@ -109,7 +109,7 @@ public class LodQuadBuilder
|
|||||||
{0, 0}, // 3
|
{0, 0}, // 3
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
///endregion
|
//endregion
|
||||||
|
|
||||||
private int premergeCount = 0;
|
private int premergeCount = 0;
|
||||||
|
|
||||||
@@ -394,7 +394,7 @@ public class LodQuadBuilder
|
|||||||
// for horizontal and bottom faces of grass blocks, use the dirt color to
|
// for horizontal and bottom faces of grass blocks, use the dirt color to
|
||||||
// prevent green cliff walls
|
// prevent green cliff walls
|
||||||
color = this.clientLevelWrapper.getDirtBlockColor();
|
color = this.clientLevelWrapper.getDirtBlockColor();
|
||||||
color = ColorUtil.applyShade(color, MC_RENDER.getShade(quad.direction));
|
color = ColorUtil.applyShade(color, this.clientLevelWrapper.getShade(quad.direction));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-8
@@ -129,6 +129,7 @@ public class FullDataToRenderDataTransformer
|
|||||||
ColumnRenderView tempExpandingColumnView = ColumnRenderView.getPooled();
|
ColumnRenderView tempExpandingColumnView = ColumnRenderView.getPooled();
|
||||||
RenderDataPointReducingList reducingList = new RenderDataPointReducingList())
|
RenderDataPointReducingList reducingList = new RenderDataPointReducingList())
|
||||||
{
|
{
|
||||||
|
DhBlockPosMutable mutableBlockPos = new DhBlockPosMutable();
|
||||||
for (int x = 0; x < FullDataSourceV2.WIDTH; x++)
|
for (int x = 0; x < FullDataSourceV2.WIDTH; x++)
|
||||||
{
|
{
|
||||||
for (int z = 0; z < FullDataSourceV2.WIDTH; z++)
|
for (int z = 0; z < FullDataSourceV2.WIDTH; z++)
|
||||||
@@ -142,7 +143,7 @@ public class FullDataToRenderDataTransformer
|
|||||||
baseX + BitShiftUtil.pow(x, dataDetail), baseZ + BitShiftUtil.pow(z, dataDetail),
|
baseX + BitShiftUtil.pow(x, dataDetail), baseZ + BitShiftUtil.pow(z, dataDetail),
|
||||||
columnArrayView, dataColumn,
|
columnArrayView, dataColumn,
|
||||||
// pooled references so we don't need to re-allocate/get them 4000 times per render source
|
// pooled references so we don't need to re-allocate/get them 4000 times per render source
|
||||||
phantomCheckout, tempExpandingColumnView, reducingList);
|
phantomCheckout, tempExpandingColumnView, reducingList, mutableBlockPos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,7 +158,7 @@ public class FullDataToRenderDataTransformer
|
|||||||
ColumnRenderView columnArrayView,
|
ColumnRenderView columnArrayView,
|
||||||
LongArrayList fullDataColumn,
|
LongArrayList fullDataColumn,
|
||||||
// pooled references
|
// pooled references
|
||||||
PhantomArrayListCheckout phantomCheckout, ColumnRenderView tempExpandingColumnView, RenderDataPointReducingList reducingList)
|
PhantomArrayListCheckout phantomCheckout, ColumnRenderView tempExpandingColumnView, RenderDataPointReducingList reducingList, DhBlockPosMutable mutableBlockPos)
|
||||||
{
|
{
|
||||||
// we can't do anything if the full data is missing or empty
|
// we can't do anything if the full data is missing or empty
|
||||||
if (fullDataColumn == null
|
if (fullDataColumn == null
|
||||||
@@ -170,7 +171,7 @@ public class FullDataToRenderDataTransformer
|
|||||||
if (fullDataLength <= columnArrayView.maxVerticalSliceCount)
|
if (fullDataLength <= columnArrayView.maxVerticalSliceCount)
|
||||||
{
|
{
|
||||||
// Directly use the arrayView since it fits.
|
// Directly use the arrayView since it fits.
|
||||||
setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, columnArrayView, fullDataColumn);
|
setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, columnArrayView, fullDataColumn, mutableBlockPos);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -178,7 +179,7 @@ public class FullDataToRenderDataTransformer
|
|||||||
|
|
||||||
// expand the ColumnArrayView to fit the new larger max vertical size
|
// expand the ColumnArrayView to fit the new larger max vertical size
|
||||||
tempExpandingColumnView.populate(dataArrayList, fullDataLength, 0, fullDataLength);
|
tempExpandingColumnView.populate(dataArrayList, fullDataLength, 0, fullDataLength);
|
||||||
setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, tempExpandingColumnView, fullDataColumn);
|
setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, tempExpandingColumnView, fullDataColumn, mutableBlockPos);
|
||||||
|
|
||||||
columnArrayView.changeVerticalSizeFrom(tempExpandingColumnView, reducingList);
|
columnArrayView.changeVerticalSizeFrom(tempExpandingColumnView, reducingList);
|
||||||
}
|
}
|
||||||
@@ -186,7 +187,7 @@ public class FullDataToRenderDataTransformer
|
|||||||
private static void setRenderColumnView(
|
private static void setRenderColumnView(
|
||||||
IClientLevelWrapper levelWrapper, FullDataSourceV2 fullDataSource,
|
IClientLevelWrapper levelWrapper, FullDataSourceV2 fullDataSource,
|
||||||
int blockX, int blockZ,
|
int blockX, int blockZ,
|
||||||
ColumnRenderView renderColumnData, LongArrayList fullColumnData)
|
ColumnRenderView renderColumnData, LongArrayList fullColumnData, DhBlockPosMutable mutableBlockPos)
|
||||||
{
|
{
|
||||||
//===============//
|
//===============//
|
||||||
// config values //
|
// config values //
|
||||||
@@ -242,7 +243,8 @@ public class FullDataToRenderDataTransformer
|
|||||||
|
|
||||||
FullDataPointIdMap fullDataMapping = fullDataSource.mapping;
|
FullDataPointIdMap fullDataMapping = fullDataSource.mapping;
|
||||||
|
|
||||||
DhBlockPosMutable mutableBlockPos = new DhBlockPosMutable(blockX, 0, blockZ);
|
mutableBlockPos.setX(blockX);
|
||||||
|
mutableBlockPos.setZ(blockZ);
|
||||||
|
|
||||||
// goes from the top down
|
// goes from the top down
|
||||||
for (int fullDataIndex = 0; fullDataIndex < fullColumnData.size(); fullDataIndex++)
|
for (int fullDataIndex = 0; fullDataIndex < fullColumnData.size(); fullDataIndex++)
|
||||||
@@ -450,8 +452,17 @@ public class FullDataToRenderDataTransformer
|
|||||||
// use the previous block's color
|
// use the previous block's color
|
||||||
color = colorToApplyToNextBlock;
|
color = colorToApplyToNextBlock;
|
||||||
colorToApplyToNextBlock = -1;
|
colorToApplyToNextBlock = -1;
|
||||||
skyLight = skylightToApplyToNextBlock;
|
|
||||||
blockLight = blocklightToApplyToNextBlock;
|
// use the skylight override if present
|
||||||
|
if (skylightToApplyToNextBlock != -1)
|
||||||
|
{
|
||||||
|
skyLight = skylightToApplyToNextBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blocklightToApplyToNextBlock != -1)
|
||||||
|
{
|
||||||
|
blockLight = blocklightToApplyToNextBlock;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+20
-14
@@ -91,10 +91,27 @@ public class LodRequestModule implements Closeable
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Initial wait is to prevent an issue
|
||||||
|
// where this starts before the child object's constructor finishes,
|
||||||
|
// causing null pointers on final non-null references.
|
||||||
|
// The try-catch in the while loop should also handle this
|
||||||
|
// but this way we shouldn't have error logs.
|
||||||
|
Thread.sleep(500);
|
||||||
|
|
||||||
|
// run until the threadpool is shut down
|
||||||
while (!Thread.interrupted())
|
while (!Thread.interrupted())
|
||||||
{
|
{
|
||||||
Thread.sleep(20);
|
try
|
||||||
this.tick();
|
{
|
||||||
|
Thread.sleep(20);
|
||||||
|
|
||||||
|
this.tick();
|
||||||
|
}
|
||||||
|
catch (InterruptedException e) { throw e; }
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LOGGER.error("Unexpected error in [" + LodRequestModule.class.getSimpleName() + "] tick loop, error: [" + e.getMessage() + "].", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (InterruptedException ignore) { }
|
catch (InterruptedException ignore) { }
|
||||||
@@ -104,18 +121,7 @@ public class LodRequestModule implements Closeable
|
|||||||
boolean shouldDoWorldGen = this.onWorldGenCompleteListener.shouldDoWorldGen();
|
boolean shouldDoWorldGen = this.onWorldGenCompleteListener.shouldDoWorldGen();
|
||||||
|
|
||||||
// if the world is read only don't generate anything
|
// if the world is read only don't generate anything
|
||||||
shouldDoWorldGen &= !DhApiWorldProxy.INSTANCE.getReadOnly();
|
shouldDoWorldGen &= !DhApiWorldProxy.INSTANCE.tryGetReadOnly();
|
||||||
|
|
||||||
// don't generate chunks for client levels that aren't being rendered
|
|
||||||
// (this can happen when moving between dimensions)
|
|
||||||
if (this.level instanceof IDhClientLevel)
|
|
||||||
{
|
|
||||||
boolean isRendering = ((IDhClientLevel) this.level).isRendering();
|
|
||||||
if (!isRendering)
|
|
||||||
{
|
|
||||||
shouldDoWorldGen = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -202,7 +202,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
private synchronized void tryQueueNewWorldGenRequestsAsync()
|
private synchronized void tryQueueNewWorldGenRequestsAsync()
|
||||||
{
|
{
|
||||||
if (!DhApiWorldProxy.INSTANCE.worldLoaded()
|
if (!DhApiWorldProxy.INSTANCE.worldLoaded()
|
||||||
|| DhApiWorldProxy.INSTANCE.getReadOnly())
|
|| DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import com.seibel.distanthorizons.core.multiplayer.server.FullDataSourceRequestH
|
|||||||
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerState;
|
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerState;
|
||||||
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager;
|
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager;
|
||||||
import com.seibel.distanthorizons.core.network.exceptions.RequestOutOfRangeException;
|
import com.seibel.distanthorizons.core.network.exceptions.RequestOutOfRangeException;
|
||||||
import com.seibel.distanthorizons.core.network.exceptions.RequestRejectedException;
|
|
||||||
import com.seibel.distanthorizons.core.network.exceptions.SectionRequiresSplittingException;
|
import com.seibel.distanthorizons.core.network.exceptions.SectionRequiresSplittingException;
|
||||||
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
|
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
|
||||||
import com.seibel.distanthorizons.core.network.messages.AbstractTrackableMessage;
|
import com.seibel.distanthorizons.core.network.messages.AbstractTrackableMessage;
|
||||||
@@ -200,26 +199,6 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
|
|||||||
|
|
||||||
LodUtil.assertTrue(message.getSession().serverPlayer != null);
|
LodUtil.assertTrue(message.getSession().serverPlayer != null);
|
||||||
|
|
||||||
// Check if the player is in this dimension,
|
|
||||||
// since handling multiple dimensions isn't allowed
|
|
||||||
if (message.getSession().serverPlayer.getLevel() != this.getLevelWrapper())
|
|
||||||
{
|
|
||||||
// If the message can be replied to - reply with an error, otherwise just ignore
|
|
||||||
if (message instanceof AbstractTrackableMessage)
|
|
||||||
{
|
|
||||||
((AbstractTrackableMessage) message).sendResponse(
|
|
||||||
new RequestRejectedException(
|
|
||||||
"Generation not allowed. " +
|
|
||||||
"Requested dimension: ["+((ILevelRelatedMessage) message).getLevelName()+"], " +
|
|
||||||
"player dimension: [" + message.getSession().serverPlayer.getLevel().getDhIdentifier() + "], " +
|
|
||||||
"handler dimension: [" + this.getLevelWrapper().getDhIdentifier() + "]"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,16 +21,21 @@ package com.seibel.distanthorizons.core.level;
|
|||||||
|
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||||
|
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.file.fullDatafile.IDataSourceUpdateListenerFunc;
|
import com.seibel.distanthorizons.core.file.fullDatafile.IDataSourceUpdateListenerFunc;
|
||||||
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
|
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
|
||||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||||
import com.seibel.distanthorizons.core.render.QuadTree.LodQuadTree;
|
import com.seibel.distanthorizons.core.render.QuadTree.LodQuadTree;
|
||||||
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
|
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||||
|
import com.seibel.distanthorizons.core.util.math.Vec3d;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IImmersivePortalsAccessor;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
@@ -44,6 +49,7 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
|
|||||||
{
|
{
|
||||||
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
||||||
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||||
|
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
|
||||||
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
|
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
|
||||||
|
|
||||||
private final IDhClientLevel clientLevel;
|
private final IDhClientLevel clientLevel;
|
||||||
@@ -106,7 +112,10 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
|
|||||||
this.ClientRenderStateRef.set(clientRenderState);
|
this.ClientRenderStateRef.set(clientRenderState);
|
||||||
}
|
}
|
||||||
|
|
||||||
clientRenderState.quadtree.tryTick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
|
// use camera position instead of player pos so free cam mods work better
|
||||||
|
Vec3d cameraDoublePos = MC_RENDER.getCameraExactPosition();
|
||||||
|
DhBlockPos2D cameraBlockPos = new DhBlockPos2D((int)cameraDoublePos.x, (int)cameraDoublePos.z);
|
||||||
|
clientRenderState.quadtree.tryTick(cameraBlockPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -170,6 +179,7 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.fullDataSourceProvider.removeDataSourceUpdateListener(this);
|
this.fullDataSourceProvider.removeDataSourceUpdateListener(this);
|
||||||
|
this.genericRenderer.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -259,7 +269,7 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
|
|||||||
@Override
|
@Override
|
||||||
public void close()
|
public void close()
|
||||||
{
|
{
|
||||||
LOGGER.info("Shutting down " + ClientRenderState.class.getSimpleName());
|
//LOGGER.info("Shutting down " + ClientRenderState.class.getSimpleName());
|
||||||
this.quadtree.close();
|
this.quadtree.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -167,15 +167,17 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Check this before decoding data to prevent errors if multiple client levels
|
||||||
|
// are receiving data at once (Immersive Portals compatibility).
|
||||||
|
boolean isSameLevel = message.isSameLevelAs(this.levelWrapper);
|
||||||
|
//NETWORK_LOGGER.debug("Buffer ["+message.payload.dtoBufferId+"] isSameLevel: ["+isSameLevel+"]");
|
||||||
|
if (!isSameLevel)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try (FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSource(message.payload))
|
try (FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSource(message.payload))
|
||||||
{
|
{
|
||||||
boolean isSameLevel = message.isSameLevelAs(this.levelWrapper);
|
|
||||||
NETWORK_LOGGER.debug("Buffer ["+message.payload.dtoBufferId+"] isSameLevel: ["+isSameLevel+"]");
|
|
||||||
if (!isSameLevel)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Executor executor = ThreadPoolUtil.getFileHandlerExecutor();
|
Executor executor = ThreadPoolUtil.getFileHandlerExecutor();
|
||||||
if (executor != null)
|
if (executor != null)
|
||||||
@@ -219,6 +221,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (MC_CLIENT.getWrappedClientLevel() == null || MC_CLIENT.getWrappedClientLevel().getDhLevel() != this) return;
|
||||||
this.clientside.clientTick();
|
this.clientside.clientTick();
|
||||||
|
|
||||||
if (this.syncOnLoadRequestQueue != null)
|
if (this.syncOnLoadRequestQueue != null)
|
||||||
|
|||||||
@@ -71,7 +71,11 @@ public class DhClientServerLevel extends AbstractDhServerLevel implements IDhCli
|
|||||||
//region
|
//region
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clientTick() { this.clientside.clientTick(); }
|
public void clientTick()
|
||||||
|
{
|
||||||
|
if (MC_CLIENT.getWrappedClientLevel() == null || MC_CLIENT.getWrappedClientLevel().getDhLevel() != this) return;
|
||||||
|
this.clientside.clientTick();
|
||||||
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -151,8 +151,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
|||||||
|
|
||||||
public synchronized boolean tick(DhBlockPos2D targetPos)
|
public synchronized boolean tick(DhBlockPos2D targetPos)
|
||||||
{
|
{
|
||||||
if (DhApiWorldProxy.INSTANCE.worldLoaded()
|
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||||
&& DhApiWorldProxy.INSTANCE.getReadOnly())
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -13,6 +13,7 @@ import com.seibel.distanthorizons.core.network.event.ScopedNetworkEventSource;
|
|||||||
import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent;
|
import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent;
|
||||||
import com.seibel.distanthorizons.core.network.event.internal.IncompatibleMessageInternalEvent;
|
import com.seibel.distanthorizons.core.network.event.internal.IncompatibleMessageInternalEvent;
|
||||||
import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage;
|
import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage;
|
||||||
|
import com.seibel.distanthorizons.core.network.messages.base.RequestLevelInitMessage;
|
||||||
import com.seibel.distanthorizons.core.network.messages.base.SessionConfigMessage;
|
import com.seibel.distanthorizons.core.network.messages.base.SessionConfigMessage;
|
||||||
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceResponseMessage;
|
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceResponseMessage;
|
||||||
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage;
|
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage;
|
||||||
@@ -164,7 +165,8 @@ public class ClientNetworkState implements Closeable
|
|||||||
// send message //
|
// send message //
|
||||||
//==============//
|
//==============//
|
||||||
|
|
||||||
|
public void sendLevelInitRequest(String clientLevelKey)
|
||||||
|
{ this.getSession().sendMessage(new RequestLevelInitMessage(clientLevelKey)); }
|
||||||
|
|
||||||
public void sendConfigMessage() { this.sendConfigMessage(true); }
|
public void sendConfigMessage() { this.sendConfigMessage(true); }
|
||||||
public void sendConfigMessage(boolean blocking)
|
public void sendConfigMessage(boolean blocking)
|
||||||
|
|||||||
+3
-3
@@ -7,10 +7,10 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
|||||||
import com.seibel.distanthorizons.core.network.INetworkObject;
|
import com.seibel.distanthorizons.core.network.INetworkObject;
|
||||||
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage;
|
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage;
|
||||||
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
|
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
|
||||||
import io.netty.buffer.CompositeByteBuf;
|
import io.netty.buffer.CompositeByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
private final ConcurrentMap<Integer, CompositeByteBuf> buffersById = CacheBuilder.newBuilder()
|
private final ConcurrentMap<Integer, CompositeByteBuf> buffersById = CacheBuilder.newBuilder()
|
||||||
.expireAfterAccess(10, TimeUnit.SECONDS)
|
.expireAfterAccess(30, TimeUnit.SECONDS)
|
||||||
.<Integer, CompositeByteBuf>build().asMap();
|
.<Integer, CompositeByteBuf>build().asMap();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -56,7 +56,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
|
|||||||
public FullDataSourceV2DTO decodeDataSource(FullDataPayload payload)
|
public FullDataSourceV2DTO decodeDataSource(FullDataPayload payload)
|
||||||
{
|
{
|
||||||
CompositeByteBuf compositeByteBuffer = this.buffersById.get(payload.dtoBufferId);
|
CompositeByteBuf compositeByteBuffer = this.buffersById.get(payload.dtoBufferId);
|
||||||
LodUtil.assertTrue(compositeByteBuffer != null, "decoded data source missing byte buffer");
|
Objects.requireNonNull(compositeByteBuffer, "Unable to get a complete buffer for a received payload. Ignore this if it doesn't spam similar errors");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
+28
-1
@@ -2,6 +2,7 @@ package com.seibel.distanthorizons.core.multiplayer.server;
|
|||||||
|
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
|
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
|
||||||
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.level.AbstractDhServerLevel;
|
import com.seibel.distanthorizons.core.level.AbstractDhServerLevel;
|
||||||
import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig;
|
import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig;
|
||||||
import com.seibel.distanthorizons.core.multiplayer.fullData.FullDataPayloadSender;
|
import com.seibel.distanthorizons.core.multiplayer.fullData.FullDataPayloadSender;
|
||||||
@@ -9,20 +10,25 @@ import com.seibel.distanthorizons.core.multiplayer.fullData.SharedBandwidthLimit
|
|||||||
import com.seibel.distanthorizons.core.network.event.internal.IncompatibleMessageInternalEvent;
|
import com.seibel.distanthorizons.core.network.event.internal.IncompatibleMessageInternalEvent;
|
||||||
import com.seibel.distanthorizons.core.network.messages.base.CloseReasonMessage;
|
import com.seibel.distanthorizons.core.network.messages.base.CloseReasonMessage;
|
||||||
import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage;
|
import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage;
|
||||||
|
import com.seibel.distanthorizons.core.network.messages.base.RequestLevelInitMessage;
|
||||||
import com.seibel.distanthorizons.core.network.messages.base.SessionConfigMessage;
|
import com.seibel.distanthorizons.core.network.messages.base.SessionConfigMessage;
|
||||||
import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent;
|
import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent;
|
||||||
import com.seibel.distanthorizons.core.network.exceptions.RateLimitedException;
|
import com.seibel.distanthorizons.core.network.exceptions.RateLimitedException;
|
||||||
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceRequestMessage;
|
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceRequestMessage;
|
||||||
import com.seibel.distanthorizons.core.network.session.NetworkSession;
|
import com.seibel.distanthorizons.core.network.session.NetworkSession;
|
||||||
import com.seibel.distanthorizons.core.util.ratelimiting.SupplierBasedRateAndConcurrencyLimiter;
|
import com.seibel.distanthorizons.core.util.ratelimiting.SupplierBasedRateAndConcurrencyLimiter;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public class ServerPlayerState implements Closeable
|
public class ServerPlayerState implements Closeable
|
||||||
{
|
{
|
||||||
|
private final IMinecraftSharedWrapper MC_SHARED = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
|
||||||
|
|
||||||
private final ConfigChangeListener<String> levelKeyPrefixChangeListener
|
private final ConfigChangeListener<String> levelKeyPrefixChangeListener
|
||||||
= new ConfigChangeListener<>(Config.Server.levelKeyPrefix, this::onLevelKeyPrefixConfigChanged);
|
= new ConfigChangeListener<>(Config.Server.levelKeyPrefix, this::onLevelKeyPrefixConfigChanged);
|
||||||
private final SessionConfig.AnyChangeListener configAnyChangeListener = new SessionConfig.AnyChangeListener(this::sendConfigMessage);
|
private final SessionConfig.AnyChangeListener configAnyChangeListener = new SessionConfig.AnyChangeListener(this::sendConfigMessage);
|
||||||
@@ -66,6 +72,12 @@ public class ServerPlayerState implements Closeable
|
|||||||
this.sendConfigMessage();
|
this.sendConfigMessage();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.networkSession.registerHandler(RequestLevelInitMessage.class, (requestLevelKeyMessage) ->
|
||||||
|
{
|
||||||
|
sendLevelKey(requestLevelKeyMessage.clientLevelKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
this.networkSession.registerHandler(CloseInternalEvent.class, event -> {
|
this.networkSession.registerHandler(CloseInternalEvent.class, event -> {
|
||||||
// No-op. prevents "Unhandled message" log entries
|
// No-op. prevents "Unhandled message" log entries
|
||||||
});
|
});
|
||||||
@@ -85,12 +97,27 @@ public class ServerPlayerState implements Closeable
|
|||||||
//=================//
|
//=================//
|
||||||
|
|
||||||
private void onLevelKeyPrefixConfigChanged(String newLevelKey) { this.sendLevelKey(); }
|
private void onLevelKeyPrefixConfigChanged(String newLevelKey) { this.sendLevelKey(); }
|
||||||
|
|
||||||
|
private void sendLevelKey(String clientLevelKey)
|
||||||
|
{
|
||||||
|
sendLevelKey(() ->
|
||||||
|
MC_SHARED
|
||||||
|
.getWrappedServerLevel(clientLevelKey)
|
||||||
|
.getKeyedLevelDimensionName());
|
||||||
|
}
|
||||||
private void sendLevelKey()
|
private void sendLevelKey()
|
||||||
|
{
|
||||||
|
sendLevelKey(() ->
|
||||||
|
this.getServerPlayer()
|
||||||
|
.getLevel()
|
||||||
|
.getKeyedLevelDimensionName());
|
||||||
|
}
|
||||||
|
private void sendLevelKey(Supplier<String> levelKeySupplier)
|
||||||
{
|
{
|
||||||
if (Config.Server.sendLevelKeys.get())
|
if (Config.Server.sendLevelKeys.get())
|
||||||
{
|
{
|
||||||
|
String levelKey = levelKeySupplier.get();
|
||||||
// let the client's know about the change
|
// let the client's know about the change
|
||||||
String levelKey = this.getServerPlayer().getLevel().getKeyedLevelDimensionName();
|
|
||||||
if (!levelKey.equals(this.lastLevelKey))
|
if (!levelKey.equals(this.lastLevelKey))
|
||||||
{
|
{
|
||||||
this.lastLevelKey = levelKey;
|
this.lastLevelKey = levelKey;
|
||||||
|
|||||||
+2
-4
@@ -21,12 +21,9 @@ package com.seibel.distanthorizons.core.network.messages;
|
|||||||
|
|
||||||
import com.google.common.collect.BiMap;
|
import com.google.common.collect.BiMap;
|
||||||
import com.google.common.collect.HashBiMap;
|
import com.google.common.collect.HashBiMap;
|
||||||
import com.seibel.distanthorizons.core.network.messages.base.CodecCrashMessage;
|
import com.seibel.distanthorizons.core.network.messages.base.*;
|
||||||
import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage;
|
|
||||||
import com.seibel.distanthorizons.core.network.messages.base.SessionConfigMessage;
|
|
||||||
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage;
|
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage;
|
||||||
import com.seibel.distanthorizons.core.network.messages.requests.CancelMessage;
|
import com.seibel.distanthorizons.core.network.messages.requests.CancelMessage;
|
||||||
import com.seibel.distanthorizons.core.network.messages.base.CloseReasonMessage;
|
|
||||||
import com.seibel.distanthorizons.core.network.messages.requests.ExceptionMessage;
|
import com.seibel.distanthorizons.core.network.messages.requests.ExceptionMessage;
|
||||||
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPartialUpdateMessage;
|
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPartialUpdateMessage;
|
||||||
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceRequestMessage;
|
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceRequestMessage;
|
||||||
@@ -60,6 +57,7 @@ public class MessageRegistry
|
|||||||
|
|
||||||
// Level keys
|
// Level keys
|
||||||
this.registerMessage(LevelInitMessage.class, LevelInitMessage::new);
|
this.registerMessage(LevelInitMessage.class, LevelInitMessage::new);
|
||||||
|
this.registerMessage(RequestLevelInitMessage.class, RequestLevelInitMessage::new);
|
||||||
|
|
||||||
// Config (for full DH support)
|
// Config (for full DH support)
|
||||||
this.registerMessage(SessionConfigMessage.class, SessionConfigMessage::new);
|
this.registerMessage(SessionConfigMessage.class, SessionConfigMessage::new);
|
||||||
|
|||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the Distant Horizons mod
|
||||||
|
* licensed under the GNU LGPL v3 License.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 James Seibel
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.seibel.distanthorizons.core.network.messages.base;
|
||||||
|
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
/** used for full DH support */
|
||||||
|
public class RequestLevelInitMessage extends AbstractNetworkMessage
|
||||||
|
{
|
||||||
|
public String clientLevelKey;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=============//
|
||||||
|
// constructor //
|
||||||
|
//=============//
|
||||||
|
|
||||||
|
public RequestLevelInitMessage() { }
|
||||||
|
public RequestLevelInitMessage(String clientLevelKey) { this.clientLevelKey = clientLevelKey; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//===============//
|
||||||
|
// serialization //
|
||||||
|
//===============//
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void encode(ByteBuf out) { this.writeString(this.clientLevelKey, out); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void decode(ByteBuf in) { this.clientLevelKey = this.readString(in); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// base overrides //
|
||||||
|
//================//
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MoreObjects.ToStringHelper toStringHelper()
|
||||||
|
{
|
||||||
|
return super.toStringHelper()
|
||||||
|
.add("levelKey", this.clientLevelKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -77,6 +77,8 @@ public class DhApiRenderProxy implements IDhApiRenderProxy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO clear tint handler too
|
||||||
|
|
||||||
return DhApiResult.createSuccess();
|
return DhApiResult.createSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+163
-79
@@ -21,6 +21,7 @@ package com.seibel.distanthorizons.core.render.QuadTree;
|
|||||||
|
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
|
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
|
||||||
|
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
||||||
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
|
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
|
||||||
@@ -148,7 +149,8 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
int initialPlayerBlockX, int initialPlayerBlockZ,
|
int initialPlayerBlockX, int initialPlayerBlockZ,
|
||||||
FullDataSourceProviderV2 fullDataSourceProvider)
|
FullDataSourceProviderV2 fullDataSourceProvider)
|
||||||
{
|
{
|
||||||
super(viewDiameterInBlocks, new DhBlockPos2D(initialPlayerBlockX, initialPlayerBlockZ), DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
|
super(viewDiameterInBlocks, FullDataSourceV2.WIDTH,
|
||||||
|
new DhBlockPos2D(initialPlayerBlockX, initialPlayerBlockZ), DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
|
||||||
|
|
||||||
DEBUG_RENDERER.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus);
|
DEBUG_RENDERER.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus);
|
||||||
|
|
||||||
@@ -245,17 +247,38 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
//===================//
|
//===================//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
this.setCenterBlockPos(playerPos, (renderSection) ->
|
// remove out of bound sections
|
||||||
{
|
this.setCenterBlockPos(playerPos,
|
||||||
// removing out of bounds sections
|
// remove completely out of bound nodes
|
||||||
if (renderSection != null)
|
// (the root node is no longer in bounds)
|
||||||
|
(renderSection) ->
|
||||||
{
|
{
|
||||||
this.fullDataSourceProvider.removeRetrievalRequestIf((long genPos) -> DhSectionPos.contains(renderSection.pos, genPos));
|
if (renderSection != null)
|
||||||
this.missingGenerationPosSet.remove(renderSection.pos);
|
{
|
||||||
this.queuedGenerationPosSet.remove(renderSection.pos);
|
this.fullDataSourceProvider.removeRetrievalRequestIf((long genPos) -> DhSectionPos.contains(renderSection.pos, genPos));
|
||||||
renderSection.close();
|
|
||||||
|
// unfortunately we have to fully go through each set
|
||||||
|
// since a removed position may be larger than the multiple generated positions
|
||||||
|
// it contains
|
||||||
|
this.missingGenerationPosSet.removeIf((Long genPos) -> DhSectionPos.contains(renderSection.pos, genPos));
|
||||||
|
this.queuedGenerationPosSet.removeIf((Long genPos) -> DhSectionPos.contains(renderSection.pos, genPos));
|
||||||
|
|
||||||
|
renderSection.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// mutate partially out of bound nodes
|
||||||
|
// (the root node is still in bounds, but this individual child node isn't)
|
||||||
|
(renderSection) ->
|
||||||
|
{
|
||||||
|
if (renderSection != null)
|
||||||
|
{
|
||||||
|
// when this node comes back into render distance
|
||||||
|
// we'll need to re-load it since the full data
|
||||||
|
// may have been modified while it was out of bounds
|
||||||
|
renderSection.renderDataDirty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
@@ -304,7 +327,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
node.value.retreivedMissingSectionsForRetreival = false;
|
node.value.queuedMissingSectionsForRetrieval = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,8 +395,6 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
//=========================//
|
//=========================//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
// also handles disabling beacons
|
|
||||||
|
|
||||||
for (QuadNode<LodRenderSection> node : this.tickNodeHolder.getDisableNodes())
|
for (QuadNode<LodRenderSection> node : this.tickNodeHolder.getDisableNodes())
|
||||||
{
|
{
|
||||||
if (node == null || node.value == null) { continue; }
|
if (node == null || node.value == null) { continue; }
|
||||||
@@ -449,9 +470,9 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
|
|
||||||
// since this section wants to render
|
// since this section wants to render
|
||||||
// check if it needs any generation to do so
|
// check if it needs any generation to do so
|
||||||
if (!node.value.retreivedMissingSectionsForRetreival)
|
if (!node.value.queuedMissingSectionsForRetrieval)
|
||||||
{
|
{
|
||||||
node.value.retreivedMissingSectionsForRetreival = true;
|
node.value.queuedMissingSectionsForRetrieval = true;
|
||||||
this.tryQueuePosForRetrieval(node.value.pos); // can be quite slow
|
this.tryQueuePosForRetrieval(node.value.pos); // can be quite slow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -476,9 +497,10 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
//=========================//
|
//=========================//
|
||||||
// tick - recursive update //
|
// tick - recursive update //
|
||||||
//=========================//
|
//=========================//
|
||||||
///region
|
//region
|
||||||
|
|
||||||
private void recursivelyUpdateRenderSectionNode(
|
/** @return true if the node at this position has uploaded its render data */
|
||||||
|
private boolean recursivelyUpdateRenderSectionNode(
|
||||||
@NotNull DhBlockPos2D playerPos,
|
@NotNull DhBlockPos2D playerPos,
|
||||||
@NotNull QuadNode<LodRenderSection> rootNode,
|
@NotNull QuadNode<LodRenderSection> rootNode,
|
||||||
@Nullable QuadNode<LodRenderSection> parentNode,
|
@Nullable QuadNode<LodRenderSection> parentNode,
|
||||||
@@ -490,19 +512,22 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
// get/create the node //
|
// get/create the node //
|
||||||
// and render section //
|
// and render section //
|
||||||
//=====================//
|
//=====================//
|
||||||
///region
|
//region
|
||||||
|
|
||||||
// create the node
|
quadNode = this.tryAddNodeToTree(rootNode, quadNode, sectionPos);
|
||||||
if (quadNode == null)
|
|
||||||
{
|
|
||||||
rootNode.setValue(sectionPos, new LodRenderSection(sectionPos, this, this.level, this.fullDataSourceProvider));
|
// Skip sections that are out-of-bounds.
|
||||||
quadNode = rootNode.getNode(sectionPos);
|
// If not done some sections will appear and/or generate
|
||||||
}
|
// outside the desired render distance
|
||||||
if (quadNode == null)
|
if (!this.isSectionPosInBounds(quadNode.sectionPos))
|
||||||
{
|
{
|
||||||
LodUtil.assertNotReach("Unable to add node with pos ["+DhSectionPos.toString(sectionPos)+"] to tree root ["+rootNode+"].");
|
this.tickNodeHolder.addDisableNode(quadNode);
|
||||||
|
this.recursivelyDisableChildNodes(quadNode);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// make sure the render section is created (shouldn't be necessary, but just in case)
|
// make sure the render section is created (shouldn't be necessary, but just in case)
|
||||||
LodRenderSection renderSection = quadNode.value;
|
LodRenderSection renderSection = quadNode.value;
|
||||||
if (renderSection == null)
|
if (renderSection == null)
|
||||||
@@ -511,7 +536,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
quadNode.setValue(sectionPos, renderSection);
|
quadNode.setValue(sectionPos, renderSection);
|
||||||
}
|
}
|
||||||
|
|
||||||
///endregion
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -519,7 +544,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
// handle enabling, loading, //
|
// handle enabling, loading, //
|
||||||
// and disabling render sections //
|
// and disabling render sections //
|
||||||
//===============================//
|
//===============================//
|
||||||
///region
|
//region
|
||||||
|
|
||||||
// load every node for rendering
|
// load every node for rendering
|
||||||
if (!renderSection.gpuUploadInProgress()
|
if (!renderSection.gpuUploadInProgress()
|
||||||
@@ -535,94 +560,153 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
|
|
||||||
if (DhSectionPos.getDetailLevel(quadNode.sectionPos) > expectedDetailLevel)
|
if (DhSectionPos.getDetailLevel(quadNode.sectionPos) > expectedDetailLevel)
|
||||||
{
|
{
|
||||||
this.onDetailLevelTooHigh(playerPos, rootNode, quadNode);
|
return this.onDetailLevelTooLow(playerPos, rootNode, quadNode);
|
||||||
}
|
}
|
||||||
// the (expectedDetailLevel-1) fixes corners being cut out due to distance calculations using the LOD center
|
// the (expectedDetailLevel-1) fixes corners being cut out due to distance calculations using the LOD center
|
||||||
else if (DhSectionPos.getDetailLevel(quadNode.sectionPos) == expectedDetailLevel
|
else if (DhSectionPos.getDetailLevel(quadNode.sectionPos) == expectedDetailLevel
|
||||||
|| DhSectionPos.getDetailLevel(quadNode.sectionPos) == expectedDetailLevel - 1)
|
|| DhSectionPos.getDetailLevel(quadNode.sectionPos) == expectedDetailLevel - 1)
|
||||||
{
|
{
|
||||||
this.onDesiredDetailLevel(quadNode, parentNode);
|
return this.onDesiredDetailLevel(quadNode, parentNode);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new IllegalStateException("LodQuadTree shouldn't be updating renderSections below the expected detail level: [" + expectedDetailLevel + "].");
|
throw new IllegalStateException("LodQuadTree shouldn't be updating renderSections below the expected detail level: [" + expectedDetailLevel + "].");
|
||||||
}
|
}
|
||||||
|
|
||||||
///endregion
|
//endregion
|
||||||
}
|
}
|
||||||
private void onDetailLevelTooHigh(
|
/** @return true if the node at this position has uploaded its render data */
|
||||||
|
private boolean onDetailLevelTooLow(
|
||||||
@NotNull DhBlockPos2D playerPos,
|
@NotNull DhBlockPos2D playerPos,
|
||||||
@NotNull QuadNode<LodRenderSection> rootNode, @NotNull QuadNode<LodRenderSection> quadNode)
|
@NotNull QuadNode<LodRenderSection> rootNode,
|
||||||
|
@NotNull QuadNode<LodRenderSection> quadNode)
|
||||||
{
|
{
|
||||||
// recursively update each child node
|
// recursively update each child node
|
||||||
boolean allChildNodesCanRender = true;
|
int childNodeRenderCount = 0;
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i);
|
|
||||||
long childPos = DhSectionPos.getChildByIndex(quadNode.sectionPos, i);
|
long childPos = DhSectionPos.getChildByIndex(quadNode.sectionPos, i);
|
||||||
this.recursivelyUpdateRenderSectionNode(
|
QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i);
|
||||||
playerPos,
|
|
||||||
rootNode, quadNode, childNode, childPos);
|
|
||||||
childNode = quadNode.getChildByIndex(i); // needs to be gotten again in case a new node was added to the tree (this will often happen when moving into new areas where the children were deleted)
|
|
||||||
|
|
||||||
// nodes shouldn't be null, but just in case
|
boolean childCanRender = this.recursivelyUpdateRenderSectionNode(
|
||||||
if (childNode != null
|
playerPos,
|
||||||
&& childNode.value != null
|
rootNode, quadNode, childNode, childPos);
|
||||||
&& !childNode.value.gpuUploadComplete())
|
if (childCanRender)
|
||||||
{
|
{
|
||||||
// the node is present but not uploaded yet
|
// node can be rendered
|
||||||
allChildNodesCanRender = false;
|
childNodeRenderCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isRootNode = (quadNode == rootNode);
|
||||||
if (allChildNodesCanRender)
|
if (isRootNode)
|
||||||
{
|
{
|
||||||
// all child nodes can render, this node isn't needed
|
// Never render the root node.
|
||||||
|
// This is done to prevent flashing when moving across root node
|
||||||
|
// boundaries.
|
||||||
|
// Otherwise, when moving, new empty nodes will be added at the edge of the tree
|
||||||
|
// which will require the root node to render to cover the "empty" area.
|
||||||
this.tickNodeHolder.addDisableNode(quadNode);
|
this.tickNodeHolder.addDisableNode(quadNode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (childNodeRenderCount >= 4)
|
||||||
|
{
|
||||||
|
this.tickNodeHolder.addDisableNode(quadNode);
|
||||||
|
|
||||||
|
// all children can render,
|
||||||
|
// the area will be filled when rendering
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// not all child positions are loaded yet, this one should be rendered instead
|
boolean nodeCanRender = quadNode.value != null
|
||||||
this.tickNodeHolder.addEnableNode(quadNode);
|
&& quadNode.value.canRender();
|
||||||
|
if (nodeCanRender)
|
||||||
|
{
|
||||||
|
// not all child positions are loaded yet, this one should be rendered instead
|
||||||
|
this.tickNodeHolder.addEnableNode(quadNode);
|
||||||
|
this.recursivelyDisableChildNodes(quadNode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.tickNodeHolder.addDisableNode(quadNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodeCanRender;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void onDesiredDetailLevel(
|
|
||||||
@NotNull QuadNode<LodRenderSection> quadNode, @Nullable QuadNode<LodRenderSection> parentNode)
|
/** @return true if the node at this position has uploaded its render data */
|
||||||
|
private boolean onDesiredDetailLevel(
|
||||||
|
@NotNull QuadNode<LodRenderSection> quadNode,
|
||||||
|
@Nullable QuadNode<LodRenderSection> parentNode)
|
||||||
{
|
{
|
||||||
boolean allAdjNodesCanRender = true;
|
// Skip sections that are out-of-bounds.
|
||||||
|
// If not done some sections will appear and/or generate
|
||||||
// if the parent node is null, that means we're at the root node
|
// outside the desired render distance
|
||||||
// and we should always render
|
if (!this.isSectionPosInBounds(quadNode.sectionPos))
|
||||||
if (parentNode != null)
|
|
||||||
{
|
{
|
||||||
// check if all adjacent nodes are ready to render
|
return true;
|
||||||
// this check is done to prevent some overlapping due to the parent node
|
}
|
||||||
// still being active
|
|
||||||
for (int i = 0; i < 4; i++)
|
if (quadNode.value != null
|
||||||
|
&& quadNode.value.canRender())
|
||||||
|
{
|
||||||
|
if (!this.tickNodeHolder.getEnabledNodes().contains(parentNode))
|
||||||
{
|
{
|
||||||
QuadNode<LodRenderSection> adjNode = parentNode.getChildByIndex(i);
|
this.tickNodeHolder.addEnableDeleteChildrenNode(quadNode);
|
||||||
// nodes shouldn't be null, but just in case there's an issue
|
return true;
|
||||||
if (adjNode != null
|
}
|
||||||
&& adjNode.value != null
|
else
|
||||||
&& !adjNode.value.gpuUploadComplete())
|
{
|
||||||
{
|
this.tickNodeHolder.addDisableNode(quadNode);
|
||||||
// the node is present but not uploaded yet
|
return false;
|
||||||
allAdjNodesCanRender = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (allAdjNodesCanRender
|
|
||||||
&& quadNode.value != null
|
|
||||||
&& quadNode.value.gpuUploadComplete())
|
|
||||||
{
|
{
|
||||||
this.tickNodeHolder.addEnableDeleteChildrenNode(quadNode);
|
this.tickNodeHolder.addDisableNode(quadNode);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///endregion
|
@NotNull
|
||||||
|
private QuadNode<LodRenderSection> tryAddNodeToTree(
|
||||||
|
@NotNull QuadNode<LodRenderSection> rootNode,
|
||||||
|
@Nullable QuadNode<LodRenderSection> quadNode,
|
||||||
|
long sectionPos // section pos is needed here since the quad node may be null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// create the node
|
||||||
|
if (quadNode == null)
|
||||||
|
{
|
||||||
|
rootNode.setValue(sectionPos, new LodRenderSection(sectionPos, this, this.level, this.fullDataSourceProvider));
|
||||||
|
quadNode = rootNode.getNode(sectionPos);
|
||||||
|
}
|
||||||
|
if (quadNode == null)
|
||||||
|
{
|
||||||
|
LodUtil.assertNotReach("Unable to add node with pos ["+DhSectionPos.toString(sectionPos)+"] to tree root ["+rootNode+"].");
|
||||||
|
}
|
||||||
|
|
||||||
|
return quadNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recursivelyDisableChildNodes(@NotNull QuadNode<LodRenderSection> quadNode)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i);
|
||||||
|
this.tickNodeHolder.removeEnableAndDisableNode(childNode);
|
||||||
|
|
||||||
|
if (childNode != null)
|
||||||
|
{
|
||||||
|
this.recursivelyDisableChildNodes(childNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
//=====================//
|
//=====================//
|
||||||
// tick - work queuing //
|
// tick - work queuing //
|
||||||
@@ -1127,7 +1211,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
{
|
{
|
||||||
color = Color.ORANGE;
|
color = Color.ORANGE;
|
||||||
}
|
}
|
||||||
else if (!renderSection.gpuUploadComplete())
|
else if (!renderSection.canRender())
|
||||||
{
|
{
|
||||||
// uploaded but the buffer is missing
|
// uploaded but the buffer is missing
|
||||||
color = Color.PINK;
|
color = Color.PINK;
|
||||||
@@ -1169,7 +1253,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
@Override
|
@Override
|
||||||
public void close()
|
public void close()
|
||||||
{
|
{
|
||||||
LOGGER.info("Shutting down LodQuadTree...");
|
//LOGGER.info("Shutting down LodQuadTree...");
|
||||||
|
|
||||||
DEBUG_RENDERER.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus);
|
DEBUG_RENDERER.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus);
|
||||||
Config.Common.WorldGenerator.enableDistantGeneration.removeListener(this);
|
Config.Common.WorldGenerator.enableDistantGeneration.removeListener(this);
|
||||||
@@ -1204,7 +1288,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
LOGGER.info("Finished shutting down LodQuadTree");
|
//LOGGER.info("Finished shutting down LodQuadTree");
|
||||||
}
|
}
|
||||||
|
|
||||||
//endregion base methods
|
//endregion base methods
|
||||||
|
|||||||
+100
-73
@@ -34,11 +34,9 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
|||||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer;
|
import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.BeaconRenderHandler;
|
|
||||||
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
|
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
|
||||||
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodBufferContainer;
|
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodBufferContainer;
|
||||||
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
|
import com.seibel.distanthorizons.core.util.ExceptionUtil;
|
||||||
import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo;
|
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||||
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
|
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
|
||||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||||
@@ -48,10 +46,8 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
import javax.annotation.WillNotClose;
|
import javax.annotation.WillNotClose;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A render section represents an area that could be rendered.
|
* A render section represents an area that could be rendered.
|
||||||
@@ -67,7 +63,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
|
|
||||||
public final long pos;
|
public final long pos;
|
||||||
|
|
||||||
private final IDhClientLevel level;
|
private final IDhClientLevel clientLevel;
|
||||||
private final IClientLevelWrapper levelWrapper;
|
private final IClientLevelWrapper levelWrapper;
|
||||||
@WillNotClose
|
@WillNotClose
|
||||||
private final FullDataSourceProviderV2 fullDataSourceProvider;
|
private final FullDataSourceProviderV2 fullDataSourceProvider;
|
||||||
@@ -75,8 +71,16 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
|
|
||||||
|
|
||||||
private boolean renderingEnabled = false;
|
private boolean renderingEnabled = false;
|
||||||
private boolean beaconsRendering = false;
|
/**
|
||||||
public boolean retreivedMissingSectionsForRetreival = false;
|
* Used when a node goes out of render distance
|
||||||
|
* but isn't removed from the underlying quad tree structure. <br><br>
|
||||||
|
*
|
||||||
|
* In those cases we should act as if the node was removed
|
||||||
|
* for cached render data caching purposes, but not
|
||||||
|
* for re-creating missing nodes.
|
||||||
|
*/
|
||||||
|
public boolean renderDataDirty = false;
|
||||||
|
public boolean queuedMissingSectionsForRetrieval = false;
|
||||||
|
|
||||||
/** this reference is necessary so we can determine what VBO to render */
|
/** this reference is necessary so we can determine what VBO to render */
|
||||||
public LodBufferContainer renderBufferContainer;
|
public LodBufferContainer renderBufferContainer;
|
||||||
@@ -94,13 +98,6 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
*/
|
*/
|
||||||
private Runnable getAndBuildRenderDataRunnable = null;
|
private Runnable getAndBuildRenderDataRunnable = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents just uploading the {@link LodQuadBuilder} to the GPU. <br>
|
|
||||||
* Separate from {@link LodRenderSection#getAndBuildRenderDataFutureRef} because they run on
|
|
||||||
* different threads (buffer uploading is on the MC render thread) and need to be canceled separately.
|
|
||||||
*/
|
|
||||||
private final AtomicReference<CompletableFuture<LodBufferContainer>> bufferUploadFutureRef = new AtomicReference<>(null);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=============//
|
//=============//
|
||||||
@@ -111,12 +108,12 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
public LodRenderSection(
|
public LodRenderSection(
|
||||||
long pos,
|
long pos,
|
||||||
LodQuadTree quadTree,
|
LodQuadTree quadTree,
|
||||||
IDhClientLevel level, FullDataSourceProviderV2 fullDataSourceProvider)
|
IDhClientLevel clientLevel, FullDataSourceProviderV2 fullDataSourceProvider)
|
||||||
{
|
{
|
||||||
this.pos = pos;
|
this.pos = pos;
|
||||||
this.quadTree = quadTree;
|
this.quadTree = quadTree;
|
||||||
this.level = level;
|
this.clientLevel = clientLevel;
|
||||||
this.levelWrapper = level.getClientLevelWrapper();
|
this.levelWrapper = clientLevel.getClientLevelWrapper();
|
||||||
this.fullDataSourceProvider = fullDataSourceProvider;
|
this.fullDataSourceProvider = fullDataSourceProvider;
|
||||||
|
|
||||||
DEBUG_RENDERER.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus);
|
DEBUG_RENDERER.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus);
|
||||||
@@ -158,6 +155,8 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// shouldn't happen since this method is synchronized, but just in case
|
||||||
|
// make sure we only ever start one upload task
|
||||||
if (!this.getAndBuildRenderDataFutureRef.compareAndSet(null, future))
|
if (!this.getAndBuildRenderDataFutureRef.compareAndSet(null, future))
|
||||||
{
|
{
|
||||||
CompletableFuture<Void> oldFuture = this.getAndBuildRenderDataFutureRef.get();
|
CompletableFuture<Void> oldFuture = this.getAndBuildRenderDataFutureRef.get();
|
||||||
@@ -170,6 +169,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// build LOD data on a DH thread
|
||||||
LodQuadBuilder lodQuadBuilder = this.getAndBuildRenderData();
|
LodQuadBuilder lodQuadBuilder = this.getAndBuildRenderData();
|
||||||
if (lodQuadBuilder == null)
|
if (lodQuadBuilder == null)
|
||||||
{
|
{
|
||||||
@@ -177,7 +177,8 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.uploadToGpuAsync(lodQuadBuilder)
|
// uploading will primarily happen on the render thread
|
||||||
|
this.uploadToGpuAsync(future, lodQuadBuilder)
|
||||||
.thenRun(() ->
|
.thenRun(() ->
|
||||||
{
|
{
|
||||||
// the future is passed in separately (IE not using the local var) to prevent any possible race condition null pointers
|
// the future is passed in separately (IE not using the local var) to prevent any possible race condition null pointers
|
||||||
@@ -187,7 +188,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
LOGGER.error("Unexpected issue creating render data for pos: ["+DhSectionPos.toString(this.pos)+"], error: ["+e.getMessage()+"].", e);
|
LOGGER.error("Unexpected issue creating render data for pos: ["+DhSectionPos.toString(this.pos)+"], error: ["+e.getMessage()+"].", e);
|
||||||
future.complete(null);
|
future.completeExceptionally(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
executor.execute(this.getAndBuildRenderDataRunnable);
|
executor.execute(this.getAndBuildRenderDataRunnable);
|
||||||
@@ -202,6 +203,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//=======================//
|
||||||
|
// Get LOD ID data //
|
||||||
|
// and build render data //
|
||||||
|
//=======================//
|
||||||
|
//region
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private synchronized LodQuadBuilder getAndBuildRenderData()
|
private synchronized LodQuadBuilder getAndBuildRenderData()
|
||||||
{
|
{
|
||||||
@@ -215,7 +224,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
|
|
||||||
|
|
||||||
boolean enableTransparency = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled;
|
boolean enableTransparency = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled;
|
||||||
LodQuadBuilder lodQuadBuilder = new LodQuadBuilder(enableTransparency, this.level.getClientLevelWrapper());
|
LodQuadBuilder lodQuadBuilder = new LodQuadBuilder(enableTransparency, this.clientLevel.getClientLevelWrapper());
|
||||||
|
|
||||||
|
|
||||||
// get the adjacent positions
|
// get the adjacent positions
|
||||||
@@ -238,7 +247,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
|
|
||||||
// the render sources are only needed by this synchronous method,
|
// the render sources are only needed by this synchronous method,
|
||||||
// then they can be closed
|
// then they can be closed
|
||||||
ColumnRenderBufferBuilder.makeLodRenderData(lodQuadBuilder, thisRenderSource, this.level, adjacentRenderSections, adjIsSameDetailLevel);
|
ColumnRenderBufferBuilder.makeLodRenderData(lodQuadBuilder, thisRenderSource, this.clientLevel, adjacentRenderSections, adjIsSameDetailLevel);
|
||||||
return lodQuadBuilder;
|
return lodQuadBuilder;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -288,52 +297,63 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
detailLevel += DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
|
detailLevel += DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
|
||||||
return detailLevel == DhSectionPos.getDetailLevel(this.pos);
|
return detailLevel == DhSectionPos.getDetailLevel(this.pos);
|
||||||
}
|
}
|
||||||
private synchronized CompletableFuture<LodBufferContainer> uploadToGpuAsync(LodQuadBuilder lodQuadBuilder)
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
private synchronized CompletableFuture<LodBufferContainer> uploadToGpuAsync(
|
||||||
|
CompletableFuture<Void> parentFuture,
|
||||||
|
LodQuadBuilder lodQuadBuilder)
|
||||||
{
|
{
|
||||||
CompletableFuture<LodBufferContainer> oldFuture = this.bufferUploadFutureRef.getAndSet(null);
|
CompletableFuture<LodBufferContainer> uploadFuture = LodBufferContainer.tryMakeAndUploadBuffersAsync(this.pos, this.clientLevel, lodQuadBuilder);
|
||||||
if (oldFuture != null)
|
uploadFuture.whenComplete((bufferContainer, e) ->
|
||||||
{
|
{
|
||||||
// canceling the previous future
|
try
|
||||||
// prevents the CPU from working on something that won't be used
|
|
||||||
oldFuture.cancel(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
CompletableFuture<LodBufferContainer> future = ColumnRenderBufferBuilder.uploadBuffersAsync(this.level, this.pos, lodQuadBuilder);
|
|
||||||
future.handle((lodBufferContainer, throwable) ->
|
|
||||||
{
|
|
||||||
if (!this.bufferUploadFutureRef.compareAndSet(future, null)
|
|
||||||
// if the old future is canceled then the future ref will be different and that's expected
|
|
||||||
&& !future.isCancelled()
|
|
||||||
// if the old future is already done, then we don't care about the ref being swapped
|
|
||||||
&& !future.isDone())
|
|
||||||
{
|
{
|
||||||
LOGGER.warn("Buffer upload future ref changed for pos: ["+DhSectionPos.toString(this.pos)+"].");
|
// handle errors and early shutdown
|
||||||
|
if (e != null)
|
||||||
|
{
|
||||||
|
if (!ExceptionUtil.isShutdownException(e))
|
||||||
|
{
|
||||||
|
LOGGER.error("Unexpected issue uploading buffers for pos: [" + DhSectionPos.toString(this.pos) + "], error: [" + e.getMessage() + "].", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bufferContainer != null)
|
||||||
|
{
|
||||||
|
// shouldn't happen, but just in case
|
||||||
|
bufferContainer.close();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// close the old container
|
||||||
|
LodBufferContainer oldContainer = this.renderBufferContainer;
|
||||||
|
this.renderBufferContainer = bufferContainer.buffersUploaded ? bufferContainer : null;
|
||||||
|
if (oldContainer != null)
|
||||||
|
{
|
||||||
|
oldContainer.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// upload complete
|
||||||
|
this.renderDataDirty = false;
|
||||||
|
|
||||||
|
|
||||||
|
if (parentFuture.isCancelled())
|
||||||
|
{
|
||||||
|
// if the parent future was canceled that likely means
|
||||||
|
// this LodRenderSection was closed before this point,
|
||||||
|
// meaning this buffer will become homeless,
|
||||||
|
// so we need to clean it up here
|
||||||
|
bufferContainer.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception finishEx)
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
future.thenAccept((LodBufferContainer buffer) ->
|
|
||||||
{
|
|
||||||
// needed to clean up the old data
|
|
||||||
LodBufferContainer previousContainer = this.renderBufferContainer;
|
|
||||||
|
|
||||||
// upload complete
|
|
||||||
this.renderBufferContainer = buffer.buffersUploaded ? buffer : null;
|
|
||||||
|
|
||||||
if (previousContainer != null)
|
|
||||||
{
|
{
|
||||||
previousContainer.close();
|
LOGGER.error("Unexpected buffer finish exception: ["+finishEx.getMessage()+"]", finishEx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return uploadFuture;
|
||||||
if (!this.bufferUploadFutureRef.compareAndSet(null, future))
|
|
||||||
{
|
|
||||||
LodUtil.assertNotReach("Buffer upload future ref couldn't be set due to concurrency error, pos: ["+DhSectionPos.toString(this.pos)+"].");
|
|
||||||
}
|
|
||||||
|
|
||||||
return future;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//endregion render data uploading
|
//endregion render data uploading
|
||||||
@@ -345,7 +365,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
//=================//
|
//=================//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
public boolean gpuUploadComplete() { return this.renderBufferContainer != null; }
|
public boolean canRender() { return this.renderBufferContainer != null; }
|
||||||
|
public boolean gpuUploadComplete()
|
||||||
|
{
|
||||||
|
return this.renderBufferContainer != null
|
||||||
|
// render dirty is here so we can trigger new GPU uploads
|
||||||
|
// even if the render data is present
|
||||||
|
&& !this.renderDataDirty;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean getRenderingEnabled() { return this.renderingEnabled; }
|
public boolean getRenderingEnabled() { return this.renderingEnabled; }
|
||||||
public void setRenderingEnabled(boolean enabled) { this.renderingEnabled = enabled;}
|
public void setRenderingEnabled(boolean enabled) { this.renderingEnabled = enabled;}
|
||||||
@@ -374,14 +401,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
{
|
{
|
||||||
color = Color.yellow;
|
color = Color.yellow;
|
||||||
}
|
}
|
||||||
else if (this.gpuUploadComplete())
|
else if (this.canRender())
|
||||||
{
|
{
|
||||||
//color = Color.cyan;
|
//color = Color.cyan;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int levelMinY = this.level.getLevelWrapper().getMinHeight();
|
int levelMinY = this.clientLevel.getLevelWrapper().getMinHeight();
|
||||||
int levelMaxY = this.level.getLevelWrapper().getMaxHeight();
|
int levelMaxY = this.clientLevel.getLevelWrapper().getMaxHeight();
|
||||||
|
|
||||||
// show the wireframe a bit lower than world max height,
|
// show the wireframe a bit lower than world max height,
|
||||||
// since most worlds don't render all the way up to the max height
|
// since most worlds don't render all the way up to the max height
|
||||||
@@ -418,12 +445,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (this.renderBufferContainer != null)
|
// render loading is no longer needed
|
||||||
{
|
|
||||||
this.renderBufferContainer.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// removes any in-progress futures since they aren't needed any more
|
|
||||||
CompletableFuture<Void> buildFuture = this.getAndBuildRenderDataFutureRef.get();
|
CompletableFuture<Void> buildFuture = this.getAndBuildRenderDataFutureRef.get();
|
||||||
if (buildFuture != null)
|
if (buildFuture != null)
|
||||||
{
|
{
|
||||||
@@ -439,12 +461,17 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
renderLoaderExecutor.remove(runnable);
|
renderLoaderExecutor.remove(runnable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cancel the future after removing the runnable
|
||||||
|
// to make sure the runnable is properly removed
|
||||||
|
buildFuture.cancel(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
CompletableFuture<LodBufferContainer> uploadFuture = this.bufferUploadFutureRef.get();
|
|
||||||
if (uploadFuture != null)
|
this.setRenderingEnabled(false);
|
||||||
|
if (this.renderBufferContainer != null)
|
||||||
{
|
{
|
||||||
uploadFuture.cancel(true);
|
this.renderBufferContainer.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-9
@@ -63,21 +63,39 @@ public class QuadTreeTickNodeHolder
|
|||||||
{
|
{
|
||||||
if(this.presentNodes.add(node))
|
if(this.presentNodes.add(node))
|
||||||
{
|
{
|
||||||
// not a big fan of having to check every node to prevent overlaps, but it does work
|
// in James testing as of 4-21-2026
|
||||||
this.nodesToEnable.removeIf((QuadNode<LodRenderSection> checkNode) ->
|
// this should no longer be needed to prevent overlaps,
|
||||||
|
// however I'm keeping it here as a quick fix solution if
|
||||||
|
// the problem comes up again
|
||||||
|
if (false)
|
||||||
{
|
{
|
||||||
boolean contained = DhSectionPos.contains(node.sectionPos, checkNode.sectionPos);
|
// not a big fan of having to check every node to prevent overlaps, but it does work
|
||||||
if (contained)
|
this.nodesToEnable.removeIf((QuadNode<LodRenderSection> checkNode) ->
|
||||||
{
|
{
|
||||||
this.nodesToDisable.add(checkNode);
|
boolean contained = DhSectionPos.contains(node.sectionPos, checkNode.sectionPos);
|
||||||
}
|
if (contained)
|
||||||
|
{
|
||||||
return contained;
|
this.nodesToDisable.add(checkNode);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
return contained;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.nodesToEnable.add(node);
|
this.nodesToEnable.add(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** */
|
||||||
|
public void removeEnableAndDisableNode(QuadNode<LodRenderSection> node)
|
||||||
|
{
|
||||||
|
this.nodesToEnable.remove(node);
|
||||||
|
this.nodesToEnableDeleteChildrenList.remove(node);
|
||||||
|
|
||||||
|
this.presentNodes.add(node); // should already be present, but re-added just in case
|
||||||
|
this.nodesToDisable.add(node); // node shouldn't be rendered since it's being disabled by a parent
|
||||||
|
}
|
||||||
|
|
||||||
public HashSet<QuadNode<LodRenderSection>> getEnabledNodes() { return this.nodesToEnable; }
|
public HashSet<QuadNode<LodRenderSection>> getEnabledNodes() { return this.nodesToEnable; }
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,18 +6,14 @@ import com.seibel.distanthorizons.core.api.internal.SharedApi;
|
|||||||
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
|
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.jar.EPlatform;
|
|
||||||
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
||||||
import com.seibel.distanthorizons.core.util.RenderUtil;
|
import com.seibel.distanthorizons.core.util.RenderUtil;
|
||||||
import com.seibel.distanthorizons.core.util.math.Mat4f;
|
import com.seibel.distanthorizons.core.util.math.Mat4f;
|
||||||
import com.seibel.distanthorizons.core.util.math.Vec3d;
|
import com.seibel.distanthorizons.core.util.math.Vec3d;
|
||||||
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
|
|
||||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
|
||||||
import com.seibel.distanthorizons.core.world.IDhClientWorld;
|
import com.seibel.distanthorizons.core.world.IDhClientWorld;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.ILightMapWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.ILightMapWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.AbstractOptifineAccessor;
|
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IOptifineAccessor;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IOptifineAccessor;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||||
@@ -87,7 +83,7 @@ public class RenderParams extends DhApiRenderParam
|
|||||||
this.dhClientWorld = SharedApi.tryGetDhClientWorld();
|
this.dhClientWorld = SharedApi.tryGetDhClientWorld();
|
||||||
if (this.dhClientWorld != null)
|
if (this.dhClientWorld != null)
|
||||||
{
|
{
|
||||||
this.dhClientLevel = (IDhClientLevel) this.dhClientWorld.getLevel(clientLevelWrapper);
|
this.dhClientLevel = this.dhClientWorld.getOrLoadClientLevel(clientLevelWrapper);
|
||||||
if (this.dhClientLevel != null)
|
if (this.dhClientLevel != null)
|
||||||
{
|
{
|
||||||
this.renderBufferHandler = this.dhClientLevel.getRenderBufferHandler();
|
this.renderBufferHandler = this.dhClientLevel.getRenderBufferHandler();
|
||||||
@@ -176,45 +172,6 @@ public class RenderParams extends DhApiRenderParam
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//// potential fix for a segfault when
|
|
||||||
//// Sodium and DH are running together
|
|
||||||
//if (EPlatform.get() == EPlatform.MACOS
|
|
||||||
// && !initialLoadingComplete)
|
|
||||||
//{
|
|
||||||
// // Once MC starts rendering, wait a few seconds so
|
|
||||||
// // MC/Sodium can finish their shader compiling before DH does its own.
|
|
||||||
// // This will allow DH to compile its own shaders after Sodium finishes
|
|
||||||
// // compiling its own.
|
|
||||||
// long nowMs = System.currentTimeMillis();
|
|
||||||
// long firstAllowedRenderTimeMs = firstRenderTimeMs + TIME_FOR_MAC_TO_FINISH_COMPILING_IN_MS;
|
|
||||||
// if (nowMs < firstAllowedRenderTimeMs)
|
|
||||||
// {
|
|
||||||
// return "Waiting for initial MC compile...";
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// // null shouldn't happen, but just in case
|
|
||||||
// PriorityTaskPicker.Executor renderLoadExecutor = ThreadPoolUtil.getRenderLoadingExecutor();
|
|
||||||
// if (renderLoadExecutor == null)
|
|
||||||
// {
|
|
||||||
// return "Waiting for DH Threadpool...";
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // wait for DH to finish loading, by the time that's done
|
|
||||||
// // java should have finished all of DH's JIT compiling,
|
|
||||||
// // which will hopefully mean less concurrency and thus a lower
|
|
||||||
// // chance of breaking
|
|
||||||
// // (plus this gives Sodium/vanill a bit longer to finish their setup)
|
|
||||||
// int taskCount = renderLoadExecutor.getQueueSize();
|
|
||||||
// if (taskCount > 0)
|
|
||||||
// {
|
|
||||||
// return "Waiting for DH JIT compiling...";
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// initialLoadingComplete = true;
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+78
-6
@@ -10,6 +10,7 @@ import com.seibel.distanthorizons.core.util.TimerUtil;
|
|||||||
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
|
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
|
||||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@@ -49,6 +50,14 @@ public class RenderThreadTaskHandler
|
|||||||
|
|
||||||
|
|
||||||
private long nanoSinceTasksRun = System.nanoTime();
|
private long nanoSinceTasksRun = System.nanoTime();
|
||||||
|
private final boolean running;
|
||||||
|
|
||||||
|
private Thread renderThread;
|
||||||
|
/**
|
||||||
|
* the currently running {@link QueuedRunnable}
|
||||||
|
* will be null if nothing is running.
|
||||||
|
*/
|
||||||
|
private volatile @Nullable QueuedRunnable currentQueuedRunnable;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -57,7 +66,22 @@ public class RenderThreadTaskHandler
|
|||||||
//=============//
|
//=============//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
private RenderThreadTaskHandler() { TIMER.scheduleAtFixedRate(TimerUtil.createTimerTask(this::manualCleanupTick), MS_BETWEEN_CLEANUP_TICKS, MS_BETWEEN_CLEANUP_TICKS); }
|
private RenderThreadTaskHandler()
|
||||||
|
{
|
||||||
|
// we only want to run this when the client is available
|
||||||
|
IMinecraftSharedWrapper mcShared = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
|
||||||
|
if (!mcShared.isDedicatedServer())
|
||||||
|
{
|
||||||
|
LOGGER.debug("Starting ["+RenderThreadTaskHandler.class.getSimpleName()+"]...");
|
||||||
|
this.running = true;
|
||||||
|
TIMER.scheduleAtFixedRate(TimerUtil.createTimerTask(this::manualCleanupTick), MS_BETWEEN_CLEANUP_TICKS, MS_BETWEEN_CLEANUP_TICKS);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.running = false;
|
||||||
|
LOGGER.debug("Skipping ["+RenderThreadTaskHandler.class.getSimpleName()+"] startup due to running on a dedicated server.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
@@ -70,6 +94,13 @@ public class RenderThreadTaskHandler
|
|||||||
|
|
||||||
public void queueRunningOnRenderThread(String name, Runnable renderCall)
|
public void queueRunningOnRenderThread(String name, Runnable renderCall)
|
||||||
{
|
{
|
||||||
|
// don't queuing tasks if they'll never be run
|
||||||
|
if (!this.running)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// don't get the stacktrace on release to reduce GC pressure
|
// don't get the stacktrace on release to reduce GC pressure
|
||||||
StackTraceElement[] stackTrace = null;
|
StackTraceElement[] stackTrace = null;
|
||||||
if (ModInfo.IS_DEV_BUILD)
|
if (ModInfo.IS_DEV_BUILD)
|
||||||
@@ -116,12 +147,21 @@ public class RenderThreadTaskHandler
|
|||||||
long loopStartTimeNano = System.nanoTime();
|
long loopStartTimeNano = System.nanoTime();
|
||||||
this.nanoSinceTasksRun = loopStartTimeNano;
|
this.nanoSinceTasksRun = loopStartTimeNano;
|
||||||
|
|
||||||
|
|
||||||
|
if (this.renderThread == null)
|
||||||
|
{
|
||||||
|
this.renderThread = Thread.currentThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
QueuedRunnable runnable = RENDER_THREAD_RUNNABLE_QUEUE.poll();
|
QueuedRunnable runnable = RENDER_THREAD_RUNNABLE_QUEUE.poll();
|
||||||
while(runnable != null)
|
while(runnable != null)
|
||||||
{
|
{
|
||||||
long taskStartNano = System.nanoTime();
|
long taskStartNano = System.nanoTime();
|
||||||
|
|
||||||
|
this.currentQueuedRunnable = runnable;
|
||||||
runnable.run();
|
runnable.run();
|
||||||
|
this.currentQueuedRunnable = null;
|
||||||
|
|
||||||
// only try running for a limited amount of time to prevent lag spikes
|
// only try running for a limited amount of time to prevent lag spikes
|
||||||
long taskNano = System.nanoTime() - taskStartNano;
|
long taskNano = System.nanoTime() - taskStartNano;
|
||||||
@@ -179,8 +219,19 @@ public class RenderThreadTaskHandler
|
|||||||
// this means we could have GL jobs building up.
|
// this means we could have GL jobs building up.
|
||||||
// Run the queued tasks on MC's executor (hopefully this should always run,
|
// Run the queued tasks on MC's executor (hopefully this should always run,
|
||||||
// even if DH's render code isn't being hit).
|
// even if DH's render code isn't being hit).
|
||||||
IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
IMinecraftClientWrapper mcClient = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||||
MC.executeOnRenderThread(() -> this.runRenderThreadTasks(500 * 1_000_000L));
|
if (mcClient != null)
|
||||||
|
{
|
||||||
|
mcClient.executeOnRenderThread(() -> this.runRenderThreadTasks(500 * 1_000_000L));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// shouldn't happen, but just in case
|
||||||
|
|
||||||
|
// somehow the timer started when there wasn't a client wrapper
|
||||||
|
// this probably means the timer was started on a dedicated server
|
||||||
|
RATE_LIMITED_LOGGER.warn("["+RenderThreadTaskHandler.class.getSimpleName()+"] timer started when ["+IMinecraftClientWrapper.class.getSimpleName()+"] is null. This shouldn't happen but can likely be ignored.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
@@ -190,7 +241,7 @@ public class RenderThreadTaskHandler
|
|||||||
//===========//
|
//===========//
|
||||||
// debugging //
|
// debugging //
|
||||||
//===========//
|
//===========//
|
||||||
///region
|
//region
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* if tasks are currently queued the debug
|
* if tasks are currently queued the debug
|
||||||
@@ -246,7 +297,28 @@ public class RenderThreadTaskHandler
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
///endregion
|
|
||||||
|
/** Returns true if the currently running thread is being run by this handler */
|
||||||
|
public boolean isCurrentThread()
|
||||||
|
{
|
||||||
|
if (this.renderThread != null)
|
||||||
|
{
|
||||||
|
return Thread.currentThread() == this.renderThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldn't normally be needed, but can be used if this
|
||||||
|
// handler hasn't been run yet
|
||||||
|
return Thread.currentThread().getName().equals("Render thread");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only recommended to be used by the task that's currently being run.
|
||||||
|
* Use {@link RenderThreadTaskHandler#isCurrentThread()} to check. <br>
|
||||||
|
* Can be used to get stack traces for render thread tasks while they're being run.
|
||||||
|
*/
|
||||||
|
public @Nullable QueuedRunnable getCurrentlyRunningTask() { return this.currentQueuedRunnable; }
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -255,7 +327,7 @@ public class RenderThreadTaskHandler
|
|||||||
//================//
|
//================//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
private static class QueuedRunnable implements Runnable
|
public static class QueuedRunnable implements Runnable
|
||||||
{
|
{
|
||||||
/** used to easily track what's being done on the render thread */
|
/** used to easily track what's being done on the render thread */
|
||||||
public final String name;
|
public final String name;
|
||||||
|
|||||||
+40
-10
@@ -26,6 +26,7 @@ import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
|
|||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
|
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
|
||||||
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
|
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
|
||||||
import com.seibel.distanthorizons.core.render.RenderParams;
|
import com.seibel.distanthorizons.core.render.RenderParams;
|
||||||
@@ -36,6 +37,8 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccess
|
|||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.*;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.*;
|
||||||
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
|
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is where all the magic happens. <br>
|
* This is where all the magic happens. <br>
|
||||||
* This is where LODs are draw to the world.
|
* This is where LODs are draw to the world.
|
||||||
@@ -180,6 +183,24 @@ public class LodRenderer
|
|||||||
renderBufferHandler.buildRenderList(renderParams);
|
renderBufferHandler.buildRenderList(renderParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
boolean renderFog;
|
||||||
|
Boolean apiFogOverride = Config.Client.Advanced.Graphics.Fog.enableDhFog.getApiValue();
|
||||||
|
if (apiFogOverride != null)
|
||||||
|
{
|
||||||
|
// 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
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
@@ -207,8 +228,6 @@ public class LodRenderer
|
|||||||
// opaque LODs
|
// opaque LODs
|
||||||
profiler.popPush("LOD Opaque");
|
profiler.popPush("LOD Opaque");
|
||||||
|
|
||||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderPassEvent.class, renderParams);
|
|
||||||
|
|
||||||
this.renderTerrain(this.terrainRenderer, renderBufferHandler, renderParams, /*opaquePass*/ true, profiler);
|
this.renderTerrain(this.terrainRenderer, renderBufferHandler, renderParams, /*opaquePass*/ true, profiler);
|
||||||
|
|
||||||
// custom objects with SSAO
|
// custom objects with SSAO
|
||||||
@@ -249,9 +268,8 @@ public class LodRenderer
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fog
|
// fog
|
||||||
if (Config.Client.Advanced.Graphics.Fog.enableDhFog.get()
|
|
||||||
// this is done to fix issues with: underwater fog, blindness effect, etc.
|
if (renderFog)
|
||||||
|| renderParams.vanillaFogEnabled)
|
|
||||||
{
|
{
|
||||||
profiler.popPush("LOD Fog");
|
profiler.popPush("LOD Fog");
|
||||||
|
|
||||||
@@ -273,6 +291,22 @@ public class LodRenderer
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (Config.Client.Advanced.Debugging.PositionFinder.positionFinderEnable.get())
|
||||||
|
{
|
||||||
|
// can be used to find specific positions when debugging
|
||||||
|
this.debugWireframeRenderer.renderBox(new AbstractDebugWireframeRenderer.Box(
|
||||||
|
DhSectionPos.encode(
|
||||||
|
Config.Client.Advanced.Debugging.PositionFinder.positionFinderDetailLevel.get().byteValue(),
|
||||||
|
Config.Client.Advanced.Debugging.PositionFinder.positionFinderXPos.get(),
|
||||||
|
Config.Client.Advanced.Debugging.PositionFinder.positionFinderZPos.get()),
|
||||||
|
Config.Client.Advanced.Debugging.PositionFinder.positionFinderMinBlockY.get(),
|
||||||
|
Config.Client.Advanced.Debugging.PositionFinder.positionFinderMaxBlockY.get(),
|
||||||
|
Config.Client.Advanced.Debugging.PositionFinder.positionFinderMarginPercent.get(),
|
||||||
|
Color.GREEN
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=============================//
|
//=============================//
|
||||||
// Apply to the MC Framebuffer //
|
// Apply to the MC Framebuffer //
|
||||||
@@ -294,15 +328,11 @@ public class LodRenderer
|
|||||||
|
|
||||||
if (Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled)
|
if (Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled)
|
||||||
{
|
{
|
||||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderPassEvent.class, renderParams);
|
|
||||||
|
|
||||||
profiler.popPush("LOD Transparent");
|
profiler.popPush("LOD Transparent");
|
||||||
this.renderTerrain(this.terrainRenderer, renderBufferHandler, renderParams, /*opaquePass*/ false, profiler);
|
this.renderTerrain(this.terrainRenderer, renderBufferHandler, renderParams, /*opaquePass*/ false, profiler);
|
||||||
|
|
||||||
|
|
||||||
if (Config.Client.Advanced.Graphics.Fog.enableDhFog.get()
|
if (renderFog)
|
||||||
// this is done to fix issues with: underwater fog, blindness effect, etc.
|
|
||||||
|| renderParams.vanillaFogEnabled)
|
|
||||||
{
|
{
|
||||||
profiler.popPush("LOD Fog");
|
profiler.popPush("LOD Fog");
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ public class ExceptionUtil
|
|||||||
return throwable instanceof InterruptedException
|
return throwable instanceof InterruptedException
|
||||||
|| throwable instanceof UncheckedInterruptedException
|
|| throwable instanceof UncheckedInterruptedException
|
||||||
|| throwable instanceof RejectedExecutionException
|
|| throwable instanceof RejectedExecutionException
|
||||||
|
|| throwable instanceof CancellationException
|
||||||
|| throwable instanceof ClosedByInterruptException;
|
|| throwable instanceof ClosedByInterruptException;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,8 +38,8 @@ public class ExceptionUtil
|
|||||||
unwrapped instanceof CancellationException;
|
unwrapped instanceof CancellationException;
|
||||||
}
|
}
|
||||||
public static Throwable ensureUnwrap(Throwable t)
|
public static Throwable ensureUnwrap(Throwable t)
|
||||||
{
|
{ return t instanceof CompletionException ? ensureUnwrap(t.getCause()) : t; }
|
||||||
return t instanceof CompletionException ? ensureUnwrap(t.getCause()) : t;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+15
@@ -451,6 +451,21 @@ public class RenderDataPointReducingList extends AbstractPhantomArrayList
|
|||||||
this.setBigger(smaller, bigger);
|
this.setBigger(smaller, bigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (writeIndex == 0)
|
||||||
|
{
|
||||||
|
// if every data point in the list is NULL (0) the write index will be 0,
|
||||||
|
// and in order to prevent accessing index -1 below,
|
||||||
|
// setting the write index to 1 is needed.
|
||||||
|
|
||||||
|
// This shouldn't happen normally, however if the lod data is slightly malformed
|
||||||
|
// (which is specifically the case for the commonly shared wyncraft LODs)
|
||||||
|
// this check is needed.
|
||||||
|
// It would probably be best to fix the 6 or so NULL datapoints that are next
|
||||||
|
// to each other in the full data source, but for now this fix works.
|
||||||
|
|
||||||
|
writeIndex = 1;
|
||||||
|
}
|
||||||
|
|
||||||
this.smallest = this.sortingArray.getShort(0);
|
this.smallest = this.sortingArray.getShort(0);
|
||||||
this.biggest = this.sortingArray.getShort(writeIndex - 1);
|
this.biggest = this.sortingArray.getShort(writeIndex - 1);
|
||||||
this.setSmaller(this.getSmallest(), NULL);
|
this.setSmaller(this.getSmallest(), NULL);
|
||||||
|
|||||||
@@ -247,10 +247,7 @@ public class RenderDataPointUtil
|
|||||||
{
|
{
|
||||||
return "Y+:" + getYMax(dataPoint) +
|
return "Y+:" + getYMax(dataPoint) +
|
||||||
" Y-:" + getYMin(dataPoint) +
|
" Y-:" + getYMin(dataPoint) +
|
||||||
" argb:" + getAlpha(dataPoint) + " " +
|
" argb:" + getAlpha(dataPoint) + "," + getRed(dataPoint) + "," + getGreen(dataPoint) + "," + getBlue(dataPoint) +
|
||||||
getRed(dataPoint) + " " +
|
|
||||||
getGreen(dataPoint) + " " +
|
|
||||||
getBlue(dataPoint) +
|
|
||||||
" BL:" + getLightBlock(dataPoint) +
|
" BL:" + getLightBlock(dataPoint) +
|
||||||
" SL:" + getLightSky(dataPoint) +
|
" SL:" + getLightSky(dataPoint) +
|
||||||
" MAT:" + getBlockMaterialId(dataPoint) + "["+ EDhApiBlockMaterial.getFromIndex(getBlockMaterialId(dataPoint))+"]";
|
" MAT:" + getBlockMaterialId(dataPoint) + "["+ EDhApiBlockMaterial.getFromIndex(getBlockMaterialId(dataPoint))+"]";
|
||||||
|
|||||||
@@ -123,7 +123,16 @@ public class RenderUtil
|
|||||||
// At low render distances this hides the vanilla RD border
|
// At low render distances this hides the vanilla RD border
|
||||||
|
|
||||||
int chunkRenderDistance = MC_RENDER.getRenderDistance();
|
int chunkRenderDistance = MC_RENDER.getRenderDistance();
|
||||||
if (chunkRenderDistance <= 2)
|
|
||||||
|
if (IRIS_ACCESSOR != null
|
||||||
|
&& IRIS_ACCESSOR.isShaderPackInUse())
|
||||||
|
{
|
||||||
|
// shaders handle the near clip plane/overdraw differently, best to play it
|
||||||
|
// safe and have the plane really close otherwise
|
||||||
|
// there might be cutouts on the screen edges
|
||||||
|
overdraw = 0.2f;
|
||||||
|
}
|
||||||
|
else if (chunkRenderDistance <= 2)
|
||||||
{
|
{
|
||||||
overdraw = 0.2f;
|
overdraw = 0.2f;
|
||||||
}
|
}
|
||||||
@@ -154,7 +163,7 @@ public class RenderUtil
|
|||||||
|
|
||||||
if (Config.Client.Advanced.Graphics.Culling.reduceOverdrawWithFastMovement.get())
|
if (Config.Client.Advanced.Graphics.Culling.reduceOverdrawWithFastMovement.get())
|
||||||
{
|
{
|
||||||
double avgSpeed = ClientApi.INSTANCE.cameraSpeedRollingAverage.getAverage();
|
double avgSpeed = ClientApi.INSTANCE.getAvgCameraSpeed();
|
||||||
if (avgSpeed >= DynamicOverdraw.MIN_SPEED)
|
if (avgSpeed >= DynamicOverdraw.MIN_SPEED)
|
||||||
{
|
{
|
||||||
// if the player is moving fast enough,
|
// if the player is moving fast enough,
|
||||||
|
|||||||
+2
-43
@@ -331,7 +331,7 @@ public class PhantomArrayListPool
|
|||||||
if (pool.logGarbageCollectedStacks
|
if (pool.logGarbageCollectedStacks
|
||||||
&& checkout.allocationStackTrace != null) // stack trace shouldn't be null, but just in case
|
&& checkout.allocationStackTrace != null) // stack trace shouldn't be null, but just in case
|
||||||
{
|
{
|
||||||
putAndIncrementTrackingString(checkout.allocationStackTrace, allocationStackTraceCountPairList);
|
PhantomLoggingHelper.putAndIncrementTrackingString(checkout.allocationStackTrace, allocationStackTraceCountPairList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -363,18 +363,7 @@ public class PhantomArrayListPool
|
|||||||
// log stack traces if present
|
// log stack traces if present
|
||||||
if (pool.logGarbageCollectedStacks)
|
if (pool.logGarbageCollectedStacks)
|
||||||
{
|
{
|
||||||
// high numbers first
|
PhantomLoggingHelper.LogAllocationStackTracePairCounts(LOGGER, allocationStackTraceCountPairList);
|
||||||
allocationStackTraceCountPairList.sort((a, b) -> Integer.compare(b.second.get(), a.second.get()));
|
|
||||||
|
|
||||||
StringBuilder stringBuilder = new StringBuilder();
|
|
||||||
for (int j = 0; j < allocationStackTraceCountPairList.size(); j++)
|
|
||||||
{
|
|
||||||
int count = allocationStackTraceCountPairList.get(j).second.get();
|
|
||||||
String stack = allocationStackTraceCountPairList.get(j).first;
|
|
||||||
|
|
||||||
stringBuilder.append(count).append(". ").append(stack).append("\n");
|
|
||||||
}
|
|
||||||
LOGGER.warn("Stacks: ["+ allocationStackTraceCountPairList.size()+"]\n" + stringBuilder.toString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -389,36 +378,6 @@ public class PhantomArrayListPool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* This was separated out so it could be used for other string pair lists.
|
|
||||||
* James originally had an idea to add a shorter static string
|
|
||||||
* ID to each allocated {@link PhantomArrayListCheckout} as a simpler version of the stack trace,
|
|
||||||
* however it became a bit more difficult and messy than he wanted to deal with, so for now we just
|
|
||||||
* have the stack trace.
|
|
||||||
*/
|
|
||||||
private static void putAndIncrementTrackingString(
|
|
||||||
String key,
|
|
||||||
ArrayList<Pair<String, AtomicInteger>> allocationStackTraceCountPairList)
|
|
||||||
{
|
|
||||||
// sequential search, for the number of elements we're dealing with (less than 20)
|
|
||||||
// this should be sufficiently fast
|
|
||||||
boolean pairFound = false;
|
|
||||||
for (int i = 0; i < allocationStackTraceCountPairList.size(); i++)
|
|
||||||
{
|
|
||||||
Pair<String, AtomicInteger> possiblePair = allocationStackTraceCountPairList.get(i);
|
|
||||||
if (possiblePair.first.equals(key))
|
|
||||||
{
|
|
||||||
possiblePair.second.getAndIncrement();
|
|
||||||
pairFound = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pairFound)
|
|
||||||
{
|
|
||||||
allocationStackTraceCountPairList.add(new Pair<>(key, new AtomicInteger(1)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///endregion
|
///endregion
|
||||||
|
|
||||||
|
|||||||
+232
@@ -0,0 +1,232 @@
|
|||||||
|
package com.seibel.distanthorizons.core.util.objects.pooling;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
|
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
|
||||||
|
import com.seibel.distanthorizons.core.util.ThreadUtil;
|
||||||
|
import com.seibel.distanthorizons.core.util.objects.Pair;
|
||||||
|
|
||||||
|
import java.lang.ref.PhantomReference;
|
||||||
|
import java.lang.ref.Reference;
|
||||||
|
import java.lang.ref.ReferenceQueue;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public class PhantomLoggingHelper
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* This was separated out so it could be used for other string pair lists.
|
||||||
|
* James originally had an idea to add a shorter static string
|
||||||
|
* ID to each allocated {@link PhantomArrayListCheckout} as a simpler version of the stack trace,
|
||||||
|
* however it became a bit more difficult and messy than he wanted to deal with, so for now we just
|
||||||
|
* have the stack trace.
|
||||||
|
*/
|
||||||
|
public static void putAndIncrementTrackingString(
|
||||||
|
String key,
|
||||||
|
ArrayList<Pair<String, AtomicInteger>> allocationStackTraceCountPairList)
|
||||||
|
{
|
||||||
|
// sequential search, for the number of elements we're dealing with (less than 20)
|
||||||
|
// this should be sufficiently fast
|
||||||
|
boolean pairFound = false;
|
||||||
|
for (int i = 0; i < allocationStackTraceCountPairList.size(); i++)
|
||||||
|
{
|
||||||
|
Pair<String, AtomicInteger> possiblePair = allocationStackTraceCountPairList.get(i);
|
||||||
|
if (possiblePair.first.equals(key))
|
||||||
|
{
|
||||||
|
possiblePair.second.getAndIncrement();
|
||||||
|
pairFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pairFound)
|
||||||
|
{
|
||||||
|
allocationStackTraceCountPairList.add(new Pair<>(key, new AtomicInteger(1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LogAllocationStackTracePairCounts(DhLogger logger, ArrayList<Pair<String, AtomicInteger>> allocationStackTraceCountPairList)
|
||||||
|
{
|
||||||
|
// high numbers first
|
||||||
|
allocationStackTraceCountPairList.sort((a, b) -> Integer.compare(b.second.get(), a.second.get()));
|
||||||
|
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
for (int j = 0; j < allocationStackTraceCountPairList.size(); j++)
|
||||||
|
{
|
||||||
|
int count = allocationStackTraceCountPairList.get(j).second.get();
|
||||||
|
String stack = allocationStackTraceCountPairList.get(j).first;
|
||||||
|
|
||||||
|
stringBuilder.append(count).append(". ").append(stack).append("\n");
|
||||||
|
}
|
||||||
|
logger.warn("Stacks: ["+ allocationStackTraceCountPairList.size()+"]\n" + stringBuilder.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// helper classes //
|
||||||
|
//================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be quickly added to a {@link AutoCloseable} implementing
|
||||||
|
* class to confirm it's being properly closed.
|
||||||
|
*/
|
||||||
|
public static class BasicPhantomReference implements AutoCloseable
|
||||||
|
{
|
||||||
|
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
||||||
|
|
||||||
|
/** if enabled the number of GC'ed buffers will be logged */
|
||||||
|
private static final boolean LOG_PHANTOM_RECOVERY = true;
|
||||||
|
/**
|
||||||
|
* If enabled the GC'ed buffers allocation/upload stacks will be logged.
|
||||||
|
* Note: due to how the buffers are often run on the render thread,
|
||||||
|
* these stacks will likely only be of limited use.
|
||||||
|
* For more robust debugging it would likely be best to somehow track
|
||||||
|
* the stacks of where these calls are happening before they're queued
|
||||||
|
* for the render thread.
|
||||||
|
*/
|
||||||
|
private static final boolean LOG_PHANTOM_ALLOCATION_STACKS = true;
|
||||||
|
|
||||||
|
private static final int PHANTOM_REF_CHECK_TIME_IN_MS = 5 * 1000;
|
||||||
|
private static final ReferenceQueue<BasicPhantomReference> PHANTOM_REFERENCE_QUEUE = new ReferenceQueue<>();
|
||||||
|
private static final ConcurrentHashMap<PhantomReference<? extends BasicPhantomReference>, Class<?>> PHANTOM_TO_PARENT_CLASS = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private static final ThreadPoolExecutor CLEANUP_THREAD = ThreadUtil.makeSingleDaemonThreadPool("BasicPhantom Cleanup");
|
||||||
|
|
||||||
|
|
||||||
|
private final Class<?> parentClass;
|
||||||
|
private final PhantomReference<? extends BasicPhantomReference> phantomReference;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==============//
|
||||||
|
// constructors //
|
||||||
|
//==============//
|
||||||
|
//region
|
||||||
|
|
||||||
|
static { CLEANUP_THREAD.execute(() -> runPhantomReferenceCleanupLoop()); }
|
||||||
|
|
||||||
|
public BasicPhantomReference(Class<?> parentClass)
|
||||||
|
{
|
||||||
|
this.parentClass = parentClass;
|
||||||
|
this.phantomReference = new PhantomReference<>(this, PHANTOM_REFERENCE_QUEUE);
|
||||||
|
PHANTOM_TO_PARENT_CLASS.put(this.phantomReference, this.parentClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// base overrides //
|
||||||
|
//================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
this.phantomReference.clear();
|
||||||
|
PHANTOM_TO_PARENT_CLASS.remove(this.phantomReference);
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// static cleanup //
|
||||||
|
//================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
private static void runPhantomReferenceCleanupLoop()
|
||||||
|
{
|
||||||
|
// these arrays are stored here so they don't have to be re-allocated each loop
|
||||||
|
ArrayList<Pair<String, AtomicInteger>> allocationStackTraceCountPairList = new ArrayList<>();
|
||||||
|
ArrayList<Pair<String, AtomicInteger>> parentClassNameCountPairList = new ArrayList<>();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
allocationStackTraceCountPairList.clear();
|
||||||
|
parentClassNameCountPairList.clear();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Thread.sleep(PHANTOM_REF_CHECK_TIME_IN_MS);
|
||||||
|
}
|
||||||
|
catch (InterruptedException ignore) { }
|
||||||
|
|
||||||
|
int collectedCount = 0;
|
||||||
|
|
||||||
|
Reference<? extends BasicPhantomReference> phantomRef = PHANTOM_REFERENCE_QUEUE.poll();
|
||||||
|
while (phantomRef != null)
|
||||||
|
{
|
||||||
|
// destroy the buffer if it hasn't been cleared yet
|
||||||
|
Class<?> parentClass = PHANTOM_TO_PARENT_CLASS.remove((PhantomReference<? extends BasicPhantomReference>)phantomRef); // cast to make IntelliJ happy
|
||||||
|
{
|
||||||
|
String parentClassName = "NULL";
|
||||||
|
if (parentClass != null)
|
||||||
|
{
|
||||||
|
parentClassName = parentClass.getSimpleName();
|
||||||
|
}
|
||||||
|
|
||||||
|
PhantomLoggingHelper.putAndIncrementTrackingString(parentClassName, parentClassNameCountPairList);
|
||||||
|
//LOGGER.info("Phantom collected for class: [" + parentClassName + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//if (LOG_PHANTOM_ALLOCATION_STACKS) // stack trace shouldn't be null, but just in case
|
||||||
|
//{
|
||||||
|
// String stack = BUFFER_ID_TO_ALLOCATION_STRING.get(idRef);
|
||||||
|
// if (stack != null)
|
||||||
|
// {
|
||||||
|
// PhantomLoggingHelper.putAndIncrementTrackingString(stack, allocationStackTraceCountPairList);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
collectedCount++;
|
||||||
|
phantomRef = PHANTOM_REFERENCE_QUEUE.poll();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (LOG_PHANTOM_RECOVERY)
|
||||||
|
{
|
||||||
|
// we only want to log when something has been returned
|
||||||
|
if (collectedCount != 0)
|
||||||
|
{
|
||||||
|
LOGGER.warn("Phantoms collected: ["+ F3Screen.NUMBER_FORMAT.format(collectedCount)+"].");
|
||||||
|
|
||||||
|
PhantomLoggingHelper.LogAllocationStackTracePairCounts(LOGGER, parentClassNameCountPairList);
|
||||||
|
|
||||||
|
//// log stack traces if present
|
||||||
|
//if (LOG_PHANTOM_ALLOCATION_STACKS)
|
||||||
|
//{
|
||||||
|
// PhantomLoggingHelper.LogAllocationStackTracePairCounts(LOGGER, allocationStackTraceCountPairList);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LOGGER.error("Unexpected error in buffer cleanup thread: [" + e.getMessage() + "].", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+1
-1
@@ -325,7 +325,7 @@ public class QuadNode<T>
|
|||||||
|
|
||||||
public void deleteAllChildren() { this.deleteAllChildren(null); }
|
public void deleteAllChildren() { this.deleteAllChildren(null); }
|
||||||
/** @param removedItemConsumer is only fired for non-null nodes, however the value passed in may be null */
|
/** @param removedItemConsumer is only fired for non-null nodes, however the value passed in may be null */
|
||||||
public void deleteAllChildren(Consumer<? super T> removedItemConsumer)
|
public void deleteAllChildren(@Nullable Consumer<? super T> removedItemConsumer)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
|
|||||||
+120
-35
@@ -19,10 +19,12 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.core.util.objects.quadTree;
|
package com.seibel.distanthorizons.core.util.objects.quadTree;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
|
import com.seibel.distanthorizons.core.render.QuadTree.LodQuadTree;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||||
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
|
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
|
||||||
import com.seibel.distanthorizons.coreapi.util.MathUtil;
|
import com.seibel.distanthorizons.coreapi.util.MathUtil;
|
||||||
@@ -61,6 +63,11 @@ public class QuadTree<T>
|
|||||||
private final MovableGridRingList<QuadNode<T>> topRingList;
|
private final MovableGridRingList<QuadNode<T>> topRingList;
|
||||||
|
|
||||||
private DhBlockPos2D centerBlockPos;
|
private DhBlockPos2D centerBlockPos;
|
||||||
|
/**
|
||||||
|
* defines how many blocks the center needs to move in blocks
|
||||||
|
* before we check for out-of-bound nodes.
|
||||||
|
*/
|
||||||
|
private int blockDistanceForNodeClearing;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -74,10 +81,13 @@ public class QuadTree<T>
|
|||||||
*
|
*
|
||||||
* @param diameterInBlocks equivalent to the distance between the two opposing sides
|
* @param diameterInBlocks equivalent to the distance between the two opposing sides
|
||||||
*/
|
*/
|
||||||
public QuadTree(int diameterInBlocks, DhBlockPos2D centerBlockPos, byte treeLeafDetailLevel)
|
public QuadTree(
|
||||||
|
int diameterInBlocks, int blockDistanceForNodeClearing,
|
||||||
|
DhBlockPos2D centerBlockPos, byte treeLeafDetailLevel)
|
||||||
{
|
{
|
||||||
this.centerBlockPos = centerBlockPos;
|
this.centerBlockPos = centerBlockPos;
|
||||||
this.diameterInBlocks = diameterInBlocks;
|
this.diameterInBlocks = diameterInBlocks;
|
||||||
|
this.blockDistanceForNodeClearing = blockDistanceForNodeClearing;
|
||||||
|
|
||||||
this.treeLeafDetailLevel = treeLeafDetailLevel;
|
this.treeLeafDetailLevel = treeLeafDetailLevel;
|
||||||
// the min detail level must be greater than 0 (to prevent divide by 0 errors) and greater than the maximum detail level
|
// the min detail level must be greater than 0 (to prevent divide by 0 errors) and greater than the maximum detail level
|
||||||
@@ -130,18 +140,22 @@ public class QuadTree<T>
|
|||||||
public int leafNodeCount()
|
public int leafNodeCount()
|
||||||
{
|
{
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (QuadNode<T> node : this.topRingList)
|
for (QuadNode<T> rootNode : this.topRingList)
|
||||||
{
|
{
|
||||||
if (node == null)
|
if (rootNode == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterator<QuadNode<T>> leafNodeIterator = node.getLeafNodeIterator();
|
Iterator<QuadNode<T>> leafNodeIterator = rootNode.getLeafNodeIterator();
|
||||||
while (leafNodeIterator.hasNext())
|
while (leafNodeIterator.hasNext())
|
||||||
{
|
{
|
||||||
leafNodeIterator.next();
|
QuadNode<T> node = leafNodeIterator.next();
|
||||||
count++;
|
if (node != null
|
||||||
|
&& this.isSectionPosInBounds(node.sectionPos))
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,32 +250,32 @@ public class QuadTree<T>
|
|||||||
int ringListPosX = DhSectionPos.getX(rootPos);
|
int ringListPosX = DhSectionPos.getX(rootPos);
|
||||||
int ringListPosZ = DhSectionPos.getZ(rootPos);
|
int ringListPosZ = DhSectionPos.getZ(rootPos);
|
||||||
|
|
||||||
QuadNode<T> topQuadNode = this.topRingList.get(ringListPosX, ringListPosZ);
|
QuadNode<T> rootQuadNode = this.topRingList.get(ringListPosX, ringListPosZ);
|
||||||
if (topQuadNode == null)
|
if (rootQuadNode == null)
|
||||||
{
|
{
|
||||||
if (!setNewValue)
|
if (!setNewValue)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
topQuadNode = new QuadNode<T>(rootPos, this.treeLeafDetailLevel);
|
rootQuadNode = new QuadNode<T>(rootPos, this.treeLeafDetailLevel);
|
||||||
boolean successfullyAdded = this.topRingList.set(ringListPosX, ringListPosZ, topQuadNode);
|
boolean successfullyAdded = this.topRingList.set(ringListPosX, ringListPosZ, rootQuadNode);
|
||||||
if (!successfullyAdded)
|
if (!successfullyAdded)
|
||||||
{
|
{
|
||||||
LodUtil.assertNotReach("Failed to add top quadTree node at position: " + rootPos);
|
LodUtil.assertNotReach("Failed to add root quadTree node at position: ["+DhSectionPos.toString(rootPos)+"]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!DhSectionPos.contains(topQuadNode.sectionPos, pos))
|
if (!DhSectionPos.contains(rootQuadNode.sectionPos, pos))
|
||||||
{
|
{
|
||||||
LodUtil.assertNotReach("failed to get a root node that contains the input position: " + pos + " root node pos: " + topQuadNode.sectionPos);
|
LodUtil.assertNotReach("failed to get a root node that contains the input position: " + pos + " root node pos: " + rootQuadNode.sectionPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QuadNode<T> returnNode = topQuadNode.getNode(pos);
|
QuadNode<T> returnNode = rootQuadNode.getNode(pos);
|
||||||
if (setNewValue)
|
if (setNewValue)
|
||||||
{
|
{
|
||||||
topQuadNode.setValue(pos, newValue);
|
rootQuadNode.setValue(pos, newValue);
|
||||||
}
|
}
|
||||||
return returnNode;
|
return returnNode;
|
||||||
}
|
}
|
||||||
@@ -354,32 +368,103 @@ public class QuadTree<T>
|
|||||||
//================//
|
//================//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
public void setCenterBlockPos(DhBlockPos2D newCenterPos) { this.setCenterBlockPos(newCenterPos, null); }
|
public void setCenterBlockPos(DhBlockPos2D newCenterPos) { this.setCenterBlockPos(newCenterPos, null, null); }
|
||||||
public void setCenterBlockPos(DhBlockPos2D newCenterPos, Consumer<? super T> removedItemConsumer)
|
/**
|
||||||
|
* @param removedConsumer fired when a root node is completely removed from the underlying data structure
|
||||||
|
* @param mutateOutOfBoundConsumer fired when a child node is out of bounds, but not removed from the underlying data structure
|
||||||
|
*/
|
||||||
|
public void setCenterBlockPos(
|
||||||
|
DhBlockPos2D newCenterPos,
|
||||||
|
@Nullable Consumer<? super T> removedConsumer,
|
||||||
|
@Nullable Consumer<? super T> mutateOutOfBoundConsumer)
|
||||||
{
|
{
|
||||||
this.centerBlockPos = newCenterPos;
|
// did we move significantly?
|
||||||
|
boolean ringListMoved = false;
|
||||||
MovableGridRingList.Pos2D expectedCenterPos = new MovableGridRingList.Pos2D(
|
int newCenterPosX = BitShiftUtil.divideByPowerOfTwo(newCenterPos.x, this.treeRootDetailLevel);
|
||||||
BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.x, this.treeRootDetailLevel),
|
int newCenterPosZ = BitShiftUtil.divideByPowerOfTwo(newCenterPos.z, this.treeRootDetailLevel);
|
||||||
BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.z, this.treeRootDetailLevel));
|
if (this.topRingList.getCenter().getX() != newCenterPosX
|
||||||
|
|| this.topRingList.getCenter().getY() != newCenterPosZ)
|
||||||
if (this.topRingList.getCenter().equals(expectedCenterPos))
|
|
||||||
{
|
{
|
||||||
// tree doesn't need to be moved
|
ringListMoved = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// did we move a little bit?
|
||||||
|
boolean recalculateOutOfBoundNodes = false;
|
||||||
|
int centerBlockDistance = this.centerBlockPos.manhattanDist(newCenterPos);
|
||||||
|
if (centerBlockDistance >= this.blockDistanceForNodeClearing)
|
||||||
|
{
|
||||||
|
recalculateOutOfBoundNodes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ringListMoved
|
||||||
|
&& !recalculateOutOfBoundNodes)
|
||||||
|
{
|
||||||
|
// the tree didn't move enough that we need
|
||||||
|
// to re-calculate anything
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// remove out of bounds root nodes
|
|
||||||
this.topRingList.moveTo(expectedCenterPos.getX(), expectedCenterPos.getY(), (quadNode) ->
|
this.centerBlockPos = newCenterPos;
|
||||||
|
|
||||||
|
// remove out of bound root nodes
|
||||||
|
this.topRingList.moveTo(newCenterPosX, newCenterPosZ, (quadNode) ->
|
||||||
{
|
{
|
||||||
if (quadNode != null && removedItemConsumer != null)
|
if (quadNode != null)
|
||||||
{
|
{
|
||||||
quadNode.deleteAllChildren(removedItemConsumer);
|
quadNode.deleteAllChildren(removedConsumer);
|
||||||
|
|
||||||
removedItemConsumer.accept(quadNode.value);
|
if (removedConsumer != null)
|
||||||
|
{
|
||||||
|
removedConsumer.accept(quadNode.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// mutate out of bound child nodes
|
||||||
|
this.topRingList.forEach((rootNode) ->
|
||||||
|
{
|
||||||
|
this.mutateOutOfBoundChildNodes(rootNode, mutateOutOfBoundConsumer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* we don't want to actually remove nodes or node data
|
||||||
|
* since that can cause the {@link LodQuadTree} to
|
||||||
|
* flash low-detail LODs when moving into previously-loaded
|
||||||
|
* LODs, which is really disorienting.
|
||||||
|
*/
|
||||||
|
private void mutateOutOfBoundChildNodes(@Nullable QuadNode<T> quadNode, @Nullable Consumer<? super T> mutateOutOfBoundConsumer)
|
||||||
|
{
|
||||||
|
// nodes shouldn't be null, but just in case
|
||||||
|
if (quadNode == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// go over each child node
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
QuadNode<T> childNode = quadNode.getChildByIndex(i);
|
||||||
|
if (childNode == null
|
||||||
|
|| childNode.value == null)
|
||||||
|
{
|
||||||
|
// no need to go any deeper if this node is already empty
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mutate nodes from the bottom up
|
||||||
|
this.mutateOutOfBoundChildNodes(childNode, mutateOutOfBoundConsumer);
|
||||||
|
|
||||||
|
// mutate this node if out of bounds
|
||||||
|
if (!this.isSectionPosInBounds(childNode.sectionPos))
|
||||||
|
{
|
||||||
|
if (mutateOutOfBoundConsumer != null)
|
||||||
|
{
|
||||||
|
mutateOutOfBoundConsumer.accept(childNode.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final DhBlockPos2D getCenterBlockPos() { return this.centerBlockPos; }
|
public final DhBlockPos2D getCenterBlockPos() { return this.centerBlockPos; }
|
||||||
@@ -484,7 +569,7 @@ public class QuadTree<T>
|
|||||||
|
|
||||||
private class QuadTreeNodeIterator implements Iterator<QuadNode<T>>
|
private class QuadTreeNodeIterator implements Iterator<QuadNode<T>>
|
||||||
{
|
{
|
||||||
private final QuadTreeRootPosIterator rootNodeIterator;
|
private final QuadTreeRootPosIterator rootNodePosIterator;
|
||||||
private Iterator<QuadNode<T>> currentNodeIterator;
|
private Iterator<QuadNode<T>> currentNodeIterator;
|
||||||
|
|
||||||
private QuadNode<T> lastNode = null;
|
private QuadNode<T> lastNode = null;
|
||||||
@@ -497,7 +582,7 @@ public class QuadTree<T>
|
|||||||
|
|
||||||
public QuadTreeNodeIterator(boolean onlyReturnLeaves, @Nullable INodeIteratorStoppingFunc<T> stopIteratingFunc)
|
public QuadTreeNodeIterator(boolean onlyReturnLeaves, @Nullable INodeIteratorStoppingFunc<T> stopIteratingFunc)
|
||||||
{
|
{
|
||||||
this.rootNodeIterator = new QuadTreeRootPosIterator(false, stopIteratingFunc);
|
this.rootNodePosIterator = new QuadTreeRootPosIterator(false, stopIteratingFunc);
|
||||||
this.onlyReturnLeaves = onlyReturnLeaves;
|
this.onlyReturnLeaves = onlyReturnLeaves;
|
||||||
|
|
||||||
this.stopIteratingFunc = stopIteratingFunc;
|
this.stopIteratingFunc = stopIteratingFunc;
|
||||||
@@ -508,7 +593,7 @@ public class QuadTree<T>
|
|||||||
@Override
|
@Override
|
||||||
public boolean hasNext()
|
public boolean hasNext()
|
||||||
{
|
{
|
||||||
if (!this.rootNodeIterator.hasNext()
|
if (!this.rootNodePosIterator.hasNext()
|
||||||
&& this.currentNodeIterator != null
|
&& this.currentNodeIterator != null
|
||||||
&& !this.currentNodeIterator.hasNext())
|
&& !this.currentNodeIterator.hasNext())
|
||||||
{
|
{
|
||||||
@@ -544,9 +629,9 @@ public class QuadTree<T>
|
|||||||
{
|
{
|
||||||
Iterator<QuadNode<T>> nodeIterator = null;
|
Iterator<QuadNode<T>> nodeIterator = null;
|
||||||
while ((nodeIterator == null || !nodeIterator.hasNext())
|
while ((nodeIterator == null || !nodeIterator.hasNext())
|
||||||
&& this.rootNodeIterator.hasNext())
|
&& this.rootNodePosIterator.hasNext())
|
||||||
{
|
{
|
||||||
long sectionPos = this.rootNodeIterator.nextLong();
|
long sectionPos = this.rootNodePosIterator.nextLong();
|
||||||
|
|
||||||
// try-get to prevent concurrency errors if the tree is being moved while we walk through it
|
// try-get to prevent concurrency errors if the tree is being moved while we walk through it
|
||||||
QuadNode<T> rootNode = QuadTree.this.tryGetNode(sectionPos);
|
QuadNode<T> rootNode = QuadTree.this.tryGetNode(sectionPos);
|
||||||
|
|||||||
+8
-1
@@ -73,10 +73,17 @@ public class RateLimitedThreadPoolExecutor extends ThreadPoolExecutor
|
|||||||
{
|
{
|
||||||
super.afterExecute(runnable, throwable);
|
super.afterExecute(runnable, throwable);
|
||||||
|
|
||||||
|
double ratio = this.runTimeRatioConfig.get();
|
||||||
|
if (ratio >= 1.0)
|
||||||
|
{
|
||||||
|
// Avoid sleeping for 0 time
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
long runTime = System.nanoTime() - this.runStartTime.get();
|
long runTime = System.nanoTime() - this.runStartTime.get();
|
||||||
Thread.sleep(TimeUnit.NANOSECONDS.toMillis((long) (runTime / this.runTimeRatioConfig.get() - runTime)));
|
Thread.sleep(TimeUnit.NANOSECONDS.toMillis((long) (runTime / ratio - runTime)));
|
||||||
}
|
}
|
||||||
catch (InterruptedException ignore)
|
catch (InterruptedException ignore)
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -170,7 +170,7 @@ public class ThreadPoolUtil
|
|||||||
*/
|
*/
|
||||||
public static boolean worldGenThreadsCanRun()
|
public static boolean worldGenThreadsCanRun()
|
||||||
{
|
{
|
||||||
double cameraSpeed = ClientApi.INSTANCE.cameraSpeedRollingAverage.getAverage();
|
double cameraSpeed = ClientApi.INSTANCE.getAvgCameraSpeed();
|
||||||
// stop these threads if moving a little bit slower than max elytra speed
|
// stop these threads if moving a little bit slower than max elytra speed
|
||||||
double maxAllowedSpeed = (LodUtil.ROCKET_ELYTRA_SPEED_IN_BLOCKS_PER_SEC - 10.0);
|
double maxAllowedSpeed = (LodUtil.ROCKET_ELYTRA_SPEED_IN_BLOCKS_PER_SEC - 10.0);
|
||||||
if (cameraSpeed > maxAllowedSpeed)
|
if (cameraSpeed > maxAllowedSpeed)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.seibel.distanthorizons.core.world;
|
package com.seibel.distanthorizons.core.world;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelUnloadEvent;
|
||||||
import com.seibel.distanthorizons.core.file.structure.LocalSaveStructure;
|
import com.seibel.distanthorizons.core.file.structure.LocalSaveStructure;
|
||||||
import com.seibel.distanthorizons.core.level.AbstractDhServerLevel;
|
import com.seibel.distanthorizons.core.level.AbstractDhServerLevel;
|
||||||
import com.seibel.distanthorizons.core.level.IDhLevel;
|
import com.seibel.distanthorizons.core.level.IDhLevel;
|
||||||
@@ -8,6 +9,7 @@ import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManag
|
|||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
|
||||||
|
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -138,6 +140,7 @@ public abstract class AbstractDhServerWorld<TDhServerLevel extends AbstractDhSer
|
|||||||
if (serverLevelWrapper != null)
|
if (serverLevelWrapper != null)
|
||||||
{
|
{
|
||||||
serverLevelWrapper.onUnload();
|
serverLevelWrapper.onUnload();
|
||||||
|
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(serverLevelWrapper));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ public abstract class AbstractDhWorld implements IDhWorld, Closeable
|
|||||||
String levelCountStr = F3Screen.NUMBER_FORMAT.format(this.getLoadedLevelCount());
|
String levelCountStr = F3Screen.NUMBER_FORMAT.format(this.getLoadedLevelCount());
|
||||||
|
|
||||||
String readOnlyStr = "";
|
String readOnlyStr = "";
|
||||||
if (DhApiWorldProxy.INSTANCE.getReadOnly())
|
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||||
{
|
{
|
||||||
readOnlyStr += " - ReadOnly";
|
readOnlyStr += " - ReadOnly";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,6 +113,23 @@ public class DhApiWorldProxy implements IDhApiWorldProxy
|
|||||||
return this.isReadOnly;
|
return this.isReadOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns false if no world is loaded.
|
||||||
|
* Can be used in places where the world state might be a bit more questionable
|
||||||
|
* without having to worry about the {@link IllegalStateException} thrown by
|
||||||
|
* {@link DhApiWorldProxy#getReadOnly()}
|
||||||
|
*/
|
||||||
|
public boolean tryGetReadOnly()
|
||||||
|
{
|
||||||
|
if (SharedApi.getAbstractDhWorld() == null)
|
||||||
|
{
|
||||||
|
// no world is loaded, use the default value for the next world
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.isReadOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//================//
|
//================//
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.core.world;
|
package com.seibel.distanthorizons.core.world;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelLoadEvent;
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelUnloadEvent;
|
||||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||||
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
||||||
import com.seibel.distanthorizons.core.level.DhClientServerLevel;
|
import com.seibel.distanthorizons.core.level.DhClientServerLevel;
|
||||||
@@ -27,6 +29,7 @@ import com.seibel.distanthorizons.core.util.LodUtil;
|
|||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
|
||||||
|
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -34,7 +37,7 @@ import java.util.concurrent.CompletableFuture;
|
|||||||
|
|
||||||
public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLevel> implements IDhClientWorld
|
public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLevel> implements IDhClientWorld
|
||||||
{
|
{
|
||||||
private final Set<DhClientServerLevel> dhLevels = Collections.synchronizedSet(new HashSet<>());
|
private final Map<DhClientServerLevel, Set<ILevelWrapper>> dhLevels = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
|
||||||
private final Timer clientTickTimer = TimerUtil.CreateTimer("ClientTickTimer");
|
private final Timer clientTickTimer = TimerUtil.CreateTimer("ClientTickTimer");
|
||||||
|
|
||||||
@@ -54,7 +57,7 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
|
|||||||
@Override
|
@Override
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
DhClientServerWorld.this.dhLevels.forEach(DhClientServerLevel::clientTick);
|
DhClientServerWorld.this.dhLevels.keySet().forEach(DhClientServerLevel::clientTick);
|
||||||
}
|
}
|
||||||
}, 0, IDhClientWorld.TICK_RATE_IN_MS);
|
}, 0, IDhClientWorld.TICK_RATE_IN_MS);
|
||||||
}
|
}
|
||||||
@@ -75,7 +78,8 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
DhClientServerLevel level = new DhClientServerLevel(this.saveStructure, (IServerLevelWrapper) levelWrapper, this.getServerPlayerStateManager());
|
DhClientServerLevel level = new DhClientServerLevel(this.saveStructure, (IServerLevelWrapper) levelWrapper, this.getServerPlayerStateManager());
|
||||||
this.dhLevels.add(level);
|
this.dhLevels.computeIfAbsent(level, k -> Collections.synchronizedSet(new HashSet<>()));
|
||||||
|
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(wrapper));
|
||||||
return level;
|
return level;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -92,6 +96,10 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (wrapper instanceof IClientLevelWrapper)
|
||||||
|
{
|
||||||
|
((IClientLevelWrapper) wrapper).markAccessed();
|
||||||
|
}
|
||||||
return this.dhLevelByLevelWrapper.computeIfAbsent(wrapper, (levelWrapper) ->
|
return this.dhLevelByLevelWrapper.computeIfAbsent(wrapper, (levelWrapper) ->
|
||||||
{
|
{
|
||||||
IClientLevelWrapper clientLevelWrapper = (IClientLevelWrapper) levelWrapper;
|
IClientLevelWrapper clientLevelWrapper = (IClientLevelWrapper) levelWrapper;
|
||||||
@@ -111,13 +119,14 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
|
|||||||
|
|
||||||
level.startRenderer();
|
level.startRenderer();
|
||||||
clientLevelWrapper.setDhLevel(level);
|
clientLevelWrapper.setDhLevel(level);
|
||||||
|
dhLevels.get(level).add(wrapper);
|
||||||
return level;
|
return level;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void unloadLevel(@NotNull ILevelWrapper wrapper)
|
public boolean unloadLevel(@NotNull ILevelWrapper wrapper)
|
||||||
{
|
{
|
||||||
if (this.dhLevelByLevelWrapper.containsKey(wrapper))
|
if (this.dhLevelByLevelWrapper.containsKey(wrapper))
|
||||||
{
|
{
|
||||||
@@ -135,9 +144,18 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
|
|||||||
// If the level wrapper is a Client Level Wrapper, then that means the client side leaves the level,
|
// If the level wrapper is a Client Level Wrapper, then that means the client side leaves the level,
|
||||||
// but note that the server side still has the level loaded. So, we don't want to unload the level,
|
// but note that the server side still has the level loaded. So, we don't want to unload the level,
|
||||||
// we just want to stop rendering it.
|
// we just want to stop rendering it.
|
||||||
this.dhLevelByLevelWrapper.remove(wrapper).stopRenderer(); // Ignore resource warning. The level obj is referenced elsewhere.
|
DhClientServerLevel level = this.dhLevelByLevelWrapper.remove(wrapper); // Ignore resource warning. The level obj is referenced elsewhere.
|
||||||
|
Set<ILevelWrapper> wrappers = dhLevels.get(level);
|
||||||
|
if (wrappers != null) wrappers.remove(wrapper);
|
||||||
|
if ((wrappers == null || wrappers.isEmpty()) && level.isRendering()) {
|
||||||
|
level.stopRenderer();
|
||||||
|
}
|
||||||
|
wrapper.onUnload(); // We still want to unload the wrapper though.
|
||||||
}
|
}
|
||||||
|
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(wrapper));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -155,13 +173,21 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
|
|||||||
synchronized (this.dhLevels)
|
synchronized (this.dhLevels)
|
||||||
{
|
{
|
||||||
// close each level
|
// close each level
|
||||||
for (DhClientServerLevel level : this.dhLevels)
|
for (DhClientServerLevel level : this.dhLevels.keySet())
|
||||||
{
|
{
|
||||||
// level wrapper shouldn't be null, but just in case
|
// level wrapper shouldn't be null, but just in case
|
||||||
IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper();
|
IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper();
|
||||||
if (serverLevelWrapper != null)
|
if (serverLevelWrapper != null)
|
||||||
{
|
{
|
||||||
serverLevelWrapper.onUnload();
|
serverLevelWrapper.onUnload();
|
||||||
|
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(serverLevelWrapper));
|
||||||
|
}
|
||||||
|
|
||||||
|
IClientLevelWrapper clientLevelWrapper = level.getClientLevelWrapper();
|
||||||
|
if (clientLevelWrapper != null)
|
||||||
|
{
|
||||||
|
clientLevelWrapper.onUnload();
|
||||||
|
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(clientLevelWrapper));
|
||||||
}
|
}
|
||||||
|
|
||||||
// close levels asynchronously to speed up
|
// close levels asynchronously to speed up
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.core.world;
|
package com.seibel.distanthorizons.core.world;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelLoadEvent;
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelUnloadEvent;
|
||||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||||
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
||||||
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
|
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
|
||||||
@@ -28,18 +30,17 @@ import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState;
|
|||||||
import com.seibel.distanthorizons.core.util.TimerUtil;
|
import com.seibel.distanthorizons.core.util.TimerUtil;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||||
|
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Timer;
|
|
||||||
import java.util.TimerTask;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
|
public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
|
||||||
{
|
{
|
||||||
private final ConcurrentHashMap<IClientLevelWrapper, DhClientLevel> levels;
|
private final ConcurrentHashMap<String, DhClientLevel> levels;
|
||||||
|
private final Map<String, Set<IClientLevelWrapper>> levelWrappers = new ConcurrentHashMap<>();
|
||||||
public final ClientOnlySaveStructure saveStructure;
|
public final ClientOnlySaveStructure saveStructure;
|
||||||
public final ClientNetworkState networkState = new ClientNetworkState();
|
public final ClientNetworkState networkState = new ClientNetworkState();
|
||||||
|
|
||||||
@@ -76,6 +77,32 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
|
|||||||
// methods //
|
// methods //
|
||||||
//=========//
|
//=========//
|
||||||
|
|
||||||
|
private DhClientLevel createClientLevel(@NotNull IClientLevelWrapper clientLevelWrapper) {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!ClientApi.INSTANCE.canLoadClientLevel(clientLevelWrapper))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
DhClientLevel level = new DhClientLevel(this.saveStructure, clientLevelWrapper, this.networkState);
|
||||||
|
levelWrappers.computeIfAbsent(clientLevelWrapper.getDhIdentifier(), k -> Collections.synchronizedSet(new HashSet<>())).add(clientLevelWrapper);
|
||||||
|
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(clientLevelWrapper));
|
||||||
|
ClientApi.INSTANCE.loadWaitingChunksForLevel(clientLevelWrapper);
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LOGGER.fatal("Failed to load client level, error: ["+e.getMessage()+"].", e);
|
||||||
|
|
||||||
|
ClientApi.INSTANCE.showChatMessageNextFrame(
|
||||||
|
MinecraftTextFormat.RED + "Distant Horizons: Client level loading failed." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
|
||||||
|
"Unable to load level ["+clientLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DhClientLevel getOrLoadLevel(@NotNull ILevelWrapper wrapper)
|
public DhClientLevel getOrLoadLevel(@NotNull ILevelWrapper wrapper)
|
||||||
{
|
{
|
||||||
@@ -83,25 +110,20 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
IClientLevelWrapper clientLevelWrapper = (IClientLevelWrapper) wrapper;
|
||||||
return this.levels.computeIfAbsent((IClientLevelWrapper) wrapper,
|
clientLevelWrapper.markAccessed();
|
||||||
(clientLevelWrapper) ->
|
DhClientLevel storedLevel = this.levels.computeIfAbsent(wrapper.getDhIdentifier(),
|
||||||
|
(key) -> createClientLevel(clientLevelWrapper)
|
||||||
|
);
|
||||||
|
if (storedLevel != null && storedLevel.getClientLevelWrapper() != wrapper) {
|
||||||
|
unloadLevel(storedLevel.getLevelWrapper());
|
||||||
|
storedLevel = createClientLevel(clientLevelWrapper);
|
||||||
|
if (storedLevel != null)
|
||||||
{
|
{
|
||||||
try
|
this.levels.put(wrapper.getDhIdentifier(), storedLevel);
|
||||||
{
|
}
|
||||||
return new DhClientLevel(this.saveStructure, clientLevelWrapper, this.networkState);
|
}
|
||||||
}
|
return storedLevel;
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
LOGGER.fatal("Failed to load client level, error: ["+e.getMessage()+"].", e);
|
|
||||||
|
|
||||||
ClientApi.INSTANCE.showChatMessageNextFrame(
|
|
||||||
MinecraftTextFormat.RED + "Distant Horizons: Client level loading failed." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
|
|
||||||
"Unable to load level ["+clientLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -112,7 +134,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.levels.get(wrapper);
|
return this.levels.get(wrapper.getDhIdentifier());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -121,19 +143,26 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
|
|||||||
public int getLoadedLevelCount() { return this.levels.size(); }
|
public int getLoadedLevelCount() { return this.levels.size(); }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void unloadLevel(@NotNull ILevelWrapper wrapper)
|
public boolean unloadLevel(@NotNull ILevelWrapper wrapper)
|
||||||
{
|
{
|
||||||
if (!(wrapper instanceof IClientLevelWrapper))
|
if (!(wrapper instanceof IClientLevelWrapper))
|
||||||
{
|
{
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.levels.containsKey(wrapper))
|
if (this.levels.containsKey(wrapper.getDhIdentifier()))
|
||||||
{
|
{
|
||||||
LOGGER.info("Unloading level " + this.levels.get(wrapper));
|
LOGGER.info("Unloading level " + this.levels.get(wrapper.getDhIdentifier()));
|
||||||
wrapper.onUnload();
|
wrapper.onUnload();
|
||||||
this.levels.remove(wrapper).close();
|
Set<IClientLevelWrapper> wrappers = this.levelWrappers.get(wrapper.getDhIdentifier());
|
||||||
|
wrappers.remove(wrapper);
|
||||||
|
if (wrappers.isEmpty()) {
|
||||||
|
this.levels.remove(wrapper.getDhIdentifier()).close();
|
||||||
|
}
|
||||||
|
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(wrapper));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -156,6 +185,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
|
|||||||
if (clientLevelWrapper != null)
|
if (clientLevelWrapper != null)
|
||||||
{
|
{
|
||||||
clientLevelWrapper.onUnload();
|
clientLevelWrapper.onUnload();
|
||||||
|
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(clientLevelWrapper));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -178,6 +208,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.levels.clear();
|
this.levels.clear();
|
||||||
|
this.levelWrappers.clear();
|
||||||
this.clientTickTimer.cancel();
|
this.clientTickTimer.cancel();
|
||||||
LOGGER.info("Closed DhWorld of type [" + this.environment + "].");
|
LOGGER.info("Closed DhWorld of type [" + this.environment + "].");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,12 +19,15 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.core.world;
|
package com.seibel.distanthorizons.core.world;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelLoadEvent;
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelUnloadEvent;
|
||||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||||
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
||||||
import com.seibel.distanthorizons.core.generation.PregenManager;
|
import com.seibel.distanthorizons.core.generation.PregenManager;
|
||||||
import com.seibel.distanthorizons.core.level.DhServerLevel;
|
import com.seibel.distanthorizons.core.level.DhServerLevel;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
|
||||||
|
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
@@ -64,7 +67,9 @@ public class DhServerWorld extends AbstractDhServerWorld<DhServerLevel>
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return new DhServerLevel(this.saveStructure, (IServerLevelWrapper) serverLevelWrapper, this.getServerPlayerStateManager());
|
DhServerLevel level = new DhServerLevel(this.saveStructure, (IServerLevelWrapper) serverLevelWrapper, this.getServerPlayerStateManager());
|
||||||
|
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(wrapper));
|
||||||
|
return level;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -80,19 +85,21 @@ public class DhServerWorld extends AbstractDhServerWorld<DhServerLevel>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void unloadLevel(@NotNull ILevelWrapper wrapper)
|
public boolean unloadLevel(@NotNull ILevelWrapper wrapper)
|
||||||
{
|
{
|
||||||
if (!(wrapper instanceof IServerLevelWrapper))
|
if (!(wrapper instanceof IServerLevelWrapper))
|
||||||
{
|
{
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.dhLevelByLevelWrapper.containsKey(wrapper))
|
if (this.dhLevelByLevelWrapper.containsKey(wrapper))
|
||||||
{
|
{
|
||||||
DhServerLevel level = this.dhLevelByLevelWrapper.get(wrapper);
|
|
||||||
wrapper.onUnload();
|
wrapper.onUnload();
|
||||||
this.dhLevelByLevelWrapper.remove(wrapper).close();
|
this.dhLevelByLevelWrapper.remove(wrapper).close();
|
||||||
|
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(wrapper));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -49,6 +49,6 @@ public interface IDhWorld extends Closeable
|
|||||||
Iterable<? extends IDhLevel> getAllLoadedLevels();
|
Iterable<? extends IDhLevel> getAllLoadedLevels();
|
||||||
int getLoadedLevelCount();
|
int getLoadedLevelCount();
|
||||||
|
|
||||||
void unloadLevel(@NotNull ILevelWrapper levelWrapper);
|
boolean unloadLevel(@NotNull ILevelWrapper levelWrapper);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
-2
@@ -96,8 +96,6 @@ public interface IMinecraftRenderWrapper extends IBindable
|
|||||||
@Nullable
|
@Nullable
|
||||||
ILightMapWrapper getLightmapWrapper(@NotNull ILevelWrapper level);
|
ILightMapWrapper getLightmapWrapper(@NotNull ILevelWrapper level);
|
||||||
|
|
||||||
float getShade(EDhDirection lodDirection);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.core.wrapperInterfaces.minecraft;
|
package com.seibel.distanthorizons.core.wrapperInterfaces.minecraft;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
|
||||||
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
|
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -31,6 +32,6 @@ public interface IMinecraftSharedWrapper extends IBindable
|
|||||||
|
|
||||||
int getPlayerCount();
|
int getPlayerCount();
|
||||||
|
|
||||||
|
IServerLevelWrapper getWrappedServerLevel(String levelKey);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the Distant Horizons mod
|
||||||
|
* licensed under the GNU LGPL v3 License.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 James Seibel
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
||||||
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
|
||||||
|
import com.seibel.distanthorizons.core.util.math.Vec3d;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public interface IImmersivePortalsAccessor extends IModAccessor
|
||||||
|
{
|
||||||
|
|
||||||
|
boolean isRenderingPortal();
|
||||||
|
|
||||||
|
boolean wasPortalRecentlyVisible();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
DhBlockPos getOriginalPlayerBlockPos();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
DhChunkPos getOriginalPlayerChunkPos();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
IClientLevelWrapper getOriginalClientLevelWrapper();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
Vec3d getOriginalCameraPos();
|
||||||
|
|
||||||
|
}
|
||||||
+369
@@ -0,0 +1,369 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the Distant Horizons mod
|
||||||
|
* licensed under the GNU LGPL v3 License.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 James Seibel
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.api.DhApi;
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBeforeRenderEvent;
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiCancelableEventParam;
|
||||||
|
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
|
||||||
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.MethodType;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public abstract class ImmersivePortalsAbstractAccessor implements IImmersivePortalsAccessor
|
||||||
|
{
|
||||||
|
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
||||||
|
|
||||||
|
private static Class<?> portalClass;
|
||||||
|
private static MethodHandle isRenderingMethodHandle;
|
||||||
|
private static Method shouldSkipRenderingPortalMethod;
|
||||||
|
private static MethodHandle getGlobalPortalsMethodHandle;
|
||||||
|
|
||||||
|
private static long lastPortalMsTime = -1;
|
||||||
|
private static boolean portalVisible = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=============//
|
||||||
|
// constructor //
|
||||||
|
//=============//
|
||||||
|
//region
|
||||||
|
|
||||||
|
public ImmersivePortalsAbstractAccessor()
|
||||||
|
{
|
||||||
|
LOGGER.info("Immersive Portals detected: some DH features will be disabled or may only partially function.");
|
||||||
|
|
||||||
|
BeforeRenderEvent event = new BeforeRenderEvent(this);
|
||||||
|
DhApi.events.bind(DhApiBeforeRenderEvent.class, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
//=====================//
|
||||||
|
// reflection handling //
|
||||||
|
//=====================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
private static Class<?> getPortalRenderingClass()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Class.forName("qouteall.imm_ptl.core.render.context_management.PortalRendering");
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException first)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Class.forName("com.qouteall.immersive_portals.render.context_management.PortalRendering"); // 1.16
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException second)
|
||||||
|
{
|
||||||
|
RuntimeException err = new RuntimeException(first);
|
||||||
|
err.addSuppressed(second);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Class<?> getPortalRendererClass()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Class.forName("qouteall.imm_ptl.core.render.renderer.PortalRenderer"); // 1.21+
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException first)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Class.forName("qouteall.imm_ptl.core.render.PortalRenderer");
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException second)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Class.forName("com.qouteall.immersive_portals.render.PortalRenderer"); // 1.16
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException third)
|
||||||
|
{
|
||||||
|
RuntimeException err = new RuntimeException(first);
|
||||||
|
err.addSuppressed(second);
|
||||||
|
err.addSuppressed(third);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Class<?> getPortalClass()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
portalClass = Class.forName("qouteall.imm_ptl.core.portal.Portal");
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException first)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
portalClass = Class.forName("com.qouteall.immersive_portals.portal.Portal"); // 1.16
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException second)
|
||||||
|
{
|
||||||
|
RuntimeException err = new RuntimeException(first);
|
||||||
|
err.addSuppressed(second);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return portalClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Class<?> getGlobalPortalStorageClass()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Class.forName("qouteall.imm_ptl.core.portal.global_portals.GlobalPortalStorage");
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException first)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Class.forName("com.qouteall.immersive_portals.McHelper"); // 1.16
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException second)
|
||||||
|
{
|
||||||
|
RuntimeException err = new RuntimeException(first);
|
||||||
|
err.addSuppressed(second);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Class<?> getIPCGlobalClass()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Class.forName("qouteall.imm_ptl.core.IPCGlobal");
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException first)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Class.forName("com.qouteall.immersive_portals.CGlobal"); // 1.16
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException second)
|
||||||
|
{
|
||||||
|
RuntimeException err = new RuntimeException(first);
|
||||||
|
err.addSuppressed(second);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean shouldSkipRenderingPortal(Object portal, Supplier<?> frustumSupplier)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (shouldSkipRenderingPortalMethod == null)
|
||||||
|
{
|
||||||
|
shouldSkipRenderingPortalMethod = getPortalRendererClass().getDeclaredMethod(
|
||||||
|
"shouldSkipRenderingPortal", getPortalClass(), Supplier.class
|
||||||
|
);
|
||||||
|
shouldSkipRenderingPortalMethod.setAccessible(true);
|
||||||
|
}
|
||||||
|
if (Modifier.isStatic(shouldSkipRenderingPortalMethod.getModifiers()))
|
||||||
|
{
|
||||||
|
return (boolean) shouldSkipRenderingPortalMethod.invoke(null, portal, frustumSupplier);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (boolean) shouldSkipRenderingPortalMethod.invoke(
|
||||||
|
getIPCGlobalClass().getField("renderer").get(null), portal, frustumSupplier
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Throwable e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Object getClientLevel();
|
||||||
|
|
||||||
|
protected abstract Class<?> getLevelClass();
|
||||||
|
|
||||||
|
protected abstract Iterable<?> getEntitiesForRendering();
|
||||||
|
|
||||||
|
protected abstract Supplier<?> getFrustumSupplier();
|
||||||
|
|
||||||
|
private List<?> getGlobalPortals(Object level)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (getGlobalPortalsMethodHandle == null)
|
||||||
|
{
|
||||||
|
getGlobalPortalsMethodHandle = MethodHandles.lookup().findStatic(
|
||||||
|
getGlobalPortalStorageClass(),
|
||||||
|
"getGlobalPortals", MethodType.methodType(List.class).appendParameterTypes(
|
||||||
|
getLevelClass()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (List<?>) getGlobalPortalsMethodHandle.invoke(level);
|
||||||
|
}
|
||||||
|
catch (Throwable e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//===========//
|
||||||
|
// overrides //
|
||||||
|
//===========//
|
||||||
|
//region
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRenderingPortal()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (isRenderingMethodHandle == null)
|
||||||
|
{
|
||||||
|
isRenderingMethodHandle = MethodHandles.lookup().findStatic(
|
||||||
|
getPortalRenderingClass(),
|
||||||
|
"isRendering", MethodType.methodType(Boolean.TYPE)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (boolean) isRenderingMethodHandle.invoke();
|
||||||
|
}
|
||||||
|
catch (Throwable e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean wasPortalRecentlyVisible()
|
||||||
|
{
|
||||||
|
// I did consider setting portalVisible to true whenever PortalRendering::isRendering was true instead,
|
||||||
|
// but that would still render fading immediately after startup before entering the portal at least once.
|
||||||
|
// This is more robust, but slightly worse for performance. Still, people can just turn fading off if they have issues.
|
||||||
|
boolean isPortalVisible = isPortalVisibleRightNow();
|
||||||
|
if (isPortalVisible)
|
||||||
|
{
|
||||||
|
lastPortalMsTime = System.currentTimeMillis();
|
||||||
|
portalVisible = true;
|
||||||
|
}
|
||||||
|
else if (portalVisible)
|
||||||
|
{
|
||||||
|
if (System.currentTimeMillis() - lastPortalMsTime > 1000)
|
||||||
|
{
|
||||||
|
portalVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simply checking portal visibility right now is not sufficient, that will still render the fading on top of the portal.
|
||||||
|
// Instead, we check if a portal was rendered during the last second or so.
|
||||||
|
return portalVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Essentially reimplements PortalRenderer::getPortalsToRender because it did not exist in 1.16. */
|
||||||
|
private boolean isPortalVisibleRightNow()
|
||||||
|
{
|
||||||
|
Supplier<?> frustumSupplier = getFrustumSupplier();
|
||||||
|
if (frustumSupplier == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Object portal : getGlobalPortals(getClientLevel()))
|
||||||
|
{
|
||||||
|
if (!shouldSkipRenderingPortal(portal, frustumSupplier))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Object entity : getEntitiesForRendering())
|
||||||
|
{
|
||||||
|
if (isPortalObject(entity)
|
||||||
|
&& !shouldSkipRenderingPortal(entity, frustumSupplier))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
private static boolean isPortalObject(Object object) { return getPortalClass().isInstance(object); }
|
||||||
|
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getModName() { return "Immersive Portals"; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=======//
|
||||||
|
// event //
|
||||||
|
//=======//
|
||||||
|
//region
|
||||||
|
|
||||||
|
private static class BeforeRenderEvent extends DhApiBeforeRenderEvent
|
||||||
|
{
|
||||||
|
@NotNull
|
||||||
|
private final IImmersivePortalsAccessor immersivePortals;
|
||||||
|
|
||||||
|
|
||||||
|
public BeforeRenderEvent(@NotNull IImmersivePortalsAccessor portalAccessor) { this.immersivePortals = portalAccessor; }
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeRender(DhApiCancelableEventParam<DhApiRenderParam> event)
|
||||||
|
{
|
||||||
|
// needed because otherwise DH doesn't render to the level anyway
|
||||||
|
// and will probably render the level the player is currently in instead
|
||||||
|
if (this.immersivePortals.isRenderingPortal())
|
||||||
|
{
|
||||||
|
event.cancelEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+5
-1
@@ -23,10 +23,14 @@ import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderRegist
|
|||||||
import com.seibel.distanthorizons.core.render.RenderParams;
|
import com.seibel.distanthorizons.core.render.RenderParams;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
|
||||||
|
|
||||||
public interface IDhGenericRenderer extends IDhApiCustomRenderRegister
|
public interface IDhGenericRenderer extends IDhApiCustomRenderRegister, AutoCloseable
|
||||||
{
|
{
|
||||||
void render(RenderParams renderEventParam, IProfilerWrapper profiler, boolean renderingWithSsao);
|
void render(RenderParams renderEventParam, IProfilerWrapper profiler, boolean renderingWithSsao);
|
||||||
|
|
||||||
String getVboRenderDebugMenuString();
|
String getVboRenderDebugMenuString();
|
||||||
|
|
||||||
|
@Override void close(); // override to remove "throws exception"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
@@ -20,6 +20,7 @@
|
|||||||
package com.seibel.distanthorizons.core.wrapperInterfaces.world;
|
package com.seibel.distanthorizons.core.wrapperInterfaces.world;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||||
|
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
||||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@@ -29,6 +30,9 @@ import java.awt.*;
|
|||||||
public interface IClientLevelWrapper extends ILevelWrapper
|
public interface IClientLevelWrapper extends ILevelWrapper
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/** used to track when this level was last used for Immersive portals support */
|
||||||
|
void markAccessed();
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
IServerLevelWrapper tryGetServerSideWrapper();
|
IServerLevelWrapper tryGetServerSideWrapper();
|
||||||
|
|
||||||
@@ -39,4 +43,6 @@ public interface IClientLevelWrapper extends ILevelWrapper
|
|||||||
|
|
||||||
Color getCloudColor(float tickDelta);
|
Color getCloudColor(float tickDelta);
|
||||||
|
|
||||||
|
float getShade(EDhDirection lodDirection);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,10 @@
|
|||||||
"API LOCK",
|
"API LOCK",
|
||||||
"distanthorizons.general.disabledByApi.@tooltip":
|
"distanthorizons.general.disabledByApi.@tooltip":
|
||||||
"This option is controlled by another mod via DH's API \nso it cannot be changed via the UI or config file.",
|
"This option is controlled by another mod via DH's API \nso it cannot be changed via the UI or config file.",
|
||||||
|
"distanthorizons.general.unsupportedMcVersion":
|
||||||
|
"VER LOCK",
|
||||||
|
"distanthorizons.general.unsupportedMcVersion.@tooltip":
|
||||||
|
"DH doesn't support changing this option on this version of Minecraft. \nAny config file or API set values will be ignored.",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -188,11 +191,11 @@
|
|||||||
"If true non terrain objects will be rendered in DH's terrain. \nThis includes beacon beams and clouds.",
|
"If true non terrain objects will be rendered in DH's terrain. \nThis includes beacon beams and clouds.",
|
||||||
"distanthorizons.config.client.advanced.graphics.genericRendering.enableBeaconRendering":
|
"distanthorizons.config.client.advanced.graphics.genericRendering.enableBeaconRendering":
|
||||||
"Enable Beacon Rendering",
|
"Enable Beacon Rendering",
|
||||||
"distanthorizons.config.client.advanced.graphics.genericRendering.beaconRenderHeight":
|
"distanthorizons.config.client.advanced.graphics.genericRendering.beaconRenderHeight":
|
||||||
"Beacon render height",
|
"Beacon render height",
|
||||||
"distanthorizons.config.client.advanced.graphics.genericRendering.beaconRenderHeight.@tooltip":
|
"distanthorizons.config.client.advanced.graphics.genericRendering.beaconRenderHeight.@tooltip":
|
||||||
"Sets the maximum height at which beacons will render. \nThis will only affect new beacons coming into LOD render distance. \nBeacons currently visible in LOD chunks will not be affected.",
|
"Sets the maximum height at which beacons will render. \nThis will only affect new beacons coming into LOD render distance. \nBeacons currently visible in LOD chunks will not be affected.",
|
||||||
"distanthorizons.config.client.advanced.graphics.genericRendering.expandDistantBeacons":
|
"distanthorizons.config.client.advanced.graphics.genericRendering.expandDistantBeacons":
|
||||||
"Expand Distant Beacons",
|
"Expand Distant Beacons",
|
||||||
"distanthorizons.config.client.advanced.graphics.genericRendering.expandDistantBeacons.@tooltip":
|
"distanthorizons.config.client.advanced.graphics.genericRendering.expandDistantBeacons.@tooltip":
|
||||||
"If true LOD beacon beams will be rendered wider at extreme distances, \nmaking them easier to see. \nIf false all LOD beacon beams will only ever be 1 block wide.",
|
"If true LOD beacon beams will be rendered wider at extreme distances, \nmaking them easier to see. \nIf false all LOD beacon beams will only ever be 1 block wide.",
|
||||||
@@ -533,6 +536,26 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"distanthorizons.config.client.advanced.debugging.positionFinderDebugging":
|
||||||
|
"Position Finder",
|
||||||
|
|
||||||
|
"distanthorizons.config.client.advanced.debugging.positionFinderDebugging.positionFinderEnable":
|
||||||
|
"Enable Position Finder",
|
||||||
|
"distanthorizons.config.client.advanced.debugging.positionFinderDebugging.positionFinderDetailLevel":
|
||||||
|
"Absolute Detail Level",
|
||||||
|
"distanthorizons.config.client.advanced.debugging.positionFinderDebugging.positionFinderXPos":
|
||||||
|
"Pos X",
|
||||||
|
"distanthorizons.config.client.advanced.debugging.positionFinderDebugging.positionFinderZPos":
|
||||||
|
"Pos Z",
|
||||||
|
"distanthorizons.config.client.advanced.debugging.positionFinderDebugging.positionFinderMinBlockY":
|
||||||
|
"Min Block Y",
|
||||||
|
"distanthorizons.config.client.advanced.debugging.positionFinderDebugging.positionFinderMaxBlockY":
|
||||||
|
"Max Block Y",
|
||||||
|
"distanthorizons.config.client.advanced.debugging.positionFinderDebugging.positionFinderMarginPercent":
|
||||||
|
"Margin %",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"distanthorizons.config.client.advanced.debugging.f3Screen":
|
"distanthorizons.config.client.advanced.debugging.f3Screen":
|
||||||
"F3 Screen",
|
"F3 Screen",
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,13 @@ import org.junit.Test;
|
|||||||
public class PooledDataSourceCheckoutTest
|
public class PooledDataSourceCheckoutTest
|
||||||
{
|
{
|
||||||
|
|
||||||
@Test
|
/**
|
||||||
|
* commented out for now since it has
|
||||||
|
* a chance of breaking if any other tests
|
||||||
|
* using the same pools run at the same time
|
||||||
|
* or before this one.
|
||||||
|
*/
|
||||||
|
//@Test
|
||||||
public void TestCheckouts()
|
public void TestCheckouts()
|
||||||
{
|
{
|
||||||
// something like this should probably be called before starting the test to ensure
|
// something like this should probably be called before starting the test to ensure
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ public class QuadTreeTest
|
|||||||
public void BasicPositiveQuadTreeTest()
|
public void BasicPositiveQuadTreeTest()
|
||||||
{
|
{
|
||||||
AbstractTestTreeParams treeParams = new LargeTestTree();
|
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);
|
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()
|
public void BasicNegativeQuadTreeTest()
|
||||||
{
|
{
|
||||||
AbstractTestTreeParams treeParams = new LargeTestTree();
|
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 //
|
// root node //
|
||||||
@@ -128,7 +128,7 @@ public class QuadTreeTest
|
|||||||
public void OutOfBoundsQuadTreeTest()
|
public void OutOfBoundsQuadTreeTest()
|
||||||
{
|
{
|
||||||
AbstractTestTreeParams treeParams = new LargeTestTree();
|
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());
|
Assert.assertEquals("tree diameter incorrect", treeParams.getWidthInBlocks(), tree.diameterInBlocks());
|
||||||
|
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ public class QuadTreeTest
|
|||||||
public void outOfBoundsInTreeTest()
|
public void outOfBoundsInTreeTest()
|
||||||
{
|
{
|
||||||
// very specific tree parameters to match test results
|
// 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);
|
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()
|
public void QuadTreeRootAlignedMovingTest()
|
||||||
{
|
{
|
||||||
AbstractTestTreeParams treeParams = new LargeTestTree();
|
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);
|
int pseudoRootNodeWidthInBlocks = BitShiftUtil.powerOfTwo(10);
|
||||||
|
|
||||||
@@ -210,7 +210,7 @@ public class QuadTreeTest
|
|||||||
testSet(tree, ne, 3);
|
testSet(tree, ne, 3);
|
||||||
testSet(tree, sw, 4);
|
testSet(tree, sw, 4);
|
||||||
testSet(tree, se, 5);
|
testSet(tree, se, 5);
|
||||||
Assert.assertEquals("incorrect leaf node count", tree.leafNodeCount(), 4);
|
Assert.assertEquals("incorrect leaf node count", 4, tree.leafNodeCount());
|
||||||
|
|
||||||
|
|
||||||
// fake move //
|
// fake move //
|
||||||
@@ -237,7 +237,7 @@ public class QuadTreeTest
|
|||||||
testGet(tree, ne, 3);
|
testGet(tree, ne, 3);
|
||||||
testGet(tree, sw, 4);
|
testGet(tree, sw, 4);
|
||||||
testGet(tree, se, 5);
|
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, sw, null, IndexOutOfBoundsException.class);
|
||||||
testGet(tree, se, 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);
|
DhBlockPos2D edgeMoveBlockPos = new DhBlockPos2D(pseudoRootNodeWidthInBlocks - (treeParams.getWidthInRootNodes() * pseudoRootNodeWidthInBlocks), 0);
|
||||||
tree.setCenterBlockPos(edgeMoveBlockPos);
|
tree.setCenterBlockPos(edgeMoveBlockPos);
|
||||||
Assert.assertEquals("Tree center incorrect", edgeMoveBlockPos, tree.getCenterBlockPos());
|
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()
|
public void QuadTreeIterationTest()
|
||||||
{
|
{
|
||||||
AbstractTestTreeParams treeParams = new LargeTestTree();
|
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 //
|
// (pseudo) root nodes //
|
||||||
@@ -336,7 +336,7 @@ public class QuadTreeTest
|
|||||||
public void QuadTreeIterationFilterTest()
|
public void QuadTreeIterationFilterTest()
|
||||||
{
|
{
|
||||||
AbstractTestTreeParams treeParams = new TinyTestTree();
|
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<>();
|
ArrayList<String> foundNodePositionStrings = new ArrayList<>();
|
||||||
|
|
||||||
@@ -532,7 +535,7 @@ public class QuadTreeTest
|
|||||||
public void CenteredGridListIterationTest()
|
public void CenteredGridListIterationTest()
|
||||||
{
|
{
|
||||||
AbstractTestTreeParams treeParams = new TinyTestTree();
|
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);
|
testSet(tree, DhSectionPos.encode(tree.treeRootDetailLevel, 0, 0), 0);
|
||||||
|
|
||||||
// confirm the root node were added
|
// confirm the root node were added
|
||||||
@@ -573,18 +576,18 @@ public class QuadTreeTest
|
|||||||
AbstractTestTreeParams treeParams = new TinyTestTree();
|
AbstractTestTreeParams treeParams = new TinyTestTree();
|
||||||
|
|
||||||
// exactly inside (5*0,0)
|
// 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)
|
// 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)
|
// 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());
|
Assert.assertEquals("tree creation failed, incorrect initial position", DhBlockPos2D.ZERO, tree.getCenterBlockPos());
|
||||||
|
|
||||||
tree.setCenterBlockPos(treeMovePos);
|
tree.setCenterBlockPos(treeMovePos);
|
||||||
@@ -615,7 +618,7 @@ public class QuadTreeTest
|
|||||||
public void TinyGridAlignedTreeTest()
|
public void TinyGridAlignedTreeTest()
|
||||||
{
|
{
|
||||||
AbstractTestTreeParams treeParams = new MediumTestTree();
|
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
|
// minimum size tree should be 3 root nodes wide
|
||||||
Assert.assertEquals("incorrect tree node width", 3, tree.ringListWidth());
|
Assert.assertEquals("incorrect tree node width", 3, tree.ringListWidth());
|
||||||
Assert.assertEquals("incorrect tree width", treeParams.getWidthInBlocks(), tree.diameterInBlocks());
|
Assert.assertEquals("incorrect tree width", treeParams.getWidthInBlocks(), tree.diameterInBlocks());
|
||||||
@@ -644,7 +647,7 @@ public class QuadTreeTest
|
|||||||
public void TinyGridOffsetTreeTest()
|
public void TinyGridOffsetTreeTest()
|
||||||
{
|
{
|
||||||
AbstractTestTreeParams treeParams = new MediumTestTree();
|
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
|
// minimum size tree should be 3 root nodes wide
|
||||||
Assert.assertEquals("incorrect tree node width", 3, tree.ringListWidth());
|
Assert.assertEquals("incorrect tree node width", 3, tree.ringListWidth());
|
||||||
Assert.assertEquals("incorrect tree width", treeParams.getWidthInBlocks(), tree.diameterInBlocks());
|
Assert.assertEquals("incorrect tree width", treeParams.getWidthInBlocks(), tree.diameterInBlocks());
|
||||||
@@ -683,7 +686,7 @@ public class QuadTreeTest
|
|||||||
public void TreeDetailLevelLimitTest()
|
public void TreeDetailLevelLimitTest()
|
||||||
{
|
{
|
||||||
AbstractTestTreeParams treeParams = new MediumTestTree();
|
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);
|
Assert.assertEquals("Test detail level's need to be adjusted. This isn't necessarily a failed test.", 10, tree.treeRootDetailLevel);
|
||||||
|
|
||||||
// valid detail levels
|
// valid detail levels
|
||||||
@@ -705,7 +708,7 @@ public class QuadTreeTest
|
|||||||
public void QuadNodeDetailLimitTest()
|
public void QuadNodeDetailLimitTest()
|
||||||
{
|
{
|
||||||
AbstractTestTreeParams treeParams = new MediumTestTree();
|
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);
|
Assert.assertEquals("Test detail level's need to be adjusted. This isn't necessarily a failed test.", 10, tree.treeRootDetailLevel);
|
||||||
|
|
||||||
// create the root node
|
// create the root node
|
||||||
@@ -817,8 +820,7 @@ public class QuadTreeTest
|
|||||||
@Test
|
@Test
|
||||||
public void quadNodeChildPositionOutOfBoundsTest()
|
public void quadNodeChildPositionOutOfBoundsTest()
|
||||||
{
|
{
|
||||||
int treeWidthInBlocks = 64;
|
QuadTree<Integer> tree = new QuadTree<>(64, 1, new DhBlockPos2D(-2, 0), (byte) 0);
|
||||||
QuadTree<Integer> tree = new QuadTree<>(treeWidthInBlocks, new DhBlockPos2D(-2, 0), (byte) 0);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -865,7 +867,7 @@ public class QuadTreeTest
|
|||||||
public void toStringTest()
|
public void toStringTest()
|
||||||
{
|
{
|
||||||
AbstractTestTreeParams treeParams = new MediumTestTree();
|
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();
|
String treeString = tree.toString();
|
||||||
Assert.assertNotNull(treeString);
|
Assert.assertNotNull(treeString);
|
||||||
@@ -929,25 +931,28 @@ public class QuadTreeTest
|
|||||||
private abstract static class AbstractTestTreeParams
|
private abstract static class AbstractTestTreeParams
|
||||||
{
|
{
|
||||||
public abstract int getWidthInBlocks();
|
public abstract int getWidthInBlocks();
|
||||||
|
public abstract int getBlockDistanceForNodeClearing();
|
||||||
|
|
||||||
/** the tree should be slightly larger than the width in blocks to account for offset centers */
|
/** 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; }
|
public int getWidthInRootNodes() { return MathUtil.log2(this.getWidthInBlocks()) + 2; }
|
||||||
/** the top (root) detail level in the tree */
|
/** 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) */
|
/** @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
|
private static class LargeTestTree extends AbstractTestTreeParams
|
||||||
{
|
{
|
||||||
public int getWidthInBlocks() { return 8192; }
|
public int getWidthInBlocks() { return 8192; }
|
||||||
|
public int getBlockDistanceForNodeClearing() { return 16; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MediumTestTree extends AbstractTestTreeParams
|
private static class MediumTestTree extends AbstractTestTreeParams
|
||||||
{
|
{
|
||||||
public int getWidthInBlocks() { return 1024; }
|
public int getWidthInBlocks() { return 1024; }
|
||||||
|
public int getBlockDistanceForNodeClearing() { return 4; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -955,6 +960,7 @@ public class QuadTreeTest
|
|||||||
{
|
{
|
||||||
// top detail level = 6
|
// top detail level = 6
|
||||||
public int getWidthInBlocks() { return 32; }
|
public int getWidthInBlocks() { return 32; }
|
||||||
|
public int getBlockDistanceForNodeClearing() { return 1; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user