1 /**
2 Copyright: Copyright (c) 2017-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.storage.chunk.editor;
7 
8 import voxelman.container.hash.set;
9 import voxelman.core.config;
10 import voxelman.world.storage.chunk;
11 import voxelman.world.storage;
12 
13 
14 final class ChunkEditor
15 {
16 	private ubyte numLayers;
17 	private ChunkManager chunkManager;
18 	void delegate(ChunkWorldPos) addServerObserverHandler;
19 	void delegate(ChunkWorldPos) removeServerObserverHandler;
20 
21 	private WriteBuffer[ChunkWorldPos][] writeBuffers;
22 
23 	this(ubyte _numLayers, ChunkManager _chunkManager) {
24 		numLayers = _numLayers;
25 		writeBuffers.length = _numLayers;
26 		chunkManager = _chunkManager;
27 	}
28 
29 	/// Returns all write buffers. You can modify data in write buffers, but not
30 	/// hashmap itself.
31 	/// Can be used for metadata update before commit.
32 	WriteBuffer[ChunkWorldPos] getWriteBuffers(ubyte layer) {
33 		return writeBuffers[layer];
34 	}
35 
36 	/// called at the end of tick
37 	void commitSnapshots(TimestampType currentTime) {
38 		foreach(ubyte layer; 0..numLayers)
39 		{
40 			auto writeBuffersCopy = writeBuffers[layer];
41 			// Clear it here because commit can unload chunk.
42 			// And unload asserts that chunk is not in writeBuffers.
43 			writeBuffers[layer] = null;
44 			foreach(cwp, writeBuffer; writeBuffersCopy)
45 			{
46 				if (writeBuffer.isModified)
47 				{
48 					chunkManager.commitLayerSnapshot(cwp, writeBuffer, currentTime, layer);
49 				}
50 				else
51 				{
52 					freeLayerArray(writeBuffer.layer);
53 				}
54 				removeServerObserverHandler(cwp);
55 			}
56 		}
57 	}
58 
59 	/// Returns writeable copy of current chunk snapshot.
60 	/// This buffer is valid until commit.
61 	/// After commit this buffer becomes next immutable snapshot.
62 	/// Returns null if chunk is not added and/or not loaded.
63 	/// If write buffer was not yet created then it is created based on policy.
64 	///
65 	/// If allowNonLoaded is enabled, then will create write buffer even if chunk is in non_loaded state.
66 	///   Useful for offline generation and conversion tools that write directly to chunk manager.
67 	///   You can write all chunk at once and then commit. Internal user will prevent write buffers from unloading.
68 	///   And on commit a save will be performed automatically.
69 	///
70 	/// BUG: returned pointer points inside hash table.
71 	///      If new write buffer is added hash table can reallocate.
72 	///      Do not create new write buffers while keeping pointer to any write buffer.
73 	///      Reallocation can prevent changes to buffers obtained earlier than reallocation to be invisible.
74 	WriteBuffer* getOrCreateWriteBuffer(ChunkWorldPos cwp, ubyte layer,
75 		WriteBufferPolicy policy = WriteBufferPolicy.createUniform,
76 		bool allowNonLoaded = false)
77 	{
78 		if (!chunkManager.isChunkLoaded(cwp) && !allowNonLoaded) return null;
79 		auto writeBuffer = cwp in writeBuffers[layer];
80 		if (writeBuffer is null) {
81 			writeBuffer = createWriteBuffer(cwp, layer);
82 			if (writeBuffer && policy == WriteBufferPolicy.copySnapshotArray) {
83 				auto old = chunkManager.getChunkSnapshot(cwp, layer);
84 				if (!old.isNull) {
85 					applyLayer(old, writeBuffer.layer);
86 				}
87 			}
88 		}
89 		return writeBuffer;
90 	}
91 
92 	// Creates write buffer for writing changes in it.
93 	// Latest snapshot's data is not copied in it.
94 	// Write buffer is then avaliable through getWriteBuffer/getOrCreateWriteBuffer.
95 	// On commit stage WB is moved into new snapshot if write buffer was modified.
96 	// Adds internal user that is removed on commit to prevent chunk from unloading with uncommitted changes.
97 	// Returns pointer to created write buffer.
98 	private WriteBuffer* createWriteBuffer(ChunkWorldPos cwp, ubyte layer) {
99 		assert(cwp !in writeBuffers[layer]);
100 		auto wb = WriteBuffer.init;
101 		wb.layer.layerId = layer;
102 		wb.layer.dataLength = chunkManager.layerInfos[layer].uniformExpansionType;
103 		writeBuffers[layer][cwp] = wb;
104 		addServerObserverHandler(cwp); // prevent unload until commit
105 		return cwp in writeBuffers[layer];
106 	}
107 }