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()
|
||||
{
|
||||
isImmersivePortalsPresent = null;
|
||||
isImmersivePortalsActive = null;
|
||||
synchronized (ImmersivePortalsCompat.class)
|
||||
{
|
||||
isImmersivePortalsPresent = null;
|
||||
isImmersivePortalsActive = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-3
@@ -661,15 +661,21 @@ public class ChunkWrapper implements IChunkWrapper
|
||||
@Override
|
||||
public String toString() { return this.chunk.getClass().getSimpleName() + this.chunk.getPos(); }
|
||||
|
||||
//@Override
|
||||
//@Override
|
||||
//public int hashCode()
|
||||
//{
|
||||
// if (this.blockBiomeHashCode == 0)
|
||||
// {
|
||||
// this.blockBiomeHashCode = this.getBlockBiomeHashCode();
|
||||
// }
|
||||
//
|
||||
//
|
||||
// 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;
|
||||
|
||||
import com.seibel.distanthorizons.common.wrappers.world.ClientLevelWrapper;
|
||||
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
|
||||
import com.seibel.distanthorizons.core.level.IKeyedClientLevelManager;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
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 static final KeyedClientLevelManager INSTANCE = new KeyedClientLevelManager();
|
||||
|
||||
/** This is set and managed by the ClientApi for servers with support for DH. */
|
||||
@Nullable
|
||||
private IServerKeyedClientLevel serverKeyedLevel = null;
|
||||
private static class KeyInfo {
|
||||
public final String serverKey;
|
||||
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 */
|
||||
private boolean enabled = false;
|
||||
private volatile boolean enabled = false;
|
||||
|
||||
|
||||
|
||||
@@ -33,23 +52,110 @@ public class KeyedClientLevelManager implements IKeyedClientLevelManager
|
||||
|
||||
@Override
|
||||
@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
|
||||
public IServerKeyedClientLevel setServerKeyedLevel(IClientLevelWrapper clientLevel, String serverKey, String levelKey)
|
||||
{
|
||||
IServerKeyedClientLevel keyedLevel = new ServerKeyedClientLevelWrapper((ClientLevel) clientLevel.getWrappedMcObject(), serverKey, levelKey);
|
||||
this.serverKeyedLevel = keyedLevel;
|
||||
// 1. Determine the target dimension name
|
||||
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;
|
||||
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
|
||||
public void clearKeyedLevel() { this.serverKeyedLevel = null; }
|
||||
public void clearKeyedLevel()
|
||||
{
|
||||
synchronized (this.keyedLevelsCache)
|
||||
{
|
||||
this.keyedLevelsCache.clear();
|
||||
this.keysByDimensionName.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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.ClientBlockStateColorCache;
|
||||
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.dataObjects.fullData.sources.FullDataSourceV2;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
@@ -104,7 +105,7 @@ public class ClientLevelWrapper implements IClientLevelWrapper
|
||||
public synchronized void markRendered() {
|
||||
this.lastRenderTime = System.currentTimeMillis();
|
||||
}
|
||||
public long getLastRenderTime() { return this.lastRenderTime; }
|
||||
public synchronized long getLastRenderTime() { return this.lastRenderTime; }
|
||||
public boolean isDhLevelLoaded() {
|
||||
return this.dhLevel != null;
|
||||
}
|
||||
@@ -125,6 +126,7 @@ public class ClientLevelWrapper implements IClientLevelWrapper
|
||||
ClientLevelWrapper wrapper = ref.get();
|
||||
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)
|
||||
{
|
||||
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
|
||||
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;
|
||||
@@ -186,7 +220,18 @@ public class ClientLevelWrapper implements IClientLevelWrapper
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
return overrideLevel;
|
||||
@@ -419,6 +464,22 @@ public class ClientLevelWrapper implements IClientLevelWrapper
|
||||
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
|
||||
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;
|
||||
#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 org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -223,6 +229,41 @@ public class ServerLevelWrapper implements IServerLevelWrapper
|
||||
#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
|
||||
public ServerLevel getWrappedMcObject() { return this.level; }
|
||||
|
||||
|
||||
@@ -143,6 +143,16 @@ public class FabricServerProxy implements AbstractModInitializer.IEventProxy
|
||||
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) ->
|
||||
{
|
||||
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) ->
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user