1 /++ 2 Initialization, termination, and registration of D libraries in Godot 3 +/ 4 module godot.d.register; 5 6 import godotutil.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.d.traits; 14 import godot.d.script; 15 import godot.d.wrap; 16 import godot.d.udas; 17 import godot.d.reference; 18 19 import godot.core, godot.c; 20 21 import godot.gdnativelibrary; 22 23 enum bool is_(alias a) = is(a); 24 template fileClassesAsLazyImports(FileInfo f) 25 { 26 template classFrom(string className) 27 { 28 mixin("alias classFrom = from!\""~f.moduleName~"\""~className[f.moduleName.length..$]~";"); 29 } 30 alias fileClassesAsLazyImports = staticMap!(classFrom, aliasSeqOf!(f.classes)); 31 } 32 33 alias GodotInitOptions = const(godot_gdnative_init_options*); 34 alias GodotTerminateOptions = const(godot_gdnative_terminate_options*); 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 { 42 no = false, 43 yes = true 44 } 45 46 /++ 47 This mixin will generate the GDNative C interface functions for this D library. 48 Pass to it a name string for the library, followed by the GodotScript types to 49 register, functions to call, and other options to configure Godot-D. 50 51 The symbolPrefix must match the GDNativeLibrary's symbolPrefix in Godot. 52 53 D runtime will be initialized and terminated, unless you pass $(D LoadDRuntime.no) 54 or compile with BetterC. 55 56 Functions taking GodotInitOptions or no arguments will be called at init. 57 Functions taking GodotTerminateOptions will be called at termination. 58 59 Example: 60 --- 61 import godot, godot.node; 62 class TestClass : GodotScript!Node 63 { } 64 mixin GodotNativeLibrary!( 65 "testlib", 66 TestClass, 67 (GodotInitOptions o){ print("Initialized"); }, 68 (GodotTerminateOptions o){ print("Terminated"); } 69 ); 70 --- 71 +/ 72 mixin template GodotNativeLibrary(string symbolPrefix, Args...) 73 { 74 private static import godot.c; 75 private static import godot.gdnativelibrary; 76 private static import godotutil.classes; 77 private import godot.d.reference; 78 79 private __gshared Ref!(godot.gdnativelibrary.GDNativeLibrary) _GODOT_library; 80 private __gshared void* _GODOT_library_handle; 81 82 static if(__traits(compiles, import("classes.csv"))) 83 { 84 enum godotutil.classes.ProjectInfo _GODOT_projectInfo = godotutil.classes.ProjectInfo.fromCsv(import("classes.csv")); 85 } 86 else 87 { 88 enum godotutil.classes.ProjectInfo _GODOT_projectInfo = godotutil.classes.ProjectInfo.init; 89 } 90 91 /// HACK: empty main to force the compiler to add emulated TLS. 92 version(Android) void main() { } 93 94 // Windows DLL entry points handle TLS+DRuntime initialization and thread attachment 95 version(Windows) 96 { 97 version(D_BetterC) {} 98 else 99 { 100 import core.sys.windows.dll : SimpleDllMain; 101 mixin SimpleDllMain; 102 } 103 } 104 105 pragma(mangle, symbolPrefix~"gdnative_init") 106 export extern(C) static void godot_gdnative_init(godot.c.godot_gdnative_init_options* options) 107 { 108 import godot.c.api; 109 import godot.d.reference; 110 import std.meta, std.traits; 111 import core.runtime : Runtime; 112 import godot.d.output; 113 import godot.d.traits; 114 version(Windows) {} 115 else 116 { 117 version(D_BetterC) enum bool loadDRuntime = staticIndexOf!(LoadDRuntime.yes, Args) != -1; 118 else enum bool loadDRuntime = staticIndexOf!(LoadDRuntime.no, Args) == -1; 119 static if(loadDRuntime) Runtime.initialize(); 120 } 121 122 godot_gdnative_api_struct_init(options.api_struct); 123 124 import core.exception : assertHandler; 125 assertHandler = (options.in_editor) ? (&godotAssertHandlerEditorDebug) 126 : (&godotAssertHandlerCrash); 127 128 *cast(typeof(options.gd_native_library)*)&_GODOT_library = options.gd_native_library; 129 _GODOT_library.reference(); 130 131 foreach(Arg; Args) 132 { 133 static if(is(Arg)) { } // is type 134 else static if( isCallable!Arg ) 135 { 136 static if( is(typeof(Arg())) ) Arg(); 137 else static if( is(typeof(Arg(options))) ) Arg(options); 138 } 139 else static if(is(typeof(Arg) == LoadDRuntime)) { } 140 else 141 { 142 static assert(0, "Unrecognized argument <"~Arg.stringof~"> passed to GodotNativeLibrary"); 143 } 144 } 145 } 146 pragma(mangle, symbolPrefix~"nativescript_init") 147 export extern(C) static void godot_nativescript_init(void* handle) 148 { 149 import std.meta, std.traits; 150 import godot.d.register : register; 151 import std.array : join; 152 import godot.d.output; 153 import godot.d.traits; 154 155 _GODOT_library_handle = handle; 156 157 alias classList = staticMap!(fileClassesAsLazyImports, aliasSeqOf!(_GODOT_projectInfo.files)); 158 static foreach(C; NoDuplicates!(classList, Filter!(is_, Args))) 159 { 160 static if(is(C)) 161 { 162 static if(extendsGodotBaseClass!C) 163 { 164 register!C(_GODOT_library_handle, _GODOT_library); 165 } 166 } 167 } 168 169 foreach(Arg; Args) 170 { 171 static if(is(Arg)) // is type 172 { 173 static assert(is(Arg == class) && extendsGodotBaseClass!Arg, 174 fullyQualifiedName!Arg ~ " is not a D class that extends a Godot class!"); 175 } 176 else static if( isCallable!Arg ) 177 { 178 179 } 180 else static if(is(typeof(Arg) == LoadDRuntime)) { } 181 else 182 { 183 static assert(0, "Unrecognized argument <"~Arg.stringof~"> passed to GodotNativeLibrary"); 184 } 185 } 186 } 187 pragma(mangle, symbolPrefix~"gdnative_terminate") 188 export extern(C) static void godot_gdnative_terminate(godot.c.godot_gdnative_terminate_options* options) 189 { 190 import std.meta, std.traits; 191 import godot.d.script : NativeScriptTemplate; 192 import std.array : join; 193 import godot.d.output; 194 import godot.d.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 private extern(C) 238 godot_variant _GODOT_nop(godot_object o, void* methodData, 239 void* userData, int numArgs, godot_variant** args) 240 { 241 godot_variant n; 242 _godot_api.godot_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)(void* handle, GDNativeLibrary lib) if(is(T == class)) 250 { 251 import godot.c; 252 import godot.object, godot.resource; 253 import godot.d; 254 static import godot.nativescript; 255 256 static if(BaseClassesTuple!T.length == 2) // base class is GodotScript; use owner 257 { 258 alias Base = typeof(T.owner); 259 alias baseName = Base._GODOT_internal_name; 260 } 261 else // base class is another D script 262 { 263 alias Base = BaseClassesTuple!T[0]; 264 static if(hasUDA!(Base, Rename)) enum immutable(char*) baseName = godotName!Base; 265 else enum immutable(char*) baseName = fullyQualifiedName!Base; 266 } 267 268 static if(hasUDA!(T, Rename)) enum immutable(char*) name = godotName!T; 269 else enum immutable(char*) name = fullyQualifiedName!T; 270 271 auto icf = godot_instance_create_func(&createFunc!T, null, null); 272 auto idf = godot_instance_destroy_func(&destroyFunc!T, null, null); 273 274 static if(hasUDA!(T, Tool)) _godot_nativescript_api.godot_nativescript_register_tool_class(handle, name, baseName, icf, idf); 275 else _godot_nativescript_api.godot_nativescript_register_class(handle, name, baseName, icf, idf); 276 277 if(GDNativeVersion.hasNativescript!(1, 1)) 278 { 279 _godot_nativescript_api.godot_nativescript_set_type_tag(handle, name, NativeScriptTag!T.tag); 280 } 281 else // register a no-op function that indicates this is a D class 282 { 283 godot_instance_method md; 284 md.method = &_GODOT_nop; 285 md.free_func = null; 286 _godot_nativescript_api.godot_nativescript_register_method(handle, name, "_GDNATIVE_D_typeid", godot_method_attributes.init, md); 287 } 288 289 static foreach(mf; godotMethods!T) 290 {{ 291 godot_method_attributes ma; 292 static if(is( getUDAs!(mf, Method)[0] )) ma.rpc_type = godot_method_rpc_mode 293 .GODOT_METHOD_RPC_MODE_DISABLED; 294 else 295 { 296 ma.rpc_type = cast(godot_method_rpc_mode)(getUDAs!(mf, Method)[0].rpcMode); 297 } 298 299 godot_instance_method md; 300 static if(godotName!mf == "_ready" && onReadyFieldNames!T.length) 301 { 302 md.method = &OnReadyWrapper!T.callOnReady; 303 } 304 else md.method = &MethodWrapper!(T, mf).callMethod; 305 md.free_func = null; 306 307 char[godotName!mf.length+1] mfn = void; 308 mfn[0..godotName!mf.length] = godotName!mf[]; 309 mfn[$-1] = '\0'; 310 _godot_nativescript_api.godot_nativescript_register_method(handle, name, mfn.ptr, ma, md); 311 }} 312 313 // OnReady when there is no _ready method 314 static if(staticIndexOf!("_ready", staticMap!(godotName, godotMethods!T)) == -1 315 && onReadyFieldNames!T.length) 316 { 317 enum ma = godot_method_attributes.init; 318 godot_instance_method md; 319 md.method = &OnReadyWrapper!T.callOnReady; 320 _godot_nativescript_api.godot_nativescript_register_method(handle, name, "_ready", ma, md); 321 } 322 323 static foreach(sName; godotSignals!T) 324 {{ 325 alias s = Alias!(mixin("T."~sName)); 326 static assert(hasStaticMember!(T, sName), "Signal declaration "~fullyQualifiedName!s 327 ~" must be static. Otherwise it would take up memory in every instance of "~T.stringof); 328 329 godot_signal gs; 330 (*cast(String*)&gs.name) = String(godotName!s); 331 gs.num_args = Parameters!s.length; 332 333 static if(Parameters!s.length) 334 { 335 godot_signal_argument[Parameters!s.length] args; 336 gs.args = args.ptr; 337 } 338 339 foreach(pi, P; Parameters!s) 340 { 341 static assert(Variant.compatible!P, fullyQualifiedName!s~" parameter "~pi.text~" \"" 342 ~ParameterIdentifierTuple!s[pi]~"\": type "~P.stringof~" is incompatible with Godot"); 343 static if(ParameterIdentifierTuple!s[pi].length > 0) 344 { 345 (*cast(String*)&args[pi].name) = String(ParameterIdentifierTuple!s[pi]); 346 } 347 else 348 { 349 (*cast(String*)&args[pi].name) = (String(P.stringof) ~ String("Arg") ~ Variant(pi).as!String); 350 } 351 args[pi].type = Variant.variantTypeOf!P; 352 args[pi].usage = cast(godot_property_usage_flags)Property.Usage.defaultUsage; 353 } 354 355 _godot_nativescript_api.godot_nativescript_register_signal(handle, name, &gs); 356 }} 357 358 enum bool matchName(string p, alias a) = (godotName!a == p); 359 static foreach(pName; godotPropertyNames!T) 360 {{ 361 alias getterMatches = Filter!(ApplyLeft!(matchName, pName), godotPropertyGetters!T); 362 static assert(getterMatches.length <= 1); /// TODO: error message 363 alias setterMatches = Filter!(ApplyLeft!(matchName, pName), godotPropertySetters!T); 364 static assert(setterMatches.length <= 1); 365 366 godot_property_set_func sf; 367 godot_property_get_func gf; 368 godot_property_attributes attr; 369 370 static if(getterMatches.length) alias P = NonRef!(ReturnType!(getterMatches[0])); 371 else alias P = Parameters!(setterMatches[0])[0]; 372 static assert(!is(P : Ref!U, U)); /// TODO: proper Ref handling 373 enum Variant.Type vt = extractPropertyVariantType!(getterMatches, setterMatches); 374 attr.type = cast(godot_int)vt; 375 376 enum Property uda = extractPropertyUDA!(getterMatches, setterMatches); 377 attr.rset_type = cast(godot_method_rpc_mode)uda.rpcMode; 378 attr.hint = cast(godot_property_hint)uda.hint; 379 380 static if(vt == Variant.Type.object && extends!(P, Resource)) 381 { 382 attr.hint |= godot_property_hint.GODOT_PROPERTY_HINT_RESOURCE_TYPE; 383 } 384 385 static if(uda.hintString.length) _godot_api.godot_string_parse_utf8( 386 &attr.hint_string, uda.hintString.ptr); 387 else 388 { 389 static if(vt == Variant.Type.object) 390 { 391 _godot_api.godot_string_parse_utf8(&attr.hint_string, 392 GodotClass!P._GODOT_internal_name); 393 } 394 else _godot_api.godot_string_new(&attr.hint_string); 395 } 396 attr.usage = cast(godot_property_usage_flags)(uda.usage | 397 Property.Usage.scriptVariable); 398 399 Variant defval; 400 static if(getterMatches.length) enum gDef = hasUDA!(getterMatches[0], DefaultValue); 401 else enum gDef = false; 402 static if(setterMatches.length) enum sDef = hasUDA!(setterMatches[0], DefaultValue); 403 else enum sDef = false; 404 405 static if(gDef || sDef) 406 { 407 static if(gDef) alias defExprSeq = TemplateArgsOf!(getUDAs!(getterMatches[0], DefaultValue)[0]); 408 else alias defExprSeq = TemplateArgsOf!(getUDAs!(setterMatches[0], DefaultValue)[0]); 409 defval = defExprSeq[0]; 410 } 411 else static if( is(typeof( { P p; } )) ) // use type's default value 412 { 413 static if(isFloatingPoint!P) 414 { 415 // Godot doesn't support NaNs. Initialize properties to 0.0 instead. 416 defval = 0.0; 417 } 418 else defval = P.init; 419 } 420 else 421 { 422 /// FIXME: call default constructor function 423 defval = null; 424 } 425 attr.default_value = defval._godot_variant; 426 427 static if(getterMatches.length) 428 { 429 alias GetWrapper = MethodWrapper!(T, getterMatches[0]); 430 gf.get_func = &GetWrapper.callPropertyGet; 431 gf.free_func = null; 432 } 433 else 434 { 435 gf.get_func = &emptyGetter; 436 } 437 438 static if(setterMatches.length) 439 { 440 alias SetWrapper = MethodWrapper!(T, setterMatches[0]); 441 sf.set_func = &SetWrapper.callPropertySet; 442 sf.free_func = null; 443 } 444 else 445 { 446 sf.set_func = &emptySetter; 447 } 448 449 char[pName.length+1] pn = void; 450 pn[0..pName.length] = pName[]; 451 pn[$-1] = '\0'; 452 _godot_nativescript_api.godot_nativescript_register_property(handle, name, pn.ptr, &attr, sf, gf); 453 }} 454 static foreach(pName; godotPropertyVariableNames!T) 455 {{ 456 import std..string; 457 458 godot_property_set_func sf; 459 godot_property_get_func gf; 460 godot_property_attributes attr; 461 462 alias P = typeof(mixin("T."~pName)); 463 enum Variant.Type vt = Variant.variantTypeOf!P; 464 attr.type = cast(godot_int)vt; 465 466 alias udas = getUDAs!(mixin("T."~pName), Property); 467 enum Property uda = is(udas[0]) ? Property.init : udas[0]; 468 attr.rset_type = cast(godot_method_rpc_mode)uda.rpcMode; 469 attr.hint = cast(godot_property_hint)uda.hint; 470 471 static if(vt == Variant.Type.object && is(GodotClass!P : Resource)) 472 { 473 attr.hint |= godot_property_hint.GODOT_PROPERTY_HINT_RESOURCE_TYPE; 474 } 475 476 static if(uda.hintString.length) _godot_api.godot_string_parse_utf8( 477 &attr.hint_string, uda.hintString.ptr); 478 else 479 { 480 static if(vt == Variant.Type.object) 481 { 482 _godot_api.godot_string_parse_utf8(&attr.hint_string, 483 GodotClass!P._GODOT_internal_name); 484 } 485 else _godot_api.godot_string_new(&attr.hint_string); 486 } 487 attr.usage = cast(godot_property_usage_flags)uda.usage | 488 cast(godot_property_usage_flags)Property.Usage.scriptVariable; 489 490 Variant defval = getDefaultValueFromAlias!(T, pName)(); 491 attr.default_value = defval._godot_variant; 492 493 alias Wrapper = VariableWrapper!(T, pName); 494 495 { 496 gf.method_data = null; 497 gf.get_func = &Wrapper.callPropertyGet; 498 gf.free_func = null; 499 } 500 501 { 502 sf.method_data = null; 503 sf.set_func = &Wrapper.callPropertySet; 504 sf.free_func = null; 505 } 506 507 enum pnLength = godotName!(mixin("T."~pName)).length; 508 char[pnLength+1] pn = void; 509 pn[0..pnLength] = godotName!(mixin("T."~pName))[]; 510 pn[$-1] = '\0'; 511 _godot_nativescript_api.godot_nativescript_register_property(handle, name, pn.ptr, &attr, sf, gf); 512 }} 513 514 515 516 godot.d.script.NativeScriptTemplate!T = memnew!(godot.nativescript.NativeScript); 517 godot.d.script.NativeScriptTemplate!T.setLibrary(lib); 518 godot.d.script.NativeScriptTemplate!T.setClassName(String(name)); 519 } 520