Compare commits

...

6 Commits

Author SHA1 Message Date
s809 034ec7d656 Bump protocol version 2025-08-16 21:01:45 +05:00
s809 fb5e15a2f1 Add a server keys feature 2025-08-16 20:59:28 +05:00
s809 674fc30e77 Replace pooled buffers with unpooled 2025-08-07 17:55:22 +05:00
s809 a05bd307f9 Reduce network logging by default 2025-07-27 23:21:13 +05:00
James Seibel d78a50ce49 up version number 2.3.4 -> 2.3.5 2025-07-19 14:59:14 -05:00
James Seibel 013eab9268 add space to self updater warning log 2025-07-19 14:57:22 -05:00
19 changed files with 120 additions and 120 deletions
@@ -40,6 +40,7 @@ public enum EDhApiLoggerMode
LOG_DEBUG_TO_CHAT_AND_FILE(Level.DEBUG, Level.DEBUG), LOG_DEBUG_TO_CHAT_AND_FILE(Level.DEBUG, Level.DEBUG),
LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE(Level.INFO, Level.WARN), LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE(Level.INFO, Level.WARN),
LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE(Level.INFO, Level.ERROR), LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE(Level.INFO, Level.ERROR),
LOG_ERROR_TO_CHAT_AND_WARNING_TO_FILE(Level.ERROR, Level.WARN),
; ;
public final Level levelForFile; public final Level levelForFile;
@@ -31,14 +31,14 @@ public final class ModInfo
public static final String DEDICATED_SERVER_INITIAL_PATH = "dedicated_server_initial"; public static final String DEDICATED_SERVER_INITIAL_PATH = "dedicated_server_initial";
/** Incremented every time any packets are added, changed or removed, with a few exceptions. */ /** Incremented every time any packets are added, changed or removed, with a few exceptions. */
public static final int PROTOCOL_VERSION = 11; public static final int PROTOCOL_VERSION = 12;
public static final String WRAPPER_PACKET_PATH = "message"; public static final String WRAPPER_PACKET_PATH = "message";
/** The internal mod name */ /** The internal mod name */
public static final String NAME = "DistantHorizons"; public static final String NAME = "DistantHorizons";
/** Human-readable version of NAME */ /** Human-readable version of NAME */
public static final String READABLE_NAME = "Distant Horizons"; public static final String READABLE_NAME = "Distant Horizons";
public static final String VERSION = "2.3.4-b"; public static final String VERSION = "2.3.5-b-dev";
/** Returns true if the current build is an unstable developer build, false otherwise. */ /** Returns true if the current build is an unstable developer build, false otherwise. */
public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev"); public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
@@ -75,7 +75,12 @@ public class ClientPluginChannelApi
private void onLevelInitMessage(LevelInitMessage msg) private void onLevelInitMessage(LevelInitMessage msg)
{ {
if (!msg.levelKey.matches(LevelInitMessage.VALIDATION_REGEX)) if (!msg.serverKey.matches(LevelInitMessage.SERVER_KEY_REGEX))
{
throw new IllegalArgumentException("Server sent invalid server key.");
}
if (!msg.levelKey.matches(LevelInitMessage.LEVEL_KEY_REGEX))
{ {
throw new IllegalArgumentException("Server sent invalid level key."); throw new IllegalArgumentException("Server sent invalid level key.");
} }
@@ -105,10 +110,12 @@ public class ClientPluginChannelApi
this.levelUnloadHandler.accept(clientLevel); this.levelUnloadHandler.accept(clientLevel);
} }
if (existingKeyedClientLevel == null || !existingKeyedClientLevel.getServerLevelKey().equals(msg.levelKey)) if (existingKeyedClientLevel == null
|| !existingKeyedClientLevel.getServerKey().equals(msg.serverKey)
|| !existingKeyedClientLevel.getServerLevelKey().equals(msg.levelKey))
{ {
LOGGER.info("Loading level with key: [" + msg.levelKey + "]."); LOGGER.info("Loading level with key: [" + msg.levelKey + "].");
IServerKeyedClientLevel keyedLevel = KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel, msg.levelKey); IServerKeyedClientLevel keyedLevel = KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel, msg.serverKey, msg.levelKey);
this.levelLoadHandler.accept(keyedLevel); this.levelLoadHandler.accept(keyedLevel);
} }
}); });
@@ -1520,7 +1520,7 @@ public class Config
public static ConfigEntry<EDhApiLoggerMode> logNetworkEvent = new ConfigEntry.Builder<EDhApiLoggerMode>() public static ConfigEntry<EDhApiLoggerMode> logNetworkEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.setChatCommandName("logging.logNetworkEvent") .setChatCommandName("logging.logNetworkEvent")
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_WARNING_TO_FILE)
.comment("" .comment(""
+ "If enabled, the mod will log information about network operations. \n" + "If enabled, the mod will log information about network operations. \n"
+ "This can be useful for debugging.") + "This can be useful for debugging.")
@@ -1599,6 +1599,28 @@ public class Config
+ "") + "")
.build(); .build();
public static ConfigEntry<Integer> serverId = new ConfigEntry.Builder<Integer>()
.set(new Random().nextInt())
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
.comment(""
+ "DO NOT CHANGE UNLESS YOU KNOW WHAT YOU'RE DOING.\n"
+ "Autogenerated ID used to prevent multiple independent servers from accidentally\n"
+ "writing over each other's LODs when the same serverKey is set on both.\n"
+ "")
.build();
public static ConfigEntry<String> serverKey = new ConfigEntry.Builder<String>()
.setChatCommandName("levelKeys.serverKey")
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
.set("")
.comment(""
+ "Custom server key used which can be used to always reuse the same LOD data folder,\n"
+ "for cases when the server doesn't have a static IP for some reason.\n"
+ "If this value is empty, the client itself decides which folder name to use.\n"
+ "Requires rejoining the server to apply after changing.\n"
+ "")
.build();
public static ConfigEntry<String> levelKeyPrefix = new ConfigEntry.Builder<String>() public static ConfigEntry<String> levelKeyPrefix = new ConfigEntry.Builder<String>()
.setChatCommandName("levelKeys.prefix") .setChatCommandName("levelKeys.prefix")
.set("") .set("")
@@ -81,13 +81,20 @@ public class ClientOnlySaveStructure implements ISaveStructure
{ {
IServerKeyedClientLevel keyedClientLevel = (IServerKeyedClientLevel) newLevelWrapper; IServerKeyedClientLevel keyedClientLevel = (IServerKeyedClientLevel) newLevelWrapper;
LOGGER.info("Loading level [" + newLevelWrapper.getDhIdentifier() + "] with key: [" + keyedClientLevel.getServerLevelKey() + "]."); LOGGER.info("Loading level [" + newLevelWrapper.getDhIdentifier() + "] with key: [" + keyedClientLevel.getServerLevelKey() + "].");
// This world was identified by the server directly, so we can know for sure which folder to use.
saveFolder = getSaveFolderByLevelId(keyedClientLevel.getServerLevelKey()); String serverKey = keyedClientLevel.getServerKey();
if (serverKey.isEmpty())
{
serverKey = getServerFolderName();
}
// This world was identified by the server directly, so we can know for sure which folder to use.
saveFolder = getSaveFolderByLevelId(serverKey, keyedClientLevel.getServerLevelKey());
} }
else else
{ {
// get the default folder // get the default folder
saveFolder = getSaveFolderByLevelId(levelWrapper.getDhIdentifier()); saveFolder = getSaveFolderByLevelId(getServerFolderName(), levelWrapper.getDhIdentifier());
} }
// Allow API users to override the save folder // Allow API users to override the save folder
@@ -116,7 +123,7 @@ public class ClientOnlySaveStructure implements ISaveStructure
return this.getSaveFolder(levelWrapper); return this.getSaveFolder(levelWrapper);
} }
return getSaveFolderByLevelId(levelWrapper.getDimensionType().getName()); return getSaveFolderByLevelId(getServerFolderName(), levelWrapper.getDimensionType().getName());
} }
@@ -173,11 +180,11 @@ public class ClientOnlySaveStructure implements ISaveStructure
} }
private static File getSaveFolderByLevelId(String dimensionName) private static File getSaveFolderByLevelId(String folderName, String dimensionName)
{ {
String path = MC_SHARED.getInstallationDirectory().getPath() + File.separatorChar String path = MC_SHARED.getInstallationDirectory().getPath() + File.separatorChar
+ SERVER_DATA_FOLDER_NAME + File.separatorChar + SERVER_DATA_FOLDER_NAME + File.separatorChar
+ getServerFolderName() + File.separatorChar + folderName + File.separatorChar
+ dimensionName.replaceAll(":", "@@"); + dimensionName.replaceAll(":", "@@");
return new File(path); return new File(path);
@@ -111,7 +111,7 @@ public class SelfUpdater
} }
if (!ModrinthGetter.mcVersions.contains(mcVersion)) if (!ModrinthGetter.mcVersions.contains(mcVersion))
{ {
LOGGER.warn("Minecraft version ["+ mcVersion +"] is not findable on Modrinth, only findable versions are ["+ StringUtil.join(",", ModrinthGetter.mcVersions) +"]"); LOGGER.warn("Minecraft version ["+ mcVersion +"] is not findable on Modrinth, only findable versions are ["+ StringUtil.join(", ", ModrinthGetter.mcVersions) +"]");
return false; return false;
} }
@@ -261,29 +261,27 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
} }
LodUtil.assertTrue(this.beaconBeamRepo != null, "beaconBeamRepo should not be null"); LodUtil.assertTrue(this.beaconBeamRepo != null, "beaconBeamRepo should not be null");
try (FullDataPayload payload = new FullDataPayload(data, this.beaconBeamRepo.getAllBeamsForPos(data.getPos()))) FullDataPayload payload = new FullDataPayload(data, this.beaconBeamRepo.getAllBeamsForPos(data.getPos()));
for (ServerPlayerState serverPlayerState : this.serverPlayerStateManager.getReadyPlayers())
{ {
for (ServerPlayerState serverPlayerState : this.serverPlayerStateManager.getReadyPlayers()) if (serverPlayerState.getServerPlayer().getLevel() != this.serverLevelWrapper)
{ {
if (serverPlayerState.getServerPlayer().getLevel() != this.serverLevelWrapper) continue;
}
if (!serverPlayerState.sessionConfig.isRealTimeUpdatesEnabled())
{
continue;
}
Vec3d playerPosition = serverPlayerState.getServerPlayer().getPosition();
int distanceFromPlayer = DhSectionPos.getChebyshevSignedBlockDistance(data.getPos(), new DhBlockPos2D((int) playerPosition.x, (int) playerPosition.z)) / 16;
if (distanceFromPlayer <= serverPlayerState.sessionConfig.getMaxUpdateDistanceRadius())
{
serverPlayerState.fullDataPayloadSender.sendInChunks(payload, () ->
{ {
continue; serverPlayerState.networkSession.sendMessage(new FullDataPartialUpdateMessage(this.serverLevelWrapper, payload));
} });
if (!serverPlayerState.sessionConfig.isRealTimeUpdatesEnabled())
{
continue;
}
Vec3d playerPosition = serverPlayerState.getServerPlayer().getPosition();
int distanceFromPlayer = DhSectionPos.getChebyshevSignedBlockDistance(data.getPos(), new DhBlockPos2D((int) playerPosition.x, (int) playerPosition.z)) / 16;
if (distanceFromPlayer <= serverPlayerState.sessionConfig.getMaxUpdateDistanceRadius())
{
serverPlayerState.fullDataPayloadSender.sendInChunks(payload, () ->
{
serverPlayerState.networkSession.sendMessage(new FullDataPartialUpdateMessage(this.serverLevelWrapper, payload));
});
}
} }
} }
}); });
@@ -164,7 +164,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
} }
try(FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSourceAndReleaseBuffer(message.payload)) try (FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSource(message.payload))
{ {
boolean isSameLevel = message.isSameLevelAs(this.levelWrapper); boolean isSameLevel = message.isSameLevelAs(this.levelWrapper);
NETWORK_LOGGER.debug("Buffer {} isSameLevel: {}", message.payload.dtoBufferId, isSameLevel); NETWORK_LOGGER.debug("Buffer {} isSameLevel: {}", message.payload.dtoBufferId, isSameLevel);
@@ -30,7 +30,7 @@ public interface IKeyedClientLevelManager extends IBindable
{ {
IServerKeyedClientLevel getServerKeyedLevel(); IServerKeyedClientLevel getServerKeyedLevel();
/** Called when a client level is wrapped by a ServerEnhancedClientLevel, for integration into mod internals. */ /** Called when a client level is wrapped by a ServerEnhancedClientLevel, for integration into mod internals. */
IServerKeyedClientLevel setServerKeyedLevel(IClientLevelWrapper clientLevel, String levelKey); IServerKeyedClientLevel setServerKeyedLevel(IClientLevelWrapper clientLevel, String serverKey, String levelKey);
void clearKeyedLevel(); void clearKeyedLevel();
boolean isEnabled(); boolean isEnabled();
@@ -24,6 +24,9 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapp
/** Enhances a {@link IClientLevelWrapper} with server provided level information. */ /** Enhances a {@link IClientLevelWrapper} with server provided level information. */
public interface IServerKeyedClientLevel extends IClientLevelWrapper public interface IServerKeyedClientLevel extends IClientLevelWrapper
{ {
/** Returns the folder name the server wants the player to use. */
String getServerKey();
/** Returns the level key, which is used to select the correct folder on the client. */ /** Returns the level key, which is used to select the correct folder on the client. */
String getServerLevelKey(); String getServerLevelKey();
@@ -256,7 +256,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
if (response.payload != null) if (response.payload != null)
{ {
FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSourceAndReleaseBuffer(response.payload); FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSource(response.payload);
// set application flags based on the received detail level, // set application flags based on the received detail level,
// this is needed so the data sources propagate correctly // this is needed so the data sources propagate correctly
@@ -9,18 +9,17 @@ import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMe
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO; import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO; import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
/** /**
* @see FullDataSplitMessage * @see FullDataSplitMessage
*/ */
public class FullDataPayload implements INetworkObject, AutoCloseable public class FullDataPayload implements INetworkObject
{ {
private static final AtomicInteger lastBufferId = new AtomicInteger(); private static final AtomicInteger lastBufferId = new AtomicInteger();
@@ -47,7 +46,7 @@ public class FullDataPayload implements INetworkObject, AutoCloseable
EDhApiDataCompressionMode compressionMode = Config.Common.LodBuilding.dataCompression.get(); EDhApiDataCompressionMode compressionMode = Config.Common.LodBuilding.dataCompression.get();
try (FullDataSourceV2DTO dataSourceDto = FullDataSourceV2DTO.CreateFromDataSource(fullDataSource, compressionMode)) try (FullDataSourceV2DTO dataSourceDto = FullDataSourceV2DTO.CreateFromDataSource(fullDataSource, compressionMode))
{ {
this.dtoBuffer = ByteBufAllocator.DEFAULT.buffer(); this.dtoBuffer = Unpooled.buffer();
dataSourceDto.encode(this.dtoBuffer); dataSourceDto.encode(this.dtoBuffer);
} }
} }
@@ -85,12 +84,6 @@ public class FullDataPayload implements INetworkObject, AutoCloseable
// base overrides // // base overrides //
//================// //================//
@Override
public void close()
{
this.dtoBuffer.release();
}
@Override @Override
public String toString() public String toString()
{ {
@@ -9,8 +9,8 @@ import com.seibel.distanthorizons.core.network.INetworkObject;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage; import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO; import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.CompositeByteBuf; import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.Unpooled;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import java.util.Objects; import java.util.Objects;
@@ -24,15 +24,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
private final ConcurrentMap<Integer, CompositeByteBuf> buffersById = CacheBuilder.newBuilder() private final ConcurrentMap<Integer, CompositeByteBuf> buffersById = CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.SECONDS) .expireAfterAccess(10, TimeUnit.SECONDS)
.removalListener((RemovalNotification<Integer, CompositeByteBuf> notification) -> .<Integer, CompositeByteBuf>build().asMap();
{
// If an entry was replaced without removing, the buffer has to be released manually
if (notification.getCause() != RemovalCause.REPLACED)
{
Objects.requireNonNull(notification.getValue()).release();
}
})
.build().asMap();
@Override @Override
public void close() public void close()
@@ -46,13 +38,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
{ {
if (message.isFirst) if (message.isFirst)
{ {
if (composite != null) composite = Unpooled.compositeBuffer();
{
composite.release();
LOGGER.debug("Released existing full data buffer [" + message.bufferId + "]");
}
composite = ByteBufAllocator.DEFAULT.compositeBuffer();
LOGGER.debug("Created new full data buffer [" + message.bufferId + "]: [" + composite + "]"); LOGGER.debug("Created new full data buffer [" + message.bufferId + "]: [" + composite + "]");
} }
else if (composite == null) else if (composite == null)
@@ -67,7 +53,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
}); });
} }
public FullDataSourceV2DTO decodeDataSourceAndReleaseBuffer(FullDataPayload payload) public FullDataSourceV2DTO decodeDataSource(FullDataPayload payload)
{ {
CompositeByteBuf compositeByteBuffer = this.buffersById.get(payload.dtoBufferId); CompositeByteBuf compositeByteBuffer = this.buffersById.get(payload.dtoBufferId);
LodUtil.assertTrue(compositeByteBuffer != null); LodUtil.assertTrue(compositeByteBuffer != null);
@@ -38,12 +38,6 @@ public class FullDataPayloadSender implements AutoCloseable
public void close() public void close()
{ {
this.tickTimerTask.cancel(); this.tickTimerTask.cancel();
PendingTransfer pendingTransfer;
while ((pendingTransfer = this.transferQueue.poll()) != null)
{
pendingTransfer.close();
}
} }
@@ -78,36 +72,25 @@ public class FullDataPayloadSender implements AutoCloseable
if (pendingTransfer.buffer.readableBytes() == 0) if (pendingTransfer.buffer.readableBytes() == 0)
{ {
pendingTransfer.sendFinalMessage.run(); pendingTransfer.sendFinalMessage.run();
pendingTransfer.close();
this.transferQueue.poll(); this.transferQueue.poll();
} }
} }
} }
private static class PendingTransfer implements AutoCloseable private static class PendingTransfer
{ {
public final int bufferId; public final int bufferId;
public final ByteBuf buffer; public final ByteBuf buffer;
public final Runnable sendFinalMessage; public final Runnable sendFinalMessage;
private final AtomicBoolean isClosed = new AtomicBoolean();
private PendingTransfer(FullDataPayload payload, Runnable sendFinalMessage) private PendingTransfer(FullDataPayload payload, Runnable sendFinalMessage)
{ {
this.bufferId = payload.dtoBufferId; this.bufferId = payload.dtoBufferId;
this.buffer = payload.dtoBuffer.retainedDuplicate().readerIndex(0); this.buffer = payload.dtoBuffer.duplicate().readerIndex(0);
this.sendFinalMessage = sendFinalMessage; this.sendFinalMessage = sendFinalMessage;
} }
@Override
public void close()
{
if (this.isClosed.compareAndSet(false, true))
{
this.buffer.release();
}
}
} }
} }
@@ -119,16 +119,14 @@ public class FullDataSourceRequestHandler
} }
// send the found data source to client // send the found data source to client
try (FullDataPayload payload = new FullDataPayload(fullDataSource, this.getAllBeamsForPos(message.sectionPos))) FullDataPayload payload = new FullDataPayload(fullDataSource, this.getAllBeamsForPos(message.sectionPos));
fullDataSource.close();
serverPlayerState.fullDataPayloadSender.sendInChunks(payload, () ->
{ {
fullDataSource.close(); message.sendResponse(new FullDataSourceResponseMessage(payload));
rateLimiterSet.syncOnLoginRateLimiter.release();
serverPlayerState.fullDataPayloadSender.sendInChunks(payload, () -> });
{
message.sendResponse(new FullDataSourceResponseMessage(payload));
rateLimiterSet.syncOnLoginRateLimiter.release();
});
}
} }
catch (Exception e) catch (Exception e)
{ {
@@ -245,19 +243,17 @@ public class FullDataSourceRequestHandler
} }
CompletableFuture.runAsync(() -> CompletableFuture.runAsync(() ->
{ {
try (FullDataPayload payload = new FullDataPayload(requestGroup.fullDataSource, this.getAllBeamsForPos(entry.getKey()))) FullDataPayload payload = new FullDataPayload(requestGroup.fullDataSource, this.getAllBeamsForPos(entry.getKey()));
requestGroup.fullDataSource.close();
for (DataSourceRequestGroup.RequestData requestData : requestGroup.requestMessages.values())
{ {
requestGroup.fullDataSource.close(); this.requestGroupsByFutureId.remove(requestData.futureId());
for (DataSourceRequestGroup.RequestData requestData : requestGroup.requestMessages.values()) requestData.serverPlayerState.fullDataPayloadSender.sendInChunks(payload, () -> {
{ requestData.message.sendResponse(new FullDataSourceResponseMessage(payload));
this.requestGroupsByFutureId.remove(requestData.futureId()); requestData.rateLimiterSet.generationRequestRateLimiter.release();
});
requestData.serverPlayerState.fullDataPayloadSender.sendInChunks(payload, () -> {
requestData.message.sendResponse(new FullDataSourceResponseMessage(payload));
requestData.rateLimiterSet.generationRequestRateLimiter.release();
});
}
} }
}, executor); }, executor);
} }
@@ -27,6 +27,10 @@ public class ServerPlayerState implements Closeable
private final SessionConfig.AnyChangeListener configAnyChangeListener = new SessionConfig.AnyChangeListener(this::sendConfigMessage); private final SessionConfig.AnyChangeListener configAnyChangeListener = new SessionConfig.AnyChangeListener(this::sendConfigMessage);
private final String serverKeyWithoutId = Config.Server.serverKey.get();
private final String serverKey = (this.serverKeyWithoutId.isEmpty() ? "" : Config.Server.serverId.get() + "_" + this.serverKeyWithoutId.trim())
.replaceAll("[^" + LevelInitMessage.PART_ALLOWED_CHARS_REGEX + " ]", "")
.replaceAll(" ", "_");
private String lastLevelKey = ""; private String lastLevelKey = "";
@@ -89,7 +93,7 @@ public class ServerPlayerState implements Closeable
if (!levelKey.equals(this.lastLevelKey)) if (!levelKey.equals(this.lastLevelKey))
{ {
this.lastLevelKey = levelKey; this.lastLevelKey = levelKey;
this.networkSession.sendMessage(new LevelInitMessage(levelKey)); this.networkSession.sendMessage(new LevelInitMessage(this.serverKey, levelKey));
} }
} }
} }
@@ -10,12 +10,18 @@ public class LevelInitMessage extends AbstractNetworkMessage
public static final String PART_ALLOWED_CHARS_REGEX = "a-zA-Z0-9-_"; public static final String PART_ALLOWED_CHARS_REGEX = "a-zA-Z0-9-_";
// A plain string of characters
// 1-150 characters in total
public static final String SERVER_KEY_REGEX = String.format("^(?=.{1,%s}$)[%s]+$",
MAX_LENGTH, PART_ALLOWED_CHARS_REGEX);
// prefix@namespace:path // prefix@namespace:path
// 1-150 characters in total, all parts except namespace can be omitted // 1-150 characters in total, all parts except namespace can be omitted
public static final String VALIDATION_REGEX = String.format("^(?=.{1,%s}$)([%s]+@)?[%s]+(:[%s]+)?$", public static final String LEVEL_KEY_REGEX = String.format("^(?=.{1,%s}$)([%s]+@)?[%s]+(:[%s]+)?$",
MAX_LENGTH, PART_ALLOWED_CHARS_REGEX, PART_ALLOWED_CHARS_REGEX, PART_ALLOWED_CHARS_REGEX); MAX_LENGTH, PART_ALLOWED_CHARS_REGEX, PART_ALLOWED_CHARS_REGEX, PART_ALLOWED_CHARS_REGEX);
public String serverKey;
public String levelKey; public String levelKey;
public long serverTime; public long serverTime;
@@ -26,8 +32,9 @@ public class LevelInitMessage extends AbstractNetworkMessage
//==============// //==============//
public LevelInitMessage() { } public LevelInitMessage() { }
public LevelInitMessage(String levelKey) public LevelInitMessage(String serverKey, String levelKey)
{ {
this.serverKey = serverKey;
this.levelKey = levelKey; this.levelKey = levelKey;
this.serverTime = System.currentTimeMillis(); this.serverTime = System.currentTimeMillis();
} }
@@ -41,6 +48,7 @@ public class LevelInitMessage extends AbstractNetworkMessage
@Override @Override
public void encode(ByteBuf out) public void encode(ByteBuf out)
{ {
this.writeString(this.serverKey, out);
this.writeString(this.levelKey, out); this.writeString(this.levelKey, out);
out.writeLong(this.serverTime); out.writeLong(this.serverTime);
} }
@@ -48,6 +56,7 @@ public class LevelInitMessage extends AbstractNetworkMessage
@Override @Override
public void decode(ByteBuf in) public void decode(ByteBuf in)
{ {
this.serverKey = this.readString(in);
this.levelKey = this.readString(in); this.levelKey = this.readString(in);
this.serverTime = in.readLong(); this.serverTime = in.readLong();
} }
@@ -62,6 +71,7 @@ public class LevelInitMessage extends AbstractNetworkMessage
public MoreObjects.ToStringHelper toStringHelper() public MoreObjects.ToStringHelper toStringHelper()
{ {
return super.toStringHelper() return super.toStringHelper()
.add("serverKey", this.serverKey)
.add("levelKey", this.levelKey) .add("levelKey", this.levelKey)
.add("serverTime", this.serverTime); .add("serverTime", this.serverTime);
} }
@@ -22,8 +22,8 @@ package com.seibel.distanthorizons.core.network.messages.fullData;
import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
import com.seibel.distanthorizons.core.multiplayer.fullData.FullDataPayload; import com.seibel.distanthorizons.core.multiplayer.fullData.FullDataPayload;
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage; import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.core.util.TimerUtil;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.util.Timer; import java.util.Timer;
@@ -34,16 +34,10 @@ import java.util.Timer;
*/ */
public class FullDataSplitMessage extends AbstractNetworkMessage public class FullDataSplitMessage extends AbstractNetworkMessage
{ {
private static final long BUFFER_RELEASE_DELAY_MS = 5000L;
public int bufferId; public int bufferId;
public ByteBuf buffer; public ByteBuf buffer;
public boolean isFirst; public boolean isFirst;
// Reference counting is unreliable here for some reason so this is a "fix"
private static final Timer bufferReleaseTimer = TimerUtil.CreateTimer("FullDataBufferCleanupTimer");
private boolean releaseScheduled = false;
//==============// //==============//
// constructors // // constructors //
@@ -72,12 +66,6 @@ public class FullDataSplitMessage extends AbstractNetworkMessage
out.writeBytes(this.buffer.readerIndex(0)); out.writeBytes(this.buffer.readerIndex(0));
out.writeBoolean(this.isFirst); out.writeBoolean(this.isFirst);
if (!this.releaseScheduled)
{
bufferReleaseTimer.schedule(TimerUtil.createTimerTask(this.buffer::release), BUFFER_RELEASE_DELAY_MS);
this.releaseScheduled = true;
}
} }
@Override @Override
@@ -86,7 +74,7 @@ public class FullDataSplitMessage extends AbstractNetworkMessage
this.bufferId = in.readInt(); this.bufferId = in.readInt();
int bufferSize = in.readInt(); int bufferSize = in.readInt();
this.buffer = in.readBytes(bufferSize); this.buffer = Unpooled.copiedBuffer(in.readSlice(bufferSize));
this.isFirst = in.readBoolean(); this.isFirst = in.readBoolean();
} }
@@ -1022,6 +1022,8 @@
"File: Info, Chat: Warning", "File: Info, Chat: Warning",
"distanthorizons.config.enum.EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE": "distanthorizons.config.enum.EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE":
"File: Info, Chat: Error", "File: Info, Chat: Error",
"distanthorizons.config.enum.EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_WARNING_TO_FILE":
"File: Warning, Chat: Error",
"distanthorizons.config.enum.EDhApiGpuUploadMethod.AUTO": "distanthorizons.config.enum.EDhApiGpuUploadMethod.AUTO":
"Auto", "Auto",