1 /**
2  * Defines the static class Config, which handles all configuration options.
3  */
4 module utility.config;
5 import utility.filepath;
6 
7 // Imports for conversions
8 import core.dgame : GameState;
9 import components.assets, components.lights;
10 import graphics.shaders;
11 import utility.output : Verbosity;
12 import utility.input : Keyboard;
13 
14 import gl3n.linalg;
15 import yaml;
16 
17 import std.array, std.conv, std..string, std.path, std.typecons, std.variant;
18 
19 /**
20  * Static class which handles the configuration options and YAML interactions.
21  */
22 final abstract class Config
23 {
24 public static:
25 	final void initialize()
26 	{
27 		constructor = new Constructor;
28 
29 		constructor.addConstructorScalar( "!Vector2", &constructVector2 );
30 		constructor.addConstructorMapping( "!Vector2-Map", &constructVector2 );
31 		constructor.addConstructorScalar( "!Vector3", &constructVector3 );
32 		constructor.addConstructorMapping( "!Vector3-Map", &constructVector2 );
33 		constructor.addConstructorScalar( "!Quaternion", &constructQuaternion );
34 		constructor.addConstructorMapping( "!Quaternion-Map", &constructQuaternion );
35 		constructor.addConstructorScalar( "!GameState", &constructConv!GameState );
36 		constructor.addConstructorScalar( "!Verbosity", &constructConv!Verbosity );
37 		constructor.addConstructorScalar( "!Keyboard", &constructConv!Keyboard );
38 		constructor.addConstructorScalar( "!Shader", ( ref Node node ) => Shaders.get( node.get!string ) );
39 		constructor.addConstructorMapping( "!Light-Directional", &constructDirectionalLight );
40 		constructor.addConstructorMapping( "!Light-Ambient", &constructAmbientLight );
41 		//constructor.addConstructorScalar( "!Texture", ( ref Node node ) => Assets.get!Texture( node.get!string ) );
42 		//constructor.addConstructorScalar( "!Mesh", ( ref Node node ) => Assets.get!Mesh( node.get!string ) );
43 		//constructor.addConstructorScalar( "!Material", ( ref Node node ) => Assets.get!Material( node.get!string ) );
44 
45 		config = loadYaml( FilePath.Resources.ConfigFile );
46 	}
47 
48 	/**
49 	 * Load a yaml file with the engine-specific mappings.
50 	 */
51 	final Node loadYaml( string path )
52 	{
53 		auto loader = Loader( path );
54 		loader.constructor = constructor;
55 		return loader.load();
56 	}
57 
58 	/**
59 	 * Process all yaml files in a directory, and call the callback with all the root level nodes.
60 	 */
61 	final void processYamlDirectory( string folder, void delegate( Node ) callback )
62 	{
63 		foreach( file; FilePath.scanDirectory( folder, "*.yml" ) )
64 		{
65 			auto object = Config.loadYaml( file.fullPath );
66 
67 			if( object.isSequence() )
68 				foreach( Node innerObj; object )
69 					callback( innerObj );
70 			else
71 				callback( object );
72 		}
73 	}
74 
75 	/**
76 	 * Get the element, cast to the given type, at the given path, in the given node.
77 	 */
78 	final T get( T )( string path, Node node = config )
79 	{
80 		Node current = node;
81 		string left;
82 		string right = path;
83 
84 		while( true )
85 		{
86 			auto split = right.indexOf( '.' );
87 			if( split == -1 )
88 			{
89 				return current[ right ].get!T;
90 			}
91 			else
92 			{
93 				left = right[ 0..split ];
94 				right = right[ split + 1..$ ];
95 				current = current[ left ];
96 			}
97 		}
98 	}
99 
100 	/**
101 	* Try to get the value at path, assign to result, and return success.
102 	*/
103 	final bool tryGet( T )( string path, ref T result, Node node = config )
104 	{
105 		Node res;
106 		bool found = tryGet( path, res, node );
107 		if( found )
108 			result = res.get!T;
109 		return found;
110 	}
111 
112 	/// ditto
113 	final bool tryGet( T: Node )( string path, ref T result, Node node = config )
114 	{
115 		Node current;
116 		string left;
117 		string right = path;
118 
119 		for( current = node; right.length; )
120 		{
121 			auto split = right.indexOf( '.' );
122 
123 			if( split == -1 )
124 			{
125 				left = right;
126 				right.length = 0;
127 			}
128 			else
129 			{
130 				left = right[ 0..split ];
131 				right = right[ split + 1..$ ];
132 			}
133 
134 			if( !current.isMapping || !current.containsKey( left ) )
135 				return false;
136 
137 			current = current[ left ];
138 		}
139 
140 		result = current;
141 		return true;
142 	}
143 
144 	/// ditto
145 	final bool tryGet( T = Node )( string path, ref Variant result, Node node = config )
146 	{
147 		// Get the value
148 		T temp;
149 		bool found = tryGet!T( path, temp, node );
150 
151 		// Assign and return results
152 		if( found )
153 			result = temp;
154 
155 		return found;
156 	}
157 
158 	@disable bool tryGet( T: Variant )( string path, ref T result, Node node = config );
159 
160 	/**
161 	 * Get element as a file path.
162 	 */
163 	final string getPath( string path )
164 	{
165 		return FilePath.ResourceHome ~ get!string( path );//buildNormalizedPath( FilePath.ResourceHome, get!string( path ) );;
166 	}
167 
168 private:
169 	Node config;
170 	Constructor constructor;
171 }
172 
173 vec2 constructVector2( ref Node node )
174 {
175 	vec2 result;
176 
177 	if( node.isMapping )
178 	{
179 		result.x = node[ "x" ].as!float;
180 		result.y = node[ "y" ].as!float;
181 	}
182 	else if( node.isScalar )
183 	{
184 		string[] vals = node.as!string.split();
185 
186 		if( vals.length != 2 )
187 		{
188 			throw new Exception( "Invalid number of values: " ~ node.as!string );
189 		}
190 
191 		result.x = vals[ 0 ].to!float;
192 		result.y = vals[ 1 ].to!float;
193 	}
194 
195 	return result;
196 }
197 
198 vec3 constructVector3( ref Node node )
199 {
200 	vec3 result;
201 
202 	if( node.isMapping )
203 	{
204 		result.x = node[ "x" ].as!float;
205 		result.y = node[ "y" ].as!float;
206 		result.z = node[ "z" ].as!float;
207 	}
208 	else if( node.isScalar )
209 	{
210 		string[] vals = node.as!string.split();
211 
212 		if( vals.length != 3 )
213 		{
214 			throw new Exception( "Invalid number of values: " ~ node.as!string );
215 		}
216 
217 		result.x = vals[ 0 ].to!float;
218 		result.y = vals[ 1 ].to!float;
219 		result.z = vals[ 2 ].to!float;
220 	}
221 
222 	return result;
223 }
224 
225 quat constructQuaternion( ref Node node )
226 {
227 	quat result;
228 
229 	if( node.isMapping )
230 	{
231 		result.x = node[ "x" ].as!float;
232 		result.y = node[ "y" ].as!float;
233 		result.z = node[ "z" ].as!float;
234 		result.w = node[ "w" ].as!float;
235 	}
236 	else if( node.isScalar )
237 	{
238 		string[] vals = node.as!string.split();
239 
240 		if( vals.length != 3 )
241 		{
242 			throw new Exception( "Invalid number of values: " ~ node.as!string );
243 		}
244 
245 		result.x = vals[ 0 ].to!float;
246 		result.y = vals[ 1 ].to!float;
247 		result.z = vals[ 2 ].to!float;
248 		result.w = vals[ 3 ].to!float;
249 	}
250 
251 	return result;
252 }
253 
254 Light constructDirectionalLight( ref Node node )
255 {
256 	vec3 color;
257 	vec3 dir;
258 
259 	Config.tryGet( "Color", color, node );
260 	Config.tryGet( "Direction", dir, node );
261 
262 	return new DirectionalLight( color, dir );
263 }
264 
265 Light constructAmbientLight( ref Node node )
266 {
267 	vec3 color;
268 	Config.tryGet( "Color", color, node );
269 
270 	return new AmbientLight( color );
271 }
272 
273 T constructConv( T )( ref Node node ) if( is( T == enum ) )
274 {
275 	if( node.isScalar )
276 	{
277 		return node.as!string.to!T;
278 	}
279 	else
280 	{
281 		throw new Exception( "Enum must be represented as a scalar." );
282 	}
283 }
284 
285 unittest
286 {
287 	import std.stdio;
288 	writeln( "Dash Config get unittest" );
289 
290 	auto n1 = Node( [ "test1": 10 ] );
291 
292 	assert( Config.get!int( "test1", n1 ) == 10, "Config.get error." );
293 
294 	try
295 	{
296 		Config.get!int( "dontexist", n1 );
297 		assert( false, "Config.get didn't throw." );
298 	}
299 	catch { }
300 }
301 unittest
302 {
303 	import std.stdio;
304 	writeln( "Dash Config tryGet unittest" );
305 
306 	auto n1 = Node( [ "test1": 10 ] );
307 
308 	int val;
309 	assert( Config.tryGet( "test1", val, n1 ), "Config.tryGet failed." );
310 	assert( !Config.tryGet( "dontexist", val, n1 ), "Config.tryGet returned true." );
311 }
312