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.chunk.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,
15 	trisect4d, calcBox, shiftAndClampBoxByBorders;
16 
17 
18 // Manages lists of observers per chunk
19 final class ChunkObserverManager {
20 	void delegate(ChunkWorldPos) loadChunkHandler;
21 	void delegate(ChunkWorldPos) unloadChunkHandler;
22 	void delegate(ChunkWorldPos, SessionId) chunkObserverAddedHandler;
23 
24 	private ChunkObservers[ChunkWorldPos] chunkObservers;
25 	ViewBoxes viewBoxes;
26 
27 	void update() {
28 
29 	}
30 
31 	SessionId[] getChunkObservers(ChunkWorldPos cwp) {
32 		return chunkObservers.get(cwp, ChunkObservers.init).clients;
33 	}
34 
35 	void addServerObserverBox(WorldBox box, Box dimBorders) {
36 		WorldBox boundedBox = shiftAndClampBoxByBorders(box, dimBorders);
37 		foreach(ChunkWorldPos cwp; boundedBox)
38 			addServerObserver(cwp);
39 	}
40 
41 	void removeServerObserverBox(WorldBox box, Box dimBorders) {
42 		WorldBox boundedBox = shiftAndClampBoxByBorders(box, dimBorders);
43 		foreach(ChunkWorldPos cwp; boundedBox)
44 			removeServerObserver(cwp);
45 	}
46 
47 	void addServerObserver(ChunkWorldPos cwp) {
48 		auto list = chunkObservers.get(cwp, ChunkObservers.init);
49 		++list.numServerObservers;
50 		onChunkNumObserversChanged(cwp, list.numObservers);
51 		chunkObservers[cwp] = list;
52 	}
53 
54 	void removeServerObserver(ChunkWorldPos cwp) {
55 		auto list = chunkObservers.get(cwp, ChunkObservers.init);
56 		--list.numServerObservers;
57 		onChunkNumObserversChanged(cwp, list.numObservers);
58 		if (list.empty)
59 			chunkObservers.remove(cwp);
60 		else
61 			chunkObservers[cwp] = list;
62 	}
63 
64 	void removeObserver(SessionId sessionId) {
65 		changeObserverBox(sessionId, WorldBox());
66 	}
67 
68 	void changeObserverBox(SessionId sessionId, ChunkWorldPos observerPosition, int viewRadius, Box dimBorders) {
69 		WorldBox newBox = calcBox(observerPosition, viewRadius);
70 		WorldBox boundedBox = shiftAndClampBoxByBorders(newBox, dimBorders);
71 		changeObserverBox(sessionId, boundedBox);
72 	}
73 
74 	void changeObserverBox(SessionId sessionId, WorldBox newBox) {
75 		WorldBox oldBox = viewBoxes[sessionId];
76 
77 		if (newBox == oldBox)
78 			return;
79 
80 		TrisectResult tsect = trisect4d(oldBox, newBox);
81 
82 		// remove observer
83 		foreach(a; tsect.aPositions) {
84 			removeChunkObserver(ChunkWorldPos(a, oldBox.dimension), sessionId);
85 		}
86 
87 		// add observer
88 		foreach(b; tsect.bPositions) {
89 			addChunkObserver(ChunkWorldPos(b, newBox.dimension), sessionId);
90 		}
91 
92 		viewBoxes[sessionId] = newBox;
93 	}
94 
95 	private bool addChunkObserver(ChunkWorldPos cwp, SessionId sessionId) {
96 		auto list = chunkObservers.get(cwp, ChunkObservers.init);
97 		if (list.add(sessionId)) {
98 			onChunkNumObserversChanged(cwp, list.numObservers);
99 			chunkObserverAddedHandler(cwp, sessionId);
100 			chunkObservers[cwp] = list;
101 			return true;
102 		}
103 		return false;
104 	}
105 
106 	private void removeChunkObserver(ChunkWorldPos cwp, SessionId sessionId) {
107 		auto list = chunkObservers.get(cwp, ChunkObservers.init);
108 		bool removed = list.remove(sessionId);
109 		if (removed)
110 			onChunkNumObserversChanged(cwp, list.numObservers);
111 		if (list.empty)
112 			chunkObservers.remove(cwp);
113 		else
114 			chunkObservers[cwp] = list;
115 	}
116 
117 	// Here comes sum of all internal and external chunk users which results in loading or unloading of specific chunk.
118 	private void onChunkNumObserversChanged(ChunkWorldPos cwp, size_t numObservers) {
119 		if (numObservers > 0) {
120 			loadChunkHandler(cwp);
121 		} else {
122 			unloadChunkHandler(cwp);
123 		}
124 	}
125 }
126 
127 private struct ViewBoxes
128 {
129 	private WorldBox[SessionId] viewInfos;
130 
131 	WorldBox opIndex(SessionId sessionId) {
132 		return viewInfos.get(sessionId, WorldBox.init);
133 	}
134 
135 	void opIndexAssign(WorldBox newBox, SessionId sessionId) {
136 		if (newBox.empty)
137 			viewInfos.remove(sessionId);
138 		else
139 			viewInfos[sessionId] = newBox;
140 	}
141 }
142 
143 // Describes observers for a single chunk
144 private struct ChunkObservers {
145 	import std.algorithm : canFind, countUntil;
146 
147 	// clients observing this chunk
148 	private SessionId[] _clients;
149 	// Each client can observe a chunk multiple times via multiple boxes.
150 	private size_t[] numObservations;
151 	// ref counts for keeping chunk loaded
152 	size_t numServerObservers;
153 
154 	SessionId[] clients() @property {
155 		return _clients;
156 	}
157 
158 	bool empty() @property const {
159 		return numObservers == 0;
160 	}
161 
162 	size_t numObservers() @property const {
163 		return _clients.length + numServerObservers;
164 	}
165 
166 	bool contains(SessionId sessionId) const {
167 		return canFind(_clients, sessionId);
168 	}
169 
170 	// returns true if sessionId was not in clients already
171 	bool add(SessionId sessionId)	{
172 		auto index = countUntil(_clients, sessionId);
173 		if (index == -1) {
174 			_clients ~= sessionId;
175 			numObservations ~= 1;
176 			return true;
177 		} else {
178 			++numObservations[index];
179 			return false;
180 		}
181 	}
182 
183 	// returns true if sessionId is no longer in list (has zero observations)
184 	bool remove(SessionId sessionId)
185 	{
186 		auto index = countUntil(_clients, sessionId);
187 		if (index == -1)
188 		{
189 			return false;
190 		}
191 		else
192 		{
193 			--numObservations[index];
194 			if (numObservations[index] == 0)
195 			{
196 				numObservations[index] = numObservations[$-1];
197 				numObservations = numObservations[0..$-1];
198 				_clients[index] = _clients[$-1];
199 				_clients = _clients[0..$-1];
200 
201 				return true;
202 			}
203 			else
204 			{
205 				return false;
206 			}
207 		}
208 	}
209 }