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 }