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