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 b2aa3e9ec..5dab86791 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
@@ -149,37 +149,37 @@ public class ServerApi
public void serverPlayerJoinEvent(IServerPlayerWrapper player)
{
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
- if (serverWorld instanceof DhServerWorld) // TODO add support for DhClientServerWorld's (lan worlds) as well
+ LOGGER.info("Player [" + player.getName() + "] joined.");
+ if (serverWorld != null)
{
- LOGGER.info("Player [" + player.getName()+ "] joined.");
- ((DhServerWorld) serverWorld).addPlayer(player);
+ serverWorld.addPlayer(player);
}
}
public void serverPlayerDisconnectEvent(IServerPlayerWrapper player)
{
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
- if (serverWorld instanceof DhServerWorld) // TODO add support for DhClientServerWorld's (lan worlds) as well
+ LOGGER.info("Player [" + player.getName() + "] disconnected.");
+ if (serverWorld != null)
{
- LOGGER.info("Player [" + player.getName() + "] disconnected.");
- ((DhServerWorld) serverWorld).removePlayer(player);
+ serverWorld.removePlayer(player);
}
}
public void serverPlayerLevelChangeEvent(IServerPlayerWrapper player, IServerLevelWrapper originLevel, IServerLevelWrapper destinationLevel)
{
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
- if (serverWorld instanceof DhServerWorld) // TODO add support for DhClientServerWorld's (lan worlds) as well
+ LOGGER.info("Player [" + player.getName() + "] changed level: [" + originLevel.getKeyedLevelDimensionName() + "] -> [" + destinationLevel.getKeyedLevelDimensionName() + "].");
+ if (serverWorld != null)
{
- LOGGER.info("Player [" + player.getName() + "] changed level: ["+originLevel.getKeyedLevelDimensionName()+"] -> ["+destinationLevel.getKeyedLevelDimensionName()+"].");
- ((DhServerWorld) serverWorld).changePlayerLevel(player, originLevel, destinationLevel);
+ serverWorld.changePlayerLevel(player, originLevel, destinationLevel);
}
}
public void pluginMessageReceived(IServerPlayerWrapper player, @NotNull AbstractNetworkMessage message)
{
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
- if (serverWorld instanceof DhServerWorld) // TODO add support for DhClientServerWorld's (lan worlds) as well
+ if (serverWorld != null)
{
- ((DhServerWorld) serverWorld).remotePlayerConnectionHandler.handlePluginMessage(player, message);
+ serverWorld.getServerPlayerStateManager().handlePluginMessage(player, message);
}
}
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java
index 40afa8b85..97b6ba83f 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java
@@ -117,11 +117,11 @@ public class SharedApi
public static AbstractDhWorld getAbstractDhWorld() { return currentWorld; }
/** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhClientServerWorld} */
- public static DhClientServerWorld getDhClientServerWorld() { return (currentWorld != null && DhClientServerWorld.class.isInstance(currentWorld)) ? (DhClientServerWorld) currentWorld : null; }
+ public static DhClientServerWorld getDhClientServerWorld() { return (currentWorld instanceof DhClientServerWorld) ? (DhClientServerWorld) currentWorld : null; }
/** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhClientWorld} or {@link DhClientServerWorld} */
- public static IDhClientWorld getIDhClientWorld() { return (currentWorld != null && IDhClientWorld.class.isInstance(currentWorld)) ? (IDhClientWorld) currentWorld : null; }
+ public static IDhClientWorld getIDhClientWorld() { return (currentWorld instanceof IDhClientWorld) ? (IDhClientWorld) currentWorld : null; }
/** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhServerWorld} or {@link DhClientServerWorld} */
- public static IDhServerWorld getIDhServerWorld() { return (currentWorld != null && IDhServerWorld.class.isInstance(currentWorld)) ? (IDhServerWorld) currentWorld : null; }
+ public static IDhServerWorld getIDhServerWorld() { return (currentWorld instanceof IDhServerWorld) ? (IDhServerWorld) currentWorld : null; }
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataSourceProvider.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataSourceProvider.java
index 02b5a39be..8f99a0d21 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataSourceProvider.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataSourceProvider.java
@@ -28,8 +28,10 @@ import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue;
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
import com.seibel.distanthorizons.core.level.IDhLevel;
+import com.seibel.distanthorizons.core.level.WorldGenModule;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
+import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.LodUtil;
@@ -418,9 +420,13 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
/** used by external event listeners */
- @FunctionalInterface
public interface IOnWorldGenCompleteListener
{
+ boolean shouldDoWorldGen();
+
+ @Nullable
+ DhBlockPos2D getTargetPosForGeneration();
+
/** Fired whenever a section has completed generating */
void onWorldGenTaskComplete(long pos);
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhServerLevel.java
new file mode 100644
index 000000000..49ffc28e7
--- /dev/null
+++ b/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhServerLevel.java
@@ -0,0 +1,510 @@
+package com.seibel.distanthorizons.core.level;
+
+import com.seibel.distanthorizons.core.config.Config;
+import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
+import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
+import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
+import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
+import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
+import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerState;
+import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager;
+import com.seibel.distanthorizons.core.network.exceptions.InvalidLevelException;
+import com.seibel.distanthorizons.core.network.exceptions.RequestRejectedException;
+import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
+import com.seibel.distanthorizons.core.network.messages.AbstractTrackableMessage;
+import com.seibel.distanthorizons.core.network.messages.ILevelRelatedMessage;
+import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPartialUpdateMessage;
+import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPayload;
+import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceRequestMessage;
+import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceResponseMessage;
+import com.seibel.distanthorizons.core.network.messages.requests.CancelMessage;
+import com.seibel.distanthorizons.core.pos.DhSectionPos;
+import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
+import com.seibel.distanthorizons.core.util.LodUtil;
+import com.seibel.distanthorizons.core.util.math.Vec3d;
+import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
+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 org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import java.util.Map;
+import java.util.concurrent.*;
+
+public abstract class AbstractDhServerLevel extends AbstractDhLevel implements IDhServerLevel
+{
+ protected static final Logger LOGGER = DhLoggerBuilder.getLogger();
+ private static final ConfigBasedLogger NETWORK_LOGGER = new ConfigBasedLogger(LogManager.getLogger(),
+ () -> Config.Client.Advanced.Logging.logNetworkEvent.get());
+
+ /** 1 Mebibyte minus 576 bytes for other info */
+ public static final int FULL_DATA_SPLIT_SIZE_IN_BYTES = 1_048_000;
+
+ public final ServerLevelModule serverside;
+ protected final IServerLevelWrapper serverLevelWrapper;
+
+ protected final ServerPlayerStateManager serverPlayerStateManager;
+
+ /**
+ * This queue is used for ensuring fair generation speed for each player.
+ * Every tick the first player gets used for centering generation, and then is immediately moved into the back of the queue.
+ * TODO only add players that actually have something to generate
+ */
+ protected final ConcurrentLinkedQueue worldGenPlayerCenteringQueue = new ConcurrentLinkedQueue<>();
+
+ private final ConcurrentMap requestGroupByPos = new ConcurrentHashMap<>();
+ private final ConcurrentMap requestGroupByFutureId = new ConcurrentHashMap<>();
+
+
+
+ //=============//
+ // constructor //
+ //=============//
+
+ public AbstractDhServerLevel(
+ AbstractSaveStructure saveStructure,
+ IServerLevelWrapper serverLevelWrapper,
+ ServerPlayerStateManager serverPlayerStateManager
+ )
+ { this(saveStructure, serverLevelWrapper, serverPlayerStateManager, true); }
+ public AbstractDhServerLevel(
+ AbstractSaveStructure saveStructure,
+ IServerLevelWrapper serverLevelWrapper,
+ ServerPlayerStateManager serverPlayerStateManager,
+ boolean runRepoReliantSetup
+ )
+ {
+ if (saveStructure.getFullDataFolder(serverLevelWrapper).mkdirs())
+ {
+ LOGGER.warn("unable to create data folder.");
+ }
+ this.serverLevelWrapper = serverLevelWrapper;
+ this.serverside = new ServerLevelModule(this, saveStructure);
+ this.createAndSetSupportingRepos(this.serverside.fullDataFileHandler.repo.databaseFile);
+ if (runRepoReliantSetup)
+ {
+ this.runRepoReliantSetup();
+ }
+
+ LOGGER.info("Started " + this.getClass().getSimpleName() + " for [" + serverLevelWrapper + "] at [" + saveStructure + "].");
+
+ this.serverPlayerStateManager = serverPlayerStateManager;
+ }
+
+
+
+ //=======//
+ // ticks //
+ //=======//
+
+ @Override
+ public void serverTick()
+ {
+ // Send finished data source requests
+ for (Map.Entry entry : this.requestGroupByPos.entrySet())
+ {
+ DataSourceRequestGroup requestGroup = entry.getValue();
+
+ if (requestGroup.fullDataSource == null)
+ {
+ continue;
+ }
+
+ NETWORK_LOGGER.debug("[" + this.serverLevelWrapper.getDimensionName() + "] Fulfilled request group [" + entry.getKey() + "]");
+
+ // Make this group unavailable for adding into
+ this.requestGroupByPos.remove(entry.getKey());
+ requestGroup.requestRemoveSemaphore.acquireUninterruptibly(Short.MAX_VALUE);
+ requestGroup.requestAddSemaphore.acquireUninterruptibly(Short.MAX_VALUE);
+
+ ThreadPoolExecutor executor = ThreadPoolUtil.getNetworkCompressionExecutor();
+ if (executor == null)
+ {
+ LOGGER.warn("Unable to send FullDataSourceResponseMessage - getNetworkCompressionExecutor() is null");
+ continue;
+ }
+ CompletableFuture.runAsync(() ->
+ {
+ FullDataPayload payload = new FullDataPayload(requestGroup.fullDataSource);
+ for (FullDataSourceRequestMessage msg : requestGroup.requestMessages.values())
+ {
+ this.requestGroupByFutureId.remove(msg.futureId);
+
+ ServerPlayerState serverPlayerState = this.serverPlayerStateManager.getConnectedPlayer(msg.serverPlayer());
+ if (serverPlayerState == null)
+ {
+ continue;
+ }
+
+ serverPlayerState.getRateLimiterSet(this).generationRequestRateLimiter.release();
+ payload.splitAndSend(FULL_DATA_SPLIT_SIZE_IN_BYTES, msg.getSession()::sendMessage);
+ msg.sendResponse(new FullDataSourceResponseMessage(payload));
+ }
+ }, executor);
+ }
+ }
+
+ @Override
+ public abstract boolean shouldDoWorldGen();
+
+ @Override
+ @Nullable
+ public DhBlockPos2D getTargetPosForGeneration()
+ {
+ IServerPlayerWrapper firstPlayer = this.worldGenPlayerCenteringQueue.peek();
+ if (firstPlayer == null)
+ {
+ return null;
+ }
+
+ // Put first player in back before removing from front, so it can be removed by other thread without blocking
+ // - if it gets removed, remove() below will remove the item we just put instead
+ this.worldGenPlayerCenteringQueue.add(firstPlayer);
+ this.worldGenPlayerCenteringQueue.remove(firstPlayer);
+
+ Vec3d position = firstPlayer.getPosition();
+ return new DhBlockPos2D((int) position.x, (int) position.z);
+ }
+
+ @Override public void worldGenTick()
+ {
+ this.serverside.worldGenModule.worldGenTick();
+ }
+
+
+
+ //==================//
+ // network handling //
+ //==================//
+
+ public void registerNetworkHandlers(ServerPlayerState serverPlayerState)
+ {
+ serverPlayerState.networkSession.registerHandler(FullDataSourceRequestMessage.class, (message) ->
+ {
+ if (!this.messagePlayerInThisLevel(message))
+ {
+ // we can't handle players in other levels, don't continue
+ return;
+ }
+
+
+ ServerPlayerState.RateLimiterSet rateLimiterSet = serverPlayerState.getRateLimiterSet(this);
+
+ if (message.clientTimestamp == null)
+ {
+ this.queueWorldGenForRequestMessage(serverPlayerState, message, rateLimiterSet);
+ }
+ else
+ {
+ this.queueLodSyncForRequestMessage(serverPlayerState, message, rateLimiterSet);
+ }
+ });
+
+
+ serverPlayerState.networkSession.registerHandler(CancelMessage.class, msg ->
+ {
+ DataSourceRequestGroup requestGroup = this.requestGroupByFutureId.remove(msg.futureId);
+ if (requestGroup == null)
+ {
+ return;
+ }
+
+ // If this fails, the group is being removed and completing cancellation is not necessary
+ if (requestGroup.requestRemoveSemaphore.tryAcquire())
+ {
+ // Prevent adding requests in case the group will be removed by this cancellation
+ requestGroup.requestAddSemaphore.acquireUninterruptibly(Short.MAX_VALUE);
+ requestGroup.requestRemoveSemaphore.release();
+
+ serverPlayerState.getRateLimiterSet(this).generationRequestRateLimiter.release();
+
+ FullDataSourceRequestMessage requestMessage = requestGroup.requestMessages.remove(msg.futureId);
+ if (requestGroup.requestMessages.isEmpty())
+ {
+ NETWORK_LOGGER.debug("[" + this.serverLevelWrapper.getDimensionName() + "] Cancelled request group [" + DhSectionPos.toString(requestMessage.sectionPos) + "].");
+ this.requestGroupByPos.remove(requestMessage.sectionPos);
+ this.serverside.fullDataFileHandler.removeRetrievalRequestIf(pos -> pos == requestMessage.sectionPos);
+ }
+ else
+ {
+ requestGroup.requestAddSemaphore.release(Short.MAX_VALUE);
+ }
+ }
+ });
+ }
+ private void queueLodSyncForRequestMessage(ServerPlayerState serverPlayerState, FullDataSourceRequestMessage message, ServerPlayerState.RateLimiterSet rateLimiterSet)
+ {
+ if (!serverPlayerState.sessionConfig.getSynchronizeOnLogin())
+ {
+ message.sendResponse(new RequestRejectedException("Operation is disabled in config."));
+ return;
+ }
+
+ if (!rateLimiterSet.syncOnLoginRateLimiter.tryAcquire(message))
+ {
+ return;
+ }
+
+
+ // the client timestamp will be null if we want to retrieve the LOD regardless of when it was last updated
+ long clientTimestamp = (message.clientTimestamp != null) ? message.clientTimestamp : -1;
+ // the server timestamp will be null if no LOD data exists for this position
+ Long serverTimestamp = this.serverside.fullDataFileHandler.getTimestampForPos(message.sectionPos);
+ if (serverTimestamp == null
+ || serverTimestamp <= clientTimestamp)
+ {
+ // either no data exists to sync, or the client is already up to date
+ rateLimiterSet.syncOnLoginRateLimiter.release();
+ message.sendResponse(new FullDataSourceResponseMessage(null));
+ return;
+ }
+
+
+
+ ThreadPoolExecutor executor = ThreadPoolUtil.getNetworkCompressionExecutor();
+ if (executor == null)
+ {
+ // shouldn't normally happen, but just in case
+ LOGGER.warn("Unable to send FullDataSourceResponseMessage - getNetworkCompressionExecutor() is null");
+ return;
+ }
+
+ this.serverside.fullDataFileHandler.getAsync(message.sectionPos).thenAcceptAsync(fullDataSource ->
+ {
+ rateLimiterSet.syncOnLoginRateLimiter.release();
+
+ FullDataPayload payload = new FullDataPayload(fullDataSource);
+ payload.splitAndSend(FULL_DATA_SPLIT_SIZE_IN_BYTES, message.getSession()::sendMessage);
+ message.sendResponse(new FullDataSourceResponseMessage(payload));
+ }, executor);
+ }
+ private void queueWorldGenForRequestMessage(ServerPlayerState serverPlayerState, FullDataSourceRequestMessage message, ServerPlayerState.RateLimiterSet rateLimiterSet)
+ {
+ if (!serverPlayerState.sessionConfig.isDistantGenerationEnabled())
+ {
+ message.sendResponse(new RequestRejectedException("Operation is disabled in config."));
+ return;
+ }
+
+ if (!rateLimiterSet.generationRequestRateLimiter.tryAcquire(message))
+ {
+ return;
+ }
+
+ while (true)
+ {
+ DataSourceRequestGroup requestGroup = this.requestGroupByPos.computeIfAbsent(message.sectionPos, pos ->
+ {
+ DataSourceRequestGroup newGroup = new DataSourceRequestGroup();
+ this.tryFulfillDataSourceRequestGroup(newGroup, pos);
+ NETWORK_LOGGER.debug("[" + this.serverLevelWrapper.getDimensionName() + "] Created request group for pos [" + DhSectionPos.toString(pos) + "].");
+ return newGroup;
+ });
+
+ // If this fails, loop until either a permit is acquired or the group is removed to create another one
+ if (!requestGroup.requestAddSemaphore.tryAcquire())
+ {
+ Thread.yield();
+ continue;
+ }
+
+ this.requestGroupByFutureId.put(message.futureId, requestGroup);
+ requestGroup.requestMessages.put(message.futureId, message);
+ requestGroup.requestAddSemaphore.release();
+ break;
+ }
+ }
+
+
+ /** May send an error message in response if the message is a {@link AbstractTrackableMessage} */
+ private boolean messagePlayerInThisLevel(T message)
+ {
+ if (!(message instanceof ILevelRelatedMessage))
+ {
+ LodUtil.assertNotReach("Received message [" + ILevelRelatedMessage.class.getSimpleName() + "] does not implement [" + message.getClass().getSimpleName() + "]");
+ }
+
+ // Only handle requests for this level
+ if (!((ILevelRelatedMessage) message).isSameLevelAs(this.getServerLevelWrapper()))
+ {
+ return false;
+ }
+
+ LodUtil.assertTrue(message.getSession().serverPlayer != null);
+
+ // Check if the player is in this dimension,
+ // since handling multiple dimensions isn't allowed
+ if (message.getSession().serverPlayer.getLevel() != this.getLevelWrapper())
+ {
+ // If the message can be replied to - reply with an error, otherwise just ignore
+ if (message instanceof AbstractTrackableMessage)
+ {
+ ((AbstractTrackableMessage) message).sendResponse(
+ new InvalidLevelException(
+ "Generation not allowed. " +
+ "Requested dimension: [" + ((ILevelRelatedMessage) message).getLevelName() + "], " +
+ "player dimension: [" + message.getSession().serverPlayer.getLevel().getDimensionName() + "], " +
+ "handler dimension: [" + this.getLevelWrapper().getDimensionName() + "]"
+ )
+ );
+ }
+
+ return false;
+ }
+
+
+ return true;
+ }
+
+
+
+ //===========//
+ // world gen //
+ //===========//
+
+ @Override
+ public void onWorldGenTaskComplete(long pos)
+ {
+ DataSourceRequestGroup requestGroup = this.requestGroupByPos.get(pos);
+ if (requestGroup != null)
+ {
+ this.tryFulfillDataSourceRequestGroup(requestGroup, pos);
+ }
+ }
+
+ private void tryFulfillDataSourceRequestGroup(DataSourceRequestGroup requestGroup, long pos)
+ {
+ this.serverside.fullDataFileHandler.getAsync(pos).thenAccept(fullDataSource ->
+ {
+ if (this.serverside.fullDataFileHandler.isFullyGenerated(fullDataSource.columnGenerationSteps))
+ {
+ requestGroup.fullDataSource = fullDataSource;
+ }
+ else
+ {
+ this.serverside.fullDataFileHandler.queuePositionForRetrieval(pos);
+ }
+ });
+ }
+
+
+
+ //=================//
+ // player handling //
+ //=================//
+
+ public void addPlayer(IServerPlayerWrapper serverPlayer) { this.worldGenPlayerCenteringQueue.add(serverPlayer); }
+ public void removePlayer(IServerPlayerWrapper serverPlayer) { this.worldGenPlayerCenteringQueue.remove(serverPlayer); }
+
+ @Override
+ public CompletableFuture updateDataSourcesAsync(FullDataSourceV2 data)
+ {
+ if (!Config.Client.Advanced.Multiplayer.ServerNetworking.enableRealTimeUpdates.get())
+ {
+ return this.getFullDataProvider().updateDataSourceAsync(data);
+ }
+
+ ThreadPoolExecutor executor = ThreadPoolUtil.getNetworkCompressionExecutor();
+ if (executor == null)
+ {
+ LOGGER.warn("Unable to send FullDataPartialUpdateMessage - getNetworkCompressionExecutor() is null");
+ return this.getFullDataProvider().updateDataSourceAsync(data);
+ }
+ CompletableFuture.runAsync(() ->
+ {
+ FullDataPayload payload = new FullDataPayload(data);
+ for (ServerPlayerState serverPlayerState : this.serverPlayerStateManager.getConnectedPlayers())
+ {
+ if (serverPlayerState.getServerPlayer().getLevel() != this.serverLevelWrapper)
+ {
+ continue;
+ }
+
+ if (!serverPlayerState.sessionConfig.isRealTimeUpdatesEnabled())
+ {
+ continue;
+ }
+
+ Vec3d playerPosition = serverPlayerState.getServerPlayer().getPosition();
+ int distanceFromPlayer = DhSectionPos.getManhattanBlockDistance(data.getPos(), new DhBlockPos2D((int) playerPosition.x, (int) playerPosition.z)) / 16;
+ if (distanceFromPlayer >= serverPlayerState.getServerPlayer().getViewDistance()
+ && distanceFromPlayer <= serverPlayerState.sessionConfig.getRenderDistanceRadius())
+ {
+ payload.splitAndSend(FULL_DATA_SPLIT_SIZE_IN_BYTES, serverPlayerState.networkSession::sendMessage);
+ serverPlayerState.networkSession.sendMessage(new FullDataPartialUpdateMessage(this.serverLevelWrapper, payload));
+ }
+ }
+ }, executor);
+
+
+ return this.getFullDataProvider().updateDataSourceAsync(data);
+ }
+
+
+
+ //=========//
+ // getters //
+ //=========//
+
+ @Override
+ public int getMinY() { return this.getLevelWrapper().getMinHeight(); }
+
+ @Override
+ public IServerLevelWrapper getServerLevelWrapper() { return this.serverLevelWrapper; }
+
+ @Override
+ public ILevelWrapper getLevelWrapper() { return this.getServerLevelWrapper(); }
+
+ @Override
+ public FullDataSourceProviderV2 getFullDataProvider() { return this.serverside.fullDataFileHandler; }
+
+ @Override
+ public AbstractSaveStructure getSaveStructure() { return this.serverside.saveStructure; }
+
+ @Override
+ public boolean hasSkyLight() { return this.serverLevelWrapper.hasSkyLight(); }
+
+
+
+ //==========//
+ // shutdown //
+ //==========//
+
+ @Override
+ public void close()
+ {
+ super.close();
+ this.serverside.close();
+ LOGGER.info("Closed DHLevel for [" + this.getLevelWrapper() + "].");
+ }
+
+
+
+ //================//
+ // helper classes //
+ //================//
+
+ private static class DataSourceRequestGroup
+ {
+ public final ConcurrentMap requestMessages = new ConcurrentHashMap<>();
+
+ @CheckForNull
+ public FullDataSourceV2 fullDataSource;
+
+ /**
+ * These semaphores prevent a given thread from accidentally locking on the same group
+ * multiple times, as the semaphore is tied to the given thread.
+ * Reentrant Lock isn't used since it would allow the thread to lock on the same group.
+ * the Short.MAX_VALUE is just a very large number that should be larger than the number of
+ * threads we'll have.
+ */
+ public final Semaphore requestAddSemaphore = new Semaphore(Short.MAX_VALUE, true);
+ /** @see DataSourceRequestGroup#requestAddSemaphore */
+ public final Semaphore requestRemoveSemaphore = new Semaphore(Short.MAX_VALUE, true);
+
+ }
+
+}
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 c409b502f..56a03f798 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
@@ -52,6 +52,7 @@ import javax.annotation.CheckForNull;
import java.awt.*;
import java.io.File;
import java.util.List;
+import java.util.Optional;
import java.util.concurrent.CompletableFuture;
/** The level used when connected to a server */
@@ -108,7 +109,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
this.dataFileHandler = new RemoteFullDataSourceProvider(this, saveStructure, fullDataSaveDirOverride, this.syncOnLoginRequestQueue);
this.worldGeneratorEnabledConfig = new AppliedConfigState<>(Config.Client.Advanced.WorldGenerator.enableDistantGeneration);
- this.worldGenModule = new WorldGenModule(this);
+ this.worldGenModule = new WorldGenModule(this, this.dataFileHandler, () -> new WorldGenState(this, networkState));
this.clientside = new ClientLevelModule(this);
@@ -171,7 +172,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
}
@Override
- public void worldGenTick()
+ public boolean shouldDoWorldGen()
{
ClientNetworkState networkState = this.networkState;
@@ -182,33 +183,23 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
isAllowedDimension = MC_CLIENT.getWrappedClientLevel() == this.levelWrapper;
}
- boolean shouldDoWorldGen = isClientUsable
+ return isClientUsable
&& networkState.sessionConfig.isDistantGenerationEnabled()
&& isAllowedDimension
&& this.clientside.isRendering();
-
- boolean isWorldGenRunning = this.worldGenModule.isWorldGenRunning();
- if (shouldDoWorldGen && !isWorldGenRunning)
- {
- // start world gen
- this.worldGenModule.startWorldGen(this.dataFileHandler, new WorldGenState(this, networkState));
-
- // populate the queue based on the current rendering tree
- ClientLevelModule.ClientRenderState renderState = this.clientside.ClientRenderStateRef.get();
- renderState.quadtree.leafNodeIterator().forEachRemaining(node -> {
- this.dataFileHandler.getAsync(node.sectionPos);
- });
- }
- else if (!shouldDoWorldGen && isWorldGenRunning)
- {
- // stop world gen
- this.worldGenModule.stopWorldGen(this.dataFileHandler);
- }
-
- if (this.worldGenModule.isWorldGenRunning())
- {
- this.worldGenModule.worldGenTick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
- }
+ }
+
+ @Override
+ @Nullable
+ public DhBlockPos2D getTargetPosForGeneration()
+ {
+ return new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos());
+ }
+
+ @Override
+ public void worldGenTick()
+ {
+ this.worldGenModule.worldGenTick();
}
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java
index ef307e589..6085f9d61 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java
@@ -22,12 +22,11 @@ package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
-import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
+import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager;
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
-import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
@@ -36,24 +35,20 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrap
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
-import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
-import org.apache.logging.log4j.Logger;
+import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/** The level used on a singleplayer world */
-public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLevel, IDhServerLevel
+public class DhClientServerLevel extends AbstractDhServerLevel implements IDhClientLevel
{
- private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
- public final ServerLevelModule serverside;
public final ClientLevelModule clientside;
-
- private final IServerLevelWrapper serverLevelWrapper;
+ private int localPlayerWorldGenPosInQueue = 0;
@@ -61,20 +56,13 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
// constructor //
//=============//
- public DhClientServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper)
+ public DhClientServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper, ServerPlayerStateManager serverPlayerStateManager)
{
- if (saveStructure.getFullDataFolder(serverLevelWrapper).mkdirs())
- {
- LOGGER.warn("unable to create data folder.");
- }
- this.serverLevelWrapper = serverLevelWrapper;
- this.serverLevelWrapper.setParentLevel(this);
- this.serverside = new ServerLevelModule(this, saveStructure);
- this.clientside = new ClientLevelModule(this);
- this.createAndSetSupportingRepos(this.serverside.fullDataFileHandler.repo.databaseFile);
- this.runRepoReliantSetup();
+ super(saveStructure, serverLevelWrapper, serverPlayerStateManager, false);
- LOGGER.info("Started " + DhClientServerLevel.class.getSimpleName() + " for " + serverLevelWrapper + " with saves at " + saveStructure);
+ this.serverLevelWrapper.setParentLevel(this);
+ this.clientside = new ClientLevelModule(this);
+ this.runRepoReliantSetup();
}
@@ -95,32 +83,26 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
{ this.clientside.renderDeferred(renderEventParam, profiler); }
@Override
- public void serverTick() { }
-
- @Override
- public void worldGenTick()
+ public boolean shouldDoWorldGen()
{
- this.serverside.worldGeneratorEnabledConfig.pollNewValue(); // if not called the get() line below may not
- boolean shouldDoWorldGen = this.serverside.worldGeneratorEnabledConfig.get() && this.clientside.isRendering();
- boolean isWorldGenRunning = this.serverside.worldGenModule.isWorldGenRunning();
- if (shouldDoWorldGen && !isWorldGenRunning)
- {
- // start world gen
- this.serverside.worldGenModule.startWorldGen(this.serverside.fullDataFileHandler, new ServerLevelModule.WorldGenState(this));
- }
- else if (!shouldDoWorldGen && isWorldGenRunning)
- {
- // stop world gen
- this.serverside.worldGenModule.stopWorldGen(this.serverside.fullDataFileHandler);
- }
-
- if (isWorldGenRunning)
- {
- this.serverside.worldGenModule.worldGenTick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
- }
+ return this.serverside.worldGeneratorEnabledConfig.get() && this.clientside.isRendering() || !this.worldGenPlayerCenteringQueue.isEmpty();
}
-
+ @Override
+ @Nullable
+ public DhBlockPos2D getTargetPosForGeneration()
+ {
+ if (this.localPlayerWorldGenPosInQueue > 0)
+ {
+ this.localPlayerWorldGenPosInQueue--;
+ return super.getTargetPosForGeneration();
+ }
+ else
+ {
+ this.localPlayerWorldGenPosInQueue = this.worldGenPlayerCenteringQueue.size();
+ return new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos());
+ }
+ }
//========//
// render //
@@ -157,28 +139,14 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
public void clearRenderCache() { this.clientside.clearRenderCache(); }
@Override
- public IServerLevelWrapper getServerLevelWrapper() { return this.serverLevelWrapper; }
- @Override
- public ILevelWrapper getLevelWrapper() { return this.getServerLevelWrapper(); }
-
- @Override
- public FullDataSourceProviderV2 getFullDataProvider() { return this.serverside.fullDataFileHandler; }
-
- @Override
- public AbstractSaveStructure getSaveStructure()
+ public CompletableFuture updateDataSourcesAsync(FullDataSourceV2 data)
{
- return this.serverside.saveStructure;
+ return CompletableFuture.allOf(
+ super.updateDataSourcesAsync(data),
+ this.clientside.updateDataSourcesAsync(data)
+ );
}
- @Override
- public boolean hasSkyLight() { return this.serverLevelWrapper.hasSkyLight(); }
-
- @Override
- public CompletableFuture updateDataSourcesAsync(FullDataSourceV2 data) { return this.clientside.updateDataSourcesAsync(data); }
-
- @Override
- public int getMinY() { return this.getLevelWrapper().getMinHeight(); }
-
//===========//
@@ -247,6 +215,8 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
@Override
public void onWorldGenTaskComplete(long pos)
{
+ super.onWorldGenTaskComplete(pos);
+
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(pos, 128f, 156f, 0.09f, Color.red.darker()),
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 f146d42b7..3fd3b0e80 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
@@ -19,86 +19,23 @@
package com.seibel.distanthorizons.core.level;
-import com.seibel.distanthorizons.core.config.Config;
-import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
-import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
-import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
-import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
-import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
-import com.seibel.distanthorizons.core.multiplayer.server.RemotePlayerConnectionHandler;
-import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerState;
-import com.seibel.distanthorizons.core.network.exceptions.InvalidLevelException;
-import com.seibel.distanthorizons.core.network.exceptions.RequestRejectedException;
-import com.seibel.distanthorizons.core.network.messages.ILevelRelatedMessage;
-import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
-import com.seibel.distanthorizons.core.network.messages.AbstractTrackableMessage;
-import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPartialUpdateMessage;
-import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPayload;
-import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceRequestMessage;
-import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceResponseMessage;
-import com.seibel.distanthorizons.core.network.messages.requests.CancelMessage;
-import com.seibel.distanthorizons.core.pos.DhSectionPos;
+import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager;
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
-import com.seibel.distanthorizons.core.util.LodUtil;
-import com.seibel.distanthorizons.core.util.math.Vec3d;
-import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
-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 org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import javax.annotation.CheckForNull;
import java.util.List;
-import java.util.Map;
-import java.util.concurrent.*;
-public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
+public class DhServerLevel extends AbstractDhServerLevel
{
- private static final Logger LOGGER = DhLoggerBuilder.getLogger();
- private static final ConfigBasedLogger NETWORK_LOGGER = new ConfigBasedLogger(LogManager.getLogger(),
- () -> Config.Client.Advanced.Logging.logNetworkEvent.get());
-
- /** 1 Mebibyte minus 576 bytes for other info */
- public static final int FULL_DATA_SPLIT_SIZE_IN_BYTES = 1_048_000;
-
- public final ServerLevelModule serverside;
- private final IServerLevelWrapper serverLevelWrapper;
-
- private final RemotePlayerConnectionHandler remotePlayerConnectionHandler;
-
- /**
- * This queue is used for ensuring fair generation speed for each player.
- * Every tick the first player gets used for centering generation, and then is immediately moved into the back of the queue.
- * TODO only add players that actually have something to generate
- */
- private final ConcurrentLinkedQueue worldGenPlayerCenteringQueue = new ConcurrentLinkedQueue<>();
-
- private final ConcurrentMap requestGroupByPos = new ConcurrentHashMap<>();
- private final ConcurrentMap requestGroupByFutureId = new ConcurrentHashMap<>();
-
-
-
//=============//
// constructor //
//=============//
- public DhServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper, RemotePlayerConnectionHandler remotePlayerConnectionHandler)
+ public DhServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper, ServerPlayerStateManager serverPlayerStateManager)
{
- if (saveStructure.getFullDataFolder(serverLevelWrapper).mkdirs())
- {
- LOGGER.warn("unable to create data folder.");
- }
- this.serverLevelWrapper = serverLevelWrapper;
- this.serverside = new ServerLevelModule(this, saveStructure);
- this.createAndSetSupportingRepos(this.serverside.fullDataFileHandler.repo.databaseFile);
- this.runRepoReliantSetup();
-
- LOGGER.info("Started DHLevel for ["+serverLevelWrapper+"] at ["+saveStructure+"].");
-
- this.remotePlayerConnectionHandler = remotePlayerConnectionHandler;
+ super(saveStructure, serverLevelWrapper, serverPlayerStateManager);
}
@@ -108,355 +45,9 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
//=======//
@Override
- public void serverTick()
+ public boolean shouldDoWorldGen()
{
- // Send finished data source requests
- for (Map.Entry entry : this.requestGroupByPos.entrySet())
- {
- DataSourceRequestGroup requestGroup = entry.getValue();
-
- if (requestGroup.fullDataSource == null)
- {
- continue;
- }
-
- NETWORK_LOGGER.debug("["+this.serverLevelWrapper.getDimensionName()+"] Fulfilled request group ["+entry.getKey()+"]");
-
- // Make this group unavailable for adding into
- this.requestGroupByPos.remove(entry.getKey());
- requestGroup.requestRemoveSemaphore.acquireUninterruptibly(Short.MAX_VALUE);
- requestGroup.requestAddSemaphore.acquireUninterruptibly(Short.MAX_VALUE);
-
- ThreadPoolExecutor executor = ThreadPoolUtil.getNetworkCompressionExecutor();
- if (executor == null)
- {
- LOGGER.warn("Unable to send FullDataSourceResponseMessage - getNetworkCompressionExecutor() is null");
- continue;
- }
- CompletableFuture.runAsync(() ->
- {
- FullDataPayload payload = new FullDataPayload(requestGroup.fullDataSource);
- for (FullDataSourceRequestMessage msg : requestGroup.requestMessages.values())
- {
- this.requestGroupByFutureId.remove(msg.futureId);
-
- ServerPlayerState serverPlayerState = this.remotePlayerConnectionHandler.getConnectedPlayer(msg.serverPlayer());
- if (serverPlayerState == null)
- {
- continue;
- }
-
- serverPlayerState.getRateLimiterSet(this).generationRequestRateLimiter.release();
- payload.splitAndSend(FULL_DATA_SPLIT_SIZE_IN_BYTES, msg.getSession()::sendMessage);
- msg.sendResponse(new FullDataSourceResponseMessage(payload));
- }
- }, executor);
- }
- }
-
- @Override
- public void worldGenTick()
- {
- boolean shouldDoWorldGen = true; //todo;
- boolean isWorldGenRunning = this.serverside.worldGenModule.isWorldGenRunning();
- if (shouldDoWorldGen && !isWorldGenRunning)
- {
- // start world gen
- this.serverside.worldGenModule.startWorldGen(this.serverside.fullDataFileHandler, new ServerLevelModule.WorldGenState(this));
- }
- else if (!shouldDoWorldGen && isWorldGenRunning)
- {
- // stop world gen
- this.serverside.worldGenModule.stopWorldGen(this.serverside.fullDataFileHandler);
- }
-
- if (this.serverside.worldGenModule.isWorldGenRunning())
- {
- IServerPlayerWrapper firstPlayer = this.worldGenPlayerCenteringQueue.peek();
- if (firstPlayer == null)
- {
- return;
- }
-
- // Put first player in back before removing from front, so it can be removed by other thread without blocking
- // - if it gets removed, remove() below will remove the item we just put instead
- this.worldGenPlayerCenteringQueue.add(firstPlayer);
- this.worldGenPlayerCenteringQueue.remove(firstPlayer);
-
- Vec3d position = firstPlayer.getPosition();
- this.serverside.worldGenModule.worldGenTick(new DhBlockPos2D((int) position.x, (int) position.z));
- }
- }
-
-
-
- //==================//
- // network handling //
- //==================//
-
- public void registerNetworkHandlers(ServerPlayerState serverPlayerState)
- {
- serverPlayerState.networkSession.registerHandler(FullDataSourceRequestMessage.class, (message) ->
- {
- if (!this.messagePlayerInThisLevel(message))
- {
- // we can't handle players in other levels, don't continue
- return;
- }
-
-
- ServerPlayerState.RateLimiterSet rateLimiterSet = serverPlayerState.getRateLimiterSet(this);
-
- if (message.clientTimestamp == null)
- {
- this.queueWorldGenForRequestMessage(serverPlayerState, message, rateLimiterSet);
- }
- else
- {
- this.queueLodSyncForRequestMessage(serverPlayerState, message, rateLimiterSet);
- }
- });
-
-
- serverPlayerState.networkSession.registerHandler(CancelMessage.class, msg ->
- {
- DataSourceRequestGroup requestGroup = this.requestGroupByFutureId.remove(msg.futureId);
- if (requestGroup == null)
- {
- return;
- }
-
- // If this fails, the group is being removed and completing cancellation is not necessary
- if (requestGroup.requestRemoveSemaphore.tryAcquire())
- {
- // Prevent adding requests in case the group will be removed by this cancellation
- requestGroup.requestAddSemaphore.acquireUninterruptibly(Short.MAX_VALUE);
- requestGroup.requestRemoveSemaphore.release();
-
- serverPlayerState.getRateLimiterSet(this).generationRequestRateLimiter.release();
-
- FullDataSourceRequestMessage requestMessage = requestGroup.requestMessages.remove(msg.futureId);
- if (requestGroup.requestMessages.isEmpty())
- {
- NETWORK_LOGGER.debug("["+this.serverLevelWrapper.getDimensionName()+"] Cancelled request group ["+DhSectionPos.toString(requestMessage.sectionPos)+"].");
- this.requestGroupByPos.remove(requestMessage.sectionPos);
- this.serverside.fullDataFileHandler.removeRetrievalRequestIf(pos -> pos == requestMessage.sectionPos);
- }
- else
- {
- requestGroup.requestAddSemaphore.release(Short.MAX_VALUE);
- }
- }
- });
- }
- private void queueLodSyncForRequestMessage(ServerPlayerState serverPlayerState, FullDataSourceRequestMessage message, ServerPlayerState.RateLimiterSet rateLimiterSet)
- {
- if (!serverPlayerState.sessionConfig.getSynchronizeOnLogin())
- {
- message.sendResponse(new RequestRejectedException("Operation is disabled in config."));
- return;
- }
-
- if (!rateLimiterSet.syncOnLoginRateLimiter.tryAcquire(message))
- {
- return;
- }
-
-
- // the client timestamp will be null if we want to retrieve the LOD regardless of when it was last updated
- long clientTimestamp = (message.clientTimestamp != null) ? message.clientTimestamp : -1;
- // the server timestamp will be null if no LOD data exists for this position
- Long serverTimestamp = this.serverside.fullDataFileHandler.getTimestampForPos(message.sectionPos);
- if (serverTimestamp == null
- || serverTimestamp <= clientTimestamp)
- {
- // either no data exists to sync, or the client is already up to date
- rateLimiterSet.syncOnLoginRateLimiter.release();
- message.sendResponse(new FullDataSourceResponseMessage(null));
- return;
- }
-
-
-
- ThreadPoolExecutor executor = ThreadPoolUtil.getNetworkCompressionExecutor();
- if (executor == null)
- {
- // shouldn't normally happen, but just in case
- LOGGER.warn("Unable to send FullDataSourceResponseMessage - getNetworkCompressionExecutor() is null");
- return;
- }
-
- this.serverside.fullDataFileHandler.getAsync(message.sectionPos).thenAcceptAsync(fullDataSource ->
- {
- rateLimiterSet.syncOnLoginRateLimiter.release();
-
- FullDataPayload payload = new FullDataPayload(fullDataSource);
- payload.splitAndSend(FULL_DATA_SPLIT_SIZE_IN_BYTES, message.getSession()::sendMessage);
- message.sendResponse(new FullDataSourceResponseMessage(payload));
- }, executor);
- }
- private void queueWorldGenForRequestMessage(ServerPlayerState serverPlayerState, FullDataSourceRequestMessage message, ServerPlayerState.RateLimiterSet rateLimiterSet)
- {
- if (!serverPlayerState.sessionConfig.isDistantGenerationEnabled())
- {
- message.sendResponse(new RequestRejectedException("Operation is disabled in config."));
- return;
- }
-
- if (!rateLimiterSet.generationRequestRateLimiter.tryAcquire(message))
- {
- return;
- }
-
- while (true)
- {
- DataSourceRequestGroup requestGroup = this.requestGroupByPos.computeIfAbsent(message.sectionPos, pos ->
- {
- DataSourceRequestGroup newGroup = new DataSourceRequestGroup();
- this.tryFulfillDataSourceRequestGroup(newGroup, pos);
- NETWORK_LOGGER.debug("["+this.serverLevelWrapper.getDimensionName()+"] Created request group for pos ["+DhSectionPos.toString(pos)+"].");
- return newGroup;
- });
-
- // If this fails, loop until either a permit is acquired or the group is removed to create another one
- if (!requestGroup.requestAddSemaphore.tryAcquire())
- {
- Thread.yield();
- continue;
- }
-
- this.requestGroupByFutureId.put(message.futureId, requestGroup);
- requestGroup.requestMessages.put(message.futureId, message);
- requestGroup.requestAddSemaphore.release();
- break;
- }
- }
-
-
- /** May send an error message in response if the message is a {@link AbstractTrackableMessage} */
- private boolean messagePlayerInThisLevel(T message)
- {
- if (!(message instanceof ILevelRelatedMessage))
- {
- LodUtil.assertNotReach("Received message ["+ILevelRelatedMessage.class.getSimpleName()+"] does not implement ["+message.getClass().getSimpleName()+"]");
- }
-
- // Only handle requests for this level
- if (!((ILevelRelatedMessage) message).isSameLevelAs(this.getServerLevelWrapper()))
- {
- return false;
- }
-
- LodUtil.assertTrue(message.getSession().serverPlayer != null);
-
- // Check if the player is in this dimension,
- // since handling multiple dimensions isn't allowed
- if (message.getSession().serverPlayer.getLevel() != this.getLevelWrapper())
- {
- // If the message can be replied to - reply with an error, otherwise just ignore
- if (message instanceof AbstractTrackableMessage)
- {
- ((AbstractTrackableMessage) message).sendResponse(
- new InvalidLevelException(
- "Generation not allowed. " +
- "Requested dimension: ["+((ILevelRelatedMessage) message).getLevelName()+"], " +
- "player dimension: ["+message.getSession().serverPlayer.getLevel().getDimensionName()+"], " +
- "handler dimension: ["+this.getLevelWrapper().getDimensionName()+"]"
- )
- );
- }
-
- return false;
- }
-
-
- return true;
- }
-
-
-
- //===========//
- // world gen //
- //===========//
-
- @Override
- public void onWorldGenTaskComplete(long pos)
- {
- DataSourceRequestGroup requestGroup = this.requestGroupByPos.get(pos);
- if (requestGroup != null)
- {
- this.tryFulfillDataSourceRequestGroup(requestGroup, pos);
- }
- }
-
- private void tryFulfillDataSourceRequestGroup(DataSourceRequestGroup requestGroup, long pos)
- {
- this.serverside.fullDataFileHandler.getAsync(pos).thenAccept(fullDataSource ->
- {
- if (this.serverside.fullDataFileHandler.isFullyGenerated(fullDataSource.columnGenerationSteps))
- {
- requestGroup.fullDataSource = fullDataSource;
- }
- else
- {
- this.serverside.fullDataFileHandler.queuePositionForRetrieval(pos);
- }
- });
- }
-
-
-
- //=================//
- // player handling //
- //=================//
-
- public void addPlayer(IServerPlayerWrapper serverPlayer) { this.worldGenPlayerCenteringQueue.add(serverPlayer); }
- public void removePlayer(IServerPlayerWrapper serverPlayer) { this.worldGenPlayerCenteringQueue.remove(serverPlayer); }
-
-
-
- @Override
- public CompletableFuture updateDataSourcesAsync(FullDataSourceV2 data)
- {
- if (!Config.Client.Advanced.Multiplayer.ServerNetworking.enableRealTimeUpdates.get())
- {
- return this.getFullDataProvider().updateDataSourceAsync(data);
- }
-
- ThreadPoolExecutor executor = ThreadPoolUtil.getNetworkCompressionExecutor();
- if (executor == null)
- {
- LOGGER.warn("Unable to send FullDataPartialUpdateMessage - getNetworkCompressionExecutor() is null");
- return this.getFullDataProvider().updateDataSourceAsync(data);
- }
- CompletableFuture.runAsync(() ->
- {
- FullDataPayload payload = new FullDataPayload(data);
- for (ServerPlayerState serverPlayerState : this.remotePlayerConnectionHandler.getConnectedPlayers())
- {
- if (serverPlayerState.getServerPlayer().getLevel() != this.serverLevelWrapper)
- {
- continue;
- }
-
- if (!serverPlayerState.sessionConfig.isRealTimeUpdatesEnabled())
- {
- continue;
- }
-
- Vec3d playerPosition = serverPlayerState.getServerPlayer().getPosition();
- int distanceFromPlayer = DhSectionPos.getManhattanBlockDistance(data.getPos(), new DhBlockPos2D((int) playerPosition.x, (int) playerPosition.z)) / 16;
- if (distanceFromPlayer >= serverPlayerState.getServerPlayer().getViewDistance()
- && distanceFromPlayer <= serverPlayerState.sessionConfig.getRenderDistanceRadius())
- {
- payload.splitAndSend(FULL_DATA_SPLIT_SIZE_IN_BYTES, serverPlayerState.networkSession::sendMessage);
- serverPlayerState.networkSession.sendMessage(new FullDataPartialUpdateMessage(this.serverLevelWrapper, payload));
- }
- }
- }, executor);
-
-
- return this.getFullDataProvider().updateDataSourceAsync(data);
+ return true; //todo;
}
@@ -465,24 +56,6 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
// getters //
//=========//
- @Override
- public int getMinY() { return this.getLevelWrapper().getMinHeight(); }
-
- @Override
- public IServerLevelWrapper getServerLevelWrapper() { return this.serverLevelWrapper; }
-
- @Override
- public ILevelWrapper getLevelWrapper() { return this.getServerLevelWrapper(); }
-
- @Override
- public FullDataSourceProviderV2 getFullDataProvider() { return this.serverside.fullDataFileHandler; }
-
- @Override
- public AbstractSaveStructure getSaveStructure() { return this.serverside.saveStructure; }
-
- @Override
- public boolean hasSkyLight() { return this.serverLevelWrapper.hasSkyLight(); }
-
@Override
public GenericObjectRenderer getGenericRenderer()
{
@@ -523,30 +96,4 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
LOGGER.info("Closed DHLevel for ["+this.getLevelWrapper()+"].");
}
-
-
- //================//
- // helper classes //
- //================//
-
- private static class DataSourceRequestGroup
- {
- public final ConcurrentMap requestMessages = new ConcurrentHashMap<>();
-
- @CheckForNull
- public FullDataSourceV2 fullDataSource;
-
- /**
- * These semaphores prevent a given thread from accidentally locking on the same group
- * multiple times, as the semaphore is tied to the given thread.
- * Reentrant Lock isn't used since it would allow the thread to lock on the same group.
- * the Short.MAX_VALUE is just a very large number that should be larger than the number of
- * threads we'll have.
- */
- public final Semaphore requestAddSemaphore = new Semaphore(Short.MAX_VALUE, true);
- /** @see DataSourceRequestGroup#requestAddSemaphore */
- public final Semaphore requestRemoveSemaphore = new Semaphore(Short.MAX_VALUE, true);
-
- }
-
}
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java b/core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java
index 7ca1f3e09..9da620788 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java
@@ -34,7 +34,7 @@ public class ServerLevelModule implements AutoCloseable
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
- public final IDhServerLevel parentServerLevel;
+ private final IDhServerLevel parentServerLevel;
public final AbstractSaveStructure saveStructure;
public final GeneratedFullDataSourceProvider fullDataFileHandler;
public final AppliedConfigState worldGeneratorEnabledConfig;
@@ -53,7 +53,7 @@ public class ServerLevelModule implements AutoCloseable
this.saveStructure = saveStructure;
this.fullDataFileHandler = new GeneratedFullDataSourceProvider(parentServerLevel, saveStructure);
this.worldGeneratorEnabledConfig = new AppliedConfigState<>(Config.Client.Advanced.WorldGenerator.enableDistantGeneration);
- this.worldGenModule = new WorldGenModule(this.parentServerLevel);
+ this.worldGenModule = new WorldGenModule(this.parentServerLevel, this.fullDataFileHandler, () -> new ServerLevelModule.WorldGenState(this.parentServerLevel));
}
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/WorldGenModule.java b/core/src/main/java/com/seibel/distanthorizons/core/level/WorldGenModule.java
index 5ddf8729a..9b0c4716d 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/level/WorldGenModule.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/level/WorldGenModule.java
@@ -25,11 +25,15 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import org.apache.logging.log4j.Logger;
+import org.jetbrains.annotations.Nullable;
import java.io.Closeable;
import java.util.List;
+import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BooleanSupplier;
+import java.util.function.Supplier;
/**
* Handles both single-player/server-side world gen and client side LOD requests.
@@ -41,6 +45,9 @@ public class WorldGenModule implements Closeable
private final GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener onWorldGenCompleteListener;
+ private final GeneratedFullDataSourceProvider dataSourceProvider;
+ private final Supplier extends AbstractWorldGenState> worldGenStateSupplier;
+
private final AtomicReference worldGenStateRef = new AtomicReference<>();
@@ -49,9 +56,15 @@ public class WorldGenModule implements Closeable
// constructor //
//=============//
- public WorldGenModule(GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener onWorldGenCompleteListener)
+ public WorldGenModule(
+ GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener onWorldGenCompleteListener,
+ GeneratedFullDataSourceProvider dataSourceProvider,
+ Supplier extends AbstractWorldGenState> worldGenStateSupplier
+ )
{
this.onWorldGenCompleteListener = onWorldGenCompleteListener;
+ this.dataSourceProvider = dataSourceProvider;
+ this.worldGenStateSupplier = worldGenStateSupplier;
}
@@ -95,13 +108,33 @@ public class WorldGenModule implements Closeable
dataFileHandler.removeWorldGenCompleteListener(this.onWorldGenCompleteListener);
}
- /** @param targetPosForGeneration the position that world generation should be centered around */
- public void worldGenTick(DhBlockPos2D targetPosForGeneration)
+ public void worldGenTick()
{
- AbstractWorldGenState worldGenState = this.worldGenStateRef.get();
- if (worldGenState != null)
+ boolean shouldDoWorldGen = this.onWorldGenCompleteListener.shouldDoWorldGen();
+
+ boolean isWorldGenRunning = this.isWorldGenRunning();
+ if (shouldDoWorldGen && !isWorldGenRunning)
{
- worldGenState.startGenerationQueueAndSetTargetPos(targetPosForGeneration);
+ // start world gen
+ this.startWorldGen(this.dataSourceProvider, this.worldGenStateSupplier.get());
+ }
+ else if (!shouldDoWorldGen && isWorldGenRunning)
+ {
+ // stop world gen
+ this.stopWorldGen(this.dataSourceProvider);
+ }
+
+ if (this.isWorldGenRunning())
+ {
+ AbstractWorldGenState worldGenState = this.worldGenStateRef.get();
+ if (worldGenState != null)
+ {
+ DhBlockPos2D targetPosForGeneration = this.onWorldGenCompleteListener.getTargetPosForGeneration();
+ if (targetPosForGeneration != null)
+ {
+ worldGenState.startGenerationQueueAndSetTargetPos(targetPosForGeneration);
+ }
+ }
}
}
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 dfb1205a4..c5674a763 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
@@ -2,7 +2,7 @@ package com.seibel.distanthorizons.core.multiplayer.server;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
-import com.seibel.distanthorizons.core.level.DhServerLevel;
+import com.seibel.distanthorizons.core.level.AbstractDhServerLevel;
import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig;
import com.seibel.distanthorizons.core.network.messages.base.CurrentLevelKeyMessage;
import com.seibel.distanthorizons.core.network.messages.base.SessionConfigMessage;
@@ -33,8 +33,8 @@ public class ServerPlayerState implements Closeable
@NotNull
public final SessionConfig sessionConfig = new SessionConfig();
- private final ConcurrentHashMap rateLimiterSets = new ConcurrentHashMap<>();
- public RateLimiterSet getRateLimiterSet(DhServerLevel level) { return this.rateLimiterSets.computeIfAbsent(level, ignored -> new RateLimiterSet()); }
+ private final ConcurrentHashMap rateLimiterSets = new ConcurrentHashMap<>();
+ public RateLimiterSet getRateLimiterSet(AbstractDhServerLevel level) { return this.rateLimiterSets.computeIfAbsent(level, ignored -> new RateLimiterSet()); }
public void clearRateLimiterSets() { this.rateLimiterSets.clear(); }
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/RemotePlayerConnectionHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServerPlayerStateManager.java
similarity index 61%
rename from core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/RemotePlayerConnectionHandler.java
rename to core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServerPlayerStateManager.java
index 2ae9e1290..51ac789f1 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/RemotePlayerConnectionHandler.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServerPlayerStateManager.java
@@ -9,11 +9,12 @@ import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicBoolean;
-public class RemotePlayerConnectionHandler
+public class ServerPlayerStateManager
{
private final ConcurrentMap connectedPlayerStateByPlayerWrapper = new ConcurrentHashMap<>();
- private final ConcurrentMap> messageQueueByPlayerWrapper = new ConcurrentHashMap<>();
+ private final ConcurrentMap messageQueueByPlayerWrapper = new ConcurrentHashMap<>();
@@ -26,17 +27,8 @@ public class RemotePlayerConnectionHandler
ServerPlayerState playerState = new ServerPlayerState(serverPlayer);
this.connectedPlayerStateByPlayerWrapper.put(serverPlayer, playerState);
- Queue queuedMessages = this.messageQueueByPlayerWrapper.get(serverPlayer);
- if (queuedMessages != null)
- {
- NetworkSession networkSession = playerState.networkSession;
- for (AbstractNetworkMessage message : queuedMessages)
- {
- networkSession.tryHandleMessage(message);
- }
-
- this.messageQueueByPlayerWrapper.remove(serverPlayer);
- }
+ MessageQueueState messageQueue = this.messageQueueByPlayerWrapper.computeIfAbsent(serverPlayer, k -> new MessageQueueState());
+ this.handlePluginMessagesFromQueue(playerState, messageQueue);
return playerState;
}
@@ -58,14 +50,24 @@ public class RemotePlayerConnectionHandler
public void handlePluginMessage(IServerPlayerWrapper player, AbstractNetworkMessage message)
{
+ MessageQueueState messageQueue = this.messageQueueByPlayerWrapper.computeIfAbsent(player, k -> new MessageQueueState());
+ messageQueue.messageQueue.add(message);
+
ServerPlayerState playerState = this.connectedPlayerStateByPlayerWrapper.get(player);
if (playerState != null)
{
- playerState.networkSession.tryHandleMessage(message);
+ this.handlePluginMessagesFromQueue(playerState, messageQueue);
}
- else
+ }
+
+ private void handlePluginMessagesFromQueue(ServerPlayerState playerState, MessageQueueState messageQueueState)
+ {
+ while (!messageQueueState.messageQueue.isEmpty() && messageQueueState.isBeingDrained.compareAndSet(false, true))
{
- this.messageQueueByPlayerWrapper.computeIfAbsent(player, k -> new ConcurrentLinkedQueue<>()).add(message);
+ AbstractNetworkMessage message = messageQueueState.messageQueue.poll();
+ playerState.networkSession.tryHandleMessage(message);
+
+ messageQueueState.isBeingDrained.set(false);
}
}
@@ -80,6 +82,12 @@ public class RemotePlayerConnectionHandler
public Iterable getConnectedPlayers() { return this.connectedPlayerStateByPlayerWrapper.values(); }
+ private static class MessageQueueState
+ {
+ public final Queue messageQueue = new ConcurrentLinkedQueue<>();
+ public final AtomicBoolean isBeingDrained = new AtomicBoolean();
+
+ }
}
\ No newline at end of file
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/AbstractDhServerWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/AbstractDhServerWorld.java
new file mode 100644
index 000000000..d5bb12dea
--- /dev/null
+++ b/core/src/main/java/com/seibel/distanthorizons/core/world/AbstractDhServerWorld.java
@@ -0,0 +1,120 @@
+package com.seibel.distanthorizons.core.world;
+
+import com.seibel.distanthorizons.core.file.structure.LocalSaveStructure;
+import com.seibel.distanthorizons.core.level.AbstractDhServerLevel;
+import com.seibel.distanthorizons.core.level.IDhLevel;
+import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerState;
+import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager;
+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 org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+
+public abstract class AbstractDhServerWorld extends AbstractDhWorld implements IDhServerWorld
+{
+ protected final HashMap levelWrapperByDhLevel = new HashMap<>();
+ public final LocalSaveStructure saveStructure = new LocalSaveStructure();
+
+ private final ServerPlayerStateManager serverPlayerStateManager;
+
+ public AbstractDhServerWorld(EWorldEnvironment worldEnvironment)
+ {
+ super(worldEnvironment);
+ this.serverPlayerStateManager = new ServerPlayerStateManager();
+ }
+
+
+ //=================//
+ // player handling //
+ //=================//
+
+
+ @Override
+ public ServerPlayerStateManager getServerPlayerStateManager()
+ {
+ return this.serverPlayerStateManager;
+ }
+
+ @Override
+ public void addPlayer(IServerPlayerWrapper serverPlayer)
+ {
+ ServerPlayerState playerState = this.serverPlayerStateManager.registerJoinedPlayer(serverPlayer);
+ this.getLevel(serverPlayer.getLevel()).addPlayer(serverPlayer);
+
+ for (TDhServerLevel level : this.levelWrapperByDhLevel.values())
+ {
+ level.registerNetworkHandlers(playerState);
+ }
+ }
+
+ @Override
+ public void removePlayer(IServerPlayerWrapper serverPlayer)
+ {
+ this.getLevel(serverPlayer.getLevel()).removePlayer(serverPlayer);
+ this.serverPlayerStateManager.unregisterLeftPlayer(serverPlayer);
+
+ // If player's left, session is already closed
+ }
+
+ @Override
+ public void changePlayerLevel(IServerPlayerWrapper player, IServerLevelWrapper originLevel, IServerLevelWrapper destinationLevel)
+ {
+ this.getLevel(destinationLevel).addPlayer(player);
+ this.getLevel(originLevel).removePlayer(player);
+ }
+
+
+
+ //================//
+ // level handling //
+ //================//
+
+ @Override
+ public TDhServerLevel getLevel(@NotNull ILevelWrapper wrapper) { return this.levelWrapperByDhLevel.get(wrapper); }
+ @Override
+ public Iterable extends IDhLevel> getAllLoadedLevels() { return this.levelWrapperByDhLevel.values(); }
+ @Override
+ public int getLoadedLevelCount() { return this.levelWrapperByDhLevel.size(); }
+
+
+
+ //==============//
+ // tick methods //
+ //==============//
+
+ @Override
+ public void serverTick() { this.levelWrapperByDhLevel.values().forEach(TDhServerLevel::serverTick); }
+
+ @Override
+ public void worldGenTick() { this.levelWrapperByDhLevel.values().forEach(TDhServerLevel::worldGenTick); }
+
+
+
+ //================//
+ // base overrides //
+ //================//
+
+ @Override
+ public void close()
+ {
+ for (TDhServerLevel level : this.levelWrapperByDhLevel.values())
+ {
+ LOGGER.info("Unloading level [" + level.getLevelWrapper().getDimensionName() + "].");
+
+ // level wrapper shouldn't be null, but just in case
+ IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper();
+ if (serverLevelWrapper != null)
+ {
+ serverLevelWrapper.onUnload();
+ }
+
+ level.close();
+ }
+
+ this.levelWrapperByDhLevel.clear();
+ LOGGER.info("Closed DhWorld of type [" + this.environment + "].");
+ }
+
+}
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java
index a9d238398..41f9ce6df 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java
@@ -19,8 +19,6 @@
package com.seibel.distanthorizons.core.world;
-import com.seibel.distanthorizons.core.file.structure.LocalSaveStructure;
-import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.level.DhClientServerLevel;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.objects.EventLoop;
@@ -31,15 +29,14 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapp
import org.jetbrains.annotations.NotNull;
import java.io.File;
-import java.util.HashMap;
+import java.util.Collections;
import java.util.HashSet;
+import java.util.Set;
import java.util.concurrent.ExecutorService;
-public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWorld, IDhServerWorld
+public class DhClientServerWorld extends AbstractDhServerWorld implements IDhClientWorld
{
- private final HashMap levelWrapperByDhLevel = new HashMap<>();
- private final HashSet dhLevels = new HashSet<>();
- public final LocalSaveStructure saveStructure = new LocalSaveStructure();
+ private final Set dhLevels = Collections.synchronizedSet(new HashSet<>());
public ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("Client Server World Ticker Thread", 2);
public EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick); //TODO: Rate-limit the loop
@@ -71,7 +68,7 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
{
File levelFile = this.saveStructure.getLevelFolder(levelWrapper);
LodUtil.assertTrue(levelFile != null);
- DhClientServerLevel level = new DhClientServerLevel(this.saveStructure, (IServerLevelWrapper) levelWrapper);
+ DhClientServerLevel level = new DhClientServerLevel(this.saveStructure, (IServerLevelWrapper) levelWrapper, this.getServerPlayerStateManager());
this.dhLevels.add(level);
return level;
});
@@ -102,14 +99,6 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
}
}
- @Override
- public DhClientServerLevel getLevel(@NotNull ILevelWrapper wrapper) { return this.levelWrapperByDhLevel.get(wrapper); }
-
- @Override
- public Iterable extends IDhLevel> getAllLoadedLevels() { return this.dhLevels; }
- @Override
- public int getLoadedLevelCount() { return this.dhLevels.size(); }
-
@Override
public void unloadLevel(@NotNull ILevelWrapper wrapper)
{
@@ -140,16 +129,12 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
this.dhLevels.forEach(DhClientServerLevel::clientTick);
}
- public void clientTick()
+ @Override public void clientTick()
{
//LOGGER.info("Client world tick");
this.eventLoop.tick();
}
- public void serverTick() { this.dhLevels.forEach(DhClientServerLevel::serverTick); }
-
- public void worldGenTick() { this.dhLevels.forEach(DhClientServerLevel::worldGenTick); }
-
//================//
@@ -160,22 +145,22 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
@Override
public synchronized void close()
{
- // clear dhLevels to prevent concurrent modification errors
- HashSet levelsToClose = new HashSet<>(this.dhLevels);
- this.dhLevels.clear();
- // close each level
- for (DhClientServerLevel level : levelsToClose)
+ synchronized (this.dhLevels)
{
- LOGGER.info("Unloading level " + level.getServerLevelWrapper().getDimensionName());
-
- // level wrapper shouldn't be null, but just in case
- IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper();
- if (serverLevelWrapper != null)
+ // close each level
+ for (DhClientServerLevel level : this.dhLevels)
{
- serverLevelWrapper.onUnload();
+ LOGGER.info("Unloading level " + level.getServerLevelWrapper().getDimensionName());
+
+ // level wrapper shouldn't be null, but just in case
+ IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper();
+ if (serverLevelWrapper != null)
+ {
+ serverLevelWrapper.onUnload();
+ }
+
+ level.close();
}
-
- level.close();
}
this.levelWrapperByDhLevel.clear();
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 22905066b..96ad5f872 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
@@ -19,29 +19,16 @@
package com.seibel.distanthorizons.core.world;
-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.multiplayer.server.RemotePlayerConnectionHandler;
-import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerState;
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 org.jetbrains.annotations.NotNull;
import java.io.File;
-import java.util.HashMap;
-public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
+public class DhServerWorld extends AbstractDhServerWorld
{
- private final HashMap levels;
- public final LocalSaveStructure saveStructure;
-
- public final RemotePlayerConnectionHandler remotePlayerConnectionHandler;
-
-
-
//==============//
// constructors //
//==============//
@@ -49,48 +36,10 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
public DhServerWorld()
{
super(EWorldEnvironment.Server_Only);
-
- this.saveStructure = new LocalSaveStructure();
- this.levels = new HashMap<>();
-
- this.remotePlayerConnectionHandler = new RemotePlayerConnectionHandler();
-
LOGGER.info("Started ["+DhServerWorld.class.getSimpleName()+"] of type ["+this.environment+"].");
}
-
- //=================//
- // player handling //
- //=================//
-
- public void addPlayer(IServerPlayerWrapper serverPlayer)
- {
- ServerPlayerState playerState = this.remotePlayerConnectionHandler.registerJoinedPlayer(serverPlayer);
- this.getLevel(serverPlayer.getLevel()).addPlayer(serverPlayer);
-
- for (DhServerLevel level : this.levels.values())
- {
- level.registerNetworkHandlers(playerState);
- }
- }
-
- public void removePlayer(IServerPlayerWrapper serverPlayer)
- {
- this.getLevel(serverPlayer.getLevel()).removePlayer(serverPlayer);
- this.remotePlayerConnectionHandler.unregisterLeftPlayer(serverPlayer);
-
- // If player's left, session is already closed
- }
-
- public void changePlayerLevel(IServerPlayerWrapper player, IServerLevelWrapper originLevel, IServerLevelWrapper destinationLevel)
- {
- this.getLevel(destinationLevel).addPlayer(player);
- this.getLevel(originLevel).removePlayer(player);
- }
-
-
-
//================//
// level handling //
//================//
@@ -103,30 +52,14 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
return null;
}
- return this.levels.computeIfAbsent((IServerLevelWrapper) wrapper, (serverLevelWrapper) ->
+ return this.levelWrapperByDhLevel.computeIfAbsent(wrapper, (serverLevelWrapper) ->
{
File levelFile = this.saveStructure.getLevelFolder(wrapper);
LodUtil.assertTrue(levelFile != null);
- return new DhServerLevel(this.saveStructure, serverLevelWrapper, this.remotePlayerConnectionHandler);
+ return new DhServerLevel(this.saveStructure, (IServerLevelWrapper) serverLevelWrapper, this.getServerPlayerStateManager());
});
}
- @Override
- public DhServerLevel getLevel(@NotNull ILevelWrapper wrapper)
- {
- if (!(wrapper instanceof IServerLevelWrapper))
- {
- return null;
- }
-
- return this.levels.get(wrapper);
- }
-
- @Override
- public Iterable extends IDhLevel> getAllLoadedLevels() { return this.levels.values(); }
- @Override
- public int getLoadedLevelCount() { return this.levels.size(); }
-
@Override
public void unloadLevel(@NotNull ILevelWrapper wrapper)
{
@@ -135,51 +68,12 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
return;
}
- if (this.levels.containsKey(wrapper))
+ if (this.levelWrapperByDhLevel.containsKey(wrapper))
{
- LOGGER.info("Unloading level {} ", this.levels.get(wrapper));
+ LOGGER.info("Unloading level {} ", this.levelWrapperByDhLevel.get(wrapper));
wrapper.onUnload();
- this.levels.remove(wrapper).close();
+ this.levelWrapperByDhLevel.remove(wrapper).close();
}
}
-
-
- //==============//
- // tick methods //
- //==============//
-
- @Override
- public void serverTick() { this.levels.values().forEach(DhServerLevel::serverTick); }
-
- @Override
- public void worldGenTick() { this.levels.values().forEach(DhServerLevel::worldGenTick); }
-
-
-
- //================//
- // base overrides //
- //================//
-
- @Override
- public void close()
- {
- for (DhServerLevel level : this.levels.values())
- {
- LOGGER.info("Unloading level [" + level.getLevelWrapper().getDimensionName() + "].");
-
- // level wrapper shouldn't be null, but just in case
- IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper();
- if (serverLevelWrapper != null)
- {
- serverLevelWrapper.onUnload();
- }
-
- level.close();
- }
-
- this.levels.clear();
- LOGGER.info("Closed DhWorld of type [" + this.environment + "].");
- }
-
}
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/IDhServerWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/IDhServerWorld.java
index 17bd2fcbf..d71a510f5 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/world/IDhServerWorld.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/world/IDhServerWorld.java
@@ -20,11 +20,18 @@
package com.seibel.distanthorizons.core.world;
import com.seibel.distanthorizons.core.level.IDhServerLevel;
+import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager;
+import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
+import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
/** Used both for dedicated server and singleplayer worlds */
public interface IDhServerWorld extends IDhWorld
{
+ ServerPlayerStateManager getServerPlayerStateManager();
+ void addPlayer(IServerPlayerWrapper serverPlayer);
+ void removePlayer(IServerPlayerWrapper serverPlayer);
+ void changePlayerLevel(IServerPlayerWrapper player, IServerLevelWrapper originLevel, IServerLevelWrapper destinationLevel);
void serverTick();
default IDhServerLevel getOrLoadServerLevel(ILevelWrapper levelWrapper) { return (IDhServerLevel) this.getOrLoadLevel(levelWrapper); }