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