From 28a8bc39f22403cefd169f252fdad0e2954fce96 Mon Sep 17 00:00:00 2001 From: s809 <43530948+s809@users.noreply.github.com> Date: Thu, 4 Jun 2026 23:05:14 +0500 Subject: [PATCH] Make level key requests work kinda --- .../core/api/internal/ClientApi.java | 70 +------------------ .../multiplayer/server/ServerPlayerState.java | 2 - .../core/world/DhClientWorld.java | 57 ++++++++++++++- 3 files changed, 58 insertions(+), 71 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java index 758e5d0e7..211750ab5 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java @@ -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 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); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServerPlayerState.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServerPlayerState.java index 979c8969a..6ac87b895 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServerPlayerState.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServerPlayerState.java @@ -69,8 +69,6 @@ public class ServerPlayerState implements Closeable this.networkSession.registerHandler(SessionConfigMessage.class, (sessionConfigMessage) -> { this.sessionConfig.constrainingConfig = sessionConfigMessage.config; - - this.sendLevelKey(); this.sendConfigMessage(); }); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java index fecb5f460..e5ab421c0 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java @@ -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 levelInitRequestDebounce = CacheBuilder.newBuilder() + .expireAfterAccess(FIRST_LEVEL_LOAD_DELAY_IN_MS, TimeUnit.MILLISECONDS) + .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> closeFutures = new ArrayList<>(); for (DhClientLevel dhClientLevel : this.clientLevelByDhId.values())