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