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