1 /** 2 Copyright: Copyright (c) 2015-2018 Andrey Penechko. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 module voxelman.world.storage.chunk.manager; 7 8 import voxelman.log; 9 import std.typecons : Nullable; 10 import std..string : format; 11 public import std.typecons : Flag, Yes, No; 12 13 import voxelman.container.multihashset; 14 import voxelman.container.hash.set; 15 import voxelman.world.block; 16 import voxelman.core.config; 17 import voxelman.world.storage.chunk; 18 import voxelman.world.storage.coordinates : ChunkWorldPos, adjacentPositions; 19 import voxelman.world.storage.utils; 20 import voxelman.world.storage.chunk.chunkprovider; 21 22 23 private enum ChunkState { 24 non_loaded, 25 added_loaded, 26 added_loading, 27 removed_loaded_used 28 } 29 30 private enum traceStateStr = q{ 31 //infof("state @%s %s => %s", cwp, state, 32 // chunkStates.get(cwp, ChunkState.non_loaded)); 33 }; 34 35 36 struct AdjChunkPositions27 { 37 this(ChunkWorldPos cwp) { 38 central = cwp; 39 adjacentPositions!26(cwp, adjacent26); 40 } 41 union { 42 ChunkWorldPos[27] all; 43 ChunkWorldPos[26] adjacent26; 44 struct { 45 ChunkWorldPos[6] adjacent6; 46 ChunkWorldPos[20] adjacent20; 47 ChunkWorldPos central; 48 } 49 } 50 } 51 52 struct AdjChunkLayers27 { 53 union { 54 Nullable!ChunkLayerSnap[27] all; 55 Nullable!ChunkLayerSnap[26] adjacent26; 56 struct { 57 Nullable!ChunkLayerSnap[6] adjacent6; 58 Nullable!ChunkLayerSnap[20] adjacent20; 59 Nullable!ChunkLayerSnap central; 60 } 61 } 62 } 63 64 Nullable!ChunkLayerSnap[len] getChunkSnapshots 65 (size_t len) 66 (ChunkManager cm, 67 ChunkWorldPos[len] positions, 68 ubyte layer, 69 Flag!"Uncompress" uncompress = No.Uncompress) 70 { 71 typeof(return) result; 72 73 foreach(i, cwp; positions) 74 { 75 result[i] = cm.getChunkSnapshot(cwp, layer, uncompress); 76 } 77 78 return result; 79 } 80 81 //version = TRACE_SNAP_USERS; 82 //version = DBG_OUT; 83 //version = DBG_COMPR; 84 85 enum WriteBufferPolicy 86 { 87 createUniform, 88 copySnapshotArray, 89 } 90 91 struct ChunkLayerInfo 92 { 93 /// Defines the size of 94 LayerDataLenType uniformExpansionType; 95 } 96 97 struct ModifiedChunksRange 98 { 99 private HashSet!ChunkWorldPos modifiedChunks; 100 int opApply(scope int delegate(in ChunkWorldPos) del) { 101 return modifiedChunks.opApply(del); 102 } 103 } 104 105 enum MAX_CHUNK_LAYERS = 8; 106 107 final class ChunkManager { 108 void delegate(ChunkWorldPos) onChunkRemovedHandler; 109 void delegate(ChunkWorldPos) onChunkLoadedHandler; 110 111 void delegate(ChunkWorldPos) loadChunkHandler; 112 void delegate(ChunkWorldPos) saveChunkHandler; 113 void delegate(ChunkWorldPos) cancelLoadChunkHandler; 114 115 // debug 116 long totalLayerDataBytes; 117 long numLoadedChunks; 118 size_t numTrackedChunks() { return chunkStates.length; } 119 120 private ChunkLayerSnap[ChunkWorldPos][] snapshots; 121 private ChunkLayerSnap[TimestampType][ChunkWorldPos][] oldSnapshots; 122 private ChunkState[ChunkWorldPos] chunkStates; 123 private HashSet!ChunkWorldPos modifiedChunks; 124 // used to change state from added_loaded to removed_loaded_used 125 private MultiHashSet!ChunkWorldPos totalSnapshotUsers; 126 127 128 ubyte numLayers; 129 package ChunkLayerInfo[] layerInfos; 130 131 this(ubyte _numLayers) { 132 numLayers = _numLayers; 133 snapshots.length = numLayers; 134 oldSnapshots.length = numLayers; 135 layerInfos.length = numLayers; 136 } 137 138 void setLayerInfo(ubyte layer, ChunkLayerInfo info) { 139 layerInfos[layer] = info; 140 } 141 142 bool areChunksLoaded(ChunkWorldPos[] positions) { 143 foreach(pos; positions) 144 if (!isChunkLoaded(pos)) 145 return false; 146 return true; 147 } 148 149 bool isChunkLoaded(ChunkWorldPos cwp) { 150 return getChunkState(cwp) == ChunkState.added_loaded; 151 } 152 153 bool isChunkAdded(ChunkWorldPos cwp) { 154 auto state = getChunkState(cwp); 155 with(ChunkState) { 156 return state == added_loaded || state == added_loading; 157 } 158 } 159 160 bool hasSnapshot(ChunkWorldPos cwp, ubyte layer) { 161 return (cwp in snapshots[layer]) !is null; 162 } 163 164 ModifiedChunksRange getModifiedChunks() { 165 return ModifiedChunksRange(modifiedChunks); 166 } 167 168 /// Used on client to clear modified chunks instead of saving them. 169 void clearModifiedChunks() { 170 modifiedChunks.clear(); 171 } 172 173 /// returned value isNull if chunk is not loaded/added 174 /// If uncompress is Yes then tries to convert snapshot to uncompressed. 175 /// If has users, then uncompressed snapshot copy is returned. Original will not be uncompressed. 176 Nullable!ChunkLayerSnap getChunkSnapshot(ChunkWorldPos cwp, ubyte layer, Flag!"Uncompress" uncompress = No.Uncompress) { 177 if (isChunkLoaded(cwp)) 178 { 179 auto snap = cwp in snapshots[layer]; 180 if (snap) 181 { 182 if (snap.type == StorageType.compressedArray && uncompress) 183 { 184 ubyte[] decompressedData = decompressLayerData((*snap).getArray!ubyte); 185 if (snap.numUsers == 0) { 186 recycleSnapshotMemory(*snap); 187 snap.dataPtr = decompressedData.ptr; 188 snap.dataLength = cast(LayerDataLenType)decompressedData.length; 189 totalLayerDataBytes += snap.dataLength; 190 snap.type = StorageType.fullArray; 191 } 192 else 193 { 194 // BUG: memory leak. Memory is allocated and returned, but not assigned to the snapshot 195 // This way, when snapshot data is deleted, uncompressed data is not deleted. 196 // a) store owner info inside ChunkLayerSnap, so that user can free resources when done. 197 // b) store both compressed and uncompressed data here in snapshots. 198 ChunkLayerSnap res = *snap; 199 res.dataPtr = decompressedData.ptr; 200 res.dataLength = cast(LayerDataLenType)decompressedData.length; 201 res.type = StorageType.fullArray; 202 return Nullable!ChunkLayerSnap(res); 203 } 204 } 205 auto res = Nullable!ChunkLayerSnap(*snap); 206 return res; 207 } 208 else 209 { 210 auto res = ChunkLayerSnap.init; 211 res.dataLength = layerInfos[layer].uniformExpansionType; 212 return Nullable!ChunkLayerSnap(res); 213 } 214 } 215 216 auto res = Nullable!ChunkLayerSnap.init; 217 return res; 218 } 219 220 /// Returns timestamp of current chunk snapshot. 221 /// Store this timestamp to use in removeSnapshotUser 222 /// Adding a user to non-existent snapshot of loaded chunk is allowed, 223 /// since a ChunkLayerSnap.init is returned for such layer. 224 /// Special TimestampType.max is returned. 225 TimestampType addCurrentSnapshotUser(ChunkWorldPos cwp, ubyte layer) { 226 auto snap = cwp in snapshots[layer]; 227 if (!snap) 228 { 229 return TimestampType.max; 230 } 231 version(TRACE_SNAP_USERS) tracef("#%s:%s (before add) %s/%s", cwp, layer, snap.numUsers, totalSnapshotUsers[cwp]); 232 233 auto state = getChunkState(cwp); 234 assert(state == ChunkState.added_loaded, 235 format("To add user chunk must be both added and loaded, not %s", state)); 236 237 totalSnapshotUsers.add(cwp); 238 239 ++snap.numUsers; 240 version(TRACE_SNAP_USERS) tracef("#%s:%s (add cur:+1) %s/%s @%s", cwp, layer, snap.numUsers, totalSnapshotUsers[cwp], snap.timestamp); 241 return snap.timestamp; 242 } 243 244 /// Generic removal of snapshot user. Removes chunk if numUsers == 0. 245 /// Use this to remove added snapshot user. Use timestamp returned from addCurrentSnapshotUser. 246 /// Removing TimestampType.max is no-op. 247 void removeSnapshotUser(ChunkWorldPos cwp, TimestampType timestamp, ubyte layer) { 248 if (timestamp == TimestampType.max) return; 249 auto snap = cwp in snapshots[layer]; 250 if (snap && snap.timestamp == timestamp) 251 { 252 auto totalUsersLeft = removeCurrentSnapshotUser(cwp, layer); 253 if (totalUsersLeft == 0) 254 { 255 auto state = getChunkState(cwp); 256 assert(state == ChunkState.added_loaded || state == ChunkState.removed_loaded_used); 257 if (state == ChunkState.removed_loaded_used) 258 { 259 clearChunkData(cwp); 260 --numLoadedChunks; 261 } 262 } 263 } 264 else 265 { 266 removeOldSnapshotUser(cwp, timestamp, layer); 267 } 268 } 269 270 private struct SnapshotIterator 271 { 272 private ChunkLayerSnap[ChunkWorldPos][] snapshots; 273 private ubyte numLayers; 274 private ChunkWorldPos cwp; 275 private MultiHashSet!ChunkWorldPos* totalSnapshotUsers; 276 int opApply(scope int delegate(ChunkLayerItem) del) { 277 ubyte numChunkLayers; 278 foreach(ubyte layerId; 0..numLayers) 279 { 280 if (auto snap = cwp in snapshots[layerId]) 281 { 282 ++snap.numUsers; // in case new snapshot replaces current one, we need to keep it while it is saved 283 totalSnapshotUsers.add(cwp); 284 if (auto ret = del(ChunkLayerItem(*snap, layerId))) 285 return ret; 286 } 287 } 288 289 return 0; 290 } 291 } 292 293 SnapshotIterator iterateChunkSnapshotsAddUsers(ChunkWorldPos cwp) { 294 return SnapshotIterator(snapshots, numLayers, cwp, &totalSnapshotUsers); 295 } 296 297 /// Internal. Called by code which loads chunks from storage. 298 void onSnapshotLoaded(ChunkWorldPos cwp, ChunkLayerItem[] layers, bool markAsModified) { 299 version(DBG_OUT)infof("res loaded %s", cwp); 300 301 foreach(layer; layers) 302 { 303 totalLayerDataBytes += getLayerDataBytes(layer); 304 305 snapshots[layer.layerId][cwp] = ChunkLayerSnap(layer); 306 version(DBG_COMPR)if (layer.type == StorageType.compressedArray) 307 infof("CM Loaded %s %s %s\n(%(%02x%))", cwp, layer.dataPtr, layer.dataLength, layer.getArray!ubyte); 308 } 309 310 auto state = getChunkState(cwp); 311 312 with(ChunkState) final switch(state) 313 { 314 case non_loaded: 315 clearChunkData(cwp); 316 break; 317 case added_loaded: 318 assert(false, "On loaded should not occur for already loaded chunk"); 319 case added_loading: 320 chunkStates[cwp] = added_loaded; 321 ++numLoadedChunks; 322 if (markAsModified) modifiedChunks.put(cwp); 323 notifyLoaded(cwp); 324 break; 325 case removed_loaded_used: 326 assert(false, "On loaded should not occur for already loaded chunk"); 327 } 328 mixin(traceStateStr); 329 } 330 331 // Puts chunk in added state requesting load if needed. 332 // Notifies on add. Notifies on load if loaded. 333 void loadChunk(ChunkWorldPos cwp) { 334 auto state = getChunkState(cwp); 335 with(ChunkState) final switch(state) { 336 case non_loaded: 337 chunkStates[cwp] = added_loading; 338 if (loadChunkHandler) loadChunkHandler(cwp); 339 break; 340 case added_loaded: 341 break; // ignore 342 case added_loading: 343 break; // ignore 344 case removed_loaded_used: 345 chunkStates[cwp] = added_loaded; 346 notifyLoaded(cwp); 347 break; 348 } 349 mixin(traceStateStr); 350 } 351 352 // Puts chunk in removed state requesting save if needed. 353 // Notifies on remove. 354 void unloadChunk(ChunkWorldPos cwp) { 355 auto state = getChunkState(cwp); 356 notifyRemoved(cwp); 357 with(ChunkState) final switch(state) { 358 case non_loaded: 359 assert(false, "Unload should not occur when chunk was not yet loaded"); 360 case added_loaded: 361 if(cwp in modifiedChunks) 362 { 363 modifiedChunks.remove(cwp); 364 if (saveChunkHandler) saveChunkHandler(cwp); 365 } 366 else 367 { 368 auto totalUsersLeft = totalSnapshotUsers[cwp]; 369 if (totalUsersLeft == 0) 370 { 371 clearChunkData(cwp); 372 --numLoadedChunks; 373 } 374 else 375 { 376 chunkStates[cwp] = removed_loaded_used; 377 } 378 } 379 break; 380 case added_loading: 381 if (cancelLoadChunkHandler) cancelLoadChunkHandler(cwp); 382 clearChunkData(cwp); 383 break; 384 case removed_loaded_used: 385 assert(false, "Unload should not occur when chunk is already removed"); 386 } 387 mixin(traceStateStr); 388 } 389 390 // Commit for single chunk. 391 void commitLayerSnapshot(ChunkWorldPos cwp, WriteBuffer writeBuffer, TimestampType currentTime, ubyte layer) { 392 modifiedChunks.put(cwp); 393 394 auto currentSnapshot = getChunkSnapshot(cwp, layer); 395 if (!currentSnapshot.isNull) handleCurrentSnapCommit(cwp, layer, currentSnapshot.get()); 396 397 assert(writeBuffer.isModified); 398 399 if (writeBuffer.removeSnapshot) 400 { 401 freeLayerArray(writeBuffer.layer); 402 snapshots[layer].remove(cwp); 403 return; 404 } 405 406 writeBuffer.layer.timestamp = currentTime; 407 snapshots[layer][cwp] = ChunkLayerSnap(writeBuffer.layer); 408 totalLayerDataBytes += getLayerDataBytes(writeBuffer.layer); 409 410 if (!isChunkLoaded(cwp)) { 411 chunkStates[cwp] = ChunkState.added_loaded; 412 // BUG/TODO: this will be called on first write buffer, while needs to be called on last write buffer 413 notifyLoaded(cwp); 414 } 415 } 416 417 // PPPPPP RRRRRR IIIII VV VV AAA TTTTTTT EEEEEEE 418 // PP PP RR RR III VV VV AAAAA TTT EE 419 // PPPPPP RRRRRR III VV VV AA AA TTT EEEEE 420 // PP RR RR III VV VV AAAAAAA TTT EE 421 // PP RR RR IIIII VVV AA AA TTT EEEEEEE 422 // 423 424 private void notifyRemoved(ChunkWorldPos cwp) { 425 if (onChunkRemovedHandler) onChunkRemovedHandler(cwp); 426 } 427 428 private void notifyLoaded(ChunkWorldPos cwp) { 429 if (onChunkLoadedHandler) onChunkLoadedHandler(cwp); 430 } 431 432 private ChunkState getChunkState(ChunkWorldPos cwp) { 433 return chunkStates.get(cwp, ChunkState.non_loaded); 434 } 435 436 // Fully removes chunk 437 private void clearChunkData(ChunkWorldPos cwp) { 438 foreach(layer; 0..numLayers) 439 { 440 if (auto snap = cwp in snapshots[layer]) 441 { 442 recycleSnapshotMemory(*snap); 443 snapshots[layer].remove(cwp); 444 } 445 assert(totalSnapshotUsers[cwp] == 0); 446 } 447 assert(cwp !in modifiedChunks); 448 chunkStates.remove(cwp); 449 } 450 451 // Returns number of current snapshot users left. 452 private size_t removeCurrentSnapshotUser(ChunkWorldPos cwp, ubyte layer) { 453 auto snap = cwp in snapshots[layer]; 454 assert(snap && snap.numUsers > 0, "cannot remove chunk user. Snapshot has 0 users"); 455 456 version(TRACE_SNAP_USERS) tracef("#%s:%s (rem before) %s/%s", cwp, layer, snap.numUsers, totalSnapshotUsers[cwp]); 457 458 --snap.numUsers; 459 assert(totalSnapshotUsers[cwp] > 0, "cannot remove chunk user. Snapshot has 0 users"); 460 totalSnapshotUsers.remove(cwp); 461 462 version(TRACE_SNAP_USERS) tracef("#%s:%s (rem cur:-1) %s/%s @%s", cwp, layer, snap.numUsers, totalSnapshotUsers[cwp], snap.timestamp); 463 464 return totalSnapshotUsers[cwp]; 465 } 466 467 /// Snapshot is removed from oldSnapshots if numUsers == 0. 468 private void removeOldSnapshotUser(ChunkWorldPos cwp, TimestampType timestamp, ubyte layer) { 469 ChunkLayerSnap[TimestampType]* chunkSnaps = cwp in oldSnapshots[layer]; 470 version(TRACE_SNAP_USERS) tracef("#%s:%s (rem old) x/%s @%s", cwp, layer, totalSnapshotUsers[cwp], timestamp); 471 assert(chunkSnaps, "old snapshot should have waited for releasing user"); 472 ChunkLayerSnap* snapshot = timestamp in *chunkSnaps; 473 assert(snapshot, "cannot release snapshot user. No such snapshot"); 474 assert(snapshot.numUsers > 0, "cannot remove chunk user. Snapshot has 0 users"); 475 --snapshot.numUsers; 476 version(TRACE_SNAP_USERS) tracef("#%s:%s (rem old:-1) %s/%s @%s", cwp, layer, snapshot.numUsers, totalSnapshotUsers[cwp], timestamp); 477 if (snapshot.numUsers == 0) { 478 (*chunkSnaps).remove(timestamp); 479 if ((*chunkSnaps).length == 0) { // all old snaps of one chunk released 480 oldSnapshots[layer].remove(cwp); 481 } 482 recycleSnapshotMemory(*snapshot); 483 } 484 } 485 486 private void handleCurrentSnapCommit(ChunkWorldPos cwp, ubyte layer, ChunkLayerSnap currentSnapshot) 487 { 488 if (currentSnapshot.numUsers == 0) { 489 version(TRACE_SNAP_USERS) tracef("#%s:%s (commit:%s) %s/%s", cwp, layer, currentSnapshot.numUsers, 0, totalSnapshotUsers[cwp]); 490 recycleSnapshotMemory(currentSnapshot); 491 } else { 492 // transfer users from current layer snapshot into old snapshot 493 totalSnapshotUsers.remove(cwp, currentSnapshot.numUsers); 494 495 if (auto layerSnaps = cwp in oldSnapshots[layer]) { 496 version(TRACE_SNAP_USERS) tracef("#%s:%s (commit add:%s) %s/%s", cwp, layer, 497 currentSnapshot.numUsers, 0, totalSnapshotUsers[cwp]); 498 assert(currentSnapshot.timestamp !in *layerSnaps); 499 (*layerSnaps)[currentSnapshot.timestamp] = currentSnapshot; 500 } else { 501 version(TRACE_SNAP_USERS) tracef("#%s:%s (commit new:%s) %s/%s", cwp, layer, 502 currentSnapshot.numUsers, 0, totalSnapshotUsers[cwp]); 503 oldSnapshots[layer][cwp] = [currentSnapshot.timestamp : currentSnapshot]; 504 version(TRACE_SNAP_USERS) tracef("oldSnapshots[%s][%s] == %s", layer, cwp, oldSnapshots[layer][cwp]); 505 } 506 } 507 } 508 509 // Called when snapshot data can be recycled. 510 private void recycleSnapshotMemory(ref ChunkLayerSnap snap) { 511 totalLayerDataBytes -= getLayerDataBytes(snap); 512 freeLayerArray(snap); 513 } 514 } 515