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