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