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 }