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 }