1 /++ 2 Initialization, termination, and registration of D libraries in Godot 3 +/ 4 module godot.api.register; 5 6 import godot.util.tools.classes; 7 8 import std.format; 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.traits; 14 import godot.api.script; 15 import godot.api.wrap; 16 import godot.api.udas; 17 import godot.api.reference; 18 19 import godot, godot.abi; 20 21 import godot.abi.gdextension; 22 23 // global instance for current library 24 __gshared GDNativeExtensionClassLibraryPtr _GODOT_library; 25 26 enum bool is_(alias a) = is(a); 27 template fileClassesAsLazyImports(FileInfo f) { 28 template classFrom(string className) { 29 mixin( 30 "alias classFrom = from!\"" ~ f.moduleName ~ "\"" ~ className[f.moduleName.length .. $] ~ ";"); 31 } 32 33 alias fileClassesAsLazyImports = staticMap!(classFrom, aliasSeqOf!(f.classes)); 34 } 35 36 /++ 37 Pass to GodotNativeLibrary to control D runtime initialization/termination. 38 Default is `yes` unless compiling with BetterC. 39 +/ 40 enum LoadDRuntime : bool { 41 no = false, 42 yes = true 43 } 44 45 /++ 46 This mixin will generate the GDNative C interface functions for this D library. 47 Pass to it a name string for the library, followed by the GodotScript types to 48 register, functions to call, and other options to configure Godot-D. 49 50 The symbolPrefix must match the GDNativeLibrary's symbolPrefix in Godot. 51 52 D runtime will be initialized and terminated, unless you pass $(D LoadDRuntime.no) 53 or compile with BetterC. 54 55 Functions taking GodotInitOptions or no arguments will be called at init. 56 Functions taking GodotTerminateOptions will be called at termination. 57 58 Example: 59 --- 60 import godot, godot.node; 61 class TestClass : GodotScript!Node 62 { } 63 mixin GodotNativeLibrary!( 64 "testlib", 65 TestClass, 66 (GodotInitOptions o){ print("Initialized"); }, 67 (GodotTerminateOptions o){ print("Terminated"); } 68 ); 69 --- 70 +/ 71 mixin template GodotNativeLibrary(string symbolPrefix, Args...) { 72 private static import godot.abi; 73 74 private static import godot.abi.gdextension; 75 76 private static import godot.util.tools.classes; 77 private import godot.api.reference; 78 79 static if (__traits(compiles, import("classes.csv"))) { 80 enum godotutil.classes.ProjectInfo _GODOT_projectInfo = godotutil.classes.ProjectInfo.fromCsv( 81 import("classes.csv")); 82 } else { 83 enum godotutil.classes.ProjectInfo _GODOT_projectInfo = godotutil.classes.ProjectInfo.init; 84 } 85 86 /// HACK: empty main to force the compiler to add emulated TLS. 87 version (Android) void main() { 88 } 89 90 // Windows DLL entry points handle TLS+DRuntime initialization and thread attachment 91 version (Windows) { 92 version (D_BetterC) { 93 } else { 94 import core.sys.windows.dll : SimpleDllMain; 95 96 mixin SimpleDllMain; 97 } 98 } 99 100 /// This is the main entry point declared in your .gdextension file, it will be called by godot engine on load 101 pragma(mangle, symbolPrefix ~ "_gdextension_entry") 102 export extern (C) static GDNativeBool godot_gdextension_entry(GDNativeInterface* p_interface, 103 GDNativeExtensionClassLibraryPtr p_library, GDNativeInitialization* r_initialization) { 104 import godot.abi.gdextension; 105 import godot.api.reference; 106 import std.meta, std.traits; 107 import core.runtime : Runtime; 108 import godot.api.output; 109 import godot.api.traits; 110 static import godot.api.register; 111 112 version (Windows) { 113 } else { 114 version (D_BetterC) 115 enum bool loadDRuntime = staticIndexOf!(LoadDRuntime.yes, Args) != -1; 116 else 117 enum bool loadDRuntime = staticIndexOf!(LoadDRuntime.no, Args) == -1; 118 static if (loadDRuntime) 119 Runtime.initialize(); 120 } 121 122 _godot_api = p_interface; 123 godot.api.register._GODOT_library = p_library; 124 125 //import core.exception : assertHandler; 126 //assertHandler = (options.in_editor) ? (&godotAssertHandlerEditorDebug) 127 // : (&godotAssertHandlerCrash); 128 129 // TODO: explore various stages, for example for making core classes 130 r_initialization.minimum_initialization_level = GDNATIVE_INITIALIZATION_SCENE; 131 r_initialization.initialize = &initializeLevel; 132 r_initialization.deinitialize = &deinitializeLevel; 133 134 foreach (Arg; Args) { 135 static if (is(Arg)) { 136 } // is type 137 else static if (isCallable!Arg) { 138 static if (is(typeof(Arg()))) 139 Arg(); 140 else static if (is(typeof(Arg(options)))) 141 Arg(options); 142 } else static if (is(typeof(Arg) == LoadDRuntime)) { 143 } else { 144 static assert(0, "Unrecognized argument <" ~ Arg.stringof ~ "> passed to GodotNativeLibrary"); 145 } 146 } 147 148 return 1; // return OK 149 } 150 151 extern (C) void initializeLevel(void* userdata, GDNativeInitializationLevel level) //@nogc nothrow 152 { 153 //writeln("Initializing level: ", level); 154 import std.exception; 155 156 register_types(userdata, level); 157 } 158 159 extern (C) void deinitializeLevel(void* userdata, GDNativeInitializationLevel level) //@nogc nothrow 160 { 161 //writeln("Deinitializing level: ", level); 162 } 163 164 static void register_types(void* userdata, GDNativeInitializationLevel level) //@nogc nothrow 165 { 166 import std.meta, std.traits; 167 import godot.api.register : register, fileClassesAsLazyImports; 168 import std.array : join; 169 import godot.api.output; 170 import godot.api.traits; 171 172 // currently only scene-level scripts supportes 173 if (level != GDNATIVE_INITIALIZATION_SCENE) 174 return; 175 176 alias classList = staticMap!(fileClassesAsLazyImports, aliasSeqOf!(_GODOT_projectInfo.files)); 177 static foreach (C; NoDuplicates!(classList, Filter!(is_, Args))) { 178 static if (is(C)) { 179 static if (extendsGodotBaseClass!C) { 180 register!C(_GODOT_library); 181 } 182 } 183 } 184 } 185 186 /* 187 pragma(mangle, symbolPrefix~"gdnative_terminate") 188 export extern(C) static void godot_gdnative_terminate(godot.abi.godot_gdnative_terminate_options* options) 189 { 190 import std.meta, std.traits; 191 import godot.api.script : NativeScriptTemplate; 192 import std.array : join; 193 import godot.api.output; 194 import godot.api.traits; 195 196 alias classList = staticMap!(fileClassesAsLazyImports, aliasSeqOf!(_GODOT_projectInfo.files)); 197 static foreach(C; NoDuplicates!(classList, Filter!(is_, Args))) 198 { 199 static if(is(C)) 200 { 201 static if(extendsGodotBaseClass!C) 202 { 203 NativeScriptTemplate!C.unref(); 204 } 205 } 206 } 207 208 foreach(Arg; Args) 209 { 210 static if(is(Arg)) // is type 211 { 212 } 213 else static if(isCallable!Arg) 214 { 215 static if(is(typeof(Arg(options)))) Arg(options); 216 } 217 else static if(is(typeof(Arg) == LoadDRuntime)) { } 218 else 219 { 220 static assert(0, "Unrecognized argument <"~Arg.stringof~"> passed to GodotNativeLibrary"); 221 } 222 } 223 224 _GODOT_library.unref(); 225 226 version(Windows) {} 227 else 228 { 229 import core.runtime : Runtime; 230 version(D_BetterC) enum bool loadDRuntime = staticIndexOf!(LoadDRuntime.yes, Args) != -1; 231 else enum bool loadDRuntime = staticIndexOf!(LoadDRuntime.no, Args) == -1; 232 static if(loadDRuntime) Runtime.terminate(); 233 } 234 } 235 */ 236 } 237 238 private extern (C) 239 godot_variant _GODOT_nop(godot_object o, void* methodData, 240 void* userData, int numArgs, godot_variant** args) { 241 godot_variant n; 242 _godot_api.variant_new_nil(&n); 243 return n; 244 } 245 246 /++ 247 Register a class and all its $(D @GodotMethod) member functions into Godot. 248 +/ 249 void register(T)(GDNativeExtensionClassLibraryPtr lib) if (is(T == class)) { 250 import std.array; 251 import godot.abi; 252 import godot.object, godot.resource; 253 import godot.api; 254 255 //static import godot.nativescript; 256 257 static if (BaseClassesTuple!T.length == 2) // base class is GodotScript; use owner 258 { 259 alias Base = typeof(T.owner); 260 alias baseName = Base._GODOT_internal_name; 261 } 262 else // base class is another D script 263 { 264 alias Base = BaseClassesTuple!T[0]; 265 static if (hasUDA!(Base, Rename)) 266 enum immutable(char*) baseName = godotName!Base ~ '\0'; 267 else 268 enum immutable(char*) baseName = __traits(identifier, Base) ~ '\0'; 269 } 270 271 static if (hasUDA!(T, Rename)) 272 enum immutable(char*) name = godotName!T ~ '\0'; 273 else 274 enum immutable(char*) name = __traits(identifier, T) ~ '\0'; 275 enum fqn = fullyQualifiedName!T ~ '\0'; 276 277 __gshared static GDNativeExtensionClassCreationInfo class_info; 278 class_info.create_instance_func = &createFunc!T; 279 class_info.free_instance_func = &destroyFunc!T; 280 class_info.class_userdata = cast(void*) cast(const char*) name; 281 282 extern (C) static GDNativeExtensionClassCallVirtual getVirtualFn(void* p_userdata, const char* p_name) { 283 import core.stdc.stdio; 284 import core.stdc.string; 285 286 //printf("requested method %s\n", p_name); 287 static if (__traits(compiles, __traits(getMember, T, "_ready"))) 288 if (strcmp(p_name, "_ready") == 0) { 289 //puts("onready method found"); 290 return cast(GDNativeExtensionClassCallVirtual)&OnReadyWrapper!(T, __traits(getMember, T, "_ready")) 291 .callOnReady; 292 } 293 return VirtualMethodsHelper!T.findVCall(cast(string) p_name[0 .. p_name.strlen]); 294 } 295 296 class_info.get_virtual_func = &getVirtualFn; 297 298 _godot_api.classdb_register_extension_class(lib, name, baseName, &class_info); 299 300 void registerMethod(alias mf, string nameOverride = null)() { 301 static if (nameOverride.length) { 302 char[nameOverride.length + 1] mfn = void; 303 mfn[0 .. nameOverride.length] = nameOverride; 304 mfn[$ - 1] = '\0'; 305 } else { 306 char[godotName!mf.length + 1] mfn = void; 307 mfn[0 .. godotName!mf.length] = godotName!mf[]; 308 mfn[$ - 1] = '\0'; 309 } 310 311 uint flags = GDNATIVE_EXTENSION_METHOD_FLAGS_DEFAULT; 312 313 // virtual methods like '_ready' 314 if (__traits(identifier, mf)[0] == '_') 315 flags |= GDNATIVE_EXTENSION_METHOD_FLAG_VIRTUAL; 316 317 enum isOnReady = godotName!mf == "_ready" && onReadyFieldNames!T.length; 318 319 GDNativeExtensionClassMethodInfo mi = { 320 mfn.ptr, //const char *name; 321 &mf, //void *method_userdata; 322 &MethodWrapper!(T, mf).callMethod, //GDNativeExtensionClassMethodCall call_func; 323 &MethodWrapper!(T, mf).callPtrMethod, //GDNativeExtensionClassMethodPtrCall ptrcall_func; 324 flags, //uint32_t method_flags; /* GDNativeExtensionClassMethodFlags */ 325 cast(uint32_t) arity!mf, //uint32_t argument_count; 326 cast(GDNativeBool) !is(ReturnType!mf == void), //GDNativeBool has_return_value; 327 &MethodWrapperMeta!mf.getArgTypesFn, //(GDNativeExtensionClassMethodGetArgumentType) get_argument_type_func; 328 &MethodWrapperMeta!mf.getArgInfoFn, //GDNativeExtensionClassMethodGetArgumentInfo get_argument_info_func; /* name and hint information for the argument can be omitted in release builds. Class name should always be present if it applies. */ 329 &MethodWrapperMeta!mf.getArgMetadataFn, //GDNativeExtensionClassMethodGetArgumentMetadata get_argument_metadata_func; 330 MethodWrapperMeta!mf.getDefaultArgNum, //uint32_t default_argument_count; 331 MethodWrapperMeta!mf.getDefaultArgs(), //GDNativeVariantPtr *default_arguments; 332 333 }; 334 335 _godot_api.classdb_register_extension_class_method(lib, name, &mi); 336 } 337 338 void registerMemberAccessor(alias mf, alias propType, string funcName)() { 339 static assert(Parameters!propType.length == 0 || Parameters!propType.length == 1, 340 "only getter or setter is allowed with exactly zero or one arguments"); 341 342 static if (funcName) { 343 char[funcName.length + 1] mfn = void; 344 mfn[0 .. funcName.length] = funcName; 345 mfn[$ - 1] = '\0'; 346 } else 347 char[1] mfn = '\0'; 348 349 uint flags = GDNATIVE_EXTENSION_METHOD_FLAGS_DEFAULT; 350 351 GDNativeExtensionClassMethodInfo mi = { 352 mfn.ptr, //const char *name; 353 &mf, //void *method_userdata; 354 &mf, //GDNativeExtensionClassMethodCall call_func; 355 null, //GDNativeExtensionClassMethodPtrCall ptrcall_func; 356 flags, //uint32_t method_flags; /* GDNativeExtensionClassMethodFlags */ 357 cast(uint32_t) arity!propType, //uint32_t argument_count; 358 cast(GDNativeBool) !is(ReturnType!mf == void), //GDNativeBool has_return_value; 359 &MethodWrapperMeta!propType.getArgTypesFn, //(GDNativeExtensionClassMethodGetArgumentType) get_argument_type_func; 360 &MethodWrapperMeta!propType.getArgInfoFn, //GDNativeExtensionClassMethodGetArgumentInfo get_argument_info_func; /* name and hint information for the argument can be omitted in release builds. Class name should always be present if it applies. */ 361 &MethodWrapperMeta!propType.getArgMetadataFn, //GDNativeExtensionClassMethodGetArgumentMetadata get_argument_metadata_func; 362 MethodWrapperMeta!propType.getDefaultArgNum, //uint32_t default_argument_count; 363 MethodWrapperMeta!propType.getDefaultArgs(), //GDNativeVariantPtr *default_arguments; 364 365 }; 366 367 _godot_api.classdb_register_extension_class_method(lib, name.ptr, &mi); 368 } 369 370 static foreach (mf; godotMethods!T) { 371 { 372 static if (hasUDA!(mf, Rename)) 373 enum string externalName = godotName!mf; 374 else 375 enum string externalName = (fullyQualifiedName!mf).replace(".", "_"); 376 registerMethod!mf(); 377 } 378 } 379 380 static foreach (sName; godotSignals!T) { 381 { 382 alias s = Alias!(mixin("T." ~ sName)); 383 static assert(hasStaticMember!(T, sName), "Signal declaration " ~ fullyQualifiedName!s 384 ~ " must be static. Otherwise it would take up memory in every instance of " ~ T 385 .stringof); 386 387 static if (hasUDA!(s, Rename)) 388 enum string externalName = godotName!s; 389 else 390 enum string externalName = (fullyQualifiedName!s).replace(".", "_"); 391 392 __gshared static GDNativePropertyInfo[Parameters!s.length] prop; 393 static foreach (int i, p; Parameters!s) { 394 static assert(Variant.compatible!p, fullyQualifiedName!s ~ " parameter " ~ i.text ~ " \"" 395 ~ ParameterIdentifierTuple!s[i] ~ "\": type " ~ p.stringof ~ " is incompatible with Godot"); 396 397 // get name or argN fallback placeholder in case of function pointers 398 static if (ParameterIdentifierTuple!s[i].length > 0) 399 prop[i].name = ParameterIdentifierTuple!s[i]; 400 else 401 prop[i].name = "arg" ~ i.stringof; 402 403 if (Variant.variantTypeOf!p == VariantType.object) 404 prop[i].class_name = name.ptr; 405 prop[i].type = Variant.variantTypeOf!p; 406 prop[i].hint = 0; 407 prop[i].hint_string = null; 408 prop[i].usage = GDNATIVE_EXTENSION_METHOD_FLAGS_DEFAULT; 409 } 410 411 _godot_api.classdb_register_extension_class_signal(lib, name.ptr, (externalName).ptr, prop.ptr, Parameters!s 412 .length); 413 } 414 } 415 416 // -------- PROPERTIES 417 418 enum bool matchName(string p, alias a) = (godotName!a == p); 419 static foreach (pName; godotPropertyNames!T) { 420 { 421 enum propName = pName ~ '\0'; 422 423 alias getterMatches = Filter!(ApplyLeft!(matchName, pName), godotPropertyGetters!T); 424 static assert(getterMatches.length <= 1); /// TODO: error message 425 alias setterMatches = Filter!(ApplyLeft!(matchName, pName), godotPropertySetters!T); 426 static assert(setterMatches.length <= 1); 427 428 static if (getterMatches.length) 429 alias P = NonRef!(ReturnType!(getterMatches[0])); 430 else 431 alias P = Parameters!(setterMatches[0])[0]; 432 //static assert(!is(P : Ref!U, U)); /// TODO: proper Ref handling 433 enum VariantType vt = extractPropertyVariantType!(getterMatches, setterMatches); 434 435 enum Property uda = extractPropertyUDA!(getterMatches, setterMatches); 436 437 __gshared static GDNativePropertyInfo pinfo; 438 439 static if (Variant.variantTypeOf!P == VariantType.object) 440 pinfo.class_name = godotName!(P).ptr; 441 pinfo.type = vt; 442 pinfo.name = propName.ptr; 443 pinfo.hint = GDNATIVE_EXTENSION_METHOD_ARGUMENT_METADATA_NONE; 444 //pinfo.usage = GDNATIVE_EXTENSION_METHOD_FLAGS_DEFAULT | GDNATIVE_EXTENSION_METHOD_FLAG_EDITOR; 445 pinfo.usage = 7; // godot-cpp uses 7 as value which is default|const|editor currently, doesn't shows up in inspector without const. WTF? 446 static if (uda.hintString.length) 447 pinfo.hint_string = uda.hintString; 448 449 // register acessor methods for that property 450 static if (getterMatches.length) { 451 enum get_prop = "get_" ~ pName ~ '\0'; 452 registerMethod!(getterMatches[0], get_prop); 453 } else 454 enum get_prop = string.init; 455 456 static if (setterMatches.length) { 457 enum set_prop = "set_" ~ pName ~ '\0'; 458 registerMethod!(setterMatches[0], set_prop); 459 } else 460 enum set_prop = string.init; 461 462 _godot_api.classdb_register_extension_class_property(lib, name.ptr, &pinfo, set_prop.ptr, get_prop 463 .ptr); 464 } 465 } 466 static foreach (pName; godotPropertyVariableNames!T) { 467 { 468 import std.string; 469 470 enum propName = pName ~ '\0'; 471 alias P = typeof(mixin("T." ~ pName)); 472 enum Variant.Type vt = Variant.variantTypeOf!P; 473 alias udas = getUDAs!(mixin("T." ~ pName), Property); 474 enum Property uda = is(udas[0]) ? Property.init : udas[0]; 475 476 __gshared static GDNativePropertyInfo pinfo; 477 478 static if (Variant.variantTypeOf!P == VariantType.object) 479 pinfo.class_name = godotName!(P).ptr, 480 pinfo.type = vt; 481 pinfo.name = propName.ptr; 482 pinfo.usage = GDNATIVE_EXTENSION_METHOD_FLAGS_DEFAULT | GDNATIVE_EXTENSION_METHOD_FLAG_EDITOR; 483 static if (uda.hintString.length) 484 pinfo.hint_string = uda.hintString; 485 486 // register acessor methods for that property 487 enum get_prop = "get_" ~ pName ~ '\0'; 488 alias fnWrapper = VariableWrapper!(T, __traits(getMember, T, pName)); 489 static fnWrapper.getterType getterTmp; // dummy func for now because current registration code requires actual function, and there isn't one 490 registerMemberAccessor!(fnWrapper.callPropertyGet, getterTmp, get_prop); 491 492 enum set_prop = "set_" ~ pName ~ '\0'; 493 static fnWrapper.setterType setterTmp; // dummy func for now because current registration code requires actual function, and there isn't one 494 registerMemberAccessor!(fnWrapper.callPropertySet, setterTmp, set_prop); 495 496 _godot_api.classdb_register_extension_class_property(lib, name.ptr, &pinfo, set_prop.ptr, get_prop 497 .ptr); 498 } 499 } 500 501 static foreach (E; godotEnums!T) { 502 { 503 static foreach (ev; __traits(allMembers, E)) { 504 //pragma(msg, ev, ":", cast(int) __traits(getMember, E, ev)); 505 _godot_api.classdb_register_extension_class_integer_constant(lib, name.ptr, 506 __traits(identifier, E), ev, cast(int) __traits(getMember, E, ev), false); 507 } 508 } 509 } 510 511 static foreach (pName; godotConstants!T) { 512 { 513 alias E = __traits(getMember, T, pName); 514 //pragma(msg, pName, ":", cast(int) E); 515 _godot_api.classdb_register_extension_class_integer_constant(lib, name.ptr, null, pName, cast( 516 int) E, false); 517 } 518 } 519 520 /* 521 auto icf = godot_instance_create_func(&createFunc!T, null, null); 522 auto idf = godot_instance_destroy_func(&destroyFunc!T, null, null); 523 524 static if(hasUDA!(T, Tool)) _godot_nativescript_api.godot_nativescript_register_tool_class(handle, name, baseName, icf, idf); 525 else _godot_nativescript_api.godot_nativescript_register_class(handle, name, baseName, icf, idf); 526 527 if(GDNativeVersion.hasNativescript!(1, 1)) 528 { 529 _godot_nativescript_api.godot_nativescript_set_type_tag(handle, name, NativeScriptTag!T.tag); 530 } 531 else // register a no-op function that indicates this is a D class 532 { 533 godot_instance_method md; 534 md.method = &_GODOT_nop; 535 md.free_func = null; 536 _godot_nativescript_api.godot_nativescript_register_method(handle, name, "_GDNATIVE_D_typeid", godot_method_attributes.init, md); 537 } 538 539 static foreach(mf; godotMethods!T) 540 {{ 541 godot_method_attributes ma; 542 static if(is( getUDAs!(mf, Method)[0] )) ma.rpc_type = godot_method_rpc_mode 543 .GODOT_METHOD_RPC_MODE_DISABLED; 544 else 545 { 546 ma.rpc_type = cast(godot_method_rpc_mode)(getUDAs!(mf, Method)[0].rpcMode); 547 } 548 549 godot_instance_method md; 550 static if(godotName!mf == "_ready" && onReadyFieldNames!T.length) 551 { 552 md.method = &OnReadyWrapper!T.callOnReady; 553 } 554 else md.method = &MethodWrapper!(T, mf).callMethod; 555 md.free_func = null; 556 557 char[godotName!mf.length+1] mfn = void; 558 mfn[0..godotName!mf.length] = godotName!mf[]; 559 mfn[$-1] = '\0'; 560 _godot_nativescript_api.godot_nativescript_register_method(handle, name, mfn.ptr, ma, md); 561 }} 562 563 // OnReady when there is no _ready method 564 static if(staticIndexOf!("_ready", staticMap!(godotName, godotMethods!T)) == -1 565 && onReadyFieldNames!T.length) 566 { 567 enum ma = godot_method_attributes.init; 568 godot_instance_method md; 569 md.method = &OnReadyWrapper!T.callOnReady; 570 _godot_nativescript_api.godot_nativescript_register_method(handle, name, "_ready", ma, md); 571 } 572 573 static foreach(sName; godotSignals!T) 574 {{ 575 alias s = Alias!(mixin("T."~sName)); 576 static assert(hasStaticMember!(T, sName), "Signal declaration "~fullyQualifiedName!s 577 ~" must be static. Otherwise it would take up memory in every instance of "~T.stringof); 578 579 godot_signal gs; 580 (*cast(String*)&gs.name) = String(godotName!s); 581 gs.num_args = Parameters!s.length; 582 583 static if(Parameters!s.length) 584 { 585 godot_signal_argument[Parameters!s.length] args; 586 gs.args = args.ptr; 587 } 588 589 foreach(pi, P; Parameters!s) 590 { 591 static assert(Variant.compatible!P, fullyQualifiedName!s~" parameter "~pi.text~" \"" 592 ~ParameterIdentifierTuple!s[pi]~"\": type "~P.stringof~" is incompatible with Godot"); 593 static if(ParameterIdentifierTuple!s[pi].length > 0) 594 { 595 (*cast(String*)&args[pi].name) = String(ParameterIdentifierTuple!s[pi]); 596 } 597 else 598 { 599 (*cast(String*)&args[pi].name) = (String(P.stringof) ~ String("Arg") ~ Variant(pi).as!String); 600 } 601 args[pi].type = Variant.variantTypeOf!P; 602 args[pi].usage = cast(godot_property_usage_flags)Property.Usage.defaultUsage; 603 } 604 605 _godot_nativescript_api.godot_nativescript_register_signal(handle, name, &gs); 606 }} 607 608 enum bool matchName(string p, alias a) = (godotName!a == p); 609 static foreach(pName; godotPropertyNames!T) 610 {{ 611 alias getterMatches = Filter!(ApplyLeft!(matchName, pName), godotPropertyGetters!T); 612 static assert(getterMatches.length <= 1); /// TODO: error message 613 alias setterMatches = Filter!(ApplyLeft!(matchName, pName), godotPropertySetters!T); 614 static assert(setterMatches.length <= 1); 615 616 godot_property_set_func sf; 617 godot_property_get_func gf; 618 godot_property_attributes attr; 619 620 static if(getterMatches.length) alias P = NonRef!(ReturnType!(getterMatches[0])); 621 else alias P = Parameters!(setterMatches[0])[0]; 622 static assert(!is(P : Ref!U, U)); /// TODO: proper Ref handling 623 enum Variant.Type vt = extractPropertyVariantType!(getterMatches, setterMatches); 624 attr.type = cast(godot_int)vt; 625 626 enum Property uda = extractPropertyUDA!(getterMatches, setterMatches); 627 attr.rset_type = cast(godot_method_rpc_mode)uda.rpcMode; 628 attr.hint = cast(godot_property_hint)uda.hint; 629 630 static if(vt == Variant.Type.object && extends!(P, Resource)) 631 { 632 attr.hint |= godot_property_hint.GODOT_PROPERTY_HINT_RESOURCE_TYPE; 633 } 634 635 static if(uda.hintString.length) _godot_api.string_parse_utf8( 636 &attr.hint_string, uda.hintString.ptr); 637 else 638 { 639 static if(vt == Variant.Type.object) 640 { 641 _godot_api.string_parse_utf8(&attr.hint_string, 642 GodotClass!P._GODOT_internal_name); 643 } 644 else _godot_api.string_new(&attr.hint_string); 645 } 646 attr.usage = cast(godot_property_usage_flags)(uda.usage | 647 Property.Usage.scriptVariable); 648 649 Variant defval; 650 static if(getterMatches.length) enum gDef = hasUDA!(getterMatches[0], DefaultValue); 651 else enum gDef = false; 652 static if(setterMatches.length) enum sDef = hasUDA!(setterMatches[0], DefaultValue); 653 else enum sDef = false; 654 655 static if(gDef || sDef) 656 { 657 static if(gDef) alias defExprSeq = TemplateArgsOf!(getUDAs!(getterMatches[0], DefaultValue)[0]); 658 else alias defExprSeq = TemplateArgsOf!(getUDAs!(setterMatches[0], DefaultValue)[0]); 659 defval = defExprSeq[0]; 660 } 661 else static if( is(typeof( { P p; } )) ) // use type's default value 662 { 663 static if(isFloatingPoint!P) 664 { 665 // Godot doesn't support NaNs. Initialize properties to 0.0 instead. 666 defval = 0.0; 667 } 668 else defval = P.init; 669 } 670 else 671 { 672 /// FIXME: call default constructor function 673 defval = null; 674 } 675 attr.default_value = defval._godot_variant; 676 677 static if(getterMatches.length) 678 { 679 alias GetWrapper = MethodWrapper!(T, getterMatches[0]); 680 gf.get_func = &GetWrapper.callPropertyGet; 681 gf.free_func = null; 682 } 683 else 684 { 685 gf.get_func = &emptyGetter; 686 } 687 688 static if(setterMatches.length) 689 { 690 alias SetWrapper = MethodWrapper!(T, setterMatches[0]); 691 sf.set_func = &SetWrapper.callPropertySet; 692 sf.free_func = null; 693 } 694 else 695 { 696 sf.set_func = &emptySetter; 697 } 698 699 char[pName.length+1] pn = void; 700 pn[0..pName.length] = pName[]; 701 pn[$-1] = '\0'; 702 _godot_nativescript_api.godot_nativescript_register_property(handle, name, pn.ptr, &attr, sf, gf); 703 }} 704 static foreach(pName; godotPropertyVariableNames!T) 705 {{ 706 import std.string; 707 708 godot_property_set_func sf; 709 godot_property_get_func gf; 710 godot_property_attributes attr; 711 712 alias P = typeof(mixin("T."~pName)); 713 enum Variant.Type vt = Variant.variantTypeOf!P; 714 attr.type = cast(godot_int)vt; 715 716 alias udas = getUDAs!(mixin("T."~pName), Property); 717 enum Property uda = is(udas[0]) ? Property.init : udas[0]; 718 attr.rset_type = cast(godot_method_rpc_mode)uda.rpcMode; 719 attr.hint = cast(godot_property_hint)uda.hint; 720 721 static if(vt == Variant.Type.object && is(GodotClass!P : Resource)) 722 { 723 attr.hint |= godot_property_hint.GODOT_PROPERTY_HINT_RESOURCE_TYPE; 724 } 725 726 static if(uda.hintString.length) _godot_api.string_parse_utf8( 727 &attr.hint_string, uda.hintString.ptr); 728 else 729 { 730 static if(vt == Variant.Type.object) 731 { 732 _godot_api.string_parse_utf8(&attr.hint_string, 733 GodotClass!P._GODOT_internal_name); 734 } 735 else _godot_api.string_new(&attr.hint_string); 736 } 737 attr.usage = cast(godot_property_usage_flags)uda.usage | 738 cast(godot_property_usage_flags)Property.Usage.scriptVariable; 739 740 Variant defval = getDefaultValueFromAlias!(T, pName)(); 741 attr.default_value = defval._godot_variant; 742 743 alias Wrapper = VariableWrapper!(T, pName); 744 745 { 746 gf.method_data = null; 747 gf.get_func = &Wrapper.callPropertyGet; 748 gf.free_func = null; 749 } 750 751 { 752 sf.method_data = null; 753 sf.set_func = &Wrapper.callPropertySet; 754 sf.free_func = null; 755 } 756 757 enum pnLength = godotName!(mixin("T."~pName)).length; 758 char[pnLength+1] pn = void; 759 pn[0..pnLength] = godotName!(mixin("T."~pName))[]; 760 pn[$-1] = '\0'; 761 _godot_nativescript_api.godot_nativescript_register_property(handle, name, pn.ptr, &attr, sf, gf); 762 }} 763 764 765 766 godot.api.script.NativeScriptTemplate!T = memnew!(godot.nativescript.NativeScript); 767 godot.api.script.NativeScriptTemplate!T.setLibrary(lib); 768 godot.api.script.NativeScriptTemplate!T.setClassName(String(name)); 769 */ 770 }