1 /** 2 Copyright: Copyright (c) 2015-2017 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.worldaccess; 7 8 import voxelman.log; 9 import std..string; 10 import voxelman.geometry.box; 11 import voxelman.core.config; 12 import voxelman.world.block; 13 import voxelman.world.storage; 14 15 final class WorldAccess 16 { 17 private ChunkManager chunkManager; 18 BlockInfoTable blockInfos; 19 BlockChange[][ChunkWorldPos] blockChanges; 20 21 this(ChunkManager chunkManager) { 22 this.chunkManager = chunkManager; 23 } 24 25 // TODO move out change management 26 import std.range : isInputRange, array; 27 void onBlockChange(ChunkWorldPos cwp, BlockChange change) 28 { 29 blockChanges[cwp] = blockChanges.get(cwp, null) ~ change; 30 } 31 32 bool isChunkLoaded(ChunkWorldPos cwp) { 33 return chunkManager.isChunkLoaded(cwp); 34 } 35 36 void applyBlockChanges(ChunkWorldPos cwp, BlockChange[] changes) 37 { 38 WriteBuffer* writeBuffer = chunkManager.getOrCreateWriteBuffer(cwp, 39 BLOCK_LAYER, WriteBufferPolicy.copySnapshotArray); 40 if (writeBuffer is null) return; 41 applyChanges(writeBuffer, changes); 42 writeBuffer.layer.metadata = calcChunkFullMetadata(writeBuffer.layer, blockInfos); 43 } 44 45 bool setBlock(BlockWorldPos bwp, BlockId blockId, BlockMetadata blockMeta = 0) { 46 auto blockIndex = BlockChunkIndex(bwp); 47 auto cwp = ChunkWorldPos(bwp); 48 WriteBuffer* writeBuffer = chunkManager.getOrCreateWriteBuffer(cwp, 49 BLOCK_LAYER, WriteBufferPolicy.copySnapshotArray); 50 if (writeBuffer is null) return false; 51 52 BlockId[] blocks = writeBuffer.layer.getArray!BlockId; 53 blocks[blockIndex] = blockId; 54 onBlockChange(cwp, BlockChange(blockIndex.index, blockId, blockMeta)); 55 updateWriteBufferMetadata(writeBuffer); 56 return true; 57 } 58 59 bool fillBox(WorldBox blockFillBox, BlockId blockId, BlockMetadata blockMeta = 0) { 60 WorldBox affectedChunks = blockBoxToChunkBox(blockFillBox); 61 ushort dimension = blockFillBox.dimension; 62 63 foreach(chunkPos; affectedChunks.positions) { 64 Box chunkBlockBox = chunkToBlockBox(chunkPos); 65 auto intersection = boxIntersection(chunkBlockBox, blockFillBox); 66 assert(!intersection.empty); 67 68 auto cwp = ChunkWorldPos(chunkPos, dimension); 69 auto chunkLocalBox = intersection; 70 chunkLocalBox.position -= chunkBlockBox.position; 71 72 fillChunkBox(cwp, chunkLocalBox, blockId, blockMeta); 73 } 74 return true; 75 } 76 77 // blockBox is in chunk-local coordinates 78 bool fillChunkBox(ChunkWorldPos cwp, Box blockBox, BlockId blockId, BlockMetadata blockMeta = 0) { 79 auto oldBlocks = chunkManager.getChunkSnapshot(cwp, BLOCK_LAYER); 80 if (oldBlocks.isNull) return false; 81 82 if (oldBlocks.type == StorageType.uniform) 83 { 84 if (oldBlocks.getUniform!BlockId() == blockId) { 85 return false; 86 } 87 } 88 89 WriteBuffer* blockWB; 90 91 // uniform fill 92 if (blockBox.size == CHUNK_SIZE_VECTOR) 93 { 94 blockWB = chunkManager.getOrCreateWriteBuffer(cwp, BLOCK_LAYER); 95 if (blockWB is null) return false; 96 blockWB.makeUniform!BlockId(blockId); 97 } 98 else 99 { 100 blockWB = chunkManager.getOrCreateWriteBuffer(cwp, 101 BLOCK_LAYER, WriteBufferPolicy.copySnapshotArray); 102 BlockId[] blocks = blockWB.getArray!BlockId; 103 assert(blocks.length == CHUNK_SIZE_CUBE, format("blocks %s", blocks.length)); 104 setSubArray(blocks, CHUNK_SIZE_VECTOR, blockBox, blockId); 105 } 106 fillChunkBoxMetadata(cwp, blockBox, blockMeta); 107 updateWriteBufferMetadata(blockWB); 108 109 return true; 110 } 111 112 private void fillChunkBoxMetadata(ChunkWorldPos cwp, Box blockBox, BlockMetadata blockMeta) { 113 auto oldMetas = chunkManager.getChunkSnapshot(cwp, METADATA_LAYER); 114 115 if (oldMetas.type == StorageType.uniform) 116 { 117 if (oldMetas.getUniform!BlockMetadata == blockMeta) { 118 return; 119 } 120 } 121 122 if (blockBox.size == CHUNK_SIZE_VECTOR) 123 { 124 WriteBuffer* metadataWB = chunkManager.getOrCreateWriteBuffer(cwp, METADATA_LAYER); 125 metadataWB.makeUniform!BlockMetadata(blockMeta); 126 if (blockMeta == 0) { 127 metadataWB.removeSnapshot = true; 128 } 129 } 130 else 131 { 132 WriteBuffer* metadataWB = chunkManager.getOrCreateWriteBuffer(cwp, 133 METADATA_LAYER, WriteBufferPolicy.copySnapshotArray); 134 assert(metadataWB.layer.type == StorageType.fullArray); 135 BlockMetadata[] metas = metadataWB.getArray!BlockMetadata; 136 assert(metas.length == CHUNK_SIZE_CUBE, format("metas %s", metas.length)); 137 setSubArray(metas, CHUNK_SIZE_VECTOR, blockBox, blockMeta); 138 } 139 } 140 141 private void updateWriteBufferMetadata(WriteBuffer* writeBuffer) { 142 writeBuffer.layer.metadata = calcChunkFullMetadata(writeBuffer.layer, blockInfos); 143 } 144 145 BlockId getBlock(BlockWorldPos bwp) { 146 auto blockIndex = BlockChunkIndex(bwp); 147 auto chunkPos = ChunkWorldPos(bwp); 148 auto snap = chunkManager.getChunkSnapshot(chunkPos, BLOCK_LAYER, Yes.Uncompress); 149 if (!snap.isNull) { 150 return snap.getBlockId(blockIndex); 151 } 152 return 0; 153 } 154 155 BlockMetadata getBlockMeta(BlockWorldPos bwp) { 156 auto blockIndex = BlockChunkIndex(bwp); 157 auto chunkPos = ChunkWorldPos(bwp); 158 auto snap = chunkManager.getChunkSnapshot(chunkPos, METADATA_LAYER, Yes.Uncompress); 159 if (!snap.isNull) { 160 return snap.getLayerItemNoncompressed!BlockMetadata(blockIndex); 161 } 162 return 0; 163 } 164 165 BlockIdAndMeta getBlockIdAndMeta(BlockWorldPos bwp) { 166 BlockIdAndMeta result; 167 auto blockIndex = BlockChunkIndex(bwp); 168 auto chunkPos = ChunkWorldPos(bwp); 169 170 auto blocks = chunkManager.getChunkSnapshot(chunkPos, BLOCK_LAYER, Yes.Uncompress); 171 if (!blocks.isNull) { 172 result.id = blocks.getBlockId(blockIndex); 173 } 174 175 auto metas = chunkManager.getChunkSnapshot(chunkPos, METADATA_LAYER, Yes.Uncompress); 176 if (!metas.isNull) { 177 result.metadata = metas.getLayerItemNoncompressed!BlockMetadata(blockIndex); 178 } 179 180 return result; 181 } 182 183 bool isFree(BlockWorldPos bwp) { 184 auto blockId = getBlock(bwp); 185 return blockId == 1; // air 186 } 187 }