add server support for immersive portals
This commit is contained in:
committed by
Acuadragon100
parent
e65b1e2dfc
commit
2e1a2367bd
@@ -86,7 +86,10 @@ public class ImmersivePortalsCompat
|
|||||||
*/
|
*/
|
||||||
public static void resetDetection()
|
public static void resetDetection()
|
||||||
{
|
{
|
||||||
isImmersivePortalsPresent = null;
|
synchronized (ImmersivePortalsCompat.class)
|
||||||
isImmersivePortalsActive = null;
|
{
|
||||||
|
isImmersivePortalsPresent = null;
|
||||||
|
isImmersivePortalsActive = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-3
@@ -661,15 +661,21 @@ public class ChunkWrapper implements IChunkWrapper
|
|||||||
@Override
|
@Override
|
||||||
public String toString() { return this.chunk.getClass().getSimpleName() + this.chunk.getPos(); }
|
public String toString() { return this.chunk.getClass().getSimpleName() + this.chunk.getPos(); }
|
||||||
|
|
||||||
//@Override
|
//@Override
|
||||||
//public int hashCode()
|
//public int hashCode()
|
||||||
//{
|
//{
|
||||||
// if (this.blockBiomeHashCode == 0)
|
// if (this.blockBiomeHashCode == 0)
|
||||||
// {
|
// {
|
||||||
// this.blockBiomeHashCode = this.getBlockBiomeHashCode();
|
// this.blockBiomeHashCode = this.getBlockBiomeHashCode();
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// return this.blockBiomeHashCode;
|
// return this.blockBiomeHashCode;
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IChunkWrapper copy()
|
||||||
|
{
|
||||||
|
return new ChunkWrapper(this.chunk, this.wrappedLevel, false);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+117
-11
@@ -1,20 +1,39 @@
|
|||||||
package com.seibel.distanthorizons.common.wrappers.level;
|
package com.seibel.distanthorizons.common.wrappers.level;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.common.wrappers.world.ClientLevelWrapper;
|
||||||
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
|
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
|
||||||
import com.seibel.distanthorizons.core.level.IKeyedClientLevelManager;
|
import com.seibel.distanthorizons.core.level.IKeyedClientLevelManager;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.multiplayer.ClientLevel;
|
import net.minecraft.client.multiplayer.ClientLevel;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
public class KeyedClientLevelManager implements IKeyedClientLevelManager
|
public class KeyedClientLevelManager implements IKeyedClientLevelManager
|
||||||
{
|
{
|
||||||
public static final KeyedClientLevelManager INSTANCE = new KeyedClientLevelManager();
|
public static final KeyedClientLevelManager INSTANCE = new KeyedClientLevelManager();
|
||||||
|
|
||||||
/** This is set and managed by the ClientApi for servers with support for DH. */
|
private static class KeyInfo {
|
||||||
@Nullable
|
public final String serverKey;
|
||||||
private IServerKeyedClientLevel serverKeyedLevel = null;
|
public final String levelKey;
|
||||||
|
public KeyInfo(String serverKey, String levelKey) {
|
||||||
|
this.serverKey = serverKey;
|
||||||
|
this.levelKey = levelKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Stores the server-provided keys indexed by dimension name for persistence. */
|
||||||
|
private final Map<String, KeyInfo> keysByDimensionName = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/** Cache for already keyed level wrappers to maintain object identity. */
|
||||||
|
private final Map<ClientLevel, IServerKeyedClientLevel> keyedLevelsCache = Collections.synchronizedMap(new WeakHashMap<>());
|
||||||
|
|
||||||
/** Allows to keep level manager enabled between loading different keyed levels */
|
/** Allows to keep level manager enabled between loading different keyed levels */
|
||||||
private boolean enabled = false;
|
private volatile boolean enabled = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -33,23 +52,110 @@ public class KeyedClientLevelManager implements IKeyedClientLevelManager
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public IServerKeyedClientLevel getServerKeyedLevel() { return this.serverKeyedLevel; }
|
public IServerKeyedClientLevel getServerKeyedLevel()
|
||||||
|
{
|
||||||
|
return this.getServerKeyedLevel(Minecraft.getInstance().level);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public IServerKeyedClientLevel getServerKeyedLevel(@Nullable ClientLevel level)
|
||||||
|
{
|
||||||
|
if (level == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We synchronize on the cache map to ensure atomicity of the lookup-and-populate sequence.
|
||||||
|
// This prevents multiple threads from creating duplicate wrappers for the same level.
|
||||||
|
synchronized (this.keyedLevelsCache)
|
||||||
|
{
|
||||||
|
// Check the cache first
|
||||||
|
IServerKeyedClientLevel cached = this.keyedLevelsCache.get(level);
|
||||||
|
if (cached != null)
|
||||||
|
{
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the dimension name for this level
|
||||||
|
// We use bypassLevelKeyManager=true to avoid recursion back into this manager
|
||||||
|
IClientLevelWrapper wrappedLevel = ClientLevelWrapper.getWrapper(level, true);
|
||||||
|
if (wrappedLevel == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String dimensionName = wrappedLevel.getDimensionName();
|
||||||
|
KeyInfo info = this.keysByDimensionName.get(dimensionName);
|
||||||
|
if (info == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and cache a new keyed wrapper
|
||||||
|
IServerKeyedClientLevel keyedLevel = new ServerKeyedClientLevelWrapper(level, info.serverKey, info.levelKey);
|
||||||
|
this.keyedLevelsCache.put(level, keyedLevel);
|
||||||
|
return keyedLevel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IServerKeyedClientLevel setServerKeyedLevel(IClientLevelWrapper clientLevel, String serverKey, String levelKey)
|
public IServerKeyedClientLevel setServerKeyedLevel(IClientLevelWrapper clientLevel, String serverKey, String levelKey)
|
||||||
{
|
{
|
||||||
IServerKeyedClientLevel keyedLevel = new ServerKeyedClientLevelWrapper((ClientLevel) clientLevel.getWrappedMcObject(), serverKey, levelKey);
|
// 1. Determine the target dimension name
|
||||||
this.serverKeyedLevel = keyedLevel;
|
String targetDimensionName = clientLevel.getDimensionName();
|
||||||
|
int separatorIndex = levelKey.lastIndexOf("@");
|
||||||
|
if (separatorIndex != -1)
|
||||||
|
{
|
||||||
|
targetDimensionName = levelKey.substring(separatorIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String finalTargetDimensionName = targetDimensionName;
|
||||||
|
|
||||||
|
// 2. Store the key for this dimension
|
||||||
|
this.keysByDimensionName.put(finalTargetDimensionName, new KeyInfo(serverKey, levelKey));
|
||||||
this.enabled = true;
|
this.enabled = true;
|
||||||
return keyedLevel;
|
|
||||||
|
// 3. Clear the cache for this dimension to ensure new wrappers are created with the new key
|
||||||
|
// (though in practice keys shouldn't change mid-session)
|
||||||
|
//
|
||||||
|
// We synchronize manually on the map to ensure atomicity of the compound removal operation
|
||||||
|
// and to prevent race conditions or deadlocks with other threads accessing the map.
|
||||||
|
// We avoid calling ClientLevelWrapper.getWrapper() inside the lock to prevent circular lock dependencies.
|
||||||
|
synchronized (this.keyedLevelsCache)
|
||||||
|
{
|
||||||
|
this.keyedLevelsCache.keySet().removeIf(level -> {
|
||||||
|
#if MC_VER <= MC_1_21_10
|
||||||
|
String levelDim = level.dimension().location().toString();
|
||||||
|
#else
|
||||||
|
String levelDim = level.dimension().identifier().toString();
|
||||||
|
#endif
|
||||||
|
return levelDim.equals(finalTargetDimensionName);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Return the keyed wrapper for whatever level the core passed us,
|
||||||
|
// but only if it matches the dimension we just keyed.
|
||||||
|
return this.getServerKeyedLevel((ClientLevel) clientLevel.getWrappedMcObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearKeyedLevel() { this.serverKeyedLevel = null; }
|
public void clearKeyedLevel()
|
||||||
|
{
|
||||||
|
synchronized (this.keyedLevelsCache)
|
||||||
|
{
|
||||||
|
this.keyedLevelsCache.clear();
|
||||||
|
this.keysByDimensionName.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEnabled() { return this.enabled; }
|
public boolean isEnabled() { return this.enabled; }
|
||||||
@Override
|
|
||||||
public void disable() { this.enabled = false; }
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disable()
|
||||||
|
{
|
||||||
|
this.enabled = false;
|
||||||
|
this.clearKeyedLevel();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+65
-4
@@ -6,6 +6,7 @@ import com.seibel.distanthorizons.common.wrappers.block.BiomeWrapper;
|
|||||||
import com.seibel.distanthorizons.common.wrappers.block.BlockStateWrapper;
|
import com.seibel.distanthorizons.common.wrappers.block.BlockStateWrapper;
|
||||||
import com.seibel.distanthorizons.common.wrappers.block.ClientBlockStateColorCache;
|
import com.seibel.distanthorizons.common.wrappers.block.ClientBlockStateColorCache;
|
||||||
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
|
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
|
||||||
|
import com.seibel.distanthorizons.common.wrappers.level.KeyedClientLevelManager;
|
||||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
@@ -104,7 +105,7 @@ public class ClientLevelWrapper implements IClientLevelWrapper
|
|||||||
public synchronized void markRendered() {
|
public synchronized void markRendered() {
|
||||||
this.lastRenderTime = System.currentTimeMillis();
|
this.lastRenderTime = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
public long getLastRenderTime() { return this.lastRenderTime; }
|
public synchronized long getLastRenderTime() { return this.lastRenderTime; }
|
||||||
public boolean isDhLevelLoaded() {
|
public boolean isDhLevelLoaded() {
|
||||||
return this.dhLevel != null;
|
return this.dhLevel != null;
|
||||||
}
|
}
|
||||||
@@ -125,6 +126,7 @@ public class ClientLevelWrapper implements IClientLevelWrapper
|
|||||||
ClientLevelWrapper wrapper = ref.get();
|
ClientLevelWrapper wrapper = ref.get();
|
||||||
if (wrapper != null && wrapper.isDhLevelLoaded() && wrapper.level != MINECRAFT.level)
|
if (wrapper != null && wrapper.isDhLevelLoaded() && wrapper.level != MINECRAFT.level)
|
||||||
{
|
{
|
||||||
|
// We use the synchronized getter to prevent race conditions with markRendered()
|
||||||
if (currentTime - wrapper.getLastRenderTime() > timeout)
|
if (currentTime - wrapper.getLastRenderTime() > timeout)
|
||||||
{
|
{
|
||||||
toUnload.add(wrapper);
|
toUnload.add(wrapper);
|
||||||
@@ -148,6 +150,23 @@ public class ClientLevelWrapper implements IClientLevelWrapper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static ClientLevelWrapper getWrapperByDimensionName(String dimensionName)
|
||||||
|
{
|
||||||
|
synchronized (LEVEL_WRAPPER_REF_BY_CLIENT_LEVEL)
|
||||||
|
{
|
||||||
|
for (WeakReference<ClientLevelWrapper> ref : LEVEL_WRAPPER_REF_BY_CLIENT_LEVEL.values())
|
||||||
|
{
|
||||||
|
ClientLevelWrapper wrapper = ref.get();
|
||||||
|
if (wrapper != null && wrapper.getDimensionName().equals(dimensionName))
|
||||||
|
{
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -157,9 +176,24 @@ public class ClientLevelWrapper implements IClientLevelWrapper
|
|||||||
@Nullable
|
@Nullable
|
||||||
public static IClientLevelWrapper getWrapperIfDifferent(@Nullable IClientLevelWrapper levelWrapper, @NotNull ClientLevel level)
|
public static IClientLevelWrapper getWrapperIfDifferent(@Nullable IClientLevelWrapper levelWrapper, @NotNull ClientLevel level)
|
||||||
{
|
{
|
||||||
if (KEYED_CLIENT_LEVEL_MANAGER.isEnabled() && KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel() != levelWrapper)
|
if (KEYED_CLIENT_LEVEL_MANAGER.isEnabled())
|
||||||
{
|
{
|
||||||
return getWrapper(level);
|
IServerKeyedClientLevel keyedLevel = null;
|
||||||
|
if (KEYED_CLIENT_LEVEL_MANAGER instanceof KeyedClientLevelManager)
|
||||||
|
{
|
||||||
|
keyedLevel = ((KeyedClientLevelManager) KEYED_CLIENT_LEVEL_MANAGER).getServerKeyedLevel(level);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// FIXME: If the implementation is not KeyedClientLevelManager,
|
||||||
|
// this fallback may return the key for the wrong dimension in multiverse scenarios.
|
||||||
|
keyedLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyedLevel != levelWrapper)
|
||||||
|
{
|
||||||
|
return getWrapper(level);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientLevelWrapper clientLevelWrapper = (ClientLevelWrapper)levelWrapper;
|
ClientLevelWrapper clientLevelWrapper = (ClientLevelWrapper)levelWrapper;
|
||||||
@@ -186,7 +220,18 @@ public class ClientLevelWrapper implements IClientLevelWrapper
|
|||||||
}
|
}
|
||||||
|
|
||||||
// used if the client is connected to a server that defines the currently loaded level
|
// used if the client is connected to a server that defines the currently loaded level
|
||||||
IServerKeyedClientLevel overrideLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel();
|
IServerKeyedClientLevel overrideLevel = null;
|
||||||
|
if (KEYED_CLIENT_LEVEL_MANAGER instanceof KeyedClientLevelManager)
|
||||||
|
{
|
||||||
|
overrideLevel = ((KeyedClientLevelManager) KEYED_CLIENT_LEVEL_MANAGER).getServerKeyedLevel(level);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// FIXME: If the implementation is not KeyedClientLevelManager,
|
||||||
|
// this fallback may return the key for the wrong dimension in multiverse scenarios.
|
||||||
|
overrideLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel();
|
||||||
|
}
|
||||||
|
|
||||||
if (overrideLevel != null)
|
if (overrideLevel != null)
|
||||||
{
|
{
|
||||||
return overrideLevel;
|
return overrideLevel;
|
||||||
@@ -419,6 +464,22 @@ public class ClientLevelWrapper implements IClientLevelWrapper
|
|||||||
return this.dimMinHeight;
|
return this.dimMinHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IChunkWrapper tryGetChunk(DhChunkPos pos)
|
||||||
|
{
|
||||||
|
if (!this.level.hasChunk(pos.getX(), pos.getZ()))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChunkAccess chunk = this.level.getChunk(pos.getX(), pos.getZ(), ChunkStatus.EMPTY, false);
|
||||||
|
if (chunk == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ChunkWrapper(chunk, this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ClientLevel getWrappedMcObject() { return this.level; }
|
public ClientLevel getWrappedMcObject() { return this.level; }
|
||||||
|
|
||||||
|
|||||||
+41
@@ -49,6 +49,12 @@ import net.minecraft.world.level.chunk.ChunkStatus;
|
|||||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if MC_VER <= MC_1_21_10
|
||||||
|
#else
|
||||||
|
import net.minecraft.world.level.ChunkPos;
|
||||||
|
import net.minecraft.server.level.ChunkHolder;
|
||||||
|
#endif
|
||||||
|
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@@ -223,6 +229,41 @@ public class ServerLevelWrapper implements IServerLevelWrapper
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IChunkWrapper tryGetChunk(DhChunkPos pos)
|
||||||
|
{
|
||||||
|
#if MC_VER < MC_1_21_11
|
||||||
|
if (!this.level.hasChunk(pos.getX(), pos.getZ()))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChunkAccess chunk = this.level.getChunk(pos.getX(), pos.getZ(), ChunkStatus.FULL, false);
|
||||||
|
if (chunk == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ChunkWrapper(chunk, this);
|
||||||
|
#else
|
||||||
|
|
||||||
|
// directly hitting the chunkMap is required otherwise MC will run this on the main server thread,
|
||||||
|
// causing lag
|
||||||
|
ChunkHolder chunkHolder = this.level.getChunkSource().chunkMap.getVisibleChunkIfPresent(new ChunkPos(pos.getX(), pos.getZ()).toLong());
|
||||||
|
if (chunkHolder == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChunkAccess chunk = chunkHolder.getChunkIfPresent(ChunkStatus.FULL);
|
||||||
|
if (chunk == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ChunkWrapper(chunk, this);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ServerLevel getWrappedMcObject() { return this.level; }
|
public ServerLevel getWrappedMcObject() { return this.level; }
|
||||||
|
|
||||||
|
|||||||
@@ -143,6 +143,16 @@ public class FabricServerProxy implements AbstractModInitializer.IEventProxy
|
|||||||
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) ->
|
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) ->
|
||||||
{
|
{
|
||||||
ServerApi.INSTANCE.serverPlayerJoinEvent(this.getServerPlayerWrapper(handler.player));
|
ServerApi.INSTANCE.serverPlayerJoinEvent(this.getServerPlayerWrapper(handler.player));
|
||||||
|
|
||||||
|
// Send identification for all loaded levels to the joining player
|
||||||
|
// This is necessary for Immersive Portals which can render multiple dimensions at once
|
||||||
|
for (ServerLevel level : server.getAllLevels())
|
||||||
|
{
|
||||||
|
if (level != handler.player.level())
|
||||||
|
{
|
||||||
|
ServerApi.INSTANCE.serverLevelLoadEvent(this.getServerLevelWrapper(level));
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
ServerPlayConnectionEvents.DISCONNECT.register((handler, server) ->
|
ServerPlayConnectionEvents.DISCONNECT.register((handler, server) ->
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user