1 /** 2 Copyright: Copyright (c) 2014-2016 Andrey Penechko. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 module voxelman.storage.regionstorage; 7 8 import std.experimental.logger; 9 import std.array : Appender; 10 import std.file : exists, mkdirRecurse; 11 import std.format : formattedWrite; 12 import std.path : isValidPath, dirSeparator; 13 import std.stdio : FOPEN_MAX; 14 15 import dlib.math.vector : ivec3; 16 import voxelman.core.config : TimestampType; 17 import voxelman.storage.chunk; 18 import voxelman.storage.coordinates; 19 import voxelman.storage.region : Region, REGION_SIZE, ChunkStoreInfo; 20 import voxelman.storage.utils; 21 22 enum MAX_CACHED_REGIONS = FOPEN_MAX; 23 24 /// Used for easy saving/loading chunks into region files. 25 struct RegionStorage 26 { 27 private string regionDirectory; 28 private Region*[RegionWorldPos] regions; 29 private char[] buffer; 30 private Appender!(char[]) appender; 31 32 @disable this(); 33 this(string regionDir) 34 { 35 assert(isValidPath(regionDir)); 36 37 regionDirectory = regionDir; 38 if (!exists(regionDirectory)) 39 mkdirRecurse(regionDirectory); 40 41 buffer = new char[1024]; 42 appender = Appender!(char[])(buffer); 43 } 44 45 void clear() 46 { 47 foreach(Region* region; regions.byValue) 48 region.close(); 49 50 regions = null; 51 } 52 53 bool isChunkOnDisk(ChunkWorldPos chunkPos) 54 { 55 RegionWorldPos regionPos = RegionWorldPos(chunkPos); 56 ChunkRegionPos localChunkPositions = ChunkRegionPos(chunkPos); 57 58 if (!isRegionOnDisk(regionPos)) 59 return false; 60 61 return loadRegion(regionPos).isChunkOnDisk(localChunkPositions); 62 } 63 64 public TimestampType chunkTimestamp(ChunkWorldPos chunkPos) 65 { 66 RegionWorldPos regionPos = RegionWorldPos(chunkPos); 67 ChunkRegionPos localChunkPositions = ChunkRegionPos(chunkPos); 68 69 if (!isRegionOnDisk(regionPos)) 70 return 0; 71 72 return loadRegion(regionPos).chunkTimestamp(localChunkPositions); 73 } 74 75 public ChunkStoreInfo getChunkStoreInfo(ChunkWorldPos chunkPos) 76 { 77 RegionWorldPos regionPos = RegionWorldPos(chunkPos); 78 ChunkRegionPos localChunkPositions = ChunkRegionPos(chunkPos); 79 80 if (!isRegionOnDisk(regionPos)) 81 { 82 return ChunkStoreInfo(false, localChunkPositions, chunkPos, 83 regionPos, ChunkRegionIndex(localChunkPositions)); 84 } 85 86 auto res = loadRegion(regionPos).getChunkStoreInfo(localChunkPositions); 87 res.positionInWorld = chunkPos; 88 res.parentRegionPosition = regionPos; 89 return res; 90 } 91 92 bool isRegionOnDisk(RegionWorldPos regionPos) 93 { 94 if (getRegion(regionPos) !is null) 95 return true; 96 return exists(regionFilename(regionPos)); 97 } 98 99 ubyte[] readChunk(ChunkWorldPos chunkPos, ubyte[] outBuffer, out TimestampType timestamp) 100 { 101 RegionWorldPos regionPos = RegionWorldPos(chunkPos); 102 ChunkRegionPos localChunkPositions = ChunkRegionPos(chunkPos); 103 104 Region* region = loadRegion(regionPos); 105 return region.readChunk(localChunkPositions, outBuffer, timestamp); 106 } 107 108 void writeChunk(ChunkWorldPos chunkPos, in ubyte[] blockData, TimestampType timestamp) 109 { 110 RegionWorldPos regionPos = RegionWorldPos(chunkPos); 111 ChunkRegionPos localChunkPositions = ChunkRegionPos(chunkPos); 112 113 Region* region = loadRegion(regionPos); 114 region.writeChunk(localChunkPositions, blockData, timestamp); 115 } 116 117 private Region* loadRegion(RegionWorldPos regionPos) 118 { 119 if (auto region = getRegion(regionPos)) 120 return region; 121 122 if (regions.length >= MAX_CACHED_REGIONS) 123 clear(); 124 125 string filename = regionFilename(regionPos).idup; 126 assert(isValidPath(filename)); 127 128 Region* region = new Region(filename); 129 regions[regionPos] = region; 130 return region; 131 } 132 133 private Region* getRegion(RegionWorldPos regionPos) 134 { 135 return regions.get(regionPos, null); 136 } 137 138 private const(char[]) regionFilename(RegionWorldPos regionPos) 139 { 140 appender.clear(); 141 formattedWrite(appender, "%s%s%s_%s_%s.region", 142 regionDirectory, dirSeparator, regionPos.x, regionPos.y, regionPos.z); 143 return appender.data; 144 } 145 }