Remove multiverse similarity percent and add IDhApiSaveStructure

Similarity percent should no longer be needed since the server support was added
This commit is contained in:
James Seibel
2024-09-28 14:30:34 -05:00
parent 5af706daa0
commit 03c7b48c5d
16 changed files with 136 additions and 155 deletions
@@ -39,15 +39,4 @@ public interface IDhApiMultiplayerConfig extends IDhApiConfigGroup
*/
IDhApiConfigValue<EDhApiServerFolderNameMode> folderSavingMode();
/**
* Defines the necessary similarity (as a percent) that two potential levels
* need in order to be considered the same. <br> <br>
*
* Setting this to zero causes every level of a specific dimension type to be considered
* the same level. <br>
* Setting this to a non-zero value allows for usage in servers that user Multiverse
* or similar mods.
*/
IDhApiConfigValue<Double> multiverseSimilarityRequirement();
}
@@ -0,0 +1,51 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.api.interfaces.override.levelHandling;
import com.seibel.distanthorizons.api.enums.EDhApiDetailLevel;
import com.seibel.distanthorizons.api.interfaces.override.IDhApiOverrideable;
import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper;
import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
import java.io.File;
/**
* Used to override which folder DH uses when loading a level.
* Can be used to redirect LOD data saving into a more manageable location
* or for replays/local-servers that are running out of a different folder
* than where the DH data is normally saved.
*
* @author James Seibel
* @version 2024-9-28
* @since API 4.0.0
*/
public interface IDhApiSaveStructure extends IDhApiOverrideable
{
/**
* Called when DH first loads a level to determine which folder it should use
* for file handling.
*
* @param currentFilePath the file path DH is planning to use. If this method returns null this is the file path that will be used.
* @param levelWrapper the level this file path is used for.
* @return null if you don't want to override the file path. Non-null if you want to change the file path.
*/
File overrideFilePath(File currentFilePath, IDhApiLevelWrapper levelWrapper);
}
@@ -36,7 +36,4 @@ public class DhApiMultiplayerConfig implements IDhApiMultiplayerConfig
public IDhApiConfigValue<EDhApiServerFolderNameMode> folderSavingMode()
{ return new DhApiConfigValue<EDhApiServerFolderNameMode, EDhApiServerFolderNameMode>(Config.Client.Advanced.Multiplayer.serverFolderNameMode); }
public IDhApiConfigValue<Double> multiverseSimilarityRequirement()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Multiplayer.multiverseSimilarityRequiredPercent); }
}
@@ -230,7 +230,7 @@ public class ClientApi
}
}
public void clientLevelLoadEvent(IClientLevelWrapper level)
public void clientLevelLoadEvent(IClientLevelWrapper levelWrapper)
{
// wait a moment before loading the level to give the server a chance to handle the client's login request
if (MC_CLIENT.clientConnectedToDedicatedServer())
@@ -241,7 +241,7 @@ public class ClientApi
this.firstLevelLoadTimer.schedule(new TimerTask()
{
@Override
public void run() { ClientApi.this.clientLevelLoadEvent(level); }
public void run() { ClientApi.this.clientLevelLoadEvent(levelWrapper); }
}, FIRST_LEVEL_LOAD_DELAY_IN_MS);
return;
}
@@ -251,12 +251,12 @@ public class ClientApi
try
{
LOGGER.info("Loading client level [" + level + "]-["+level.getDimensionName()+"].");
LOGGER.info("Loading client level [" + levelWrapper + "]-["+levelWrapper.getDimensionName()+"].");
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null)
{
if (!this.pluginChannelApi.allowLevelLoading(level))
if (!this.pluginChannelApi.allowLevelLoading(levelWrapper))
{
LOGGER.info("Levels in this connection are managed by the server, skipping auto-load.");
@@ -266,14 +266,14 @@ public class ClientApi
}
world.getOrLoadLevel(level);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(level));
world.getOrLoadLevel(levelWrapper);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(levelWrapper));
this.loadWaitingChunksForLevel(level);
this.loadWaitingChunksForLevel(levelWrapper);
}
else
{
this.waitingClientLevels.add(level);
this.waitingClientLevels.add(levelWrapper);
}
}
catch (Exception e)
@@ -979,33 +979,6 @@ public class Config
+ EDhApiServerFolderNameMode.NAME_IP_PORT_MC_VERSION + ": Example: \"Minecraft Server IP 192.168.1.40:25565 GameVersion 1.16.5\"")
.build();
@Deprecated
public static ConfigEntry<Double> multiverseSimilarityRequiredPercent = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(0.0, 0.0, 1.0)
.comment(""
+ "AKA: Multiverse support. \n"
+ "\n"
+ "When matching levels (dimensions) of the same type (overworld, nether, etc.) the \n"
+ "loaded chunks must be at least this percent the same \n"
+ "in order to be considered the same world. \n"
+ "\n"
+ "Note: If you use portals to enter a dimension at two \n"
+ "different locations the system will think the dimension \n"
+ "it is two different levels. \n"
+ "\n"
+ "1.0 (100%) the chunks must be identical. \n"
+ "0.5 (50%) the chunks must be half the same. \n"
+ "0.0 (0%) disables multi-dimension support, \n"
+ " only one world will be used per dimension. \n"
+ "\n"
+ "If multiverse support is needed start with a value of 0.2 \n"
+ "and tweak the sensitivity from there."
+ "Lower values mean the matching is less strict.\n"
+ "Higher values mean the matching is more strict.\n"
+ "")
.build();
public static ConfigCategory serverNetworking = new ConfigCategory.Builder().set(ServerNetworking.class).build();
@@ -79,7 +79,7 @@ public abstract class AbstractDataSourceHandler
public AbstractDataSourceHandler(TDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride)
{
this.level = level;
this.saveDir = (saveDirOverride == null) ? saveStructure.getFullDataFolder(level.getLevelWrapper()) : saveDirOverride;
this.saveDir = (saveDirOverride == null) ? saveStructure.getSaveFolder(level.getLevelWrapper()) : saveDirOverride;
this.repo = this.createRepo();
}
@@ -45,7 +45,7 @@ public class FullDataSourceProviderV1<TDhLevel extends IDhLevel>
public FullDataSourceProviderV1(TDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride)
{
this.level = level;
this.saveDir = (saveDirOverride == null) ? saveStructure.getFullDataFolder(level.getLevelWrapper()) : saveDirOverride;
this.saveDir = (saveDirOverride == null) ? saveStructure.getSaveFolder(level.getLevelWrapper()) : saveDirOverride;
if (!this.saveDir.exists() && !this.saveDir.mkdirs())
{
LOGGER.warn("Unable to create full data folder, file saving may fail.");
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core.file.structure;
import com.google.common.net.PercentEscaper;
import com.seibel.distanthorizons.api.interfaces.override.levelHandling.IDhApiSaveStructure;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.file.subDimMatching.SubDimensionLevelMatcher;
import com.seibel.distanthorizons.core.config.Config;
@@ -31,6 +32,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftCli
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.OverrideInjector;
import com.seibel.distanthorizons.coreapi.util.StringUtil;
import org.apache.logging.log4j.Logger;
@@ -52,7 +54,6 @@ public class ClientOnlySaveStructure implements ISaveStructure
private static final IMinecraftSharedWrapper MC_SHARED = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
private SubDimensionLevelMatcher subDimMatcher = null;
private final File folder;
private final HashMap<ILevelWrapper, File> levelWrapperToFileMap = new HashMap<>();
@@ -64,6 +65,8 @@ public class ClientOnlySaveStructure implements ISaveStructure
public ClientOnlySaveStructure()
{
this.folder = new File(getSaveStructureFolderPath());
if (!this.folder.exists())
@@ -82,58 +85,6 @@ public class ClientOnlySaveStructure implements ISaveStructure
// folder methods //
//================//
@Override
public File getLevelFolder(ILevelWrapper levelWrapper)
{
return this.levelWrapperToFileMap.computeIfAbsent(levelWrapper, (newLevelWrapper) ->
{
// Use the server provided key if one was provided
if (newLevelWrapper instanceof IServerKeyedClientLevel)
{
IServerKeyedClientLevel keyedClientLevel = (IServerKeyedClientLevel) newLevelWrapper;
LOGGER.info("Loading level " + newLevelWrapper.getDimensionName() + " with key: " + keyedClientLevel.getServerLevelKey());
// This world was identified by the server directly, so we can know for sure which folder to use.
return new File(getSaveStructureFolderPath() + File.separatorChar + keyedClientLevel.getServerLevelKey().replaceAll(":", "@@"));
}
// use multiverse matching if enabled and in multiplayer (the server should already know where the player is)
if (newLevelWrapper instanceof IClientLevelWrapper && Config.Client.Advanced.Multiplayer.multiverseSimilarityRequiredPercent.get() != 0)
{
IClientLevelWrapper newClientLevelWrapper = (IClientLevelWrapper) newLevelWrapper;
// create the matcher if one doesn't exist
if (this.subDimMatcher == null || !this.subDimMatcher.isFindingLevel(newClientLevelWrapper))
{
LOGGER.info("Loading level " + newClientLevelWrapper.getDimensionName());
List<File> levelFolders = this.getDhDataFoldersForLevel(newClientLevelWrapper);
this.subDimMatcher = new SubDimensionLevelMatcher(newClientLevelWrapper, this.folder, levelFolders);
}
File levelFile = this.subDimMatcher.tryGetLevel();
if (levelFile != null)
{
this.subDimMatcher.close();
this.subDimMatcher = null;
}
return levelFile;
}
// we aren't using multiverse matching, shut down the matcher
// TODO this additional call may not be needed
if (this.subDimMatcher != null)
{
this.subDimMatcher.close();
this.subDimMatcher = null;
}
// get the default folder
return this.getLevelFolderWithoutSimilarityMatching(newLevelWrapper);
});
}
private File getLevelFolderWithoutSimilarityMatching(ILevelWrapper level)
{
List<File> folders = this.getDhDataFoldersForLevel(level);
@@ -176,15 +127,40 @@ public class ClientOnlySaveStructure implements ISaveStructure
@Override
public File getFullDataFolder(ILevelWrapper level)
public File getSaveFolder(ILevelWrapper levelWrapper)
{
File levelFolder = this.levelWrapperToFileMap.get(level);
if (levelFolder == null)
return this.levelWrapperToFileMap.computeIfAbsent(levelWrapper, (newLevelWrapper) ->
{
return null;
}
return levelFolder;
File saveFolder;
// Use the server provided key if one was provided
if (newLevelWrapper instanceof IServerKeyedClientLevel)
{
IServerKeyedClientLevel keyedClientLevel = (IServerKeyedClientLevel) newLevelWrapper;
LOGGER.info("Loading level [" + newLevelWrapper.getDimensionName() + "] with key: [" + keyedClientLevel.getServerLevelKey() + "].");
// This world was identified by the server directly, so we can know for sure which folder to use.
saveFolder = new File(getSaveStructureFolderPath() + File.separatorChar + keyedClientLevel.getServerLevelKey().replaceAll(":", "@@"));
}
else
{
// get the default folder
saveFolder = this.getLevelFolderWithoutSimilarityMatching(newLevelWrapper);;
}
// Allow API users to override the save folder
IDhApiSaveStructure saveStructureOverride = OverrideInjector.INSTANCE.get(IDhApiSaveStructure.class);
if (saveStructureOverride != null)
{
File overrideFile = saveStructureOverride.overrideFilePath(saveFolder, newLevelWrapper);
if (overrideFile != null)
{
LOGGER.info("Save folder overridden from ["+saveFolder.getPath()+"] -> ["+overrideFile.getPath()+"].");
saveFolder = overrideFile;
}
}
return saveFolder;
});
}
@@ -307,7 +283,7 @@ public class ClientOnlySaveStructure implements ISaveStructure
//==================//
@Override
public void close() { this.subDimMatcher.close(); }
public void close() { }
@Override
public String toString() { return "[" + this.getClass().getSimpleName() + "@" + this.folder.getName() + "]"; }
@@ -19,9 +19,7 @@
package com.seibel.distanthorizons.core.file.structure;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.apache.logging.log4j.Logger;
import java.io.File;
@@ -30,14 +28,11 @@ public interface ISaveStructure extends AutoCloseable
{
String DATABASE_NAME = "DistantHorizons.sqlite";
/**
/**
* Returns the folder that contains LOD data for the given {@link ILevelWrapper}.
* If no appropriate folder exists, one will be created.
* If no appropriate folder exists, one will be created.
*/
File getLevelFolder(ILevelWrapper wrapper);
/** Will return null if no parent folder exists for the given {@link ILevelWrapper}. */
File getFullDataFolder(ILevelWrapper world);
File getSaveFolder(ILevelWrapper levelWrapper);
}
@@ -19,18 +19,25 @@
package com.seibel.distanthorizons.core.file.structure;
import com.seibel.distanthorizons.api.interfaces.override.levelHandling.IDhApiSaveStructure;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.world.EWorldEnvironment;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.OverrideInjector;
import org.apache.logging.log4j.Logger;
import java.io.File;
/**
* Designed for Client_Server & Server_Only environments.
* Designed for {@link EWorldEnvironment#Client_Server} & {@link EWorldEnvironment#Server_Only} environments.
*
* @version 2022-12-17
*/
public class LocalSaveStructure implements ISaveStructure
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private File debugPath = new File("");
@@ -44,19 +51,26 @@ public class LocalSaveStructure implements ISaveStructure
//================//
@Override
public File getLevelFolder(ILevelWrapper wrapper)
public File getSaveFolder(ILevelWrapper levelWrapper)
{
IServerLevelWrapper serverSide = (IServerLevelWrapper) wrapper;
this.debugPath = serverSide.getSaveFolder();
return serverSide.getSaveFolder();
}
@Override
public File getFullDataFolder(ILevelWrapper level)
{
IServerLevelWrapper serverLevelWrapper = (IServerLevelWrapper) level;
IServerLevelWrapper serverLevelWrapper = (IServerLevelWrapper) levelWrapper;
this.debugPath = serverLevelWrapper.getSaveFolder();
return serverLevelWrapper.getSaveFolder();
File saveFolder = serverLevelWrapper.getSaveFolder();
// Allow API users to override the save folder
IDhApiSaveStructure saveStructureOverride = OverrideInjector.INSTANCE.get(IDhApiSaveStructure.class);
if (saveStructureOverride != null)
{
File overrideFile = saveStructureOverride.overrideFilePath(saveFolder, levelWrapper);
if (overrideFile != null)
{
LOGGER.info("Save folder overridden from ["+saveFolder.getPath()+"] -> ["+overrideFile.getPath()+"].");
saveFolder = overrideFile;
}
}
return saveFolder;
}
@@ -89,7 +89,7 @@ public class SubDimCompare implements Comparable<SubDimCompare>
/** Returns true if this sub dimension is close enough to be considered a valid sub dimension */
public boolean isValidSubDim()
{
double minimumSimilarityRequired = Config.Client.Advanced.Multiplayer.multiverseSimilarityRequiredPercent.get();
double minimumSimilarityRequired = 0.5;//Config.Client.Advanced.Multiplayer.multiverseSimilarityRequiredPercent.get();
return this.getPercentEqual() >= minimumSimilarityRequired
|| this.playerPosDist <= MAX_SIMILAR_PLAYER_POS_DISTANCE_IN_BLOCKS;
}
@@ -75,7 +75,7 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
boolean runRepoReliantSetup
)
{
if (saveStructure.getFullDataFolder(serverLevelWrapper).mkdirs())
if (saveStructure.getSaveFolder(serverLevelWrapper).mkdirs())
{
LOGGER.warn("unable to create data folder.");
}
@@ -82,10 +82,11 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
// constructor //
//=============//
public DhClientLevel(ISaveStructure saveStructure, IClientLevelWrapper clientLevelWrapper, @Nullable ClientNetworkState networkState) { this(saveStructure, clientLevelWrapper, null, true, networkState); }
public DhClientLevel(ISaveStructure saveStructure, IClientLevelWrapper clientLevelWrapper, @Nullable ClientNetworkState networkState)
{ this(saveStructure, clientLevelWrapper, null, true, networkState); }
public DhClientLevel(ISaveStructure saveStructure, IClientLevelWrapper clientLevelWrapper, @Nullable File fullDataSaveDirOverride, boolean enableRendering, @Nullable ClientNetworkState networkState)
{
if (saveStructure.getFullDataFolder(clientLevelWrapper).mkdirs())
if (saveStructure.getSaveFolder(clientLevelWrapper).mkdirs())
{
LOGGER.warn("unable to create data folder.");
}
@@ -66,8 +66,6 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
{
return this.dhLevelByLevelWrapper.computeIfAbsent(wrapper, (levelWrapper) ->
{
File levelFile = this.saveStructure.getLevelFolder(levelWrapper);
LodUtil.assertTrue(levelFile != null);
DhClientServerLevel level = new DhClientServerLevel(this.saveStructure, (IServerLevelWrapper) levelWrapper, this.getServerPlayerStateManager());
this.dhLevels.add(level);
return level;
@@ -73,17 +73,8 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
return null;
}
return this.levels.computeIfAbsent((IClientLevelWrapper) wrapper, (clientLevelWrapper) ->
{
File file = this.saveStructure.getLevelFolder(wrapper);
if (file == null)
{
return null;
}
return new DhClientLevel(this.saveStructure, clientLevelWrapper, this.networkState);
});
return this.levels.computeIfAbsent((IClientLevelWrapper) wrapper,
(clientLevelWrapper) -> new DhClientLevel(this.saveStructure, clientLevelWrapper, this.networkState));
}
@Override
@@ -52,12 +52,8 @@ public class DhServerWorld extends AbstractDhServerWorld<DhServerLevel>
return null;
}
return this.dhLevelByLevelWrapper.computeIfAbsent(wrapper, (serverLevelWrapper) ->
{
File levelFile = this.saveStructure.getLevelFolder(wrapper);
LodUtil.assertTrue(levelFile != null);
return new DhServerLevel(this.saveStructure, (IServerLevelWrapper) serverLevelWrapper, this.getServerPlayerStateManager());
});
return this.dhLevelByLevelWrapper.computeIfAbsent(wrapper,
(serverLevelWrapper) -> new DhServerLevel(this.saveStructure, (IServerLevelWrapper) serverLevelWrapper, this.getServerPlayerStateManager()));
}
@Override