1 /++ 2 Templates for binding Godot C++ classes to use from D 3 4 The binding generator will implement these templates for the classes in Godot's 5 API JSON. 6 +/ 7 module godot.api.bind; 8 9 import std.meta, std.traits; 10 import std.conv : text; 11 12 import godot, godot.abi; 13 public import godot.refcounted; 14 import godot.api.traits; 15 16 /// Type to mark varargs GodotMethod. 17 struct GodotVarArgs { 18 19 } 20 21 package(godot) struct MethodHash { 22 uint hash; 23 } 24 25 package(godot) struct GodotName { 26 string name; 27 } 28 29 /++ 30 Definition of a method from API JSON. 31 +/ 32 struct GodotMethod(Return, Args...) { 33 GDExtensionMethodBindPtr mb; /// MethodBind for ptrcalls 34 String name; /// String name from Godot (snake_case, not always valid D) 35 36 static if (Args.length) 37 enum bool hasVarArgs = is(Args[$ - 1] : GodotVarArgs); 38 else 39 enum bool hasVarArgs = false; 40 41 /+package(godot)+/ 42 void bind(in string className, in string methodName, in GDExtensionInt hash = 0) { 43 if (mb) 44 return; 45 mb = _godot_api.classdb_get_method_bind(cast(GDExtensionStringNamePtr) StringName(className), cast(GDExtensionStringNamePtr) StringName(methodName), hash); 46 name = String(methodName); 47 } 48 49 /+package(godot)+/ 50 void bind(in GDExtensionVariantType type, in string methodName, in GDExtensionInt hash = 0) { 51 if (mb) 52 return; 53 mb = _godot_api.variant_get_ptr_builtin_method(type, cast(GDExtensionStringNamePtr) StringName(methodName), hash); 54 name = String(methodName); 55 } 56 } 57 58 struct GodotConstructor(Return, Args...) { 59 GDExtensionPtrConstructor mb; /// MethodBind for ptrcalls 60 61 /+package(godot)+/ 62 void bind(in GDExtensionVariantType type, in int index) { 63 if (mb) 64 return; 65 mb = _godot_api.variant_get_ptr_constructor(type, index); 66 } 67 } 68 69 /++ 70 Raw Method call helper 71 +/ 72 Return callBuiltinMethod(Return, Args...)(in GDExtensionPtrBuiltInMethod method, GDExtensionTypePtr obj, Args args) { 73 static if (!is(Return == void)) 74 Return ret = void; 75 else 76 typeof(null) ret = null; 77 78 import core.stdc.string; 79 memset(&ret, 0, Return.sizeof); 80 81 GDExtensionTypePtr[Args.length + 1] _args; 82 foreach (i, a; args) { 83 // TODO: remove this static_if hack thing once the object casts mess will be resolved 84 static if (is(typeof(a) == GodotObject)) 85 _args[i] = cast(void*) a; 86 else static if (is(typeof(a) == void*)) 87 _args[i] = a; 88 else 89 _args[i] = &a; 90 } 91 92 method(obj, _args.ptr, &ret, _args.length); 93 static if (!is(Return == void)) 94 return ret; 95 } 96 97 //@nogc nothrow 98 pragma(inline, true) 99 package(godot) void checkClassBinding(C)() { 100 if (!C._classBindingInitialized) { 101 initializeClassBinding!C(); 102 } 103 } 104 105 // these have same order as in GDExtensionVariantType 106 private immutable enum coreTypes = [ 107 "Nil", "Bool", "Int", "Float", "String", 108 109 "Vector2", "Vector2i", "Rect2", "Rect2i", "Vector3", 110 "Vector3i", "Transform2D", "Vector4", "Vector4i", 111 "Plane", "Quaternion", "AABB", "Basis", "Transform3D", "Projection", 112 113 "Color", "StringName", "NodePath", "RID", "Object", 114 "Callable", "Signal", "Dictionary", "Array", 115 116 "PackedByteArray", "PackedInt32Array", "PackedInt64Array", 117 "PackedFloat32Array", 118 "PackedFloat64Array", "PackedStringArray", "PackedVector2Array", 119 "PackedVector3Array", 120 "PackedColorArray", 121 ]; 122 123 //@nogc nothrow 124 pragma(inline, false) 125 package(godot) void initializeClassBinding(C)() { 126 import std.algorithm; 127 import std.string : indexOf; 128 129 synchronized { 130 if (!C._classBindingInitialized) { 131 static foreach (n; __traits(allMembers, C.GDExtensionClassBinding)) { 132 static if (n == "_singleton") 133 C.GDExtensionClassBinding._singleton = godot_object( 134 _godot_api.global_get_singleton( 135 cast(GDExtensionStringNamePtr) StringName(C.GDExtensionClassBinding._singletonName))); 136 else static if (n == "_singletonName") { 137 } else { 138 // core types require special registration for built-in types 139 static if (coreTypes.canFind(C._GODOT_internal_name)) { 140 static if (isInstanceOf!(GodotConstructor, __traits(getMember, C.GDExtensionClassBinding, n))) { 141 // binds constructor using GDExtensionVariantType and index 142 __traits(getMember, C.GDExtensionClassBinding, n).bind( 143 cast(int) coreTypes.countUntil(C._GODOT_internal_name), 144 to!int(getUDAs!(mixin("C.GDExtensionClassBinding." ~ n), GodotName)[0].name[$ - 2 .. $ - 1]), // get last number from name in form of "_new_2" 145 146 ); 147 } else { 148 // binds native built-in method 149 __traits(getMember, C.GDExtensionClassBinding, n).bind( 150 cast(int) coreTypes.countUntil(C._GODOT_internal_name), 151 getUDAs!(mixin("C.GDExtensionClassBinding." ~ n), GodotName)[0].name, 152 getUDAs!(mixin("C.GDExtensionClassBinding." ~ n), MethodHash)[0].hash, 153 ); 154 } 155 } else static if (C.stringof.endsWith("Singleton")) { 156 // do nothing, let singleton load all methods on demand through getter check 157 } else { 158 //enum immutable(char*) cn = C._GODOT_internal_name; 159 __traits(getMember, C.GDExtensionClassBinding, n).bind( 160 C._GODOT_internal_name, 161 getUDAs!(__traits(getMember, C.GDExtensionClassBinding, n), GodotName)[0].name, 162 0 //getUDAs!(__traits(getMember, C.GDExtensionClassBinding, n), MethodHash)[0].hash, 163 164 ); 165 } 166 } 167 } 168 C._classBindingInitialized = true; 169 } 170 } 171 } 172 173 enum bool needsConversion(Src, Dest) = !isGodotClass!Dest && !is(Src : Dest); 174 175 /// temporary var if conversion is needed 176 template tempType(Src, Dest) { 177 static if (needsConversion!(Src, Dest)) 178 alias tempType = Dest; 179 else 180 alias tempType = void[0]; 181 } 182 183 /++ 184 Direct pointer call through MethodBind. 185 +/ 186 RefOrT!Return ptrcall(Return, MB, Args...)(MB method, in godot_object self, Args args) 187 in { 188 debug if (self.ptr is null) { 189 auto utf8 = (String("Method ") ~ method.name ~ String(" called on null reference")).utf8; 190 auto msg = cast(char[]) utf8.data[0 .. utf8.length]; 191 assert(0, msg); 192 } 193 } 194 do { 195 import std.typecons; 196 import std.range : iota; 197 198 alias MBArgs = TemplateArgsOf!(MB)[1 .. $]; 199 static assert(Args.length == MBArgs.length); 200 201 static if (Args.length != 0) { 202 alias _iota = aliasSeqOf!(iota(Args.length)); 203 alias _tempType(size_t i) = tempType!(Args[i], MBArgs[i]); 204 const(void)*[Args.length] aarr = void; 205 206 Tuple!(staticMap!(_tempType, _iota)) temp = void; 207 } 208 foreach (ai, A; Args) { 209 static if (isGodotClass!A) { 210 static assert(is(Unqual!A : MBArgs[ai]) || staticIndexOf!( 211 MBArgs[ai], GodotClass!A.GodotClass) != -1, "method" ~ 212 " argument " ~ ai.text ~ " of type " ~ A.stringof ~ 213 " does not inherit parameter type " ~ MBArgs[ai].stringof); 214 aarr[ai] = getGDExtensionObject(args[ai]).ptr; 215 } else static if (!needsConversion!(Args[ai], MBArgs[ai])) { 216 aarr[ai] = cast(const(void)*)(&args[ai]); 217 } else // needs conversion 218 { 219 static assert(is(typeof(MBArgs[ai](args[ai]))), "method" ~ 220 " argument " ~ ai.text ~ " of type " ~ A.stringof ~ 221 " cannot be converted to parameter type " ~ MBArgs[ai].stringof); 222 223 import std.conv : emplace; 224 225 emplace(&temp[ai], args[ai]); 226 aarr[ai] = cast(const(void)*)(&temp[ai]); 227 } 228 } 229 static if (!is(Return : void)) 230 RefOrT!Return r = godotDefaultInit!(RefOrT!Return); 231 232 static if (is(Return : void)) 233 alias rptr = Alias!null; 234 else 235 void* rptr = cast(void*)&r; 236 237 static if (Args.length == 0) 238 alias aptr = Alias!null; 239 else 240 const(void)** aptr = aarr.ptr; 241 242 _godot_api.object_method_bind_ptrcall(method.mb, cast(GDExtensionObjectPtr) self.ptr, aptr, rptr); 243 static if (!is(Return : void)) 244 return r; 245 } 246 247 /++ 248 Variant call, for virtual and vararg methods. 249 250 Forwards to `callv`, but does compile-time type check of args other than varargs. 251 +/ 252 Return callv(MB, Return, Args...)(MB method, godot_object self, Args args) 253 in { 254 import std.experimental.allocator, std.experimental.allocator.mallocator; 255 256 debug if (self.ptr is null) { 257 CharString utf8 = (String("Method ") ~ method.name ~ String(" called on null reference")) 258 .utf8; 259 auto msg = utf8.data; 260 assert(0, msg); // leak msg; Error is unrecoverable 261 } 262 } 263 do { 264 alias MBArgs = TemplateArgsOf!(MB)[1 .. $]; 265 266 import godot.object; 267 268 GodotObject o = void; 269 o._godot_object = self; 270 271 Array a = Array.make(); 272 static if (Args.length != 0) 273 a.resize(cast(int) Args.length); 274 foreach (ai, A; Args) { 275 static if (is(MBArgs[$ - 1] : GodotVarArgs) && ai >= MBArgs.length - 1) { 276 // do nothing 277 } else { 278 static assert(ai < MBArgs.length, "Too many arguments"); 279 static assert(is(A : MBArgs[ai]) || isImplicitlyConvertible!(A, MBArgs[ai]), 280 "method" ~ " argument " ~ ai.text ~ " of type " ~ A.stringof ~ 281 " cannot be converted to parameter type " ~ MBArgs[ai].stringof); 282 } 283 a[ai] = args[ai]; 284 } 285 286 Variant r = o.callv(method.name, a); 287 return r.as!Return; 288 } 289 290 package(godot) 291 mixin template baseCasts() { 292 private import godot.api.reference, godot.api.traits : RefOrT, NonRef; 293 294 inout(To) as(To)() inout if (isGodotBaseClass!To) { 295 static if (extends!(typeof(this), To)) 296 return cast(inout) To(cast() _godot_object); 297 else static if (extends!(To, typeof(this))) { 298 if (_godot_object.ptr is null) 299 return typeof(return).init; 300 //String c = String(To._GODOT_internal_name); 301 // HACK: string 302 if (isClass(To._GODOT_internal_name)) 303 return inout(To)(_godot_object); 304 return typeof(return).init; 305 } else 306 static assert(0, To.stringof ~ " is not polymorphic to " 307 ~ typeof(this).stringof); 308 } 309 310 inout(ToRef) as(ToRef)() inout 311 if (is(ToRef : Ref!To, To) && extends!(To, RefCounted)) { 312 import std.traits : TemplateArgsOf, Unqual; 313 314 ToRef ret = cast() as!(Unqual!(TemplateArgsOf!ToRef[0])); 315 return cast(inout) ret; 316 } 317 318 inout(To) as(To)() inout if (extendsGodotBaseClass!To) { 319 godot_object go = cast() _godot_object; 320 return cast(inout(To)) _godot_api.object_get_instance_binding(go.ptr, _GODOT_library, &_instanceCallbacks); 321 } 322 323 template opCast(To) if (isGodotBaseClass!To) { 324 alias opCast = as!To; 325 } 326 327 template opCast(To) if (extendsGodotBaseClass!To) { 328 alias opCast = as!To; 329 } 330 331 template opCast(ToRef) if (is(ToRef : Ref!To, To) && extends!(To, RefCounted)) { 332 alias opCast = as!ToRef; 333 } 334 // void* cast for passing this type to ptrcalls 335 package(godot) void* opCast(T : void*)() const { 336 return cast(void*) _godot_object.ptr; 337 } 338 // strip const, because the C API sometimes expects a non-const godot_object 339 godot_object opCast(T : godot_object)() const { 340 return cast(godot_object) _godot_object; 341 } 342 // implicit conversion to bool like D class references 343 bool opCast(T : bool)() const { 344 return _godot_object.ptr !is null; 345 } 346 }