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 }