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.serverworld; 7 8 import std.array : empty; 9 import core.atomic : atomicStore, atomicLoad; 10 11 import cbor; 12 import netlib; 13 import pluginlib; 14 15 import voxelman.container.buffer; 16 import voxelman.geometry.box; 17 import voxelman.log; 18 import voxelman.math; 19 20 import voxelman.core.config; 21 import voxelman.core.events; 22 import voxelman.net.events; 23 import voxelman.utils.compression; 24 25 import voxelman.input.keybindingmanager; 26 import voxelman.config.configmanager : ConfigOption, ConfigManager; 27 import voxelman.eventdispatcher.plugin : EventDispatcherPlugin; 28 import voxelman.net.plugin : NetServerPlugin; 29 import voxelman.session.server; 30 import voxelman.block.plugin; 31 import voxelman.blockentity.plugin; 32 import voxelman.dbg.plugin; 33 34 import voxelman.net.packets; 35 import voxelman.core.packets; 36 37 import voxelman.world.blockentity.blockentityaccess; 38 import voxelman.world.gen.generators; 39 import voxelman.world.storage; 40 import voxelman.world.storage.dimensionobservermanager; 41 42 public import voxelman.world.worlddb : WorldDb; 43 44 enum START_NEW_WORLD = false; 45 46 struct IdMapManagerServer 47 { 48 string[][string] idMaps; 49 void regIdMap(string name, string[] mapItems) 50 { 51 idMaps[name] = mapItems; 52 } 53 } 54 55 struct WorldInfo 56 { 57 string name = DEFAULT_WORLD_NAME; 58 TimestampType simulationTick; 59 ClientDimPos spawnPos; 60 DimensionId spawnDimension; 61 } 62 63 //version = DBG_COMPR; 64 final class ServerWorld : IPlugin 65 { 66 private: 67 EventDispatcherPlugin evDispatcher; 68 NetServerPlugin connection; 69 ClientManager clientMan; 70 BlockPluginServer blockPlugin; 71 BlockEntityServer blockEntityPlugin; 72 73 Debugger dbg; 74 75 ConfigOption numGenWorkersOpt; 76 77 ubyte[] buf; 78 auto dbKey = IoKey("voxelman.world.world_info"); 79 string worldFilename; 80 81 shared bool isSaving; 82 IoManager ioManager; 83 WorldDb worldDb; 84 PluginDataSaver pluginDataSaver; 85 86 public: 87 ChunkManager chunkManager; 88 ChunkProvider chunkProvider; 89 ChunkObserverManager chunkObserverManager; 90 91 DimensionManager dimMan; 92 DimensionObserverManager dimObserverMan; 93 94 ActiveChunks activeChunks; 95 IdMapManagerServer idMapManager; 96 97 WorldInfo worldInfo; 98 WorldAccess worldAccess; 99 BlockEntityAccess entityAccess; 100 101 mixin IdAndSemverFrom!"voxelman.world.plugininfo"; 102 103 override void registerResourceManagers(void delegate(IResourceManager) registerHandler) 104 { 105 ioManager = new IoManager(&loadWorld); 106 registerHandler(ioManager); 107 } 108 109 override void registerResources(IResourceManagerRegistry resmanRegistry) 110 { 111 ConfigManager config = resmanRegistry.getResourceManager!ConfigManager; 112 numGenWorkersOpt = config.registerOption!int("num_workers", 4); 113 ioManager.registerWorldLoadSaveHandlers(&readWorldInfo, &writeWorldInfo); 114 ioManager.registerWorldLoadSaveHandlers(&activeChunks.read, &activeChunks.write); 115 ioManager.registerWorldLoadSaveHandlers(&dimMan.load, &dimMan.save); 116 117 dimMan.generatorMan.factory.registerGenerator!GeneratorFlat; 118 dimMan.generatorMan.factory.registerGenerator!Generator2d; 119 dimMan.generatorMan.factory.registerGenerator!Generator2d3d; 120 121 dbg = resmanRegistry.getResourceManager!Debugger; 122 } 123 124 override void preInit() 125 { 126 pluginDataSaver.stringMap = &ioManager.stringMap; 127 idMapManager.regIdMap("string_map", ioManager.stringMap.strings); 128 129 buf = new ubyte[](1024*64*4); 130 chunkManager = new ChunkManager(); 131 worldAccess = new WorldAccess(chunkManager); 132 entityAccess = new BlockEntityAccess(chunkManager); 133 chunkObserverManager = new ChunkObserverManager(); 134 135 chunkManager.setup(NUM_CHUNK_LAYERS); 136 chunkManager.setLayerInfo(ChunkLayerInfo(BLOCK_METADATA_UNIFORM_FILL_BITS), METADATA_LAYER); 137 chunkManager.isChunkSavingEnabled = true; 138 139 // Component connections 140 chunkManager.startChunkSave = &chunkProvider.startChunkSave; 141 chunkManager.pushLayer = &chunkProvider.pushLayer; 142 chunkManager.endChunkSave = &chunkProvider.endChunkSave; 143 chunkManager.loadChunkHandler = &chunkProvider.loadChunk; 144 chunkManager.cancelLoadChunkHandler = &chunkProvider.cancelLoad; 145 146 chunkProvider.onChunkLoadedHandler = &chunkManager.onSnapshotLoaded; 147 chunkProvider.onChunkSavedHandler = &chunkManager.onSnapshotSaved; 148 chunkProvider.generatorGetter = &dimMan.generatorMan.opIndex; 149 150 chunkObserverManager.changeChunkNumObservers = &chunkManager.setExternalChunkObservers; 151 chunkObserverManager.chunkObserverAdded = &onChunkObserverAdded; 152 chunkObserverManager.loadQueueSpaceAvaliable = &chunkProvider.loadQueueSpaceAvaliable; 153 154 dimObserverMan.dimensionObserverAdded = &onDimensionObserverAdded; 155 156 activeChunks.loadChunk = &chunkObserverManager.addServerObserver; 157 activeChunks.unloadChunk = &chunkObserverManager.removeServerObserver; 158 159 chunkManager.onChunkLoadedHandler = &onChunkLoaded; 160 } 161 162 override void init(IPluginManager pluginman) 163 { 164 blockPlugin = pluginman.getPlugin!BlockPluginServer; 165 clientMan = pluginman.getPlugin!ClientManager; 166 167 evDispatcher = pluginman.getPlugin!EventDispatcherPlugin; 168 evDispatcher.subscribeToEvent(&handlePreUpdateEvent); 169 evDispatcher.subscribeToEvent(&handlePostUpdateEvent); 170 evDispatcher.subscribeToEvent(&handleStopEvent); 171 evDispatcher.subscribeToEvent(&handleClientDisconnected); 172 evDispatcher.subscribeToEvent(&handleSaveEvent); 173 evDispatcher.subscribeToEvent(&handleClientConnectedEvent); 174 175 blockEntityPlugin = pluginman.getPlugin!BlockEntityServer; 176 177 connection = pluginman.getPlugin!NetServerPlugin; 178 connection.registerPacketHandler!FillBlockBoxPacket(&handleFillBlockBoxPacket); 179 connection.registerPacketHandler!PlaceBlockEntityPacket(&handlePlaceBlockEntityPacket); 180 connection.registerPacketHandler!RemoveBlockEntityPacket(&handleRemoveBlockEntityPacket); 181 182 chunkProvider.init(worldDb, numGenWorkersOpt.get!uint, blockPlugin.getBlocks()); 183 worldDb = null; 184 activeChunks.loadActiveChunks(); 185 worldAccess.blockInfos = blockPlugin.getBlocks(); 186 } 187 188 TimestampType currentTimestamp() @property 189 { 190 return worldInfo.simulationTick; 191 } 192 193 void setDimensionBorders(DimensionId dim, Box borders) 194 { 195 DimensionInfo* dimInfo = dimMan.getOrCreate(dim); 196 dimInfo.borders = borders; 197 sendDimensionBorders(dim); 198 } 199 200 private void handleSaveEvent(ref WorldSaveInternalEvent event) 201 { 202 if (!atomicLoad(isSaving)) { 203 atomicStore(isSaving, true); 204 chunkManager.save(); 205 foreach(saveHandler; ioManager.worldSaveHandlers) { 206 saveHandler(pluginDataSaver); 207 } 208 chunkProvider.pushSaveHandler(&worldSaver); 209 } 210 } 211 212 // executed on io thread. Stores values written into pluginDataSaver. 213 private void worldSaver(WorldDb wdb) 214 { 215 foreach(ubyte[16] key, ubyte[] data; pluginDataSaver) { 216 if (data.length == 0) 217 wdb.del(key); 218 wdb.put(key, data); 219 } 220 pluginDataSaver.reset(); 221 atomicStore(isSaving, false); 222 } 223 224 private void loadWorld(string _worldFilename) 225 { 226 worldFilename = _worldFilename; 227 worldDb = new WorldDb; 228 static if (START_NEW_WORLD) 229 { 230 static import std.file; 231 if (std.file.exists(_worldFilename)) 232 { 233 std.file.remove(_worldFilename); 234 } 235 } 236 worldDb.open(_worldFilename); 237 238 worldDb.beginTxn(); 239 scope(exit) worldDb.abortTxn(); 240 241 auto dataLoader = PluginDataLoader(&ioManager.stringMap, worldDb); 242 foreach(loadHandler; ioManager.worldLoadHandlers) { 243 loadHandler(dataLoader); 244 } 245 } 246 247 private void readWorldInfo(ref PluginDataLoader loader) 248 { 249 import std.path : absolutePath, buildNormalizedPath; 250 ubyte[] data = loader.readEntryRaw(dbKey); 251 if (!data.empty) { 252 worldInfo = decodeCborSingleDup!WorldInfo(data); 253 infof("Loading world %s", worldFilename.absolutePath.buildNormalizedPath); 254 } else { 255 infof("Creating world %s", worldFilename.absolutePath.buildNormalizedPath); 256 createWorld(); 257 } 258 } 259 260 void createWorld() 261 { 262 dimMan.generatorMan[0] = new GeneratorFlat; 263 dimMan.generatorMan[1] = new Generator2d; 264 dimMan.generatorMan[2] = new Generator2d3d; 265 } 266 267 private void writeWorldInfo(ref PluginDataSaver saver) 268 { 269 saver.writeEntryEncoded(dbKey, worldInfo); 270 } 271 272 private void handlePreUpdateEvent(ref PreUpdateEvent event) 273 { 274 ++worldInfo.simulationTick; 275 chunkProvider.update(); 276 chunkObserverManager.update(); 277 } 278 279 private void handlePostUpdateEvent(ref PostUpdateEvent event) 280 { 281 chunkManager.commitSnapshots(currentTimestamp); 282 sendChanges(worldAccess.blockChanges); 283 worldAccess.blockChanges = null; 284 285 import voxelman.world.gen.generators; 286 import core.atomic; 287 dbg.setVar("gen cache hits", atomicLoad(cache_hits)); 288 dbg.setVar("gen cache misses", atomicLoad(cache_misses)); 289 dbg.setVar("total loads", chunkProvider.totalReceived); 290 dbg.setVar("canceled loads", chunkProvider.numSuccessfulCancelations); 291 dbg.setVar("wasted loads", chunkProvider.numWastedLoads); 292 } 293 294 private void handleStopEvent(ref GameStopEvent event) 295 { 296 while(atomicLoad(isSaving)) 297 { 298 import core.thread : Thread; 299 Thread.yield(); 300 } 301 chunkProvider.stop(); 302 } 303 304 private void onDimensionObserverAdded(DimensionId dimensionId, SessionId sessionId) 305 { 306 sendDimensionBorders(sessionId, dimensionId); 307 } 308 309 private void onChunkObserverAdded(ChunkWorldPos cwp, SessionId sessionId) 310 { 311 sendChunk(sessionId, cwp); 312 } 313 314 private void handleClientConnectedEvent(ref ClientConnectedEvent event) 315 { 316 foreach(key, idmap; idMapManager.idMaps) 317 { 318 connection.sendTo(event.sessionId, IdMapPacket(key, idmap)); 319 } 320 } 321 322 private void handleClientDisconnected(ref ClientDisconnectedEvent event) 323 { 324 chunkObserverManager.removeObserver(event.sessionId); 325 dimObserverMan.removeObserver(event.sessionId); 326 } 327 328 private void onChunkLoaded(ChunkWorldPos cwp) 329 { 330 sendChunk(chunkObserverManager.getChunkObservers(cwp), cwp); 331 } 332 333 private void sendDimensionBorders(SessionId sessionId, DimensionId dim) 334 { 335 if (auto dimInfo = dimMan[dim]) 336 connection.sendTo(sessionId, DimensionInfoPacket(dim, dimInfo.borders)); 337 } 338 339 private void sendDimensionBorders(DimensionId dim) 340 { 341 static Buffer!SessionId sessionBuffer; 342 if (auto dimInfo = dimMan[dim]) 343 { 344 foreach(sessionId; dimObserverMan.getDimensionObservers(dim)) 345 sessionBuffer.put(sessionId); 346 347 connection.sendTo(sessionBuffer.data, DimensionInfoPacket(dim, dimInfo.borders)); 348 sessionBuffer.clear(); 349 } 350 } 351 352 private void sendChunk(S)(S sessions, ChunkWorldPos cwp) 353 { 354 import voxelman.core.packets : ChunkDataPacket; 355 356 if (!chunkManager.isChunkLoaded(cwp)) return; 357 ChunkLayerData[NUM_CHUNK_LAYERS] layerBuf; 358 size_t compressedSize; 359 360 ubyte numChunkLayers; 361 foreach(ubyte layerId; 0..chunkManager.numLayers) 362 { 363 if (!chunkManager.hasSnapshot(cwp, layerId)) continue; 364 365 auto layer = chunkManager.getChunkSnapshot(cwp, layerId); 366 assert(!layer.isNull); 367 368 version(DBG_COMPR)if (layer.type != StorageType.uniform) 369 { 370 ubyte[] compactBlocks = layer.getArray!ubyte; 371 infof("Send %s %s %s\n(%(%02x%))", cwp, compactBlocks.ptr, compactBlocks.length, cast(ubyte[])compactBlocks); 372 } 373 374 ChunkLayerData bd = toBlockData(layer, layerId); 375 if (layer.type == StorageType.fullArray) 376 { 377 ubyte[] compactBlocks = compressLayerData(layer.getArray!ubyte, buf[compressedSize..$]); 378 compressedSize += compactBlocks.length; 379 bd.blocks = compactBlocks; 380 } 381 layerBuf[numChunkLayers] = bd; 382 383 ++numChunkLayers; 384 } 385 386 connection.sendTo(sessions, ChunkDataPacket(cwp.ivector, layerBuf[0..numChunkLayers])); 387 } 388 389 private void sendChanges(BlockChange[][ChunkWorldPos] changes) 390 { 391 import voxelman.core.packets : MultiblockChangePacket; 392 foreach(pair; changes.byKeyValue) 393 { 394 connection.sendTo( 395 chunkObserverManager.getChunkObservers(pair.key), 396 MultiblockChangePacket(pair.key.ivector, pair.value)); 397 } 398 } 399 400 private void handleFillBlockBoxPacket(ubyte[] packetData, SessionId sessionId) 401 { 402 import voxelman.core.packets : FillBlockBoxPacket; 403 if (clientMan.isSpawned(sessionId)) 404 { 405 auto packet = unpackPacketNoDup!FillBlockBoxPacket(packetData); 406 // TODO send to observers only. 407 worldAccess.fillBox(packet.box, packet.blockId, packet.blockMeta); 408 connection.sendToAll(packet); 409 } 410 } 411 412 private void handlePlaceBlockEntityPacket(ubyte[] packetData, SessionId sessionId) 413 { 414 auto packet = unpackPacket!PlaceBlockEntityPacket(packetData); 415 placeEntity( 416 packet.box, packet.data, 417 worldAccess, entityAccess); 418 419 // TODO send to observers only. 420 connection.sendToAll(packet); 421 } 422 423 private void handleRemoveBlockEntityPacket(ubyte[] packetData, SessionId peer) 424 { 425 auto packet = unpackPacket!RemoveBlockEntityPacket(packetData); 426 WorldBox vol = removeEntity(BlockWorldPos(packet.blockPos), 427 blockEntityPlugin.blockEntityInfos, worldAccess, entityAccess, /*AIR*/1); 428 //infof("Remove entity at %s", vol); 429 430 connection.sendToAll(packet); 431 } 432 }