1 /**
2 Copyright: Copyright (c) 2016-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.storage.iomanager;
7 
8 import voxelman.log;
9 import std.experimental.allocator.mallocator;
10 import std.bitmanip;
11 import std.array : empty;
12 import std.traits;
13 
14 import cbor;
15 import pluginlib;
16 import voxelman.core.config;
17 import voxelman.container.buffer;
18 import voxelman.config.configmanager : ConfigOption, ConfigManager;
19 import voxelman.world.worlddb;
20 import voxelman.utils.mapping;
21 
22 
23 alias SaveHandler = void delegate(ref PluginDataSaver);
24 alias LoadHandler = void delegate(ref PluginDataLoader);
25 
26 final class IoManager : IResourceManager
27 {
28 	package(voxelman.world) LoadHandler[] worldLoadHandlers;
29 	package(voxelman.world) SaveHandler[] worldSaveHandlers;
30 	StringMap stringMap;
31 
32 private:
33 	ConfigOption saveDirOpt;
34 	ConfigOption worldNameOpt;
35 
36 	void delegate(string) onPostInit;
37 
38 	auto dbKey = IoKey(null);
39 	void loadStringKeys(ref PluginDataLoader loader) {
40 		stringMap.load(loader.readEntryDecoded!(string[])(dbKey));
41 		if (stringMap.strings.length == 0) {
42 			stringMap.put(null); // reserve 0 index for string map
43 		}
44 	}
45 
46 	void saveStringKeys(ref PluginDataSaver saver) {
47 		saver.writeEntryEncoded(dbKey, stringMap.strings);
48 	}
49 
50 public:
51 	this(void delegate(string) onPostInit)
52 	{
53 		this.onPostInit = onPostInit;
54 		stringMap.put(null); // reserve 0 index for string map
55 		worldLoadHandlers ~= &loadStringKeys;
56 		worldSaveHandlers ~= &saveStringKeys;
57 	}
58 
59 	override string id() @property { return "voxelman.world.iomanager"; }
60 
61 	override void init(IResourceManagerRegistry resmanRegistry) {
62 		ConfigManager config = resmanRegistry.getResourceManager!ConfigManager;
63 		saveDirOpt = config.registerOption!string("save_dir", "../../saves");
64 		worldNameOpt = config.registerOption!string("world_name", "world");
65 	}
66 	override void postInit() {
67 		import std.path : buildPath;
68 		import std.file : mkdirRecurse;
69 		auto saveFilename = buildPath(saveDirOpt.get!string, worldNameOpt.get!string~".db");
70 		mkdirRecurse(saveDirOpt.get!string);
71 		onPostInit(saveFilename);
72 	}
73 
74 	StringMap* getStringMap() {
75 		return &stringMap;
76 	}
77 
78 	void registerWorldLoadSaveHandlers(LoadHandler loadHandler, SaveHandler saveHandler)
79 	{
80 		worldLoadHandlers ~= loadHandler;
81 		worldSaveHandlers ~= saveHandler;
82 	}
83 }
84 
85 struct IoKey {
86 	string str;
87 	uint id = uint.max;
88 }
89 
90 struct StringMap {
91 	Buffer!string array;
92 	uint[string] map;
93 
94 	void load(string[] ids) {
95 		array.clear();
96 		foreach(str; ids) {
97 			put(str);
98 		}
99 	}
100 
101 	string[] strings() {
102 		return array.data;
103 	}
104 
105 	uint put(string key) {
106 		uint id = cast(uint)array.data.length;
107 		map[key] = id;
108 		array.put(key);
109 		return id;
110 	}
111 
112 	uint get(ref IoKey key) {
113 		if (key.id == uint.max) {
114 			key.id = map.get(key.str, uint.max);
115 			if (key.id == uint.max) {
116 				key.id = put(key.str);
117 			}
118 		}
119 		return key.id;
120 	}
121 }
122 
123 enum IoStorageType {
124 	database,
125 	network
126 }
127 
128 struct PluginDataSaver
129 {
130 	StringMap* stringMap;
131 	private Buffer!ubyte buffer;
132 	private size_t prevDataLength;
133 
134 	Buffer!ubyte* beginWrite() {
135 		prevDataLength = buffer.data.length;
136 		return &buffer;
137 	}
138 
139 	IoStorageType storageType() { return IoStorageType.database; }
140 
141 	void endWrite(ref IoKey key) {
142 		// write empty entries, they will be deleted in storage worker
143 		uint entrySize = cast(uint)(buffer.data.length - prevDataLength);
144 		buffer.put(*cast(ubyte[4]*)&entrySize);
145 		buffer.put(formWorldKey(stringMap.get(key)));
146 	}
147 
148 	void writeEntryEncoded(T)(ref IoKey key, T data) {
149 		beginWrite();
150 		encodeCbor(buffer, data);
151 		endWrite(key);
152 	}
153 
154 	void writeMapping(T)(ref IoKey key, T mapping)
155 		if (__traits(isSame, TemplateOf!T, Mapping))
156 	{
157 		auto sink = beginWrite();
158 		encodeCborArrayHeader(sink, mapping.infoArray.length);
159 		foreach(const ref info; mapping.infoArray)
160 		{
161 			encodeCborString(sink, info.name);
162 		}
163 		endWrite(key);
164 	}
165 
166 	package(voxelman.world) void reset() @nogc {
167 		buffer.clear();
168 	}
169 
170 	package(voxelman.world) int opApply(scope int delegate(ubyte[16] key, ubyte[] data) dg)
171 	{
172 		ubyte[] data = buffer.data;
173 		while(!data.empty)
174 		{
175 			ubyte[16] key = data[$-16..$];
176 			uint entrySize = *cast(uint*)(data[$-4-16..$-16].ptr);
177 			ubyte[] entry = data[$-4-16-entrySize..$-4-16];
178 			auto result = dg(key, entry);
179 
180 			data = data[0..$-4-16-entrySize];
181 
182 			if (result) return result;
183 		}
184 		return 0;
185 	}
186 }
187 
188 unittest
189 {
190 	PluginDataSaver saver;
191 	StringMap stringMap;
192 	saver.stringMap = &stringMap;
193 
194 	auto dbKey1 = IoKey("Key1");
195 	saver.writeEntryEncoded(dbKey1, 1);
196 
197 	auto dbKey2 = IoKey("Key2");
198 	auto sink = saver.beginWrite();
199 		encodeCbor(sink, 2);
200 	saver.endWrite(dbKey2);
201 
202 	// iteration
203 	foreach(ubyte[16] key, ubyte[] data; saver) {
204 		//
205 	}
206 	saver.reset();
207 }
208 
209 struct PluginDataLoader
210 {
211 	StringMap* stringMap;
212 	WorldDb worldDb;
213 
214 	IoStorageType storageType() { return IoStorageType.database; }
215 
216 	ubyte[] readEntryRaw(ref IoKey key) {
217 		auto data = worldDb.get(formWorldKey(stringMap.get(key)));
218 		return data;
219 	}
220 
221 	/// decodes entry if data in db is not empty. Leaves value untouched otherwise.
222 	void readEntryDecoded(T)(ref IoKey key, ref T value) {
223 		ubyte[] data = readEntryRaw(key);
224 		if (data)
225 			decodeCbor!(Yes.Duplicate)(data, value);
226 	}
227 
228 	T readEntryDecoded(T)(ref IoKey key) {
229 		ubyte[] data = readEntryRaw(key);
230 		T value;
231 		if (data) {
232 			decodeCbor!(Yes.Duplicate)(data, value);
233 		}
234 		return value;
235 	}
236 
237 	void readMapping(T)(ref IoKey key, ref T mapping)
238 		if (__traits(isSame, TemplateOf!T, Mapping))
239 	{
240 		ubyte[] data = readEntryRaw(key);
241 		if (data)
242 		{
243 			string[] value;
244 			decodeCbor!(Yes.Duplicate)(data, value);
245 
246 			mapping.setMapping(value);
247 		}
248 	}
249 }