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, 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             static if (isFloatingPoint!(A[ai])) {
311                 return (cast(Variant*)args[ai]).as!(A[ai]);
312             } 
313             else static if (isGodotClass!(A[ai])) {
314                 if (args)
315                     return *cast(A[ai]*)args[ai];
316                 return A[ai].init;
317             }
318             else {
319                 return va[ai].as!(A[ai]);
320             }
321         }
322 
323         template ArgCall(size_t ai) {
324             alias ArgCall = variantToArg!ai; //A[i] function()
325         }
326 
327         alias argIota = aliasSeqOf!(iota(A.length));
328         alias argCall = staticMap!(ArgCall, argIota);
329 
330         static if (is(R == void)) {
331             mixin("obj." ~ name ~ "(argCall);");
332         } else {
333             // allow Variant to be returned as is, i.e. no wrapping
334             static if (is(R == Variant)) {
335                 mixin("v = obj." ~ name ~ "(argCall);");
336             }
337             else {
338                 mixin("v = Variant(obj." ~ name ~ "(argCall));");
339             }
340 
341             if (r_return && v._godot_variant._opaque.ptr) {
342                 //*cast(godot_variant*) r_return = vd;   // since alpha 12 instead of this now have to copy it
343                 _godot_api.variant_new_copy(r_return, &v._godot_variant); // since alpha 12 this is now the case
344             }
345         }
346         //return vd;
347     }
348 
349     extern (C)
350     static void callPtrMethod(void* methodData, void* instance,
351         const(void*)* args, void* r_return) {
352 
353         T obj = cast(T) instance;
354 
355         A[ai] nativeToArg(size_t ai)() {
356             static if (isFloatingPoint!(A[ai])) {
357                 return cast(A[ai]) (*cast(godot_float*)args[ai]);
358             } 
359             else 
360                 return (*cast(A[ai]*) args[ai]);
361         }
362 
363         template ArgCall(size_t ai) {
364             alias ArgCall = nativeToArg!ai; //A[i] function()
365         }
366 
367         alias argIota = aliasSeqOf!(iota(A.length));
368         alias argCall = staticMap!(ArgCall, argIota);
369 
370         static if (is(R == void)) {
371             mixin("obj." ~ name ~ "(argCall);");
372         } else {
373             mixin("*(cast(R*) r_return) = obj." ~ name ~ "(argCall);");
374         }
375     }
376 
377     extern (C)
378     static void virtualCall(GDExtensionClassInstancePtr instance, const GDExtensionTypePtr* args, GDExtensionTypePtr ret) {
379         callPtrMethod(&mf, instance, args, ret);
380     }
381 }
382 
383 package(godot) struct MethodWrapperMeta(alias mf) {
384     alias R = ReturnType!mf; // the return type (can be void)
385     alias A = Parameters!mf; // the argument types (can be empty)
386 
387     //enum string name = __traits(identifier, mf);
388 
389     // GDExtensionClassMethodGetArgumentType signature:
390     //   GDExtensionVariantType function(void *p_method_userdata, int32_t p_argument)
391     extern (C)
392     static GDExtensionVariantType* getArgTypes() {
393         // fill array of argument types and use cached data
394         import godot.variant;
395         import std.meta : staticMap;
396 
397         immutable __gshared static VariantType[A.length] argInfo = [
398             staticMap!(Variant.variantTypeOf, A)
399         ];
400         return cast(GDExtensionVariantType*) argInfo.ptr;
401     }
402 
403     // yeah, it says return types, godot goes brrr
404     extern (C)
405     static GDExtensionVariantType* getReturnTypes() {
406         // fill array of argument types and use cached data
407         import godot.variant;
408         immutable __gshared static VariantType[2] retInfo = [Variant.variantTypeOf!R, VariantType.nil ];
409         return cast(GDExtensionVariantType*) retInfo.ptr;
410     }
411 
412     // function parameter type information
413     extern (C)
414     static GDExtensionPropertyInfo[A.length+1] getArgInfo() {
415         GDExtensionPropertyInfo[A.length + 1] argInfo;
416         __gshared static StringName[A.length+1] snClassNames = void;
417         __gshared static StringName[A.length+1] snArgNames = void;
418         __gshared static StringName[A.length+1] snHintStrings = void;
419         static foreach (i; 0 .. A.length) {
420             if (Variant.variantTypeOf!(A[i]) == VariantType.object) {
421                 snClassNames[i] = StringName(A[i].stringof);
422             }
423             else {
424                 snClassNames[i] = stringName();
425             }
426             snArgNames[i] = StringName((ParameterIdentifierTuple!mf)[i]);
427             snHintStrings[i] = stringName();
428             argInfo[i].class_name = cast(GDExtensionStringNamePtr) snClassNames[i];
429             argInfo[i].name = cast(GDExtensionStringNamePtr) snArgNames[i];
430             argInfo[i].type = cast(GDExtensionVariantType) Variant.variantTypeOf!(A[i]);
431             argInfo[i].usage = GDEXTENSION_METHOD_FLAGS_DEFAULT;
432             argInfo[i].hint_string = cast(GDExtensionStringNamePtr) snHintStrings[i];
433         }
434         return argInfo;
435     }
436 
437     // return type information
438     extern (C)
439     static GDExtensionPropertyInfo[2] getReturnInfo() {
440         // FIXME: StringName makes it no longer CTFE-able
441         static if (is(R == Variant)) {
442             import godot.globalenums : PropertyUsageFlags;
443             enum propUsageFlags = PropertyUsageFlags.propertyUsageNilIsVariant;
444             // fallback value in case for some reason this enum will go away
445             //enum propUsageFlags = 131072;
446         }
447         else {
448             enum propUsageFlags = 0;
449         }
450         __gshared static StringName snName = void;
451         __gshared static StringName snClassName = void;
452         __gshared static StringName snHint = void;
453 
454         // shouldn't this be the opposite?
455         static if (Variant.variantTypeOf!R == VariantType.object) {
456             snClassName = stringName();
457         } 
458         else {
459             snClassName = StringName(R.stringof);
460         }
461         snName = stringName();
462         snHint = stringName();
463 
464         GDExtensionPropertyInfo[2] retInfo = [ 
465             GDExtensionPropertyInfo(
466                 cast(GDExtensionVariantType) Variant.variantTypeOf!R,
467                 cast(GDExtensionStringNamePtr) snName,
468                 cast(GDExtensionStringNamePtr) snClassName,
469                 0, // aka PropertyHint.propertyHintNone
470                 cast(GDExtensionStringNamePtr) snHint,
471                 propUsageFlags
472             ), 
473             GDExtensionPropertyInfo.init 
474         ];
475         return retInfo;
476     }
477 
478     // metadata array for argument types
479     extern (C)
480     static GDExtensionClassMethodArgumentMetadata* getArgMetadata() {
481         __gshared static GDExtensionClassMethodArgumentMetadata[A.length] argInfo = GDEXTENSION_METHOD_ARGUMENT_METADATA_NONE;
482         return argInfo.ptr;
483     }
484 
485     // metadata for return type
486     extern (C)
487     static GDExtensionClassMethodArgumentMetadata getReturnMetadata() {
488         return GDEXTENSION_METHOD_ARGUMENT_METADATA_NONE;
489     }
490 
491     import std.traits;
492 
493     private enum bool notVoid(alias T) = !is(T == void);
494     enum getDefaultArgNum = cast(int32_t) Filter!(notVoid, ParameterDefaults!mf).length;
495     //enum getDefaultArgNum = cast(int32_t) Parameters!mf.length;
496 
497     // this function expected to return Variant[] containing default values
498     extern (C)
499     static GDExtensionVariantPtr* getDefaultArgs() {
500         //pragma(msg, "fn: ", __traits(identifier, mf), " > ",  ParameterDefaults!mf);
501 
502         // just pick any possible property for now
503         //alias udas = getUDAs!(mf, Property);
504         //static if (udas.length)
505         //{
506         //	__gshared static Variant[1] defval;
507         //	static if (!is(R == void))
508         //		defval[0] = Variant(R.init);
509         //	else
510         //		defval[0] = Variant(A[0].init);
511         //	return cast(GDExtensionVariantPtr*) &defval[0];
512         //}
513         {
514             __gshared static Variant*[ParameterDefaults!mf.length + 1] defaultsPtrs;
515             __gshared static Variant[ParameterDefaults!mf.length + 1] defaults;
516             static foreach (i, val; ParameterDefaults!mf) {
517                 // typeof val is needed because default value returns alias/expression and not a type itself
518                 static if (is(val == void) || !Variant.compatibleToGodot!(typeof(val)))
519                     defaults[i] = Variant(null); // even though it doesn't have it we probably need some value
520                 else
521                     defaults[i] = Variant(val);
522                 defaultsPtrs[i] = &defaults[i];
523             }
524             defaults[ParameterDefaults!mf.length + 1 .. $] = Variant();
525 
526             return cast(GDExtensionVariantPtr*)&defaultsPtrs[0];
527         }
528     }
529 }
530 
531 // Special wrapper that fetches OnReady members and then calls real _ready 
532 package(godot) struct OnReadyWrapper(T, alias mf) if (is(GodotClass!T : Node)) {
533     extern (C) // for calling convention
534     static void callOnReady(void* methodData, void* instance,
535         const(void*)* args, long numArgs, void* r_return, GDExtensionCallError* r_error) {
536         //if (!instance)
537         //{
538         //	*r_error = cast(GDExtensionCallError) GDEXTENSION_CALL_ERROR_INSTANCE_IS_NULL;
539         //	return;
540         //}
541         //
542         //auto id = _godot_api.object_get_instance_id(instance);
543         //auto obj = _godot_api.object_get_instance_from_id(id);
544         T t = cast(T) methodData; // method data is an actual D object backing godot instance
545 
546         if (!t)
547             return;
548 
549         foreach (n; onReadyFieldNames!T) {
550             alias udas = getUDAs!(__traits(getMember, T, n), OnReady);
551             static assert(udas.length == 1, "Multiple OnReady UDAs on " ~ T.stringof ~ "." ~ n);
552 
553             alias A = Alias!(TemplateArgsOf!(udas[0])[0]);
554             alias F = typeof(mixin("T." ~ n));
555 
556             // First, determine where to obtain the value to assign, and put it in `result`.
557             // `result` will be alias to void if nothing to assign.
558             static if (isCallable!A) {
559                 // pass the class itself to the function
560                 static if (Parameters!A.length && isImplicitlyConvertible!(T, Parameters!A[0]))
561                     alias arg = t;
562                 else
563                     alias arg = AliasSeq!();
564                 static if (is(ReturnType!A == void)) {
565                     alias result = void;
566                     A(arg);
567                 } else {
568                     auto result = A(arg); /// temp variable for return value
569                 }
570             } else static if (is(A))
571                 static assert(0, "OnReady arg can't be a type");
572             else static if (isExpressions!A) // expression (string literal, etc)
573             {
574                     alias result = A;
575                 }
576             else // some other alias (a different variable identifier?)
577             {
578                     static if (__traits(compiles, __traits(parent, A)))
579                         alias P = Alias!(__traits(parent, A));
580                     else
581                         alias P = void;
582                     static if (is(T : P)) {
583                         // A is another variable inside this very same T
584                         auto result = __traits(getMember, t, __traits(identifier, A));
585                     } else
586                         alias result = A; // final fallback: pass it unmodified to assignment
587                 }
588 
589             // Second, assign `result` to the field depending on the types of it and `result`
590             static if (!is(result == void)) {
591                 import godot.resource;
592 
593                 static if (isImplicitlyConvertible!(typeof(result), F)) {
594                     // direct assignment
595                     mixin("t." ~ n) = result;
596                 } else static if (__traits(compiles, mixin("t." ~ n) = F(result))) {
597                     // explicit constructor (String(string), NodePath(string), etc)
598                     mixin("t." ~ n) = F(result);
599                 } else static if (isGodotClass!F && extends!(F, Node)) {
600                     // special case: node path
601                     auto np = NodePath(result);
602                     mixin("t." ~ n) = cast(F) t.owner.getNode(np);
603                 } else static if (isGodotClass!F && extends!(F, Resource)) {
604                     // special case: resource load path
605                     import godot.resourceloader;
606 
607                     mixin("t." ~ n) = cast(F) ResourceLoader.load(result);
608                 } else
609                     static assert(0, "Don't know how to assign " ~ typeof(result)
610                             .stringof ~ " " ~ result.stringof ~
611                             " to " ~ F.stringof ~ " " ~ fullyQualifiedName!(
612                                 mixin("t." ~ n)));
613             }
614         }
615 
616         // Finally, call the actual _ready() if it exists.
617         enum bool isReady(alias func) = "_ready" == func;
618         alias readies = Filter!(isReady, __traits(derivedMembers, T));
619         static if(readies.length) {
620             // superbelko: note that method_data here is actually D object instance 
621             //
622             // Explanation:
623             //   IIRC I just took some existing function that is for regular calls,
624             //   but then there is a special dedicated function for that or something like that,
625             //   but because we already have too much template heavy code 
626             //   adding yet another special case was too cumbersome.
627             //   But... it was quite some time ago and I forgot the details so I maybe wrong.
628             MethodWrapper!(T, mf).callMethod(null, methodData, args, numArgs, r_return, r_error); 
629         }
630     }
631 }
632 
633 /++
634 Template for public variables exported as properties.
635 
636 Params:
637 	T = the class that owns the variable
638 	var = the name of the member variable being wrapped
639 +/
640 package(godot) struct VariableWrapper(T, alias var) {
641     import godot.refcounted, godot.api.reference;
642 
643     alias P = typeof(var);
644     static if (extends!(P, RefCounted))
645         static assert(is(P : Ref!U, U),
646             "Reference type property " ~ T.stringof ~ "." ~ var ~ " must be ref-counted as Ref!("
647                 ~ P.stringof ~ ")");
648 
649     alias getterType = P function();
650     alias setterType = void function(P v); // ldc doesn't likes 'val' name here
651 
652     extern (C) // for calling convention
653     static void callPropertyGet(void* methodData, void* instance,
654         const(void*)* args, long numArgs, void* r_return, GDExtensionCallError* r_error) {
655         auto obj = cast(T) instance;
656         if (!obj) {
657             r_error.error = GDEXTENSION_CALL_ERROR_INSTANCE_IS_NULL;
658             return;
659         }
660         if (numArgs > 0) {
661             r_error.error = GDEXTENSION_CALL_ERROR_TOO_MANY_ARGUMENTS;
662             return;
663         }
664         Variant* v = cast(Variant*) r_return;
665         *v = Variant(mixin("obj." ~ __traits(identifier, var)));
666     }
667 
668     extern (C) // for calling convention
669     static void callPropertySet(void* methodData, void* instance,
670         const(void*)* args, long numArgs, void* r_return, GDExtensionCallError* r_error) {
671         auto obj = cast(T) instance;
672         if (!obj) {
673             r_error.error = GDEXTENSION_CALL_ERROR_INSTANCE_IS_NULL;
674             return;
675         }
676         if (numArgs < 1) {
677             r_error.error = GDEXTENSION_CALL_ERROR_TOO_FEW_ARGUMENTS;
678             return;
679         }
680         Variant* v = cast(Variant*) args[0];
681         mixin("obj." ~ __traits(identifier, var)) = v.as!P;
682     }
683 }
684 
685 extern (C) package(godot) void emptySetter(godot_object self, void* methodData,
686     void* userData, godot_variant* value) {
687     assert(0, "Can't call empty property setter");
688     //return;
689 }
690 
691 extern (C) package(godot) godot_variant emptyGetter(godot_object self, void* methodData,
692     void* userData) {
693     assert(0, "Can't call empty property getter");
694     /+godot_variant v;
695 	_godot_api.variant_new_nil(&v);
696 	return v;+/
697 }
698 
699 struct VirtualMethodsHelper(T) {
700     static bool matchesNamingConv(string name)() {
701         import std.uni : isAlphaNum;
702 
703         return name[0] == '_' && name[1].isAlphaNum;
704     }
705     import std.meta;
706     static bool isFunc(alias member)() {
707         return isFunction!(__traits(getMember, T, member));
708     }
709 
710     alias derivedMfs = Filter!(matchesNamingConv, __traits(derivedMembers, T));
711     alias onlyFuncs = Filter!(isFunc, derivedMfs);
712 
713     static GDExtensionClassCallVirtual findVCall(const GDExtensionStringNamePtr func) {
714         // FIXME: StringName issues
715         auto v = Variant(*cast(StringName*) func);
716         auto fname = v.as!String.data();
717         static foreach (name; onlyFuncs) {
718             //if (MethodWrapper!(T, __traits(getMember, T, name)).funName == func)
719             if (__traits(identifier, __traits(getMember, T, name)) == fname)
720                 return &MethodWrapper!(T, __traits(getMember, T, name)).virtualCall;
721         }
722         return null;
723     }
724 }