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 }