1 /++
2 Templates for wrapping D classes, properties, methods, and signals to be passed
3 to Godot's C interface.
4 +/
5 module godot.d.wrap;
6 
7 import std.range;
8 import std.meta, std.traits;
9 import std.experimental.allocator, std.experimental.allocator.mallocator;
10 import core.stdc.stdlib : malloc, free;
11 
12 import godot.d.udas;
13 import godot.d.traits, godot.d.script;
14 
15 import godot.core, godot.c;
16 import godot.node;
17 
18 private template staticCount(alias thing, seq...)
19 {
20 	template staticCountNum(size_t soFar, seq...)
21 	{
22 		enum size_t nextPos = staticIndexOf!(thing, seq);
23 		static if(nextPos == -1) enum size_t staticCountNum = soFar;
24 		else enum size_t staticCountNum = staticCountNum!(soFar+1, seq[nextPos+1..$]);
25 	}
26 	enum size_t staticCount = staticCountNum!(0, seq);
27 }
28 
29 private string overloadError(methods...)()
30 {
31 	alias godotNames = staticMap!(godotName, methods);
32 	foreach(m; methods)
33 	{
34 		static if(staticCount!(godotName!m, godotNames) > 1)
35 		{
36 			static assert(0, `Godot does not support overloading methods (`
37 				~ fullyQualifiedName!m ~ `, wrapped as "` ~ godotName!m ~
38 				`"); rename one with @Rename("new_name") or use Variant args`);
39 		}
40 	}
41 }
42 
43 package(godot) template godotMethods(T)
44 {
45 	alias mfs(alias mName) = MemberFunctionsTuple!(T, mName);
46 	alias allMfs = staticMap!(mfs, __traits(derivedMembers, T));
47 	enum bool isMethod(alias mf) = hasUDA!(mf, Method);
48 	
49 	alias godotMethods = Filter!(isMethod, allMfs);
50 	
51 	alias godotNames = staticMap!(godotName, godotMethods);
52 	static assert(godotNames.length == NoDuplicates!godotNames.length,
53 		overloadError!godotMethods());
54 }
55 
56 package(godot) template godotSignals(T)
57 {
58 	enum isSignalExpr(string n) = q{ isCallable!(mixin("T."~n))
59 		&& ( hasUDA!(mixin("T."~n), Signal) || is(ReturnType!(mixin("T."~n)) == Signal) ) };
60 	template isSignal(string n)
61 	{
62 		static if( __traits(compiles, mixin(isSignalExpr!n)) )
63 		{
64 			enum bool isSignal = mixin(isSignalExpr!n);
65 		}
66 		else enum bool isSignal = false;
67 	}
68 	alias godotSignals = Filter!(isSignal, __traits(derivedMembers, T));
69 }
70 
71 package(godot) template onReadyFieldNames(T)
72 {
73 	import godot.node;
74 	static if(!is(GodotClass!T : Node)) alias onReadyFieldNames = AliasSeq!();
75 	else
76 	{
77 		alias fieldNames = FieldNameTuple!T;
78 		template isORField(string n)
79 		{
80 			static if(staticIndexOf!(n, fieldNames) != -1 && staticIndexOf!(__traits
81 				(getProtection, __traits(getMember, T, n)), "public", "export") != -1)
82 			{
83 				enum bool isORField = hasUDA!(__traits(getMember, T, n), OnReady);
84 			}
85 			else enum bool isORField = false;
86 		}
87 		alias onReadyFieldNames = Filter!(isORField, __traits(derivedMembers, T));
88 	}
89 }
90 
91 package(godot) template godotPropertyGetters(T)
92 {
93 	alias mfs(alias mName) = MemberFunctionsTuple!(T, mName);
94 	alias allMfs = staticMap!(mfs, __traits(derivedMembers, T));
95 	template isGetter(alias mf)
96 	{
97 		enum bool isGetter = hasUDA!(mf, Property) && !is(ReturnType!mf == void);
98 	}
99 	
100 	alias godotPropertyGetters = Filter!(isGetter, allMfs);
101 	
102 	alias godotNames = Filter!(godotName, godotPropertyGetters);
103 	static assert(godotNames.length == NoDuplicates!godotNames.length,
104 		overloadError!godotPropertyGetters());
105 }
106 
107 package(godot) template godotPropertySetters(T)
108 {
109 	alias mfs(alias mName) = MemberFunctionsTuple!(T, mName);
110 	alias allMfs = staticMap!(mfs, __traits(derivedMembers, T));
111 	template isSetter(alias mf)
112 	{
113 		enum bool isSetter = hasUDA!(mf, Property) && is(ReturnType!mf == void);
114 	}
115 	
116 	alias godotPropertySetters = Filter!(isSetter, allMfs);
117 	
118 	alias godotNames = Filter!(godotName, godotPropertySetters);
119 	static assert(godotNames.length == NoDuplicates!godotNames.length,
120 		overloadError!godotPropertySetters());
121 }
122 
123 package(godot) template godotPropertyNames(T)
124 {
125 	alias godotPropertyNames = NoDuplicates!(staticMap!(godotName, godotPropertyGetters!T,
126 		godotPropertySetters!T));
127 }
128 
129 package(godot) template godotPropertyVariableNames(T)
130 {
131 	alias fieldNames = FieldNameTuple!T;
132 	alias field(string name) = Alias!(__traits(getMember, T, name));
133 	template isVariable(string name)
134 	{
135 		static if(__traits(getProtection, __traits(getMember, T, name))=="public")
136 			enum bool isVariable = hasUDA!(field!name, Property);
137 		else enum bool isVariable = false;
138 	}
139 	
140 	alias godotPropertyVariableNames = Filter!(isVariable, fieldNames);
141 }
142 
143 /// get the common Variant type for a set of function or variable aliases
144 package(godot) template extractPropertyVariantType(seq...)
145 {
146 	template Type(alias a)
147 	{
148 		static if(isFunction!a && is(ReturnType!a == void)) alias Type = Parameters!a[0];
149 		else static if(isFunction!a) alias Type = NonRef!(ReturnType!a);
150 		//else alias Type = typeof(a);
151 		
152 		static assert(Variant.compatible!Type, "Property type "~
153 			Type.stringof~" is incompatible with Variant.");
154 	}
155 	alias types = NoDuplicates!(  staticMap!( Variant.variantTypeOf, staticMap!(Type, seq) )  );
156 	static assert(types.length == 1); /// TODO: better error message
157 	enum extractPropertyVariantType = types[0];
158 }
159 
160 package(godot) template extractPropertyUDA(seq...)
161 {
162 	template udas(alias a)
163 	{
164 		alias udas = getUDAs!(a, Property);
165 	}
166 	enum bool isUDAValue(alias a) = !is(a);
167 	alias values = Filter!(isUDAValue, staticMap!(udas, seq));
168 	
169 	static if(values.length == 0) enum Property extractPropertyUDA = Property.init;
170 	else static if(values.length == 1) enum Property extractPropertyUDA = values[0];
171 	else
172 	{
173 		// verify that they all have the same value, to avoid wierdness
174 		enum Property extractPropertyUDA = values[0];
175 		enum bool isSameAsFirst(Property p) = extractPropertyUDA == p;
176 		static assert(allSatisfy!(isSameAsFirst, values[1..$]));
177 	}
178 }
179 
180 /++
181 Variadic template for method wrappers.
182 
183 Params:
184 	T = the class that owns the method
185 	mf = the member function being wrapped, as an alias
186 +/
187 package(godot) struct MethodWrapper(T, alias mf)
188 {
189 	alias R = ReturnType!mf; // the return type (can be void)
190 	alias A = Parameters!mf; // the argument types (can be empty)
191 	
192 	enum string name = __traits(identifier, mf);
193 	
194 	/++
195 	C function passed to Godot that calls the wrapped method
196 	+/
197 	extern(C) // for calling convention
198 	static godot_variant callMethod(godot_object o, void* methodData,
199 		void* userData, int numArgs, godot_variant** args)
200 	{
201 		// TODO: check types for Variant compatibility, give a better error here
202 		// TODO: check numArgs, accounting for D arg defaults
203 		
204 		godot_variant vd;
205 		_godot_api.godot_variant_new_nil(&vd);
206 		Variant* v = cast(Variant*)&vd; // just a pointer; no destructor will be called
207 		
208 		T obj = cast(T)userData;
209 		
210 		A[ai] variantToArg(size_t ai)()
211 		{
212 			return (cast(Variant*)args[ai]).as!(A[ai]);
213 		}
214 		template ArgCall(size_t ai)
215 		{
216 			alias ArgCall = variantToArg!ai; //A[i] function()
217 		}
218 		
219 		alias argIota = aliasSeqOf!(iota(A.length));
220 		alias argCall = staticMap!(ArgCall, argIota);
221 		
222 		static if(is(R == void))
223 		{
224 			mixin("obj." ~ name ~ "(argCall);");
225 		}
226 		else
227 		{
228 			mixin("*v = obj." ~ name ~ "(argCall);");
229 		}
230 		
231 		return vd;
232 	}
233 	
234 	/++
235 	C function passed to Godot if this is a property getter
236 	+/
237 	static if(!is(R == void) && A.length == 0)
238 	extern(C) // for calling convention
239 	static godot_variant callPropertyGet(godot_object o, void* methodData,
240 		void* userData)
241 	{
242 		godot_variant vd;
243 		_godot_api.godot_variant_new_nil(&vd);
244 		Variant* v = cast(Variant*)&vd; // just a pointer; no destructor will be called
245 		
246 		T obj = cast(T)userData;
247 		
248 		mixin("*v = obj." ~ name ~ "();");
249 		
250 		return vd;
251 	}
252 	
253 	/++
254 	C function passed to Godot if this is a property setter
255 	+/
256 	static if(is(R == void) && A.length == 1)
257 	extern(C) // for calling convention
258 	static void callPropertySet(godot_object o, void* methodData,
259 		void* userData, godot_variant* arg)
260 	{
261 		Variant* v = cast(Variant*)arg;
262 		
263 		T obj = cast(T)userData;
264 		
265 		auto vt = v.as!(A[0]);
266 		mixin("obj." ~ name ~ "(vt);");
267 	}
268 }
269 
270 package(godot) struct OnReadyWrapper(T) if(is(GodotClass!T : Node))
271 {
272 	extern(C) // for calling convention
273 	static godot_variant callOnReady(godot_object o, void* methodData,
274 		void* userData, int numArgs, godot_variant** args)
275 	{
276 		T t = cast(T)userData;
277 		
278 		foreach(n; onReadyFieldNames!T)
279 		{
280 			alias udas = getUDAs!(__traits(getMember, T, n), OnReady);
281 			static assert(udas.length == 1, "Multiple OnReady UDAs on "~T.stringof~"."~n);
282 			
283 			alias A = Alias!(TemplateArgsOf!(udas[0])[0]);
284 			alias F = typeof(mixin("T."~n));
285 			
286 			// First, determine where to obtain the value to assign, and put it in `result`.
287 			// `result` will be alias to void if nothing to assign.
288 			static if(isCallable!A)
289 			{
290 				// pass the class itself to the function
291 				static if(Parameters!A.length && isImplicitlyConvertible!(T, Parameters!A[0]))
292 					alias arg = t;
293 				else alias arg = AliasSeq!();
294 				static if(is(ReturnType!A == void))
295 				{
296 					alias result = void;
297 					A(arg);
298 				}
299 				else
300 				{
301 					auto result = A(arg); /// temp variable for return value
302 				}
303 			}
304 			else static if(is(A)) static assert(0, "OnReady arg can't be a type");
305 			else static if(isExpressions!A) // expression (string literal, etc)
306 			{
307 				alias result = A;
308 			}
309 			else // some other alias (a different variable identifier?)
310 			{
311 				static if(__traits(compiles, __traits(parent, A)))
312 					alias P = Alias!(__traits(parent, A));
313 				else alias P = void;
314 				static if(is(T : P))
315 				{
316 					// A is another variable inside this very same T
317 					auto result = __traits(getMember, t, __traits(identifier, A));
318 				}
319 				else alias result = A; // final fallback: pass it unmodified to assignment
320 			}
321 			
322 			// Second, assign `result` to the field depending on the types of it and `result`
323 			static if(!is(result == void))
324 			{
325 				import godot.resource;
326 				
327 				static if(isImplicitlyConvertible!(typeof(result), F))
328 				{
329 					// direct assignment
330 					mixin("t."~n) = result;
331 				}
332 				else static if(__traits(compiles, mixin("t."~n) = F(result)))
333 				{
334 					// explicit constructor (String(string), NodePath(string), etc)
335 					mixin("t."~n) = F(result);
336 				}
337 				else static if(isGodotClass!F && extends!(F, Node))
338 				{
339 					// special case: node path
340 					mixin("t."~n) = cast(F)t.owner.getNode(result);
341 				}
342 				else static if(isGodotClass!F && extends!(F, Resource))
343 				{
344 					// special case: resource load path
345 					import godot.resourceloader;
346 					mixin("t."~n) = cast(F)ResourceLoader.load(result);
347 				}
348 				else static assert(0, "Don't know how to assign "~typeof(result).stringof~" "~result.stringof~
349 					" to "~F.stringof~" "~fullyQualifiedName!(mixin("t."~n)));
350 			}
351 		}
352 		
353 		// Finally, call the actual _ready() if it exists.
354 		enum bool isReady(alias func) = "_ready" == godotName!func;
355 		alias readies = Filter!(isReady, godotMethods!T);
356 		static if(readies.length) mixin("t."~__traits(identifier, readies[0])~"();");
357 		
358 		godot_variant nil;
359 		_godot_api.godot_variant_new_nil(&nil);
360 		return nil;
361 	}
362 }
363 
364 /++
365 Template for public variables exported as properties.
366 
367 Params:
368 	T = the class that owns the variable
369 	var = the name of the member variable being wrapped
370 +/
371 package(godot) struct VariableWrapper(T, string var)
372 {
373 	import godot.reference, godot.d.reference;
374 	alias P = typeof(mixin("T."~var));
375 	static if(extends!(P, Reference)) static assert(is(P : Ref!U, U),
376 		"Reference type property "~T.stringof~"."~var~" must be ref-counted as Ref!("
377 		~P.stringof~")");
378 	
379 	extern(C) // for calling convention
380 	static godot_variant callPropertyGet(godot_object o, void* methodData,
381 		void* userData)
382 	{
383 		T obj = cast(T)userData;
384 		
385 		godot_variant vd;
386 		_godot_api.godot_variant_new_nil(&vd);
387 		Variant* v = cast(Variant*)&vd; // just a pointer; no destructor will be called
388 		
389 		*v = mixin("obj."~var);
390 		
391 		return vd;
392 	}
393 	
394 	extern(C) // for calling convention
395 	static void callPropertySet(godot_object o, void* methodData,
396 		void* userData, godot_variant* arg)
397 	{
398 		T obj = cast(T)userData;
399 		
400 		Variant* v = cast(Variant*)arg;
401 		
402 		auto vt = v.as!P;
403 		mixin("obj."~var) = vt;
404 	}
405 }
406 
407 extern(C) package(godot) void emptySetter(godot_object self, void* methodData,
408 	void* userData, godot_variant* value)
409 {
410 	assert(0, "Can't call empty property setter");
411 	//return;
412 }
413 
414 extern(C) package(godot) godot_variant emptyGetter(godot_object self, void* methodData,
415 	void* userData)
416 {
417 	assert(0, "Can't call empty property getter");
418 	/+godot_variant v;
419 	_godot_api.godot_variant_new_nil(&v);
420 	return v;+/
421 }
422