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 }