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 voxelman.world.plugin; 7 8 import std.experimental.logger; 9 import std.experimental.allocator.mallocator; 10 import std.concurrency : spawn, thisTid; 11 import std.array : empty; 12 import core.atomic : atomicStore, atomicLoad; 13 import cbor; 14 import netlib; 15 import pluginlib; 16 import voxelman.math; 17 18 import voxelman.core.config; 19 import voxelman.core.events; 20 import voxelman.net.events; 21 import voxelman.utils.compression; 22 import voxelman.container.hashset; 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.login.plugin; 29 import voxelman.block.plugin; 30 import voxelman.blockentity.plugin; 31 import voxelman.dbg.plugin; 32 import voxelman.server.plugin : WorldSaveInternalEvent; 33 34 import voxelman.net.packets; 35 import voxelman.core.packets; 36 37 import voxelman.world.storage.chunk; 38 import voxelman.world.storage.chunkmanager; 39 import voxelman.world.storage.chunkobservermanager; 40 import voxelman.world.storage.chunkprovider; 41 import voxelman.world.storage.coordinates; 42 import voxelman.world.storage.storageworker; 43 import voxelman.world.storage.worldbox; 44 import voxelman.world.storage.worldaccess; 45 import voxelman.blockentity.blockentityaccess; 46 47 public import voxelman.world.worlddb : WorldDb; 48 49 50 alias SaveHandler = void delegate(ref PluginDataSaver); 51 alias LoadHandler = void delegate(ref PluginDataLoader); 52 53 final class IoManager : IResourceManager 54 { 55 private: 56 ConfigOption saveDirOpt; 57 ConfigOption worldNameOpt; 58 void delegate(string) onPostInit; 59 60 LoadHandler[] worldLoadHandlers; 61 SaveHandler[] worldSaveHandlers; 62 63 public: 64 this(void delegate(string) onPostInit) 65 { 66 this.onPostInit = onPostInit; 67 } 68 69 override string id() @property { return "voxelman.world.iomanager"; } 70 71 override void init(IResourceManagerRegistry resmanRegistry) { 72 ConfigManager config = resmanRegistry.getResourceManager!ConfigManager; 73 saveDirOpt = config.registerOption!string("save_dir", "../../saves"); 74 worldNameOpt = config.registerOption!string("world_name", "world"); 75 } 76 override void postInit() { 77 import std.path : buildPath; 78 import std.file : mkdirRecurse; 79 auto saveFilename = buildPath(saveDirOpt.get!string, worldNameOpt.get!string~".db"); 80 mkdirRecurse(saveDirOpt.get!string); 81 onPostInit(saveFilename); 82 } 83 84 void registerWorldLoadSaveHandlers(LoadHandler loadHandler, SaveHandler saveHandler) 85 { 86 worldLoadHandlers ~= loadHandler; 87 worldSaveHandlers ~= saveHandler; 88 } 89 } 90 91 struct IdMapManagerServer 92 { 93 string[][string] idMaps; 94 void regIdMap(string name, string[] mapItems) 95 { 96 idMaps[name] = mapItems; 97 } 98 } 99 100 struct PluginDataSaver 101 { 102 enum DATA_BUF_SIZE = 1024*1024*2; 103 enum KEY_BUF_SIZE = 1024*20; 104 private ubyte[] dataBuf; 105 private ubyte[] keyBuf; 106 private size_t dataLen; 107 private size_t keyLen; 108 109 private void alloc() @nogc { 110 dataBuf = cast(ubyte[])Mallocator.instance.allocate(DATA_BUF_SIZE); 111 keyBuf = cast(ubyte[])Mallocator.instance.allocate(KEY_BUF_SIZE); 112 } 113 114 private void free() @nogc { 115 Mallocator.instance.deallocate(dataBuf); 116 Mallocator.instance.deallocate(keyBuf); 117 } 118 119 ubyte[] tempBuffer() @property @nogc { 120 return dataBuf[dataLen..$]; 121 } 122 123 void writeEntry(string key, size_t bytesWritten) { 124 keyLen += encodeCbor(keyBuf[keyLen..$], key); 125 keyLen += encodeCbor(keyBuf[keyLen..$], bytesWritten); 126 dataLen += bytesWritten; 127 } 128 129 private void reset() @nogc { 130 dataLen = 0; 131 keyLen = 0; 132 } 133 134 private int opApply(int delegate(string key, ubyte[] data) dg) 135 { 136 ubyte[] keyEntriesData = keyBuf[0..keyLen]; 137 ubyte[] data = dataBuf; 138 while(!keyEntriesData.empty) 139 { 140 auto key = decodeCborSingle!string(keyEntriesData); 141 auto dataSize = decodeCborSingle!size_t(keyEntriesData); 142 auto result = dg(key, data[0..dataSize]); 143 data = data[dataSize..$]; 144 145 if (result) return result; 146 } 147 return 0; 148 } 149 } 150 151 struct PluginDataLoader 152 { 153 private WorldDb worldDb; 154 155 ubyte[] readEntry(string key) { 156 ubyte[] data = worldDb.getPerWorldValue(key); 157 //infof("Reading %s %s", key, data.length); 158 //printCborStream(data[]); 159 return data; 160 } 161 } 162 163 struct WorldInfo 164 { 165 string name = DEFAULT_WORLD_NAME; 166 TimestampType simulationTick; 167 ivec3 spawnPosition; 168 } 169 170 struct ActiveChunks 171 { 172 private immutable string dbKey = "voxelman.world.active_chunks"; 173 HashSet!ChunkWorldPos chunks; 174 void delegate(ChunkWorldPos cwp) loadChunk; 175 void delegate(ChunkWorldPos cwp) unloadChunk; 176 177 void add(ChunkWorldPos cwp) { 178 chunks.put(cwp); 179 loadChunk(cwp); 180 } 181 182 void remove(ChunkWorldPos cwp) { 183 if (chunks.remove(cwp)) 184 unloadChunk(cwp); 185 } 186 187 void loadActiveChunks() { 188 foreach(cwp; chunks.items) { 189 loadChunk(cwp); 190 infof("load active: %s", cwp); 191 } 192 } 193 194 private void read(ref PluginDataLoader loader) { 195 ubyte[] data = loader.readEntry(dbKey); 196 if (!data.empty) { 197 auto token = decodeCborToken(data); 198 assert(token.type == CborTokenType.arrayHeader); 199 foreach(_; 0..token.uinteger) 200 chunks.put(decodeCborSingle!ChunkWorldPos(data)); 201 assert(data.empty); 202 } 203 } 204 205 private void write(ref PluginDataSaver saver) { 206 auto sink = saver.tempBuffer; 207 size_t encodedSize = encodeCborArrayHeader(sink[], chunks.length); 208 foreach(cwp; chunks.items) 209 encodedSize += encodeCbor(sink[encodedSize..$], cwp); 210 saver.writeEntry(dbKey, encodedSize); 211 } 212 } 213 214 //version = DBG_COMPR; 215 final class ServerWorld : IPlugin 216 { 217 private: 218 EventDispatcherPlugin evDispatcher; 219 NetServerPlugin connection; 220 ClientDbServer clientDb; 221 BlockPluginServer blockPlugin; 222 BlockEntityServer blockEntityPlugin; 223 224 Debugger dbg; 225 IoManager ioManager; 226 227 ConfigOption numGenWorkersOpt; 228 229 ubyte[] buf; 230 WorldInfo worldInfo; 231 immutable string worldInfoKey = "voxelman.world.world_info"; 232 string worldFilename; 233 234 shared bool isSaving; 235 WorldDb worldDb; 236 PluginDataSaver pluginDataSaver; 237 238 public: 239 ChunkManager chunkManager; 240 ChunkProvider chunkProvider; 241 ChunkObserverManager chunkObserverManager; 242 ActiveChunks activeChunks; 243 IdMapManagerServer idMapManager; 244 245 WorldAccess worldAccess; 246 BlockEntityAccess entityAccess; 247 248 mixin IdAndSemverFrom!(voxelman.world.plugininfo); 249 250 override void registerResourceManagers(void delegate(IResourceManager) registerHandler) 251 { 252 ioManager = new IoManager(&loadWorld); 253 registerHandler(ioManager); 254 } 255 256 override void registerResources(IResourceManagerRegistry resmanRegistry) 257 { 258 ConfigManager config = resmanRegistry.getResourceManager!ConfigManager; 259 numGenWorkersOpt = config.registerOption!int("num_workers", 4); 260 ioManager.registerWorldLoadSaveHandlers(&readWorldInfo, &writeWorldInfo); 261 ioManager.registerWorldLoadSaveHandlers(&activeChunks.read, &activeChunks.write); 262 dbg = resmanRegistry.getResourceManager!Debugger; 263 } 264 265 override void preInit() 266 { 267 pluginDataSaver.alloc(); 268 buf = new ubyte[](1024*64*4); 269 chunkManager = new ChunkManager(); 270 worldAccess = new WorldAccess(chunkManager); 271 entityAccess = new BlockEntityAccess(chunkManager); 272 chunkObserverManager = new ChunkObserverManager(); 273 274 ubyte numLayers = 2; 275 chunkManager.setup(numLayers); 276 chunkManager.isChunkSavingEnabled = true; 277 278 // Component connections 279 chunkManager.startChunkSave = &chunkProvider.startChunkSave; 280 chunkManager.pushLayer = &chunkProvider.pushLayer; 281 chunkManager.endChunkSave = &chunkProvider.endChunkSave; 282 chunkManager.loadChunkHandler = &chunkProvider.loadChunk; 283 284 chunkProvider.onChunkLoadedHandler = &chunkManager.onSnapshotLoaded!LoadedChunkData; 285 chunkProvider.onChunkSavedHandler = &chunkManager.onSnapshotSaved!SavedChunkData; 286 287 chunkObserverManager.changeChunkNumObservers = &chunkManager.setExternalChunkObservers; 288 chunkObserverManager.chunkObserverAdded = &onChunkObserverAdded; 289 chunkObserverManager.loadQueueSpaceAvaliable = &chunkProvider.loadQueueSpaceAvaliable; 290 291 activeChunks.loadChunk = &chunkObserverManager.addServerObserver; 292 activeChunks.unloadChunk = &chunkObserverManager.removeServerObserver; 293 294 chunkManager.onChunkLoadedHandler = &onChunkLoaded; 295 } 296 297 override void init(IPluginManager pluginman) 298 { 299 blockPlugin = pluginman.getPlugin!BlockPluginServer; 300 clientDb = pluginman.getPlugin!ClientDbServer; 301 302 evDispatcher = pluginman.getPlugin!EventDispatcherPlugin; 303 evDispatcher.subscribeToEvent(&handlePreUpdateEvent); 304 evDispatcher.subscribeToEvent(&handlePostUpdateEvent); 305 evDispatcher.subscribeToEvent(&handleStopEvent); 306 evDispatcher.subscribeToEvent(&handleClientDisconnected); 307 evDispatcher.subscribeToEvent(&handleSaveEvent); 308 evDispatcher.subscribeToEvent(&handleClientConnectedEvent); 309 310 blockEntityPlugin = pluginman.getPlugin!BlockEntityServer; 311 312 connection = pluginman.getPlugin!NetServerPlugin; 313 connection.registerPacketHandler!FillBlockBoxPacket(&handleFillBlockBoxPacket); 314 connection.registerPacketHandler!PlaceBlockEntityPacket(&handlePlaceBlockEntityPacket); 315 connection.registerPacketHandler!RemoveBlockEntityPacket(&handleRemoveBlockEntityPacket); 316 317 chunkProvider.init(worldDb, numGenWorkersOpt.get!uint, blockPlugin.getBlocks()); 318 worldDb = null; 319 activeChunks.loadActiveChunks(); 320 worldAccess.blockInfos = blockPlugin.getBlocks(); 321 } 322 323 TimestampType currentTimestamp() @property 324 { 325 return worldInfo.simulationTick; 326 } 327 328 private void handleSaveEvent(ref WorldSaveInternalEvent event) 329 { 330 if (!atomicLoad(isSaving)) { 331 atomicStore(isSaving, true); 332 chunkManager.save(); 333 foreach(saveHandler; ioManager.worldSaveHandlers) { 334 saveHandler(pluginDataSaver); 335 } 336 chunkProvider.pushSaveHandler(&worldSaver); 337 } 338 } 339 340 // executed on io thread. Stores values written into pluginDataSaver. 341 private void worldSaver(WorldDb wdb) 342 { 343 foreach(string key, ubyte[] data; pluginDataSaver) { 344 //infof("Writing %s", key); 345 //printCborStream(data[]); 346 347 wdb.putPerWorldValue(key, data); 348 } 349 pluginDataSaver.reset(); 350 atomicStore(isSaving, false); 351 } 352 353 private void loadWorld(string _worldFilename) 354 { 355 worldFilename = _worldFilename; 356 worldDb = new WorldDb; 357 worldDb.open(_worldFilename); 358 359 worldDb.beginTxn(); 360 scope(exit) worldDb.abortTxn(); 361 362 auto dataLoader = PluginDataLoader(worldDb); 363 foreach(loadHandler; ioManager.worldLoadHandlers) { 364 loadHandler(dataLoader); 365 } 366 } 367 368 private void readWorldInfo(ref PluginDataLoader loader) 369 { 370 import std.path : absolutePath, buildNormalizedPath; 371 ubyte[] data = loader.readEntry(worldInfoKey); 372 if (!data.empty) { 373 worldInfo = decodeCborSingleDup!WorldInfo(data); 374 infof("Loading world %s", worldFilename.absolutePath.buildNormalizedPath); 375 } else { 376 infof("Creating world %s", worldFilename.absolutePath.buildNormalizedPath); 377 } 378 } 379 380 private void writeWorldInfo(ref PluginDataSaver saver) 381 { 382 size_t encodedSize = encodeCbor(saver.tempBuffer, worldInfo); 383 saver.writeEntry(worldInfoKey, encodedSize); 384 } 385 386 private void handlePreUpdateEvent(ref PreUpdateEvent event) 387 { 388 ++worldInfo.simulationTick; 389 chunkProvider.update(); 390 chunkObserverManager.update(); 391 } 392 393 private void handlePostUpdateEvent(ref PostUpdateEvent event) 394 { 395 chunkManager.commitSnapshots(currentTimestamp); 396 sendChanges(worldAccess.blockChanges); 397 worldAccess.blockChanges = null; 398 399 import voxelman.world.gen.generators; 400 import core.atomic; 401 dbg.setVar("cacheHits", atomicLoad(cache_hits)); 402 dbg.setVar("cacheMiss", atomicLoad(cache_misses)); 403 } 404 405 private void handleStopEvent(ref GameStopEvent event) 406 { 407 while(atomicLoad(isSaving)) 408 { 409 import core.thread : Thread; 410 Thread.yield(); 411 } 412 chunkProvider.stop(); 413 pluginDataSaver.free(); 414 } 415 416 private void onChunkObserverAdded(ChunkWorldPos cwp, ClientId clientId) 417 { 418 sendChunk(clientId, cwp); 419 } 420 421 private void handleClientConnectedEvent(ref ClientConnectedEvent event) 422 { 423 foreach(key, idmap; idMapManager.idMaps) 424 { 425 connection.sendTo(event.clientId, IdMapPacket(key, idmap)); 426 } 427 } 428 429 private void handleClientDisconnected(ref ClientDisconnectedEvent event) 430 { 431 chunkObserverManager.removeObserver(event.clientId); 432 } 433 434 private void onChunkLoaded(ChunkWorldPos cwp) 435 { 436 sendChunk(chunkObserverManager.getChunkObservers(cwp), cwp); 437 } 438 439 private void sendChunk(C)(C clients, ChunkWorldPos cwp) 440 { 441 import voxelman.core.packets : ChunkDataPacket; 442 443 if (!chunkManager.isChunkLoaded(cwp)) return; 444 BlockData[8] layerBuf; 445 size_t compressedSize; 446 447 ubyte numChunkLayers; 448 foreach(ubyte layerId; 0..chunkManager.numLayers) 449 { 450 auto layer = chunkManager.getChunkSnapshot(cwp, layerId); 451 if (layer.isNull) continue; 452 453 if (layer.dataLength == 5 && layerId == 1) 454 infof("CM Loaded %s %s", cwp, layer.type); 455 456 version(DBG_COMPR)if (layer.type != StorageType.uniform) 457 { 458 ubyte[] compactBlocks = layer.getArray!ubyte; 459 infof("Send %s %s %s\n(%(%02x%))", cwp, compactBlocks.ptr, compactBlocks.length, cast(ubyte[])compactBlocks); 460 } 461 462 BlockData bd = toBlockData(layer, layerId); 463 if (layer.type == StorageType.fullArray) 464 { 465 ubyte[] compactBlocks = compressLayerData(layer.getArray!ubyte, buf[compressedSize..$]); 466 compressedSize += compactBlocks.length; 467 bd.blocks = compactBlocks; 468 } 469 layerBuf[numChunkLayers] = bd; 470 471 ++numChunkLayers; 472 } 473 474 connection.sendTo(clients, ChunkDataPacket(cwp.ivector.arrayof, layerBuf[0..numChunkLayers])); 475 } 476 477 private void sendChanges(BlockChange[][ChunkWorldPos] changes) 478 { 479 import voxelman.core.packets : MultiblockChangePacket; 480 foreach(pair; changes.byKeyValue) 481 { 482 connection.sendTo( 483 chunkObserverManager.getChunkObservers(pair.key), 484 MultiblockChangePacket(pair.key.ivector.arrayof, pair.value)); 485 } 486 } 487 488 private void handleFillBlockBoxPacket(ubyte[] packetData, ClientId clientId) 489 { 490 import voxelman.core.packets : FillBlockBoxPacket; 491 if (clientDb.isSpawned(clientId)) 492 { 493 auto packet = unpackPacketNoDup!FillBlockBoxPacket(packetData); 494 // TODO send to observers only. 495 worldAccess.fillBox(packet.box, packet.blockId); 496 connection.sendToAll(packet); 497 } 498 } 499 500 private void handlePlaceBlockEntityPacket(ubyte[] packetData, ClientId clientId) 501 { 502 auto packet = unpackPacket!PlaceBlockEntityPacket(packetData); 503 placeEntity( 504 packet.box, packet.data, 505 worldAccess, entityAccess); 506 507 // TODO send to observers only. 508 connection.sendToAll(packet); 509 } 510 511 private void handleRemoveBlockEntityPacket(ubyte[] packetData, ClientId peer) 512 { 513 auto packet = unpackPacket!RemoveBlockEntityPacket(packetData); 514 WorldBox vol = removeEntity(BlockWorldPos(packet.blockPos), 515 blockEntityPlugin.blockEntityInfos, worldAccess, entityAccess, /*AIR*/1); 516 //infof("Remove entity at %s", vol); 517 518 connection.sendToAll(packet); 519 } 520 }