Attempt to fix dimension switching
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+10
-7
@@ -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.<FullDataSourceResponseMessage>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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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<NetworkServer> eventSource;
|
||||
|
||||
private final ConcurrentLinkedQueue<IServerPlayerWrapper> worldGenLoopingQueue = new ConcurrentLinkedQueue<>();
|
||||
private final ConcurrentMap<DhSectionPos, IncompleteDataSourceEntry> incompleteDataSources = new ConcurrentHashMap<>();
|
||||
private final AppliedConfigState<Integer> 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<IncompleteDataSourceEntry> 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<FullDataSourceRequestMessage> requestMessages = ConcurrentHashMap.newKeySet();
|
||||
public final Semaphore requestCollectionSemaphore = new Semaphore(Short.MAX_VALUE, true);
|
||||
}
|
||||
}
|
||||
|
||||
+89
@@ -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<NetworkServer> eventSource;
|
||||
private final HashMap<UUID, RemotePlayer> playersByUUID = new HashMap<>();
|
||||
private final BiMap<ChannelHandlerContext, RemotePlayer> 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<RemotePlayer> 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.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+4
@@ -14,6 +14,10 @@ public final class ChildNetworkEventSource<TParent extends NetworkEventSource> e
|
||||
{
|
||||
this.parent = parent;
|
||||
}
|
||||
public ChildNetworkEventSource(ChildNetworkEventSource<TParent> child)
|
||||
{
|
||||
this.parent = child.parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends NetworkMessage> void registerHandler(Class<T> handlerClass, Consumer<T> handlerImplementation)
|
||||
|
||||
@@ -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<EConnectionState> workingStates = EnumSet.of(
|
||||
EConnectionState.OPEN,
|
||||
EConnectionState.RECONNECT,
|
||||
EConnectionState.RECONNECT_FORCE
|
||||
);
|
||||
private static final Set<EConnectionState> 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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<UUID, RemotePlayer> playersByUUID;
|
||||
private final BiMap<ChannelHandlerContext, RemotePlayer> playersByConnection;
|
||||
|
||||
private final ConcurrentLinkedQueue<IServerPlayerWrapper> worldGenLoopingQueue = new ConcurrentLinkedQueue<>();
|
||||
private final ConcurrentMap<DhSectionPos, IncompleteDataSourceEntry> incompleteDataSources = new ConcurrentHashMap<>();
|
||||
private final RemotePlayerConnectionHandler remotePlayerConnectionHandler;
|
||||
private final AppliedConfigState<Integer> 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<IncompleteDataSourceEntry> 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<FullDataSourceRequestMessage> requestMessages = ConcurrentHashMap.newKeySet();
|
||||
public final Semaphore requestCollectionSemaphore = new Semaphore(Short.MAX_VALUE, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user