Minor refactors for LevelToFileMatcher and PlayerData

This commit is contained in:
James Seibel
2022-12-11 21:32:33 -06:00
parent 38a8c73311
commit 575bd5b1e6
2 changed files with 195 additions and 161 deletions
@@ -17,95 +17,126 @@ import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
public class LevelToFileMatcher implements AutoCloseable {
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
public static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(),
() -> Config.Client.Advanced.Debugging.DebugSwitch.logFileSubDimEvent.get());
private final ExecutorService matcherThread = LodUtil.makeSingleThreadPool("Level-To-File-Matcher");
private PlayerData playerData = null;
private PlayerData firstSeenPlayerData = null;
/** If true the LodDimensionFileHelper is attempting to determine the folder for this dimension */
private final AtomicBoolean determiningWorldFolder = new AtomicBoolean(false);
private final ILevelWrapper currentLevel;
private volatile File foundLevel = null;
private final File[] potentialFiles;
private final File levelsFolder;
public LevelToFileMatcher(ILevelWrapper targetWorld, File levelsFolder, File[] potentialFiles) {
this.currentLevel = targetWorld;
this.potentialFiles = potentialFiles;
this.levelsFolder = levelsFolder;
if (potentialFiles.length == 0) {
String newId = UUID.randomUUID().toString();
LOGGER.info("No potential level files found. Creating a new sub dimension with ID {}...",
LodUtil.shortenString(newId, 8));
foundLevel = new File(levelsFolder, newId);
}
}
// May return null, where at this moment the level is not yet known
public File tryGetLevel() {
tick();
return foundLevel;
}
public boolean isFindingLevel(ILevelWrapper level) {
return Objects.equals(level, currentLevel);
}
private void tick() {
if (foundLevel != null) return;
// prevent multiple threads running at the same time
if (determiningWorldFolder.getAndSet(true)) return;
matcherThread.submit(() ->
{
try {
// attempt to get the file handler
File saveDir = attemptToDetermineSubDimensionFolder();
if (saveDir != null) foundLevel = saveDir;
} catch (IOException e) {
LOGGER.error("Unable to set the dimension file handler for level [" + currentLevel + "]. Error: ", e);
} finally {
// make sure we unlock this method
determiningWorldFolder.set(false);
}
});
}
/**
* 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.
*
* @throws IOException if the folder doesn't exist or can't be accessed
*/
public File attemptToDetermineSubDimensionFolder() throws IOException
{
{ // Update PlayerData
PlayerData data = PlayerData.tryGetPlayerData(MC_CLIENT);
if (data != null) {
if (firstSeenPlayerData == null) {
firstSeenPlayerData = data;
}
playerData = data;
}
}
// relevant positions
DhChunkPos playerChunkPos = new DhChunkPos(playerData.playerBlockPos);
int startingBlockPosX = playerChunkPos.getMinBlockX();
int startingBlockPosZ = playerChunkPos.getMinBlockZ();
// chunk from the newly loaded level
IChunkWrapper newlyLoadedChunk = MC_CLIENT.getWrappedClientWorld().tryGetChunk(playerChunkPos);
// check if this chunk is valid to test
if (!CanDetermineLevelFolder(newlyLoadedChunk))
return null;
//TODO: Compute a ChunkData from current chunk.
/**
* Used to support multiple worlds using the same dimension type. <br/>
* This is specifically needed for servers running the Multiverse plugin (or similar).
*/
public class LevelToFileMatcher implements AutoCloseable
{
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
public static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(),
() -> Config.Client.Advanced.Debugging.DebugSwitch.logFileSubDimEvent.get());
private final ExecutorService matcherThread = LodUtil.makeSingleThreadPool("Level-To-File-Matcher");
private PlayerData playerData = null;
private PlayerData firstSeenPlayerData = null;
/** If true the LodDimensionFileHelper is attempting to determine the folder for this dimension */
private final AtomicBoolean determiningWorldFolder = new AtomicBoolean(false);
private final ILevelWrapper currentLevel;
private volatile File foundLevel = null;
private final File[] potentialFiles;
private final File levelsFolder;
public LevelToFileMatcher(ILevelWrapper targetWorld, File levelsFolder, File[] potentialFiles)
{
this.currentLevel = targetWorld;
this.potentialFiles = potentialFiles;
this.levelsFolder = levelsFolder;
if (potentialFiles.length == 0)
{
String newId = UUID.randomUUID().toString();
LOGGER.info("No potential level files found. Creating a new sub dimension with the ID [{}]...",
LodUtil.shortenString(newId, 8));
this.foundLevel = new File(levelsFolder, newId);
}
}
public boolean isFindingLevel(ILevelWrapper level) { return Objects.equals(level, this.currentLevel); }
/** May return null if the level isn't known yet */
public File tryGetLevel()
{
this.tryGetLevelInternal();
return this.foundLevel;
}
private void tryGetLevelInternal()
{
if (this.foundLevel != null)
{
return;
}
// prevent multiple threads running at the same time
if (this.determiningWorldFolder.getAndSet(true))
{
return;
}
this.matcherThread.submit(() ->
{
try
{
// attempt to get the file handler
File saveDir = this.attemptToDetermineSubDimensionFolder();
if (saveDir != null)
{
this.foundLevel = saveDir;
}
}
catch (IOException e)
{
LOGGER.error("Unable to set the dimension file handler for level [" + this.currentLevel + "]. Error: ", e);
}
finally
{
// make sure we unlock this method
this.determiningWorldFolder.set(false);
}
});
}
/**
* 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.
* @throws IOException if the folder doesn't exist or can't be accessed
*/
public File attemptToDetermineSubDimensionFolder() throws IOException
{
{ // Update PlayerData
PlayerData data = PlayerData.tryGetPlayerData(MC_CLIENT);
if (data != null)
{
if (this.firstSeenPlayerData == null)
{
this.firstSeenPlayerData = data;
}
this.playerData = data;
}
}
// relevant positions
DhChunkPos playerChunkPos = new DhChunkPos(this.playerData.playerBlockPos);
int startingBlockPosX = playerChunkPos.getMinBlockX();
int startingBlockPosZ = playerChunkPos.getMinBlockZ();
// chunk from the newly loaded level
IChunkWrapper newlyLoadedChunk = MC_CLIENT.getWrappedClientWorld().tryGetChunk(playerChunkPos);
// check if this chunk is valid to test
if (!this.CanDetermineLevelFolder(newlyLoadedChunk))
{
return null;
}
//TODO: Compute a ChunkData from current chunk.
/*
// generate a LOD to test against
boolean lodGenerated = InternalApiShared.lodBuilder.generateLodNodeFromChunk(newlyLoadedDim, newlyLoadedChunk, new LodBuilderConfig(EDistanceGenerationMode.FULL), true, true);
@@ -145,20 +176,20 @@ public class LevelToFileMatcher implements AutoCloseable {
}
return null;
}*/
// compare each world with the newly loaded one
SubDimCompare mostSimilarSubDim = null;
File[] levelFolders = potentialFiles;
LOGGER.info("Potential Sub Dimension folders: [" + levelFolders.length + "]");
for (File testLevelFolder : levelFolders)
{
LOGGER.info("Testing level folder: [" + LodUtil.shortenString(testLevelFolder.getName(), 8) + "]");
try
{
// TODO: Try load a data file overlapping the playerChunkPos from ClientOnlySaveStructure,
// and then use it to compare chunk data to current chunk.
// compare each world with the newly loaded one
SubDimCompare mostSimilarSubDim = null;
File[] levelFolders = potentialFiles;
LOGGER.info("Potential Sub Dimension folders: [" + levelFolders.length + "]");
for (File testLevelFolder : levelFolders)
{
LOGGER.info("Testing level folder: [" + LodUtil.shortenString(testLevelFolder.getName(), 8) + "]");
try
{
// TODO: Try load a data file overlapping the playerChunkPos from ClientOnlySaveStructure,
// and then use it to compare chunk data to current chunk.
/*
// get a LOD from this dimension folder
@@ -223,51 +254,53 @@ public class LevelToFileMatcher implements AutoCloseable {
LOGGER.info("Sub dimension [" + LodUtil.shortenString(testLevelFolder.getName(), 8) + "...] is current dimension probability: " + LodUtil.shortenString(subDimCompare.getPercentEqual() + "", 5) + " (" + equalDataPoints + "/" + totalDataPointCount + ")");
*/
}
catch (Exception e)
{
// this sub dimension isn't formatted correctly
// for now we are just assuming it is an unrelated file
}
}
// TODO if two sub dimensions contain the same LODs merge them???
// the first seen player data is no longer needed, the sub dimension has been determined
firstSeenPlayerData = null;
if (mostSimilarSubDim != null && mostSimilarSubDim.isValidSubDim())
{
// we found a world folder that is similar, use it
LOGGER.info("Sub Dimension set to: [" + LodUtil.shortenString(mostSimilarSubDim.folder.getName(), 8) + "...] with an equality of [" + mostSimilarSubDim.getPercentEqual() + "]");
return mostSimilarSubDim.folder;
}
else
{
// no world folder was found, create a new one
double highestEqualityPercent = mostSimilarSubDim != null ? mostSimilarSubDim.getPercentEqual() : 0;
String newId = UUID.randomUUID().toString();
String message = "No suitable sub dimension found. The highest equality was [" + LodUtil.shortenString(highestEqualityPercent + "", 5) + "]. Creating a new sub dimension with ID: " + LodUtil.shortenString(newId, 8) + "...";
LOGGER.info(message);
File folder = new File(levelsFolder, newId);
folder.mkdirs();
return folder;
}
}
/** Returns true if the given chunk is valid to test */
public boolean CanDetermineLevelFolder(IChunkWrapper chunk)
{
// we can only guess if the given chunk can be converted into a LOD
return false; //FIXME: Fix this after LodBUilder is done.
//return LodBuilder.canGenerateLodFromChunk(chunk);
}
@Override
public void close() {
matcherThread.shutdownNow();
}
}
catch (Exception e)
{
// this sub dimension isn't formatted correctly
// for now we are just assuming it is an unrelated file
}
}
// TODO if two sub dimensions contain the same LODs merge them???
// the first seen player data is no longer needed, the sub dimension has been determined
this.firstSeenPlayerData = null;
if (mostSimilarSubDim != null && mostSimilarSubDim.isValidSubDim())
{
// we found a world folder that is similar, use it
LOGGER.info("Sub Dimension set to: [" + LodUtil.shortenString(mostSimilarSubDim.folder.getName(), 8) + "...] with an equality of [" + mostSimilarSubDim.getPercentEqual() + "]");
return mostSimilarSubDim.folder;
}
else
{
// no world folder was found, create a new one
double highestEqualityPercent = mostSimilarSubDim != null ? mostSimilarSubDim.getPercentEqual() : 0;
String newId = UUID.randomUUID().toString();
String message = "No suitable sub dimension found. The highest equality was [" + LodUtil.shortenString(highestEqualityPercent + "", 5) + "]. Creating a new sub dimension with ID: " + LodUtil.shortenString(newId, 8) + "...";
LOGGER.info(message);
File folder = new File(this.levelsFolder, newId);
folder.mkdirs();
return folder;
}
}
/** Returns true if the given chunk is valid to test */
public boolean CanDetermineLevelFolder(IChunkWrapper chunk)
{
// we can only guess if the given chunk can be converted into a LOD
return false; //FIXME: Fix this after LodBUilder is done.
//return LodBuilder.canGenerateLodFromChunk(chunk);
}
@Override
public void close()
{
this.matcherThread.shutdownNow();
}
}
@@ -39,7 +39,7 @@ public class PlayerData
{
public static final IWrapperFactory FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
private static final String playerDataFileName = "_playerData.toml";
public static final String PLAYER_DATA_FILE_NAME = "_playerData.toml";
public static final String PLAYER_BLOCK_POS_X_PATH = "playerBlockPosX";
@@ -80,7 +80,7 @@ public class PlayerData
private PlayerData(IMinecraftClientWrapper mc)
{
updateData(mc);
this.updateData(mc);
}
public PlayerData(File dimensionFolder)
@@ -108,26 +108,27 @@ public class PlayerData
public static File getFileForDimensionFolder(File file)
{
return new File(file.getPath() + File.separatorChar + playerDataFileName);
}
public static File getFileForDimensionFolder(File file) { return new File(file.getPath() + File.separatorChar + PLAYER_DATA_FILE_NAME); }
/** 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();
if (playerBlockPos == null) throw new RuntimeException("No player block pos!");
if (this.playerBlockPos == null)
{
throw new RuntimeException("No player block pos!");
}
}
/** 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.add(PLAYER_BLOCK_POS_X_PATH, this.playerBlockPos.getX());
toml.add(PLAYER_BLOCK_POS_Y_PATH, this.playerBlockPos.getY());
toml.add(PLAYER_BLOCK_POS_Z_PATH, this.playerBlockPos.getZ());
toml.save();
}
@@ -136,7 +137,7 @@ public class PlayerData
@Override
public String toString()
{
return "PlayerBlockPos: [" + playerBlockPos.x + "," + playerBlockPos.y + "," + playerBlockPos.z + "]";
return "PlayerBlockPos: [" + this.playerBlockPos.x + "," + this.playerBlockPos.y + "," + this.playerBlockPos.z + "]";
}
}