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 }