1 module godot.tools.generator.classes;
2 
3 import godot.util.string;
4 import godot.tools.generator.methods;
5 import godot.tools.generator.enums;
6 import godot.tools.generator.util;
7 
8 import asdf;
9 
10 import std.range;
11 import std.algorithm.searching;
12 import std.algorithm.iteration;
13 import std.algorithm.sorting;
14 import std.path;
15 import std.conv : text;
16 import std.string;
17 
18 struct ClassList {
19     GodotClass[] classes;
20     GodotClass[Type] dictionary;
21 }
22 
23 struct ClassConstant {
24     string name;
25     int value;
26 }
27 
28 struct BuiltinConstant {
29     string name;
30     string value;
31     @serdeOptional Type type;
32 
33     // have to manually parse it as in different types value can be both string or int
34     SerdeException deserializeFromAsdf(Asdf data) {
35         // here we try read 3 options, 'name' variant is for native_structs
36         name = data["name"].get!string(null);
37 
38         // optional
39         if (auto ty = data["type"].get!string(null)) {
40             type = Type.get(ty);
41         }
42 
43         // try read string, otherwise simply assume it's an int
44         value = data["value"].get!string(null);
45         if (value is null) {
46             value = text(data["value"].get!int(0));
47         }
48 
49         return null;
50     }
51 }
52 
53 struct Operator {
54     string name;
55     @serdeOptional Type right_type;
56     //@serdeOptional
57     Type return_type;
58 }
59 
60 class Constructor : GodotMethod {
61     int index;
62     //@serdeOptional CtorArguments[] arguments;
63 
64     override void finalizeDeserialization(Asdf data) {
65         super.finalizeDeserialization(data);
66         name = "new_" ~ text(index);
67     }
68 
69     override Constructor isConstructor() const {
70         return cast() this;
71     }
72 
73     override string functionKindName() const {
74         return "ctor";
75     }
76 
77     override string loader() const {
78         return format(`GDExtensionClassBinding.%s.mb = _godot_api.variant_get_ptr_constructor(%s, %d);`,
79             wrapperIdentifier,
80             parent.name.asNativeVariantType(),
81             index
82         );
83     }
84 }
85 
86 //struct CtorArguments
87 //{
88 //	string name;
89 //	Type type;
90 //}
91 
92 // they are unrelated actually but have same fields
93 //alias BuiltinMembers = CtorArguments;
94 alias BuiltinMembers = GodotArgument;
95 
96 final class GodotClass {
97     Type name;
98     @serdeOptional @serdeKeys("inherits", "base_class") Type base_class;
99     @serdeOptional string api_type;
100     @serdeOptional bool singleton;
101     @serdeOptional string singleton_name;
102     @serdeOptional @serdeKeys("is_instantiable", "instanciable") bool instanciable;
103     @serdeOptional @serdeKeys("is_refcounted", "is_reference") bool is_reference;
104     @serdeOptional BuiltinConstant[] constants; // TODO: can constants be things other than ints?
105     @serdeOptional GodotMethod[] methods;
106     @serdeOptional GodotProperty[] properties;
107     @serdeOptional GodotEnum[] enums;
108 
109     // built-in types only
110     @serdeIgnore bool isBuiltinClass;
111     @serdeOptional bool has_destructor;
112     @serdeOptional bool is_keyed;
113     @serdeOptional Type indexing_return_type;
114     @serdeOptional Operator[] operators;
115     @serdeOptional Constructor[] constructors;
116     @serdeOptional BuiltinMembers[] members;
117     // end built-in types only
118 
119     void addUsedClass(in Type c) {
120         auto u = c.stripConstPointer();
121         if (u.isPrimitive || u.isCoreType || u.godotType == "Object")
122             return;
123         if (u.isTypedArray)
124             u = u.arrayType;
125         if (!used_classes.canFind(u))
126             used_classes ~= u;
127     }
128 
129     void finalizeDeserialization(Asdf data) {
130         assert(name.objectClass is null);
131         name.objectClass = this;
132         name.original = this;
133 
134         // FIXME why they are different? name != Type.get(name.godotType)
135         Type.get(name.godotType).original = this;
136         Type.get(name.godotType).objectClass = this;
137 
138         if (base_class && base_class.godotType != "Object" && name.godotType != "Object")
139             used_classes ~= base_class;
140 
141         foreach (m; constructors) {
142             m.parent = this;
143             m.return_type = name;
144         }
145         foreach (m; methods) {
146             m.parent = this;
147         }
148         foreach (ref e; enums) {
149             e.parent = this;
150             foreach (n; e.values)
151                 constantsInEnums ~= n.name;
152         }
153     }
154 
155 @serdeIgnore:
156     //ClassList* parent;
157 
158     const(Type)[] used_classes;
159     //GodotClass base_class_ptr = null; // needs to be set after all classes loaded
160     GodotClass[] descendant_ptrs; /// direct descendent classes
161 
162     Type[] missingEnums; /// enums that were left unregistered in Godot
163 
164     string ddocBrief;
165     string ddoc;
166     string[string] ddocConstants;
167 
168     string[] constantsInEnums; // names of constants that are enum members
169 
170     string bindingStruct() const {
171         string ret = "\tpackage(godot) __gshared bool _classBindingInitialized = false;\n";
172         ret ~= "\tpackage(godot) static struct GDExtensionClassBinding {\n";
173         ret ~= "\t\t__gshared:\n";
174         if (singleton) {
175             ret ~= "\t\tgodot_object _singleton;\n";
176             ret ~= "\t\timmutable string _singletonName = \"" ~ name.godotType.chompPrefix(
177                 "_") ~ "\";\n";
178         }
179         foreach (const ct; constructors) {
180             ret ~= ct.binding;
181         }
182         if (has_destructor) {
183             ret ~= destuctorBinding();
184         }
185         foreach (const m; methods) {
186             ret ~= m.binding;
187         }
188         ret ~= "\t}\n";
189         return ret;
190     }
191 
192     string destuctorBinding() const {
193         string ret;
194         ret ~= "\t\tGDExtensionPtrDestructor destructor;\n";
195         return ret;
196     }
197 
198     string source() {
199         string ret;
200 
201         // generate the set of referenced classes
202         foreach (m; joiner([cast(GodotMethod[]) constructors, methods])) {
203             import std.algorithm.searching;
204 
205             foreach (const ty; chain([m.return_type], m.arguments.map!(t=>t.type))) {
206                 if (ty.isMetaType) {
207                     auto c = cast() ty.stripMeta;
208                     // often a meta type is a nested type like enum or bitfield
209                     if (c && c.getParentType()) {
210                         c = c.getParentType();
211                     }
212 
213                     // superbelko: i really don't like this one...
214                     // deals with global enums, just skip it as it is already imported by default
215                     if ((ty.isEnum || ty.isBitfield) && c && !ty.godotType.canFind('.'))
216                         continue;
217 
218                     if (c && c !is name)
219                         if (ty !is c) // ugh...
220                             addUsedClass(c);
221                 } else if (ty !is name) {
222                     addUsedClass(ty);
223                 }
224             }
225         }
226         foreach (p; properties) {
227             // some crazy property named like "streams" that returns "stream_" type (that does not exists)
228             if (!(p.getter && p.setter))
229                 continue;
230 
231             Type pType;
232             GodotMethod getterMethod;
233             foreach (GodotClass c; BaseRange(cast() this)) {
234                 if (!getterMethod) {
235                     auto g = c.methods.find!(m => m.name == p.getter);
236                     if (!g.empty)
237                         getterMethod = g.front;
238                 }
239 
240                 if (getterMethod)
241                     break;
242                 if (c.base_class is null)
243                     break;
244             }
245             if (getterMethod)
246                 pType = getterMethod.return_type;
247             else
248                 pType = p.type;
249 
250             if (pType.godotType.canFind(','))
251                 continue; /// FIXME: handle with common base. Also see godot#35467
252             if (pType.isEnum) {
253                 auto c = pType.enumParent;
254                 if (c && c !is name)
255                     addUsedClass(c);
256             } else if (pType !is name) {
257                 addUsedClass(pType);
258             }
259         }
260         assert(!used_classes.canFind(name));
261         assert(!used_classes.canFind!(c => c.godotType == "Object"));
262 
263         if (!isBuiltinClass) {
264             ret ~= "module godot." ~ name.asModuleName ~ ";\n\n";
265 
266             ret ~= `public import std.meta : AliasSeq, staticIndexOf;
267 public import std.traits : Unqual;
268 public import godot.api.traits;
269 public import godot;
270 public import godot.abi;
271 public import godot.api.bind;
272 public import godot.api.reference;
273 public import godot.globalenums;
274 public import godot.object;
275 public import godot.classdb;`;
276             ret ~= "\n";
277         }
278 
279         foreach (const u; used_classes) {
280             if (!u.asModuleName)
281                 continue;
282             ret ~= "public import godot.";
283             ret ~= u.asModuleName;
284             ret ~= ";\n";
285         }
286 
287         string className = name.dType;
288         if (singleton)
289             className ~= "Singleton";
290         if (isBuiltinClass)
291             className ~= "_Bind";
292         ret ~= "/**\n" ~ ddoc ~ "\n*/\n";
293         ret ~= "@GodotBaseClass struct " ~ className ~ " {\n";
294         ret ~= "\tpackage(godot) enum string _GODOT_internal_name = \"" ~ name.godotType ~ "\";\n";
295         ret ~= "\tpublic:\n";
296         // way to much PITA, ignore for now
297         //ret ~= "@nogc nothrow:\n";
298 
299         // Pointer to Godot object, fake inheritance through alias this
300         if (name.godotType != "Object" && name.godotType != "CoreConstants" && !isBuiltinClass) {
301             ret ~= "\tunion { /** */ godot_object _godot_object; /** */ " ~ base_class.dType;
302             if (base_class && base_class.original && base_class.original.singleton)
303                 ret ~= "Singleton";
304             ret ~= " _GODOT_base; }\n\talias _GODOT_base this;\n";
305             ret ~= "\talias BaseClasses = AliasSeq!(typeof(_GODOT_base), typeof(_GODOT_base).BaseClasses);\n";
306         } else {
307             ret ~= "\t" ~ name.asOpaqueType ~ "  _godot_object;\n";
308             ret ~= "\talias BaseClasses = AliasSeq!();\n";
309         }
310 
311         ret ~= bindingStruct;
312 
313         if (!isBuiltinClass) {
314             // equality
315             ret ~= "\t/// \n";
316             ret ~= "\tpragma(inline, true) bool opEquals(in " ~ className ~ " other) const {\n";
317             ret ~= "\t\treturn _godot_object.ptr is other._godot_object.ptr; \n\t}\n";
318             // null assignment to simulate D class references
319             ret ~= "\t/// \n";
320             ret ~= "\tpragma(inline, true) typeof(null) opAssign(typeof(null) n) {\n";
321             ret ~= "\t\t_godot_object.ptr = n; return null; \n\t}\n";
322             // equality with null; unfortunately `_godot_object is null` doesn't work with structs
323             ret ~= "\t/// \n";
324             ret ~= "\tpragma(inline, true) bool opEquals(typeof(null) n) const {\n";
325             ret ~= "\t\treturn _godot_object.ptr is n; \n\t}\n";
326             // comparison operator
327             if (name.godotType == "Object") {
328                 ret ~= "\t/// \n";
329                 ret ~= "\tpragma(inline, true) int opCmp(in GodotObject other) const {\n";
330                 ret ~= "\t\tconst void* a = _godot_object.ptr, b = other._godot_object.ptr; return a is b ? 0 : a < b ? -1 : 1; \n\t}\n";
331                 ret ~= "\t/// \n";
332                 ret ~= "\tpragma(inline, true) int opCmp(T)(in T other) const if(extendsGodotBaseClass!T) {\n";
333                 ret ~= "\t\tconst void* a = _godot_object.ptr, b = other.owner._godot_object.ptr; return a is b ? 0 : a < b ? -1 : 1; \n\t}\n";
334             }
335             // hash function
336             ret ~= "\t/// \n";
337             ret ~= "\textern(D) size_t toHash() const nothrow @trusted { return cast(size_t)_godot_object.ptr; }\n";
338 
339             ret ~= "\tmixin baseCasts;\n";
340 
341             // Godot constructor.
342             ret ~= "\t/// Construct a new instance of " ~ className ~ ".\n";
343             ret ~= "\t/// Note: use `memnew!" ~ className ~ "` instead.\n";
344             ret ~= "\tstatic " ~ className ~ " _new() {\n";
345             ret ~= "\t\tStringName godotname = StringName(\"" ~ name.godotType ~ "\");\n";
346             ret ~= "\t\tif(auto obj = _godot_api.classdb_construct_object(cast(GDExtensionStringNamePtr) godotname))\n";
347             ret ~= "\t\t\treturn " ~ className ~ "(godot_object(obj));\n";
348             ret ~= "\t\treturn typeof(this).init;\n";
349             ret ~= "\t}\n";
350         }
351 
352         foreach (ct; constructors) {
353             //ret ~= "\tstatic "~name.dType~" "~ ct.name ~ ct.templateArgsString ~ ct.argsString ~ " {\n";
354             //ret ~= "\t\tif(auto fn = _godot_api.variant_get_ptr_constructor(GDEXTENSION_VARIANT_TYPE_"~name.godotType.snakeToCamel.toUpper ~ ", " ~ text(ct.index) ~"))\n";
355             //ret ~= "\t\t\treturn "~name.dType~"(godot_object(fn(...)));\n";
356             //ret ~= "\t\treturn typeof(this).init;\n";
357             //ret ~= "\t}\n";
358             ret ~= ct.source;
359         }
360 
361         // currently only core types can have destructor
362         if (has_destructor) {
363             ret ~= "\tvoid _destructor() {\n";
364             ret ~= "\t\tif (!GDExtensionClassBinding.destructor)\n";
365             ret ~= "\t\t\tGDExtensionClassBinding.destructor = _godot_api.variant_get_ptr_destructor(GDEXTENSION_VARIANT_TYPE_" ~ name
366                 .godotType.camelToSnake.toUpper ~ ");\n";
367             ret ~= "\t\tGDExtensionClassBinding.destructor(&_godot_object);\n";
368             ret ~= "\t}\n";
369         }
370 
371         //ret ~= "\t@disable new(size_t s);\n";
372 
373         foreach (const ref e; enums) {
374             ret ~= e.source;
375         }
376 
377         foreach (const ref e; missingEnums) {
378             import std.stdio;
379 
380             writeln(
381                 "Warning: The enum " ~ e.dType ~ " is missing from Godot's script API; using a non-typesafe int instead.");
382             ret ~= "\t/// Warning: The enum " ~ e.dType ~ " is missing from Godot's script API; using a non-typesafe int instead.\n";
383             ret ~= "\tdeprecated(\"The enum " ~ e.dType ~ " is missing from Godot's script API; using a non-typesafe int instead.\")\n";
384             string shortName = e.dType[e.dType.countUntil(".") + 1 .. $];
385             ret ~= "\talias " ~ shortName ~ " = int;\n";
386         }
387 
388         if (!isBuiltinClass && constants.length) {
389             ret ~= "\t/// \n";
390             ret ~= "\tenum Constants : int {\n";
391             foreach (constant; constants.sort!((a, b) => (a.value < b.value))) {
392                 if (!constantsInEnums.canFind(constant.name)) // don't document enums here; they have their own ddoc
393                 {
394                     if (auto ptr = constant.name in ddocConstants)
395                         ret ~= "\t\t/**\n\t\t" ~ (*ptr).replace("\n", "\n\t\t") ~ "\n\t\t*/\n";
396                     else
397                         ret ~= "\t\t/** */\n";
398                 }
399                 ret ~= "\t\t" ~ constant.name.snakeToCamel.escapeDType ~ " = " ~ text(
400                     constant.value) ~ ",\n";
401             }
402             ret ~= "\t}\n";
403         }
404 
405         foreach (const m; methods) {
406             ret ~= m.source;
407         }
408 
409         foreach (const p; properties) {
410             import std.stdio : writeln;
411 
412             if (p.type.godotType.canFind(','))
413                 continue; /// FIXME: handle with common base
414 
415             GodotMethod getterMethod, setterMethod;
416 
417             foreach (GodotClass c; BaseRange(cast() this)) {
418                 if (!getterMethod) {
419                     auto g = c.methods.find!(m => m.name == p.getter);
420                     if (!g.empty)
421                         getterMethod = g.front;
422                 }
423                 if (!setterMethod) {
424                     auto s = c.methods.find!(m => m.name == p.setter);
425                     if (!s.empty)
426                         setterMethod = s.front;
427                 }
428 
429                 if (getterMethod && setterMethod)
430                     break;
431 
432                 // TODO: add hide Warning: property flag
433                 if (c.base_class is null) {
434                     // if (!getterMethod)
435                     //     writeln("Warning: property ", name.godotType, ".", p.name, " specifies a getter that doesn't exist: ", p
436                     //             .getter);
437                     // if (p.setter.length && !setterMethod)
438                     //     writeln("Warning: property ", name.godotType, ".", p.name, " specifies a setter that doesn't exist: ", p
439                     //             .setter);
440                     break;
441                 }
442             }
443 
444             if (getterMethod)
445                 ret ~= p.getterSource(getterMethod);
446             if (p.setter.length) {
447                 if (setterMethod)
448                     ret ~= p.setterSource(setterMethod);
449             }
450         }
451 
452         ret ~= "}\n";
453 
454         if (singleton) {
455             ret ~= "/// Returns: the " ~ className ~ "\n";
456             //ret ~= "@property @nogc nothrow pragma(inline, true)\n";
457             ret ~= "@property pragma(inline, true)\n";
458             ret ~= className ~ " " ~ name.dType ~ "() {\n";
459             ret ~= "\tcheckClassBinding!" ~ className ~ "();\n";
460             ret ~= "\treturn " ~ className ~ "(" ~ className ~ ".GDExtensionClassBinding._singleton);\n";
461             ret ~= "}\n";
462         }
463 
464         return ret;
465     }
466 
467     struct BaseRange {
468         GodotClass front;
469         BaseRange save() const {
470             return cast() this;
471         }
472 
473         bool empty() const {
474             return front is null;
475         }
476 
477         void popFront() {
478             front = front.base_class.original;
479         }
480     }
481 }