1 /** 2 Copyright: Copyright (c) 2016 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 requiered 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 newArray.reserve(names.length); 91 92 foreach(i, name; names) 93 { 94 size_t index = nameToIndexMap.get(name, size_t.max); 95 size_t newId = newArray.length; 96 97 if (index == size_t.max) 98 { 99 InfoType info; 100 info.name = name; 101 newArray ~= info; 102 } 103 else 104 { 105 newArray ~= infoArray[index]; 106 infoArray[index].id = size_t.max; // Mark as removed 107 } 108 newArray[$-1].id = newId; 109 } 110 infoArray = newArray; 111 112 size_t[string] newMap; 113 foreach(ref info; infoArray) 114 { 115 newMap[info.name] = info.id; 116 } 117 nameToIndexMap = newMap; 118 } 119 } 120 121 //static assert(__traits(compiles, {struct ValidInfo {size_t id; string name;} Mapping!ValidInfo m;})); 122 //static assert(!is(typeof({struct InvalidInfo {} Mapping!InvalidInfo invmapping;}))); 123 //static assert(!is(typeof({struct InvalidInfo {size_t id;} Mapping!InvalidInfo invmapping;}))); 124 //static assert(!is(typeof({struct InvalidInfo {string name;} Mapping!InvalidInfo invmapping;}))); 125 unittest 126 { 127 //import std.stdio; 128 struct Info 129 { 130 string name; 131 size_t id; 132 } 133 134 Mapping!(Info) mapping; 135 mapping.setMapping(["first", "second"]); 136 mapping.put(Info("third")); 137 assert(mapping[0].name == "first"); 138 assert(mapping[1].name == "second"); 139 assert(mapping[2].name == "third"); 140 141 Mapping!(Info, true) mappingTyped; 142 mappingTyped.setMapping(["first", "second"]); 143 mappingTyped.put!int(Info("third")); 144 assert(mappingTyped[0].name == "first"); 145 assert(mappingTyped[1].name == "second"); 146 assert(mappingTyped[2].name == "third"); 147 assert(mappingTyped.id!int == 2); 148 assert(mappingTyped.id!bool == size_t.max); 149 }