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 }