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.clientworld; 7 8 import std.experimental.logger; 9 import netlib; 10 import pluginlib; 11 import voxelman.math; 12 import voxelman.geometry.box; 13 import voxelman.utils.textformatter; 14 15 import voxelman.core.config; 16 import voxelman.core.events; 17 import voxelman.net.events; 18 import voxelman.utils.compression; 19 import voxelman.container.hashset; 20 21 import voxelman.block.plugin; 22 import voxelman.blockentity.plugin; 23 import voxelman.config.configmanager : ConfigOption, ConfigManager; 24 import voxelman.eventdispatcher.plugin : EventDispatcherPlugin; 25 import voxelman.graphics.plugin; 26 import voxelman.input.keybindingmanager; 27 import voxelman.login.plugin; 28 import voxelman.net.plugin : NetServerPlugin, NetClientPlugin; 29 30 import voxelman.net.packets; 31 import voxelman.core.packets; 32 33 import voxelman.world.storage.chunk; 34 import voxelman.world.storage.chunkmanager; 35 import voxelman.world.storage.chunkobservermanager; 36 import voxelman.world.storage.chunkprovider; 37 import voxelman.world.storage.coordinates; 38 import voxelman.world.storage.worldbox; 39 import voxelman.world.storage.worldaccess; 40 import voxelman.blockentity.blockentityaccess; 41 42 import voxelman.client.chunkmeshman; 43 44 struct IdMapManagerClient 45 { 46 void delegate(string[])[string] onMapReceivedHandlers; 47 48 void regIdMapHandler(string mapName, void delegate(string[]) onMapReceived) 49 { 50 onMapReceivedHandlers[mapName] = onMapReceived; 51 } 52 } 53 54 55 //version = DBG_COMPR; 56 final class ClientWorld : IPlugin 57 { 58 private: 59 EventDispatcherPlugin evDispatcher; 60 NetClientPlugin connection; 61 GraphicsPlugin graphics; 62 ClientDbClient clientDb; 63 BlockPluginClient blockPlugin; 64 BlockEntityClient blockEntityPlugin; 65 66 ConfigOption numWorkersOpt; 67 68 public: 69 ChunkManager chunkManager; 70 ChunkObserverManager chunkObserverManager; 71 IdMapManagerClient idMapManager; 72 WorldAccess worldAccess; 73 BlockEntityAccess entityAccess; 74 ChunkMeshMan chunkMeshMan; 75 TimestampType currentTimestamp; 76 HashSet!ChunkWorldPos chunksToRemesh; 77 78 // toggles/debug 79 bool doUpdateObserverPosition = true; 80 bool drawDebugMetadata; 81 size_t totalLoadedChunks; 82 83 // Observer data 84 vec3 updatedCameraPos; 85 ChunkWorldPos observerPosition; 86 ubyte positionKey; 87 ClientId observerClientId; 88 89 ConfigOption viewRadiusOpt; 90 int viewRadius; 91 92 // Send position interval 93 double sendPositionTimer = 0; 94 enum sendPositionInterval = 0.1; 95 ChunkWorldPos prevChunkPos; 96 97 mixin IdAndSemverFrom!(voxelman.world.plugininfo); 98 99 override void registerResourceManagers(void delegate(IResourceManager) registerHandler) {} 100 101 override void registerResources(IResourceManagerRegistry resmanRegistry) 102 { 103 ConfigManager config = resmanRegistry.getResourceManager!ConfigManager; 104 numWorkersOpt = config.registerOption!uint("num_workers", 4); 105 viewRadiusOpt = config.registerOption!uint("view_distance", DEFAULT_VIEW_RADIUS); 106 107 KeyBindingManager keyBindingMan = resmanRegistry.getResourceManager!KeyBindingManager; 108 keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_RIGHT_BRACKET, "key.incViewRadius", null, &onIncViewRadius)); 109 keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_LEFT_BRACKET, "key.decViewRadius", null, &onDecViewRadius)); 110 keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_U, "key.togglePosUpdate", null, &onTogglePositionUpdate)); 111 keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_M, "key.toggleMetaData", null, &onToggleMetaData)); 112 keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_F5, "key.remesh", null, &onRemeshViewBox)); 113 keyBindingMan.registerKeyBinding(new KeyBinding(KeyCode.KEY_F1, "key.chunkmeta", null, &onPrintChunkMeta)); 114 } 115 116 override void preInit() 117 { 118 chunkManager = new ChunkManager(); 119 worldAccess = new WorldAccess(chunkManager); 120 entityAccess = new BlockEntityAccess(chunkManager); 121 122 ubyte numLayers = 2; 123 chunkManager.setup(numLayers); 124 chunkManager.loadChunkHandler = &handleLoadChunk; 125 chunkManager.isLoadCancelingEnabled = true; 126 chunkManager.isChunkSavingEnabled = false; 127 chunkManager.onChunkRemovedHandlers ~= &chunkMeshMan.onChunkRemoved; 128 129 chunkObserverManager = new ChunkObserverManager(); 130 chunkObserverManager.changeChunkNumObservers = &chunkManager.setExternalChunkObservers; 131 chunkObserverManager.chunkObserverAdded = &handleChunkObserverAdded; 132 chunkObserverManager.loadQueueSpaceAvaliable = () => size_t.max; 133 } 134 135 override void init(IPluginManager pluginman) 136 { 137 viewRadius = viewRadiusOpt.get!uint; 138 // duplicated code 139 viewRadius = clamp(viewRadius, MIN_VIEW_RADIUS, MAX_VIEW_RADIUS); 140 141 clientDb = pluginman.getPlugin!ClientDbClient; 142 143 blockPlugin = pluginman.getPlugin!BlockPluginClient; 144 blockEntityPlugin = pluginman.getPlugin!BlockEntityClient; 145 chunkMeshMan.init(chunkManager, blockPlugin.getBlocks(), 146 blockEntityPlugin.blockEntityInfos(), numWorkersOpt.get!uint); 147 148 evDispatcher = pluginman.getPlugin!EventDispatcherPlugin; 149 evDispatcher.subscribeToEvent(&handlePreUpdateEvent); 150 evDispatcher.subscribeToEvent(&handlePostUpdateEvent); 151 evDispatcher.subscribeToEvent(&handleGameStopEvent); 152 evDispatcher.subscribeToEvent(&handleSendClientSettingsEvent); 153 154 connection = pluginman.getPlugin!NetClientPlugin; 155 connection.registerPacketHandler!ChunkDataPacket(&handleChunkDataPacket); 156 connection.registerPacketHandler!FillBlockBoxPacket(&handleFillBlockBoxPacket); 157 connection.registerPacketHandler!MultiblockChangePacket(&handleMultiblockChangePacket); 158 connection.registerPacketHandler!PlaceBlockEntityPacket(&handlePlaceBlockEntityPacket); 159 connection.registerPacketHandler!RemoveBlockEntityPacket(&handleRemoveBlockEntityPacket); 160 connection.registerPacketHandler!IdMapPacket(&handleIdMapPacket); 161 162 graphics = pluginman.getPlugin!GraphicsPlugin; 163 } 164 165 override void postInit() 166 { 167 worldAccess.blockInfos = blockPlugin.getBlocks(); 168 } 169 170 void handleIdMapPacket(ubyte[] packetData, ClientId clientId) 171 { 172 auto packet = unpackPacket!IdMapPacket(packetData); 173 if (auto h = idMapManager.onMapReceivedHandlers.get(packet.mapName, null)) 174 { 175 h(packet.names); 176 } 177 } 178 179 void onTogglePositionUpdate(string) 180 { 181 doUpdateObserverPosition = !doUpdateObserverPosition; 182 } 183 184 void onToggleMetaData(string) { 185 drawDebugMetadata = !drawDebugMetadata; 186 } 187 188 void onRemeshViewBox(string) { 189 WorldBox box = chunkObserverManager.getObserverBox(observerClientId); 190 remeshBox(box, true); 191 } 192 193 void onPrintChunkMeta(string) { 194 import voxelman.block.utils : printChunkMetadata; 195 auto cwp = observerPosition; 196 auto snap = chunkManager.getChunkSnapshot(cwp, FIRST_LAYER); 197 198 if (snap.isNull) { 199 infof("No snapshot for %s", cwp); 200 return; 201 } 202 printChunkMetadata(snap.metadata); 203 } 204 205 void handlePreUpdateEvent(ref PreUpdateEvent event) 206 { 207 ++currentTimestamp; 208 209 if (doUpdateObserverPosition) 210 { 211 observerPosition = ChunkWorldPos( 212 BlockWorldPos(graphics.camera.position, observerPosition.w)); 213 } 214 215 updateObserverPosition(); 216 chunkObserverManager.update(); 217 chunkMeshMan.update(); 218 219 if (drawDebugMetadata) { 220 chunkMeshMan.drawDebug(graphics.debugBatch); 221 drawDebugChunkInfo(); 222 } 223 } 224 225 void drawDebugChunkInfo() 226 { 227 enum nearRadius = 2; 228 ChunkWorldPos chunkPos = BlockWorldPos(graphics.camera.position, currentDimention); 229 WorldBox nearBox = calcBox(chunkPos, nearRadius); 230 231 drawDebugChunkMetadata(nearBox); 232 drawDebugChunkGrid(nearBox); 233 } 234 235 void drawDebugChunkMetadata(WorldBox box) 236 { 237 import voxelman.block.utils; 238 foreach(pos; box.positions) 239 { 240 vec3 blockPos = pos * CHUNK_SIZE; 241 242 auto snap = chunkManager.getChunkSnapshot( 243 ChunkWorldPos(pos, box.dimention), FIRST_LAYER); 244 245 if (snap.isNull) continue; 246 foreach(ubyte side; 0..6) 247 { 248 Solidity solidity = chunkSideSolidity(snap.metadata, cast(Side)side); 249 static Color3ub[3] colors = [Colors.white, Colors.gray, Colors.black]; 250 Color3ub color = colors[solidity]; 251 graphics.debugBatch.putCubeFace(blockPos + CHUNK_SIZE/2, vec3(2,2,2), cast(Side)side, color, true); 252 } 253 254 if (snap.isUniform) { 255 graphics.debugBatch.putCube(blockPos + CHUNK_SIZE/2-2, vec3(6,6,6), Colors.green, false); 256 } 257 } 258 } 259 260 void drawDebugChunkGrid(WorldBox box) 261 { 262 vec3 gridPos = vec3(box.position*CHUNK_SIZE); 263 ivec3 gridCount = box.size+1; 264 vec3 gridOffset = vec3(CHUNK_SIZE, CHUNK_SIZE, CHUNK_SIZE); 265 graphics.debugBatch.put3dGrid(gridPos, gridCount, gridOffset, Colors.blue); 266 } 267 268 void handleChunkObserverAdded(ChunkWorldPos, ClientId) {} 269 270 void handleLoadChunk(ChunkWorldPos) {} 271 272 void handlePostUpdateEvent(ref PostUpdateEvent event) 273 { 274 chunkManager.commitSnapshots(currentTimestamp); 275 //chunkMeshMan.remeshChangedChunks(chunkManager.getModifiedChunks()); 276 chunkManager.clearModifiedChunks(); 277 chunkMeshMan.remeshChangedChunks(chunksToRemesh); 278 chunksToRemesh.clear(); 279 280 if (doUpdateObserverPosition) 281 sendPosition(event.deltaTime); 282 } 283 284 void handleGameStopEvent(ref GameStopEvent gameStopEvent) 285 { 286 import core.thread; 287 while(chunkMeshMan.numMeshChunkTasks > 0) 288 { 289 chunkMeshMan.update(); 290 } 291 chunkMeshMan.stop(); 292 } 293 294 void handleSendClientSettingsEvent(ref SendClientSettingsEvent event) 295 { 296 connection.send(ViewRadiusPacket(viewRadius)); 297 } 298 299 void handleChunkDataPacket(ubyte[] packetData, ClientId peer) 300 { 301 auto packet = unpackPacketNoDup!ChunkDataPacket(packetData); 302 //tracef("Received %s ChunkDataPacket(%s,%s)", packetData.length, 303 // packet.chunkPos, packet.blockData.blocks.length); 304 305 ChunkLayerItem[8] layers; 306 auto cwp = ChunkWorldPos(packet.chunkPos); 307 308 ubyte numChunkLayers; 309 foreach(layer; packet.layers) 310 { 311 if (!layer.uniform) 312 { 313 version(DBG_COMPR)infof("Receive %s %s\n(%(%02x%))", packet.chunkPos, layer.blocks.length, cast(ubyte[])layer.blocks); 314 auto decompressed = decompressLayerData(layer.blocks); 315 if (decompressed is null) 316 { 317 auto b = layer.blocks; 318 infof("Fail %s %s\n(%(%02x%))", packet.chunkPos, b.length, cast(ubyte[])b); 319 return; 320 } 321 else 322 { 323 layer.blocks = decompressed; 324 layer.validate(); 325 } 326 } 327 layers[numChunkLayers] = fromBlockData(layer); 328 ++numChunkLayers; 329 } 330 331 onChunkLoaded(cwp, layers[0..numChunkLayers]); 332 } 333 334 void onChunkLoaded(ChunkWorldPos cwp, ChunkLayerItem[] layers) 335 { 336 //tracef("onChunkLoaded %s added %s", cwp, chunkManager.isChunkAdded(cwp)); 337 ++totalLoadedChunks; 338 static struct LoadedChunkData 339 { 340 ChunkWorldPos cwp; 341 ChunkLayerItem[] layers; 342 ChunkHeaderItem getHeader() { return ChunkHeaderItem(cwp, cast(uint)layers.length, 0); } 343 ChunkLayerItem getLayer() { 344 ChunkLayerItem layer = layers[0]; 345 layers = layers[1..$]; 346 return layer; 347 } 348 } 349 350 351 if (chunkManager.isChunkLoaded(cwp)) 352 { 353 foreach(layer; layers) 354 { 355 WriteBuffer* writeBuffer = chunkManager.getOrCreateWriteBuffer(cwp, layer.layerId); 356 applyLayer(layer, writeBuffer.layer); 357 } 358 } 359 else 360 { 361 chunkManager.onSnapshotLoaded(LoadedChunkData(cwp, layers), true); 362 } 363 364 chunksToRemesh.put(cwp); 365 foreach(adj; adjacentPositions(cwp)) 366 chunksToRemesh.put(adj); 367 } 368 369 void handleMultiblockChangePacket(ubyte[] packetData, ClientId peer) 370 { 371 auto packet = unpackPacket!MultiblockChangePacket(packetData); 372 auto cwp = ChunkWorldPos(packet.chunkPos); 373 374 worldAccess.applyBlockChanges(cwp, packet.blockChanges); 375 376 chunksToRemesh.put(cwp); 377 foreach(adj; adjacentPositions(cwp)) 378 chunksToRemesh.put(adj); 379 } 380 381 void handleFillBlockBoxPacket(ubyte[] packetData, ClientId peer) 382 { 383 auto packet = unpackPacketNoDup!FillBlockBoxPacket(packetData); 384 385 worldAccess.fillBox(packet.box, packet.blockId); 386 onBlockBoxChanged(packet.box); 387 } 388 389 void onBlockBoxChanged(WorldBox blockBox) 390 { 391 WorldBox observedBox = chunkObserverManager.getObserverBox(observerClientId); 392 WorldBox modifiedBox = calcModifiedMeshesBox(blockBox); 393 WorldBox box = worldBoxIntersection(observedBox, modifiedBox); 394 395 foreach(pos; box.positions) 396 chunksToRemesh.put(ChunkWorldPos(pos, box.dimention)); 397 } 398 399 void handlePlaceBlockEntityPacket(ubyte[] packetData, ClientId peer) 400 { 401 auto packet = unpackPacket!PlaceBlockEntityPacket(packetData); 402 placeEntity(packet.box, packet.data, 403 worldAccess, entityAccess); 404 onBlockBoxChanged(packet.box); 405 } 406 407 void handleRemoveBlockEntityPacket(ubyte[] packetData, ClientId peer) 408 { 409 auto packet = unpackPacket!RemoveBlockEntityPacket(packetData); 410 WorldBox changedBox = removeEntity(BlockWorldPos(packet.blockPos), 411 blockEntityPlugin.blockEntityInfos, worldAccess, entityAccess, /*AIR*/1); 412 onBlockBoxChanged(changedBox); 413 } 414 415 void remeshBox(WorldBox box, bool printTime = false) 416 { 417 import std.datetime : MonoTime, Duration, usecs, dur; 418 MonoTime startTime = MonoTime.currTime; 419 420 void onRemeshDone(size_t chunksRemeshed) { 421 auto duration = MonoTime.currTime - startTime; 422 int seconds; short msecs; short usecs; 423 duration.split!("seconds", "msecs", "usecs")(seconds, msecs, usecs); 424 infof("Remeshed %s chunks in % 3s.%03s,%03ss", chunksRemeshed, seconds, msecs, usecs); 425 } 426 427 HashSet!ChunkWorldPos remeshedChunks; 428 foreach(pos; box.positions) { 429 remeshedChunks.put(ChunkWorldPos(pos, box.dimention)); 430 } 431 if (printTime) 432 chunkMeshMan.remeshChangedChunks(remeshedChunks, &onRemeshDone); 433 else 434 chunkMeshMan.remeshChangedChunks(remeshedChunks); 435 } 436 437 void sendPosition(double dt) 438 { 439 if (clientDb.isSpawned) 440 { 441 sendPositionTimer += dt; 442 if (sendPositionTimer > sendPositionInterval || 443 observerPosition != prevChunkPos) 444 { 445 connection.send(ClientPositionPacket( 446 graphics.camera.position.arrayof, 447 graphics.camera.heading.arrayof, 448 observerPosition.w, positionKey)); 449 450 if (sendPositionTimer < sendPositionInterval) 451 sendPositionTimer = 0; 452 else 453 sendPositionTimer -= sendPositionInterval; 454 } 455 } 456 457 prevChunkPos = observerPosition; 458 } 459 460 void setCurrentDimention(DimentionId dimention, ubyte positionKey) { 461 observerPosition.w = dimention; 462 this.positionKey = positionKey; 463 updateObserverPosition(); 464 } 465 466 DimentionId currentDimention() @property { 467 return observerPosition.w; 468 } 469 470 void incDimention() { 471 string com = cast(string)makeFormattedText("dim %s", currentDimention() + 1); 472 connection.send(CommandPacket(com)); 473 } 474 void decDimention() { 475 string com = cast(string)makeFormattedText("dim %s", currentDimention() - 1); 476 connection.send(CommandPacket(com)); 477 } 478 479 void updateObserverPosition() { 480 if (clientDb.isSpawned) { 481 if (observerClientId != clientDb.thisClientId) { 482 chunkObserverManager.removeObserver(observerClientId); 483 observerClientId = clientDb.thisClientId; 484 } 485 486 chunkObserverManager.changeObserverBox(observerClientId, observerPosition, viewRadius); 487 } 488 } 489 490 void onIncViewRadius(string) { 491 incViewRadius(); 492 } 493 494 void onDecViewRadius(string) { 495 decViewRadius(); 496 } 497 498 void incViewRadius() { 499 setViewRadius(getViewRadius() + 1); 500 } 501 502 void decViewRadius() { 503 setViewRadius(getViewRadius() - 1); 504 } 505 506 int getViewRadius() { 507 return viewRadius; 508 } 509 510 void setViewRadius(int newViewRadius) { 511 auto oldViewRadius = viewRadius; 512 // duplicated code 513 viewRadius = clamp(newViewRadius, MIN_VIEW_RADIUS, MAX_VIEW_RADIUS); 514 515 if (oldViewRadius != viewRadius) 516 { 517 connection.send(ViewRadiusPacket(viewRadius)); 518 } 519 } 520 }