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 }