1 module godot.tools.generator.classes; 2 3 import godot.util.string; 4 import godot.tools.generator.methods; 5 import godot.tools.generator.enums; 6 import godot.tools.generator.util; 7 8 import asdf; 9 10 import std.range; 11 import std.algorithm.searching; 12 import std.algorithm.iteration; 13 import std.algorithm.sorting; 14 import std.path; 15 import std.conv : text; 16 import std.string; 17 18 struct ClassList { 19 GodotClass[] classes; 20 GodotClass[Type] dictionary; 21 } 22 23 struct ClassConstant { 24 string name; 25 int value; 26 } 27 28 struct BuiltinConstant { 29 string name; 30 string value; 31 @serdeOptional Type type; 32 33 // have to manually parse it as in different types value can be both string or int 34 SerdeException deserializeFromAsdf(Asdf data) { 35 // here we try read 3 options, 'name' variant is for native_structs 36 name = data["name"].get!string(null); 37 38 // optional 39 if (auto ty = data["type"].get!string(null)) { 40 type = Type.get(ty); 41 } 42 43 // try read string, otherwise simply assume it's an int 44 value = data["value"].get!string(null); 45 if (value is null) { 46 value = text(data["value"].get!int(0)); 47 } 48 49 return null; 50 } 51 } 52 53 struct Operator { 54 string name; 55 @serdeOptional Type right_type; 56 //@serdeOptional 57 Type return_type; 58 } 59 60 class Constructor : GodotMethod { 61 int index; 62 //@serdeOptional CtorArguments[] arguments; 63 64 override void finalizeDeserialization(Asdf data) { 65 super.finalizeDeserialization(data); 66 name = "new_" ~ text(index); 67 } 68 69 override Constructor isConstructor() const { 70 return cast() this; 71 } 72 73 override string functionKindName() const { 74 return "ctor"; 75 } 76 77 override string loader() const { 78 return format(`GDExtensionClassBinding.%s.mb = _godot_api.variant_get_ptr_constructor(%s, %d);`, 79 wrapperIdentifier, 80 parent.name.asNativeVariantType(), 81 index 82 ); 83 } 84 } 85 86 //struct CtorArguments 87 //{ 88 // string name; 89 // Type type; 90 //} 91 92 // they are unrelated actually but have same fields 93 //alias BuiltinMembers = CtorArguments; 94 alias BuiltinMembers = GodotArgument; 95 96 final class GodotClass { 97 Type name; 98 @serdeOptional @serdeKeys("inherits", "base_class") Type base_class; 99 @serdeOptional string api_type; 100 @serdeOptional bool singleton; 101 @serdeOptional string singleton_name; 102 @serdeOptional @serdeKeys("is_instantiable", "instanciable") bool instanciable; 103 @serdeOptional @serdeKeys("is_refcounted", "is_reference") bool is_reference; 104 @serdeOptional BuiltinConstant[] constants; // TODO: can constants be things other than ints? 105 @serdeOptional GodotMethod[] methods; 106 @serdeOptional GodotProperty[] properties; 107 @serdeOptional GodotEnum[] enums; 108 109 // built-in types only 110 @serdeIgnore bool isBuiltinClass; 111 @serdeOptional bool has_destructor; 112 @serdeOptional bool is_keyed; 113 @serdeOptional Type indexing_return_type; 114 @serdeOptional Operator[] operators; 115 @serdeOptional Constructor[] constructors; 116 @serdeOptional BuiltinMembers[] members; 117 // end built-in types only 118 119 void addUsedClass(in Type c) { 120 auto u = c.stripConstPointer(); 121 if (u.isPrimitive || u.isCoreType || u.godotType == "Object") 122 return; 123 if (u.isTypedArray) 124 u = u.arrayType; 125 if (!used_classes.canFind(u)) 126 used_classes ~= u; 127 } 128 129 void finalizeDeserialization(Asdf data) { 130 assert(name.objectClass is null); 131 name.objectClass = this; 132 name.original = this; 133 134 // FIXME why they are different? name != Type.get(name.godotType) 135 Type.get(name.godotType).original = this; 136 Type.get(name.godotType).objectClass = this; 137 138 if (base_class && base_class.godotType != "Object" && name.godotType != "Object") 139 used_classes ~= base_class; 140 141 foreach (m; constructors) { 142 m.parent = this; 143 m.return_type = name; 144 } 145 foreach (m; methods) { 146 m.parent = this; 147 } 148 foreach (ref e; enums) { 149 e.parent = this; 150 foreach (n; e.values) 151 constantsInEnums ~= n.name; 152 } 153 } 154 155 @serdeIgnore: 156 //ClassList* parent; 157 158 const(Type)[] used_classes; 159 //GodotClass base_class_ptr = null; // needs to be set after all classes loaded 160 GodotClass[] descendant_ptrs; /// direct descendent classes 161 162 Type[] missingEnums; /// enums that were left unregistered in Godot 163 164 string ddocBrief; 165 string ddoc; 166 string[string] ddocConstants; 167 168 string[] constantsInEnums; // names of constants that are enum members 169 170 string bindingStruct() const { 171 string ret = "\tpackage(godot) __gshared bool _classBindingInitialized = false;\n"; 172 ret ~= "\tpackage(godot) static struct GDExtensionClassBinding {\n"; 173 ret ~= "\t\t__gshared:\n"; 174 if (singleton) { 175 ret ~= "\t\tgodot_object _singleton;\n"; 176 ret ~= "\t\timmutable string _singletonName = \"" ~ name.godotType.chompPrefix( 177 "_") ~ "\";\n"; 178 } 179 foreach (const ct; constructors) { 180 ret ~= ct.binding; 181 } 182 if (has_destructor) { 183 ret ~= destuctorBinding(); 184 } 185 foreach (const m; methods) { 186 ret ~= m.binding; 187 } 188 ret ~= "\t}\n"; 189 return ret; 190 } 191 192 string destuctorBinding() const { 193 string ret; 194 ret ~= "\t\tGDExtensionPtrDestructor destructor;\n"; 195 return ret; 196 } 197 198 string source() { 199 string ret; 200 201 // generate the set of referenced classes 202 foreach (m; joiner([cast(GodotMethod[]) constructors, methods])) { 203 import std.algorithm.searching; 204 205 foreach (const ty; chain([m.return_type], m.arguments.map!(t=>t.type))) { 206 if (ty.isMetaType) { 207 auto c = cast() ty.stripMeta; 208 // often a meta type is a nested type like enum or bitfield 209 if (c && c.getParentType()) { 210 c = c.getParentType(); 211 } 212 213 // superbelko: i really don't like this one... 214 // deals with global enums, just skip it as it is already imported by default 215 if ((ty.isEnum || ty.isBitfield) && c && !ty.godotType.canFind('.')) 216 continue; 217 218 if (c && c !is name) 219 if (ty !is c) // ugh... 220 addUsedClass(c); 221 } else if (ty !is name) { 222 addUsedClass(ty); 223 } 224 } 225 } 226 foreach (p; properties) { 227 // some crazy property named like "streams" that returns "stream_" type (that does not exists) 228 if (!(p.getter && p.setter)) 229 continue; 230 231 Type pType; 232 GodotMethod getterMethod; 233 foreach (GodotClass c; BaseRange(cast() this)) { 234 if (!getterMethod) { 235 auto g = c.methods.find!(m => m.name == p.getter); 236 if (!g.empty) 237 getterMethod = g.front; 238 } 239 240 if (getterMethod) 241 break; 242 if (c.base_class is null) 243 break; 244 } 245 if (getterMethod) 246 pType = getterMethod.return_type; 247 else 248 pType = p.type; 249 250 if (pType.godotType.canFind(',')) 251 continue; /// FIXME: handle with common base. Also see godot#35467 252 if (pType.isEnum) { 253 auto c = pType.enumParent; 254 if (c && c !is name) 255 addUsedClass(c); 256 } else if (pType !is name) { 257 addUsedClass(pType); 258 } 259 } 260 assert(!used_classes.canFind(name)); 261 assert(!used_classes.canFind!(c => c.godotType == "Object")); 262 263 if (!isBuiltinClass) { 264 ret ~= "module godot." ~ name.asModuleName ~ ";\n\n"; 265 266 ret ~= `public import std.meta : AliasSeq, staticIndexOf; 267 public import std.traits : Unqual; 268 public import godot.api.traits; 269 public import godot; 270 public import godot.abi; 271 public import godot.api.bind; 272 public import godot.api.reference; 273 public import godot.globalenums; 274 public import godot.object; 275 public import godot.classdb;`; 276 ret ~= "\n"; 277 } 278 279 foreach (const u; used_classes) { 280 if (!u.asModuleName) 281 continue; 282 ret ~= "public import godot."; 283 ret ~= u.asModuleName; 284 ret ~= ";\n"; 285 } 286 287 string className = name.dType; 288 if (singleton) 289 className ~= "Singleton"; 290 if (isBuiltinClass) 291 className ~= "_Bind"; 292 ret ~= "/**\n" ~ ddoc ~ "\n*/\n"; 293 ret ~= "@GodotBaseClass struct " ~ className ~ " {\n"; 294 ret ~= "\tpackage(godot) enum string _GODOT_internal_name = \"" ~ name.godotType ~ "\";\n"; 295 ret ~= "\tpublic:\n"; 296 // way to much PITA, ignore for now 297 //ret ~= "@nogc nothrow:\n"; 298 299 // Pointer to Godot object, fake inheritance through alias this 300 if (name.godotType != "Object" && name.godotType != "CoreConstants" && !isBuiltinClass) { 301 ret ~= "\tunion { /** */ godot_object _godot_object; /** */ " ~ base_class.dType; 302 if (base_class && base_class.original && base_class.original.singleton) 303 ret ~= "Singleton"; 304 ret ~= " _GODOT_base; }\n\talias _GODOT_base this;\n"; 305 ret ~= "\talias BaseClasses = AliasSeq!(typeof(_GODOT_base), typeof(_GODOT_base).BaseClasses);\n"; 306 } else { 307 ret ~= "\t" ~ name.asOpaqueType ~ " _godot_object;\n"; 308 ret ~= "\talias BaseClasses = AliasSeq!();\n"; 309 } 310 311 ret ~= bindingStruct; 312 313 if (!isBuiltinClass) { 314 // equality 315 ret ~= "\t/// \n"; 316 ret ~= "\tpragma(inline, true) bool opEquals(in " ~ className ~ " other) const {\n"; 317 ret ~= "\t\treturn _godot_object.ptr is other._godot_object.ptr; \n\t}\n"; 318 // null assignment to simulate D class references 319 ret ~= "\t/// \n"; 320 ret ~= "\tpragma(inline, true) typeof(null) opAssign(typeof(null) n) {\n"; 321 ret ~= "\t\t_godot_object.ptr = n; return null; \n\t}\n"; 322 // equality with null; unfortunately `_godot_object is null` doesn't work with structs 323 ret ~= "\t/// \n"; 324 ret ~= "\tpragma(inline, true) bool opEquals(typeof(null) n) const {\n"; 325 ret ~= "\t\treturn _godot_object.ptr is n; \n\t}\n"; 326 // comparison operator 327 if (name.godotType == "Object") { 328 ret ~= "\t/// \n"; 329 ret ~= "\tpragma(inline, true) int opCmp(in GodotObject other) const {\n"; 330 ret ~= "\t\tconst void* a = _godot_object.ptr, b = other._godot_object.ptr; return a is b ? 0 : a < b ? -1 : 1; \n\t}\n"; 331 ret ~= "\t/// \n"; 332 ret ~= "\tpragma(inline, true) int opCmp(T)(in T other) const if(extendsGodotBaseClass!T) {\n"; 333 ret ~= "\t\tconst void* a = _godot_object.ptr, b = other.owner._godot_object.ptr; return a is b ? 0 : a < b ? -1 : 1; \n\t}\n"; 334 } 335 // hash function 336 ret ~= "\t/// \n"; 337 ret ~= "\textern(D) size_t toHash() const nothrow @trusted { return cast(size_t)_godot_object.ptr; }\n"; 338 339 ret ~= "\tmixin baseCasts;\n"; 340 341 // Godot constructor. 342 ret ~= "\t/// Construct a new instance of " ~ className ~ ".\n"; 343 ret ~= "\t/// Note: use `memnew!" ~ className ~ "` instead.\n"; 344 ret ~= "\tstatic " ~ className ~ " _new() {\n"; 345 ret ~= "\t\tStringName godotname = StringName(\"" ~ name.godotType ~ "\");\n"; 346 ret ~= "\t\tif(auto obj = _godot_api.classdb_construct_object(cast(GDExtensionStringNamePtr) godotname))\n"; 347 ret ~= "\t\t\treturn " ~ className ~ "(godot_object(obj));\n"; 348 ret ~= "\t\treturn typeof(this).init;\n"; 349 ret ~= "\t}\n"; 350 } 351 352 foreach (ct; constructors) { 353 //ret ~= "\tstatic "~name.dType~" "~ ct.name ~ ct.templateArgsString ~ ct.argsString ~ " {\n"; 354 //ret ~= "\t\tif(auto fn = _godot_api.variant_get_ptr_constructor(GDEXTENSION_VARIANT_TYPE_"~name.godotType.snakeToCamel.toUpper ~ ", " ~ text(ct.index) ~"))\n"; 355 //ret ~= "\t\t\treturn "~name.dType~"(godot_object(fn(...)));\n"; 356 //ret ~= "\t\treturn typeof(this).init;\n"; 357 //ret ~= "\t}\n"; 358 ret ~= ct.source; 359 } 360 361 // currently only core types can have destructor 362 if (has_destructor) { 363 ret ~= "\tvoid _destructor() {\n"; 364 ret ~= "\t\tif (!GDExtensionClassBinding.destructor)\n"; 365 ret ~= "\t\t\tGDExtensionClassBinding.destructor = _godot_api.variant_get_ptr_destructor(GDEXTENSION_VARIANT_TYPE_" ~ name 366 .godotType.camelToSnake.toUpper ~ ");\n"; 367 ret ~= "\t\tGDExtensionClassBinding.destructor(&_godot_object);\n"; 368 ret ~= "\t}\n"; 369 } 370 371 //ret ~= "\t@disable new(size_t s);\n"; 372 373 foreach (const ref e; enums) { 374 ret ~= e.source; 375 } 376 377 foreach (const ref e; missingEnums) { 378 import std.stdio; 379 380 writeln( 381 "Warning: The enum " ~ e.dType ~ " is missing from Godot's script API; using a non-typesafe int instead."); 382 ret ~= "\t/// Warning: The enum " ~ e.dType ~ " is missing from Godot's script API; using a non-typesafe int instead.\n"; 383 ret ~= "\tdeprecated(\"The enum " ~ e.dType ~ " is missing from Godot's script API; using a non-typesafe int instead.\")\n"; 384 string shortName = e.dType[e.dType.countUntil(".") + 1 .. $]; 385 ret ~= "\talias " ~ shortName ~ " = int;\n"; 386 } 387 388 if (!isBuiltinClass && constants.length) { 389 ret ~= "\t/// \n"; 390 ret ~= "\tenum Constants : int {\n"; 391 foreach (constant; constants.sort!((a, b) => (a.value < b.value))) { 392 if (!constantsInEnums.canFind(constant.name)) // don't document enums here; they have their own ddoc 393 { 394 if (auto ptr = constant.name in ddocConstants) 395 ret ~= "\t\t/**\n\t\t" ~ (*ptr).replace("\n", "\n\t\t") ~ "\n\t\t*/\n"; 396 else 397 ret ~= "\t\t/** */\n"; 398 } 399 ret ~= "\t\t" ~ constant.name.snakeToCamel.escapeDType ~ " = " ~ text( 400 constant.value) ~ ",\n"; 401 } 402 ret ~= "\t}\n"; 403 } 404 405 foreach (const m; methods) { 406 ret ~= m.source; 407 } 408 409 foreach (const p; properties) { 410 import std.stdio : writeln; 411 412 if (p.type.godotType.canFind(',')) 413 continue; /// FIXME: handle with common base 414 415 GodotMethod getterMethod, setterMethod; 416 417 foreach (GodotClass c; BaseRange(cast() this)) { 418 if (!getterMethod) { 419 auto g = c.methods.find!(m => m.name == p.getter); 420 if (!g.empty) 421 getterMethod = g.front; 422 } 423 if (!setterMethod) { 424 auto s = c.methods.find!(m => m.name == p.setter); 425 if (!s.empty) 426 setterMethod = s.front; 427 } 428 429 if (getterMethod && setterMethod) 430 break; 431 432 // TODO: add hide Warning: property flag 433 if (c.base_class is null) { 434 // if (!getterMethod) 435 // writeln("Warning: property ", name.godotType, ".", p.name, " specifies a getter that doesn't exist: ", p 436 // .getter); 437 // if (p.setter.length && !setterMethod) 438 // writeln("Warning: property ", name.godotType, ".", p.name, " specifies a setter that doesn't exist: ", p 439 // .setter); 440 break; 441 } 442 } 443 444 if (getterMethod) 445 ret ~= p.getterSource(getterMethod); 446 if (p.setter.length) { 447 if (setterMethod) 448 ret ~= p.setterSource(setterMethod); 449 } 450 } 451 452 ret ~= "}\n"; 453 454 if (singleton) { 455 ret ~= "/// Returns: the " ~ className ~ "\n"; 456 //ret ~= "@property @nogc nothrow pragma(inline, true)\n"; 457 ret ~= "@property pragma(inline, true)\n"; 458 ret ~= className ~ " " ~ name.dType ~ "() {\n"; 459 ret ~= "\tcheckClassBinding!" ~ className ~ "();\n"; 460 ret ~= "\treturn " ~ className ~ "(" ~ className ~ ".GDExtensionClassBinding._singleton);\n"; 461 ret ~= "}\n"; 462 } 463 464 return ret; 465 } 466 467 struct BaseRange { 468 GodotClass front; 469 BaseRange save() const { 470 return cast() this; 471 } 472 473 bool empty() const { 474 return front is null; 475 } 476 477 void popFront() { 478 front = front.base_class.original; 479 } 480 } 481 }