1 /**
2 Copyright: Copyright (c) 2015-2018 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 module voxelman.world.serverworld;
7 
8 import std.array : empty;
9 import core.atomic : atomicStore, atomicLoad;
10 
11 import cbor;
12 import netlib;
13 import pluginlib;
14 
15 import voxelman.container.buffer;
16 import voxelman.log;
17 import voxelman.math;
18 
19 import voxelman.core.config;
20 import voxelman.core.events;
21 import voxelman.net.events;
22 import voxelman.platform.compression;
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.session.server;
29 import voxelman.block.plugin;
30 import voxelman.blockentity.plugin;
31 import voxelman.dbg.plugin;
32 
33 import voxelman.net.packets;
34 import voxelman.core.packets;
35 
36 import voxelman.world.blockentity.blockentityaccess;
37 import voxelman.world.gen.generators;
38 import voxelman.world.storage;
39 import voxelman.world.storage.dimensionobservermanager;
40 
41 public import voxelman.world.worlddb : WorldDb, formWorldKey;
42 
43 enum START_NEW_WORLD = false;
44 
45 alias IdMapHandler = string[] delegate();
46 struct IdMapManagerServer
47 {
48 	IdMapHandler[string] idMapHandlers;
49 	void regIdMapHandler(string name, IdMapHandler handler)
50 	{
51 		idMapHandlers[name] = handler;
52 	}
53 }
54 
55 struct WorldInfo
56 {
57 	string name = DEFAULT_WORLD_NAME;
58 	TimestampType simulationTick;
59 	ClientDimPos spawnPos;
60 	DimensionId spawnDimension;
61 }
62 
63 //version = DBG_COMPR;
64 final class ServerWorld : IPlugin
65 {
66 private:
67 	EventDispatcherPlugin evDispatcher;
68 	NetServerPlugin connection;
69 	ClientManager clientMan;
70 	BlockPluginServer blockPlugin;
71 	BlockEntityServer blockEntityPlugin;
72 
73 	Debugger dbg;
74 
75 	ConfigOption numGenWorkersOpt;
76 	ConfigOption saveUnmodifiedChunksOpt;
77 
78 	ubyte[] buf;
79 	auto dbKey = IoKey("voxelman.world.world_info");
80 	string worldFilename;
81 
82 	shared bool isSaving;
83 	IoManager ioManager;
84 	WorldDb worldDb;
85 	PluginDataSaver pluginDataSaver;
86 
87 public:
88 	ChunkManager chunkManager;
89 	ChunkEditor chunkEditor;
90 	ChunkProvider chunkProvider;
91 	ChunkObserverManager chunkObserverManager;
92 
93 	DimensionManager dimMan;
94 	DimensionObserverManager dimObserverMan;
95 
96 	ActiveChunks activeChunks;
97 	IdMapManagerServer idMapManager;
98 
99 	WorldInfo worldInfo;
100 	WorldAccess worldAccess;
101 	BlockEntityAccess entityAccess;
102 
103 	mixin IdAndSemverFrom!"voxelman.world.plugininfo";
104 
105 	override void registerResourceManagers(void delegate(IResourceManager) registerHandler)
106 	{
107 		ioManager = new IoManager(&loadWorld);
108 		registerHandler(ioManager);
109 	}
110 
111 	override void registerResources(IResourceManagerRegistry resmanRegistry)
112 	{
113 		ConfigManager config = resmanRegistry.getResourceManager!ConfigManager;
114 		numGenWorkersOpt = config.registerOption!int("num_workers", 4);
115 		saveUnmodifiedChunksOpt = config.registerOption!bool("save_unmodified_chunks", false);
116 
117 		ioManager.registerWorldLoadSaveHandlers(&readWorldInfo, &writeWorldInfo);
118 		ioManager.registerWorldLoadSaveHandlers(&activeChunks.read, &activeChunks.write);
119 		ioManager.registerWorldLoadSaveHandlers(&dimMan.load, &dimMan.save);
120 
121 		dimMan.generatorMan.factory.registerGenerator!GeneratorFlat;
122 		dimMan.generatorMan.factory.registerGenerator!Generator2d;
123 		dimMan.generatorMan.factory.registerGenerator!Generator2d3d;
124 
125 		dbg = resmanRegistry.getResourceManager!Debugger;
126 	}
127 
128 	override void preInit()
129 	{
130 		pluginDataSaver.stringMap = &ioManager.stringMap;
131 		pluginDataSaver.getKey = (uint key) => formWorldKey(key);
132 		idMapManager.regIdMapHandler("string_map", () => ioManager.stringMap.strings);
133 
134 		buf = new ubyte[](1024*64*4);
135 		chunkManager = new ChunkManager(NUM_CHUNK_LAYERS);
136 		chunkEditor = new ChunkEditor(NUM_CHUNK_LAYERS, chunkManager);
137 		worldAccess = new WorldAccess(chunkManager, chunkEditor);
138 		entityAccess = new BlockEntityAccess(chunkManager, chunkEditor);
139 		chunkObserverManager = new ChunkObserverManager();
140 		chunkProvider = new ChunkProvider(chunkManager);
141 
142 		chunkManager.setLayerInfo(METADATA_LAYER, ChunkLayerInfo(BLOCK_METADATA_UNIFORM_FILL_BITS));
143 
144 		// Component connections
145 		chunkManager.loadChunkHandler = &chunkProvider.loadChunk;
146 		chunkManager.saveChunkHandler = &chunkProvider.saveChunk;
147 		chunkManager.cancelLoadChunkHandler = &chunkProvider.cancelLoad;
148 
149 		chunkEditor.addServerObserverHandler = &chunkObserverManager.addServerObserver;
150 		chunkEditor.removeServerObserverHandler = &chunkObserverManager.removeServerObserver;
151 
152 		chunkProvider.onChunkLoadedHandler = &chunkManager.onSnapshotLoaded;
153 		chunkProvider.generatorGetter = &dimMan.generatorMan.opIndex;
154 
155 		chunkObserverManager.loadChunkHandler = &chunkManager.loadChunk;
156 		chunkObserverManager.unloadChunkHandler = &chunkManager.unloadChunk;
157 		chunkObserverManager.chunkObserverAddedHandler = &onChunkObserverAdded;
158 
159 		dimObserverMan.dimensionObserverAdded = &onDimensionObserverAdded;
160 
161 		activeChunks.loadChunk = &chunkObserverManager.addServerObserver;
162 		activeChunks.unloadChunk = &chunkObserverManager.removeServerObserver;
163 
164 		chunkManager.onChunkLoadedHandler = &onChunkLoaded;
165 	}
166 
167 	override void init(IPluginManager pluginman)
168 	{
169 		blockPlugin = pluginman.getPlugin!BlockPluginServer;
170 		clientMan = pluginman.getPlugin!ClientManager;
171 
172 		evDispatcher = pluginman.getPlugin!EventDispatcherPlugin;
173 		evDispatcher.subscribeToEvent(&handlePreUpdateEvent);
174 		evDispatcher.subscribeToEvent(&handlePostUpdateEvent);
175 		evDispatcher.subscribeToEvent(&handleStopEvent);
176 		evDispatcher.subscribeToEvent(&handleClientDisconnected);
177 		evDispatcher.subscribeToEvent(&handleSaveEvent);
178 		evDispatcher.subscribeToEvent(&handleClientConnectedEvent);
179 
180 		blockEntityPlugin = pluginman.getPlugin!BlockEntityServer;
181 
182 		connection = pluginman.getPlugin!NetServerPlugin;
183 		connection.registerPacketHandler!FillBlockBoxPacket(&handleFillBlockBoxPacket);
184 		connection.registerPacketHandler!PlaceBlockEntityPacket(&handlePlaceBlockEntityPacket);
185 		connection.registerPacketHandler!RemoveBlockEntityPacket(&handleRemoveBlockEntityPacket);
186 
187 		chunkProvider.init(worldDb, numGenWorkersOpt.get!uint,
188 			blockPlugin.getBlocks(), saveUnmodifiedChunksOpt.get!bool);
189 		worldDb = null;
190 		activeChunks.loadActiveChunks();
191 		worldAccess.blockInfos = blockPlugin.getBlocks();
192 	}
193 
194 	TimestampType currentTimestamp() @property
195 	{
196 		return worldInfo.simulationTick;
197 	}
198 
199 	void setDimensionBorders(DimensionId dim, Box borders)
200 	{
201 		DimensionInfo* dimInfo = dimMan.getOrCreate(dim);
202 		dimInfo.borders = borders;
203 		sendDimensionBorders(dim);
204 	}
205 
206 	private void handleSaveEvent(ref WorldSaveInternalEvent event)
207 	{
208 		if (!atomicLoad(isSaving)) {
209 			atomicStore(isSaving, true);
210 			chunkProvider.save();
211 			foreach(saveHandler; ioManager.worldSaveHandlers) {
212 				saveHandler(pluginDataSaver);
213 			}
214 			chunkProvider.pushSaveHandler(&worldSaver);
215 		}
216 	}
217 
218 	// executed on io thread. Stores values written into pluginDataSaver.
219 	private void worldSaver(WorldDb wdb)
220 	{
221 		foreach(ubyte[16] key, ubyte[] data; pluginDataSaver) {
222 			if (data.length == 0)
223 				wdb.del(key);
224 			wdb.put(key, data);
225 		}
226 		pluginDataSaver.reset();
227 		atomicStore(isSaving, false);
228 	}
229 
230 	private void loadWorld(string _worldFilename)
231 	{
232 		worldFilename = _worldFilename;
233 		worldDb = new WorldDb;
234 		static if (START_NEW_WORLD)
235 		{
236 			static import std.file;
237 			if (std.file.exists(_worldFilename))
238 			{
239 				std.file.remove(_worldFilename);
240 			}
241 		}
242 		worldDb.open(_worldFilename);
243 
244 		worldDb.beginTxn();
245 		scope(exit) worldDb.abortTxn();
246 
247 		ubyte[] dataGetter(ref IoKey key)
248 		{
249 			return worldDb.get(formWorldKey(ioManager.stringMap.get(key)));
250 		}
251 
252 		auto dataLoader = PluginDataLoader(&ioManager.stringMap, &dataGetter);
253 		foreach(loadHandler; ioManager.worldLoadHandlers) {
254 			loadHandler(dataLoader);
255 		}
256 	}
257 
258 	private void readWorldInfo(ref PluginDataLoader loader)
259 	{
260 		import std.path : absolutePath, buildNormalizedPath;
261 		ubyte[] data = loader.readEntryRaw(dbKey);
262 		if (!data.empty) {
263 			worldInfo = decodeCborSingleDup!WorldInfo(data);
264 			infof("Loading world %s", worldFilename.absolutePath.buildNormalizedPath);
265 		} else {
266 			infof("Creating world %s", worldFilename.absolutePath.buildNormalizedPath);
267 			createWorld();
268 		}
269 	}
270 
271 	void createWorld()
272 	{
273 		dimMan.generatorMan[0] = new GeneratorFlat;
274 		dimMan.generatorMan[1] = new Generator2d;
275 		dimMan.generatorMan[2] = new Generator2d3d;
276 	}
277 
278 	private void writeWorldInfo(ref PluginDataSaver saver)
279 	{
280 		saver.writeEntryEncoded(dbKey, worldInfo);
281 	}
282 
283 	private void handlePreUpdateEvent(ref PreUpdateEvent event)
284 	{
285 		++worldInfo.simulationTick;
286 		chunkProvider.update();
287 		chunkObserverManager.update();
288 	}
289 
290 	private void handlePostUpdateEvent(ref PostUpdateEvent event)
291 	{
292 		chunkEditor.commitSnapshots(currentTimestamp);
293 		sendChanges(worldAccess.blockChanges);
294 		worldAccess.blockChanges = null;
295 
296 		import voxelman.world.gen.generators;
297 		import core.atomic;
298 		dbg.setVar("gen cache hits", atomicLoad(cache_hits));
299 		dbg.setVar("gen cache misses", atomicLoad(cache_misses));
300 		dbg.setVar("total loads", chunkProvider.totalReceived);
301 		dbg.setVar("canceled loads", chunkProvider.numSuccessfulCancelations);
302 		dbg.setVar("wasted loads", chunkProvider.numWastedLoads);
303 	}
304 
305 	private void handleStopEvent(ref GameStopEvent event)
306 	{
307 		while(atomicLoad(isSaving))
308 		{
309 			import core.thread : Thread;
310 			Thread.yield();
311 		}
312 		chunkProvider.stop();
313 	}
314 
315 	private void onDimensionObserverAdded(DimensionId dimensionId, SessionId sessionId)
316 	{
317 		sendDimensionBorders(sessionId, dimensionId);
318 	}
319 
320 	private void onChunkObserverAdded(ChunkWorldPos cwp, SessionId sessionId)
321 	{
322 		sendChunk(sessionId, cwp);
323 	}
324 
325 	private void handleClientConnectedEvent(ref ClientConnectedEvent event)
326 	{
327 		foreach(key, handler; idMapManager.idMapHandlers)
328 		{
329 			connection.sendTo(event.sessionId, IdMapPacket(key, handler()));
330 		}
331 	}
332 
333 	private void handleClientDisconnected(ref ClientDisconnectedEvent event)
334 	{
335 		chunkObserverManager.removeObserver(event.sessionId);
336 		dimObserverMan.removeObserver(event.sessionId);
337 	}
338 
339 	private void onChunkLoaded(ChunkWorldPos cwp)
340 	{
341 		sendChunk(chunkObserverManager.getChunkObservers(cwp), cwp);
342 	}
343 
344 	private void sendDimensionBorders(SessionId sessionId, DimensionId dim)
345 	{
346 		if (auto dimInfo = dimMan[dim])
347 			connection.sendTo(sessionId, DimensionInfoPacket(dim, dimInfo.borders));
348 	}
349 
350 	private void sendDimensionBorders(DimensionId dim)
351 	{
352 		static Buffer!SessionId sessionBuffer;
353 		if (auto dimInfo = dimMan[dim])
354 		{
355 			foreach(sessionId; dimObserverMan.getDimensionObservers(dim))
356 				sessionBuffer.put(sessionId);
357 
358 			connection.sendTo(sessionBuffer.data, DimensionInfoPacket(dim, dimInfo.borders));
359 			sessionBuffer.clear();
360 		}
361 	}
362 
363 	private void sendChunk(S)(S sessions, ChunkWorldPos cwp)
364 	{
365 		import voxelman.core.packets : ChunkDataPacket;
366 
367 		if (!chunkManager.isChunkLoaded(cwp)) return;
368 		ChunkLayerData[NUM_CHUNK_LAYERS] layerBuf;
369 		size_t compressedSize;
370 
371 		ubyte numChunkLayers;
372 		foreach(ubyte layerId; 0..chunkManager.numLayers)
373 		{
374 			if (!chunkManager.hasSnapshot(cwp, layerId)) continue;
375 
376 			auto layer = chunkManager.getChunkSnapshot(cwp, layerId);
377 			assert(!layer.isNull);
378 
379 			version(DBG_COMPR)if (layer.type != StorageType.uniform)
380 			{
381 				ubyte[] compactBlocks = layer.getArray!ubyte;
382 				infof("Send %s %s %s\n(%(%02x%))", cwp, compactBlocks.ptr, compactBlocks.length, cast(ubyte[])compactBlocks);
383 			}
384 
385 			ChunkLayerData bd = toBlockData(layer, layerId);
386 			if (layer.type == StorageType.fullArray)
387 			{
388 				ubyte[] compactBlocks = compressLayerData(layer.getArray!ubyte, buf[compressedSize..$]);
389 				compressedSize += compactBlocks.length;
390 				bd.blocks = compactBlocks;
391 			}
392 			layerBuf[numChunkLayers] = bd;
393 
394 			++numChunkLayers;
395 		}
396 
397 		connection.sendTo(sessions, ChunkDataPacket(cwp.ivector, layerBuf[0..numChunkLayers]));
398 	}
399 
400 	private void sendChanges(BlockChange[][ChunkWorldPos] changes)
401 	{
402 		import voxelman.core.packets : MultiblockChangePacket;
403 		foreach(pair; changes.byKeyValue)
404 		{
405 			connection.sendTo(
406 				chunkObserverManager.getChunkObservers(pair.key),
407 				MultiblockChangePacket(pair.key.ivector, pair.value));
408 		}
409 	}
410 
411 	private void handleFillBlockBoxPacket(ubyte[] packetData, SessionId sessionId)
412 	{
413 		import voxelman.core.packets : FillBlockBoxPacket;
414 		if (clientMan.isSpawned(sessionId))
415 		{
416 			auto packet = unpackPacketNoDup!FillBlockBoxPacket(packetData);
417 			// TODO send to observers only.
418 			worldAccess.fillBox(packet.box, packet.blockId, packet.blockMeta);
419 			connection.sendToAll(packet);
420 		}
421 	}
422 
423 	private void handlePlaceBlockEntityPacket(ubyte[] packetData, SessionId sessionId)
424 	{
425 		auto packet = unpackPacket!PlaceBlockEntityPacket(packetData);
426 		placeEntity(
427 			packet.box, packet.data,
428 			worldAccess, entityAccess);
429 
430 		// TODO send to observers only.
431 		connection.sendToAll(packet);
432 	}
433 
434 	private void handleRemoveBlockEntityPacket(ubyte[] packetData, SessionId peer)
435 	{
436 		auto packet = unpackPacket!RemoveBlockEntityPacket(packetData);
437 		WorldBox vol = removeEntity(BlockWorldPos(packet.blockPos),
438 			blockEntityPlugin.blockEntityInfos, worldAccess, entityAccess, /*AIR*/1);
439 		//infof("Remove entity at %s", vol);
440 
441 		connection.sendToAll(packet);
442 	}
443 }