1 /++ 2 Initialization, termination, and registration of D libraries in Godot 3 +/ 4 module godot.d.register; 5 6 import std.format; 7 import std.meta, std.traits; 8 import std.experimental.allocator, std.experimental.allocator.mallocator; 9 import core.stdc.stdlib : malloc, free; 10 11 import godot.d.meta; 12 import godot.d.script; 13 import godot.d.wrap; 14 import godot.d.udas; 15 import godot.d.reference; 16 17 import godot.core, godot.c; 18 19 import godot.gdnativelibrary; 20 21 alias GodotInitOptions = const(godot_gdnative_init_options*); 22 alias GodotTerminateOptions = const(godot_gdnative_terminate_options*); 23 24 /++ 25 Pass this enum to GodotNativeInit and GodotNativeTerminate to skip D runtime 26 initialization/termination. 27 +/ 28 enum NoDRuntime; 29 30 /++ 31 This mixin will generate the GDNative C interface functions for this D library. 32 Pass to it a name string for the library, followed by the GodotScript types to 33 register, functions to call, and other options to configure Godot-D. 34 35 The symbolPrefix must match the GDNativeLibrary's symbolPrefix in Godot. 36 37 D runtime will be initialized and terminated, unless you pass $(D NoDRuntime). 38 39 Functions taking GodotInitOptions or no arguments will be called at init. 40 Functions taking GodotTerminateOptions will be called at termination. 41 42 Example: 43 --- 44 import godot, godot.node; 45 class TestClass : GodotScript!Node 46 { } 47 mixin GodotNativeLibrary!( 48 "testlib", 49 TestClass, 50 (GodotInitOptions o){ print("Initialized"); }, 51 (GodotTerminateOptions o){ print("Terminated"); } 52 ); 53 --- 54 +/ 55 mixin template GodotNativeLibrary(string symbolPrefix, Args...) 56 { 57 private static import godot.c; 58 private static import godot.gdnativelibrary; 59 private import godot.d.reference; 60 61 private __gshared Ref!(godot.gdnativelibrary.GDNativeLibrary) _GODOT_library; 62 private __gshared void* _GODOT_library_handle; 63 64 /// HACK: empty main to force the compiler to add emulated TLS. 65 version(Android) void main() { } 66 67 pragma(mangle, symbolPrefix~"gdnative_init") 68 export extern(C) static void godot_gdnative_init(godot.c.godot_gdnative_init_options* options) 69 { 70 import godot.c.api; 71 import godot.d.reference; 72 import std.meta, std.traits; 73 import core.runtime : Runtime; 74 static if(staticIndexOf!(NoDRuntime, Args) == -1) Runtime.initialize(); 75 76 godot_gdnative_api_struct_init(options.api_struct); 77 78 import core.exception : assertHandler; 79 assertHandler = (options.in_editor) ? (&godotAssertHandlerEditorDebug) 80 : (&godotAssertHandlerCrash); 81 82 *cast(typeof(options.gd_native_library)*)&_GODOT_library = options.gd_native_library; 83 _GODOT_library.reference(); 84 85 foreach(Arg; Args) 86 { 87 static if(is(Arg)) { } // is type 88 else static if( isCallable!Arg ) 89 { 90 static if( is(typeof(Arg())) ) Arg(); 91 else static if( is(typeof(Arg(options))) ) Arg(options); 92 } 93 else static if(Arg == NoDRuntime) { } 94 else 95 { 96 static assert(0, "Unrecognized argument <"~Arg.stringof~"> passed to GodotNativeLibrary"); 97 } 98 } 99 } 100 pragma(mangle, symbolPrefix~"nativescript_init") 101 export extern(C) static void godot_nativescript_init(void* handle) 102 { 103 import std.meta, std.traits; 104 import godot.d.register : register; 105 106 _GODOT_library_handle = handle; 107 108 foreach(Arg; Args) 109 { 110 static if(is(Arg)) // is type 111 { 112 static assert(is(Arg == class) && extendsGodotBaseClass!Arg, 113 fullyQualifiedName!Arg ~ " is not a D class that extends a Godot class!"); 114 register!Arg(_GODOT_library_handle, _GODOT_library); 115 } 116 else static if( isCallable!Arg ) 117 { 118 119 } 120 else static if(Arg == NoDRuntime) { } 121 else 122 { 123 static assert(0, "Unrecognized argument <"~Arg.stringof~"> passed to GodotNativeLibrary"); 124 } 125 } 126 } 127 pragma(mangle, symbolPrefix~"gdnative_terminate") 128 export extern(C) static void godot_gdnative_terminate(godot.c.godot_gdnative_terminate_options* options) 129 { 130 import std.meta, std.traits; 131 import godot.d.script : NativeScriptTemplate; 132 foreach(Arg; Args) 133 { 134 static if(is(Arg)) // is type 135 { 136 NativeScriptTemplate!Arg.unref(); 137 } 138 else static if(isCallable!Arg) 139 { 140 static if(is(typeof(Arg(options)))) Arg(options); 141 } 142 else static if(Arg == NoDRuntime) { } 143 else 144 { 145 static assert(0, "Unrecognized argument <"~Arg.stringof~"> passed to GodotNativeLibrary"); 146 } 147 } 148 149 _GODOT_library.unref(); 150 151 import core.runtime : Runtime; 152 static if(staticIndexOf!(NoDRuntime, Args) == -1) Runtime.terminate(); 153 } 154 } 155 156 private extern(C) 157 godot_variant _GODOT_nop(godot_object o, void* methodData, 158 void* userData, int numArgs, godot_variant** args) 159 { 160 godot_variant n; 161 _godot_api.godot_variant_new_nil(&n); 162 return n; 163 } 164 165 /++ 166 Register a class and all its $(D @GodotMethod) member functions into Godot. 167 +/ 168 void register(T)(void* handle, GDNativeLibrary lib) if(is(T == class)) 169 { 170 import godot.c; 171 import godot.object, godot.resource; 172 import godot.d; 173 static import godot.nativescript; 174 import std.string : toStringz, fromStringz; /// TODO: remove GCed functions 175 176 static if(BaseClassesTuple!T.length == 2) // base class is GodotScript; use owner 177 { 178 alias Base = typeof(T.owner); 179 alias baseName = Base._GODOT_internal_name; 180 } 181 else // base class is another D script 182 { 183 alias Base = BaseClassesTuple!T[0]; 184 static if(hasUDA!(Base, Rename)) enum immutable(char*) baseName = TemplateArgsOf!( 185 getUDAs!(Base, Rename)[0])[0]; 186 else enum immutable(char*) baseName = fullyQualifiedName!Base; 187 } 188 189 static if(hasUDA!(T, Rename)) enum immutable(char*) name = TemplateArgsOf!( 190 getUDAs!(T, Rename)[0])[0]; 191 else enum immutable(char*) name = fullyQualifiedName!T; 192 193 auto icf = godot_instance_create_func(&createFunc!T, null, null); 194 auto idf = godot_instance_destroy_func(&destroyFunc!T, null, null); 195 196 static if(hasUDA!(T, Tool)) _godot_nativescript_api.godot_nativescript_register_tool_class(handle, name, baseName, icf, idf); 197 else _godot_nativescript_api.godot_nativescript_register_class(handle, name, baseName, icf, idf); 198 199 if(GDNativeVersion.hasNativescript!(1, 1)) 200 { 201 _godot_nativescript_api.godot_nativescript_set_type_tag(handle, name, NativeScriptTag!T.tag); 202 } 203 else // register a no-op function that indicates this is a D class 204 { 205 godot_instance_method md; 206 md.method = &_GODOT_nop; 207 md.free_func = null; 208 _godot_nativescript_api.godot_nativescript_register_method(handle, name, "_GDNATIVE_D_typeid", godot_method_attributes.init, md); 209 } 210 211 foreach(mf; godotMethods!T) 212 { 213 godot_method_attributes ma; 214 static if(is( getUDAs!(mf, Method)[0] )) ma.rpc_type = godot_method_rpc_mode 215 .GODOT_METHOD_RPC_MODE_DISABLED; 216 else 217 { 218 ma.rpc_type = cast(godot_method_rpc_mode)(getUDAs!(mf, Method)[0].rpcMode); 219 } 220 221 godot_instance_method md; 222 static if(godotName!mf == "_ready" && onReadyFieldNames!T.length) 223 { 224 md.method = &OnReadyWrapper!T.callOnReady; 225 } 226 else md.method = &MethodWrapper!(T, mf).callMethod; 227 md.free_func = null; 228 229 _godot_nativescript_api.godot_nativescript_register_method(handle, name, godotName!mf.toStringz, ma, md); /// TODO: remove GCed functions 230 } 231 232 // OnReady when there is no _ready method 233 static if(staticIndexOf!("_ready", staticMap!(godotName, godotMethods!T)) == -1 234 && onReadyFieldNames!T.length) 235 { 236 enum ma = godot_method_attributes.init; 237 godot_instance_method md; 238 md.method = &OnReadyWrapper!T.callOnReady; 239 _godot_nativescript_api.godot_nativescript_register_method(handle, name, "_ready", ma, md); 240 } 241 242 foreach(sName; godotSignals!T) 243 { 244 alias s = Alias!(mixin("T."~sName)); 245 static assert(hasStaticMember!(T, sName), "Signal declaration "~fullyQualifiedName!s 246 ~" must be static. Otherwise it would take up memory in every instance of "~T.stringof); 247 248 godot_signal gs; 249 (*cast(String*)&gs.name) = String(godotName!s); 250 gs.num_args = Parameters!s.length; 251 252 static if(Parameters!s.length) 253 { 254 godot_signal_argument[Parameters!s.length] args; 255 gs.args = args.ptr; 256 } 257 258 foreach(pi, P; Parameters!s) 259 { 260 static assert(Variant.compatible!P, fullyQualifiedName!s~" parameter "~pi.text~" \"" 261 ~ParameterIdentifierTuple!s[pi]~"\": type "~P.stringof~" is incompatible with Godot"); 262 (*cast(String*)&args[pi].name) = (ParameterIdentifierTuple!s[pi].length) 263 ? String(ParameterIdentifierTuple!s[pi]) 264 : (String(P.stringof) ~ String("Arg") ~ Variant(pi).as!String); 265 args[pi].type = Variant.variantTypeOf!P; 266 args[pi].usage = cast(godot_property_usage_flags)Property.Usage.defaultUsage; 267 } 268 269 _godot_nativescript_api.godot_nativescript_register_signal(handle, name, &gs); 270 } 271 272 enum bool matchName(string p, alias a) = (godotName!a == p); 273 foreach(pName; godotPropertyNames!T) 274 { 275 alias getterMatches = Filter!(ApplyLeft!(matchName, pName), godotPropertyGetters!T); 276 static assert(getterMatches.length <= 1); /// TODO: error message 277 alias setterMatches = Filter!(ApplyLeft!(matchName, pName), godotPropertySetters!T); 278 static assert(setterMatches.length <= 1); 279 280 godot_property_set_func sf; 281 godot_property_get_func gf; 282 godot_property_attributes attr; 283 284 static if(getterMatches.length) alias P = NonRef!(ReturnType!(getterMatches[0])); 285 else alias P = Parameters!(setterMatches[0])[0]; 286 static assert(!is(P : Ref!U, U)); /// TODO: proper Ref handling 287 enum Variant.Type vt = extractPropertyVariantType!(getterMatches, setterMatches); 288 attr.type = cast(godot_int)vt; 289 290 enum Property uda = extractPropertyUDA!(getterMatches, setterMatches); 291 attr.rset_type = cast(godot_method_rpc_mode)uda.rpcMode; 292 attr.hint = cast(godot_property_hint)uda.hint; 293 294 static if(vt == Variant.Type.object && extends!(P, Resource)) 295 { 296 attr.hint |= godot_property_hint.GODOT_PROPERTY_HINT_RESOURCE_TYPE; 297 } 298 299 static if(uda.hintString.length) _godot_api.godot_string_parse_utf8( 300 &attr.hint_string, uda.hintString.ptr); 301 else 302 { 303 static if(vt == Variant.Type.object) 304 { 305 _godot_api.godot_string_parse_utf8(&attr.hint_string, 306 GodotClass!P._GODOT_internal_name); 307 } 308 else _godot_api.godot_string_new(&attr.hint_string); 309 } 310 attr.usage = cast(godot_property_usage_flags)(uda.usage | 311 Property.Usage.scriptVariable); 312 313 Variant defval; 314 enum gDef = getterMatches.length && hasUDA!(getterMatches[0], DefaultValue); 315 enum sDef = getterMatches.length && hasUDA!(setterMatches[0], DefaultValue); 316 static if(gDef || sDef) 317 { 318 static if(gDef) alias defExprSeq = TemplateArgsOf!(getUDAs!(getterMatches[0], DefaultValue)[0]); 319 else alias defExprSeq = TemplateArgsOf!(getUDAs!(setterMatches[0], DefaultValue)[0]); 320 defval = defExprSeq[0]; 321 } 322 else static if( is(typeof( { P p; } )) ) // use type's default value 323 { 324 static if(isFloatingPoint!P) 325 { 326 // Godot doesn't support NaNs. Initialize properties to 0.0 instead. 327 defval = 0.0; 328 } 329 else defval = P.init; 330 } 331 else 332 { 333 /// FIXME: call default constructor function 334 defval = null; 335 } 336 attr.default_value = defval._godot_variant; 337 338 static if(getterMatches.length) 339 { 340 alias GetWrapper = MethodWrapper!(T, getterMatches[0]); 341 gf.get_func = &GetWrapper.callPropertyGet; 342 gf.free_func = null; 343 } 344 else 345 { 346 gf.get_func = &emptyGetter; 347 } 348 349 static if(setterMatches.length) 350 { 351 alias SetWrapper = MethodWrapper!(T, setterMatches[0]); 352 sf.set_func = &SetWrapper.callPropertySet; 353 sf.free_func = null; 354 } 355 else 356 { 357 sf.set_func = &emptySetter; 358 } 359 360 _godot_nativescript_api.godot_nativescript_register_property(handle, name, pName.toStringz, &attr, sf, gf); /// TODO: remove GCed functions 361 } 362 foreach(pName; godotPropertyVariableNames!T) 363 { 364 immutable(char*) propName = godotName!(mixin("T."~pName)).toStringz; /// TODO: remove GCed functions 365 366 import std.string; 367 368 godot_property_set_func sf; 369 godot_property_get_func gf; 370 godot_property_attributes attr; 371 372 alias P = typeof(mixin("T."~pName)); 373 enum Variant.Type vt = Variant.variantTypeOf!P; 374 attr.type = cast(godot_int)vt; 375 376 alias udas = getUDAs!(mixin("T."~pName), Property); 377 enum Property uda = is(udas[0]) ? Property.init : udas[0]; 378 attr.rset_type = cast(godot_method_rpc_mode)uda.rpcMode; 379 attr.hint = cast(godot_property_hint)uda.hint; 380 381 static if(vt == Variant.Type.object && is(GodotClass!P : Resource)) 382 { 383 attr.hint |= godot_property_hint.GODOT_PROPERTY_HINT_RESOURCE_TYPE; 384 } 385 386 static if(uda.hintString.length) _godot_api.godot_string_parse_utf8( 387 &attr.hint_string, uda.hintString.ptr); 388 else 389 { 390 static if(vt == Variant.Type.object) 391 { 392 _godot_api.godot_string_parse_utf8(&attr.hint_string, 393 GodotClass!P._GODOT_internal_name); 394 } 395 else _godot_api.godot_string_new(&attr.hint_string); 396 } 397 attr.usage = cast(godot_property_usage_flags)uda.usage | 398 cast(godot_property_usage_flags)Property.Usage.scriptVariable; 399 400 static if(hasUDA!(mixin("T."~pName), DefaultValue)) 401 { 402 alias defExprSeq = TemplateArgsOf!(getUDAs!(mixin("T."~pName), DefaultValue)[0]); 403 Variant defval = defExprSeq[0]; 404 } 405 else static if( is(typeof( { P p; } )) ) 406 { 407 import std.math : isNaN; 408 static if(isFloatingPoint!P && (mixin("T."~pName).init).isNaN) 409 { 410 // Godot doesn't support NaNs. Initialize properties to 0.0 instead. 411 Variant defval = P(0.0); 412 } 413 else Variant defval = (mixin("T."~pName)).init; 414 } 415 else 416 { 417 /// FIXME: call default constructor function 418 Variant defval = null; 419 } 420 attr.default_value = defval._godot_variant; 421 422 alias Wrapper = VariableWrapper!(T, pName); 423 424 { 425 gf.method_data = null; 426 gf.get_func = &Wrapper.callPropertyGet; 427 gf.free_func = null; 428 } 429 430 { 431 sf.method_data = null; 432 sf.set_func = &Wrapper.callPropertySet; 433 sf.free_func = null; 434 } 435 436 _godot_nativescript_api.godot_nativescript_register_property(handle, name, propName, &attr, sf, gf); 437 } 438 // TODO: signals 439 440 441 442 godot.d.script.NativeScriptTemplate!T = memnew!(godot.nativescript.NativeScript); 443 godot.d.script.NativeScriptTemplate!T.setLibrary(lib); 444 godot.d.script.NativeScriptTemplate!T.setClassName(String(name)); 445 } 446