1 /** 2 Copyright: Copyright (c) 2017-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.editor; 7 8 import voxelman.container.hash.set; 9 import voxelman.core.config; 10 import voxelman.world.storage.chunk; 11 import voxelman.world.storage; 12 13 14 final class ChunkEditor 15 { 16 private ubyte numLayers; 17 private ChunkManager chunkManager; 18 void delegate(ChunkWorldPos) addServerObserverHandler; 19 void delegate(ChunkWorldPos) removeServerObserverHandler; 20 21 private WriteBuffer[ChunkWorldPos][] writeBuffers; 22 23 this(ubyte _numLayers, ChunkManager _chunkManager) { 24 numLayers = _numLayers; 25 writeBuffers.length = _numLayers; 26 chunkManager = _chunkManager; 27 } 28 29 /// Returns all write buffers. You can modify data in write buffers, but not 30 /// hashmap itself. 31 /// Can be used for metadata update before commit. 32 WriteBuffer[ChunkWorldPos] getWriteBuffers(ubyte layer) { 33 return writeBuffers[layer]; 34 } 35 36 /// called at the end of tick 37 void commitSnapshots(TimestampType currentTime) { 38 foreach(ubyte layer; 0..numLayers) 39 { 40 auto writeBuffersCopy = writeBuffers[layer]; 41 // Clear it here because commit can unload chunk. 42 // And unload asserts that chunk is not in writeBuffers. 43 writeBuffers[layer] = null; 44 foreach(cwp, writeBuffer; writeBuffersCopy) 45 { 46 if (writeBuffer.isModified) 47 { 48 chunkManager.commitLayerSnapshot(cwp, writeBuffer, currentTime, layer); 49 } 50 else 51 { 52 freeLayerArray(writeBuffer.layer); 53 } 54 removeServerObserverHandler(cwp); 55 } 56 } 57 } 58 59 /// Returns writeable copy of current chunk snapshot. 60 /// This buffer is valid until commit. 61 /// After commit this buffer becomes next immutable snapshot. 62 /// Returns null if chunk is not added and/or not loaded. 63 /// If write buffer was not yet created then it is created based on policy. 64 /// 65 /// If allowNonLoaded is enabled, then will create write buffer even if chunk is in non_loaded state. 66 /// Useful for offline generation and conversion tools that write directly to chunk manager. 67 /// You can write all chunk at once and then commit. Internal user will prevent write buffers from unloading. 68 /// And on commit a save will be performed automatically. 69 /// 70 /// BUG: returned pointer points inside hash table. 71 /// If new write buffer is added hash table can reallocate. 72 /// Do not create new write buffers while keeping pointer to any write buffer. 73 /// Reallocation can prevent changes to buffers obtained earlier than reallocation to be invisible. 74 WriteBuffer* getOrCreateWriteBuffer(ChunkWorldPos cwp, ubyte layer, 75 WriteBufferPolicy policy = WriteBufferPolicy.createUniform, 76 bool allowNonLoaded = false) 77 { 78 if (!chunkManager.isChunkLoaded(cwp) && !allowNonLoaded) return null; 79 auto writeBuffer = cwp in writeBuffers[layer]; 80 if (writeBuffer is null) { 81 writeBuffer = createWriteBuffer(cwp, layer); 82 if (writeBuffer && policy == WriteBufferPolicy.copySnapshotArray) { 83 auto old = chunkManager.getChunkSnapshot(cwp, layer); 84 if (!old.isNull) { 85 applyLayer(old, writeBuffer.layer); 86 } 87 } 88 } 89 return writeBuffer; 90 } 91 92 // Creates write buffer for writing changes in it. 93 // Latest snapshot's data is not copied in it. 94 // Write buffer is then avaliable through getWriteBuffer/getOrCreateWriteBuffer. 95 // On commit stage WB is moved into new snapshot if write buffer was modified. 96 // Adds internal user that is removed on commit to prevent chunk from unloading with uncommitted changes. 97 // Returns pointer to created write buffer. 98 private WriteBuffer* createWriteBuffer(ChunkWorldPos cwp, ubyte layer) { 99 assert(cwp !in writeBuffers[layer]); 100 auto wb = WriteBuffer.init; 101 wb.layer.layerId = layer; 102 wb.layer.dataLength = chunkManager.layerInfos[layer].uniformExpansionType; 103 writeBuffers[layer][cwp] = wb; 104 addServerObserverHandler(cwp); // prevent unload until commit 105 return cwp in writeBuffers[layer]; 106 } 107 }