From 2251cd4c25984385a0bf1e00ddffbd91b2d842b7 Mon Sep 17 00:00:00 2001 From: s809 <43530948+s809@users.noreply.github.com> Date: Tue, 1 Aug 2023 17:04:40 +0500 Subject: [PATCH] Attempt to fix dimension switching --- .../core/api/internal/ServerApi.java | 9 + .../WorldRemoteGenerationQueue.java | 17 +- .../core/level/DhClientLevel.java | 4 +- .../core/level/DhServerLevel.java | 143 +++++++++++++- .../RemotePlayerConnectionHandler.java | 89 +++++++++ .../core/network/ChildNetworkEventSource.java | 4 + .../core/network/NetworkClient.java | 16 +- .../core/world/DhClientWorld.java | 4 +- .../core/world/DhServerWorld.java | 176 +++--------------- 9 files changed, 277 insertions(+), 185 deletions(-) create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/multiplayer/RemotePlayerConnectionHandler.java diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ServerApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ServerApi.java index 18c23a3e9..b88fd43b5 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ServerApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ServerApi.java @@ -171,5 +171,14 @@ public class ServerApi ((DhServerWorld) serverWorld).removePlayer(player); } } + public void serverPlayerLevelChangeEvent(IServerPlayerWrapper player, IServerLevelWrapper origin, IServerLevelWrapper dest) + { + IDhServerWorld serverWorld = SharedApi.getIDhServerWorld(); + if (serverWorld instanceof DhServerWorld) // TODO add support for DhClientServerWorld's (lan worlds) as well + { + LOGGER.debug("Player changed level: " + player.getUUID()); + ((DhServerWorld) serverWorld).changePlayerLevel(player, origin, dest); + } + } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java index 2e0e9e527..35146b1b1 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java @@ -24,7 +24,7 @@ import io.netty.channel.ChannelException; import org.apache.logging.log4j.Logger; import java.util.ArrayList; -import java.util.Map; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -84,17 +84,19 @@ public class WorldRemoteGenerationQueue implements IWorldGenerationQueue private void sendNewRequest(DhBlockPos2D targetPos) { - DhSectionPos sectionPos = waitingTasks.keySet().stream().reduce(null, (a, b) + if (!pendingTasksSemaphore.tryAcquire()) + return; + + DhSectionPos sectionPos = Objects.requireNonNull(waitingTasks.keySet().stream().reduce(null, (a, b) -> a != null && a.getCenter().getCenterBlockPos().distSquared(targetPos) < b.getCenter().getCenterBlockPos().distSquared(targetPos) - ? a : b); - + ? a : b)); WorldGenQueueEntry entry = waitingTasks.remove(sectionPos); - pendingTasksSemaphore.acquireUninterruptibly(); eventSource.parent.sendRequest(new FullDataSourceRequestMessage(sectionPos)) - .handle((response, throwable) -> { + .handle((response, throwable) -> + { pendingTasksSemaphore.release(); finishedRequests.incrementAndGet(); @@ -132,7 +134,8 @@ public class WorldRemoteGenerationQueue implements IWorldGenerationQueue finishedRequests.decrementAndGet(); waitingTasks.put(sectionPos, entry); } - catch (ChannelException e) { + catch (ChannelException e) + { finishedRequests.decrementAndGet(); waitingTasks.put(sectionPos, entry); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java index 282869454..634496e20 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java @@ -88,8 +88,8 @@ public class DhClientLevel extends DhLevel implements IDhClientLevel public void doWorldGen() { worldGeneratorEnabledConfig.pollNewValue(); - boolean isClientWorking = networkClient != null && networkClient.isWorking(); - boolean shouldDoWorldGen = worldGeneratorEnabledConfig.get() && isClientWorking && clientside.isRendering(); + boolean isClientUsable = networkClient != null && !networkClient.isClosed(); + boolean shouldDoWorldGen = worldGeneratorEnabledConfig.get() && isClientUsable && clientside.isRendering(); boolean isWorldGenRunning = worldGenModule.isWorldGenRunning(); if (shouldDoWorldGen && !isWorldGenRunning) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java index e4be9bc7b..0bb9f61ab 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java @@ -1,26 +1,53 @@ package com.seibel.distanthorizons.core.level; +import com.seibel.distanthorizons.core.config.AppliedConfigState; +import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource; +import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource; +import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IIncompleteFullDataSource; +import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataFileHandler; import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider; import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure; +import com.seibel.distanthorizons.core.multiplayer.RemotePlayer; +import com.seibel.distanthorizons.core.multiplayer.RemotePlayerConnectionHandler; +import com.seibel.distanthorizons.core.network.ChildNetworkEventSource; +import com.seibel.distanthorizons.core.network.NetworkServer; +import com.seibel.distanthorizons.core.network.exceptions.RateLimitedException; +import com.seibel.distanthorizons.core.network.messages.FullDataSourceRequestMessage; +import com.seibel.distanthorizons.core.network.messages.FullDataSourceResponseMessage; import com.seibel.distanthorizons.core.pos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.DhLodPos; import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.util.LodUtil; +import com.seibel.distanthorizons.core.world.DhServerWorld; +import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; +import com.seibel.distanthorizons.coreapi.util.math.Vec3d; import org.apache.logging.log4j.Logger; -import java.util.concurrent.CompletableFuture; +import javax.annotation.CheckForNull; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.*; public class DhServerLevel extends DhLevel implements IDhServerLevel { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); public final ServerLevelModule serverside; private final IServerLevelWrapper serverLevelWrapper; - - public DhServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper) + + private final RemotePlayerConnectionHandler remotePlayerConnectionHandler; + private final ChildNetworkEventSource eventSource; + + private final ConcurrentLinkedQueue worldGenLoopingQueue = new ConcurrentLinkedQueue<>(); + private final ConcurrentMap incompleteDataSources = new ConcurrentHashMap<>(); + private final AppliedConfigState rateLimitConfig = new AppliedConfigState<>(Config.Client.Advanced.Multiplayer.serverNetworkingRateLimit); + + + public DhServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper, RemotePlayerConnectionHandler remotePlayerConnectionHandler) { if (saveStructure.getFullDataFolder(serverLevelWrapper).mkdirs()) { @@ -29,11 +56,97 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel this.serverLevelWrapper = serverLevelWrapper; serverside = new ServerLevelModule(this, serverLevelWrapper, saveStructure); LOGGER.info("Started DHLevel for {} with saves at {}", serverLevelWrapper, saveStructure); + + this.remotePlayerConnectionHandler = remotePlayerConnectionHandler; + this.eventSource = new ChildNetworkEventSource<>(remotePlayerConnectionHandler.eventSource); + this.registerNetworkHandlers(); + } + + private void registerNetworkHandlers() + { + this.eventSource.registerHandler(FullDataSourceRequestMessage.class, msg -> + { + RemotePlayer remotePlayer = remotePlayerConnectionHandler.getConnectedPlayer(msg); + if (remotePlayer.serverPlayer.getLevel() != this.serverLevelWrapper) + return; + + LOGGER.info("FullDataSourceRequestMessage received at pos ({}, {}) with detail level {}", msg.dhSectionPos.sectionX, msg.dhSectionPos.sectionZ, msg.dhSectionPos.sectionDetailLevel); + + if (remotePlayer.pendingFullDataRequests.incrementAndGet() > rateLimitConfig.get()) + { + remotePlayer.pendingFullDataRequests.decrementAndGet(); + msg.sendResponse(new RateLimitedException("Max concurrent requests: "+rateLimitConfig.get())); + return; + } + + while (true) + { + IncompleteDataSourceEntry entry = incompleteDataSources.computeIfAbsent(msg.dhSectionPos, pos -> { + IncompleteDataSourceEntry newEntry = new IncompleteDataSourceEntry(); + serverside.dataFileHandler.read(msg.dhSectionPos).thenAccept(fullDataSource -> { + newEntry.fullDataSource = fullDataSource; + }); + return newEntry; + }); + // If this fails, current entry is being drained and need create another one + if (entry.requestCollectionSemaphore.tryAcquire()) + { + entry.requestMessages.add(msg); + entry.requestCollectionSemaphore.release(); + break; + } + } + }); + } + + public void addPlayer(IServerPlayerWrapper serverPlayer) + { + synchronized (worldGenLoopingQueue) + { + this.worldGenLoopingQueue.add(serverPlayer); + } + } + + public void removePlayer(IServerPlayerWrapper serverPlayer) + { + synchronized (worldGenLoopingQueue) + { + this.worldGenLoopingQueue.remove(serverPlayer); + } } public void serverTick() { chunkToLodBuilder.tick(); + + for (Iterator it = incompleteDataSources.values().iterator(); it.hasNext(); ) + { + IncompleteDataSourceEntry entry = it.next(); + if (entry.fullDataSource == null) continue; + + if (entry.fullDataSource instanceof IIncompleteFullDataSource) + { + IIncompleteFullDataSource incompleteSource = (IIncompleteFullDataSource) entry.fullDataSource; + if (!incompleteSource.hasBeenPromoted()) continue; + entry.fullDataSource = incompleteSource.tryPromotingToCompleteDataSource(); + } + + LodUtil.assertTrue(entry.fullDataSource instanceof CompleteFullDataSource, "Invalid full data source"); + + it.remove(); + // This semaphore is intentionally acquired forever + entry.requestCollectionSemaphore.acquireUninterruptibly(Short.MAX_VALUE); + + CompleteFullDataSource completeSource = (CompleteFullDataSource) entry.fullDataSource; + for (FullDataSourceRequestMessage msg : entry.requestMessages) + { + RemotePlayer remotePlayer = remotePlayerConnectionHandler.getConnectedPlayer(msg); + if (remotePlayer == null) continue; + remotePlayer.pendingFullDataRequests.decrementAndGet(); + + msg.sendResponse(new FullDataSourceResponseMessage(completeSource, this)); + } + } } @Override @@ -77,14 +190,20 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel // stop world gen serverside.worldGenModule.stopWorldGen(serverside.dataFileHandler); } - } - - public void doWorldGen(DhBlockPos2D pos) { - this.doWorldGen(); if (serverside.worldGenModule.isWorldGenRunning()) { - serverside.worldGenModule.worldGenTick(pos); + IServerPlayerWrapper firstPlayer; + synchronized (worldGenLoopingQueue) + { + firstPlayer = this.worldGenLoopingQueue.poll(); + if (firstPlayer == null) + return; + this.worldGenLoopingQueue.add(firstPlayer); + } + + Vec3d position = firstPlayer.getPosition(); + serverside.worldGenModule.worldGenTick(new DhBlockPos2D((int) position.x, (int) position.z)); } } @@ -106,4 +225,12 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel public void onWorldGenTaskComplete(DhSectionPos pos) { //TODO: Send packet to client } + + private static class IncompleteDataSourceEntry + { + @CheckForNull + public IFullDataSource fullDataSource; + public final Set requestMessages = ConcurrentHashMap.newKeySet(); + public final Semaphore requestCollectionSemaphore = new Semaphore(Short.MAX_VALUE, true); + } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/RemotePlayerConnectionHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/RemotePlayerConnectionHandler.java new file mode 100644 index 000000000..ae626bb38 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/RemotePlayerConnectionHandler.java @@ -0,0 +1,89 @@ +package com.seibel.distanthorizons.core.multiplayer; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.seibel.distanthorizons.core.network.ChildNetworkEventSource; +import com.seibel.distanthorizons.core.network.NetworkServer; +import com.seibel.distanthorizons.core.network.messages.AckMessage; +import com.seibel.distanthorizons.core.network.messages.CloseMessage; +import com.seibel.distanthorizons.core.network.messages.PlayerUUIDMessage; +import com.seibel.distanthorizons.core.network.protocol.NetworkMessage; +import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; +import io.netty.channel.ChannelHandlerContext; + +import java.util.HashMap; +import java.util.UUID; + +public class RemotePlayerConnectionHandler +{ + public final ChildNetworkEventSource eventSource; + private final HashMap playersByUUID = new HashMap<>(); + private final BiMap playersByConnection = HashBiMap.create(); + + public RemotePlayerConnectionHandler(NetworkServer networkServer) + { + this.eventSource = new ChildNetworkEventSource<>(networkServer); + this.registerNetworkHandlers(); + } + + private void registerNetworkHandlers() + { + this.eventSource.registerHandler(PlayerUUIDMessage.class, playerUUIDMessage -> + { + ChannelHandlerContext channelContext = playerUUIDMessage.getChannelContext(); + RemotePlayer dhPlayer = this.playersByUUID.get(playerUUIDMessage.playerUUID); + + if (dhPlayer == null) + { + this.eventSource.parent.disconnectClient(channelContext, "Player is not logged in."); + return; + } + + if (dhPlayer.channelContext != null) + { + this.eventSource.parent.disconnectClient(channelContext, "Another connection is already in use."); + return; + } + + dhPlayer.channelContext = channelContext; + this.playersByConnection.put(channelContext, dhPlayer); + + playerUUIDMessage.sendResponse(new AckMessage()); + }); + + this.eventSource.registerHandler(CloseMessage.class, closeMessage -> + { + RemotePlayer dhPlayer = this.playersByConnection.remove(closeMessage.getChannelContext()); + if (dhPlayer != null) + { + dhPlayer.channelContext = null; + } + }); + } + + public Iterable getConnectedPlayers() + { + return playersByConnection.values(); + } + + public RemotePlayer getConnectedPlayer(NetworkMessage msg) + { + return playersByConnection.get(msg.getChannelContext()); + } + + public void mcPlayerJoined(IServerPlayerWrapper serverPlayer) + { + this.playersByUUID.put(serverPlayer.getUUID(), new RemotePlayer(serverPlayer)); + } + + public void mcPlayerLeft(IServerPlayerWrapper serverPlayer) + { + RemotePlayer dhPlayer = this.playersByUUID.remove(serverPlayer.getUUID()); + ChannelHandlerContext channelContext = this.playersByConnection.inverse().remove(dhPlayer); + if (channelContext != null) + { + this.eventSource.parent.disconnectClient(channelContext, "You are being disconnected."); + } + } + +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/ChildNetworkEventSource.java b/core/src/main/java/com/seibel/distanthorizons/core/network/ChildNetworkEventSource.java index c8535878f..e7a1e4e39 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/ChildNetworkEventSource.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/ChildNetworkEventSource.java @@ -14,6 +14,10 @@ public final class ChildNetworkEventSource e { this.parent = parent; } + public ChildNetworkEventSource(ChildNetworkEventSource child) + { + this.parent = child.parent; + } @Override public void registerHandler(Class handlerClass, Consumer handlerImplementation) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkClient.java b/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkClient.java index 68732d0f7..f9ca5c9e4 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkClient.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/NetworkClient.java @@ -7,7 +7,6 @@ import com.seibel.distanthorizons.core.network.messages.HelloMessage; import com.seibel.distanthorizons.core.network.protocol.FutureTrackableNetworkMessage; import com.seibel.distanthorizons.core.network.protocol.MessageHandler; import com.seibel.distanthorizons.core.network.protocol.NetworkChannelInitializer; -import com.seibel.distanthorizons.core.network.protocol.NetworkMessage; import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; @@ -26,18 +25,13 @@ public class NetworkClient extends NetworkEventSource implements AutoCloseable private enum EConnectionState { - NOT_CONNECTING, + INITIAL, OPEN, RECONNECT, RECONNECT_FORCE, CLOSE_WAIT, CLOSED } - private static final Set workingStates = EnumSet.of( - EConnectionState.OPEN, - EConnectionState.RECONNECT, - EConnectionState.RECONNECT_FORCE - ); private static final Set closedStates = EnumSet.of( EConnectionState.CLOSE_WAIT, EConnectionState.CLOSED @@ -50,9 +44,7 @@ public class NetworkClient extends NetworkEventSource implements AutoCloseable private final InetSocketAddress address; /** Indicates whether the client is initialized and not started connecting yet. */ - public boolean isNotConnecting() { return this.connectionState == EConnectionState.NOT_CONNECTING; } - /** Indicates whether the client is working or in auto-recoverable state. */ - public boolean isWorking() { return workingStates.contains(this.connectionState); } + public boolean isInitialState() { return this.connectionState == EConnectionState.INITIAL; } /** Indicates whether the client is closed(-ing) and should not be used. */ public boolean isClosed() { return closedStates.contains(this.connectionState); } private boolean isReady; @@ -66,7 +58,7 @@ public class NetworkClient extends NetworkEventSource implements AutoCloseable .option(ChannelOption.SO_KEEPALIVE, true) .handler(new NetworkChannelInitializer(new MessageHandler(this::handleMessage))); - private EConnectionState connectionState = EConnectionState.NOT_CONNECTING; + private EConnectionState connectionState = EConnectionState.INITIAL; private Channel channel; private int reconnectAttempts = FAILURE_RECONNECT_ATTEMPTS; @@ -98,7 +90,7 @@ public class NetworkClient extends NetworkEventSource implements AutoCloseable public void startConnecting() { - if (!isNotConnecting()) return; + if (!isInitialState()) return; this.connect(); } 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 053f96291..2885b0e1f 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 @@ -143,9 +143,9 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld public void clientTick() { this.eventLoop.tick(); } public void doWorldGen() { - if (networkClient != null && networkClient.isNotConnecting()) - networkClient.startConnecting(); this.levels.values().forEach(DhClientLevel::doWorldGen); + if (networkClient != null && networkClient.isInitialState()) + networkClient.startConnecting(); } @Override diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java index 16c6786db..4eb0fa747 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java @@ -1,33 +1,22 @@ package com.seibel.distanthorizons.core.world; -import com.google.common.collect.BiMap; -import com.google.common.collect.HashBiMap; import com.seibel.distanthorizons.core.config.AppliedConfigState; import com.seibel.distanthorizons.core.config.Config; -import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource; -import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource; -import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IIncompleteFullDataSource; -import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataFileHandler; import com.seibel.distanthorizons.core.file.structure.LocalSaveStructure; import com.seibel.distanthorizons.core.level.DhServerLevel; import com.seibel.distanthorizons.core.level.IDhLevel; -import com.seibel.distanthorizons.core.network.NetworkServer; -import com.seibel.distanthorizons.core.network.exceptions.RateLimitedException; -import com.seibel.distanthorizons.core.network.messages.*; import com.seibel.distanthorizons.core.multiplayer.RemotePlayer; -import com.seibel.distanthorizons.core.pos.DhBlockPos2D; -import com.seibel.distanthorizons.core.pos.DhSectionPos; +import com.seibel.distanthorizons.core.multiplayer.RemotePlayerConnectionHandler; +import com.seibel.distanthorizons.core.network.NetworkServer; +import com.seibel.distanthorizons.core.network.messages.RemotePlayerConfigMessage; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; -import com.seibel.distanthorizons.coreapi.util.math.Vec3d; -import io.netty.channel.ChannelHandlerContext; -import javax.annotation.CheckForNull; import java.io.File; -import java.util.*; -import java.util.concurrent.*; +import java.util.HashMap; +import java.util.concurrent.CompletableFuture; public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld { @@ -35,14 +24,11 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld public final LocalSaveStructure saveStructure; private final NetworkServer networkServer; - private final HashMap playersByUUID; - private final BiMap playersByConnection; - - private final ConcurrentLinkedQueue worldGenLoopingQueue = new ConcurrentLinkedQueue<>(); - private final ConcurrentMap incompleteDataSources = new ConcurrentHashMap<>(); + private final RemotePlayerConnectionHandler remotePlayerConnectionHandler; private final AppliedConfigState rateLimitConfig = new AppliedConfigState<>(Config.Client.Advanced.Multiplayer.serverNetworkingRateLimit); - - + + + public DhServerWorld() { super(EWorldEnvironment.Server_Only); @@ -52,103 +38,35 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld // TODO move to global payload once server specific configs are implemented this.networkServer = new NetworkServer(25049); - this.playersByUUID = new HashMap<>(); - this.playersByConnection = HashBiMap.create(); this.registerNetworkHandlers(); + this.remotePlayerConnectionHandler = new RemotePlayerConnectionHandler(networkServer); LOGGER.info("Started "+DhServerWorld.class.getSimpleName()+" of type "+this.environment); } private void registerNetworkHandlers() { - this.networkServer.registerHandler(CloseMessage.class, closeMessage -> - { - RemotePlayer dhPlayer = this.playersByConnection.remove(closeMessage.getChannelContext()); - if (dhPlayer != null) - { - dhPlayer.channelContext = null; - } - }); - - this.networkServer.registerHandler(PlayerUUIDMessage.class, playerUUIDMessage -> - { - ChannelHandlerContext channelContext = playerUUIDMessage.getChannelContext(); - RemotePlayer dhPlayer = this.playersByUUID.get(playerUUIDMessage.playerUUID); - - if (dhPlayer == null) - { - this.networkServer.disconnectClient(channelContext, "Player is not logged in."); - return; - } - - if (dhPlayer.channelContext != null) - { - this.networkServer.disconnectClient(channelContext, "Another connection is already in use."); - return; - } - - dhPlayer.channelContext = channelContext; - this.playersByConnection.put(channelContext, dhPlayer); - - playerUUIDMessage.sendResponse(new AckMessage()); - }); - this.networkServer.registerHandler(RemotePlayerConfigMessage.class, remotePlayerConfigMessage -> { remotePlayerConfigMessage.payload.fullDataRequestRateLimit = Math.min(rateLimitConfig.get(), remotePlayerConfigMessage.payload.fullDataRequestRateLimit); remotePlayerConfigMessage.sendResponse(remotePlayerConfigMessage); }); - - // This should be at DhServerLevel I guess - this.networkServer.registerHandler(FullDataSourceRequestMessage.class, msg -> - { - LOGGER.info("FullDataSourceRequestMessage received at pos ({}, {}) with detail level {}", msg.dhSectionPos.sectionX, msg.dhSectionPos.sectionZ, msg.dhSectionPos.sectionDetailLevel); - - RemotePlayer remotePlayer = playersByConnection.get(msg.getChannelContext()); - if (remotePlayer.pendingFullDataRequests.incrementAndGet() > rateLimitConfig.get()) - { - remotePlayer.pendingFullDataRequests.decrementAndGet(); - msg.sendResponse(new RateLimitedException("Max concurrent requests: "+rateLimitConfig.get())); - return; - } - - DhServerLevel level = this.getLevel(remotePlayer.serverPlayer.getLevel()); - GeneratedFullDataFileHandler handler = level.serverside.dataFileHandler; - - while (true) - { - IncompleteDataSourceEntry entry = incompleteDataSources.computeIfAbsent(msg.dhSectionPos, pos -> { - IncompleteDataSourceEntry newEntry = new IncompleteDataSourceEntry(); - handler.read(msg.dhSectionPos).thenAccept(fullDataSource -> { - newEntry.fullDataSource = fullDataSource; - }); - return newEntry; - }); - // If this fails, current entry is being drained and need create another one - if (entry.requestCollectionSemaphore.tryAcquire()) - { - entry.requestMessages.add(msg); - entry.requestCollectionSemaphore.release(); - break; - } - } - }); } public void addPlayer(IServerPlayerWrapper serverPlayer) { - this.playersByUUID.put(serverPlayer.getUUID(), new RemotePlayer(serverPlayer)); - this.worldGenLoopingQueue.add(serverPlayer); + this.remotePlayerConnectionHandler.mcPlayerJoined(serverPlayer); + this.getLevel(serverPlayer.getLevel()).addPlayer(serverPlayer); } public void removePlayer(IServerPlayerWrapper serverPlayer) { - this.worldGenLoopingQueue.remove(serverPlayer); - RemotePlayer dhPlayer = this.playersByUUID.remove(serverPlayer.getUUID()); - ChannelHandlerContext channelContext = this.playersByConnection.inverse().remove(dhPlayer); - if (channelContext != null) - { - this.networkServer.disconnectClient(channelContext, "You are being disconnected."); - } + this.getLevel(serverPlayer.getLevel()).removePlayer(serverPlayer); + this.remotePlayerConnectionHandler.mcPlayerLeft(serverPlayer); + } + public void changePlayerLevel(IServerPlayerWrapper player, IServerLevelWrapper origin, IServerLevelWrapper dest) + { + this.getLevel(origin).removePlayer(player); + this.getLevel(dest).addPlayer(player); } @Override @@ -163,7 +81,7 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld { File levelFile = this.saveStructure.getLevelFolder(wrapper); LodUtil.assertTrue(levelFile != null); - return new DhServerLevel(this.saveStructure, serverLevelWrapper); + return new DhServerLevel(this.saveStructure, serverLevelWrapper, this.remotePlayerConnectionHandler); }); } @@ -201,58 +119,16 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld if (rateLimitConfig.pollNewValue()) { - for (RemotePlayer remotePlayer : playersByConnection.values()) + for (RemotePlayer remotePlayer : this.remotePlayerConnectionHandler.getConnectedPlayers()) { remotePlayer.payload.fullDataRequestRateLimit = rateLimitConfig.get(); remotePlayer.channelContext.writeAndFlush(new RemotePlayerConfigMessage(remotePlayer.payload)); } } - - for (Iterator it = incompleteDataSources.values().iterator(); it.hasNext(); ) - { - IncompleteDataSourceEntry entry = it.next(); - if (entry.fullDataSource == null) continue; - - if (entry.fullDataSource instanceof IIncompleteFullDataSource) - { - IIncompleteFullDataSource incompleteSource = (IIncompleteFullDataSource) entry.fullDataSource; - if (!incompleteSource.hasBeenPromoted()) continue; - entry.fullDataSource = incompleteSource.tryPromotingToCompleteDataSource(); - } - - LodUtil.assertTrue(entry.fullDataSource instanceof CompleteFullDataSource, "Invalid full data source"); - - it.remove(); - // This semaphore is intentionally acquired forever - entry.requestCollectionSemaphore.acquireUninterruptibly(Short.MAX_VALUE); - - CompleteFullDataSource completeSource = (CompleteFullDataSource) entry.fullDataSource; - for (FullDataSourceRequestMessage msg : entry.requestMessages) - { - RemotePlayer remotePlayer = playersByConnection.get(msg.getChannelContext()); - if (remotePlayer == null) continue; - remotePlayer.pendingFullDataRequests.decrementAndGet(); - - DhServerLevel level = this.getLevel(remotePlayer.serverPlayer.getLevel()); - msg.sendResponse(new FullDataSourceResponseMessage(completeSource, level)); - } - } } public void doWorldGen() { - this.levels.values().forEach(level -> { - // TODO Deal with dimensions and dimension switches - - IServerPlayerWrapper firstPlayer = this.worldGenLoopingQueue.poll(); - if (firstPlayer == null) { - level.doWorldGen(); - return; - } - this.worldGenLoopingQueue.add(firstPlayer); - - Vec3d position = firstPlayer.getPosition(); - level.doWorldGen(new DhBlockPos2D((int) position.x, (int) position.z)); - }); + this.levels.values().forEach(DhServerLevel::doWorldGen); } @Override @@ -275,13 +151,5 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld this.levels.clear(); LOGGER.info("Closed DhWorld of type "+this.environment); } - - private static class IncompleteDataSourceEntry - { - @CheckForNull - public IFullDataSource fullDataSource; - public final Set requestMessages = ConcurrentHashMap.newKeySet(); - public final Semaphore requestCollectionSemaphore = new Semaphore(Short.MAX_VALUE, true); - } }