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     alias mfs(alias mName) = MemberFunctionsTuple!(T, mName);
44     alias allMfs = staticMap!(mfs, __traits(derivedMembers, T));
45     enum bool isMethod(alias mf) = hasUDA!(mf, Method);
46 
47     alias godotMethods = Filter!(isMethod, allMfs);
48 
49     alias godotNames = staticMap!(godotName, godotMethods);
50     static assert(godotNames.length == NoDuplicates!godotNames.length,
51         overloadError!godotMethods());
52 }
53 
54 package(godot) template godotSignals(T) {
55     enum isSignalExpr(string n) = q{ isCallable!(mixin("T."~n))
56 		&& ( hasUDA!(mixin("T."~n), Signal) || is(ReturnType!(mixin("T."~n)) == Signal) ) };
57     template isSignal(string n) {
58         static if (__traits(compiles, mixin(isSignalExpr!n))) {
59             enum bool isSignal = mixin(isSignalExpr!n);
60         } else
61             enum bool isSignal = false;
62     }
63 
64     alias godotSignals = Filter!(isSignal, __traits(derivedMembers, T));
65 }
66 
67 package(godot) template onReadyFieldNames(T) {
68     import godot.node;
69 
70     static if (!is(GodotClass!T : Node))
71         alias onReadyFieldNames = AliasSeq!();
72     else {
73         alias fieldNames = FieldNameTuple!T;
74         template isORField(string n) {
75             static if (staticIndexOf!(n, fieldNames) != -1 && staticIndexOf!(__traits(getProtection, __traits(
76                     getMember, T, n)), "public", "export") != -1) {
77                 enum bool isORField = hasUDA!(__traits(getMember, T, n), OnReady);
78             } else
79                 enum bool isORField = false;
80         }
81 
82         alias onReadyFieldNames = Filter!(isORField, __traits(derivedMembers, T));
83     }
84 }
85 
86 package(godot) template godotPropertyGetters(T) {
87     alias mfs(alias mName) = MemberFunctionsTuple!(T, mName);
88     alias allMfs = staticMap!(mfs, __traits(derivedMembers, T));
89     template isGetter(alias mf) {
90         enum bool isGetter = hasUDA!(mf, Property) && !is(ReturnType!mf == void);
91     }
92 
93     alias godotPropertyGetters = Filter!(isGetter, allMfs);
94 
95     alias godotNames = Filter!(godotName, godotPropertyGetters);
96     static assert(godotNames.length == NoDuplicates!godotNames.length,
97         overloadError!godotPropertyGetters());
98 }
99 
100 package(godot) template godotPropertySetters(T) {
101     alias mfs(alias mName) = MemberFunctionsTuple!(T, mName);
102     alias allMfs = staticMap!(mfs, __traits(derivedMembers, T));
103     template isSetter(alias mf) {
104         enum bool isSetter = hasUDA!(mf, Property) && is(ReturnType!mf == void);
105     }
106 
107     alias godotPropertySetters = Filter!(isSetter, allMfs);
108 
109     alias godotNames = Filter!(godotName, godotPropertySetters);
110     static assert(godotNames.length == NoDuplicates!godotNames.length,
111         overloadError!godotPropertySetters());
112 }
113 
114 package(godot) template godotPropertyNames(T) {
115     alias godotPropertyNames = NoDuplicates!(staticMap!(godotName, godotPropertyGetters!T,
116             godotPropertySetters!T));
117 }
118 
119 package(godot) template godotEnums(T) {
120     import std.traits;
121 
122     alias mfs(alias mName) = __traits(getMember, T, mName);
123     alias allMfs = staticMap!(mfs, __traits(derivedMembers, T));
124     template isEnum(alias mf) {
125         static if (is(mf Base == enum) && isIntegral!Base)
126             enum bool isEnum = hasUDA!(mf, Enum);
127         else
128             enum bool isEnum = false;
129     }
130 
131     alias godotEnums = Filter!(isEnum, allMfs);
132 }
133 
134 package(godot) template godotConstants(T) {
135     import std.traits;
136 
137     alias mfs(alias mName) = Alias!(__traits(getMember, T, mName));
138     alias allMfs = staticMap!(mfs, __traits(derivedMembers, T));
139 
140     template isCompileTimeValue(alias V, T...)
141             if (T.length == 0 || (T.length == 1 && is(T[0]))) {
142         enum isKnown = is(typeof(() { enum v = V; }));
143         static if (!T.length)
144             enum isCompileTimeValue = isKnown;
145         else
146             enum isCompileTimeValue = isKnown && is(typeof(V) == T[0]);
147     }
148 
149     template isConstant(alias mf) {
150         static if (isCompileTimeValue!mf) {
151             enum bool isConstant = hasUDA!(mf, Constant);
152         } else
153             enum bool isConstant = false;
154     }
155 
156     enum isConstantMember(string m) = isConstant!(__traits(getMember, T, m));
157     alias godotConstants = Filter!(isConstantMember, __traits(derivedMembers, T));
158     //pragma(msg, filtered);
159     //alias godotConstants = Filter!(isConstant, allMfs);
160 }
161 
162 package(godot) template godotPropertyVariableNames(T) {
163     alias fieldNames = FieldNameTuple!T;
164     alias field(string name) = Alias!(__traits(getMember, T, name));
165     template isVariable(string name) {
166         static if (__traits(getProtection, __traits(getMember, T, name)) == "public")
167             enum bool isVariable = hasUDA!(field!name, Property);
168         else
169             enum bool isVariable = false;
170     }
171 
172     alias godotPropertyVariableNames = Filter!(isVariable, fieldNames);
173 }
174 
175 /// get the common Variant type for a set of function or variable aliases
176 package(godot) template extractPropertyVariantType(seq...) {
177     template Type(alias a) {
178         static if (isFunction!a && is(ReturnType!a == void))
179             alias Type = Parameters!a[0];
180         else static if (isFunction!a)
181             alias Type = NonRef!(ReturnType!a);
182         //else alias Type = typeof(a);
183 
184         static assert(Variant.compatible!Type, "Property type " ~
185                 Type.stringof ~ " is incompatible with Variant.");
186     }
187 
188     alias types = NoDuplicates!(staticMap!(Variant.variantTypeOf, staticMap!(Type, seq)));
189     static assert(types.length == 1); /// TODO: better error message
190     enum extractPropertyVariantType = types[0];
191 }
192 
193 package(godot) template extractPropertyUDA(seq...) {
194     template udas(alias a) {
195         alias udas = getUDAs!(a, Property);
196     }
197 
198     enum bool isUDAValue(alias a) = !is(a);
199     alias values = Filter!(isUDAValue, staticMap!(udas, seq));
200 
201     static if (values.length == 0)
202         enum Property extractPropertyUDA = Property.init;
203     else static if (values.length == 1)
204         enum Property extractPropertyUDA = values[0];
205     else {
206         // verify that they all have the same value, to avoid wierdness
207         enum Property extractPropertyUDA = values[0];
208         enum bool isSameAsFirst(Property p) = extractPropertyUDA == p;
209         static assert(allSatisfy!(isSameAsFirst, values[1 .. $]));
210     }
211 }
212 
213 /++
214 Variadic template for method wrappers.
215 
216 Params:
217 	T = the class that owns the method
218 	mf = the member function being wrapped, as an alias
219 +/
220 package(godot) struct MethodWrapper(T, alias mf) {
221     alias R = ReturnType!mf; // the return type (can be void)
222     alias A = Parameters!mf; // the argument types (can be empty)
223 
224     enum string name = __traits(identifier, mf);
225 
226     /++
227 	C function passed to Godot that calls the wrapped method
228 	+/
229     extern (C) // for calling convention
230     static void callMethod(void* methodData, void* instance,
231         const(void**) args, const long numArgs, void* r_return, GDNativeCallError* r_error) //@nogc nothrow
232         {
233         // TODO: check types for Variant compatibility, give a better error here
234         // TODO: check numArgs, accounting for D arg defaults
235 
236         if (!instance) {
237             r_error.error = GDNATIVE_CALL_ERROR_INSTANCE_IS_NULL;
238             return;
239         }
240 
241         //godot_variant vd;
242         //_godot_api.variant_new_nil(&vd);
243         //Variant* v = cast(Variant*)&vd; // just a pointer; no destructor will be called
244         Variant v;
245 
246         // basically what we want here...
247         //Variant*[] va = (cast(Variant**) args)[0..numArgs];
248         // however there is also default params that we need to place here
249         scope Variant*[Parameters!mf.length + 1] va;
250         scope Variant[ParameterDefaults!mf.length] defaults;
251         static foreach (i, defval; ParameterDefaults!mf) {
252             // should never happen
253             static if (is(defval == void))
254                 defaults[i] = Variant();
255             else
256                 defaults[i] = Variant(defval);
257         }
258 
259         if (args && numArgs)
260             va[0 .. numArgs] = (cast(Variant**) args)[0 .. numArgs];
261         if (args && numArgs < defaults.length) // <-- optional parameters that godot decided not to pass
262         {
263             foreach (i; 0 .. defaults.length)
264                 va[max(0, numArgs) + i] = &defaults[i];
265         }
266 
267         T obj = cast(T) instance;
268 
269         A[ai] variantToArg(size_t ai)() {
270             // should also be string and array?
271             //static if (is(A[ai] == NodePath))
272             //{
273             //	import godot.api;
274             //	print(*va[ai]);
275             //	return (*va[ai]).as!(A[ai]);
276             //}
277             //else
278             //return (cast(Variant*)args[ai]).as!(A[ai]);
279             // TODO: properly fix double, it returns pointer instead of value itself
280             static if (is(A[ai] : real)) {
281                 return **cast(A[ai]**)&va[ai];
282             } else {
283                 return va[ai].as!(A[ai]);
284             }
285         }
286 
287         template ArgCall(size_t ai) {
288             alias ArgCall = variantToArg!ai; //A[i] function()
289         }
290 
291         alias argIota = aliasSeqOf!(iota(A.length));
292         alias argCall = staticMap!(ArgCall, argIota);
293 
294         static if (is(R == void)) {
295             mixin("obj." ~ name ~ "(argCall);");
296         } else {
297             mixin("v = Variant(obj." ~ name ~ "(argCall));");
298 
299             if (r_return && v._godot_variant._opaque.ptr) {
300                 //*cast(godot_variant*) r_return = vd;   // since alpha 12 instead of this now have to copy it
301                 _godot_api.variant_new_copy(r_return, &v._godot_variant); // since alpha 12 this is now the case
302             }
303         }
304         //return vd;
305     }
306 
307     extern (C)
308     static void callPtrMethod(void* methodData, void* instance,
309         const(void**) args, void* r_return) {
310         //GDNativeCallError err;
311         //callMethod(methodData, instance, args, A.length, r_return, &err);
312 
313         T obj = cast(T) instance;
314 
315         A[ai] nativeToArg(size_t ai)() {
316             return (*cast(A[ai]*) args[ai]);
317         }
318 
319         template ArgCall(size_t ai) {
320             alias ArgCall = nativeToArg!ai; //A[i] function()
321         }
322 
323         alias argIota = aliasSeqOf!(iota(A.length));
324         alias argCall = staticMap!(ArgCall, argIota);
325 
326         static if (is(R == void)) {
327             mixin("obj." ~ name ~ "(argCall);");
328         } else {
329             mixin("*(cast(R*) r_return) = obj." ~ name ~ "(argCall);");
330         }
331     }
332 
333     extern (C)
334     static void virtualCall(GDExtensionClassInstancePtr instance, const GDNativeTypePtr* args, GDNativeTypePtr ret) {
335         GDNativeCallError err;
336         callMethod(&mf, instance, args, Parameters!mf.length, ret, &err);
337     }
338 }
339 
340 package(godot) struct MethodWrapperMeta(alias mf) {
341     alias R = ReturnType!mf; // the return type (can be void)
342     alias A = Parameters!mf; // the argument types (can be empty)
343 
344     //enum string name = __traits(identifier, mf);
345 
346     // GDNativeExtensionClassMethodGetArgumentType signature:
347     //   GDNativeVariantType function(void *p_method_userdata, int32_t p_argument)
348     extern (C)
349     static GDNativeVariantType getArgTypesFn(void* method, int32_t argument) {
350         // fill array of argument types and use cached data
351         import godot.variant;
352         import std.meta : staticMap;
353 
354         immutable __gshared static VariantType[A.length] argInfo = [
355             staticMap!(Variant.variantTypeOf, A)
356         ];
357         immutable __gshared static VariantType retInfo = Variant.variantTypeOf!R;
358         //if (method != &mf)
359         //	return GDNATIVE_VARIANT_TYPE_NIL;
360         if (argument > A.length)
361             return GDNATIVE_VARIANT_TYPE_NIL;
362         if (argument == -1)
363             return retInfo;
364         return cast(GDNativeVariantType) argInfo[argument];
365     }
366 
367     // GDNativeExtensionClassMethodGetArgumentInfo signature:
368     //   void function(void *p_method_userdata, int32_t p_argument, GDNativePropertyInfo *r_info)
369     extern (C)
370     static void getArgInfoFn(void* method, int32_t argument, /*out*/ GDNativePropertyInfo* info) {
371         static GDNativePropertyInfo[] makeParamInfo() {
372             GDNativePropertyInfo[A.length + 1] argInfo;
373             static foreach (i; 0 .. A.length) {
374 
375                 if (Variant.variantTypeOf!(A[i]) == VariantType.object)
376                     argInfo[i].class_name = A[i].stringof;
377                 argInfo[i].name = (ParameterIdentifierTuple!mf)[i];
378                 argInfo[i].type = Variant.variantTypeOf!(A[i]);
379                 argInfo[i].usage = GDNATIVE_EXTENSION_METHOD_FLAGS_DEFAULT;
380 
381             }
382             return argInfo.dup;
383         }
384 
385         __gshared static GDNativePropertyInfo[A.length + 1] argInfo = makeParamInfo();
386         __gshared static GDNativePropertyInfo retInfo = GDNativePropertyInfo(
387             cast(uint32_t) Variant.variantTypeOf!R,
388             null,
389             (Variant.variantTypeOf!R == VariantType.object ? cast(char[]) null : R.stringof)
390                 .ptr,
391                 0,
392                 null,
393                 GDNATIVE_EXTENSION_METHOD_FLAGS_DEFAULT
394         );
395         // well... it doesn't go well with auto properties
396         //if (method != &mf)
397         //	return;
398         if (argument > cast(int) A.length) // ooof, implicit signed to unsigned cast...
399             return;
400         if (argument == -1) {
401             *info = retInfo;
402             return;
403         }
404 
405         *info = argInfo[argument];
406     }
407 
408     // GDNativeExtensionClassMethodGetArgumentMetadata signature:
409     //   GDNativeExtensionClassMethodArgumentMetadata function(void *p_method_userdata, int32_t p_argument)
410     extern (C)
411     static GDNativeExtensionClassMethodArgumentMetadata getArgMetadataFn(void* method, int32_t argument) {
412         __gshared static GDNativeExtensionClassMethodArgumentMetadata[A.length] argInfo;
413         __gshared static GDNativeExtensionClassMethodArgumentMetadata retInfo;
414         if (method != &mf)
415             return GDNATIVE_EXTENSION_METHOD_ARGUMENT_METADATA_NONE;
416         if (argument > A.length)
417             return GDNATIVE_EXTENSION_METHOD_ARGUMENT_METADATA_NONE;
418         // TODO: implement me
419         return GDNATIVE_EXTENSION_METHOD_ARGUMENT_METADATA_NONE;
420         //if (argument == -1)
421         //	return retInfo;
422         //return argInfo[argument];
423     }
424 
425     import std.traits;
426 
427     private enum bool notVoid(alias T) = !is(T == void);
428     enum getDefaultArgNum = cast(int32_t) Filter!(notVoid, ParameterDefaults!mf).length;
429     //enum getDefaultArgNum = cast(int32_t) Parameters!mf.length;
430 
431     // this function expected to return Variant[] containing default values
432     extern (C)
433     static GDNativeVariantPtr* getDefaultArgs() {
434         //pragma(msg, "fn: ", __traits(identifier, mf), " > ",  ParameterDefaults!mf);
435 
436         // just pick any possible property for now
437         //alias udas = getUDAs!(mf, Property);
438         //static if (udas.length)
439         //{
440         //	__gshared static Variant[1] defval;
441         //	static if (!is(R == void))
442         //		defval[0] = Variant(R.init);
443         //	else
444         //		defval[0] = Variant(A[0].init);
445         //	return cast(GDNativeVariantPtr*) &defval[0];
446         //}
447         {
448             __gshared static Variant*[ParameterDefaults!mf.length + 1] defaultsPtrs;
449             __gshared static Variant[ParameterDefaults!mf.length + 1] defaults;
450             static foreach (i, val; ParameterDefaults!mf) {
451                 // typeof val is needed because default value returns alias/expression and not a type itself
452                 static if (is(val == void) || !Variant.compatibleToGodot!(typeof(val)))
453                     defaults[i] = Variant(null); // even though it doesn't have it we probably need some value
454                 else
455                     defaults[i] = Variant(val);
456                 defaultsPtrs[i] = &defaults[i];
457             }
458             defaults[ParameterDefaults!mf.length + 1 .. $] = Variant();
459 
460             return cast(GDNativeVariantPtr*)&defaultsPtrs[0];
461         }
462     }
463 }
464 
465 // Special wrapper that fetches OnReady members and then calls real _ready 
466 package(godot) struct OnReadyWrapper(T, alias mf) if (is(GodotClass!T : Node)) {
467     extern (C) // for calling convention
468     static void callOnReady(void* methodData, void* instance,
469         const(void**) args, const long numArgs, void* r_return, GDNativeCallError* r_error) {
470         //if (!instance)
471         //{
472         //	*r_error = cast(GDNativeCallError) GDNATIVE_CALL_ERROR_INSTANCE_IS_NULL;
473         //	return;
474         //}
475         //
476         //auto id = _godot_api.object_get_instance_id(instance);
477         //auto obj = _godot_api.object_get_instance_from_id(id);
478         T t = cast(T) methodData; // method data is an actual D object backing godot instance
479 
480         if (!t)
481             return;
482 
483         foreach (n; onReadyFieldNames!T) {
484             alias udas = getUDAs!(__traits(getMember, T, n), OnReady);
485             static assert(udas.length == 1, "Multiple OnReady UDAs on " ~ T.stringof ~ "." ~ n);
486 
487             alias A = Alias!(TemplateArgsOf!(udas[0])[0]);
488             alias F = typeof(mixin("T." ~ n));
489 
490             // First, determine where to obtain the value to assign, and put it in `result`.
491             // `result` will be alias to void if nothing to assign.
492             static if (isCallable!A) {
493                 // pass the class itself to the function
494                 static if (Parameters!A.length && isImplicitlyConvertible!(T, Parameters!A[0]))
495                     alias arg = t;
496                 else
497                     alias arg = AliasSeq!();
498                 static if (is(ReturnType!A == void)) {
499                     alias result = void;
500                     A(arg);
501                 } else {
502                     auto result = A(arg); /// temp variable for return value
503                 }
504             } else static if (is(A))
505                 static assert(0, "OnReady arg can't be a type");
506             else static if (isExpressions!A) // expression (string literal, etc)
507             {
508                     alias result = A;
509                 }
510             else // some other alias (a different variable identifier?)
511             {
512                     static if (__traits(compiles, __traits(parent, A)))
513                         alias P = Alias!(__traits(parent, A));
514                     else
515                         alias P = void;
516                     static if (is(T : P)) {
517                         // A is another variable inside this very same T
518                         auto result = __traits(getMember, t, __traits(identifier, A));
519                     } else
520                         alias result = A; // final fallback: pass it unmodified to assignment
521                 }
522 
523             // Second, assign `result` to the field depending on the types of it and `result`
524             static if (!is(result == void)) {
525                 import godot.resource;
526 
527                 static if (isImplicitlyConvertible!(typeof(result), F)) {
528                     // direct assignment
529                     mixin("t." ~ n) = result;
530                 } else static if (__traits(compiles, mixin("t." ~ n) = F(result))) {
531                     // explicit constructor (String(string), NodePath(string), etc)
532                     mixin("t." ~ n) = F(result);
533                 } else static if (isGodotClass!F && extends!(F, Node)) {
534                     // special case: node path
535                     auto np = NodePath(result);
536                     mixin("t." ~ n) = cast(F) t.owner.getNode(np);
537                 } else static if (isGodotClass!F && extends!(F, Resource)) {
538                     // special case: resource load path
539                     import godot.resourceloader;
540 
541                     mixin("t." ~ n) = cast(F) ResourceLoader.load(result);
542                 } else
543                     static assert(0, "Don't know how to assign " ~ typeof(result)
544                             .stringof ~ " " ~ result.stringof ~
545                             " to " ~ F.stringof ~ " " ~ fullyQualifiedName!(
546                                 mixin("t." ~ n)));
547             }
548         }
549 
550         // Finally, call the actual _ready() if it exists.
551         MethodWrapper!(T, mf).callMethod(null, methodData, args, numArgs, r_return, r_error); // note that method_data here is actually D object instance
552         //enum bool isReady(alias func) = "_ready" == godotName!func;
553         //alias readies = Filter!(isReady, godotMethods!T);
554         //static if(readies.length) mixin("t."~__traits(identifier, readies[0])~"();");
555     }
556 }
557 
558 /++
559 Template for public variables exported as properties.
560 
561 Params:
562 	T = the class that owns the variable
563 	var = the name of the member variable being wrapped
564 +/
565 package(godot) struct VariableWrapper(T, alias var) {
566     import godot.refcounted, godot.api.reference;
567 
568     alias P = typeof(var);
569     static if (extends!(P, RefCounted))
570         static assert(is(P : Ref!U, U),
571             "Reference type property " ~ T.stringof ~ "." ~ var ~ " must be ref-counted as Ref!("
572                 ~ P.stringof ~ ")");
573 
574     alias getterType = P function();
575     alias setterType = void function(P val);
576 
577     extern (C) // for calling convention
578     static void callPropertyGet(void* methodData, void* instance,
579         const(void**) args, const long numArgs, void* r_return, GDNativeCallError* r_error) {
580         auto obj = cast(T) instance;
581         if (!obj) {
582             r_error.error = GDNATIVE_CALL_ERROR_INSTANCE_IS_NULL;
583             return;
584         }
585         if (numArgs > 0) {
586             r_error.error = GDNATIVE_CALL_ERROR_TOO_MANY_ARGUMENTS;
587             return;
588         }
589         Variant* v = cast(Variant*) r_return;
590         *v = Variant(mixin("obj." ~ __traits(identifier, var)));
591     }
592 
593     extern (C) // for calling convention
594     static void callPropertySet(void* methodData, void* instance,
595         const(void**) args, const long numArgs, void* r_return, GDNativeCallError* r_error) {
596         auto obj = cast(T) instance;
597         if (!obj) {
598             r_error.error = GDNATIVE_CALL_ERROR_INSTANCE_IS_NULL;
599             return;
600         }
601         if (numArgs < 1) {
602             r_error.error = GDNATIVE_CALL_ERROR_TOO_FEW_ARGUMENTS;
603             return;
604         }
605         Variant* v = cast(Variant*) args[0];
606         mixin("obj." ~ __traits(identifier, var)) = v.as!P;
607     }
608 }
609 
610 extern (C) package(godot) void emptySetter(godot_object self, void* methodData,
611     void* userData, godot_variant* value) {
612     assert(0, "Can't call empty property setter");
613     //return;
614 }
615 
616 extern (C) package(godot) godot_variant emptyGetter(godot_object self, void* methodData,
617     void* userData) {
618     assert(0, "Can't call empty property getter");
619     /+godot_variant v;
620 	_godot_api.variant_new_nil(&v);
621 	return v;+/
622 }
623 
624 struct VirtualMethodsHelper(T) {
625     static bool matchesNamingConv(string name)() {
626         import std.uni : isAlphaNum;
627 
628         return name[0] == '_' && name[1].isAlphaNum;
629     }
630 
631     alias derivedMfs = Filter!(matchesNamingConv, __traits(derivedMembers, T));
632 
633     static GDNativeExtensionClassCallVirtual findVCall(in string func) {
634         static foreach (name; derivedMfs) {
635             if (func == name)
636                 return &MethodWrapper!(T, __traits(getMember, T, name)).virtualCall;
637         }
638         return null;
639     }
640 }