1 /++
2 Templates for wrapping D classes, properties, methods, and signals to be passed
3 to Godot's C interface.
4 +/
5 module godot.api.wrap;
6 
7 import std.algorithm : max;
8 import std.range;
9 import std.meta, std.traits;
10 import std.experimental.allocator, std.experimental.allocator.mallocator;
11 import core.stdc.stdlib : malloc, free;
12 
13 import godot.api.udas;
14 import godot.api.traits, godot.api.script;
15 
16 import godot, godot.abi;
17 import godot.node;
18 
19 private template staticCount(alias thing, seq...) {
20     template staticCountNum(size_t soFar, seq...) {
21         enum size_t nextPos = staticIndexOf!(thing, seq);
22         static if (nextPos == -1)
23             enum size_t staticCountNum = soFar;
24         else
25             enum size_t staticCountNum = staticCountNum!(soFar + 1, seq[nextPos + 1 .. $]);
26     }
27 
28     enum size_t staticCount = staticCountNum!(0, seq);
29 }
30 
31 private string overloadError(methods...)() {
32     alias godotNames = staticMap!(godotName, methods);
33     foreach (m; methods) {
34         static if (staticCount!(godotName!m, godotNames) > 1) {
35             static assert(0, `Godot does not support overloading methods (`
36                     ~ fullyQualifiedName!m ~ `, wrapped as "` ~ godotName!m ~
37                     `"); rename one with @Rename("new_name") or use Variant args`);
38         }
39     }
40 }
41 
42 package(godot) template godotMethods(T) {
43     // Makes tuple of member functions excluding function pointers
44     // this is basically std.traits.MemberFunctionsTuple but with static methods
45     template mfs(alias mName) {
46         static if (isSomeFunction!(__traits(getMember, T, mName)) 
47                && !isFunctionPointer!(__traits(getMember, T, mName))) {
48             static if (__traits(getOverloads, T, mName).length) {
49                 alias mfs = __traits(getOverloads, T, mName);
50             }
51             else {
52                 alias mfs = __traits(getMember, T, mName);
53             }
54         }
55         else {
56             alias mfs = AliasSeq!();
57         }
58     }
59     
60     alias allMfs = staticMap!(mfs, __traits(derivedMembers, T));
61     enum bool isMethod(alias mf) = hasUDA!(mf, Method);
62 
63     alias godotMethods = Filter!(isMethod, allMfs);
64 
65     alias godotNames = staticMap!(godotName, godotMethods);
66     static assert(godotNames.length == NoDuplicates!godotNames.length,
67         overloadError!godotMethods());
68 }
69 
70 package(godot) template godotSignals(T) {
71     enum isSignalExpr(string n) = q{ isCallable!(mixin("T."~n))
72 		&& ( hasUDA!(mixin("T."~n), Signal) || is(ReturnType!(mixin("T."~n)) == Signal) ) };
73     template isSignal(string n) {
74         static if (__traits(compiles, mixin(isSignalExpr!n))) {
75             enum bool isSignal = mixin(isSignalExpr!n);
76         } else
77             enum bool isSignal = false;
78     }
79 
80     alias godotSignals = Filter!(isSignal, __traits(derivedMembers, T));
81 }
82 
83 package(godot) template onReadyFieldNames(T) {
84     import godot.node;
85 
86     static if (!is(GodotClass!T : Node))
87         alias onReadyFieldNames = AliasSeq!();
88     else {
89         alias fieldNames = FieldNameTuple!T;
90         template isORField(string n) {
91             static if (staticIndexOf!(n, fieldNames) != -1 && staticIndexOf!(__traits(getProtection, __traits(
92                     getMember, T, n)), "public", "export") != -1) {
93                 enum bool isORField = hasUDA!(__traits(getMember, T, n), OnReady);
94             } else
95                 enum bool isORField = false;
96         }
97 
98         alias onReadyFieldNames = Filter!(isORField, __traits(derivedMembers, T));
99     }
100 }
101 
102 package(godot) template godotPropertyGetters(T) {
103     alias mfs(alias mName) = MemberFunctionsTuple!(T, mName);
104     alias allMfs = staticMap!(mfs, __traits(derivedMembers, T));
105     template isGetter(alias mf) {
106         enum bool isGetter = hasUDA!(mf, Property) && !is(ReturnType!mf == void);
107     }
108 
109     alias godotPropertyGetters = Filter!(isGetter, allMfs);
110 
111     alias godotNames = Filter!(godotName, godotPropertyGetters);
112     static assert(godotNames.length == NoDuplicates!godotNames.length,
113         overloadError!godotPropertyGetters());
114 }
115 
116 package(godot) template godotPropertySetters(T) {
117     alias mfs(alias mName) = MemberFunctionsTuple!(T, mName);
118     alias allMfs = staticMap!(mfs, __traits(derivedMembers, T));
119     template isSetter(alias mf) {
120         enum bool isSetter = hasUDA!(mf, Property) && is(ReturnType!mf == void);
121     }
122 
123     alias godotPropertySetters = Filter!(isSetter, allMfs);
124 
125     alias godotNames = Filter!(godotName, godotPropertySetters);
126     static assert(godotNames.length == NoDuplicates!godotNames.length,
127         overloadError!godotPropertySetters());
128 }
129 
130 package(godot) template godotPropertyNames(T) {
131     alias godotPropertyNames = NoDuplicates!(staticMap!(godotName, godotPropertyGetters!T,
132             godotPropertySetters!T));
133 }
134 
135 package(godot) template godotEnums(T) {
136     import std.traits;
137 
138     alias mfs(alias mName) = __traits(getMember, T, mName);
139     alias allMfs = staticMap!(mfs, __traits(derivedMembers, T));
140     template isEnum(alias mf) {
141         static if (is(mf Base == enum) && isIntegral!Base)
142             enum bool isEnum = hasUDA!(mf, Enum);
143         else
144             enum bool isEnum = false;
145     }
146 
147     alias godotEnums = Filter!(isEnum, allMfs);
148 }
149 
150 package(godot) template godotConstants(T) {
151     import std.traits;
152 
153     alias mfs(alias mName) = Alias!(__traits(getMember, T, mName));
154     alias allMfs = staticMap!(mfs, __traits(derivedMembers, T));
155 
156     template isCompileTimeValue(alias V, T...)
157             if (T.length == 0 || (T.length == 1 && is(T[0]))) {
158         enum isKnown = is(typeof(() { enum v = V; }));
159         static if (!T.length)
160             enum isCompileTimeValue = isKnown;
161         else
162             enum isCompileTimeValue = isKnown && is(typeof(V) == T[0]);
163     }
164 
165     template isConstant(alias mf) {
166         static if (isCompileTimeValue!mf) {
167             enum bool isConstant = hasUDA!(mf, Constant);
168         } else
169             enum bool isConstant = false;
170     }
171 
172     enum isConstantMember(string m) = isConstant!(__traits(getMember, T, m));
173     alias godotConstants = Filter!(isConstantMember, __traits(derivedMembers, T));
174     //pragma(msg, filtered);
175     //alias godotConstants = Filter!(isConstant, allMfs);
176 }
177 
178 package(godot) template godotPropertyVariableNames(T) {
179     alias fieldNames = FieldNameTuple!T;
180     alias field(string name) = Alias!(__traits(getMember, T, name));
181     template isVariable(string name) {
182         static if (__traits(getProtection, __traits(getMember, T, name)) == "public")
183             enum bool isVariable = hasUDA!(field!name, Property);
184         else
185             enum bool isVariable = false;
186     }
187 
188     alias godotPropertyVariableNames = Filter!(isVariable, fieldNames);
189 }
190 
191 package(godot) template godotSingletonVariableNames(T) {
192     import std.traits;
193     alias fieldNames = AliasSeq!(__traits(derivedMembers, T));
194     alias field(string name) = Alias!(__traits(getMember, T, name));
195     template isSingleton(string name) {
196         static if (__traits(getProtection, __traits(getMember, T, name)) == "public" && hasStaticMember!(T, name))
197             enum bool isSingleton = hasUDA!(field!name, Singleton);
198         else
199             enum bool isSingleton = false;
200     }
201 
202     alias godotSingletonVariableNames = Filter!(isSingleton, fieldNames);
203 }
204 
205 /// get the common Variant type for a set of function or variable aliases
206 package(godot) template extractPropertyVariantType(seq...) {
207     template Type(alias a) {
208         static if (isFunction!a && is(ReturnType!a == void))
209             alias Type = Parameters!a[0];
210         else static if (isFunction!a)
211             alias Type = NonRef!(ReturnType!a);
212         //else alias Type = typeof(a);
213 
214         static assert(Variant.compatible!Type, "Property type " ~
215                 Type.stringof ~ " is incompatible with Variant.");
216     }
217 
218     alias types = NoDuplicates!(staticMap!(Variant.variantTypeOf, staticMap!(Type, seq)));
219     static assert(types.length == 1); /// TODO: better error message
220     enum extractPropertyVariantType = types[0];
221 }
222 
223 package(godot) template extractPropertyUDA(seq...) {
224     template udas(alias a) {
225         alias udas = getUDAs!(a, Property);
226     }
227 
228     enum bool isUDAValue(alias a) = !is(a);
229     alias values = Filter!(isUDAValue, staticMap!(udas, seq));
230 
231     static if (values.length == 0)
232         enum Property extractPropertyUDA = Property.init;
233     else static if (values.length == 1)
234         enum Property extractPropertyUDA = values[0];
235     else {
236         // verify that they all have the same value, to avoid wierdness
237         enum Property extractPropertyUDA = values[0];
238         enum bool isSameAsFirst(Property p) = extractPropertyUDA == p;
239         static assert(allSatisfy!(isSameAsFirst, values[1 .. $]));
240     }
241 }
242 
243 /++
244 Variadic template for method wrappers.
245 
246 Params:
247 	T = the class that owns the method
248 	mf = the member function being wrapped, as an alias
249 +/
250 package(godot) struct MethodWrapper(T, alias mf) {
251     alias R = ReturnType!mf; // the return type (can be void)
252     alias A = Parameters!mf; // the argument types (can be empty)
253 
254     enum string name = __traits(identifier, mf);
255 
256     // Used later instead of string comparison
257     __gshared static GDExtensionStringNamePtr funName;
258 
259     /++
260 	C function passed to Godot that calls the wrapped method
261 	+/
262     extern (C) // for calling convention
263     static void callMethod(void* methodData, void* instance,
264         const(void**) args, const long numArgs, void* r_return, GDExtensionCallError* r_error) //@nogc nothrow
265         {
266         // TODO: check types for Variant compatibility, give a better error here
267         // TODO: check numArgs, accounting for D arg defaults
268 
269         if (!(__traits(isStaticFunction, mf) || instance)) {
270             r_error.error = GDEXTENSION_CALL_ERROR_INSTANCE_IS_NULL;
271             return;
272         }
273 
274         //godot_variant vd;
275         //_godot_api.variant_new_nil(&vd);
276         //Variant* v = cast(Variant*)&vd; // just a pointer; no destructor will be called
277         Variant v;
278 
279         // basically what we want here...
280         //Variant*[] va = (cast(Variant**) args)[0..numArgs];
281         // however there is also default params that we need to place here
282         scope Variant*[Parameters!mf.length + 1] va;
283         scope Variant[ParameterDefaults!mf.length] defaults;
284         static foreach (i, defval; ParameterDefaults!mf) {
285             // should never happen
286             static if (is(defval == void))
287                 defaults[i] = Variant();
288             else
289                 defaults[i] = Variant(defval);
290         }
291 
292         if (args && numArgs)
293             va[0 .. numArgs] = (cast(Variant**) args)[0 .. numArgs];
294         if (args && numArgs < defaults.length) // <-- optional parameters that godot decided not to pass
295         {
296             foreach (i; 0 .. defaults.length)
297                 va[max(0, numArgs) + i] = &defaults[i];
298         }
299 
300         // it seems to work with static calls without this alias,
301         // but let's make it a bit more safe
302         static if (__traits(isStaticFunction, mf)) {
303             alias obj = T;
304         }
305         else {
306             T obj = cast(T) instance;
307         } 
308 
309         A[ai] variantToArg(size_t ai)() {
310             // should also be string and array?
311             //static if (is(A[ai] == NodePath))
312             //{
313             //	import godot.api;
314             //	print(*va[ai]);
315             //	return (*va[ai]).as!(A[ai]);
316             //}
317             //else
318             //return (cast(Variant*)args[ai]).as!(A[ai]);
319             // TODO: properly fix double, it returns pointer instead of value itself
320             static if (isFloatingPoint!(A[ai])) {
321                 return **cast(A[ai]**)&va[ai];
322             } else {
323                 return va[ai].as!(A[ai]);
324             }
325         }
326 
327         template ArgCall(size_t ai) {
328             alias ArgCall = variantToArg!ai; //A[i] function()
329         }
330 
331         alias argIota = aliasSeqOf!(iota(A.length));
332         alias argCall = staticMap!(ArgCall, argIota);
333 
334         static if (is(R == void)) {
335             mixin("obj." ~ name ~ "(argCall);");
336         } else {
337             // allow Variant to be returned as is, i.e. no wrapping
338             static if (is(R == Variant)) {
339                 mixin("v = obj." ~ name ~ "(argCall);");
340             }
341             else {
342                 mixin("v = Variant(obj." ~ name ~ "(argCall));");
343             }
344 
345             if (r_return && v._godot_variant._opaque.ptr) {
346                 //*cast(godot_variant*) r_return = vd;   // since alpha 12 instead of this now have to copy it
347                 _godot_api.variant_new_copy(r_return, &v._godot_variant); // since alpha 12 this is now the case
348             }
349         }
350         //return vd;
351     }
352 
353     extern (C)
354     static void callPtrMethod(void* methodData, void* instance,
355         const(void**) args, void* r_return) {
356         //GDExtensionCallError err;
357         //callMethod(methodData, instance, args, A.length, r_return, &err);
358 
359         T obj = cast(T) instance;
360 
361         A[ai] nativeToArg(size_t ai)() {
362             return (*cast(A[ai]*) args[ai]);
363         }
364 
365         template ArgCall(size_t ai) {
366             alias ArgCall = nativeToArg!ai; //A[i] function()
367         }
368 
369         alias argIota = aliasSeqOf!(iota(A.length));
370         alias argCall = staticMap!(ArgCall, argIota);
371 
372         static if (is(R == void)) {
373             mixin("obj." ~ name ~ "(argCall);");
374         } else {
375             mixin("*(cast(R*) r_return) = obj." ~ name ~ "(argCall);");
376         }
377     }
378 
379     extern (C)
380     static void virtualCall(GDExtensionClassInstancePtr instance, const GDExtensionTypePtr* args, GDExtensionTypePtr ret) {
381         GDExtensionCallError err;
382         callMethod(&mf, instance, args, Parameters!mf.length, ret, &err);
383     }
384 }
385 
386 package(godot) struct MethodWrapperMeta(alias mf) {
387     alias R = ReturnType!mf; // the return type (can be void)
388     alias A = Parameters!mf; // the argument types (can be empty)
389 
390     //enum string name = __traits(identifier, mf);
391 
392     // GDExtensionClassMethodGetArgumentType signature:
393     //   GDExtensionVariantType function(void *p_method_userdata, int32_t p_argument)
394     extern (C)
395     static GDExtensionVariantType* getArgTypes() {
396         // fill array of argument types and use cached data
397         import godot.variant;
398         import std.meta : staticMap;
399 
400         immutable __gshared static VariantType[A.length] argInfo = [
401             staticMap!(Variant.variantTypeOf, A)
402         ];
403         return cast(GDExtensionVariantType*) argInfo.ptr;
404     }
405 
406     // yeah, it says return types, godot goes brrr
407     extern (C)
408     static GDExtensionVariantType* getReturnTypes() {
409         // fill array of argument types and use cached data
410         import godot.variant;
411         immutable __gshared static VariantType[2] retInfo = [Variant.variantTypeOf!R, VariantType.nil ];
412         return cast(GDExtensionVariantType*) retInfo.ptr;
413     }
414 
415     // function parameter type information
416     extern (C)
417     static GDExtensionPropertyInfo[A.length+1] getArgInfo() {
418         GDExtensionPropertyInfo[A.length + 1] argInfo;
419         __gshared static StringName[A.length+1] snClassNames = void;
420         __gshared static StringName[A.length+1] snArgNames = void;
421         __gshared static StringName[A.length+1] snHintStrings = void;
422         static foreach (i; 0 .. A.length) {
423             if (Variant.variantTypeOf!(A[i]) == VariantType.object) {
424                 snClassNames[i] = StringName(A[i].stringof);
425             }
426             else {
427                 snClassNames[i] = stringName();
428             }
429             snArgNames[i] = StringName((ParameterIdentifierTuple!mf)[i]);
430             snHintStrings[i] = stringName();
431             argInfo[i].class_name = cast(GDExtensionStringNamePtr) snClassNames[i];
432             argInfo[i].name = cast(GDExtensionStringNamePtr) snArgNames[i];
433             argInfo[i].type = Variant.variantTypeOf!(A[i]);
434             argInfo[i].usage = GDEXTENSION_METHOD_FLAGS_DEFAULT;
435             argInfo[i].hint_string = cast(GDExtensionStringNamePtr) snHintStrings[i];
436         }
437         return argInfo;
438     }
439 
440     // return type information
441     extern (C)
442     static GDExtensionPropertyInfo[2] getReturnInfo() {
443         // FIXME: StringName makes it no longer CTFE-able
444         static if (is(R == Variant)) {
445             import godot.globalenums : PropertyUsageFlags;
446             enum propUsageFlags = PropertyUsageFlags.propertyUsageNilIsVariant;
447             // fallback value in case for some reason this enum will go away
448             //enum propUsageFlags = 131072;
449         }
450         else {
451             enum propUsageFlags = 0;
452         }
453         __gshared static StringName snName = void;
454         __gshared static StringName snClassName = void;
455         __gshared static StringName snHint = void;
456 
457         // shouldn't this be the opposite?
458         static if (Variant.variantTypeOf!R == VariantType.object) {
459             snClassName = stringName();
460         } 
461         else {
462             snClassName = StringName(R.stringof);
463         }
464         snName = stringName();
465         snHint = stringName();
466 
467         GDExtensionPropertyInfo[2] retInfo = [ 
468             GDExtensionPropertyInfo(
469                 cast(uint32_t) Variant.variantTypeOf!R,
470                 cast(GDExtensionStringNamePtr) snName,
471                 cast(GDExtensionStringNamePtr) snClassName,
472                 0, // aka PropertyHint.propertyHintNone
473                 cast(GDExtensionStringNamePtr) snHint,
474                 propUsageFlags
475             ), 
476             GDExtensionPropertyInfo.init 
477         ];
478         return retInfo;
479     }
480 
481     // metadata array for argument types
482     extern (C)
483     static GDExtensionClassMethodArgumentMetadata* getArgMetadata() {
484         __gshared static GDExtensionClassMethodArgumentMetadata[A.length] argInfo = GDEXTENSION_METHOD_ARGUMENT_METADATA_NONE;
485         return argInfo.ptr;
486     }
487 
488     // metadata for return type
489     extern (C)
490     static GDExtensionClassMethodArgumentMetadata getReturnMetadata() {
491         return GDEXTENSION_METHOD_ARGUMENT_METADATA_NONE;
492     }
493 
494     import std.traits;
495 
496     private enum bool notVoid(alias T) = !is(T == void);
497     enum getDefaultArgNum = cast(int32_t) Filter!(notVoid, ParameterDefaults!mf).length;
498     //enum getDefaultArgNum = cast(int32_t) Parameters!mf.length;
499 
500     // this function expected to return Variant[] containing default values
501     extern (C)
502     static GDExtensionVariantPtr* getDefaultArgs() {
503         //pragma(msg, "fn: ", __traits(identifier, mf), " > ",  ParameterDefaults!mf);
504 
505         // just pick any possible property for now
506         //alias udas = getUDAs!(mf, Property);
507         //static if (udas.length)
508         //{
509         //	__gshared static Variant[1] defval;
510         //	static if (!is(R == void))
511         //		defval[0] = Variant(R.init);
512         //	else
513         //		defval[0] = Variant(A[0].init);
514         //	return cast(GDExtensionVariantPtr*) &defval[0];
515         //}
516         {
517             __gshared static Variant*[ParameterDefaults!mf.length + 1] defaultsPtrs;
518             __gshared static Variant[ParameterDefaults!mf.length + 1] defaults;
519             static foreach (i, val; ParameterDefaults!mf) {
520                 // typeof val is needed because default value returns alias/expression and not a type itself
521                 static if (is(val == void) || !Variant.compatibleToGodot!(typeof(val)))
522                     defaults[i] = Variant(null); // even though it doesn't have it we probably need some value
523                 else
524                     defaults[i] = Variant(val);
525                 defaultsPtrs[i] = &defaults[i];
526             }
527             defaults[ParameterDefaults!mf.length + 1 .. $] = Variant();
528 
529             return cast(GDExtensionVariantPtr*)&defaultsPtrs[0];
530         }
531     }
532 }
533 
534 // Special wrapper that fetches OnReady members and then calls real _ready 
535 package(godot) struct OnReadyWrapper(T, alias mf) if (is(GodotClass!T : Node)) {
536     extern (C) // for calling convention
537     static void callOnReady(void* methodData, void* instance,
538         const(void**) args, const long numArgs, void* r_return, GDExtensionCallError* r_error) {
539         //if (!instance)
540         //{
541         //	*r_error = cast(GDExtensionCallError) GDEXTENSION_CALL_ERROR_INSTANCE_IS_NULL;
542         //	return;
543         //}
544         //
545         //auto id = _godot_api.object_get_instance_id(instance);
546         //auto obj = _godot_api.object_get_instance_from_id(id);
547         T t = cast(T) methodData; // method data is an actual D object backing godot instance
548 
549         if (!t)
550             return;
551 
552         foreach (n; onReadyFieldNames!T) {
553             alias udas = getUDAs!(__traits(getMember, T, n), OnReady);
554             static assert(udas.length == 1, "Multiple OnReady UDAs on " ~ T.stringof ~ "." ~ n);
555 
556             alias A = Alias!(TemplateArgsOf!(udas[0])[0]);
557             alias F = typeof(mixin("T." ~ n));
558 
559             // First, determine where to obtain the value to assign, and put it in `result`.
560             // `result` will be alias to void if nothing to assign.
561             static if (isCallable!A) {
562                 // pass the class itself to the function
563                 static if (Parameters!A.length && isImplicitlyConvertible!(T, Parameters!A[0]))
564                     alias arg = t;
565                 else
566                     alias arg = AliasSeq!();
567                 static if (is(ReturnType!A == void)) {
568                     alias result = void;
569                     A(arg);
570                 } else {
571                     auto result = A(arg); /// temp variable for return value
572                 }
573             } else static if (is(A))
574                 static assert(0, "OnReady arg can't be a type");
575             else static if (isExpressions!A) // expression (string literal, etc)
576             {
577                     alias result = A;
578                 }
579             else // some other alias (a different variable identifier?)
580             {
581                     static if (__traits(compiles, __traits(parent, A)))
582                         alias P = Alias!(__traits(parent, A));
583                     else
584                         alias P = void;
585                     static if (is(T : P)) {
586                         // A is another variable inside this very same T
587                         auto result = __traits(getMember, t, __traits(identifier, A));
588                     } else
589                         alias result = A; // final fallback: pass it unmodified to assignment
590                 }
591 
592             // Second, assign `result` to the field depending on the types of it and `result`
593             static if (!is(result == void)) {
594                 import godot.resource;
595 
596                 static if (isImplicitlyConvertible!(typeof(result), F)) {
597                     // direct assignment
598                     mixin("t." ~ n) = result;
599                 } else static if (__traits(compiles, mixin("t." ~ n) = F(result))) {
600                     // explicit constructor (String(string), NodePath(string), etc)
601                     mixin("t." ~ n) = F(result);
602                 } else static if (isGodotClass!F && extends!(F, Node)) {
603                     // special case: node path
604                     auto np = NodePath(result);
605                     mixin("t." ~ n) = cast(F) t.owner.getNode(np);
606                 } else static if (isGodotClass!F && extends!(F, Resource)) {
607                     // special case: resource load path
608                     import godot.resourceloader;
609 
610                     mixin("t." ~ n) = cast(F) ResourceLoader.load(result);
611                 } else
612                     static assert(0, "Don't know how to assign " ~ typeof(result)
613                             .stringof ~ " " ~ result.stringof ~
614                             " to " ~ F.stringof ~ " " ~ fullyQualifiedName!(
615                                 mixin("t." ~ n)));
616             }
617         }
618 
619         // Finally, call the actual _ready() if it exists.
620         enum bool isReady(alias func) = "_ready" == func;
621         alias readies = Filter!(isReady, __traits(derivedMembers, T));
622         static if(readies.length) {
623             // superbelko: note that method_data here is actually D object instance 
624             //
625             // Explanation:
626             //   IIRC I just took some existing function that is for regular calls,
627             //   but then there is a special dedicated function for that or something like that,
628             //   but because we already have too much template heavy code 
629             //   adding yet another special case was too cumbersome.
630             //   But... it was quite some time ago and I forgot the details so I maybe wrong.
631             MethodWrapper!(T, mf).callMethod(null, methodData, args, numArgs, r_return, r_error); 
632         }
633     }
634 }
635 
636 /++
637 Template for public variables exported as properties.
638 
639 Params:
640 	T = the class that owns the variable
641 	var = the name of the member variable being wrapped
642 +/
643 package(godot) struct VariableWrapper(T, alias var) {
644     import godot.refcounted, godot.api.reference;
645 
646     alias P = typeof(var);
647     static if (extends!(P, RefCounted))
648         static assert(is(P : Ref!U, U),
649             "Reference type property " ~ T.stringof ~ "." ~ var ~ " must be ref-counted as Ref!("
650                 ~ P.stringof ~ ")");
651 
652     alias getterType = P function();
653     alias setterType = void function(P v); // ldc doesn't likes 'val' name here
654 
655     extern (C) // for calling convention
656     static void callPropertyGet(void* methodData, void* instance,
657         const(void**) args, const long numArgs, void* r_return, GDExtensionCallError* r_error) {
658         auto obj = cast(T) instance;
659         if (!obj) {
660             r_error.error = GDEXTENSION_CALL_ERROR_INSTANCE_IS_NULL;
661             return;
662         }
663         if (numArgs > 0) {
664             r_error.error = GDEXTENSION_CALL_ERROR_TOO_MANY_ARGUMENTS;
665             return;
666         }
667         Variant* v = cast(Variant*) r_return;
668         *v = Variant(mixin("obj." ~ __traits(identifier, var)));
669     }
670 
671     extern (C) // for calling convention
672     static void callPropertySet(void* methodData, void* instance,
673         const(void**) args, const long numArgs, void* r_return, GDExtensionCallError* r_error) {
674         auto obj = cast(T) instance;
675         if (!obj) {
676             r_error.error = GDEXTENSION_CALL_ERROR_INSTANCE_IS_NULL;
677             return;
678         }
679         if (numArgs < 1) {
680             r_error.error = GDEXTENSION_CALL_ERROR_TOO_FEW_ARGUMENTS;
681             return;
682         }
683         Variant* v = cast(Variant*) args[0];
684         mixin("obj." ~ __traits(identifier, var)) = v.as!P;
685     }
686 }
687 
688 extern (C) package(godot) void emptySetter(godot_object self, void* methodData,
689     void* userData, godot_variant* value) {
690     assert(0, "Can't call empty property setter");
691     //return;
692 }
693 
694 extern (C) package(godot) godot_variant emptyGetter(godot_object self, void* methodData,
695     void* userData) {
696     assert(0, "Can't call empty property getter");
697     /+godot_variant v;
698 	_godot_api.variant_new_nil(&v);
699 	return v;+/
700 }
701 
702 struct VirtualMethodsHelper(T) {
703     static bool matchesNamingConv(string name)() {
704         import std.uni : isAlphaNum;
705 
706         return name[0] == '_' && name[1].isAlphaNum;
707     }
708     import std.meta;
709     static bool isFunc(alias member)() {
710         return isFunction!(__traits(getMember, T, member));
711     }
712 
713     alias derivedMfs = Filter!(matchesNamingConv, __traits(derivedMembers, T));
714     alias onlyFuncs = Filter!(isFunc, derivedMfs);
715 
716     static GDExtensionClassCallVirtual findVCall(const GDExtensionStringNamePtr func) {
717         // FIXME: StringName issues
718         auto v = Variant(*cast(StringName*) func);
719         auto fname = v.as!String.data();
720         static foreach (name; onlyFuncs) {
721             //if (MethodWrapper!(T, __traits(getMember, T, name)).funName == func)
722             if (__traits(identifier, __traits(getMember, T, name)) == fname)
723                 return &MethodWrapper!(T, __traits(getMember, T, name)).virtualCall;
724         }
725         return null;
726     }
727 }