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 }