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 }