From c22efa762c42cccd555670511dac1248ffaabfbf Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 30 Jan 2021 14:50:41 -0600 Subject: [PATCH] Set up per world LOD file reading and writing Note: this doesn't change the LoadedRegions upon world change. --- .../backsun/lod/objects/LoadedRegions.java | 69 ++----- .../java/backsun/lod/proxy/ClientProxy.java | 9 +- .../lod/util/LodRegionFileHandler.java | 195 ++++++++++++++---- 3 files changed, 177 insertions(+), 96 deletions(-) diff --git a/src/main/java/backsun/lod/objects/LoadedRegions.java b/src/main/java/backsun/lod/objects/LoadedRegions.java index 714c5bf5b..ee434a75c 100644 --- a/src/main/java/backsun/lod/objects/LoadedRegions.java +++ b/src/main/java/backsun/lod/objects/LoadedRegions.java @@ -1,9 +1,7 @@ package backsun.lod.objects; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - import backsun.lod.util.LodRegionFileHandler; +import net.minecraft.client.Minecraft; import net.minecraft.world.DimensionType; /** @@ -21,33 +19,28 @@ public class LoadedRegions private int halfWidth; public LodRegion regions[][]; - private boolean isRegionDirty[][]; - private long regionLastWriteTime[][]; + public boolean isRegionDirty[][]; private int centerX; private int centerZ; - private LodRegionFileHandler rfHandler = new LodRegionFileHandler();; - private ExecutorService fileHandlerPool = Executors.newFixedThreadPool(1); + private LodRegionFileHandler rfHandler; public LoadedRegions(DimensionType newDimension, int newMaxWidth) { dimension = newDimension; width = newMaxWidth; + // TODO what can be done if connected to a server? + rfHandler = new LodRegionFileHandler(Minecraft.getMinecraft().getIntegratedServer().getWorld(0).getSaveHandler(), this); + regions = new LodRegion[width][width]; isRegionDirty = new boolean[width][width]; - regionLastWriteTime = new long[width][width]; - // populate isRegionDirty and regionLastWriteTime + // populate isRegionDirty for(int i = 0; i < width; i++) - { for(int j = 0; j < width; j++) - { isRegionDirty[i][j] = false; - regionLastWriteTime[i][j] = -1; - } - } centerX = 0; centerZ = 0; @@ -241,7 +234,9 @@ public class LoadedRegions int zIndex = (centerZ - regionZ) + halfWidth; isRegionDirty[xIndex][zIndex] = true; - fileHandlerPool.execute(saveDirtyRegionsAsync); + + + rfHandler.saveDirtyRegionsToFile(); } /** @@ -279,43 +274,6 @@ public class LoadedRegions return rfHandler.loadRegionFromFile(regionX, regionZ); } - public void saveAllRegionsToFile() - { - Thread task = new Thread(() -> { - for (LodRegion regionArray[] : regions) - for (LodRegion region : regionArray) - rfHandler.saveRegionToDisk(region); - }); - - for(int i = 0; i < width; i++) - { - for(int j = 0; j < width; j++) - { - isRegionDirty[i][j] = false; - regionLastWriteTime[i][j] = System.currentTimeMillis(); - } - } - - - fileHandlerPool.execute(task); - } - - - private Thread saveDirtyRegionsAsync = new Thread(() -> { - for(int i = 0; i < width; i++) - { - for(int j = 0; j < width; j++) - { - if(isRegionDirty[i][j]) - { - rfHandler.saveRegionToDisk(regions[i][j]); - isRegionDirty[i][j] = false; - } - } - } - }); - - /** * Returns whether the region at the given X and Z coordinates @@ -328,6 +286,13 @@ public class LoadedRegions return xIndex >= 0 && xIndex < width && zIndex >= 0 && zIndex < width; } + + + + public int getWidth() + { + return width; + } } diff --git a/src/main/java/backsun/lod/proxy/ClientProxy.java b/src/main/java/backsun/lod/proxy/ClientProxy.java index bff5d2b79..f40d269ff 100644 --- a/src/main/java/backsun/lod/proxy/ClientProxy.java +++ b/src/main/java/backsun/lod/proxy/ClientProxy.java @@ -9,6 +9,7 @@ import backsun.lod.objects.LodRegion; import backsun.lod.renderer.LodRenderer; import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.WorldClient; +import net.minecraft.world.DimensionType; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.storage.ExtendedBlockStorage; import net.minecraftforge.client.event.RenderWorldLastEvent; @@ -131,17 +132,17 @@ public class ClientProxy extends CommonProxy // given a valid chunk object // (Minecraft often gives back empty // or null chunks in this method) - if (chunk != null && isValidChunk(chunk) && Minecraft.getMinecraft().world != null) + Minecraft mc = Minecraft.getMinecraft(); + if (mc != null && mc.world != null && chunk != null && isValidChunk(chunk)) { Thread thread = new Thread(() -> { - Minecraft mc = Minecraft.getMinecraft(); - LodChunk lod = new LodChunk(chunk, mc.world); if (regions == null) { - regions = new LoadedRegions(null, regionWidth); + DimensionType dim = DimensionType.getById(chunk.getWorld().provider.getDimension()); + regions = new LoadedRegions(dim, regionWidth); } regions.addLod(lod); diff --git a/src/main/java/backsun/lod/util/LodRegionFileHandler.java b/src/main/java/backsun/lod/util/LodRegionFileHandler.java index fa114d3a3..d1450ac4e 100644 --- a/src/main/java/backsun/lod/util/LodRegionFileHandler.java +++ b/src/main/java/backsun/lod/util/LodRegionFileHandler.java @@ -5,9 +5,14 @@ import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import backsun.lod.objects.LoadedRegions; import backsun.lod.objects.LodChunk; import backsun.lod.objects.LodRegion; +import net.minecraft.client.Minecraft; +import net.minecraft.world.storage.ISaveHandler; /** * This object handles creating LodRegions @@ -15,21 +20,49 @@ import backsun.lod.objects.LodRegion; * to file. * * @author James Seibel - * @version 09-28-2020 + * @version 01-30-2021 */ public class LodRegionFileHandler { + private LoadedRegions loadedRegion = null; + public long regionLastWriteTime[][]; + // String s = Minecraft.getMinecraftDir().getCanonicalPath() + "/saves/" + world.getSaveHandler().getSaveDirectoryName() + "/data/AA/World" + world.provider.dimensionId + ".dat"; - private final String SAVE_DIR = "C:/Users/James Seibel/Desktop/lod_save_folder/"; + private String save_dir; + public ISaveHandler saveHandler; private final String FILE_NAME_PREFIX = "lod"; - private final String FILE_NAME_DELIMITER = "."; private final String FILE_EXTENSION = ".txt"; + private ExecutorService fileWritingThreadPool = Executors.newFixedThreadPool(1); + /** Is true if the readyToReadAndWrite is false */ + private boolean waitingToSaveRegions = false; - public LodRegionFileHandler() + + public LodRegionFileHandler(ISaveHandler newSaveHandler, LoadedRegions newLoadedRegion) { + saveHandler = newSaveHandler; + if (saveHandler == null) + { + throw new NullPointerException("LodRegionFileHandler requires a world SaveHandler."); + } + + loadedRegion = newLoadedRegion; + // these two variable are used in sync with the LoadedRegions + regionLastWriteTime = new long[loadedRegion.getWidth()][loadedRegion.getWidth()]; + for(int i = 0; i < loadedRegion.getWidth(); i++) + for(int j = 0; j < loadedRegion.getWidth(); j++) + regionLastWriteTime[i][j] = -1; + + + if (saveHandler != null && saveHandler.getWorldDirectory() != null) + try { + save_dir = saveHandler.getWorldDirectory().getCanonicalPath(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } @@ -38,10 +71,14 @@ public class LodRegionFileHandler /** - * Return the LodRegion that + * Return the LodRegion at the given coordinates. + * (null if the file doesn't exist) */ public LodRegion loadRegionFromFile(int regionX, int regionZ) { + if (!readyToReadAndWrite()) + return null; + String fileName = getFileNameForRegion(regionX, regionZ); File f = new File(fileName); @@ -94,8 +131,50 @@ public class LodRegionFileHandler } - - + public synchronized void saveDirtyRegionsToFile() + { + if (!readyToReadAndWrite()) + { + // we aren't ready to read and write yet + if(!waitingToSaveRegions) + { + waitingToSaveRegions = true; + + // retry until we are able to read and write + // then wake up the fileWritingThreadPool + Thread retryReady = new Thread(() -> + { + try + { + // check once every so often so see + // if anything has changed so we can + // start reading and writing files + while(!readyToReadAndWrite()) + { + // TODO what can be done if connected to a server? + saveHandler = Minecraft.getMinecraft().getIntegratedServer().getWorld(0).getSaveHandler(); + this.wait(1000); + } + + // we can start writing files now + if (waitingToSaveRegions) + { + fileWritingThreadPool.execute(saveDirtyRegionsAsync); + waitingToSaveRegions = false; + } + } + catch (InterruptedException e) + { /* should never be called */} + }); + + retryReady.run(); + } + + return; + } + + fileWritingThreadPool.execute(saveDirtyRegionsAsync); + } public void saveRegionToDisk(LodRegion region) { @@ -106,11 +185,16 @@ public class LodRegionFileHandler File f = new File(getFileNameForRegion(x, z)); - try { if (!f.exists()) { + // make the LOD folder if it doesn't exist + if(!f.getParentFile().exists()) + { + f.getParentFile().mkdirs(); + } + f.createNewFile(); } @@ -131,51 +215,82 @@ public class LodRegionFileHandler } catch(Exception e) { - System.err.println("LOD ERROR: "); - e.printStackTrace(); + System.err.println("LOD ERROR: " + e.getMessage()); + //e.printStackTrace(); } } - - - - - /** - * Returns true if a file exists for the region - * containing the given chunk. - */ - private boolean regionFileExistForChunk(int chunkX, int chunkZ) + private Thread saveDirtyRegionsAsync = new Thread(() -> { - // convert chunk coordinates to region - // coordinates - int regionX = chunkX / 32; - int regionZ = chunkZ / 32; + for(int i = 0; i < loadedRegion.getWidth(); i++) + { + for(int j = 0; j < loadedRegion.getWidth(); j++) + { + if(loadedRegion.isRegionDirty[i][j]) + { + saveRegionToDisk(loadedRegion.regions[i][j]); + loadedRegion.isRegionDirty[i][j] = false; + } + } + } - return new File(getFileNameForRegion(regionX, regionZ)).exists(); - } - - /** - * Returns true if a file exists - * for the given region coordinates. - */ - private boolean regionFileExistForRegion(int regionX, int regionZ) - { - return new File(getFileNameForRegion(regionX, regionZ)).exists(); - } - - + waitingToSaveRegions = false; + }); + + /** * Return the name of the file that should contain the - * region at the given x and z. + * region at the given x and z.
+ * Returns null if this object isn't ready to read and write. * @param regionX * @param regionZ */ private String getFileNameForRegion(int regionX, int regionZ) { - return SAVE_DIR + - FILE_NAME_PREFIX + FILE_NAME_DELIMITER + - regionX + FILE_NAME_DELIMITER + regionZ + FILE_EXTENSION; + if (!readyToReadAndWrite()) + return null; + + return save_dir + "\\lod_data\\DIM" + loadedRegion.dimension.getId() + "\\" + + FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION; } + + + /** + * Returns if this FileHandler is ready to read + * and write files. + */ + public boolean readyToReadAndWrite() + { + return saveHandler != null && saveHandler.getWorldDirectory() != null && + save_dir != null && !save_dir.isEmpty(); + } + +// /** +// * Used to wake up the fileWritingThreadPool once +// * we are able to read and write files. +// */ +// private Thread retryReady = new Thread(() -> +// { +// try +// { +// // check once every so often so see +// // if anything has changed so we can +// // start reading and writing files +// while(!readyToReadAndWrite()) +// { +// this.wait(1000); +// } +// +// // we can start writing files now +// if (waitingToSaveRegions) +// { +// fileWritingThreadPool.execute(saveDirtyRegionsAsync); +// waitingToSaveRegions = false; +// } +// } +// catch (InterruptedException e) +// { /* should never be called */} +// }); }