merge immersive portals compat

Thanks Acuadragon100!
This commit is contained in:
James Seibel
2026-06-01 07:30:59 -05:00
parent 9231a48998
commit f69ad051d1
29 changed files with 654 additions and 232 deletions
@@ -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)
@@ -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.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.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;
@@ -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.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;
@@ -65,9 +65,9 @@ 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;
@@ -87,6 +87,12 @@ 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);
/** Delayed accessing is necessary since this object will be created before the mod accessors are bound. */
private static class DelayedAccessors
{
public 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;
@@ -124,7 +130,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;
@@ -132,8 +138,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
@@ -149,9 +155,10 @@ 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 final 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(); }
/** /**
* keeping track of this is necessary to fix * keeping track of this is necessary to fix
@@ -179,7 +186,7 @@ public class ClientApi
/** /**
* 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.
@@ -216,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();
} }
} }
@@ -250,7 +249,6 @@ public class ClientApi
// remove any waiting items // remove any waiting items
this.waitingChunkByClientLevelAndPos.clear(); this.waitingChunkByClientLevelAndPos.clear();
this.waitingClientLevels.clear();
} }
//endregion //endregion
@@ -262,44 +260,12 @@ public class ClientApi
//==============// //==============//
//region level events //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 // 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 (MC_CLIENT.clientConnectedToDedicatedServer())
{ {
@@ -309,48 +275,41 @@ public class ClientApi
this.firstLevelLoadTimer.schedule(new TimerTask() this.firstLevelLoadTimer.schedule(new TimerTask()
{ {
@Override @Override
public void run() { ClientApi.this.clientLevelLoadEvent(levelWrapper); } public void run() { canLoadClientLevel(wrapper); }
}, FIRST_LEVEL_LOAD_DELAY_IN_MS); }, FIRST_LEVEL_LOAD_DELAY_IN_MS);
return; return false;
} }
this.firstLevelLoadTimer.cancel(); this.firstLevelLoadTimer.cancel();
} }
if (!this.pluginChannelApi.allowLevelLoading(wrapper))
try
{ {
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(); AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null) if (world == null)
{ {
if (!this.pluginChannelApi.allowLevelLoading(levelWrapper)) return false;
{
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);
} }
// 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)
{ return true;
// 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)
//endregion
//==============//
// level events //
//==============//
//region
public 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())
@@ -501,7 +460,10 @@ 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
// don't track camera speed for dimensions the player isn't in
&& (DelayedAccessors.IMMERSIVE_PORTALS == null
|| !DelayedAccessors.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;
@@ -725,8 +687,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())
{ {
RENDER_PARAMS.update(EDhApiRenderPass.OPAQUE, RENDER_STATE); RENDER_PARAMS.update(EDhApiRenderPass.OPAQUE, RENDER_STATE);
fadeRenderer.render(RENDER_PARAMS); fadeRenderer.render(RENDER_PARAMS);
@@ -755,8 +716,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)
{ {
RENDER_PARAMS.update(EDhApiRenderPass.TRANSPARENT, RENDER_STATE); RENDER_PARAMS.update(EDhApiRenderPass.TRANSPARENT, RENDER_STATE);
@@ -765,6 +725,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;
}
// Don't render fade through immersive portals, this causes the fade to apply incorrectly
IImmersivePortalsAccessor immersivePortals = ModAccessorInjector.INSTANCE.get(IImmersivePortalsAccessor.class);
if (immersivePortals != null
&& immersivePortals.isRenderingPortal())
{
return false;
}
return true;
}
//endregion //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.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 //
//=======================// //=======================//
@@ -26,6 +26,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;
@@ -62,6 +63,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 + blockWidth); short maxX = (short) (minX + blockWidth);
short maxY = (short) (minY + yHeight); short maxY = (short) (minY + yHeight);
short maxZ = (short) (minZ + blockWidth); short maxZ = (short) (minZ + blockWidth);
@@ -105,7 +112,7 @@ public class ColumnBox
&& !isTopTransparent; && !isTopTransparent;
if (!skipTop) if (!skipTop)
{ {
builder.addQuadUp(minX, maxY, minZ, blockWidth, ColorUtil.applyShade(color, MC_RENDER.getShade(EDhDirection.UP)), irisBlockMaterialId, skyLightTop, blockLight); builder.addQuadUp(minX, maxY, minZ, blockWidth, ColorUtil.applyShade(color, clientLevelWrapper.getShade(EDhDirection.UP)), irisBlockMaterialId, skyLightTop, blockLight);
} }
} }
@@ -116,7 +123,7 @@ public class ColumnBox
&& !isBottomTransparent; && !isBottomTransparent;
if (!skipBottom) if (!skipBottom)
{ {
builder.addQuadDown(minX, minY, minZ, blockWidth, ColorUtil.applyShade(color, MC_RENDER.getShade(EDhDirection.DOWN)), irisBlockMaterialId, skyLightBot, blockLight); builder.addQuadDown(minX, minY, minZ, blockWidth, ColorUtil.applyShade(color, clientLevelWrapper.getShade(EDhDirection.DOWN)), irisBlockMaterialId, skyLightBot, blockLight);
} }
} }
@@ -146,7 +153,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, blockWidth, yHeight, minX, minY, minZ, blockWidth, yHeight,
color, irisBlockMaterialId, blockLight); color, irisBlockMaterialId, blockLight);
@@ -171,7 +178,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, blockWidth, yHeight, minX, minY, maxZ, blockWidth, yHeight,
color, irisBlockMaterialId, blockLight); color, irisBlockMaterialId, blockLight);
@@ -196,7 +203,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, blockWidth, yHeight, minX, minY, minZ, blockWidth, yHeight,
color, irisBlockMaterialId, blockLight); color, irisBlockMaterialId, blockLight);
@@ -221,7 +228,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, blockWidth, yHeight, maxX, minY, minZ, blockWidth, yHeight,
color, irisBlockMaterialId, blockLight); color, irisBlockMaterialId, blockLight);
@@ -230,7 +237,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 horizontalBlockWidth, short ySize, short x, short yMin, short z, short horizontalBlockWidth, short ySize,
int color, byte irisBlockMaterialId, byte blockLight) int color, byte irisBlockMaterialId, byte blockLight)
@@ -246,7 +253,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)))
@@ -420,7 +420,7 @@ public class LodQuadBuilder implements AutoCloseable
// 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));
} }
} }
} }
@@ -123,17 +123,6 @@ public class LodRequestModule implements Closeable
// if the world is read only don't generate anything // if the world is read only don't generate anything
shouldDoWorldGen &= !DhApiWorldProxy.INSTANCE.tryGetReadOnly(); shouldDoWorldGen &= !DhApiWorldProxy.INSTANCE.tryGetReadOnly();
// don't generate chunks for client levels that aren't being rendered
// (this can happen when moving between dimensions)
if (this.level instanceof IDhClientLevel)
{
boolean isRendering = ((IDhClientLevel) this.level).isRendering();
if (!isRendering)
{
shouldDoWorldGen = false;
}
}
boolean isWorldGenRunning = this.isWorldGenRunning(); boolean isWorldGenRunning = this.isWorldGenRunning();
@@ -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;
} }
@@ -126,7 +126,7 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
ClientRenderState clientRenderState = new ClientRenderState(this.clientLevel, this.clientLevel.getFullDataProvider()); ClientRenderState clientRenderState = new ClientRenderState(this.clientLevel, this.clientLevel.getFullDataProvider());
if (!this.ClientRenderStateRef.compareAndSet(null, clientRenderState)) if (!this.ClientRenderStateRef.compareAndSet(null, clientRenderState))
{ {
LOGGER.warn("Renderer already started for ["+this+"]."); LOGGER.warn("Renderer already started for ["+this.clientLevel.getClientLevelWrapper()+"].");
clientRenderState.close(); clientRenderState.close();
} }
} }
@@ -168,15 +168,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)
@@ -220,6 +222,15 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
{ {
try try
{ {
// only tick the level the player is currently in
// (done to prevent ticking LodQuadTree's for levels that aren't rendering)
IClientLevelWrapper clientLevelWrapper = MC_CLIENT.getWrappedClientLevel();
if (clientLevelWrapper == null
|| clientLevelWrapper.getDhLevel() != this)
{
return;
}
this.clientside.clientTick(); this.clientside.clientTick();
if (this.syncOnLoadRequestQueue != null) if (this.syncOnLoadRequestQueue != null)
@@ -72,7 +72,19 @@ public class DhClientServerLevel extends AbstractDhServerLevel implements IDhCli
//region //region
@Override @Override
public void clientTick() { this.clientside.clientTick(); } public void clientTick()
{
// only tick the level the player is currently in
// (done to prevent ticking LodQuadTree's for levels that aren't rendering)
IClientLevelWrapper clientLevelWrapper = MC_CLIENT.getWrappedClientLevel();
if (clientLevelWrapper == null
|| clientLevelWrapper.getDhLevel() != this)
{
return;
}
this.clientside.clientTick();
}
//endregion //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.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)
@@ -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,27 @@ 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.LodUtil;
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 com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
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 +74,12 @@ public class ServerPlayerState implements Closeable
this.sendConfigMessage(); this.sendConfigMessage();
}); });
this.networkSession.registerHandler(RequestLevelInitMessage.class, (requestLevelInitMessage) ->
{
sendLevelKey(requestLevelInitMessage.dimensionResourceLocation);
});
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 +99,33 @@ public class ServerPlayerState implements Closeable
//=================// //=================//
private void onLevelKeyPrefixConfigChanged(String newLevelKey) { this.sendLevelKey(); } private void onLevelKeyPrefixConfigChanged(String newLevelKey) { this.sendLevelKey(); }
private void sendLevelKey(String dimensionResourceLocation)
{
sendLevelKey(() ->
{
IServerLevelWrapper serverLevelWrapper = MC_SHARED.getWrappedServerLevelWithDimensionResourceLocation(dimensionResourceLocation);
if (serverLevelWrapper == null)
{
LodUtil.assertNotReach("Unable to get server level from");
}
return serverLevelWrapper.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;
@@ -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);
@@ -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 dimensionResourceLocation;
//=============//
// constructor //
//=============//
public RequestLevelInitMessage() { }
public RequestLevelInitMessage(String dimensionResourceLocation) { this.dimensionResourceLocation = dimensionResourceLocation; }
//===============//
// serialization //
//===============//
@Override
public void encode(ByteBuf out) { this.writeString(this.dimensionResourceLocation, out); }
@Override
public void decode(ByteBuf in) { this.dimensionResourceLocation = this.readString(in); }
//================//
// base overrides //
//================//
@Override
public MoreObjects.ToStringHelper toStringHelper()
{
return super.toStringHelper()
.add("dimensionResourceLocation", this.dimensionResourceLocation);
}
}
@@ -79,7 +79,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(this.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();
@@ -179,7 +179,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,
@@ -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));
} }
@@ -19,14 +19,18 @@
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;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.util.TimerUtil; import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.LodUtil; 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 +38,18 @@ 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<>()); /**
* Having a set of level wrappers is done to handle an issue where the client
* level would get unloaded when jumping back and forth between dimensions. <br><br>
*
* We might have more than one {@link ILevelWrapper} pointing to the same {@link IDhLevel}
* since they're not immediately unloaded, and we don't want to unload the {@link IDhLevel}
* until all the {@link ILevelWrapper} for that {@link IDhLevel} have been unloaded.
* Any stale {@link IDhLevel} references should disappear on their own after about
* 30 seconds or so thanks to the automatic cleanup.
*/
private final Map<DhClientServerLevel, Set<ILevelWrapper>> clientLevelWrapperSetByDhLevel
= Collections.synchronizedMap(new HashMap<>());
private final Timer clientTickTimer = TimerUtil.CreateTimer("ClientTickTimer"); private final Timer clientTickTimer = TimerUtil.CreateTimer("ClientTickTimer");
@@ -47,14 +62,14 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
public DhClientServerWorld() public DhClientServerWorld()
{ {
super(EWorldEnvironment.CLIENT_SERVER); super(EWorldEnvironment.CLIENT_SERVER);
LOGGER.info("Started DhWorld of type " + this.environment); LOGGER.info("Started DhWorld of type [" + this.environment + "].");
this.clientTickTimer.scheduleAtFixedRate(new TimerTask() this.clientTickTimer.scheduleAtFixedRate(new TimerTask()
{ {
@Override @Override
public void run() public void run()
{ {
DhClientServerWorld.this.dhLevels.forEach(DhClientServerLevel::clientTick); DhClientServerWorld.this.clientLevelWrapperSetByDhLevel.keySet().forEach(DhClientServerLevel::clientTick);
} }
}, 0, IDhClientWorld.TICK_RATE_IN_MS); }, 0, IDhClientWorld.TICK_RATE_IN_MS);
} }
@@ -75,7 +90,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.clientLevelWrapperSetByDhLevel.computeIfAbsent(level, (clientServerLevel) -> Collections.synchronizedSet(new HashSet<>()));
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(wrapper));
return level; return level;
} }
catch (Exception e) catch (Exception e)
@@ -92,11 +108,22 @@ 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) ->
{ {
if (!(levelWrapper instanceof IClientLevelWrapper))
{
LodUtil.assertNotReach("tryGetServerSideWrapper given a non-IClientLevelWrapper.");
}
IClientLevelWrapper clientLevelWrapper = (IClientLevelWrapper) levelWrapper; IClientLevelWrapper clientLevelWrapper = (IClientLevelWrapper) levelWrapper;
IServerLevelWrapper serverLevelWrapper = clientLevelWrapper.tryGetServerSideWrapper(); IServerLevelWrapper serverLevelWrapper = clientLevelWrapper.tryGetServerSideWrapper();
LodUtil.assertTrue(serverLevelWrapper != null); LodUtil.assertTrue(serverLevelWrapper != null);
if (!clientLevelWrapper.getDimensionType().equals(serverLevelWrapper.getDimensionType())) if (!clientLevelWrapper.getDimensionType().equals(serverLevelWrapper.getDimensionType()))
{ {
LodUtil.assertNotReach("tryGetServerSideWrapper returned a level for a different dimension. ClientLevelWrapper dim: [" + clientLevelWrapper.getDhIdentifier() + "] ServerLevelWrapper dim: [" + serverLevelWrapper.getDhIdentifier() + "]."); LodUtil.assertNotReach("tryGetServerSideWrapper returned a level for a different dimension. ClientLevelWrapper dim: [" + clientLevelWrapper.getDhIdentifier() + "] ServerLevelWrapper dim: [" + serverLevelWrapper.getDhIdentifier() + "].");
@@ -111,13 +138,14 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
level.startRenderer(); level.startRenderer();
clientLevelWrapper.setDhLevel(level); clientLevelWrapper.setDhLevel(level);
clientLevelWrapperSetByDhLevel.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))
{ {
@@ -128,16 +156,33 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
DhClientServerLevel clientServerLevel = this.dhLevelByLevelWrapper.remove(wrapper); DhClientServerLevel clientServerLevel = this.dhLevelByLevelWrapper.remove(wrapper);
clientServerLevel.close(); clientServerLevel.close();
this.dhLevels.remove(clientServerLevel); this.clientLevelWrapperSetByDhLevel.remove(clientServerLevel);
} }
else else
{ {
// 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 = clientLevelWrapperSetByDhLevel.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;
} }
@@ -152,16 +197,24 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
{ {
ArrayList<CompletableFuture<Void>> closeFutures = new ArrayList<>(); ArrayList<CompletableFuture<Void>> closeFutures = new ArrayList<>();
synchronized (this.dhLevels) synchronized (this.clientLevelWrapperSetByDhLevel)
{ {
// close each level // close each level
for (DhClientServerLevel level : this.dhLevels) for (DhClientServerLevel level : this.clientLevelWrapperSetByDhLevel.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,21 +30,32 @@ 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;
public final ClientOnlySaveStructure saveStructure; public final ClientOnlySaveStructure saveStructure;
public final ClientNetworkState networkState = new ClientNetworkState(); public final ClientNetworkState networkState = new ClientNetworkState();
private final ConcurrentHashMap<String, DhClientLevel> clientLevelByDhId;
/**
* Having a set of level wrappers is done to handle an issue where the client
* level would get unloaded when jumping back and forth between dimensions. <br><br>
*
* We might have more than one {@link ILevelWrapper} pointing to the same {@link IDhLevel}
* since they're not immediately unloaded, and we don't want to unload the {@link IDhLevel}
* until all the {@link ILevelWrapper} for that {@link IDhLevel} have been unloaded.
* Any stale {@link IDhLevel} references should disappear on their own after about
* 30 seconds or so thanks to the automatic cleanup.
*/
private final Map<String, Set<IClientLevelWrapper>> clientLevelWrapperSetByDhId = new ConcurrentHashMap<>();
private final Timer clientTickTimer = TimerUtil.CreateTimer("ClientTickTimer"); private final Timer clientTickTimer = TimerUtil.CreateTimer("ClientTickTimer");
@@ -56,7 +69,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
super(EWorldEnvironment.CLIENT_ONLY); super(EWorldEnvironment.CLIENT_ONLY);
this.saveStructure = new ClientOnlySaveStructure(); this.saveStructure = new ClientOnlySaveStructure();
this.levels = new ConcurrentHashMap<>(); this.clientLevelByDhId = new ConcurrentHashMap<>();
LOGGER.info("Started DhWorld of type " + this.environment); LOGGER.info("Started DhWorld of type " + this.environment);
@@ -65,7 +78,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
@Override @Override
public void run() public void run()
{ {
DhClientWorld.this.levels.values().forEach(DhClientLevel::clientTick); DhClientWorld.this.clientLevelByDhId.values().forEach(DhClientLevel::clientTick);
} }
}, 0, IDhClientWorld.TICK_RATE_IN_MS); }, 0, IDhClientWorld.TICK_RATE_IN_MS);
} }
@@ -84,24 +97,51 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
return null; return null;
} }
return this.levels.computeIfAbsent((IClientLevelWrapper) wrapper, IClientLevelWrapper clientLevelWrapper = (IClientLevelWrapper) wrapper;
(clientLevelWrapper) -> clientLevelWrapper.markAccessed();
DhClientLevel storedLevel = this.clientLevelByDhId.computeIfAbsent(wrapper.getDhIdentifier(),
(key) -> createClientLevel(clientLevelWrapper)
);
if (storedLevel != null
&& storedLevel.getClientLevelWrapper() != wrapper)
{
unloadLevel(storedLevel.getLevelWrapper());
storedLevel = createClientLevel(clientLevelWrapper);
if (storedLevel != null)
{ {
try this.clientLevelByDhId.put(wrapper.getDhIdentifier(), storedLevel);
{ }
return new DhClientLevel(this.saveStructure, clientLevelWrapper, this.networkState); }
} return storedLevel;
catch (Exception e) }
{ private DhClientLevel createClientLevel(@NotNull IClientLevelWrapper clientLevelWrapper)
LOGGER.fatal("Failed to load client level, error: ["+e.getMessage()+"].", e); {
try
ClientApi.INSTANCE.showChatMessageNextFrame( {
MinecraftTextFormat.RED + "Distant Horizons: Client level loading failed." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" + if (!ClientApi.INSTANCE.canLoadClientLevel(clientLevelWrapper))
"Unable to load level ["+clientLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information."); {
return null;
return null; }
}
}); DhClientLevel level = new DhClientLevel(this.saveStructure, clientLevelWrapper, this.networkState);
clientLevelWrapperSetByDhId.computeIfAbsent(clientLevelWrapper.getDhIdentifier(), (dhId) -> 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
@@ -112,28 +152,39 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
return null; return null;
} }
return this.levels.get(wrapper); return this.clientLevelByDhId.get(wrapper.getDhIdentifier());
} }
@Override @Override
public Iterable<? extends IDhLevel> getAllLoadedLevels() { return this.levels.values(); } public Iterable<? extends IDhLevel> getAllLoadedLevels() { return this.clientLevelByDhId.values(); }
@Override @Override
public int getLoadedLevelCount() { return this.levels.size(); } public int getLoadedLevelCount() { return this.clientLevelByDhId.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.clientLevelByDhId.containsKey(wrapper.getDhIdentifier()))
{ {
LOGGER.info("Unloading level " + this.levels.get(wrapper)); LOGGER.info("Unloading level [" + this.clientLevelByDhId.get(wrapper.getDhIdentifier()) + "].");
wrapper.onUnload(); wrapper.onUnload();
this.levels.remove(wrapper).close(); Set<IClientLevelWrapper> wrapperSet = this.clientLevelWrapperSetByDhId.get(wrapper.getDhIdentifier());
wrapperSet.remove(wrapper);
if (wrapperSet.isEmpty())
{
this.clientLevelByDhId.remove(wrapper.getDhIdentifier()).close();
}
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(wrapper));
return true;
} }
return false;
} }
@Override @Override
@@ -149,13 +200,14 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
this.networkState.close(); this.networkState.close();
ArrayList<CompletableFuture<Void>> closeFutures = new ArrayList<>(); ArrayList<CompletableFuture<Void>> closeFutures = new ArrayList<>();
for (DhClientLevel dhClientLevel : this.levels.values()) for (DhClientLevel dhClientLevel : this.clientLevelByDhId.values())
{ {
// level wrapper shouldn't be null, but just in case // level wrapper shouldn't be null, but just in case
IClientLevelWrapper clientLevelWrapper = dhClientLevel.getClientLevelWrapper(); IClientLevelWrapper clientLevelWrapper = dhClientLevel.getClientLevelWrapper();
if (clientLevelWrapper != null) if (clientLevelWrapper != null)
{ {
clientLevelWrapper.onUnload(); clientLevelWrapper.onUnload();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(clientLevelWrapper));
} }
@@ -177,7 +229,8 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
future.join(); future.join();
} }
this.levels.clear(); this.clientLevelByDhId.clear();
this.clientLevelWrapperSetByDhId.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,22 @@ 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
@@ -41,7 +41,6 @@ import java.util.concurrent.CompletableFuture;
*/ */
public interface IDhWorld extends Closeable public interface IDhWorld extends Closeable
{ {
@Nullable @Nullable
IDhLevel getOrLoadLevel(@NotNull ILevelWrapper levelWrapper); IDhLevel getOrLoadLevel(@NotNull ILevelWrapper levelWrapper);
@Nullable @Nullable
@@ -49,6 +48,14 @@ public interface IDhWorld extends Closeable
Iterable<? extends IDhLevel> getAllLoadedLevels(); Iterable<? extends IDhLevel> getAllLoadedLevels();
int getLoadedLevelCount(); int getLoadedLevelCount();
void unloadLevel(@NotNull ILevelWrapper levelWrapper); /**
* Returns
* true if the level was unloaded,
* false if the level isn't present in this world
* or couldn't be unloaded for some other reason
*/
boolean unloadLevel(@NotNull ILevelWrapper levelWrapper);
} }
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler; import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable; import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
import org.jetbrains.annotations.Nullable;
public interface IMinecraftClientWrapper extends IBindable public interface IMinecraftClientWrapper extends IBindable
{ {
@@ -64,11 +65,13 @@ public interface IMinecraftClientWrapper extends IBindable
* Returns the level the client is currently in. <br> * Returns the level the client is currently in. <br>
* Returns null if the client isn't in a level. * Returns null if the client isn't in a level.
*/ */
@Nullable
IClientLevelWrapper getWrappedClientLevel(); IClientLevelWrapper getWrappedClientLevel();
/** /**
* Returns the level the client is currently in. <br> * Returns the level the client is currently in. <br>
* Returns null if the client isn't in a level. * Returns null if the client isn't in a level.
*/ */
@Nullable
IClientLevelWrapper getWrappedClientLevel(boolean bypassLevelKeyManager); IClientLevelWrapper getWrappedClientLevel(boolean bypassLevelKeyManager);
@@ -99,8 +99,6 @@ public interface IMinecraftRenderWrapper extends IBindable
@Nullable @Nullable
ILightMapWrapper getLightmapWrapper(@NotNull ILevelWrapper level); ILightMapWrapper getLightmapWrapper(@NotNull ILevelWrapper level);
float getShade(EDhDirection lodDirection);
} }
@@ -19,7 +19,9 @@
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 org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
@@ -31,6 +33,9 @@ public interface IMinecraftSharedWrapper extends IBindable
int getPlayerCount(); int getPlayerCount();
/** If used on the client will only return a non-null object if the client is hosting a LAN server */
@Nullable
IServerLevelWrapper getWrappedServerLevelWithDimensionResourceLocation(String dimensionResourceLocation);
} }
@@ -0,0 +1,157 @@
/*
* 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 AbstractImmersivePortalsAccessor implements IImmersivePortalsAccessor
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static MethodHandle isRenderingMethodHandle;
//=============//
// constructor //
//=============//
//region
public AbstractImmersivePortalsAccessor()
{
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;
}
}
}
//endregion
//===========//
// overrides //
//===========//
//region
@Override
public String getModName() { return "Immersive Portals"; }
@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);
}
}
//endregion
//=======//
// 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
}
@@ -0,0 +1,77 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.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
{
/**
* Returns true if Immersive Portals is currently rendering a portal.
* This can be used to determine if the level currently being rendered
* is being seen through a portal if called on the render thread.
*/
boolean isRenderingPortal();
/**
* Returns the player's position for the level they're currently in.
* <br><br>
* Necessary since Immersive Portals messes with vanilla MC's
* variables in order to render the camera in multiple dimensions.
*/
@Nullable
DhBlockPos getActualPlayerBlockPos();
/**
* Returns the player's position for the level they're currently in.
* <br><br>
* Necessary since Immersive Portals messes with vanilla MC's
* variables in order to render the camera in multiple dimensions.
*/
@Nullable
DhChunkPos getActualPlayerChunkPos();
/**
* Returns the client level the player is currently in.
* <br><br>
* Necessary since Immersive Portals messes with vanilla MC's
* variables in order to render the camera in multiple dimensions.
*/
@Nullable
IClientLevelWrapper getActualClientLevelWrapper();
/**
* Returns the camera position for the level the player is currently in.
* <br><br>
* Necessary since Immersive Portals messes with vanilla MC's
* variables in order to render the camera in multiple dimensions.
*/
@Nullable
Vec3d getActualCameraPos();
}
@@ -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();
@@ -41,4 +45,6 @@ public interface IClientLevelWrapper extends ILevelWrapper
Color getCloudColor(float tickDelta); Color getCloudColor(float tickDelta);
float getShade(EDhDirection lodDirection);
} }