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