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 }