1 /++ 2 Initialization, termination, and registration of D libraries in Godot 3 +/ 4 module godot.api.register; 5 6 import godot.util.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 GDExtensionClassLibraryPtr _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 GDExtension 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 GDExtensionLibrary'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 import godot.abi.gdextension; 75 private import godot.abi.core; 76 77 private static import godot.util.classes; 78 private import godot.api.reference; 79 80 static if (__traits(compiles, import("classes.csv"))) { 81 enum godot.util.classes.ProjectInfo _GODOT_projectInfo = godot.util.classes.ProjectInfo.fromCsv( 82 import("classes.csv")); 83 } else { 84 enum godot.util.classes.ProjectInfo _GODOT_projectInfo = godot.util.classes.ProjectInfo.init; 85 } 86 87 /// HACK: empty main to force the compiler to add emulated TLS. 88 version (Android) void main() { 89 } 90 91 // Windows DLL entry points handle TLS+DRuntime initialization and thread attachment 92 version (Windows) { 93 version (D_BetterC) { 94 } else { 95 import core.sys.windows.dll : SimpleDllMain; 96 97 mixin SimpleDllMain; 98 } 99 } 100 101 /// This is the main entry point declared in your .gdextension file, it will be called by godot engine on load 102 pragma(mangle, symbolPrefix ~ "_gdextension_entry") 103 export extern (C) static GDExtensionBool godot_gdextension_entry(GDExtensionInterface* p_interface, 104 GDExtensionClassLibraryPtr p_library, GDExtensionInitialization* r_initialization) { 105 import godot.abi.gdextension; 106 import godot.api.reference; 107 import std.meta, std.traits; 108 import core.runtime : Runtime; 109 import godot.api.output; 110 import godot.api.traits; 111 static import godot.api.register; 112 113 version (Windows) { 114 } else { 115 version (D_BetterC) 116 enum bool loadDRuntime = staticIndexOf!(LoadDRuntime.yes, Args) != -1; 117 else 118 enum bool loadDRuntime = staticIndexOf!(LoadDRuntime.no, Args) == -1; 119 static if (loadDRuntime) 120 Runtime.initialize(); 121 } 122 123 _godot_api = p_interface; 124 godot.api.register._GODOT_library = p_library; 125 126 //import core.exception : assertHandler; 127 //assertHandler = (options.in_editor) ? (&godotAssertHandlerEditorDebug) 128 // : (&godotAssertHandlerCrash); 129 130 // TODO: explore various stages, for example for making core classes 131 r_initialization.minimum_initialization_level = GDEXTENSION_INITIALIZATION_SCENE; 132 r_initialization.initialize = &initializeLevel; 133 r_initialization.deinitialize = &deinitializeLevel; 134 135 foreach (Arg; Args) { 136 static if (is(Arg)) { 137 } // is type 138 else static if (isCallable!Arg) { 139 static if (is(typeof(Arg()))) 140 Arg(); 141 else static if (is(typeof(Arg(options)))) 142 Arg(options); 143 } else static if (is(typeof(Arg) == LoadDRuntime)) { 144 } else { 145 static assert(0, "Unrecognized argument <" ~ Arg.stringof ~ "> passed to GodotNativeLibrary"); 146 } 147 } 148 149 return 1; // return OK 150 } 151 152 extern (C) void initializeLevel(void* userdata, GDExtensionInitializationLevel level) //@nogc nothrow 153 { 154 //writeln("Initializing level: ", level); 155 import std.exception; 156 157 register_types(userdata, level); 158 } 159 160 extern (C) void deinitializeLevel(void* userdata, GDExtensionInitializationLevel level) //@nogc nothrow 161 { 162 //writeln("Deinitializing level: ", level); 163 164 unregister_types(userdata, level); 165 } 166 167 static void register_types(void* userdata, GDExtensionInitializationLevel level) //@nogc nothrow 168 { 169 import std.meta, std.traits; 170 import godot.api.register : register, fileClassesAsLazyImports; 171 import std.array : join; 172 import godot.api.output; 173 import godot.api.traits; 174 175 // currently only scene-level scripts supportes 176 if (level != GDEXTENSION_INITIALIZATION_SCENE) 177 return; 178 179 alias classList = staticMap!(fileClassesAsLazyImports, aliasSeqOf!(_GODOT_projectInfo.files)); 180 static foreach (C; NoDuplicates!(classList, Filter!(is_, Args))) { 181 static if (is(C)) { 182 static if (extendsGodotBaseClass!C) { 183 register!C(_GODOT_library); 184 } 185 } 186 } 187 } 188 189 static void unregister_types(void* userdata, GDExtensionInitializationLevel level) 190 { 191 import std.meta, std.traits; 192 import godot.api.register : register, fileClassesAsLazyImports; 193 import std.array : join; 194 import godot.api.output; 195 import godot.api.traits; 196 197 // FIXME: level 3 for some reason not happens 198 // currently only scene-level scripts supported 199 //if (level != GDEXTENSION_INITIALIZATION_SCENE) 200 // return; 201 202 // TODO: this will likely crash in a real project, classes has to be sorted in such way that 203 // descendants unregistered before parent 204 alias classList = staticMap!(fileClassesAsLazyImports, aliasSeqOf!(_GODOT_projectInfo.files)); 205 static foreach (C; NoDuplicates!(classList, Filter!(is_, Args))) { 206 static if (is(C)) { 207 static if (extendsGodotBaseClass!C) { 208 unregister!C(_GODOT_library); 209 } 210 } 211 } 212 213 version(Windows) {} 214 else 215 { 216 import core.runtime : Runtime; 217 version(D_BetterC) enum bool loadDRuntime = staticIndexOf!(LoadDRuntime.yes, Args) != -1; 218 else enum bool loadDRuntime = staticIndexOf!(LoadDRuntime.no, Args) == -1; 219 static if(loadDRuntime) Runtime.terminate(); 220 } 221 } 222 } 223 224 private extern (C) 225 godot_variant _GODOT_nop(godot_object o, void* methodData, 226 void* userData, int numArgs, godot_variant** args) { 227 godot_variant n; 228 _godot_api.variant_new_nil(&n); 229 return n; 230 } 231 232 /++ 233 Register a class and all its $(D @GodotMethod) member functions into Godot. 234 +/ 235 void register(T)(GDExtensionClassLibraryPtr lib) if (is(T == class)) { 236 import std.array; 237 import godot.abi; 238 import godot.object, godot.resource; 239 import godot.api; 240 241 //static import godot.nativescript; 242 243 static if (BaseClassesTuple!T.length == 2) // base class is GodotScript; use owner 244 { 245 alias Base = typeof(T.owner); 246 alias baseName = Base._GODOT_internal_name; 247 } 248 else // base class is another D script 249 { 250 alias Base = BaseClassesTuple!T[0]; 251 static if (hasUDA!(Base, Rename)) 252 enum string baseName = godotName!Base; 253 else 254 enum string baseName = __traits(identifier, Base); 255 } 256 257 // NOTE: Keep in sync with script.d createFunc(T) template 258 static if (hasUDA!(T, Rename)) 259 enum string name = godotName!T; 260 else 261 enum string name = __traits(identifier, T); 262 enum fqn = fullyQualifiedName!T ~ '\0'; 263 264 __gshared static GDExtensionClassCreationInfo class_info; 265 class_info.create_instance_func = &createFunc!T; 266 class_info.free_instance_func = &destroyFunc!T; 267 class_info.class_userdata = cast(void*) name.ptr; 268 269 extern (C) static GDExtensionClassCallVirtual getVirtualFn(void* p_userdata, const GDExtensionStringNamePtr p_name) { 270 import core.stdc.stdio; 271 import core.stdc.string; 272 import std.conv : to; 273 274 //print("requested method ", *cast(StringName*) p_name); 275 // FIXME: StringName issues 276 auto v = Variant(*cast(StringName*) p_name); 277 auto str = v.as!String.data(); 278 static if (__traits(compiles, __traits(getMember, T, "_ready"))) { 279 //if (MethodWrapper!(T, __traits(getMember, T, "_ready")).funName == p_name) { 280 if (str == "_ready") { 281 return cast(GDExtensionClassCallVirtual) 282 &OnReadyWrapper!(T, __traits(getMember, T, "_ready")).callOnReady; 283 } 284 } 285 return VirtualMethodsHelper!T.findVCall(p_name); 286 } 287 288 class_info.get_virtual_func = &getVirtualFn; 289 290 StringName snClass = StringName(name); 291 StringName snBase = StringName(baseName); 292 _godot_api.classdb_register_extension_class(lib, cast(GDExtensionStringNamePtr) snClass, cast(GDExtensionStringNamePtr) snBase, &class_info); 293 294 void registerMethod(alias mf, string nameOverride = null)() { 295 static if (nameOverride.length) { 296 string mfn = nameOverride; 297 } else { 298 string mfn = godotName!mf; 299 } 300 301 uint flags = GDEXTENSION_METHOD_FLAGS_DEFAULT; 302 303 // virtual methods like '_ready' 304 if (__traits(identifier, mf)[0] == '_') 305 flags |= GDEXTENSION_METHOD_FLAG_VIRTUAL; 306 307 if (__traits(isStaticFunction, mf)) 308 flags = GDEXTENSION_METHOD_FLAG_STATIC; 309 310 enum isOnReady = godotName!mf == "_ready" && onReadyFieldNames!T.length; 311 312 StringName snFunName = StringName(mfn); 313 GDExtensionClassMethodInfo mi = { 314 cast(GDExtensionStringNamePtr) snFunName , //const char *name; 315 &mf, //void *method_userdata; 316 &MethodWrapper!(T, mf).callMethod, //GDExtensionClassMethodCall call_func; 317 &MethodWrapper!(T, mf).callPtrMethod, //GDExtensionClassMethodPtrCall ptrcall_func; 318 flags, //uint32_t method_flags; /* GDExtensionClassMethodFlags */ 319 320 cast(GDExtensionBool) !is(ReturnType!mf == void), //GDExtensionBool has_return_value; 321 MethodWrapperMeta!mf.getReturnInfo().ptr, //GDExtensionPropertyInfo* return_value_info; 322 MethodWrapperMeta!mf.getReturnMetadata, //GDExtensionClassMethodArgumentMetadata return_value_metadata; 323 324 cast(uint32_t) arity!mf, //uint32_t argument_count; 325 MethodWrapperMeta!mf.getArgInfo().ptr, //GDExtensionPropertyInfo* arguments_info; 326 MethodWrapperMeta!mf.getArgMetadata(), //GDExtensionClassMethodArgumentMetadata* arguments_metadata; 327 328 MethodWrapperMeta!mf.getDefaultArgNum, //uint32_t default_argument_count; 329 MethodWrapperMeta!mf.getDefaultArgs(), //GDExtensionVariantPtr *default_arguments; 330 331 }; 332 _godot_api.classdb_register_extension_class_method(lib, cast(GDExtensionStringNamePtr) snClass, &mi); 333 // cache StringName for comparison later on 334 MethodWrapper!(T, mf).funName = cast(GDExtensionStringNamePtr) snFunName; 335 } 336 337 void registerMemberAccessor(alias mf, alias propType, string funcName)() { 338 static assert(Parameters!propType.length == 0 || Parameters!propType.length == 1, 339 "only getter or setter is allowed with exactly zero or one arguments"); 340 341 //static if (funcName) { 342 StringName snName = StringName(funcName); 343 //} else 344 // StringName snName = StringName(godotName!mf); 345 346 uint flags = GDEXTENSION_METHOD_FLAGS_DEFAULT; 347 348 GDExtensionClassMethodInfo mi = { 349 cast(GDExtensionStringNamePtr) snName, //const char *name; 350 &mf, //void *method_userdata; 351 &mf, //GDExtensionClassMethodCall call_func; 352 null, //GDExtensionClassMethodPtrCall ptrcall_func; 353 flags, //uint32_t method_flags; /* GDExtensionClassMethodFlags */ 354 355 cast(GDExtensionBool) !is(ReturnType!propType == void), //GDExtensionBool has_return_value; 356 MethodWrapperMeta!propType.getReturnInfo.ptr, 357 MethodWrapperMeta!propType.getReturnMetadata, 358 359 cast(uint32_t) arity!propType, //uint32_t argument_count; 360 MethodWrapperMeta!propType.getArgInfo.ptr, //GDExtensionPropertyInfo* arguments_info; 361 MethodWrapperMeta!propType.getArgMetadata, //GDExtensionClassMethodArgumentMetadata* arguments_metadata; 362 363 MethodWrapperMeta!propType.getDefaultArgNum, //uint32_t default_argument_count; 364 MethodWrapperMeta!propType.getDefaultArgs(), //GDExtensionVariantPtr *default_arguments; 365 }; 366 367 _godot_api.classdb_register_extension_class_method(lib, cast(GDExtensionStringNamePtr) snClass, &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 StringName[Parameters!s.length] snArgNames = void; 393 __gshared static StringName[Parameters!s.length] snClassNames = void; 394 __gshared static StringName[Parameters!s.length] snHintStrings = void; 395 __gshared static GDExtensionPropertyInfo[Parameters!s.length] prop; 396 static if (is(FunctionTypeOf!s FT == __parameters)){ 397 //pragma(msg, typeof(s), " : ", FT); 398 alias PARAMS = FT; 399 } 400 static foreach (int i, p; Parameters!s) { 401 static assert(Variant.compatible!p, fullyQualifiedName!s ~ " parameter " ~ i.text ~ " \"" 402 ~ ParameterIdentifierTuple!s[i] ~ "\": type " ~ p.stringof ~ " is incompatible with Godot"); 403 404 // get name or argN fallback placeholder in case of function pointers 405 static if (PARAMS.length > 0) { 406 // "(String message)" gets split in half, and then chop out closing parenthesis 407 // "(String message, String test)" handled as well 408 //pragma(msg, PARAMS[i..i+1].stringof.split()[1][0..$-1]); 409 snArgNames[i] = StringName(PARAMS[i..i+1].stringof.split()[1][0..$-1]); 410 } 411 else { 412 snArgNames[i] = StringName("arg" ~ i.stringof); 413 } 414 prop[i].name = cast(GDExtensionStringNamePtr) snArgNames[i]; 415 416 if (Variant.variantTypeOf!p == VariantType.object) 417 snClassNames[i] = snClass; 418 else 419 snClassNames[i] = stringName(); 420 prop[i].class_name = cast(GDExtensionStringNamePtr) snClassNames[i]; 421 422 snHintStrings[i] = stringName(); 423 prop[i].hint_string = cast(GDExtensionStringNamePtr) snHintStrings[i]; 424 prop[i].type = cast(GDExtensionVariantType) Variant.variantTypeOf!p; 425 prop[i].hint = 0; 426 prop[i].usage = GDEXTENSION_METHOD_FLAGS_DEFAULT; 427 } 428 429 StringName snExternalName = StringName(externalName); 430 _godot_api.classdb_register_extension_class_signal( 431 lib, 432 cast(GDExtensionStringNamePtr) snClass, 433 cast(GDExtensionStringNamePtr) snExternalName, 434 prop.ptr, 435 Parameters!s.length 436 ); 437 } 438 } 439 440 // -------- PROPERTIES 441 442 enum bool matchName(string p, alias a) = (godotName!a == p); 443 static foreach (pName; godotPropertyNames!T) { 444 { 445 alias getterMatches = Filter!(ApplyLeft!(matchName, pName), godotPropertyGetters!T); 446 static assert(getterMatches.length <= 1); /// TODO: error message 447 alias setterMatches = Filter!(ApplyLeft!(matchName, pName), godotPropertySetters!T); 448 static assert(setterMatches.length <= 1); 449 450 static if (getterMatches.length) 451 alias P = NonRef!(ReturnType!(getterMatches[0])); 452 else 453 alias P = Parameters!(setterMatches[0])[0]; 454 //static assert(!is(P : Ref!U, U)); /// TODO: proper Ref handling 455 enum VariantType vt = extractPropertyVariantType!(getterMatches, setterMatches); 456 457 enum Property uda = extractPropertyUDA!(getterMatches, setterMatches); 458 459 __gshared static GDExtensionPropertyInfo pinfo; 460 461 462 StringName snPropName = StringName(pName); 463 static if (Variant.variantTypeOf!P == VariantType.object) { 464 StringName snParamClassName = StringName(godotName!(P)); 465 } 466 else { 467 StringName snParamClassName = StringName(""); 468 } 469 static if (uda.hintString.length) { 470 StringName snHintString = StringName(uda.hintString); 471 } 472 else { 473 StringName snHintString = StringName(""); 474 } 475 476 pinfo.class_name = cast(GDExtensionStringNamePtr) snParamClassName; 477 pinfo.type = cast(GDExtensionVariantType) vt; 478 pinfo.name = cast(GDExtensionStringNamePtr) snPropName; 479 pinfo.hint = GDEXTENSION_METHOD_ARGUMENT_METADATA_NONE; 480 //import godot.globalenums : PropertyUsageFlags; 481 //pinfo.usage = PropertyUsageFlags.propertyUsageDefault; // godot-cpp uses value 7 which is default|1 currently but there is no flag for 1 482 pinfo.usage = 7; 483 pinfo.hint_string = cast(GDExtensionStringNamePtr) snHintString; 484 485 // register acessor methods for that property 486 static if (getterMatches.length) { 487 enum get_prop = "get_" ~ pName ~ '\0'; 488 registerMethod!(getterMatches[0], cast(string) get_prop); 489 } else 490 enum get_prop = string.init; 491 492 static if (setterMatches.length) { 493 enum set_prop = "set_" ~ pName ~ '\0'; 494 registerMethod!(setterMatches[0], cast(string) set_prop); 495 } else 496 enum set_prop = string.init; 497 498 StringName snSetProp = StringName(set_prop); 499 StringName snGetProp = StringName(get_prop); 500 _godot_api.classdb_register_extension_class_property( 501 lib, 502 cast(GDExtensionStringNamePtr) snClass, 503 &pinfo, 504 cast(GDExtensionStringNamePtr) snSetProp, 505 cast(GDExtensionStringNamePtr) snGetProp 506 ); 507 } 508 } 509 static foreach (pName; godotPropertyVariableNames!T) { 510 { 511 import std.string; 512 import godot.globalenums : PropertyUsageFlags; 513 514 alias P = typeof(mixin("T." ~ pName)); 515 enum Variant.Type vt = Variant.variantTypeOf!P; 516 alias udas = getUDAs!(mixin("T." ~ pName), Property); 517 enum Property uda = is(udas[0]) ? Property.init : udas[0]; 518 519 __gshared static GDExtensionPropertyInfo pinfo; 520 521 StringName snPropName = StringName(pName); 522 static if (Variant.variantTypeOf!P == VariantType.object) { 523 StringName snPName = StringName(godotName!(P)); 524 } 525 else { 526 StringName snPName = StringName(""); 527 } 528 pinfo.class_name = cast(GDExtensionStringNamePtr) snPName; 529 pinfo.type = cast(GDExtensionVariantType) vt; 530 pinfo.name = cast(GDExtensionStringNamePtr) snPropName; 531 pinfo.usage = PropertyUsageFlags.propertyUsageDefault; // godot-cpp uses value 7 which is default|1 currently but there is no flag for 1, works for now 532 static if (uda.hintString.length) 533 StringName snHintString = StringName(uda.hintString); 534 else 535 StringName snHintString = stringName(); 536 pinfo.hint_string = cast(GDExtensionStringNamePtr) snHintString; 537 // register acessor methods for that property 538 enum get_prop = "get_" ~ pName ~ '\0'; 539 alias fnWrapper = VariableWrapper!(T, __traits(getMember, T, pName)); 540 static fnWrapper.getterType getterTmp; // dummy func for now because current registration code requires actual function, and there isn't one 541 registerMemberAccessor!(fnWrapper.callPropertyGet, getterTmp, cast(string) get_prop); 542 543 enum set_prop = "set_" ~ pName ~ '\0'; 544 static fnWrapper.setterType setterTmp; // dummy func for now because current registration code requires actual function, and there isn't one 545 registerMemberAccessor!(fnWrapper.callPropertySet, setterTmp, cast(string) set_prop); 546 547 StringName snSetProp = StringName(set_prop); 548 StringName snGetProp = StringName(get_prop); 549 _godot_api.classdb_register_extension_class_property( 550 lib, 551 cast(GDExtensionStringNamePtr) snClass, 552 &pinfo, 553 cast(GDExtensionStringNamePtr) snSetProp, 554 cast(GDExtensionStringNamePtr) snGetProp 555 ); 556 } 557 } 558 559 static foreach (E; godotEnums!T) { 560 { 561 static foreach (int i, ev; __traits(allMembers, E)) { 562 //pragma(msg, ev, ":", cast(int) __traits(getMember, E, ev)); 563 // FIXME: static foreach scope complains about duplicate names 564 mixin("StringName snEnum" ~ i.stringof ~ " = StringName(__traits(identifier, E));"); 565 mixin("StringName snVal" ~ i.stringof ~ "= StringName(ev);"); 566 _godot_api.classdb_register_extension_class_integer_constant( 567 lib, 568 cast(GDExtensionStringNamePtr) snClass, 569 cast(GDExtensionStringNamePtr) mixin("snEnum"~i.stringof), 570 cast(GDExtensionStringNamePtr) mixin("snVal"~i.stringof), 571 cast(int) __traits(getMember, E, ev), 572 false 573 ); 574 } 575 } 576 } 577 578 static foreach (pName; godotConstants!T) { 579 { 580 alias E = __traits(getMember, T, pName); 581 //pragma(msg, pName, ":", cast(int) E); 582 // FIXME: static foreach scope complains about duplicate names 583 mixin("StringName snProp" ~ pName ~ " = StringName(pName);"); 584 _godot_api.classdb_register_extension_class_integer_constant( 585 lib, 586 cast(GDExtensionStringNamePtr) snClass, 587 cast(GDExtensionStringNamePtr) stringName(), 588 cast(GDExtensionStringNamePtr) mixin("snProp"~pName), 589 cast(int) E, 590 false 591 ); 592 } 593 } 594 595 static foreach (pName; godotSingletonVariableNames!T) { 596 { 597 import std.string; 598 import godot.globalenums : PropertyUsageFlags; 599 600 alias P = typeof(mixin("T." ~ pName)); 601 alias storageField = mixin("T."~pName); 602 enum Variant.Type vt = Variant.variantTypeOf!P; 603 alias udas = getUDAs!(mixin("T." ~ pName), Singleton); 604 enum Singleton uda = is(udas[0]) ? Singleton.init : udas[0]; 605 606 StringName snPropName = StringName(pName); 607 608 storageField = memnew!P; 609 610 import godot.engine; 611 Engine.registerSingleton(snPropName, storageField); 612 } 613 } 614 615 } 616 617 /++ 618 Register a class and all its $(D @GodotMethod) member functions into Godot. 619 +/ 620 void unregister(T)(GDExtensionClassLibraryPtr lib) if (is(T == class)) { 621 import std.array; 622 import godot.abi; 623 import godot.object, godot.resource; 624 import godot.api; 625 626 static if (BaseClassesTuple!T.length == 2) // base class is GodotScript; use owner 627 { 628 alias Base = typeof(T.owner); 629 alias baseName = Base._GODOT_internal_name; 630 } 631 else // base class is another D script 632 { 633 alias Base = BaseClassesTuple!T[0]; 634 static if (hasUDA!(Base, Rename)) 635 enum string baseName = godotName!Base; 636 else 637 enum string baseName = __traits(identifier, Base); 638 } 639 640 static if (hasUDA!(T, Rename)) 641 enum string name = godotName!T; 642 else 643 enum string name = __traits(identifier, T); 644 645 void unregister() { 646 static foreach (pName; godotSingletonVariableNames!T) {{ 647 import std.string; 648 import godot.engine; 649 650 alias P = typeof(mixin("T." ~ pName)); 651 alias storageField = mixin("T."~pName); 652 alias udas = getUDAs!(mixin("T." ~ pName), Singleton); 653 enum Singleton uda = is(udas[0]) ? Singleton.init : udas[0]; 654 655 StringName snPropName = StringName(pName); 656 Engine.unregisterSingleton(snPropName); 657 memdelete(storageField); 658 }} 659 660 StringName snClass = StringName(name); 661 _godot_api.classdb_unregister_extension_class(lib, cast(GDExtensionStringNamePtr) snClass); 662 } 663 }