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 }