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 }