From 06581068228024d063a27e54e74359f0b2dde25a Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 26 Mar 2022 10:20:01 -0500 Subject: [PATCH] Partially fix SubDimensionFinder --- .../com/seibel/lod/core/api/ClientApi.java | 28 +- .../handlers/LodDimensionFileHandler.java | 5 +- ...derFinder.java => LodDimensionFinder.java} | 260 +++++++----------- .../handlers/dimensionFinder/PlayerData.java | 106 +++++++ .../dimensionFinder/SubDimCompare.java | 66 +++++ .../lod/core/objects/lod/LodDimension.java | 150 +++++----- 6 files changed, 362 insertions(+), 253 deletions(-) rename src/main/java/com/seibel/lod/core/handlers/{LodSubDimensionFolderFinder.java => LodDimensionFinder.java} (66%) create mode 100644 src/main/java/com/seibel/lod/core/handlers/dimensionFinder/PlayerData.java create mode 100644 src/main/java/com/seibel/lod/core/handlers/dimensionFinder/SubDimCompare.java diff --git a/src/main/java/com/seibel/lod/core/api/ClientApi.java b/src/main/java/com/seibel/lod/core/api/ClientApi.java index 7659aa7b4..ec43884e6 100644 --- a/src/main/java/com/seibel/lod/core/api/ClientApi.java +++ b/src/main/java/com/seibel/lod/core/api/ClientApi.java @@ -27,7 +27,7 @@ import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; -import com.seibel.lod.core.handlers.LodSubDimensionFolderFinder; +import com.seibel.lod.core.handlers.LodDimensionFinder; import org.lwjgl.glfw.GLFW; import com.seibel.lod.core.ModInfo; @@ -54,7 +54,7 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper; * Specifically for the client. * * @author James Seibel - * @version 12-8-2021 + * @version 2022-3-26 */ public class ClientApi { @@ -78,6 +78,8 @@ public class ClientApi public static final long SPAM_LOGGER_FLUSH_NS = TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS); + public static LodDimensionFinder DIMENSION_FINDER = new LodDimensionFinder();; + public static class LagSpikeCatcher { long timer = System.nanoTime(); @@ -104,6 +106,8 @@ public class ClientApi + + private ClientApi() { @@ -170,19 +174,21 @@ public class ClientApi LodDimension lodDim = ApiShared.lodWorld.getLodDimension(world.getDimensionType()); // Make sure the player's data is up-to-date - LodSubDimensionFolderFinder.updatePlayerData(); + DIMENSION_FINDER.updatePlayerData(); // Make the LodDim if it does not exist if (lodDim == null) { - lodDim = new LodDimension(world.getDimensionType(), ApiShared.lodWorld, - ApiShared.lodBuilder.defaultDimensionWidthInRegions); - ApiShared.lodWorld.addLodDimension(lodDim); - } - // if necessary attempt to get the world file handler - if (lodDim.isFileHandlerNull()) - { - lodDim.attemptToSetWorldFileHandler(); + if (DIMENSION_FINDER.isDone()) + { + lodDim = DIMENSION_FINDER.getAndClearFoundLodDimension(); + ApiShared.lodWorld.addLodDimension(lodDim); + } + else + { + DIMENSION_FINDER.AttemptToDetermineSubDimensionAsync(MC.getCurrentDimension()); + return; + } } if (prefLoggerEnabled) { diff --git a/src/main/java/com/seibel/lod/core/handlers/LodDimensionFileHandler.java b/src/main/java/com/seibel/lod/core/handlers/LodDimensionFileHandler.java index 717f585b7..9c6c863c5 100644 --- a/src/main/java/com/seibel/lod/core/handlers/LodDimensionFileHandler.java +++ b/src/main/java/com/seibel/lod/core/handlers/LodDimensionFileHandler.java @@ -33,6 +33,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantLock; +import com.seibel.lod.core.api.ClientApi; import org.apache.commons.compress.compressors.xz.XZCompressorInputStream; import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream; @@ -58,7 +59,7 @@ import org.apache.logging.log4j.Logger; * * @author James Seibel * @author Cola - * @version 3-7-2022 + * @version 2022-3-26 */ public class LodDimensionFileHandler { @@ -415,7 +416,7 @@ public class LodDimensionFileHandler } // save the dimension data - LodSubDimensionFolderFinder.saveDimensionPlayerData(this.dimensionDataSaveFolder); + ClientApi.DIMENSION_FINDER.saveDimensionPlayerData(this.dimensionDataSaveFolder); trySaveRegionsToBeSaved(); // wait for the saving to finish if requested diff --git a/src/main/java/com/seibel/lod/core/handlers/LodSubDimensionFolderFinder.java b/src/main/java/com/seibel/lod/core/handlers/LodDimensionFinder.java similarity index 66% rename from src/main/java/com/seibel/lod/core/handlers/LodSubDimensionFolderFinder.java rename to src/main/java/com/seibel/lod/core/handlers/LodDimensionFinder.java index a1c1c9c5f..7cdfcbafc 100644 --- a/src/main/java/com/seibel/lod/core/handlers/LodSubDimensionFolderFinder.java +++ b/src/main/java/com/seibel/lod/core/handlers/LodDimensionFinder.java @@ -2,6 +2,8 @@ package com.seibel.lod.core.handlers; import com.electronwill.nightconfig.core.file.CommentedFileConfig; import com.seibel.lod.core.api.ApiShared; +import com.seibel.lod.core.handlers.dimensionFinder.PlayerData; +import com.seibel.lod.core.handlers.dimensionFinder.SubDimCompare; import com.seibel.lod.core.objects.opengl.builders.lodBuilding.LodBuilder; import com.seibel.lod.core.objects.opengl.builders.lodBuilding.LodBuilderConfig; import com.seibel.lod.core.enums.config.DistanceGenerationMode; @@ -12,14 +14,11 @@ import com.seibel.lod.core.objects.lod.LodRegion; import com.seibel.lod.core.objects.lod.RegionPos; import com.seibel.lod.core.util.LodUtil; import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory; -import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper; import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper; import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper; -import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper; import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper; -import org.jetbrains.annotations.NotNull; import java.io.File; import java.io.IOException; @@ -27,21 +26,88 @@ import java.util.UUID; /** * Used to guess the world folder for the player's current dimension. + * * @author James Seibel - * @version 3-23-2022 + * @version 2022-3-26 */ -public class LodSubDimensionFolderFinder +public class LodDimensionFinder { private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class); - private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class); /** Increasing this will increase accuracy but increase calculation time */ private static final VerticalQuality VERTICAL_QUALITY_TO_TEST_WITH = VerticalQuality.LOW; + public static final String THREAD_NAME = "Sub-Dimension-Finder"; - private static LodSubDimensionFolderFinder.PlayerData PLAYER_DATA = new LodSubDimensionFolderFinder.PlayerData(MC); - private static LodSubDimensionFolderFinder.PlayerData FIRST_SEEN_PLAYER_DATA = null; + private PlayerData playerData = new PlayerData(MC); + private PlayerData firstSeenPlayerData = null; + + private volatile LodDimension foundLodDimension = null; + + /** If true the LodDimensionFileHelper is attempting to determine the folder for this dimension */ + private boolean determiningWorldFolder = false; + + + + public LodDimensionFinder() + { + + } + + + + /** Returns true if a LodDimension has been found */ + public boolean isDone() + { + return foundLodDimension != null; + } + + /** Returns the found LodDimension */ + public LodDimension getAndClearFoundLodDimension() + { + // clear the found dimension + LodDimension returnDim = this.foundLodDimension; + this.foundLodDimension = null; + + return returnDim; + } + + + + public void AttemptToDetermineSubDimensionAsync(IDimensionTypeWrapper dimensionTypeWrapper) + { + // prevent multiple threads running at the same time + if (determiningWorldFolder && !isDone()) + return; + determiningWorldFolder = true; + + + // run asynchronously since this could take a while + Thread thread = new Thread(() -> + { + try + { + // attempt to get the file handler + File saveDir = attemptToDetermineSubDimensionFolder(); + if (saveDir == null) + return; + + foundLodDimension = new LodDimension(dimensionTypeWrapper, ApiShared.lodBuilder.defaultDimensionWidthInRegions, saveDir); + } + catch (IOException e) + { + ApiShared.LOGGER.error("Unable to set the dimension file handler for dimension type [" + dimensionTypeWrapper.getDimensionName() + "]. Error: " + e.getMessage(), e); + } + finally + { + // make sure we unlock this method + determiningWorldFolder = false; + } + }); + thread.setName(THREAD_NAME); + thread.start(); + } @@ -49,20 +115,23 @@ public class LodSubDimensionFolderFinder * Currently this method checks a single chunk (where the player is) * and compares it against the same chunk position in the other dimension worlds to * guess which world the player is in. - * @return the new or existing folder for this dimension, null if there was a problem * @throws IOException if the folder doesn't exist or can't be accessed */ - public static File determineSubDimensionFolder() throws IOException + public File attemptToDetermineSubDimensionFolder() throws IOException { - if (FIRST_SEEN_PLAYER_DATA == null) + if (firstSeenPlayerData == null) { - FIRST_SEEN_PLAYER_DATA = PLAYER_DATA; - PLAYER_DATA = new LodSubDimensionFolderFinder.PlayerData(MC); + firstSeenPlayerData = playerData; + playerData = new PlayerData(MC); } + // TODO check based on the dimension's last seen location instead of the first seen player data + // hopefully this should fix entering a dimension in two different locations + + // relevant positions - AbstractChunkPosWrapper playerChunkPos = FACTORY.createChunkPos(FIRST_SEEN_PLAYER_DATA.playerBlockPos); + AbstractChunkPosWrapper playerChunkPos = FACTORY.createChunkPos(firstSeenPlayerData.playerBlockPos); int startingBlockPosX = playerChunkPos.getMinBlockX(); int startingBlockPosZ = playerChunkPos.getMinBlockZ(); RegionPos playerRegionPos = new RegionPos(playerChunkPos); @@ -71,11 +140,11 @@ public class LodSubDimensionFolderFinder // chunk from the newly loaded dimension IChunkWrapper newlyLoadedChunk = MC.getWrappedClientWorld().tryGetChunk(playerChunkPos); // check if this chunk is valid to test - if (!LodSubDimensionFolderFinder.CanDetermineDimensionFolder(newlyLoadedChunk)) + if (!CanDetermineDimensionFolder(newlyLoadedChunk)) return null; // create a temporary dimension to store the test LOD - LodDimension newlyLoadedDim = new LodDimension(MC.getCurrentDimension(), null, 1); + LodDimension newlyLoadedDim = new LodDimension(MC.getCurrentDimension(), 1, null, false); newlyLoadedDim.move(playerRegionPos); newlyLoadedDim.regions.set(playerRegionPos.x, playerRegionPos.z, new LodRegion(LodUtil.BLOCK_DETAIL_LEVEL, playerRegionPos, VERTICAL_QUALITY_TO_TEST_WITH)); @@ -87,7 +156,7 @@ public class LodSubDimensionFolderFinder // log the start of this attempt ApiShared.LOGGER.info("Attempting to determine sub-dimension for [" + MC.getCurrentDimension().getDimensionName() + "]"); - ApiShared.LOGGER.info("First seen player block pos in dimension: [" + FIRST_SEEN_PLAYER_DATA.playerBlockPos.getX() + "," + FIRST_SEEN_PLAYER_DATA.playerBlockPos.getY() + "," + FIRST_SEEN_PLAYER_DATA.playerBlockPos.getZ() + "]"); + ApiShared.LOGGER.info("First seen player block pos in dimension: [" + firstSeenPlayerData.playerBlockPos.getX() + "," + firstSeenPlayerData.playerBlockPos.getY() + "," + firstSeenPlayerData.playerBlockPos.getZ() + "]"); // new chunk data @@ -150,7 +219,7 @@ public class LodSubDimensionFolderFinder ApiShared.LOGGER.info("Testing sub dimension: [" + LodUtil.shortenString(testDimFolder.getName(), 8) + "]"); // get a LOD from this dimension folder - LodDimension tempLodDim = new LodDimension(null, null, 1); + LodDimension tempLodDim = new LodDimension(null, 1, null, false); tempLodDim.move(playerRegionPos); LodDimensionFileHandler tempFileHandler = new LodDimensionFileHandler(testDimFolder, tempLodDim); LodRegion testRegion = tempFileHandler.loadRegionFromFile(LodUtil.BLOCK_DETAIL_LEVEL, playerRegionPos, VERTICAL_QUALITY_TO_TEST_WITH); @@ -171,7 +240,7 @@ public class LodSubDimensionFolderFinder ApiShared.LOGGER.info("Last known player pos: [" + testPlayerData.playerBlockPos.getX() + "," + testPlayerData.playerBlockPos.getY() + "," + testPlayerData.playerBlockPos.getZ() + "]"); // check if the block positions are close - int playerBlockDist = testPlayerData.playerBlockPos.getManhattanDistance(FIRST_SEEN_PLAYER_DATA.playerBlockPos); + int playerBlockDist = testPlayerData.playerBlockPos.getManhattanDistance(firstSeenPlayerData.playerBlockPos); ApiShared.LOGGER.info("Player block position distance between saved sub dimension and first seen is [" + playerBlockDist + "]"); @@ -217,7 +286,7 @@ public class LodSubDimensionFolderFinder } // the first seen player data is no longer needed, the sub dimension has been determined - FIRST_SEEN_PLAYER_DATA = null; + firstSeenPlayerData = null; if (mostSimilarSubDim != null && mostSimilarSubDim.isValidSubDim()) @@ -248,7 +317,7 @@ public class LodSubDimensionFolderFinder * If the worldId is empty or null this returns the dimension parent folder
* Example folder names: "dim_overworld/worldId", "dim_the_nether/worldId" */ - public static File GetDimensionFolder(IDimensionTypeWrapper newDimensionType, String worldId) + public File GetDimensionFolder(IDimensionTypeWrapper newDimensionType, String worldId) { // prevent null pointers if (worldId == null) @@ -278,7 +347,7 @@ public class LodSubDimensionFolderFinder /** Returns true if the given chunk is valid to test */ - public static boolean CanDetermineDimensionFolder(IChunkWrapper chunk) + public boolean CanDetermineDimensionFolder(IChunkWrapper chunk) { // we can only guess if the given chunk can be converted into a LOD return LodBuilder.canGenerateLodFromChunk(chunk); @@ -301,9 +370,11 @@ public class LodSubDimensionFolderFinder } } } - + return false; } + + @@ -311,15 +382,13 @@ public class LodSubDimensionFolderFinder - private static final String playerDataFileName = "_playerData.toml"; - - public static void updatePlayerData() + public void updatePlayerData() { - PLAYER_DATA.updateData(MC); + playerData.updateData(MC); } /** saves any necessary player data to the given world folder */ - public static void saveDimensionPlayerData(File worldFolder) + public void saveDimensionPlayerData(File worldFolder) { // get and create the file and path if they don't exist File file = PlayerData.getFileForDimensionFolder(worldFolder); @@ -347,139 +416,4 @@ public class LodSubDimensionFolderFinder } - /** Data container for any player data we can use to differentiate one dimension from another. */ - private static class PlayerData - { - public static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class); - - public static final String PLAYER_BLOCK_POS_X_PATH = "playerBlockPosX"; - public static final String PLAYER_BLOCK_POS_Y_PATH = "playerBlockPosY"; - public static final String PLAYER_BLOCK_POS_Z_PATH = "playerBlockPosZ"; - AbstractBlockPosWrapper playerBlockPos; - - // not implemented yet - public static final String WORLD_SPAWN_POS_X_PATH = "worldSpawnBlockPosX"; - public static final String WORLD_SPAWN_POS_Y_PATH = "worldSpawnBlockPosY"; - public static final String WORLD_SPAWN_POS_Z_PATH = "worldSpawnBlockPosZ"; - /** - * The client world has access to a spawn point, so this should be possible to fill in. - * I'm not sure what this will look like for worlds that don't have a spawn point. - */ - AbstractBlockPosWrapper worldSpawnPointBlockPos; - - - - public PlayerData(IMinecraftClientWrapper mc) - { - updateData(mc); - } - - public PlayerData(File dimensionFolder) - { - File file = getFileForDimensionFolder(dimensionFolder); - CommentedFileConfig toml = CommentedFileConfig.builder(file).build(); - - toml.load(); - - - // get the player block pos if it is specified - if (toml.contains(PLAYER_BLOCK_POS_X_PATH) - && toml.contains(PLAYER_BLOCK_POS_Y_PATH) - && toml.contains(PLAYER_BLOCK_POS_Z_PATH)) - { - int x = toml.getIntOrElse(PLAYER_BLOCK_POS_X_PATH, 0); - int y = toml.getIntOrElse(PLAYER_BLOCK_POS_Y_PATH, 0); - int z = toml.getIntOrElse(PLAYER_BLOCK_POS_Z_PATH, 0); - this.playerBlockPos = FACTORY.createBlockPos(x, y, z); - } - else - { - this.playerBlockPos = FACTORY.createBlockPos(0, 0, 0); - } - } - - - - public static File getFileForDimensionFolder(File file) - { - return new File(file.getPath() + File.separatorChar + playerDataFileName); - } - - - /** Should be called often to make sure this object is up to date with the player's info */ - public void updateData(IMinecraftClientWrapper mc) - { - this.playerBlockPos = mc.getPlayerBlockPos(); - } - - /** Writes everything from this object to the file given. */ - public void toTomlFile(CommentedFileConfig toml) - { - // player block pos - toml.add(PLAYER_BLOCK_POS_X_PATH, playerBlockPos.getX()); - toml.add(PLAYER_BLOCK_POS_Y_PATH, playerBlockPos.getY()); - toml.add(PLAYER_BLOCK_POS_Z_PATH, playerBlockPos.getZ()); - - toml.save(); - } - - - @Override - public String toString() - { - return "PlayerBlockPos: [" + playerBlockPos.getX() + "," + playerBlockPos.getY() + "," + playerBlockPos.getZ() + "]"; - } - } - - - - - private static class SubDimCompare implements Comparable - { - public int equalDataPoints = 0; - public int totalDataPoints = 0; - public int playerPosDist = 0; - public File folder = null; - - - public SubDimCompare(int newEqualDataPoints, int newTotalDataPoints, int newPlayerPosDistance, File newSubDimFolder) - { - this.equalDataPoints = newEqualDataPoints; - this.totalDataPoints = newTotalDataPoints; - this.playerPosDist = newPlayerPosDistance; - - this.folder = newSubDimFolder; - } - - /** returns a number between 0 (not equal) and 1 (totally equal) */ - public double getPercentEqual() - { - return (double) equalDataPoints / (double) totalDataPoints; - } - - - @Override - public int compareTo(@NotNull LodSubDimensionFolderFinder.SubDimCompare other) - { - if (this.equalDataPoints != other.equalDataPoints) - { - // compare based on data points - return Integer.compare(this.equalDataPoints, other.equalDataPoints); - } - else - { - // break ties based on player position - return Integer.compare(this.playerPosDist, other.playerPosDist); - } - } - - /** Returns true if this sub dimension is close enough to be considered a valid sub dimension */ - public boolean isValidSubDim() - { - double minimumSimilarityRequired = CONFIG.client().multiplayer().getMultiDimensionRequiredSimilarity(); - return this.getPercentEqual() >= minimumSimilarityRequired || this.playerPosDist <= 3; - } - } - - } diff --git a/src/main/java/com/seibel/lod/core/handlers/dimensionFinder/PlayerData.java b/src/main/java/com/seibel/lod/core/handlers/dimensionFinder/PlayerData.java new file mode 100644 index 000000000..1d9cfef4d --- /dev/null +++ b/src/main/java/com/seibel/lod/core/handlers/dimensionFinder/PlayerData.java @@ -0,0 +1,106 @@ +package com.seibel.lod.core.handlers.dimensionFinder; + + +import com.electronwill.nightconfig.core.file.CommentedFileConfig; +import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler; +import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory; +import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper; +import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; + +import java.io.File; + +/** + * Data container for any player data we can use to differentiate one dimension from another. + * + * @author James Seibel + * @version 2022-3-26 + */ +public class PlayerData +{ + public static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class); + + private static final String playerDataFileName = "_playerData.toml"; + + + public static final String PLAYER_BLOCK_POS_X_PATH = "playerBlockPosX"; + public static final String PLAYER_BLOCK_POS_Y_PATH = "playerBlockPosY"; + public static final String PLAYER_BLOCK_POS_Z_PATH = "playerBlockPosZ"; + public AbstractBlockPosWrapper playerBlockPos; + + // not implemented yet + public static final String WORLD_SPAWN_POS_X_PATH = "worldSpawnBlockPosX"; + public static final String WORLD_SPAWN_POS_Y_PATH = "worldSpawnBlockPosY"; + public static final String WORLD_SPAWN_POS_Z_PATH = "worldSpawnBlockPosZ"; + /** + * The client world has access to a spawn point, so this should be possible to fill in. + * I'm not sure what this will look like for worlds that don't have a spawn point. + */ + public AbstractBlockPosWrapper worldSpawnPointBlockPos; + + + + public PlayerData(IMinecraftClientWrapper mc) + { + updateData(mc); + } + + public PlayerData(File dimensionFolder) + { + File file = getFileForDimensionFolder(dimensionFolder); + CommentedFileConfig toml = CommentedFileConfig.builder(file).build(); + + toml.load(); + + + // get the player block pos if it is specified + if (toml.contains(PLAYER_BLOCK_POS_X_PATH) + && toml.contains(PLAYER_BLOCK_POS_Y_PATH) + && toml.contains(PLAYER_BLOCK_POS_Z_PATH)) + { + int x = toml.getIntOrElse(PLAYER_BLOCK_POS_X_PATH, 0); + int y = toml.getIntOrElse(PLAYER_BLOCK_POS_Y_PATH, 0); + int z = toml.getIntOrElse(PLAYER_BLOCK_POS_Z_PATH, 0); + this.playerBlockPos = FACTORY.createBlockPos(x, y, z); + } + else + { + this.playerBlockPos = FACTORY.createBlockPos(0, 0, 0); + } + } + + + + public static File getFileForDimensionFolder(File file) + { + return new File(file.getPath() + File.separatorChar + playerDataFileName); + } + + + /** Should be called often to make sure this object is up to date with the player's info */ + public void updateData(IMinecraftClientWrapper mc) + { + if (mc.playerExists()) + { + this.playerBlockPos = mc.getPlayerBlockPos(); + } + } + + /** Writes everything from this object to the file given. */ + public void toTomlFile(CommentedFileConfig toml) + { + // player block pos + toml.add(PLAYER_BLOCK_POS_X_PATH, playerBlockPos.getX()); + toml.add(PLAYER_BLOCK_POS_Y_PATH, playerBlockPos.getY()); + toml.add(PLAYER_BLOCK_POS_Z_PATH, playerBlockPos.getZ()); + + toml.save(); + } + + + @Override + public String toString() + { + return "PlayerBlockPos: [" + playerBlockPos.getX() + "," + playerBlockPos.getY() + "," + playerBlockPos.getZ() + "]"; + } +} + diff --git a/src/main/java/com/seibel/lod/core/handlers/dimensionFinder/SubDimCompare.java b/src/main/java/com/seibel/lod/core/handlers/dimensionFinder/SubDimCompare.java new file mode 100644 index 000000000..a2e5e34ee --- /dev/null +++ b/src/main/java/com/seibel/lod/core/handlers/dimensionFinder/SubDimCompare.java @@ -0,0 +1,66 @@ +package com.seibel.lod.core.handlers.dimensionFinder; + +import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler; +import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +/** + * Contains data used to compare different sub LodDimensions. + * Sub Dimensions are the different folders under a dimension. + * For example: "\Distant_Horizons_server_data\server_1\dim_the_nether\6fb97c01-e4c7-4634-87db-36b1e490e4c3" + * is a sub dimension for the server "server_1" in the nether. + * + * @author James Seibel + * @version 2022-3-26 + */ +public class SubDimCompare implements Comparable +{ + private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); + + + public int equalDataPoints = 0; + public int totalDataPoints = 0; + public int playerPosDist = 0; + public File folder = null; + + + public SubDimCompare(int newEqualDataPoints, int newTotalDataPoints, int newPlayerPosDistance, File newSubDimFolder) + { + this.equalDataPoints = newEqualDataPoints; + this.totalDataPoints = newTotalDataPoints; + this.playerPosDist = newPlayerPosDistance; + + this.folder = newSubDimFolder; + } + + /** returns a number between 0 (not equal) and 1 (totally equal) */ + public double getPercentEqual() + { + return (double) equalDataPoints / (double) totalDataPoints; + } + + + @Override + public int compareTo(@NotNull SubDimCompare other) + { + if (this.equalDataPoints != other.equalDataPoints) + { + // compare based on data points + return Integer.compare(this.equalDataPoints, other.equalDataPoints); + } + else + { + // break ties based on player position + return Integer.compare(this.playerPosDist, other.playerPosDist); + } + } + + /** Returns true if this sub dimension is close enough to be considered a valid sub dimension */ + public boolean isValidSubDim() + { + double minimumSimilarityRequired = CONFIG.client().multiplayer().getMultiDimensionRequiredSimilarity(); + return this.getPercentEqual() >= minimumSimilarityRequired || this.playerPosDist <= 3; + } +} diff --git a/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java b/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java index 9236c1fa2..cc684f04e 100644 --- a/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java +++ b/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java @@ -26,7 +26,6 @@ import com.seibel.lod.core.enums.config.DropoffQuality; import com.seibel.lod.core.enums.config.GenerationPriority; import com.seibel.lod.core.enums.config.VerticalQuality; import com.seibel.lod.core.handlers.LodDimensionFileHandler; -import com.seibel.lod.core.handlers.LodSubDimensionFolderFinder; import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler; import com.seibel.lod.core.objects.Pos2D; import com.seibel.lod.core.objects.PosToGenerateContainer; @@ -37,7 +36,6 @@ import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper; import java.io.File; -import java.io.IOException; import java.util.Arrays; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -56,7 +54,7 @@ import java.util.concurrent.TimeUnit; * * @author Leonardo Amato * @author James Seibel - * @version 3-17-2022 + * @version 2022-3-26 */ public class LodDimension { @@ -89,75 +87,49 @@ public class LodDimension private final ExecutorService cutAndExpandThread = Executors.newSingleThreadExecutor( new LodThreadFactory(this.getClass().getSimpleName() + " - Cut and Expand", Thread.NORM_PRIORITY - 1)); - /** If true the LodDimensionFileHelper is attempting to determine the folder for this dimension */ - private boolean determiningWorldFolder = false; + private boolean logEvents = true; + /** - * Creates the dimension centered at (0,0) - * @param newWidth in regions + * Creates the dimension centered at (0,0), with event logging, and no file saving/loading. + * + * @param newWidth measured in regions */ - public LodDimension(IDimensionTypeWrapper newDimension, LodWorld lodWorld, int newWidth) + public LodDimension(IDimensionTypeWrapper newDimension, int newWidth) { - dimension = newDimension; - width = newWidth; // FIXME any width besides 1 causes an indexOutOfBounds Exception - halfWidth = width / 2; - - if (newDimension != null && lodWorld != null) - { - attemptToSetWorldFileHandler(); - } - - - regions = new MovableGridRingList(halfWidth, 0, 0); - generateIteratorList(); + this(newDimension, newWidth, null, true); } /** - * Attempts to determine and set the file handler based on - * the chunk the player is currently in. - * @returns true if the fileHandler has been set, false otherwise + * Creates the dimension centered at (0,0) + * + * @param newWidth measured in regions + * @param saveDir can be null. If null regions will not be saved or loaded from file. */ - public void attemptToSetWorldFileHandler() + public LodDimension(IDimensionTypeWrapper newDimension, int newWidth, File saveDir) { - // check if we need to get the file handler - if (this.fileHandler != null) - return; + this(newDimension, newWidth, saveDir, true); + } + + /** + * Creates the dimension centered at (0,0) + * + * @param newWidth measured in regions + * @param saveDir can be null. If null regions will not be saved or loaded from file. + */ + public LodDimension(IDimensionTypeWrapper newDimension, int newWidth, File saveDir, boolean newLogEvents) + { + this.dimension = newDimension; + this.width = newWidth; // FIXME any width besides 1 causes an indexOutOfBounds Exception + this.halfWidth = width / 2; + this.logEvents = newLogEvents; - // prevent multiple threads running at the same time - if (this.determiningWorldFolder) - return; - this.determiningWorldFolder = true; + if (saveDir != null) + this.fileHandler = new LodDimensionFileHandler(saveDir, this); - - // run asynchronously since this could take a while - Thread thread =new Thread(() -> - { - try - { - // attempt to get the file handler - File saveDir = LodSubDimensionFolderFinder.determineSubDimensionFolder(); - if (saveDir == null) - return; - - this.fileHandler = new LodDimensionFileHandler(saveDir, this); - - // clear the previous regions so we can load the LODs from file - if (this.regions != null) - this.regions.clear(); - } - catch (IOException e) - { - ApiShared.LOGGER.error("Unable to set the dimension file handler for dimension type [" + this.dimension.getDimensionName() + "]. Error: " + e.getMessage(), e); - } - finally - { - // make sure we unlock this method - this.determiningWorldFolder = false; - } - }); - thread.setName("Sub-Dimension-Finder"); - thread.start(); + this.regions = new MovableGridRingList(halfWidth, 0, 0); + generateIteratorList(); } @@ -193,11 +165,15 @@ public class LodDimension */ public synchronized void move(RegionPos regionOffset) { - ApiShared.LOGGER.info("LodDim MOVE. Offset: "+regionOffset); + if (this.logEvents) + ApiShared.LOGGER.info("LodDim MOVE. Offset: "+regionOffset); + saveDirtyRegionsToFile(false); //async add dirty regions to be saved. Pos2D p = regions.getCenter(); regions.move(p.x+regionOffset.x, p.y+regionOffset.z); - ApiShared.LOGGER.info("LodDim MOVE complete. Offset: "+regionOffset); + + if (this.logEvents) + ApiShared.LOGGER.info("LodDim MOVE complete. Offset: "+regionOffset); } @@ -343,9 +319,13 @@ public class LodDimension if (isExpanding) return; // If we have less than 20% or 128MB ram left. Don't expend. - if (expandOrLoadPaused) { - if (LodUtil.checkRamUsage(0.2, 128)) { - ApiShared.LOGGER.info("Enough ram for expandOrLoadThread. Restarting..."); + if (expandOrLoadPaused) + { + if (LodUtil.checkRamUsage(0.2, 128)) + { + if (this.logEvents) + ApiShared.LOGGER.info("Enough ram for expandOrLoadThread. Restarting..."); + expandOrLoadPaused = false; } } @@ -359,14 +339,20 @@ public class LodDimension int dropoffSwitch = dropoffQuality.fastModeSwitch; // don't run the expander multiple times // for the same location - Runnable thread = () -> { + Runnable thread = () -> + { //ApiShared.LOGGER.info("LodDim expend Region: " + playerPosX + "," + playerPosZ); Pos2D minPos = regions.getMinInRange(); - iterateWithSpiral((int x, int z) -> { - if (!expandOrLoadPaused && !LodUtil.checkRamUsage(0.02, 64)) { + iterateWithSpiral((int x, int z) -> + { + if (!expandOrLoadPaused && !LodUtil.checkRamUsage(0.02, 64)) + { Runtime.getRuntime().gc(); - if (!LodUtil.checkRamUsage(0.2, 128)) { - ApiShared.LOGGER.warn("Not enough ram for expandOrLoadThread. Pausing until Ram is freed..."); + if (!LodUtil.checkRamUsage(0.2, 128)) + { + if (this.logEvents) + ApiShared.LOGGER.warn("Not enough ram for expandOrLoadThread. Pausing until Ram is freed..."); + // We have less than 10% or 64MB ram left. Don't expend. expandOrLoadPaused = true; saveDirtyRegionsToFile(false); @@ -396,10 +382,15 @@ public class LodDimension double deltaRPosX = debugRPosX - playerPosX; double deltaRPosZ = debugRPosZ - playerPosZ; double debugDistance = Math.sqrt(deltaRPosX*deltaRPosX + deltaRPosZ*deltaRPosZ); - if (minDistance > debugDistance || maxDistance < debugDistance || minDistance > maxDistance) { - ApiShared.LOGGER.error("MinDistance/MaxDistance is WRONG!!! minDist: [{}], maxDist: [{}], centerDist: [{}]\n" - + "At center block pos: {} {}, region pos: {}", - minDistance, maxDistance, debugDistance, debugRPosX, debugRPosZ, regionPos); + if (minDistance > debugDistance || maxDistance < debugDistance || minDistance > maxDistance) + { + if (this.logEvents) + { + ApiShared.LOGGER.error("MinDistance/MaxDistance is WRONG!!! minDist: [{}], maxDist: [{}], centerDist: [{}]\n" + + "At center block pos: {} {}, region pos: {}", + minDistance, maxDistance, debugDistance, debugRPosX, debugRPosZ, regionPos); + } + return; } } @@ -742,13 +733,18 @@ public class LodDimension return stringBuilder.toString(); } - public void shutdown() { + public void shutdown() + { cutAndExpandThread.shutdown(); - try { + try + { boolean worked = cutAndExpandThread.awaitTermination(5, TimeUnit.SECONDS); + if (!worked) ApiShared.LOGGER.error("Cut And Expend threads timed out! May cause crash on game exit due to cleanup failure."); - } catch (InterruptedException e) { + } + catch (InterruptedException e) + { ApiShared.LOGGER.error("Cut And Expend threads shutdown is interrupted! May cause crash on game exit due to cleanup failure: ", e); }