Abstract away serverside parts of world & level
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
|
||||
|
||||
+7
-1
@@ -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);
|
||||
|
||||
|
||||
@@ -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. <br>
|
||||
* Every tick the first player gets used for centering generation, and then is immediately moved into the back of the queue. <br>
|
||||
* TODO only add players that actually have something to generate
|
||||
*/
|
||||
protected final ConcurrentLinkedQueue<IServerPlayerWrapper> worldGenPlayerCenteringQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
private final ConcurrentMap<Long, DataSourceRequestGroup> requestGroupByPos = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<Long, DataSourceRequestGroup> 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<Long, DataSourceRequestGroup> 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 <T extends AbstractNetworkMessage> 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<Void> 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<Long, FullDataSourceRequestMessage> 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. <br>
|
||||
* Reentrant Lock isn't used since it would allow the thread to lock on the same group. <br>
|
||||
* 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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<Void> 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<Void> 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()),
|
||||
|
||||
@@ -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. <br>
|
||||
* Every tick the first player gets used for centering generation, and then is immediately moved into the back of the queue. <br>
|
||||
* TODO only add players that actually have something to generate
|
||||
*/
|
||||
private final ConcurrentLinkedQueue<IServerPlayerWrapper> worldGenPlayerCenteringQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
private final ConcurrentMap<Long, DataSourceRequestGroup> requestGroupByPos = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<Long, DataSourceRequestGroup> 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<Long, DataSourceRequestGroup> 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 <T extends AbstractNetworkMessage> 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<Void> 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<Long, FullDataSourceRequestMessage> 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. <br>
|
||||
* Reentrant Lock isn't used since it would allow the thread to lock on the same group. <br>
|
||||
* 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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Boolean> 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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<AbstractWorldGenState> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -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<DhServerLevel, RateLimiterSet> rateLimiterSets = new ConcurrentHashMap<>();
|
||||
public RateLimiterSet getRateLimiterSet(DhServerLevel level) { return this.rateLimiterSets.computeIfAbsent(level, ignored -> new RateLimiterSet()); }
|
||||
private final ConcurrentHashMap<AbstractDhServerLevel, RateLimiterSet> rateLimiterSets = new ConcurrentHashMap<>();
|
||||
public RateLimiterSet getRateLimiterSet(AbstractDhServerLevel level) { return this.rateLimiterSets.computeIfAbsent(level, ignored -> new RateLimiterSet()); }
|
||||
public void clearRateLimiterSets() { this.rateLimiterSets.clear(); }
|
||||
|
||||
|
||||
|
||||
+24
-16
@@ -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<IServerPlayerWrapper, ServerPlayerState> connectedPlayerStateByPlayerWrapper = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<IServerPlayerWrapper, Queue<AbstractNetworkMessage>> messageQueueByPlayerWrapper = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<IServerPlayerWrapper, MessageQueueState> messageQueueByPlayerWrapper = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
|
||||
@@ -26,17 +27,8 @@ public class RemotePlayerConnectionHandler
|
||||
ServerPlayerState playerState = new ServerPlayerState(serverPlayer);
|
||||
this.connectedPlayerStateByPlayerWrapper.put(serverPlayer, playerState);
|
||||
|
||||
Queue<AbstractNetworkMessage> 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<ServerPlayerState> getConnectedPlayers() { return this.connectedPlayerStateByPlayerWrapper.values(); }
|
||||
|
||||
|
||||
private static class MessageQueueState
|
||||
{
|
||||
public final Queue<AbstractNetworkMessage> messageQueue = new ConcurrentLinkedQueue<>();
|
||||
public final AtomicBoolean isBeingDrained = new AtomicBoolean();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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<TDhServerLevel extends AbstractDhServerLevel> extends AbstractDhWorld implements IDhServerWorld
|
||||
{
|
||||
protected final HashMap<ILevelWrapper, TDhServerLevel> 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 + "].");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<DhClientServerLevel> implements IDhClientWorld
|
||||
{
|
||||
private final HashMap<ILevelWrapper, DhClientServerLevel> levelWrapperByDhLevel = new HashMap<>();
|
||||
private final HashSet<DhClientServerLevel> dhLevels = new HashSet<>();
|
||||
public final LocalSaveStructure saveStructure = new LocalSaveStructure();
|
||||
private final Set<DhClientServerLevel> 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<DhClientServerLevel> 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();
|
||||
|
||||
@@ -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<DhServerLevel>
|
||||
{
|
||||
private final HashMap<IServerLevelWrapper, DhServerLevel> 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 + "].");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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); }
|
||||
|
||||
Reference in New Issue
Block a user