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.plugin;
7 
8 import std.experimental.logger;
9 import std.experimental.allocator.mallocator;
10 import std.concurrency : spawn, thisTid;
11 import std.array : empty;
12 import core.atomic : atomicStore, atomicLoad;
13 import cbor;
14 import netlib;
15 import pluginlib;
16 import voxelman.math;
17 
18 import voxelman.core.config;
19 import voxelman.core.events;
20 import voxelman.net.events;
21 import voxelman.utils.compression;
22 import voxelman.container.hashset;
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.login.plugin;
29 import voxelman.block.plugin;
30 import voxelman.blockentity.plugin;
31 import voxelman.dbg.plugin;
32 import voxelman.server.plugin : WorldSaveInternalEvent;
33 
34 import voxelman.net.packets;
35 import voxelman.core.packets;
36 
37 import voxelman.world.storage.chunk;
38 import voxelman.world.storage.chunkmanager;
39 import voxelman.world.storage.chunkobservermanager;
40 import voxelman.world.storage.chunkprovider;
41 import voxelman.world.storage.coordinates;
42 import voxelman.world.storage.storageworker;
43 import voxelman.world.storage.worldbox;
44 import voxelman.world.storage.worldaccess;
45 import voxelman.blockentity.blockentityaccess;
46 
47 public import voxelman.world.worlddb : WorldDb;
48 
49 
50 alias SaveHandler = void delegate(ref PluginDataSaver);
51 alias LoadHandler = void delegate(ref PluginDataLoader);
52 
53 final class IoManager : IResourceManager
54 {
55 private:
56 	ConfigOption saveDirOpt;
57 	ConfigOption worldNameOpt;
58 	void delegate(string) onPostInit;
59 
60 	LoadHandler[] worldLoadHandlers;
61 	SaveHandler[] worldSaveHandlers;
62 
63 public:
64 	this(void delegate(string) onPostInit)
65 	{
66 		this.onPostInit = onPostInit;
67 	}
68 
69 	override string id() @property { return "voxelman.world.iomanager"; }
70 
71 	override void init(IResourceManagerRegistry resmanRegistry) {
72 		ConfigManager config = resmanRegistry.getResourceManager!ConfigManager;
73 		saveDirOpt = config.registerOption!string("save_dir", "../../saves");
74 		worldNameOpt = config.registerOption!string("world_name", "world");
75 	}
76 	override void postInit() {
77 		import std.path : buildPath;
78 		import std.file : mkdirRecurse;
79 		auto saveFilename = buildPath(saveDirOpt.get!string, worldNameOpt.get!string~".db");
80 		mkdirRecurse(saveDirOpt.get!string);
81 		onPostInit(saveFilename);
82 	}
83 
84 	void registerWorldLoadSaveHandlers(LoadHandler loadHandler, SaveHandler saveHandler)
85 	{
86 		worldLoadHandlers ~= loadHandler;
87 		worldSaveHandlers ~= saveHandler;
88 	}
89 }
90 
91 struct IdMapManagerServer
92 {
93 	string[][string] idMaps;
94 	void regIdMap(string name, string[] mapItems)
95 	{
96 		idMaps[name] = mapItems;
97 	}
98 }
99 
100 struct PluginDataSaver
101 {
102 	enum DATA_BUF_SIZE = 1024*1024*2;
103 	enum KEY_BUF_SIZE = 1024*20;
104 	private ubyte[] dataBuf;
105 	private ubyte[] keyBuf;
106 	private size_t dataLen;
107 	private size_t keyLen;
108 
109 	private void alloc() @nogc {
110 		dataBuf = cast(ubyte[])Mallocator.instance.allocate(DATA_BUF_SIZE);
111 		keyBuf = cast(ubyte[])Mallocator.instance.allocate(KEY_BUF_SIZE);
112 	}
113 
114 	private void free() @nogc {
115 		Mallocator.instance.deallocate(dataBuf);
116 		Mallocator.instance.deallocate(keyBuf);
117 	}
118 
119 	ubyte[] tempBuffer() @property @nogc {
120 		return dataBuf[dataLen..$];
121 	}
122 
123 	void writeEntry(string key, size_t bytesWritten) {
124 		keyLen += encodeCbor(keyBuf[keyLen..$], key);
125 		keyLen += encodeCbor(keyBuf[keyLen..$], bytesWritten);
126 		dataLen += bytesWritten;
127 	}
128 
129 	private void reset() @nogc {
130 		dataLen = 0;
131 		keyLen = 0;
132 	}
133 
134 	private int opApply(int delegate(string key, ubyte[] data) dg)
135 	{
136 		ubyte[] keyEntriesData = keyBuf[0..keyLen];
137 		ubyte[] data = dataBuf;
138 		while(!keyEntriesData.empty)
139 		{
140 			auto key = decodeCborSingle!string(keyEntriesData);
141 			auto dataSize = decodeCborSingle!size_t(keyEntriesData);
142 			auto result = dg(key, data[0..dataSize]);
143 			data = data[dataSize..$];
144 
145 			if (result) return result;
146 		}
147 		return 0;
148 	}
149 }
150 
151 struct PluginDataLoader
152 {
153 	private WorldDb worldDb;
154 
155 	ubyte[] readEntry(string key) {
156 		ubyte[] data = worldDb.getPerWorldValue(key);
157 		//infof("Reading %s %s", key, data.length);
158 		//printCborStream(data[]);
159 		return data;
160 	}
161 }
162 
163 struct WorldInfo
164 {
165 	string name = DEFAULT_WORLD_NAME;
166 	TimestampType simulationTick;
167 	ivec3 spawnPosition;
168 }
169 
170 struct ActiveChunks
171 {
172 	private immutable string dbKey = "voxelman.world.active_chunks";
173 	HashSet!ChunkWorldPos chunks;
174 	void delegate(ChunkWorldPos cwp) loadChunk;
175 	void delegate(ChunkWorldPos cwp) unloadChunk;
176 
177 	void add(ChunkWorldPos cwp) {
178 		chunks.put(cwp);
179 		loadChunk(cwp);
180 	}
181 
182 	void remove(ChunkWorldPos cwp) {
183 		if (chunks.remove(cwp))
184 			unloadChunk(cwp);
185 	}
186 
187 	void loadActiveChunks() {
188 		foreach(cwp; chunks.items) {
189 			loadChunk(cwp);
190 			infof("load active: %s", cwp);
191 		}
192 	}
193 
194 	private void read(ref PluginDataLoader loader) {
195 		ubyte[] data = loader.readEntry(dbKey);
196 		if (!data.empty) {
197 			auto token = decodeCborToken(data);
198 			assert(token.type == CborTokenType.arrayHeader);
199 			foreach(_; 0..token.uinteger)
200 				chunks.put(decodeCborSingle!ChunkWorldPos(data));
201 			assert(data.empty);
202 		}
203 	}
204 
205 	private void write(ref PluginDataSaver saver) {
206 		auto sink = saver.tempBuffer;
207 		size_t encodedSize = encodeCborArrayHeader(sink[], chunks.length);
208 		foreach(cwp; chunks.items)
209 			encodedSize += encodeCbor(sink[encodedSize..$], cwp);
210 		saver.writeEntry(dbKey, encodedSize);
211 	}
212 }
213 
214 //version = DBG_COMPR;
215 final class ServerWorld : IPlugin
216 {
217 private:
218 	EventDispatcherPlugin evDispatcher;
219 	NetServerPlugin connection;
220 	ClientDbServer clientDb;
221 	BlockPluginServer blockPlugin;
222 	BlockEntityServer blockEntityPlugin;
223 
224 	Debugger dbg;
225 	IoManager ioManager;
226 
227 	ConfigOption numGenWorkersOpt;
228 
229 	ubyte[] buf;
230 	WorldInfo worldInfo;
231 	immutable string worldInfoKey = "voxelman.world.world_info";
232 	string worldFilename;
233 
234 	shared bool isSaving;
235 	WorldDb worldDb;
236 	PluginDataSaver pluginDataSaver;
237 
238 public:
239 	ChunkManager chunkManager;
240 	ChunkProvider chunkProvider;
241 	ChunkObserverManager chunkObserverManager;
242 	ActiveChunks activeChunks;
243 	IdMapManagerServer idMapManager;
244 
245 	WorldAccess worldAccess;
246 	BlockEntityAccess entityAccess;
247 
248 	mixin IdAndSemverFrom!(voxelman.world.plugininfo);
249 
250 	override void registerResourceManagers(void delegate(IResourceManager) registerHandler)
251 	{
252 		ioManager = new IoManager(&loadWorld);
253 		registerHandler(ioManager);
254 	}
255 
256 	override void registerResources(IResourceManagerRegistry resmanRegistry)
257 	{
258 		ConfigManager config = resmanRegistry.getResourceManager!ConfigManager;
259 		numGenWorkersOpt = config.registerOption!int("num_workers", 4);
260 		ioManager.registerWorldLoadSaveHandlers(&readWorldInfo, &writeWorldInfo);
261 		ioManager.registerWorldLoadSaveHandlers(&activeChunks.read, &activeChunks.write);
262 		dbg = resmanRegistry.getResourceManager!Debugger;
263 	}
264 
265 	override void preInit()
266 	{
267 		pluginDataSaver.alloc();
268 		buf = new ubyte[](1024*64*4);
269 		chunkManager = new ChunkManager();
270 		worldAccess = new WorldAccess(chunkManager);
271 		entityAccess = new BlockEntityAccess(chunkManager);
272 		chunkObserverManager = new ChunkObserverManager();
273 
274 		ubyte numLayers = 2;
275 		chunkManager.setup(numLayers);
276 		chunkManager.isChunkSavingEnabled = true;
277 
278 		// Component connections
279 		chunkManager.startChunkSave = &chunkProvider.startChunkSave;
280 		chunkManager.pushLayer = &chunkProvider.pushLayer;
281 		chunkManager.endChunkSave = &chunkProvider.endChunkSave;
282 		chunkManager.loadChunkHandler = &chunkProvider.loadChunk;
283 
284 		chunkProvider.onChunkLoadedHandler = &chunkManager.onSnapshotLoaded!LoadedChunkData;
285 		chunkProvider.onChunkSavedHandler = &chunkManager.onSnapshotSaved!SavedChunkData;
286 
287 		chunkObserverManager.changeChunkNumObservers = &chunkManager.setExternalChunkObservers;
288 		chunkObserverManager.chunkObserverAdded = &onChunkObserverAdded;
289 		chunkObserverManager.loadQueueSpaceAvaliable = &chunkProvider.loadQueueSpaceAvaliable;
290 
291 		activeChunks.loadChunk = &chunkObserverManager.addServerObserver;
292 		activeChunks.unloadChunk = &chunkObserverManager.removeServerObserver;
293 
294 		chunkManager.onChunkLoadedHandler = &onChunkLoaded;
295 	}
296 
297 	override void init(IPluginManager pluginman)
298 	{
299 		blockPlugin = pluginman.getPlugin!BlockPluginServer;
300 		clientDb = pluginman.getPlugin!ClientDbServer;
301 
302 		evDispatcher = pluginman.getPlugin!EventDispatcherPlugin;
303 		evDispatcher.subscribeToEvent(&handlePreUpdateEvent);
304 		evDispatcher.subscribeToEvent(&handlePostUpdateEvent);
305 		evDispatcher.subscribeToEvent(&handleStopEvent);
306 		evDispatcher.subscribeToEvent(&handleClientDisconnected);
307 		evDispatcher.subscribeToEvent(&handleSaveEvent);
308 		evDispatcher.subscribeToEvent(&handleClientConnectedEvent);
309 
310 		blockEntityPlugin = pluginman.getPlugin!BlockEntityServer;
311 
312 		connection = pluginman.getPlugin!NetServerPlugin;
313 		connection.registerPacketHandler!FillBlockBoxPacket(&handleFillBlockBoxPacket);
314 		connection.registerPacketHandler!PlaceBlockEntityPacket(&handlePlaceBlockEntityPacket);
315 		connection.registerPacketHandler!RemoveBlockEntityPacket(&handleRemoveBlockEntityPacket);
316 
317 		chunkProvider.init(worldDb, numGenWorkersOpt.get!uint, blockPlugin.getBlocks());
318 		worldDb = null;
319 		activeChunks.loadActiveChunks();
320 		worldAccess.blockInfos = blockPlugin.getBlocks();
321 	}
322 
323 	TimestampType currentTimestamp() @property
324 	{
325 		return worldInfo.simulationTick;
326 	}
327 
328 	private void handleSaveEvent(ref WorldSaveInternalEvent event)
329 	{
330 		if (!atomicLoad(isSaving)) {
331 			atomicStore(isSaving, true);
332 			chunkManager.save();
333 			foreach(saveHandler; ioManager.worldSaveHandlers) {
334 				saveHandler(pluginDataSaver);
335 			}
336 			chunkProvider.pushSaveHandler(&worldSaver);
337 		}
338 	}
339 
340 	// executed on io thread. Stores values written into pluginDataSaver.
341 	private void worldSaver(WorldDb wdb)
342 	{
343 		foreach(string key, ubyte[] data; pluginDataSaver) {
344 			//infof("Writing %s", key);
345 			//printCborStream(data[]);
346 
347 			wdb.putPerWorldValue(key, data);
348 		}
349 		pluginDataSaver.reset();
350 		atomicStore(isSaving, false);
351 	}
352 
353 	private void loadWorld(string _worldFilename)
354 	{
355 		worldFilename = _worldFilename;
356 		worldDb = new WorldDb;
357 		worldDb.open(_worldFilename);
358 
359 		worldDb.beginTxn();
360 		scope(exit) worldDb.abortTxn();
361 
362 		auto dataLoader = PluginDataLoader(worldDb);
363 		foreach(loadHandler; ioManager.worldLoadHandlers) {
364 			loadHandler(dataLoader);
365 		}
366 	}
367 
368 	private void readWorldInfo(ref PluginDataLoader loader)
369 	{
370 		import std.path : absolutePath, buildNormalizedPath;
371 		ubyte[] data = loader.readEntry(worldInfoKey);
372 		if (!data.empty) {
373 			worldInfo = decodeCborSingleDup!WorldInfo(data);
374 			infof("Loading world %s", worldFilename.absolutePath.buildNormalizedPath);
375 		} else {
376 			infof("Creating world %s", worldFilename.absolutePath.buildNormalizedPath);
377 		}
378 	}
379 
380 	private void writeWorldInfo(ref PluginDataSaver saver)
381 	{
382 		size_t encodedSize = encodeCbor(saver.tempBuffer, worldInfo);
383 		saver.writeEntry(worldInfoKey, encodedSize);
384 	}
385 
386 	private void handlePreUpdateEvent(ref PreUpdateEvent event)
387 	{
388 		++worldInfo.simulationTick;
389 		chunkProvider.update();
390 		chunkObserverManager.update();
391 	}
392 
393 	private void handlePostUpdateEvent(ref PostUpdateEvent event)
394 	{
395 		chunkManager.commitSnapshots(currentTimestamp);
396 		sendChanges(worldAccess.blockChanges);
397 		worldAccess.blockChanges = null;
398 
399 		import voxelman.world.gen.generators;
400 		import core.atomic;
401 		dbg.setVar("cacheHits", atomicLoad(cache_hits));
402 		dbg.setVar("cacheMiss", atomicLoad(cache_misses));
403 	}
404 
405 	private void handleStopEvent(ref GameStopEvent event)
406 	{
407 		while(atomicLoad(isSaving))
408 		{
409 			import core.thread : Thread;
410 			Thread.yield();
411 		}
412 		chunkProvider.stop();
413 		pluginDataSaver.free();
414 	}
415 
416 	private void onChunkObserverAdded(ChunkWorldPos cwp, ClientId clientId)
417 	{
418 		sendChunk(clientId, cwp);
419 	}
420 
421 	private void handleClientConnectedEvent(ref ClientConnectedEvent event)
422 	{
423 		foreach(key, idmap; idMapManager.idMaps)
424 		{
425 			connection.sendTo(event.clientId, IdMapPacket(key, idmap));
426 		}
427 	}
428 
429 	private void handleClientDisconnected(ref ClientDisconnectedEvent event)
430 	{
431 		chunkObserverManager.removeObserver(event.clientId);
432 	}
433 
434 	private void onChunkLoaded(ChunkWorldPos cwp)
435 	{
436 		sendChunk(chunkObserverManager.getChunkObservers(cwp), cwp);
437 	}
438 
439 	private void sendChunk(C)(C clients, ChunkWorldPos cwp)
440 	{
441 		import voxelman.core.packets : ChunkDataPacket;
442 
443 		if (!chunkManager.isChunkLoaded(cwp)) return;
444 		BlockData[8] layerBuf;
445 		size_t compressedSize;
446 
447 		ubyte numChunkLayers;
448 		foreach(ubyte layerId; 0..chunkManager.numLayers)
449 		{
450 			auto layer = chunkManager.getChunkSnapshot(cwp, layerId);
451 			if (layer.isNull) continue;
452 
453 			if (layer.dataLength == 5 && layerId == 1)
454 				infof("CM Loaded %s %s", cwp, layer.type);
455 
456 			version(DBG_COMPR)if (layer.type != StorageType.uniform)
457 			{
458 				ubyte[] compactBlocks = layer.getArray!ubyte;
459 				infof("Send %s %s %s\n(%(%02x%))", cwp, compactBlocks.ptr, compactBlocks.length, cast(ubyte[])compactBlocks);
460 			}
461 
462 			BlockData bd = toBlockData(layer, layerId);
463 			if (layer.type == StorageType.fullArray)
464 			{
465 				ubyte[] compactBlocks = compressLayerData(layer.getArray!ubyte, buf[compressedSize..$]);
466 				compressedSize += compactBlocks.length;
467 				bd.blocks = compactBlocks;
468 			}
469 			layerBuf[numChunkLayers] = bd;
470 
471 			++numChunkLayers;
472 		}
473 
474 		connection.sendTo(clients, ChunkDataPacket(cwp.ivector.arrayof, layerBuf[0..numChunkLayers]));
475 	}
476 
477 	private void sendChanges(BlockChange[][ChunkWorldPos] changes)
478 	{
479 		import voxelman.core.packets : MultiblockChangePacket;
480 		foreach(pair; changes.byKeyValue)
481 		{
482 			connection.sendTo(
483 				chunkObserverManager.getChunkObservers(pair.key),
484 				MultiblockChangePacket(pair.key.ivector.arrayof, pair.value));
485 		}
486 	}
487 
488 	private void handleFillBlockBoxPacket(ubyte[] packetData, ClientId clientId)
489 	{
490 		import voxelman.core.packets : FillBlockBoxPacket;
491 		if (clientDb.isSpawned(clientId))
492 		{
493 			auto packet = unpackPacketNoDup!FillBlockBoxPacket(packetData);
494 			// TODO send to observers only.
495 			worldAccess.fillBox(packet.box, packet.blockId);
496 			connection.sendToAll(packet);
497 		}
498 	}
499 
500 	private void handlePlaceBlockEntityPacket(ubyte[] packetData, ClientId clientId)
501 	{
502 		auto packet = unpackPacket!PlaceBlockEntityPacket(packetData);
503 		placeEntity(
504 			packet.box, packet.data,
505 			worldAccess, entityAccess);
506 
507 		// TODO send to observers only.
508 		connection.sendToAll(packet);
509 	}
510 
511 	private void handleRemoveBlockEntityPacket(ubyte[] packetData, ClientId peer)
512 	{
513 		auto packet = unpackPacket!RemoveBlockEntityPacket(packetData);
514 		WorldBox vol = removeEntity(BlockWorldPos(packet.blockPos),
515 			blockEntityPlugin.blockEntityInfos, worldAccess, entityAccess, /*AIR*/1);
516 		//infof("Remove entity at %s", vol);
517 
518 		connection.sendToAll(packet);
519 	}
520 }