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