Make level key requests work kinda

This commit is contained in:
s809
2026-06-04 23:05:14 +05:00
parent 4f0a3afd93
commit 28a8bc39f2
3 changed files with 58 additions and 71 deletions
@@ -130,12 +130,6 @@ public class ClientApi
public boolean rendererDisabledBecauseOfExceptions = false;
private final ClientPluginChannelApi pluginChannelApi = new ClientPluginChannelApi();
/** Delay loading the first level to give the server some time to respond with level to actually load */
private Timer firstLevelLoadTimer;
private static final long FIRST_LEVEL_LOAD_DELAY_IN_MS = 1_000;
/** Holds any levels that were loaded before the {@link ClientApi#onClientOnlyConnected} was fired. */
public final HashSet<IClientLevelWrapper> waitingClientLevels = new HashSet<>();
/** Holds any chunks that were found before the client levels are loaded. */
@@ -220,22 +214,12 @@ public class ClientApi
DhClientWorld world = new DhClientWorld();
SharedApi.setDhWorld(world);
this.pluginChannelApi.onJoinServer(world.networkState.getSession());
world.networkState.sendConfigMessage();
}
}
/** Synchronized to prevent a rare issue where multiple disconnect events are triggered on top of each other. */
public synchronized void onClientOnlyDisconnected()
{
// clear the first time timer
if (this.firstLevelLoadTimer != null)
{
this.firstLevelLoadTimer.cancel();
this.firstLevelLoadTimer = null;
}
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null)
{
@@ -245,8 +229,6 @@ public class ClientApi
SharedApi.setDhWorld(null);
}
this.pluginChannelApi.reset();
// remove any waiting items
this.waitingChunkByClientLevelAndPos.clear();
}
@@ -255,55 +237,6 @@ public class ClientApi
//==============//
// level events //
//==============//
//region level events
/**
* used in conjunction with the server networking to
* handle level load requests.
*/
public boolean canLoadClientLevel(IClientLevelWrapper wrapper)
{
// wait a moment before loading the level to give the server a chance to handle the client's login request
if (MC_CLIENT.clientConnectedToDedicatedServer())
{
if (this.firstLevelLoadTimer == null)
{
this.firstLevelLoadTimer = TimerUtil.CreateTimer("FirstLevelLoadTimer");
this.firstLevelLoadTimer.schedule(new TimerTask()
{
@Override
public void run() { canLoadClientLevel(wrapper); }
}, FIRST_LEVEL_LOAD_DELAY_IN_MS);
return false;
}
this.firstLevelLoadTimer.cancel();
}
if (!this.pluginChannelApi.allowLevelLoading(wrapper))
{
LOGGER.debug("Client levels in this connection are managed by the server, skipping auto-load of: ["+wrapper+"]");
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world == null)
{
return false;
}
// Instead of attempting to load themselves, send the config and wait for a server provided level key.
((DhClientWorld) world).networkState.sendLevelInitRequest(wrapper.getDimensionName());
return false;
}
return true;
}
//endregion
//==============//
// level events //
//==============//
@@ -358,7 +291,8 @@ public class ClientApi
{
executor.execute(() ->
{
NetworkSession networkSession = this.pluginChannelApi.networkSession;
DhClientWorld world = (DhClientWorld) Objects.requireNonNull(SharedApi.tryGetDhClientWorld());
NetworkSession networkSession = world.pluginChannelApi.networkSession;
if (networkSession != null)
{
networkSession.tryHandleMessage(message);
@@ -69,8 +69,6 @@ public class ServerPlayerState implements Closeable
this.networkSession.registerHandler(SessionConfigMessage.class, (sessionConfigMessage) ->
{
this.sessionConfig.constrainingConfig = sessionConfigMessage.config;
this.sendLevelKey();
this.sendConfigMessage();
});
@@ -19,13 +19,16 @@
package com.seibel.distanthorizons.core.world;
import com.google.common.cache.CacheBuilder;
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.ClientPluginChannelApi;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.level.DhClientLevel;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState;
import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
@@ -36,6 +39,8 @@ import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
{
@@ -58,6 +63,14 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
private final Timer clientTickTimer = TimerUtil.CreateTimer("ClientTickTimer");
public final ClientPluginChannelApi pluginChannelApi = new ClientPluginChannelApi();
private static final long FIRST_LEVEL_LOAD_DELAY_IN_MS = 1_000;
/** Delay loading the first level to give the server some time to respond with level to actually load */
private long allowLoadingLevelsAfter = 0;
private final ConcurrentMap<String, Boolean> levelInitRequestDebounce = CacheBuilder.newBuilder()
.expireAfterAccess(FIRST_LEVEL_LOAD_DELAY_IN_MS, TimeUnit.MILLISECONDS)
.<String, Boolean>build()
.asMap();
//==============//
@@ -73,6 +86,9 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
LOGGER.info("Started DhWorld of type " + this.environment);
this.pluginChannelApi.onJoinServer(networkState.getSession());
this.networkState.sendConfigMessage();
this.clientTickTimer.scheduleAtFixedRate(new TimerTask()
{
@Override
@@ -119,7 +135,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
{
try
{
if (!ClientApi.INSTANCE.canLoadClientLevel(clientLevelWrapper))
if (!this.ensureLevelKeyWhenAvailable(clientLevelWrapper))
{
return null;
}
@@ -148,6 +164,44 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
}
}
private boolean ensureLevelKeyWhenAvailable(@NotNull IClientLevelWrapper clientLevelWrapper)
{
if (!this.pluginChannelApi.allowLevelLoading(clientLevelWrapper))
{
LOGGER.debug("Client levels in this connection are managed by the server, skipping auto-load of: ["+clientLevelWrapper+"]");
// Instead of attempting to load themselves, send the config and wait for a server provided level key
// Debounce is to prevent request spam caused by code trying to load the level every frame
levelInitRequestDebounce.computeIfAbsent(clientLevelWrapper.getDimensionName(), dimensionName -> {
this.networkState.sendLevelInitRequest(dimensionName);
return true;
});
return false;
}
// Make non-keyed levels wait some delay since first attempt to load anything,
// so the server can reply to the level key request
if (!(clientLevelWrapper instanceof IServerKeyedClientLevel))
{
if (this.allowLoadingLevelsAfter == 0)
{
// Debounce is to prevent request spam caused by code trying to load the level every frame
levelInitRequestDebounce.computeIfAbsent(clientLevelWrapper.getDimensionName(), dimensionName -> {
this.networkState.sendLevelInitRequest(dimensionName);
return true;
});
this.allowLoadingLevelsAfter = System.currentTimeMillis() + FIRST_LEVEL_LOAD_DELAY_IN_MS;
}
if (System.currentTimeMillis() < this.allowLoadingLevelsAfter)
{
return false;
}
}
return true;
}
@Override
public DhClientLevel getLevel(@NotNull ILevelWrapper wrapper)
{
@@ -202,6 +256,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
public void close()
{
this.networkState.close();
this.pluginChannelApi.reset();
ArrayList<CompletableFuture<Void>> closeFutures = new ArrayList<>();
for (DhClientLevel dhClientLevel : this.clientLevelByDhId.values())