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 }