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 }