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.type == StorageType.uniform)
85 		{
86 			if (oldBlocks.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 
117 		if (oldMetas.type == StorageType.uniform)
118 		{
119 			if (oldMetas.getUniform!BlockMetadata == blockMeta) {
120 				return;
121 			}
122 		}
123 
124 		if (blockBox.size == CHUNK_SIZE_VECTOR)
125 		{
126 			WriteBuffer* metadataWB = chunkEditor.getOrCreateWriteBuffer(cwp, METADATA_LAYER);
127 			metadataWB.makeUniform!BlockMetadata(blockMeta);
128 			if (blockMeta == 0) {
129 				metadataWB.removeSnapshot = true;
130 			}
131 		}
132 		else
133 		{
134 			WriteBuffer* metadataWB = chunkEditor.getOrCreateWriteBuffer(cwp,
135 				METADATA_LAYER, WriteBufferPolicy.copySnapshotArray);
136 			assert(metadataWB.layer.type == StorageType.fullArray);
137 			BlockMetadata[] metas = metadataWB.getArray!BlockMetadata;
138 			assert(metas.length == CHUNK_SIZE_CUBE, format("metas %s", metas.length));
139 			setSubArray3d(metas, CHUNK_SIZE_VECTOR, blockBox, blockMeta);
140 		}
141 	}
142 
143 	private void updateWriteBufferMetadata(WriteBuffer* writeBuffer) {
144 		writeBuffer.layer.metadata = calcChunkFullMetadata(writeBuffer.layer, blockInfos);
145 	}
146 
147 	BlockId getBlock(BlockWorldPos bwp) {
148 		auto blockIndex = BlockChunkIndex(bwp);
149 		auto chunkPos = ChunkWorldPos(bwp);
150 		auto snap = chunkManager.getChunkSnapshot(chunkPos, BLOCK_LAYER, Yes.Uncompress);
151 		if (!snap.isNull) {
152 			return snap.getBlockId(blockIndex);
153 		}
154 		return 0;
155 	}
156 
157 	BlockMetadata getBlockMeta(BlockWorldPos bwp) {
158 		auto blockIndex = BlockChunkIndex(bwp);
159 		auto chunkPos = ChunkWorldPos(bwp);
160 		auto snap = chunkManager.getChunkSnapshot(chunkPos, METADATA_LAYER, Yes.Uncompress);
161 		if (!snap.isNull) {
162 			return snap.getLayerItemNoncompressed!BlockMetadata(blockIndex);
163 		}
164 		return 0;
165 	}
166 
167 	BlockIdAndMeta getBlockIdAndMeta(BlockWorldPos bwp) {
168 		BlockIdAndMeta result;
169 		auto blockIndex = BlockChunkIndex(bwp);
170 		auto chunkPos = ChunkWorldPos(bwp);
171 
172 		auto blocks = chunkManager.getChunkSnapshot(chunkPos, BLOCK_LAYER, Yes.Uncompress);
173 		if (!blocks.isNull) {
174 			result.id = blocks.getBlockId(blockIndex);
175 		}
176 
177 		auto metas = chunkManager.getChunkSnapshot(chunkPos, METADATA_LAYER, Yes.Uncompress);
178 		if (!metas.isNull) {
179 			result.metadata = metas.getLayerItemNoncompressed!BlockMetadata(blockIndex);
180 		}
181 
182 		return result;
183 	}
184 
185 	bool isFree(BlockWorldPos bwp) {
186 		auto blockId = getBlock(bwp);
187 		return blockId == 1; // air
188 	}
189 }