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