1 /** 2 Copyright: Copyright (c) 2016-2018 Andrey Penechko. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 7 module voxelman.utils.mapping; 8 9 struct Mapping(InfoType, bool withTypeMap = false) 10 { 11 static assert(__traits(compiles, {InfoType info; info.id=1; info.name="str";}), 12 "InfoType is required to have size_t id; and string name; properties"); 13 14 InfoType[] infoArray; 15 size_t[string] nameToIndexMap; 16 17 static if (withTypeMap) 18 size_t[TypeInfo] typeToIndexMap; 19 20 size_t length() @property 21 { 22 return infoArray.length; 23 } 24 25 ref InfoType opIndex(size_t id) 26 { 27 return infoArray[id]; 28 } 29 30 static if (withTypeMap) 31 { 32 size_t put(T)(InfoType info) 33 { 34 size_t newId = putImpl(info); 35 assert(typeid(T) !in typeToIndexMap, "Type "~T.stringof~" was already registered"); 36 typeToIndexMap[typeid(T)] = newId; 37 return newId; 38 } 39 40 bool contains(T)() 41 { 42 return typeid(T) in typeToIndexMap; 43 } 44 45 size_t id(T)() 46 { 47 return typeToIndexMap.get(typeid(T), size_t.max); 48 } 49 } else { 50 size_t put(InfoType info) 51 { 52 return putImpl(info); 53 } 54 } 55 56 private size_t putImpl(InfoType info) 57 { 58 size_t newId = infoArray.length; 59 nameToIndexMap[info.name] = newId; 60 info.id = newId; 61 infoArray ~= info; 62 return newId; 63 } 64 65 auto nameRange() @property 66 { 67 import std.algorithm : map; 68 return infoArray.map!(a => a.name); 69 } 70 71 size_t id(string name) 72 { 73 return nameToIndexMap.get(name, size_t.max); 74 } 75 76 string name(size_t id) 77 { 78 import std..string : format; 79 if (id >= infoArray.length) return format("|Unknown %s %s|", InfoType.stringof, id); 80 return infoArray[id].name; 81 } 82 83 void setMapping(R)(R names) 84 { 85 import std.range : isInputRange, hasLength; 86 static assert(isInputRange!R, "names should be InputRange of strings"); 87 88 InfoType[] newArray; 89 static if (hasLength!R) 90 { 91 if (names.length == 0) 92 { 93 return; 94 } 95 newArray.reserve(names.length); 96 } 97 98 foreach(i, name; names) 99 { 100 size_t index = nameToIndexMap.get(name, size_t.max); 101 size_t newId = newArray.length; 102 103 if (index == size_t.max) 104 { 105 InfoType info; 106 info.name = name; 107 newArray ~= info; 108 } 109 else 110 { 111 newArray ~= infoArray[index]; 112 infoArray[index].id = size_t.max; // Mark as removed 113 } 114 newArray[$-1].id = newId; 115 } 116 117 foreach(oldItem; infoArray) 118 if (oldItem.id != size_t.max) 119 { 120 size_t newId = newArray.length; 121 newArray ~= oldItem; 122 newArray[$-1].id = newId; 123 } 124 125 infoArray = newArray; 126 127 size_t[string] newMap; 128 foreach(ref info; infoArray) 129 { 130 newMap[info.name] = info.id; 131 } 132 nameToIndexMap = newMap; 133 } 134 } 135 136 //static assert(__traits(compiles, {struct ValidInfo {size_t id; string name;} Mapping!ValidInfo m;})); 137 //static assert(!is(typeof({struct InvalidInfo {} Mapping!InvalidInfo invmapping;}))); 138 //static assert(!is(typeof({struct InvalidInfo {size_t id;} Mapping!InvalidInfo invmapping;}))); 139 //static assert(!is(typeof({struct InvalidInfo {string name;} Mapping!InvalidInfo invmapping;}))); 140 unittest 141 { 142 //import std.stdio; 143 struct Info 144 { 145 string name; 146 size_t id; 147 } 148 149 Mapping!(Info) mapping; 150 mapping.setMapping(["first", "second"]); 151 mapping.put(Info("third")); 152 assert(mapping[0].name == "first"); 153 assert(mapping[1].name == "second"); 154 assert(mapping[2].name == "third"); 155 156 Mapping!(Info, true) mappingTyped; 157 mappingTyped.setMapping(["first", "second"]); 158 mappingTyped.put!int(Info("third")); 159 assert(mappingTyped[0].name == "first"); 160 assert(mappingTyped[1].name == "second"); 161 assert(mappingTyped[2].name == "third"); 162 assert(mappingTyped.id!int == 2); 163 assert(mappingTyped.id!bool == size_t.max); 164 }