Compare commits

...

34 Commits

Author SHA1 Message Date
James Seibel 9897570e6c Fully move getShade into the clientLevelWrapper 2026-05-31 07:43:05 -05:00
Acuadragon100 653b86c51a Move getShade into ClientLevelWrapper. 2026-05-31 11:15:34 +02:00
James Seibel 24d2fa90f4 fix world gen stopping when moving between dimensions 2026-05-30 17:53:50 -05:00
Acuadragon100 4f6d78189b Also use the original camera position when Immersive Portals is loaded. 2026-05-16 13:24:52 +02:00
Acuadragon100 1f7cf793c6 Merge remote-tracking branch 'root/main' into fix-portals 2026-05-15 16:42:23 +02:00
Acuadragon100 1908a0ccbf Fix null pointer exception when server level was unloaded before the client level. 2026-05-15 15:15:01 +02:00
James Seibel fd3a8f7ddf Add MC Version locking to the config 2026-05-15 07:44:00 -05:00
Acuadragon100 592b050937 Merge remote-tracking branch 'root/main' into fix-portals
# Conflicts:
#	core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java
#	core/src/main/java/com/seibel/distanthorizons/core/level/ClientLevelModule.java
2026-05-14 22:34:38 +02:00
Acuadragon100 9fa9b430f6 Only tick levels the player is in on the client. 2026-05-14 12:08:38 +02:00
Acuadragon100 c720a36f83 Always return the player's level and position even when a portal is being rendered. 2026-05-13 21:05:58 +02:00
James Seibel e3f586da56 temp comment out PooledDataSourceCheckoutTest 2026-05-12 21:56:18 -05:00
Acuadragon100 552ad226ea Avoid updating camera speed average when rendering a portal. 2026-05-12 21:21:18 +02:00
James Seibel 775984f651 add TODO 2026-05-11 22:01:00 -05:00
Acuadragon100 fd704bf8e6 Prevent multiple DhClientLevels of the same level from existing at once. 2026-05-03 21:07:03 +02:00
Acuadragon100 cc2febcb5c Fix current level unloading on the client. 2026-05-03 21:06:58 +02:00
Acuadragon100 809990f766 Make sure stale wrappers are not stored in the client level map. 2026-05-03 21:06:43 +02:00
James Seibel 5cb30ed7ce disable immersive portals cam speed calculation 2026-05-02 09:56:28 -05:00
James Seibel 5f54ad0650 up net protocol version 13 -> 14 2026-05-02 09:55:57 -05:00
James Seibel 9fc4d840fc immersive portal quad tree player pos fix 2026-05-02 09:55:45 -05:00
James Seibel 52b0acc452 Immersive Portal Accessor refactoring 2026-05-02 09:55:00 -05:00
James Seibel 4e647395e8 minor format updating 2026-05-02 09:53:02 -05:00
Acuadragon100 25ac1de59b Check if same level before trying to decode the data. 2026-04-26 13:25:00 +02:00
Acuadragon100 949124f8dc Fix not unloading client levels on servers. 2026-04-26 13:24:55 +02:00
Acuadragon100 c363b7fe4b Check fade rendering even without Sodium, because it can still happen without Sodium in some cases. 2026-04-26 13:24:53 +02:00
Acuadragon100 dcb049d4c2 Disable fading detection on 1.21.6+ for now.
Might be worth revisiting once a proper fork of Immersive Portals appear for newer versions.
2026-04-26 13:24:51 +02:00
Acuadragon100 ea51b9135d Import cleanup 2026-04-26 13:24:48 +02:00
Acuadragon100 da31547cfc Allow client to load all dimensions. 2026-04-26 13:24:46 +02:00
Acuadragon100 00f9fd8e53 Allow updating other dimensions. 2026-04-26 13:24:44 +02:00
Acuadragon100 7149baf0f6 Fix server loading. 2026-04-26 13:24:42 +02:00
Acuadragon100 ef3e7763dc Redo loading 2026-04-26 13:24:39 +02:00
Acuadragon100 f5ac5c56b4 Looks like shouldSkipRenderingPortal is sometimes not static. 2026-04-26 13:24:37 +02:00
Acuadragon100 77f10bed48 Probably want to detect rubidium and embeddium as well. 2026-04-26 13:24:35 +02:00
Acuadragon100 7fe0c9b0e8 Tweaked portal loading and fix portals not being detected to disable fading until entered at least once. 2026-04-26 13:24:32 +02:00
Acuadragon100 3d13ba7645 Disable fade rendering when immersive portals and sodium are active at once. 2026-04-26 13:24:13 +02:00
32 changed files with 778 additions and 229 deletions
@@ -31,7 +31,7 @@ public final class ModInfo
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. */
public static final int PROTOCOL_VERSION = 13;
public static final int PROTOCOL_VERSION = 14;
/**
* The full plugin channel name (RESOURCE_NAMESPACE:WRAPPER_PACKET_PATH)
@@ -41,6 +41,7 @@ import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.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.IDhVanillaFadeRenderer;
@@ -53,7 +54,6 @@ import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.world.DhClientWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
@@ -68,6 +68,7 @@ import org.lwjgl.glfw.GLFW;
import java.awt.*;
import java.io.File;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
@@ -87,6 +88,11 @@ public class ClientApi
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.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 */
private static final int MS_BETWEEN_STATIC_STARTUP_MESSAGES = 4_000;
@@ -119,7 +125,7 @@ public class ClientApi
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 */
private Timer firstLevelLoadTimer;
@@ -127,8 +133,8 @@ public class ClientApi
/** Holds any levels that were loaded before the {@link ClientApi#onClientOnlyConnected} was fired. */
public final HashSet<IClientLevelWrapper> waitingClientLevels = new HashSet<>();
/** Holds any chunks that were loaded before the {@link ClientApi#clientLevelLoadEvent(IClientLevelWrapper)} was fired. */
public final HashMap<Pair<IClientLevelWrapper, DhChunkPos>, IChunkWrapper> waitingChunkByClientLevelAndPos = new HashMap<>();
/** Holds any chunks that were found before the client levels are loaded. */
public final Map<Pair<IClientLevelWrapper, DhChunkPos>, IChunkWrapper> waitingChunkByClientLevelAndPos = new ConcurrentHashMap<>();
/** publicly available so {@link F3Screen} can display the error */
@Nullable
@@ -144,9 +150,13 @@ public class ClientApi
* tracked should also be to keep the ratio roughly the same.
* @see ClientApi#MIN_MS_BETWEEN_SPEED_CHECKS
*/
public RollingAverage cameraSpeedRollingAverage = new RollingAverage(40);
private RollingAverage cameraSpeedRollingAverage = new RollingAverage(40);
private Vec3d lastCameraPosForSpeedCheck = new Vec3d();
private long msSinceLastSpeedCheck = 0L;
public double getAvgCameraSpeed()
{
return cameraSpeedRollingAverage.getAverage();
}
public static long firstRenderTimeMs = 0;
@@ -176,7 +186,7 @@ public class ClientApi
/**
* 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>
*
* Synchronized shouldn't be necessary, but is present to match {@see onClientOnlyDisconnected} and prevent any unforeseen issues.
@@ -213,14 +223,6 @@ public class ClientApi
this.pluginChannelApi.onJoinServer(world.networkState.getSession());
world.networkState.sendConfigMessage();
LOGGER.info("Loading [" + this.waitingClientLevels.size() + "] waiting client level wrappers.");
for (IClientLevelWrapper level : this.waitingClientLevels)
{
this.clientLevelLoadEvent(level);
}
this.waitingClientLevels.clear();
}
}
@@ -247,7 +249,6 @@ public class ClientApi
// remove any waiting items
this.waitingChunkByClientLevelAndPos.clear();
this.waitingClientLevels.clear();
}
//endregion
@@ -259,44 +260,12 @@ public class ClientApi
//==============//
//region level events
public void clientLevelUnloadEvent(IClientLevelWrapper level)
/**
* used in conjunction with the server networking to
* handle level load requests.
*/
public boolean canLoadClientLevel(IClientLevelWrapper wrapper)
{
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())
{
@@ -306,48 +275,41 @@ public class ClientApi
this.firstLevelLoadTimer.schedule(new TimerTask()
{
@Override
public void run() { ClientApi.this.clientLevelLoadEvent(levelWrapper); }
public void run() { canLoadClientLevel(wrapper); }
}, FIRST_LEVEL_LOAD_DELAY_IN_MS);
return;
return false;
}
this.firstLevelLoadTimer.cancel();
}
try
if (!this.pluginChannelApi.allowLevelLoading(wrapper))
{
LOGGER.info("Loading client level [" + levelWrapper + "]-[" + levelWrapper.getDhIdentifier() + "].");
LOGGER.debug("Client levels in this connection are managed by the server, skipping auto-load of: ["+wrapper+"]");
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null)
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);
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;
}
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);
}
return true;
}
private void loadWaitingChunksForLevel(IClientLevelWrapper level)
//endregion
//==============//
// level events //
//==============//
//region
public void loadWaitingChunksForLevel(IClientLevelWrapper level)
{
HashSet<Pair<IClientLevelWrapper, DhChunkPos>> keysToRemove = new HashSet<>();
for (Pair<IClientLevelWrapper, DhChunkPos> levelChunkPair : this.waitingChunkByClientLevelAndPos.keySet())
@@ -498,7 +460,7 @@ public class ClientApi
//region
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
double secSinceLastCheck = (nowMs - this.msSinceLastSpeedCheck) / 1_000.0;
@@ -727,8 +689,7 @@ public class ClientApi
// or if LOD-only mode is enabled (fading is used to remove the MC render pass)
|| Config.Client.Advanced.Debugging.lodOnlyMode.get()
)
// don't fade when Iris shaders are active, otherwise the rendering can get weird
&& !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
&& shouldRenderFade())
{
RenderParams renderParams = new RenderParams(EDhApiRenderPass.OPAQUE, RENDER_STATE);
fadeRenderer.render(renderParams);
@@ -757,8 +718,7 @@ public class ClientApi
// or if LOD-only mode is enabled (fading is used to remove the MC render pass)
|| Config.Client.Advanced.Debugging.lodOnlyMode.get()
)
// don't fade when Iris shaders are active, otherwise the rendering can get weird
&& !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering();
&& shouldRenderFade();
if (renderFade)
{
RenderParams renderParams = new RenderParams(EDhApiRenderPass.TRANSPARENT, RENDER_STATE);
@@ -767,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
@@ -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.session.NetworkSession;
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.world.IClientLevelWrapper;
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 IKeyedClientLevelManager KEYED_CLIENT_LEVEL_MANAGER = SingletonInjector.INSTANCE.get(IKeyedClientLevelManager.class);
private final Consumer<IServerKeyedClientLevel> levelLoadHandler;
private final Consumer<IClientLevelWrapper> levelUnloadHandler;
@Nullable
public NetworkSession networkSession;
@@ -42,10 +40,8 @@ public class ClientPluginChannelApi
// 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);
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
|| !existingKeyedClientLevel.getServerKey().equals(msg.serverKey)
@@ -119,7 +97,11 @@ public class ClientPluginChannelApi
{
LOGGER.info("Loading level with key: [" + 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;
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.MessageRegistry;
import com.seibel.distanthorizons.core.world.*;
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.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
@@ -77,7 +74,6 @@ public class ServerApi
}
//==============//
// level events //
//==============//
@@ -90,7 +86,6 @@ public class ServerApi
if (serverWorld != null)
{
serverWorld.getOrLoadLevel(levelWrapper);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(levelWrapper));
}
}
public void serverLevelUnloadEvent(IServerLevelWrapper level)
@@ -101,12 +96,10 @@ public class ServerApi
if (serverWorld != null)
{
serverWorld.unloadLevel(level);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(level));
}
}
//=======================//
// chunk modified events //
//=======================//
@@ -55,6 +55,16 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
@Nullable
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
&& 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>
* 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
public T get()
{
// always use the MC version specific option if defined
if (this.mcVersionOverrideValue != null)
{
return this.mcVersionOverrideValue;
}
if (this.allowApiOverride
&& this.apiValue != null)
{
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.util.ColorUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
@@ -60,6 +61,12 @@ public class ColumnBox
// variable setup //
//================//
IClientLevelWrapper clientLevelWrapper = clientLevel.getClientLevelWrapper();
if (clientLevelWrapper == null)
{
LodUtil.assertNotReach("addBoxQuadsToBuilder getClientLevelWrapper should always succeed");
}
short maxX = (short) (minX + width);
short maxY = (short) (minY + yHeight);
short maxZ = (short) (minZ + width);
@@ -122,7 +129,7 @@ public class ColumnBox
&& !isTopTransparent;
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;
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
{
makeAdjVerticalQuad(
builder, phantomArrayCheckout,
builder, phantomArrayCheckout, clientLevelWrapper,
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.NORTH,
minX, minY, minZ, width, yHeight,
color, irisBlockMaterialId, blockLight);
@@ -188,7 +195,7 @@ public class ColumnBox
else
{
makeAdjVerticalQuad(
builder, phantomArrayCheckout,
builder, phantomArrayCheckout, clientLevelWrapper,
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.SOUTH,
minX, minY, maxZ, width, yHeight,
color, irisBlockMaterialId, blockLight);
@@ -213,7 +220,7 @@ public class ColumnBox
else
{
makeAdjVerticalQuad(
builder, phantomArrayCheckout,
builder, phantomArrayCheckout, clientLevelWrapper,
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.WEST,
minX, minY, minZ, width, yHeight,
color, irisBlockMaterialId, blockLight);
@@ -238,7 +245,7 @@ public class ColumnBox
else
{
makeAdjVerticalQuad(
builder, phantomArrayCheckout,
builder, phantomArrayCheckout, clientLevelWrapper,
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.EAST,
maxX, minY, minZ, width, yHeight,
color, irisBlockMaterialId, blockLight);
@@ -247,7 +254,7 @@ public class ColumnBox
}
private static void makeAdjVerticalQuad(
LodQuadBuilder builder, PhantomArrayListCheckout phantomArrayCheckout,
LodQuadBuilder builder, PhantomArrayListCheckout phantomArrayCheckout, IClientLevelWrapper clientLevelWrapper,
@NotNull ColumnRenderView adjColumnView, boolean adjacentIsSameDetailLevel, int caveCullingMaxY, EDhDirection direction,
short x, short yMin, short z, short horizontalWidth, short ySize,
int color, byte irisBlockMaterialId, byte blockLight)
@@ -263,7 +270,7 @@ public class ColumnBox
// no adjacent data //
//==================//
color = ColorUtil.applyShade(color, MC_RENDER.getShade(direction));
color = ColorUtil.applyShade(color, clientLevelWrapper.getShade(direction));
if (adjColumnView.size == 0
|| RenderDataPointUtil.hasZeroHeight(adjColumnView.get(0)))
@@ -394,7 +394,7 @@ public class LodQuadBuilder
// for horizontal and bottom faces of grass blocks, use the dirt color to
// prevent green cliff walls
color = this.clientLevelWrapper.getDirtBlockColor();
color = ColorUtil.applyShade(color, MC_RENDER.getShade(quad.direction));
color = ColorUtil.applyShade(color, this.clientLevelWrapper.getShade(quad.direction));
}
}
}
@@ -123,17 +123,6 @@ public class LodRequestModule implements Closeable
// if the world is read only don't generate anything
shouldDoWorldGen &= !DhApiWorldProxy.INSTANCE.tryGetReadOnly();
// don't generate chunks for client levels that aren't being rendered
// (this can happen when moving between dimensions)
if (this.level instanceof IDhClientLevel)
{
boolean isRendering = ((IDhClientLevel) this.level).isRendering();
if (!isRendering)
{
shouldDoWorldGen = false;
}
}
boolean isWorldGenRunning = this.isWorldGenRunning();
@@ -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.ServerPlayerStateManager;
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.messages.AbstractNetworkMessage;
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);
// 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;
}
@@ -21,10 +21,12 @@ package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.core.config.Config;
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.file.fullDatafile.IDataSourceUpdateListenerFunc;
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
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.render.QuadTree.LodQuadTree;
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
@@ -33,6 +35,7 @@ import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
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.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.logging.DhLogger;
@@ -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))
{
boolean isSameLevel = message.isSameLevelAs(this.levelWrapper);
NETWORK_LOGGER.debug("Buffer ["+message.payload.dtoBufferId+"] isSameLevel: ["+isSameLevel+"]");
if (!isSameLevel)
{
return;
}
Executor executor = ThreadPoolUtil.getFileHandlerExecutor();
if (executor != null)
@@ -219,6 +221,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
{
try
{
if (MC_CLIENT.getWrappedClientLevel() == null || MC_CLIENT.getWrappedClientLevel().getDhLevel() != this) return;
this.clientside.clientTick();
if (this.syncOnLoadRequestQueue != null)
@@ -71,7 +71,11 @@ public class DhClientServerLevel extends AbstractDhServerLevel implements IDhCli
//region
@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
@@ -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.IncompatibleMessageInternalEvent;
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.fullData.FullDataSourceResponseMessage;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage;
@@ -164,7 +165,8 @@ public class ClientNetworkState implements Closeable
// send message //
//==============//
public void sendLevelInitRequest(String clientLevelKey)
{ this.getSession().sendMessage(new RequestLevelInitMessage(clientLevelKey)); }
public void sendConfigMessage() { this.sendConfigMessage(true); }
public void sendConfigMessage(boolean blocking)
@@ -2,6 +2,7 @@ package com.seibel.distanthorizons.core.multiplayer.server;
import com.seibel.distanthorizons.core.config.Config;
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.multiplayer.config.SessionConfig;
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.messages.base.CloseReasonMessage;
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.event.internal.CloseInternalEvent;
import com.seibel.distanthorizons.core.network.exceptions.RateLimitedException;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceRequestMessage;
import com.seibel.distanthorizons.core.network.session.NetworkSession;
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 org.jetbrains.annotations.NotNull;
import java.io.Closeable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
public class ServerPlayerState implements Closeable
{
private final IMinecraftSharedWrapper MC_SHARED = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
private final ConfigChangeListener<String> levelKeyPrefixChangeListener
= new ConfigChangeListener<>(Config.Server.levelKeyPrefix, this::onLevelKeyPrefixConfigChanged);
private final SessionConfig.AnyChangeListener configAnyChangeListener = new SessionConfig.AnyChangeListener(this::sendConfigMessage);
@@ -66,6 +72,12 @@ public class ServerPlayerState implements Closeable
this.sendConfigMessage();
});
this.networkSession.registerHandler(RequestLevelInitMessage.class, (requestLevelKeyMessage) ->
{
sendLevelKey(requestLevelKeyMessage.clientLevelKey);
});
this.networkSession.registerHandler(CloseInternalEvent.class, event -> {
// 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 sendLevelKey(String clientLevelKey)
{
sendLevelKey(() ->
MC_SHARED
.getWrappedServerLevel(clientLevelKey)
.getKeyedLevelDimensionName());
}
private void sendLevelKey()
{
sendLevelKey(() ->
this.getServerPlayer()
.getLevel()
.getKeyedLevelDimensionName());
}
private void sendLevelKey(Supplier<String> levelKeySupplier)
{
if (Config.Server.sendLevelKeys.get())
{
String levelKey = levelKeySupplier.get();
// let the client's know about the change
String levelKey = this.getServerPlayer().getLevel().getKeyedLevelDimensionName();
if (!levelKey.equals(this.lastLevelKey))
{
this.lastLevelKey = levelKey;
@@ -21,12 +21,9 @@ package com.seibel.distanthorizons.core.network.messages;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.seibel.distanthorizons.core.network.messages.base.CodecCrashMessage;
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.base.*;
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.base.CloseReasonMessage;
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.FullDataSourceRequestMessage;
@@ -60,6 +57,7 @@ public class MessageRegistry
// Level keys
this.registerMessage(LevelInitMessage.class, LevelInitMessage::new);
this.registerMessage(RequestLevelInitMessage.class, RequestLevelInitMessage::new);
// Config (for full DH support)
this.registerMessage(SessionConfigMessage.class, SessionConfigMessage::new);
@@ -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();
}
@@ -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.dependencyInjection.ModAccessorInjector;
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.util.RenderUtil;
import com.seibel.distanthorizons.core.util.math.Mat4f;
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.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.ILightMapWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.AbstractOptifineAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IOptifineAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
@@ -87,7 +83,7 @@ public class RenderParams extends DhApiRenderParam
this.dhClientWorld = SharedApi.tryGetDhClientWorld();
if (this.dhClientWorld != null)
{
this.dhClientLevel = (IDhClientLevel) this.dhClientWorld.getLevel(clientLevelWrapper);
this.dhClientLevel = this.dhClientWorld.getOrLoadClientLevel(clientLevelWrapper);
if (this.dhClientLevel != null)
{
this.renderBufferHandler = this.dhClientLevel.getRenderBufferHandler();
@@ -163,7 +163,7 @@ public class RenderUtil
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 the player is moving fast enough,
@@ -170,7 +170,7 @@ public class ThreadPoolUtil
*/
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
double maxAllowedSpeed = (LodUtil.ROCKET_ELYTRA_SPEED_IN_BLOCKS_PER_SEC - 10.0);
if (cameraSpeed > maxAllowedSpeed)
@@ -1,5 +1,6 @@
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.level.AbstractDhServerLevel;
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.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
@@ -138,6 +140,7 @@ public abstract class AbstractDhServerWorld<TDhServerLevel extends AbstractDhSer
if (serverLevelWrapper != null)
{
serverLevelWrapper.onUnload();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(serverLevelWrapper));
}
@@ -19,6 +19,8 @@
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.enums.MinecraftTextFormat;
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.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import org.jetbrains.annotations.NotNull;
import java.util.*;
@@ -34,7 +37,7 @@ import java.util.concurrent.CompletableFuture;
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");
@@ -54,7 +57,7 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
@Override
public void run()
{
DhClientServerWorld.this.dhLevels.forEach(DhClientServerLevel::clientTick);
DhClientServerWorld.this.dhLevels.keySet().forEach(DhClientServerLevel::clientTick);
}
}, 0, IDhClientWorld.TICK_RATE_IN_MS);
}
@@ -75,7 +78,8 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
try
{
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;
}
catch (Exception e)
@@ -92,6 +96,10 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
}
else
{
if (wrapper instanceof IClientLevelWrapper)
{
((IClientLevelWrapper) wrapper).markAccessed();
}
return this.dhLevelByLevelWrapper.computeIfAbsent(wrapper, (levelWrapper) ->
{
IClientLevelWrapper clientLevelWrapper = (IClientLevelWrapper) levelWrapper;
@@ -111,13 +119,14 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
level.startRenderer();
clientLevelWrapper.setDhLevel(level);
dhLevels.get(level).add(wrapper);
return level;
});
}
}
@Override
public void unloadLevel(@NotNull ILevelWrapper wrapper)
public boolean unloadLevel(@NotNull ILevelWrapper 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,
// 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.
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)
{
// close each level
for (DhClientServerLevel level : this.dhLevels)
for (DhClientServerLevel level : this.dhLevels.keySet())
{
// level wrapper shouldn't be null, but just in case
IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper();
if (serverLevelWrapper != null)
{
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
@@ -19,6 +19,8 @@
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.enums.MinecraftTextFormat;
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.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
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 ClientNetworkState networkState = new ClientNetworkState();
@@ -76,6 +77,32 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
// 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
public DhClientLevel getOrLoadLevel(@NotNull ILevelWrapper wrapper)
{
@@ -83,25 +110,20 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
{
return null;
}
return this.levels.computeIfAbsent((IClientLevelWrapper) wrapper,
(clientLevelWrapper) ->
IClientLevelWrapper clientLevelWrapper = (IClientLevelWrapper) wrapper;
clientLevelWrapper.markAccessed();
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
{
return new DhClientLevel(this.saveStructure, clientLevelWrapper, this.networkState);
}
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;
}
});
this.levels.put(wrapper.getDhIdentifier(), storedLevel);
}
}
return storedLevel;
}
@Override
@@ -112,7 +134,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
return null;
}
return this.levels.get(wrapper);
return this.levels.get(wrapper.getDhIdentifier());
}
@Override
@@ -121,19 +143,26 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
public int getLoadedLevelCount() { return this.levels.size(); }
@Override
public void unloadLevel(@NotNull ILevelWrapper wrapper)
public boolean unloadLevel(@NotNull ILevelWrapper wrapper)
{
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();
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
@@ -156,6 +185,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
if (clientLevelWrapper != null)
{
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.levelWrappers.clear();
this.clientTickTimer.cancel();
LOGGER.info("Closed DhWorld of type [" + this.environment + "].");
}
@@ -19,12 +19,15 @@
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.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.generation.PregenManager;
import com.seibel.distanthorizons.core.level.DhServerLevel;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.CompletableFuture;
@@ -64,7 +67,9 @@ public class DhServerWorld extends AbstractDhServerWorld<DhServerLevel>
{
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)
{
@@ -80,19 +85,21 @@ public class DhServerWorld extends AbstractDhServerWorld<DhServerLevel>
}
@Override
public void unloadLevel(@NotNull ILevelWrapper wrapper)
public boolean unloadLevel(@NotNull ILevelWrapper wrapper)
{
if (!(wrapper instanceof IServerLevelWrapper))
{
return;
return false;
}
if (this.dhLevelByLevelWrapper.containsKey(wrapper))
{
DhServerLevel level = this.dhLevelByLevelWrapper.get(wrapper);
wrapper.onUnload();
this.dhLevelByLevelWrapper.remove(wrapper).close();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(wrapper));
return true;
}
return false;
}
@Override
@@ -49,6 +49,6 @@ public interface IDhWorld extends Closeable
Iterable<? extends IDhLevel> getAllLoadedLevels();
int getLoadedLevelCount();
void unloadLevel(@NotNull ILevelWrapper levelWrapper);
boolean unloadLevel(@NotNull ILevelWrapper levelWrapper);
}
@@ -96,8 +96,6 @@ public interface IMinecraftRenderWrapper extends IBindable
@Nullable
ILightMapWrapper getLightmapWrapper(@NotNull ILevelWrapper level);
float getShade(EDhDirection lodDirection);
}
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.wrapperInterfaces.minecraft;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
import java.io.File;
@@ -31,6 +32,6 @@ public interface IMinecraftSharedWrapper extends IBindable
int getPlayerCount();
IServerLevelWrapper getWrappedServerLevel(String levelKey);
}
@@ -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();
}
@@ -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
}
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core.wrapperInterfaces.world;
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.wrapperInterfaces.block.IBlockStateWrapper;
import org.jetbrains.annotations.Nullable;
@@ -29,6 +30,9 @@ import java.awt.*;
public interface IClientLevelWrapper extends ILevelWrapper
{
/** used to track when this level was last used for Immersive portals support */
void markAccessed();
@Nullable
IServerLevelWrapper tryGetServerSideWrapper();
@@ -39,4 +43,6 @@ public interface IClientLevelWrapper extends ILevelWrapper
Color getCloudColor(float tickDelta);
float getShade(EDhDirection lodDirection);
}
@@ -28,7 +28,10 @@
"API LOCK",
"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.",
"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.",
@@ -39,7 +39,13 @@ import org.junit.Test;
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()
{
// something like this should probably be called before starting the test to ensure