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.storage.chunkobservermanager;
7 
8 import std.experimental.logger;
9 import dlib.math.vector : ivec3;
10 import netlib.connection : ClientId;
11 import voxelman.storage.coordinates : ChunkWorldPos;
12 import voxelman.storage.volume : Volume, TrisectResult, trisect, calcVolume;
13 
14 enum chunkPackLoadSize = 200;
15 
16 struct ViewInfo
17 {
18 	ClientId clientId;
19 	Volume viewVolume;
20 	//ivec3 viewRadius;
21 	ivec3 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 		size_t chunksToLoad = loadQueueSpaceAvaliable();
48 		//infof("queue space %s", chunksToLoad);
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 				if (currentRing > info.viewRadius)
64 					break;
65 
66 				//infof("For infos C:%s VR:%s OR:%s CI:%s", info.clientId, info.viewRadius,
67 				//	info.numObservedRings, info.currentChunkIndex);
68 
69 				size_t chunksToLoadClient = chunkPackLoadSize;
70 
71 				immutable ivec3 observerPosition = info.observerPosition;
72 				immutable size_t sideSize = currentRing * 2 + 1;
73 				immutable size_t sideMax = sideSize - 1;
74 				immutable size_t sideSizeSqr = sideSize * sideSize;
75 				immutable size_t numIndexes = sideSizeSqr * sideSize;
76 				//infof("numIndexes %s sideSize %s", numIndexes, sideSize);
77 
78 				size_t index = info.currentChunkIndex;
79 				ivec3 position;
80 				bool empty() {
81 					return index == numIndexes;
82 				}
83 				// returns true if no positions left
84 				bool popFront() {
85 					size_t x, y, z;
86 					while(true) {
87 						if (index == numIndexes)
88 							return true;
89 
90 						x = index % sideSize;
91 						y = (index / sideSizeSqr) % sideSize;
92 						z = (index / sideSize) % sideSize;
93 						++index;
94 
95 						if (x == 0 || y == 0 || z == 0 ||
96 							x == sideMax || y == sideMax || z == sideMax)
97 							break;
98 					}
99 					position = ivec3(x, y, z) + observerPosition - ivec3(currentRing, currentRing, currentRing);
100 					//infof("popFront %s %s index %s", position, ivec3(x, y, z), index-1);
101 					return false;
102 				}
103 
104 				while (true) {
105 					bool stop = empty();
106 					if (stop) {// ring end
107 						//infof("Ring %s loaded for C:%s", info.numObservedRings, info.clientId);
108 						++info.numObservedRings;
109 						info.currentChunkIndex = 0;
110 						break;
111 					}
112 					popFront();
113 
114 					bool added = addChunkObserver(ChunkWorldPos(position), info.clientId);
115 
116 					if (added) {
117 						//infof("Add %s", position);
118 						--chunksToLoad;
119 						--chunksToLoadClient;
120 						++chunksObserved;
121 
122 						if (chunksToLoad == 0)
123 							break infinite_loop;
124 						if (chunksToLoadClient == 0)
125 							break;
126 					}
127 				}
128 			}
129 			// nothing to update
130 			if (chunksObserved == 0)
131 				break infinite_loop;
132 			//else
133 			//	infof("Observed %s chunks", chunksObserved);
134 		}
135 
136 		foreach(info; infos) {
137 			viewInfos[info.clientId] = info;
138 		}
139 	};
140 
141 	ClientId[] getChunkObservers(ChunkWorldPos cwp) {
142 		if (auto observers = cwp in chunkObservers)
143 			return observers.clients;
144 		else
145 			return null;
146 	}
147 
148 	void addServerObserver(ChunkWorldPos cwp) {
149 		auto list = chunkObservers.get(cwp, ChunkObservers.init);
150 		++list.numServerObservers;
151 		changeChunkNumObservers(cwp, list.numObservers);
152 		chunkObservers[cwp] = list;
153 	}
154 
155 	void removeServerObserver(ChunkWorldPos cwp) {
156 		auto list = chunkObservers.get(cwp, ChunkObservers.init);
157 		--list.numServerObservers;
158 		changeChunkNumObservers(cwp, list.numObservers);
159 		if (list.empty)
160 			chunkObservers.remove(cwp);
161 		else
162 			chunkObservers[cwp] = list;
163 	}
164 
165 	void removeObserver(ClientId clientId) {
166 		if (clientId in viewInfos) {
167 			changeObserverVolume(clientId, ChunkWorldPos.init, 0);
168 			viewInfos.remove(clientId);
169 		}
170 		else
171 			warningf("removing observer %s, that was not added", clientId);
172 	}
173 
174 	void changeObserverVolume(ClientId clientId, ChunkWorldPos observerPosition, int viewRadius) {
175 		ViewInfo info = viewInfos.get(clientId, ViewInfo.init);
176 
177 		Volume oldVolume = info.viewVolume;
178 		immutable int size = viewRadius*2 + 1;
179 		Volume newVolume = calcVolume(observerPosition, viewRadius);
180 
181 		if (newVolume == oldVolume)
182 			return;
183 
184 		info = ViewInfo(clientId, newVolume, observerPosition.vector, viewRadius);
185 
186 		//infof("oldV %s newV %s", oldVolume, newVolume);
187 		TrisectResult tsect = trisect(oldVolume, newVolume);
188 
189 		// remove observer
190 		foreach(a; tsect.aPositions) {
191 			removeChunkObserver(ChunkWorldPos(a), clientId);
192 			//infof("Rem %s", a);
193 		}
194 
195 		// add observer
196 		foreach(b; tsect.bPositions) {
197 			addChunkObserver(ChunkWorldPos(b), clientId);
198 		}
199 
200 		if (newVolume.empty)
201 			viewInfos.remove(clientId);
202 		else
203 			viewInfos[clientId] = info;
204 	}
205 
206 	private bool addChunkObserver(ChunkWorldPos cwp, ClientId clientId) {
207 		auto list = chunkObservers.get(cwp, ChunkObservers.init);
208 		if (list.add(clientId)) {
209 			changeChunkNumObservers(cwp, list.numObservers);
210 			chunkObserverAdded(cwp, clientId);
211 			chunkObservers[cwp] = list;
212 			return true;
213 		}
214 		return false;
215 	}
216 
217 	private void removeChunkObserver(ChunkWorldPos cwp, ClientId clientId) {
218 		auto list = chunkObservers.get(cwp, ChunkObservers.init);
219 		bool removed = list.remove(clientId);
220 		if (removed)
221 			changeChunkNumObservers(cwp, list.numObservers);
222 		if (list.empty)
223 			chunkObservers.remove(cwp);
224 		else
225 			chunkObservers[cwp] = list;
226 	}
227 }
228 
229 // Describes observers for a single chunk
230 private struct ChunkObservers {
231 	// clients observing this chunk
232 	private ClientId[] _clients;
233 	// ref counts for keeping chunk loaded
234 	size_t numServerObservers;
235 
236 	ClientId[] clients() @property {
237 		return _clients;
238 	}
239 
240 	bool empty() @property const {
241 		return numObservers == 0;
242 	}
243 
244 	size_t numObservers() @property const {
245 		return _clients.length + numServerObservers;
246 	}
247 
248 	bool contains(ClientId clientId) const {
249 		import std.algorithm : canFind;
250 		return canFind(_clients, clientId);
251 	}
252 
253 	bool add(ClientId clientId) {
254 		if (!contains(clientId)) {
255 			_clients ~= clientId;
256 			return true;
257 		} else
258 			return false;
259 	}
260 
261 	bool remove(ClientId clientId) {
262 		import std.algorithm : remove, SwapStrategy;
263 		size_t startLength = _clients.length;
264 		_clients = remove!((a) => a == clientId, SwapStrategy.unstable)(_clients);
265 		return (startLength - _clients.length) > 0;
266 	}
267 }