1 /**
2 Copyright: Copyright (c) 2015-2016 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 module storage;
7 
8 import std.algorithm : map, joiner;
9 import std.experimental.logger;
10 import std.string;
11 import std.typecons : Nullable;
12 import std.conv : to;
13 import server;
14 import chunkmanager : ChunkManager;
15 import voxelman.utils.queue;
16 import voxelman.world.storage.utils;
17 
18 // Storage made to test delays in read and write.
19 struct ChunkInMemoryStorage {
20 	void delegate(ChunkWorldPos, ChunkDataSnapshot)[] onChunkLoadedHandlers;
21 	void delegate(ChunkWorldPos, ChunkDataSnapshot)[] onChunkSavedHandlers;
22 
23 	private static struct SaveItem {
24 		ChunkWorldPos cwp;
25 		ChunkDataSnapshot snapshot;
26 	}
27 	private static struct LoadItem {
28 		ChunkWorldPos cwp;
29 		BlockId[] blockBuffer;
30 	}
31 	private ChunkDataSnapshot[ChunkWorldPos] snapshots;
32 	private Queue!LoadItem snapshotsToLoad;
33 	private Queue!SaveItem snapshotsToSave;
34 
35 	// load one chunk per update
36 	void update() {
37 		auto toLoad = snapshotsToLoad.valueRange;
38 		if (!toLoad.empty) {
39 			LoadItem loadItem = toLoad.front;
40 			toLoad.popFront();
41 			ChunkDataSnapshot snap = snapshots.get(loadItem.cwp, ChunkDataSnapshot());
42 			if (snap.blocks) {
43 				loadItem.blockBuffer[] = snap.blocks;
44 			}
45 			snap.blocks = loadItem.blockBuffer;
46 
47 			foreach(handler; onChunkLoadedHandlers)
48 				handler(loadItem.cwp, snap);
49 		}
50 
51 		auto toSave = snapshotsToSave.valueRange;
52 		if (!toSave.empty) {
53 			SaveItem saveItem = toSave.front;
54 			toSave.popFront();
55 			ChunkDataSnapshot* snap = saveItem.cwp in snapshots;
56 			if (snap) {
57 				snap.blocks[] = saveItem.snapshot.blocks;
58 				saveItem.snapshot.blocks = snap.blocks;
59 			} else {
60 				saveItem.snapshot.blocks = saveItem.snapshot.blocks.dup;
61 			}
62 			ChunkWorldPos cwp = saveItem.cwp;
63 			snapshots[cwp] = saveItem.snapshot;
64 			foreach(handler; onChunkSavedHandlers)
65 				handler(cwp, saveItem.snapshot);
66 		}
67 	}
68 
69 	// duplicate queries aren't checked.
70 	void loadChunk(ChunkWorldPos cwp, BlockId[] blockBuffer) {
71 		snapshotsToLoad.put(LoadItem(cwp, blockBuffer));
72 	}
73 
74 	void saveChunk(ChunkWorldPos cwp, ChunkDataSnapshot snapshot) {
75 		snapshotsToSave.put(SaveItem(cwp, snapshot));
76 	}
77 }
78 
79 final class WorldAccess {
80 	private ChunkManager* chunkManager;
81 
82 	this(ChunkManager* chunkManager) {
83 		this.chunkManager = chunkManager;
84 	}
85 
86 	bool setBlock(BlockWorldPos bwp, BlockId blockId) {
87 		auto blockPos = BlockChunkPos(bwp);
88 		auto chunkPos = ChunkWorldPos(bwp);
89 		BlockId[] blocks = chunkManager.getWriteBuffer(chunkPos);
90 		if (blocks is null)
91 			return false;
92 		infof("  SETB @%s", chunkPos);
93 		blocks[blockPos.pos] = blockId;
94 
95 		import std.range : only;
96 		chunkManager.onBlockChanges(chunkPos, only(BlockChange(blockPos, blockId)));
97 		return true;
98 	}
99 
100 	BlockId getBlock(BlockWorldPos bwp) {
101 		auto blockPos = BlockChunkPos(bwp);
102 		auto chunkPos = ChunkWorldPos(bwp);
103 		auto snap = chunkManager.getChunkSnapshot(chunkPos);
104 		if (!snap.isNull) {
105 			return snap.blocks[blockPos.pos];
106 		}
107 		return 0;
108 	}
109 }
110 
111 struct Volume1D {
112 	int position;
113 	int size;
114 
115 	bool empty() @property const {
116 		return size == 0;
117 	}
118 
119 	bool contains(int otherPosition) const {
120 		if (otherPosition < position || otherPosition >= position + size) return false;
121 		return true;
122 	}
123 
124 	bool opEquals()(auto ref const Volume1D other) const {
125 		return position == other.position && size == other.size;
126 	}
127 
128 	// generates all positions within volume.
129 	auto positions() @property const {
130 		import std.range : iota;
131 		return iota(position, position + size);
132 	}
133 }
134 
135 // Manages lists of observers per chunk
136 final class ChunkObserverManager {
137 	void delegate(ChunkWorldPos, size_t numObservers) changeChunkNumObservers;
138 
139 	private ChunkObservers[ChunkWorldPos] chunkObservers;
140 	private Volume1D[ClientId] viewVolumes;
141 
142 	ClientId[] getChunkObservers(ChunkWorldPos cwp) {
143 		if (auto observers = cwp in chunkObservers)
144 			return observers.clients;
145 		else
146 			return null;
147 	}
148 
149 	void addServerObserver(ChunkWorldPos cwp) {
150 		auto list = chunkObservers.get(cwp, ChunkObservers.init);
151 		++list.numServerObservers;
152 		changeChunkNumObservers(cwp, list.numObservers);
153 		chunkObservers[cwp] = list;
154 	}
155 
156 	void removeServerObserver(ChunkWorldPos cwp) {
157 		auto list = chunkObservers.get(cwp, ChunkObservers.init);
158 		--list.numServerObservers;
159 		changeChunkNumObservers(cwp, list.numObservers);
160 		if (list.empty)
161 			chunkObservers.remove(cwp);
162 		else
163 			chunkObservers[cwp] = list;
164 	}
165 
166 	void addObserver(ClientId clientId, Volume1D volume) {
167 		assert(clientId !in viewVolumes, "Client is already added");
168 		changeObserverVolume(clientId, volume);
169 	}
170 
171 	void removeObserver(ClientId clientId) {
172 		if (clientId in viewVolumes) {
173 			changeObserverVolume(clientId, Volume1D.init);
174 			viewVolumes.remove(clientId);
175 		}
176 		else
177 			warningf("removing observer %s, that was not added", clientId);
178 	}
179 
180 	void changeObserverVolume(ClientId clientId, Volume1D newVolume) {
181 		Volume1D oldVolume = viewVolumes.get(clientId, Volume1D.init);
182 		viewVolumes[clientId] = newVolume;
183 
184 		TrisectAxisResult tsect = trisectAxis(oldVolume.position, oldVolume.position + oldVolume.size,
185 			newVolume.position, newVolume.position + newVolume.size);
186 
187 		// remove observer
188 		foreach(a; tsect.aranges[0..tsect.numRangesA]
189 		.map!(a => Volume1D(a.start, a.length).positions).joiner) {
190 			removeChunkObserver(ChunkWorldPos(a), clientId);
191 		}
192 
193 		// add observer
194 		foreach(b; tsect.branges[0..tsect.numRangesB]
195 		.map!(b => Volume1D(b.start, b.length).positions).joiner) {
196 			addChunkObserver(ChunkWorldPos(b), clientId);
197 		}
198 	}
199 
200 	private void addChunkObserver(ChunkWorldPos cwp, ClientId clientId) {
201 		auto list = chunkObservers.get(cwp, ChunkObservers.init);
202 		list.add(clientId);
203 		changeChunkNumObservers(cwp, list.numObservers);
204 		chunkObservers[cwp] = list;
205 		infof(" OBSV @%s by '%s'", cwp, clientId);
206 	}
207 
208 	private void removeChunkObserver(ChunkWorldPos cwp, ClientId clientId) {
209 		auto list = chunkObservers.get(cwp, ChunkObservers.init);
210 		list.remove(clientId);
211 		changeChunkNumObservers(cwp, list.numObservers);
212 		if (list.empty)
213 			chunkObservers.remove(cwp);
214 		else
215 			chunkObservers[cwp] = list;
216 		infof(" UNOB @%s by '%s'", cwp, clientId);
217 	}
218 }
219 
220 // Describes observers for a single chunk
221 struct ChunkObservers {
222 	// clients observing this chunk
223 	private ClientId[] _clients;
224 	// ref counts for keeping chunk loaded
225 	size_t numServerObservers;
226 
227 	ClientId[] clients() @property {
228 		return _clients;
229 	}
230 
231 	bool empty() @property const {
232 		return numObservers == 0;
233 	}
234 
235 	size_t numObservers() @property const {
236 		return _clients.length + numServerObservers;
237 	}
238 
239 	void add(ClientId clientId) {
240 		_clients ~= clientId;
241 	}
242 
243 	void remove(ClientId clientId) {
244 		import std.algorithm : remove, SwapStrategy;
245 		_clients = remove!((a) => a == clientId, SwapStrategy.unstable)(_clients);
246 	}
247 }