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 voxelman.world.storage.chunkobservermanager;
7 
8 import std.experimental.logger;
9 import voxelman.math;
10 import netlib.connection : ClientId;
11 import voxelman.world.storage.coordinates : ChunkWorldPos;
12 import voxelman.world.storage.worldbox : WorldBox, TrisectResult, trisect4d, calcBox;
13 
14 enum chunkPackLoadSize = 200;
15 
16 struct ViewInfo
17 {
18 	ClientId clientId;
19 	WorldBox viewBox;
20 	//ivec3 viewRadius;
21 	ChunkWorldPos observerPosition;
22 	int viewRadius;
23 
24 	size_t numObservedRings;
25 	size_t currentChunkIndex;
26 }
27 
28 // Manages lists of observers per chunk
29 final class ChunkObserverManager {
30 	void delegate(ChunkWorldPos, size_t numObservers) changeChunkNumObservers;
31 	void delegate(ChunkWorldPos, ClientId) chunkObserverAdded;
32 	size_t delegate() loadQueueSpaceAvaliable;
33 
34 	private ChunkObservers[ChunkWorldPos] chunkObservers;
35 	private ViewInfo[ClientId] viewInfos;
36 
37 	void update() {
38 
39 	}
40 	string a = q{
41 
42 		import std.algorithm : sort;
43 
44 		if (viewInfos.length == 0)
45 			return;
46 
47 		ViewInfo[] infos = viewInfos.values;
48 		size_t chunksObserved;
49 
50 		infinite_loop:
51 		while (true)
52 		{
53 			sort!((a, b) => a.numObservedRings < b.numObservedRings)(infos);
54 			chunksObserved = 0;
55 
56 			foreach(ref info; infos)
57 			{
58 				immutable size_t currentRing = info.numObservedRings;
59 				// fully loaded
60 				if (currentRing > info.viewRadius)
61 					break;
62 
63 				//infof("For infos C:%s VR:%s OR:%s CI:%s", info.clientId, info.viewRadius,
64 				//	info.numObservedRings, info.currentChunkIndex);
65 
66 				size_t chunksToLoadClient = chunkPackLoadSize;
67 
68 				immutable ivec3 observerPosition = info.observerPosition;
69 				immutable size_t sideSize = currentRing * 2 + 1;
70 				immutable size_t sideMax = sideSize - 1;
71 				immutable size_t sideSizeSqr = sideSize * sideSize;
72 				immutable size_t numIndexes = sideSizeSqr * sideSize;
73 				//infof("numIndexes %s sideSize %s", numIndexes, sideSize);
74 
75 				size_t index = info.currentChunkIndex;
76 				ivec3 position;
77 				bool empty() {
78 					return index == numIndexes;
79 				}
80 				// returns true if no positions left
81 				bool popFront() {
82 					size_t x, y, z;
83 					while(true) {
84 						if (index == numIndexes)
85 							return true;
86 
87 						x = index % sideSize;
88 						y = (index / sideSizeSqr) % sideSize;
89 						z = (index / sideSize) % sideSize;
90 						++index;
91 
92 						if (x == 0 || y == 0 || z == 0 ||
93 							x == sideMax || y == sideMax || z == sideMax)
94 							break;
95 					}
96 					position = ivec3(x, y, z) + observerPosition - ivec3(currentRing, currentRing, currentRing);
97 					//infof("popFront %s %s index %s", position, ivec3(x, y, z), index-1);
98 					return false;
99 				}
100 
101 				while (true) {
102 					bool stop = empty();
103 					if (stop) {// ring end
104 						//infof("Ring %s loaded for C:%s", info.numObservedRings, info.clientId);
105 						++info.numObservedRings;
106 						info.currentChunkIndex = 0;
107 						break;
108 					}
109 					popFront();
110 
111 					bool added = addChunkObserver(ChunkWorldPos(position), info.clientId);
112 
113 					if (added) {
114 						//infof("Add %s", position);
115 
116 						--chunksToLoadClient;
117 						++chunksObserved;
118 
119 						if (loadQueueSpaceAvaliable() == 0)
120 							break infinite_loop;
121 						if (chunksToLoadClient == 0)
122 							break;
123 					}
124 				}
125 			}
126 			// nothing to update
127 			if (chunksObserved == 0)
128 				break infinite_loop;
129 			//else
130 			//	infof("Observed %s chunks", chunksObserved);
131 		}
132 
133 		foreach(info; infos) {
134 			viewInfos[info.clientId] = info;
135 		}
136 	};
137 
138 	ClientId[] getChunkObservers(ChunkWorldPos cwp) {
139 		if (auto observers = cwp in chunkObservers)
140 			return observers.clients;
141 		else
142 			return null;
143 	}
144 
145 	void addServerObserver(ChunkWorldPos cwp) {
146 		auto list = chunkObservers.get(cwp, ChunkObservers.init);
147 		++list.numServerObservers;
148 		changeChunkNumObservers(cwp, list.numObservers);
149 		chunkObservers[cwp] = list;
150 	}
151 
152 	void removeServerObserver(ChunkWorldPos cwp) {
153 		auto list = chunkObservers.get(cwp, ChunkObservers.init);
154 		--list.numServerObservers;
155 		changeChunkNumObservers(cwp, list.numObservers);
156 		if (list.empty)
157 			chunkObservers.remove(cwp);
158 		else
159 			chunkObservers[cwp] = list;
160 	}
161 
162 	void removeObserver(ClientId clientId) {
163 		if (clientId in viewInfos) {
164 			changeObserverBox(clientId, ChunkWorldPos.init, 0);
165 			viewInfos.remove(clientId);
166 		}
167 		else
168 			warningf("removing observer %s, that was not added", clientId);
169 	}
170 
171 	WorldBox getObserverBox(ClientId clientId) {
172 		ViewInfo info = viewInfos.get(clientId, ViewInfo.init);
173 		return info.viewBox;
174 	}
175 
176 	void changeObserverBox(ClientId clientId, ChunkWorldPos observerPosition, int viewRadius) {
177 		ViewInfo info = viewInfos.get(clientId, ViewInfo.init);
178 
179 		WorldBox oldBox = info.viewBox;
180 		WorldBox newBox = calcBox(observerPosition, viewRadius);
181 
182 		if (newBox == oldBox)
183 			return;
184 
185 		info = ViewInfo(clientId, newBox, observerPosition, viewRadius);
186 
187 		//infof("oldV %s newV %s", oldBox, newBox);
188 		TrisectResult tsect = trisect4d(oldBox, newBox);
189 
190 		// remove observer
191 		foreach(a; tsect.aPositions) {
192 			removeChunkObserver(ChunkWorldPos(a, oldBox.dimention), clientId);
193 			//infof("Rem %s", a);
194 		}
195 
196 		// add observer
197 		foreach(b; tsect.bPositions) {
198 			addChunkObserver(ChunkWorldPos(b, newBox.dimention), clientId);
199 		}
200 
201 		if (newBox.empty)
202 			viewInfos.remove(clientId);
203 		else
204 			viewInfos[clientId] = info;
205 	}
206 
207 	private bool addChunkObserver(ChunkWorldPos cwp, ClientId clientId) {
208 		auto list = chunkObservers.get(cwp, ChunkObservers.init);
209 		if (list.add(clientId)) {
210 			changeChunkNumObservers(cwp, list.numObservers);
211 			chunkObserverAdded(cwp, clientId);
212 			chunkObservers[cwp] = list;
213 			return true;
214 		}
215 		return false;
216 	}
217 
218 	private void removeChunkObserver(ChunkWorldPos cwp, ClientId clientId) {
219 		auto list = chunkObservers.get(cwp, ChunkObservers.init);
220 		bool removed = list.remove(clientId);
221 		if (removed)
222 			changeChunkNumObservers(cwp, list.numObservers);
223 		if (list.empty)
224 			chunkObservers.remove(cwp);
225 		else
226 			chunkObservers[cwp] = list;
227 	}
228 }
229 
230 // Describes observers for a single chunk
231 private struct ChunkObservers {
232 	import std.algorithm : canFind, countUntil;
233 
234 	// clients observing this chunk
235 	private ClientId[] _clients;
236 	// Each client can observe a chunk multiple times via multiple boxes.
237 	private size_t[] numObservations;
238 	// ref counts for keeping chunk loaded
239 	size_t numServerObservers;
240 
241 	ClientId[] clients() @property {
242 		return _clients;
243 	}
244 
245 	bool empty() @property const {
246 		return numObservers == 0;
247 	}
248 
249 	size_t numObservers() @property const {
250 		return _clients.length + numServerObservers;
251 	}
252 
253 	bool contains(ClientId clientId) const {
254 		return canFind(_clients, clientId);
255 	}
256 
257 	// returns true if clientId was not in clients already
258 	bool add(ClientId clientId)	{
259 		auto index = countUntil(_clients, clientId);
260 		if (index == -1) {
261 			_clients ~= clientId;
262 			numObservations ~= 1;
263 			return true;
264 		} else {
265 			++numObservations[index];
266 			return false;
267 		}
268 	}
269 
270 	// returns true if clientId is no longer in list (has zero observations)
271 	bool remove(ClientId clientId)
272 	{
273 		auto index = countUntil(_clients, clientId);
274 		if (index == -1)
275 		{
276 			return false;
277 		}
278 		else
279 		{
280 			--numObservations[index];
281 			if (numObservations[index] == 0)
282 			{
283 				numObservations[index] = numObservations[$-1];
284 				numObservations = numObservations[0..$-1];
285 				_clients[index] = _clients[$-1];
286 				_clients = _clients[0..$-1];
287 
288 				return true;
289 			}
290 			else
291 			{
292 				return false;
293 			}
294 		}
295 	}
296 }