1 module api.classes;
2 
3 import godotutil..string;
4 import api.methods, api.enums, api.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 {
16 	GodotClass[] classes;
17 	GodotClass[Type] dictionary;
18 }
19 
20 class GodotClass
21 {
22 	Type name;
23 	Type base_class;
24 	string api_type;
25 	bool singleton;
26 	bool instanciable;
27 	bool is_reference;
28 	int[string] constants; // TODO: can constants be things other than ints?
29 	GodotMethod[] methods;
30 	GodotProperty[] properties;
31 	GodotEnum[] enums;
32 	
33 	void addUsedClass(in Type c)
34 	{
35 		if(c.isPrimitive || c.isCoreType || c.godot == "Object") return;
36 		if(!used_classes.canFind(c)) used_classes ~= c;
37 	}
38 
39 	void finalizeDeserialization(Asdf data)
40 	{
41 		assert(name.objectClass is null);
42 		name.objectClass = this;
43 		
44 		if(base_class && base_class.godot != "Object" && name.godot != "Object") used_classes ~= base_class;
45 		
46 		foreach(m; methods)
47 		{
48 			m.parent = this;
49 		}
50 		foreach(ref e; enums)
51 		{
52 			e.parent = this;
53 			foreach(n; e.values.keys) constantsInEnums ~= n;
54 		}
55 	}
56 	
57 	@serializationIgnore:
58 	ClassList* parent;
59 	
60 	const(Type)[] used_classes;
61 	GodotClass base_class_ptr = null; // needs to be set after all classes loaded
62 	GodotClass[] descendant_ptrs; /// direct descendent classes
63 	
64 	Type[] missingEnums; /// enums that were left unregistered in Godot
65 	
66 	string ddocBrief;
67 	string ddoc;
68 	string[string] ddocConstants;
69 	
70 	string[] constantsInEnums; // names of constants that are enum members
71 	
72 	string bindingStruct() const
73 	{
74 		string ret = "\tpackage(godot) __gshared bool _classBindingInitialized = false;\n";
75 		ret ~= "\tpackage(godot) static struct GDNativeClassBinding\n\t{\n";
76 		ret ~= "\t\t__gshared:\n";
77 		if(singleton)
78 		{
79 			ret ~= "\t\tgodot_object _singleton;\n";
80 			ret ~= "\t\timmutable char* _singletonName = \""~name.godot.chompPrefix("_")~"\";\n";
81 		}
82 		foreach(const m; methods)
83 		{
84 			ret ~= m.binding;
85 		}
86 		ret ~= "\t}\n";
87 		return ret;
88 	}
89 	
90 	string source()
91 	{
92 		string ret;
93 
94 		// generate the set of referenced classes
95 		foreach(m; methods)
96 		{
97 			import std.algorithm.searching;
98 			if(m.return_type.isEnum)
99 			{
100 				auto c = m.return_type.enumParent;
101 				if(c && c !is name) addUsedClass(c);
102 			}
103 			else if(m.return_type !is name)
104 			{
105 				addUsedClass(m.return_type);
106 			}
107 			foreach(const a; m.arguments)
108 			{
109 				if(a.type.isEnum)
110 				{
111 					auto c = a.type.enumParent;
112 					if(c && c !is name) addUsedClass(c);
113 				}
114 				else if(a.type !is name)
115 				{
116 					addUsedClass(a.type);
117 				}
118 			}
119 		}
120 		foreach(p; properties)
121 		{
122 			Type pType;
123 			GodotMethod getterMethod;
124 			foreach(GodotClass c; BaseRange(cast()this))
125 			{
126 				if(!getterMethod)
127 				{
128 					auto g = c.methods.find!(m => m.name == p.getter);
129 					if(!g.empty) getterMethod = g.front;
130 				}
131 				
132 				if(getterMethod) break;
133 				if(c.base_class_ptr is null) break;
134 			}
135 			if(getterMethod) pType = getterMethod.return_type;
136 			else pType = p.type;
137 
138 			if(pType.godot.canFind(',')) continue; /// FIXME: handle with common base. Also see godot#35467
139 			if(pType.isEnum)
140 			{
141 				auto c = pType.enumParent;
142 				if(c && c !is name) addUsedClass(c);
143 			}
144 			else if(pType !is name)
145 			{
146 				addUsedClass(pType);
147 			}
148 		}
149 		assert(!used_classes.canFind(name));
150 		assert(!used_classes.canFind!(c => c.godot == "Object"));
151 
152 		foreach(const u; used_classes)
153 		{
154 			ret ~= "import godot.";
155 			ret ~= u.moduleName;
156 			ret ~= ";\n";
157 		}
158 
159 		string className = name.d;
160 		if(singleton) className ~= "Singleton";
161 		ret ~= "/**\n"~ddoc~"\n*/\n";
162 		ret ~= "@GodotBaseClass struct "~className;
163 		ret ~= "\n{\n";
164 		ret ~= "\tpackage(godot) enum string _GODOT_internal_name = \""~name.godot~"\";\n";
165 		ret ~= "public:\n";
166 		ret ~= "@nogc nothrow:\n";
167 		
168 		// Pointer to Godot object, fake inheritance through alias this
169 		if(name.godot != "Object")
170 		{
171 			ret ~= "\tunion { /** */ godot_object _godot_object; /** */ "~base_class.d;
172 			if(base_class_ptr.singleton) ret ~= "Singleton";
173 			ret ~= " _GODOT_base; }\n\talias _GODOT_base this;\n";
174 			ret ~= "\talias BaseClasses = AliasSeq!(typeof(_GODOT_base), typeof(_GODOT_base).BaseClasses);\n";
175 		}
176 		else
177 		{
178 			ret ~= "\tgodot_object _godot_object;\n";
179 			ret ~= "\talias BaseClasses = AliasSeq!();\n";
180 		}
181 		
182 		ret ~= bindingStruct;
183 		
184 		// equality
185 		ret ~= "\t/// \n";
186 		ret ~= "\tpragma(inline, true) bool opEquals(in "~className~" other) const\n";
187 		ret ~= "\t{ return _godot_object.ptr is other._godot_object.ptr; }\n";
188 		// null assignment to simulate D class references
189 		ret ~= "\t/// \n";
190 		ret ~= "\tpragma(inline, true) typeof(null) opAssign(typeof(null) n)\n";
191 		ret ~= "\t{ _godot_object.ptr = n; return null; }\n";
192 		// equality with null; unfortunately `_godot_object is null` doesn't work with structs
193 		ret ~= "\t/// \n";
194 		ret ~= "\tpragma(inline, true) bool opEquals(typeof(null) n) const\n";
195 		ret ~= "\t{ return _godot_object.ptr is n; }\n";
196 		// comparison operator
197 		if(name.godot == "Object")
198 		{
199 			ret ~= "\t/// \n";
200 			ret ~= "\tpragma(inline, true) int opCmp(in GodotObject other) const\n";
201 			ret ~= "\t{ const void* a = _godot_object.ptr, b = other._godot_object.ptr; return a is b ? 0 : a < b ? -1 : 1; }\n";
202 			ret ~= "\t/// \n";
203 			ret ~= "\tpragma(inline, true) int opCmp(T)(in T other) const if(extendsGodotBaseClass!T)\n";
204 			ret ~= "\t{ const void* a = _godot_object.ptr, b = other.owner._godot_object.ptr; return a is b ? 0 : a < b ? -1 : 1; }\n";
205 		}
206 		// hash function
207 		ret ~= "\t/// \n";
208 		ret ~= "\tsize_t toHash() const @trusted { return cast(size_t)_godot_object.ptr; }\n";
209 		
210 		ret ~= "\tmixin baseCasts;\n";
211 		
212 		// Godot constructor.
213 		ret ~= "\t/// Construct a new instance of "~className~".\n";
214 		ret ~= "\t/// Note: use `memnew!"~className~"` instead.\n";
215 		ret ~= "\tstatic "~className~" _new()\n\t{\n";
216 		ret ~= "\t\tstatic godot_class_constructor constructor;\n";
217 		ret ~= "\t\tif(constructor is null) constructor = _godot_api.godot_get_class_constructor(\""~name.godot~"\");\n";
218 		ret ~= "\t\tif(constructor is null) return typeof(this).init;\n";
219 		ret ~= "\t\treturn cast("~className~")(constructor());\n";
220 		ret ~= "\t}\n";
221 
222 		ret ~= "\t@disable new(size_t s);\n";
223 		
224 		foreach(const ref e; enums)
225 		{
226 			ret ~= e.source;
227 		}
228 		
229 		foreach(const ref e; missingEnums)
230 		{
231 			import std.stdio;
232 			writeln("Warning: The enum "~e.d~" is missing from Godot's script API; using a non-typesafe int instead.");
233 			ret ~= "\t/// Warning: The enum "~e.d~" is missing from Godot's script API; using a non-typesafe int instead.\n";
234 			ret ~= "\tdeprecated(\"The enum "~e.d~" is missing from Godot's script API; using a non-typesafe int instead.\")\n";
235 			string shortName = e.d[e.d.countUntil(".")+1..$];
236 			ret ~= "\talias " ~ shortName ~ " = int;\n";
237 		}
238 		
239 		if(constants.length)
240 		{
241 			ret ~= "\t/// \n";
242 			ret ~= "\tenum Constants : int\n\t{\n";
243 			foreach(const string name; constants.keys.sort!((a, b)=>(constants[a] < constants[b])))
244 			{
245 				if(!constantsInEnums.canFind(name)) // don't document enums here; they have their own ddoc
246 				{
247 					if(auto ptr = name in ddocConstants) ret ~= "\t\t/**\n\t\t" ~ (*ptr).replace("\n", "\n\t\t") ~ "\n\t\t*/\n";
248 					else ret ~= "\t\t/** */\n";
249 				}
250 				ret ~= "\t\t"~name.snakeToCamel.escapeD~" = "~text(constants[name])~",\n";
251 			}
252 			ret ~= "\t}\n";
253 		}
254 		
255 		foreach(const m; methods)
256 		{
257 			ret ~= m.source;
258 		}
259 		
260 		foreach(const p; properties)
261 		{
262 			import std.stdio : writeln;
263 			if(p.type.godot.canFind(',')) continue; /// FIXME: handle with common base
264 			
265 			GodotMethod getterMethod, setterMethod;
266 			
267 			foreach(GodotClass c; BaseRange(cast()this))
268 			{
269 				if(!getterMethod)
270 				{
271 					auto g = c.methods.find!(m => m.name == p.getter);
272 					if(!g.empty) getterMethod = g.front;
273 				}
274 				if(!setterMethod)
275 				{
276 					auto s = c.methods.find!(m => m.name == p.setter);
277 					if(!s.empty) setterMethod = s.front;
278 				}
279 				
280 				if(getterMethod && setterMethod) break;
281 				
282 				if(c.base_class_ptr is null)
283 				{
284 					if(!getterMethod) writeln("Warning: property ", name.godot, ".", p.name, " specifies a getter that doesn't exist: ", p.getter);
285 					if(p.setter.length && !setterMethod) writeln("Warning: property ", name.godot, ".", p.name, " specifies a setter that doesn't exist: ", p.setter);
286 					break;
287 				}
288 			}
289 			
290 			if(getterMethod) ret ~= p.getterSource(getterMethod);
291 			if(p.setter.length)
292 			{
293 				if(setterMethod) ret ~= p.setterSource(setterMethod);
294 			}
295 		}
296 		
297 		
298 		
299 		
300 		ret ~= "}\n";
301 		
302 		if(singleton)
303 		{
304 			ret ~= "/// Returns: the "~className~"\n";
305 			ret ~= "@property @nogc nothrow pragma(inline, true)\n";
306 			ret ~= className ~ " " ~ name.d;
307 			ret ~= "()\n{\n";
308 			ret ~= "\tcheckClassBinding!"~className~"();\n";
309 			ret ~= "\treturn "~className~"("~className~".GDNativeClassBinding._singleton);\n";
310 			ret ~= "}\n";
311 		}
312 		
313 		return ret;
314 	}
315 	
316 	struct BaseRange
317 	{
318 		GodotClass front;
319 		BaseRange save() const { return cast()this; }
320 		bool empty() const { return front is null; }
321 		void popFront() { front = front.base_class_ptr; }
322 	}
323 }
324 
325