1 /**
2 Copyright: Copyright (c) 2013-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.chunk;
7 
8 import std.array : uninitializedArray;
9 import std.string : format;
10 
11 import dlib.math.vector;
12 
13 import voxelman.core.config;
14 import voxelman.core.block;
15 import voxelman.core.chunkmesh;
16 import voxelman.storage.coordinates;
17 import voxelman.storage.region;
18 import voxelman.storage.utils;
19 
20 
21 /// Container for chunk updates
22 /// If blockChanges is null uses newBlockData
23 struct ChunkChange
24 {
25 	BlockChange[] blockChanges;
26 	BlockData newBlockData;
27 }
28 
29 // container of single block change.
30 // position is chunk local [0; CHUNK_SIZE-1];
31 struct BlockChange
32 {
33 	// index of block in chunk data
34 	ushort index;
35 
36 	BlockType blockType;
37 }
38 
39 ushort[2] areaOfImpact(BlockChange[] changes)
40 {
41 	ushort start;
42 	ushort end;
43 
44 	foreach(change; changes)
45 	{
46 		if (change.index < start)
47 			start = change.index;
48 		if (change.index > end)
49 			end = change.index;
50 	}
51 
52 	return cast(ushort[2])[start, end+1];
53 }
54 
55 enum StorageType
56 {
57 	uniform,
58 	rle,
59 	array,
60 }
61 
62 struct ChunkDataSnapshot {
63 	//BlockType[] blocks;
64 	BlockData blockData;
65 	TimestampType timestamp;
66 	uint numUsers;
67 }
68 
69 // stores all used snapshots of the chunk. Current is blocks
70 struct BlockDataSnapshot
71 {
72 	BlockData blockData;
73 	TimestampType timestamp;
74 	uint numUsers;
75 }
76 
77 // Stores blocks of the chunk
78 struct BlockData
79 {
80 	/// null if uniform is true, or contains chunk data otherwise
81 	BlockType[] blocks;
82 
83 	/// type of common block
84 	BlockType uniformType = 0; // Unknown block
85 
86 	/// is chunk filled with block of the same type
87 	bool uniform = true;
88 
89 	void convertToArray()
90 	{
91 		if (uniform)
92 		{
93 			blocks = uninitializedArray!(BlockType[])(CHUNK_SIZE_CUBE);
94 			blocks[] = uniformType;
95 			uniform = false;
96 		}
97 	}
98 
99 	void copyToBuffer(BlockType[] outBuffer)
100 	{
101 		assert(outBuffer.length == CHUNK_SIZE_CUBE);
102 		if (uniform)
103 			outBuffer[] = uniformType;
104 		else
105 			outBuffer[] = blocks;
106 	}
107 
108 	void convertToUniform(BlockType _uniformType)
109 	{
110 		uniform = true;
111 		uniformType = _uniformType;
112 		deleteBlocks();
113 	}
114 
115 	void deleteBlocks()
116 	{
117 		blocks = null;
118 	}
119 
120 	BlockType getBlockType(BlockChunkIndex index)
121 	{
122 		if (uniform) return uniformType;
123 		return blocks[index];
124 	}
125 
126 	// returns true if data was changed
127 	bool setBlockType(BlockChunkIndex index, BlockType blockType)
128 	{
129 		if (uniform)
130 		{
131 			if (uniformType != blockType)
132 			{
133 				convertToArray();
134 				blocks[index] = blockType;
135 				return true;
136 			}
137 		}
138 		else
139 		{
140 			if (blocks[index] == blockType)
141 				return false;
142 
143 			blocks[index] = blockType;
144 			return true;
145 		}
146 
147 		return false;
148 	}
149 
150 	// returns [first changed index, last changed index + 1]
151 	// if they match, then no changes occured
152 	// for use on client, when handling MultiblockChangePacket
153 	ushort[2] applyChanges(BlockChange[] changes)
154 	{
155 		ushort start;
156 		ushort end;
157 
158 		foreach(change; changes)
159 		{
160 			if (setBlockType(BlockChunkIndex(change.index), change.blockType))
161 			{
162 				if (change.index < start)
163 					start = change.index;
164 				if (change.index > end)
165 					end = change.index;
166 			}
167 		}
168 
169 		return cast(ushort[2])[start, end+1];
170 	}
171 
172 	// Same as applyChanges, but does only
173 	// change application, no area of impact is calculated
174 	void applyChangesFast(BlockChange[] changes)
175 	{
176 		foreach(change; changes)
177 		{
178 			setBlockType(BlockChunkIndex(change.index), change.blockType);
179 		}
180 	}
181 
182 	//
183 	void applyChangesChecked(BlockChange[] changes)
184 	{
185 		foreach(change; changes)
186 		{
187 			if (change.index <= CHUNK_SIZE_CUBE)
188 				setBlockType(BlockChunkIndex(change.index), change.blockType);
189 		}
190 	}
191 }
192 
193 // Single chunk
194 struct Chunk
195 {
196 	@disable this();
197 
198 	this(ChunkWorldPos position)
199 	{
200 		this.position = position;
201 	}
202 
203 	BlockType getBlockType(int x, int y, int z)
204 	{
205 		return getBlockType(BlockChunkIndex(x, y, z));
206 	}
207 
208 	BlockType getBlockType(BlockChunkIndex blockChunkIndex)
209 	{
210 		return snapshot.blockData.getBlockType(blockChunkIndex);
211 	}
212 
213 	bool allAdjacentLoaded() @property
214 	{
215 		foreach(a; adjacent)
216 		{
217 			if (a is null || !a.isLoaded) return false;
218 		}
219 
220 		return true;
221 	}
222 
223 	bool canBeMeshed() @property
224 	{
225 		return isLoaded && allAdjacentLoaded;
226 	}
227 
228 	bool needsMesh() @property
229 	{
230 		return isLoaded && isVisible && !hasMesh && !isMeshing;
231 	}
232 
233 	bool isUsed() @property
234 	{
235 		return numReaders > 0 || hasWriter;
236 	}
237 
238 	bool adjacentUsed() @property
239 	{
240 		foreach(a; adjacent)
241 			if (a !is null && a.isUsed) return true;
242 		return false;
243 	}
244 
245 	bool adjacentHasUnappliedChanges() @property
246 	{
247 		foreach(a; adjacent)
248 			if (a !is null && a.hasUnappliedChanges) return true;
249 		return false;
250 	}
251 
252 	bool isMarkedForDeletion() @property
253 	{
254 		return next || prev;
255 	}
256 
257 	BlockDataSnapshot* getReadableSnapshot(TimestampType timestamp)
258 	{
259 		if (isLoaded)
260 			return &snapshot;
261 		else
262 			return null;
263 	}
264 
265 	BlockDataSnapshot* getWriteableSnapshot(TimestampType timestamp)
266 	{
267 		if (isLoaded)
268 		{
269 			snapshot.timestamp = timestamp;
270 			return &snapshot;
271 		}
272 		else
273 			return null;
274 	}
275 
276 	ChunkWorldPos position;
277 	BlockDataSnapshot snapshot;
278 	ChunkMesh mesh;
279 	Chunk*[6] adjacent;
280 
281 	// updates
282 	ChunkChange change;
283 	ubyte[] newMeshData; // used for swapping
284 
285 	bool isLoaded = false;
286 	bool isVisible = false;
287 	bool hasMesh = false;
288 	bool isMeshing = false;
289 
290 
291 	// If marked, then chunk is awaiting remesh.
292 	// Do not add chunk to mesh if already dirty
293 	bool isDirty = false;
294 
295 	// Used when remeshing.
296 	// true if chunk is in changedChunks queue and has unapplied changes
297 	bool hasUnappliedChanges = false;
298 
299 	// How many tasks are reading or writing this chunk
300 	bool hasWriter = false;
301 	ushort numReaders = 0;
302 
303 	// In deletion queue.
304 	Chunk* next;
305 	Chunk* prev;
306 }