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 storage; 7 8 import std.algorithm : map, joiner; 9 import std.experimental.logger; 10 import std.string; 11 import std.typecons : Nullable; 12 import std.conv : to; 13 import server; 14 import chunkmanager : ChunkManager; 15 import voxelman.utils.queue; 16 import voxelman.storage.utils; 17 18 // Storage made to test delays in read and write. 19 struct ChunkInMemoryStorage { 20 void delegate(ChunkWorldPos, ChunkDataSnapshot)[] onChunkLoadedHandlers; 21 void delegate(ChunkWorldPos, ChunkDataSnapshot)[] onChunkSavedHandlers; 22 23 private static struct SaveItem { 24 ChunkWorldPos cwp; 25 ChunkDataSnapshot snapshot; 26 } 27 private static struct LoadItem { 28 ChunkWorldPos cwp; 29 BlockId[] blockBuffer; 30 } 31 private ChunkDataSnapshot[ChunkWorldPos] snapshots; 32 private Queue!LoadItem snapshotsToLoad; 33 private Queue!SaveItem snapshotsToSave; 34 35 // load one chunk per update 36 void update() { 37 auto toLoad = snapshotsToLoad.valueRange; 38 if (!toLoad.empty) { 39 LoadItem loadItem = toLoad.front; 40 toLoad.popFront(); 41 ChunkDataSnapshot snap = snapshots.get(loadItem.cwp, ChunkDataSnapshot()); 42 if (snap.blocks) { 43 loadItem.blockBuffer[] = snap.blocks; 44 } 45 snap.blocks = loadItem.blockBuffer; 46 47 foreach(handler; onChunkLoadedHandlers) 48 handler(loadItem.cwp, snap); 49 } 50 51 auto toSave = snapshotsToSave.valueRange; 52 if (!toSave.empty) { 53 SaveItem saveItem = toSave.front; 54 toSave.popFront(); 55 ChunkDataSnapshot* snap = saveItem.cwp in snapshots; 56 if (snap) { 57 snap.blocks[] = saveItem.snapshot.blocks; 58 saveItem.snapshot.blocks = snap.blocks; 59 } else { 60 saveItem.snapshot.blocks = saveItem.snapshot.blocks.dup; 61 } 62 ChunkWorldPos cwp = saveItem.cwp; 63 snapshots[cwp] = saveItem.snapshot; 64 foreach(handler; onChunkSavedHandlers) 65 handler(cwp, saveItem.snapshot); 66 } 67 } 68 69 // duplicate queries aren't checked. 70 void loadChunk(ChunkWorldPos cwp, BlockId[] blockBuffer) { 71 snapshotsToLoad.put(LoadItem(cwp, blockBuffer)); 72 } 73 74 void saveChunk(ChunkWorldPos cwp, ChunkDataSnapshot snapshot) { 75 snapshotsToSave.put(SaveItem(cwp, snapshot)); 76 } 77 } 78 79 final class WorldAccess { 80 private ChunkManager* chunkManager; 81 82 this(ChunkManager* chunkManager) { 83 this.chunkManager = chunkManager; 84 } 85 86 bool setBlock(BlockWorldPos bwp, BlockId blockId) { 87 auto blockPos = BlockChunkPos(bwp); 88 auto chunkPos = ChunkWorldPos(bwp); 89 BlockId[] blocks = chunkManager.getWriteBuffer(chunkPos); 90 if (blocks is null) 91 return false; 92 infof(" SETB @%s", chunkPos); 93 blocks[blockPos.pos] = blockId; 94 95 import std.range : only; 96 chunkManager.onBlockChanges(chunkPos, only(BlockChange(blockPos, blockId))); 97 return true; 98 } 99 100 BlockId getBlock(BlockWorldPos bwp) { 101 auto blockPos = BlockChunkPos(bwp); 102 auto chunkPos = ChunkWorldPos(bwp); 103 auto snap = chunkManager.getChunkSnapshot(chunkPos); 104 if (!snap.isNull) { 105 return snap.blocks[blockPos.pos]; 106 } 107 return 0; 108 } 109 } 110 111 struct Volume1D { 112 int position; 113 int size; 114 115 bool empty() @property const { 116 return size == 0; 117 } 118 119 bool contains(int otherPosition) const { 120 if (otherPosition < position || otherPosition >= position + size) return false; 121 return true; 122 } 123 124 bool opEquals()(auto ref const Volume1D other) const { 125 return position == other.position && size == other.size; 126 } 127 128 // generates all positions within volume. 129 auto positions() @property const { 130 import std.range : iota; 131 return iota(position, position + size); 132 } 133 } 134 135 // Manages lists of observers per chunk 136 final class ChunkObserverManager { 137 void delegate(ChunkWorldPos, size_t numObservers) changeChunkNumObservers; 138 139 private ChunkObservers[ChunkWorldPos] chunkObservers; 140 private Volume1D[ClientId] viewVolumes; 141 142 ClientId[] getChunkObservers(ChunkWorldPos cwp) { 143 if (auto observers = cwp in chunkObservers) 144 return observers.clients; 145 else 146 return null; 147 } 148 149 void addServerObserver(ChunkWorldPos cwp) { 150 auto list = chunkObservers.get(cwp, ChunkObservers.init); 151 ++list.numServerObservers; 152 changeChunkNumObservers(cwp, list.numObservers); 153 chunkObservers[cwp] = list; 154 } 155 156 void removeServerObserver(ChunkWorldPos cwp) { 157 auto list = chunkObservers.get(cwp, ChunkObservers.init); 158 --list.numServerObservers; 159 changeChunkNumObservers(cwp, list.numObservers); 160 if (list.empty) 161 chunkObservers.remove(cwp); 162 else 163 chunkObservers[cwp] = list; 164 } 165 166 void addObserver(ClientId clientId, Volume1D volume) { 167 assert(clientId !in viewVolumes, "Client is already added"); 168 changeObserverVolume(clientId, volume); 169 } 170 171 void removeObserver(ClientId clientId) { 172 if (clientId in viewVolumes) { 173 changeObserverVolume(clientId, Volume1D.init); 174 viewVolumes.remove(clientId); 175 } 176 else 177 warningf("removing observer %s, that was not added", clientId); 178 } 179 180 void changeObserverVolume(ClientId clientId, Volume1D newVolume) { 181 Volume1D oldVolume = viewVolumes.get(clientId, Volume1D.init); 182 viewVolumes[clientId] = newVolume; 183 184 TrisectAxisResult tsect = trisectAxis(oldVolume.position, oldVolume.position + oldVolume.size, 185 newVolume.position, newVolume.position + newVolume.size); 186 187 // remove observer 188 foreach(a; tsect.aranges[0..tsect.numRangesA] 189 .map!(a => Volume1D(a.start, a.length).positions).joiner) { 190 removeChunkObserver(ChunkWorldPos(a), clientId); 191 } 192 193 // add observer 194 foreach(b; tsect.branges[0..tsect.numRangesB] 195 .map!(b => Volume1D(b.start, b.length).positions).joiner) { 196 addChunkObserver(ChunkWorldPos(b), clientId); 197 } 198 } 199 200 private void addChunkObserver(ChunkWorldPos cwp, ClientId clientId) { 201 auto list = chunkObservers.get(cwp, ChunkObservers.init); 202 list.add(clientId); 203 changeChunkNumObservers(cwp, list.numObservers); 204 chunkObservers[cwp] = list; 205 infof(" OBSV @%s by '%s'", cwp, clientId); 206 } 207 208 private void removeChunkObserver(ChunkWorldPos cwp, ClientId clientId) { 209 auto list = chunkObservers.get(cwp, ChunkObservers.init); 210 list.remove(clientId); 211 changeChunkNumObservers(cwp, list.numObservers); 212 if (list.empty) 213 chunkObservers.remove(cwp); 214 else 215 chunkObservers[cwp] = list; 216 infof(" UNOB @%s by '%s'", cwp, clientId); 217 } 218 } 219 220 // Describes observers for a single chunk 221 struct ChunkObservers { 222 // clients observing this chunk 223 private ClientId[] _clients; 224 // ref counts for keeping chunk loaded 225 size_t numServerObservers; 226 227 ClientId[] clients() @property { 228 return _clients; 229 } 230 231 bool empty() @property const { 232 return numObservers == 0; 233 } 234 235 size_t numObservers() @property const { 236 return _clients.length + numServerObservers; 237 } 238 239 void add(ClientId clientId) { 240 _clients ~= clientId; 241 } 242 243 void remove(ClientId clientId) { 244 import std.algorithm : remove, SwapStrategy; 245 _clients = remove!((a) => a == clientId, SwapStrategy.unstable)(_clients); 246 } 247 }