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 server; 7 8 import std.experimental.logger; 9 import std.array; 10 import std.algorithm; 11 import std.exception; 12 import std.range; 13 import std.math; 14 import std.conv; 15 import std.string; 16 17 import storage; 18 import chunkmanager; 19 20 alias Timestamp = uint; 21 alias BlockId = ubyte; 22 alias ClientId = uint; 23 24 // 1D version of engine for test 25 enum CHUNK_SIZE = 16; 26 27 struct HashSet(K) { 28 private void[0][K] set; 29 30 void put()(auto ref K key) { 31 set[key] = (void[0]).init; 32 } 33 34 bool remove()(auto ref K key) { 35 return set.remove(key); 36 } 37 38 size_t length() const @property { 39 return set.length; 40 } 41 42 @property bool empty() const { 43 return set.length == 0; 44 } 45 46 bool opCast(T: bool)() const { 47 return !empty; 48 } 49 50 bool opBinaryRight(string op)(auto ref K key) const if(op == "in") { 51 return cast(bool)(key in set); 52 } 53 54 void clear() { 55 set = null; 56 } 57 58 auto items() @property { 59 return set.byKey; 60 } 61 } 62 63 struct BlockWorldPos { 64 this(int blockWorldPos){ 65 pos = blockWorldPos; 66 } 67 this(float blockWorldPos) { 68 pos = cast(int)blockWorldPos; 69 } 70 int pos; 71 string toString() @safe {return to!string(pos);} 72 } 73 74 struct BlockChunkPos { 75 this(BlockWorldPos blockWorldPos) { 76 int ipos = blockWorldPos.pos % CHUNK_SIZE; 77 if (ipos < 0) ipos += CHUNK_SIZE; 78 pos = cast(ubyte)ipos; 79 } 80 this(ubyte blockChunkPos) { 81 pos = blockChunkPos; 82 } 83 ubyte pos; 84 string toString() @safe {return to!string(pos);} 85 } 86 87 // Position of chunk in world space. -int.max..int.max 88 struct ChunkWorldPos { 89 this(BlockWorldPos blockWorldPos) { 90 pos = cast(int)floor(cast(float)blockWorldPos.pos / CHUNK_SIZE); 91 } 92 this(int chunkWorldPos) { 93 pos = chunkWorldPos; 94 } 95 int pos; 96 string toString() @safe {return to!string(pos);} 97 } 98 99 struct BlockChange { 100 BlockChunkPos index; 101 BlockId blockId; 102 } 103 104 struct ChunkDataSnapshot { 105 BlockId[] blocks; 106 Timestamp timestamp; 107 uint numUsers; 108 } 109 110 struct Client { 111 string name; 112 ClientId id; 113 Volume1D viewVolume; 114 void sendChunk(ChunkWorldPos pos, const ubyte[] chunkData){} 115 void sendChanges(ChunkWorldPos cwp, BlockChange[] changes){ 116 infof("changes @%s %s", cwp, changes); 117 } 118 void delegate(Server* server) sendDataToServer; 119 } 120 121 struct ChunkFreeList { 122 BlockId[][] items; 123 size_t numItems; 124 125 BlockId[] allocate() { 126 if (numItems > 0) { 127 --numItems; 128 return items[numItems]; 129 } else { 130 return new BlockId[CHUNK_SIZE]; 131 } 132 } 133 134 void deallocate(BlockId[] blocks) { 135 if (items.length < numItems) { 136 items[numItems] = blocks; 137 } else { 138 items.reserve(32); 139 items ~= blocks; 140 } 141 } 142 } 143 144 struct Set(T) { 145 T[] items; 146 alias items this; 147 148 bool contains(T item) { 149 return canFind(items, item); 150 } 151 152 // returns true if already has one 153 bool put(T item) { 154 if (!contains(item)) { 155 items ~= item; 156 return false; 157 } else 158 return true; 159 } 160 161 void remove(T item) { 162 T[] items; 163 items = std.algorithm.remove!(a => a == item, SwapStrategy.unstable)(items); 164 } 165 } 166 167 final class Server { 168 Timestamp currentTime; 169 ChunkInMemoryStorage inmemStorage; 170 ChunkManager chunkManager; 171 ChunkObserverManager chunkObserverManager; 172 WorldAccess worldAccess; 173 174 Client*[ClientId] clientMap; 175 176 this() 177 { 178 chunkManager = new ChunkManager(); 179 chunkManager.onChunkLoadedHandlers ~= &onChunkLoaded; 180 chunkManager.chunkChangesHandlers ~= &sendChanges; 181 chunkManager.loadChunkHandler = &inmemStorage.loadChunk; 182 chunkManager.saveChunkHandler = &inmemStorage.saveChunk; 183 inmemStorage.onChunkLoadedHandlers ~= &chunkManager.onSnapshotLoaded; 184 inmemStorage.onChunkSavedHandlers ~= &chunkManager.onSnapshotSaved; 185 chunkObserverManager = new ChunkObserverManager(); 186 chunkObserverManager.changeChunkNumObservers = &chunkManager.setExternalChunkUsers; 187 worldAccess = new WorldAccess(&chunkManager); 188 } 189 190 void preUpdate() { 191 // Advance time 192 ++currentTime; 193 inmemStorage.update(); 194 } 195 196 void update() { 197 // logic. modify world, modify observers 198 } 199 200 void postUpdate() { 201 chunkManager.commitSnapshots(currentTime); 202 chunkManager.sendChanges(); 203 204 // do regular save 205 // chunkManager.save(currentTime); 206 } 207 208 void save() { 209 chunkManager.save(); 210 } 211 212 void sendChanges(ChunkWorldPos cwp, BlockChange[] changes) { 213 foreach(clientId; chunkObserverManager.getChunkObservers(cwp)) { 214 clientMap[clientId].sendChanges(cwp, changes); 215 } 216 } 217 218 void onChunkLoaded(ChunkWorldPos cwp, ChunkDataSnapshot snap) { 219 infof("LOAD @%s", cwp); 220 foreach(clientId; chunkObserverManager.getChunkObservers(cwp)) { 221 clientMap[clientId].sendChunk(cwp, snap.blocks); 222 } 223 } 224 225 void onClientConnected(Client* client) { 226 infof("CONN '%s'", client.name); 227 clientMap[client.id] = client; 228 chunkObserverManager.addObserver(client.id, client.viewVolume); 229 } 230 231 void onClientDisconnected(Client* client) { 232 infof("DISC '%s'", client.name); 233 chunkObserverManager.removeObserver(client.id); 234 } 235 236 bool setBlock(BlockWorldPos bwp, BlockId blockId) { 237 return worldAccess.setBlock(bwp, blockId); 238 } 239 240 BlockId getBlock(BlockWorldPos bwp) { 241 return worldAccess.getBlock(bwp); 242 } 243 }