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 }