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