1 module godot.tools.generator.methods; 2 3 import godot.util.string; 4 import godot.tools.generator.classes; 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.path; 14 import std.conv : text; 15 import std.string; 16 17 import std.typecons; 18 19 class GodotMethod { 20 @serdeOptional 21 string name; // constructors doesn't have name 22 @serdeOptional @serdeKeys("return_type", "return_value") 23 Type return_type; 24 @serdeOptional 25 bool is_editor; 26 @serdeOptional 27 bool is_noscript; 28 @serdeOptional 29 bool is_const; 30 @serdeOptional 31 bool is_virtual; 32 @serdeOptional 33 bool is_static; 34 @serdeOptional 35 string category; 36 @serdeOptional @serdeKeys("is_vararg", "has_varargs") 37 bool has_varargs; 38 @serdeOptional 39 bool is_from_script; 40 @serdeOptional 41 uint hash; 42 @serdeOptional 43 GodotArgument[] arguments; 44 45 void finalizeDeserialization(Asdf data) { 46 // FIXME: why data is here if it's not used? 47 // Superbelko: Because this is post-serialize event and we only doing some adjustments here 48 if (!return_type) 49 return_type = Type.get("void"); 50 foreach (i, ref arg; arguments) { 51 arg.index = i; 52 arg.parent = this; 53 } 54 } 55 56 @serdeIgnore: 57 GodotClass parent; 58 59 // indicates that this method is a helper methods that simply 60 // redirectes all arguments to the specidied method, for example string types helpers 61 GodotMethod redirectsTo; 62 63 string ddoc; 64 65 Constructor isConstructor() const { 66 return null; 67 } 68 // Operator isOperator() { return null; } 69 // Indexer isIndexer() { return null; } 70 71 bool same(in GodotMethod other) const { 72 return name == other.name && is_const == other.is_const; 73 } 74 75 bool needsStringHelpers() const { 76 static bool anyString (GodotArgument a) { 77 return a.type.stripConst.isGodotStringType; 78 } 79 // const all the way... 80 return (cast(GodotArgument[]) arguments).canFind!anyString(); 81 } 82 83 string templateArgsString() const { 84 string ret = ""; 85 bool first = true; // track first arg to skip comma 86 foreach (i, ref arg; arguments) { 87 if (arg.type.acceptImplicit) { 88 if (first) 89 first = false; 90 else 91 ret ~= ", "; 92 ret ~= text(arg.type.godotType, "Arg", i); 93 } 94 } 95 if (has_varargs) { 96 if (!first) 97 ret ~= ", "; 98 ret ~= "VarArgs..."; 99 } 100 // template parens only if it actually is a template 101 if (ret.length != 0) 102 ret = text("(", ret, ")"); 103 return ret; 104 } 105 106 string argsString() const { 107 string ret = "("; 108 109 foreach (i, ref arg; arguments) { 110 // FIXME: do it prettier 111 string typeString = ""; 112 113 if (i != 0) ret ~= ", "; 114 if (arg.type.acceptImplicit) { 115 ret ~= text(arg.type.dCallParamPrefix, arg.type.godotType, "Arg", i); 116 typeString = text(arg.type.godotType, "Arg", i); 117 } else { 118 ret ~= text(arg.type.dCallParamPrefix, arg.type.dType); 119 typeString = arg.type.dType; 120 } 121 122 ret ~= " " ~ arg.name.escapeDType; 123 124 // HACK: look at GodotArgument 125 // FIXME: Causes forward reference 126 if (arg.default_value != "\0") { 127 if (arg.type.isBitfield || arg.type.isEnum) { 128 ret ~= " = cast(" ~ typeString ~ ") " ~ escapeDefaultType(arg.type, arg.default_value); 129 } else { 130 // This probably should be in StringHelper method class 131 if (redirectsTo && redirectsTo.arguments[i].type.isGodotStringType) { 132 ret ~= " = " ~ stripStringDefaultValueType(redirectsTo.arguments[i].type, arg.default_value); 133 } 134 else { 135 ret ~= " = " ~ escapeDefaultType(arg.type, arg.default_value); 136 } 137 } 138 } 139 } 140 if (has_varargs) { 141 if (arguments.length != 0) 142 ret ~= ", "; 143 ret ~= "VarArgs varArgs"; 144 } 145 ret ~= ")"; 146 return ret; 147 } 148 149 /++ 150 Outputs binding method declaration with meta information. 151 e.g.: 152 153 @GodotName("insert") @MethodHash(0) GodotMethod!(long, long, Variant) method_insert; 154 +/ 155 string binding() const { 156 string ret; 157 158 ret ~= "\t\t@GodotName(\"" ~ name ~ "\") GodotMethod!(" ~ return_type.dType; 159 foreach (ai, const arg; arguments) { 160 ret ~= ", " ~ arg.type.dType; 161 } 162 if (has_varargs) 163 ret ~= ", GodotVarArgs"; 164 ret ~= ") " ~ wrapperIdentifier ~ ";\n"; 165 166 return ret; 167 } 168 169 /// Function pointer name for this method 170 /// "constructor_new_0", "method_normalize", ... 171 string wrapperIdentifier() const { 172 return functionKindName ~ "_" ~ name.snakeToCamel.escapeDType; 173 } 174 175 /// Function type name used in some cases: like "method", "ctor", "getter", etc... 176 string functionKindName() const { 177 return "method"; 178 } 179 180 /++ 181 Formats whole method including function signature and body with implementation. 182 e.g.: 183 ```d 184 string getSlice(in string delimiter, in long slice) const { 185 if (!GDExtensionClassBinding.method_getSlice.mb) 186 GDExtensionClassBinding.method_getSlice.mb = _godot_api.variant_get_ptr_builtin_method(GDEXTENSION_VARIANT_TYPE_STRING, "get_slice", 3535100402); 187 return toDString(callBuiltinMethod!(String)(cast(GDExtensionPtrBuiltInMethod) GDExtensionClassBinding.method_getSlice.mb, cast(void*) &_godot_object, cast() toGodotString(delimiter), cast() slice)); 188 } 189 ``` 190 +/ 191 string source() const { 192 string ret; 193 194 // ddoc comment (if any) 195 ret ~= "\t/**\n\t" ~ ddoc.replace("\n", "\n\t") ~ "\n\t*/\n"; 196 ret ~= "\t"; 197 198 ret ~= signature(); 199 200 ret ~= " {\n"; 201 202 ret ~= body_(); 203 204 ret ~= "\t}\n"; 205 206 if (needsStringHelpers && !isConstructor()) { 207 ret ~= "\n"; 208 209 // copy-paste a method 210 auto m = new StringHelperGodotMethod(); { 211 m.name = this.name; 212 m.is_editor = this.is_editor; 213 m.is_noscript = this.is_noscript; 214 m.is_const = this.is_const; 215 m.is_virtual = this.is_virtual; 216 m.is_static = this.is_static; 217 m.category = this.category; 218 m.has_varargs = this.has_varargs; 219 m.is_from_script = this.is_from_script; 220 m.hash = this.hash; 221 m.parent = cast() this.parent; 222 m.return_type = cast() this.return_type; 223 m.redirectsTo = cast() this; 224 } 225 GodotArgument[] newargs; 226 foreach(a; arguments) { 227 // replace string types with plain D string 228 if (a.type.isGodotStringType) 229 newargs ~= GodotArgument(a.name, Type.get("string"), a.default_value, a.index, m); 230 else 231 newargs ~= GodotArgument(a.name, cast() a.type, a.default_value, a.index, m); 232 } 233 m.arguments = newargs; 234 ret ~= m.source(); 235 } 236 237 return ret; 238 } 239 240 /// Formats function signature, e.g. 241 /// Array slice(in long begin, in long end, in long step, in bool deep) const 242 string signature() const { 243 string ret; 244 245 // optional static modifier 246 if (isConstructor) { 247 ret ~= "static "; 248 } 249 // note that even though it strips constness of return type the method is still marked const 250 // const in D is transitive, which means compiler should disallow modifying returned reference types 251 ret ~= return_type.stripConst.dRef ~ " "; 252 // none of the types (Classes/Core/Primitive) are pointers in D 253 // Classes are reference types; the others are passed by value. 254 ret ~= name.snakeToCamel.escapeDType; 255 256 ret ~= templateArgsString; 257 ret ~= argsString; 258 259 // function const attribute 260 if (is_const) 261 ret ~= " const"; 262 else if (name == "callv" && parent.name.godotType == "Object") 263 ret ~= " const"; /// HACK 264 265 return ret; 266 } 267 268 /// Formats body containing implementation, omitting outer braces 269 string body_() const { 270 string ret; 271 272 // load function pointer 273 ret ~= "\t\tif (!GDExtensionClassBinding." ~ wrapperIdentifier ~ ".mb) {\n"; 274 // tab() will indent it correctly starting from first element 275 ret ~= loader().split('\n').map!(s => s.tab(3)).join('\n') ~ "\n"; 276 ret ~= "\t\t}\n"; 277 278 if (is_virtual || has_varargs) { 279 // keep it like this for now, serves as example. 280 // function will put normal arguments first, then varargs 281 // next, in order to call that function we need actually array of pointers 282 // after that we call the function with array of pointers instead of plain args array 283 version (none) 284 if (name == "emit_signal") { 285 // two tabs 286 ret ~= ` Variant[varArgs.length+2] _GODOT_args; 287 _GODOT_args[0] = String("emit_signal"); 288 _GODOT_args[1] = signal; 289 foreach(vai, VA; VarArgs) { 290 _GODOT_args[vai+2] = Variant(varArgs[vai]); 291 } 292 Variant*[varArgs.length+2] _args; 293 foreach(i; 0.._GODOT_args.length) { 294 _args[i] = &_GODOT_args[i]; 295 } 296 Variant ret; 297 GDExtensionCallError err; 298 _godot_api.object_method_bind_call(GDExtensionClassBinding.method_emitSignal.mb, _godot_object.ptr, cast(void**) _args.ptr, _GODOT_args.length, cast(void*) &ret, &err); 299 debug if (int code = ret.as!int()) { 300 import godot.api; 301 print("signal error: ", signal, " code: ", code); 302 } 303 return cast(GodotError) err.error;`; 304 } 305 306 // static array must have at least 1 element 307 import std.algorithm : max; 308 309 int argsLength = max(1, (cast(int) arguments.length)); 310 // choose between varargs and regular function for arguments 311 if (has_varargs) { 312 ret ~= "\t\tVariant[varArgs.length+" ~ text(argsLength) ~ "] _GODOT_args;\n"; 313 ret ~= "\t\tVariant*[varArgs.length+" ~ text(argsLength) ~ "] _args;\n"; 314 } else { 315 ret ~= "\t\tVariant[" ~ text(argsLength) ~ "] _GODOT_args;\n"; 316 ret ~= "\t\tVariant*[" ~ text(argsLength) ~ "] _args;\n"; 317 318 } 319 foreach (i, const arg; arguments) { 320 // gathers normal parameters in variant array to be later used as pointers 321 ret ~= "\t\t_GODOT_args[" ~ text(cast(int) i) ~ "] = " ~ escapeDType(arg.name) ~ ";\n"; 322 } 323 324 if (has_varargs) { 325 // copy varargs after regular args 326 ret ~= "\t\tforeach(vai, VA; VarArgs) {\n"; 327 ret ~= "\t\t\t_GODOT_args[vai+" ~ text( 328 cast(int) arguments.length) ~ "] = Variant(varArgs[vai]);\n"; 329 ret ~= "\t\t}\n"; 330 } 331 332 // make pointer array 333 ret ~= "\t\tforeach(i; 0.._GODOT_args.length) {\n"; 334 ret ~= "\t\t\t_args[i] = &_GODOT_args[i];\n"; 335 ret ~= "\t\t}\n"; 336 337 //ret ~= "\t\tStringName _GODOT_method_name = StringName(\""~name~"\");\n"; 338 339 ret ~= "\t\tVariant ret;\n"; 340 ret ~= "\t\tGDExtensionCallError err;\n"; 341 342 // there is subtle difference, we pass &godot_object for builtins and .ptr for any other object 343 // but normally they should work just with &godot_object, we had issues with that in the past though 344 // so here we now use real ptr value instead 345 if (parent.isBuiltinClass) 346 ret ~= "\t\t_godot_api.object_method_bind_call(GDExtensionClassBinding." ~ wrapperIdentifier ~ ".mb, cast(void*) &_godot_object, cast(void**) _args.ptr, _GODOT_args.length, cast(void*) &ret, &err);\n"; 347 else 348 ret ~= "\t\t_godot_api.object_method_bind_call(GDExtensionClassBinding." ~ wrapperIdentifier ~ ".mb, cast(void*) _godot_object.ptr, cast(void**) _args.ptr, _GODOT_args.length, cast(void*) &ret, &err);\n"; 349 // ret ~= "\t\t"; 350 // DMD 2.101 complains about Type* pointers escaping function scope 351 // So instead of returning it directly make a temporary pointer variable 352 if (return_type.dType != "void") { 353 if (return_type.isPointerType) { 354 ret ~= "\t\tauto r = "; 355 } 356 else { 357 ret ~= "\t\treturn "; 358 } 359 360 if (return_type.dType != "Variant") { 361 ret ~= "ret.as!(RefOrT!(" ~ return_type.stripConst.dType ~ "))"; 362 if (return_type.isPointerType) { 363 ret ~= ";\n\t\treturn r"; 364 } 365 } 366 else { 367 ret ~= "ret"; 368 } 369 ret ~= ";\n"; 370 } 371 } else { // end varargs/virtual impl 372 // adds temp variable for static ctor 373 if (isConstructor) { 374 ret ~= "\t\t"; 375 ret ~= parent.name.canBeCopied ? parent.name.dType : parent.name.asOpaqueType; 376 ret ~= " _godot_object;\n"; 377 } 378 // omit return for constructors, it will be wrapped and returned later 379 if (return_type.dType != "void" && !(isConstructor && parent.name.isCoreType)) { 380 ret ~= "\t\treturn "; 381 } else { 382 ret ~= "\t\t"; 383 } 384 385 ret ~= callType() ~ "!(" ~ return_type.dType ~ ")("; 386 if (parent.isBuiltinClass) 387 ret ~= "cast(GDExtensionPtrBuiltInMethod) "; 388 ret ~= "GDExtensionClassBinding." ~ wrapperIdentifier; 389 if (parent.isBuiltinClass) // Adds method pointer accessor instead of template itself 390 ret ~= ".mb"; 391 ret ~= ", "; 392 if (parent.isBuiltinClass) 393 ret ~= "cast(void*) &_godot_object"; 394 else 395 ret ~= "_godot_object"; 396 foreach (ai, const arg; arguments) { 397 // FIXME: const cast hack 398 // FIXME: make auto-cast in escapeDType? 399 // FIXME: StringName pointer wrapping should be in call handlers 400 // it also relies on that ugly cast. 401 // The problem is that for some reason that call expects StringName** 402 // and unlike C++ I haven't come with a way to do that 403 if (arg.type.godotType == "StringName" && callType == "callBuiltinMethod") 404 ret ~= ", cast(void*) " ~ arg.name.escapeDType(arg.type.godotType); 405 else 406 ret ~= ", cast() " ~ arg.name.escapeDType(arg.type.godotType); 407 } 408 ret ~= ");\n"; 409 // wrap temporary object 410 if (isConstructor) { 411 if (parent.name.canBeCopied) { 412 ret ~= "\t\treturn _godot_object;\n"; 413 } else { 414 ret ~= "\t\treturn " ~ return_type.dType ~ "(_godot_object);\n"; 415 } 416 } 417 } // end normal method impl 418 419 return ret; 420 } 421 422 /// call type wrapper, "ptrcall", "callv", "callBuiltinMethod", etc... 423 string callType() const { 424 if (parent.isBuiltinClass) 425 return "callBuiltinMethod"; 426 //if (has_varargs) 427 // return "callv"; 428 return "ptrcall"; 429 } 430 431 /// formats function pointer loader, e.g. 432 /// GDExtensionClassBinding.method_append.mb = _godot_api.clasdb_get_methodbind("class", "method", hash); 433 string loader() const { 434 char[] buf; 435 buf ~= "StringName classname = StringName(\"" ~ parent.name.godotType ~ "\");\n"; 436 buf ~= "StringName methodname = StringName(\"" ~ name ~ "\");\n"; 437 // probably better to move in its own subclass 438 if (parent.isBuiltinClass) { 439 return cast(string) buf ~ format(`GDExtensionClassBinding.%s.mb = _godot_api.variant_get_ptr_builtin_method(%s, cast(GDExtensionStringNamePtr) methodname, %d);`, 440 wrapperIdentifier, 441 parent.name.asNativeVariantType, 442 hash 443 ); 444 } 445 446 return cast(string) buf ~ format(`GDExtensionClassBinding.%s.mb = _godot_api.classdb_get_method_bind(cast(GDExtensionStringNamePtr) classname, cast(GDExtensionStringNamePtr) methodname, %d);`, 447 wrapperIdentifier, 448 hash, 449 ); 450 } 451 } 452 453 struct GodotArgument { 454 string name; 455 Type type; 456 457 // HACK: when godot doesn't want to specifically 458 // tell you default it leaves it empty ("default_value": "") 459 // so when asdf hits it sets default_value to [] 460 // which is the same as if it's undefined 461 @serdeOptional 462 string default_value = "\0"; 463 464 @serdeIgnore: 465 466 size_t index; 467 GodotMethod parent; 468 } 469 470 class GodotProperty { 471 string name; 472 Type type; 473 @serdeOptional 474 string getter, setter; 475 @serdeOptional 476 int index = -1; 477 478 @serdeIgnore: 479 480 string ddoc; 481 482 string getterSource(in GodotMethod m) const { 483 string retType = m.return_type.dType; 484 string ret; 485 ret ~= "\t/**\n\t" ~ ddoc.replace("\n", "\n\t") ~ "\n\t*/\n"; 486 ret ~= "\t@property " ~ retType ~ " " ~ name.replace("/", "_") 487 // ret ~= "\t@property " ~ m.return_type.dType ~ " " ~ name.replace("/", "_") 488 .snakeToCamel.escapeDType ~ "() {\n"; /// TODO: const? 489 ret ~= "\t\treturn " ~ getter.snakeToCamel.escapeDType ~ "("; 490 if (index != -1) { 491 // add cast to enum types 492 if (m.arguments[0].type.isEnum) 493 ret ~= "cast(" ~ m.arguments[0].type.dType ~ ") "; 494 ret ~= text(index); 495 } 496 ret ~= ");\n"; 497 ret ~= "\t}\n"; 498 return ret; 499 } 500 501 string setterSource(in GodotMethod m) const { 502 string setType = m.arguments[$ - 1].type.dType; 503 string ret; 504 ret ~= "\t/// ditto\n"; 505 ret ~= "\t@property void " ~ name.replace("/", "_") 506 .snakeToCamel.escapeDType ~ "(" ~ setType ~ " v) {\n"; 507 // .snakeToCamel.escapeDType ~ "(" ~ m.arguments[$ - 1].type.dType ~ " v) {\n"; 508 ret ~= "\t\t" ~ setter.snakeToCamel.escapeDType ~ "("; 509 if (index != -1) { 510 // add cast to enum types 511 if (m.arguments[0].type.isEnum) 512 ret ~= "cast(" ~ m.arguments[0].type.dType ~ ") "; 513 ret ~= text(index) ~ ", "; 514 } 515 ret ~= "v);\n"; 516 ret ~= "\t}\n"; 517 return ret; 518 } 519 } 520 521 522 class StringHelperGodotMethod : GodotMethod { 523 override string body_() const { 524 // simply forwards all arguments to the actual method but wrap any string types 525 string ret; 526 527 // wrap string types before calling the method 528 foreach (i, const arg; arguments) { 529 auto realType = redirectsTo.arguments[i].type; 530 if (realType.isGodotStringType) { 531 // writes something like this: 532 // StringName arg3 = StringName(p_name); 533 ret ~= "\t\t" ~ escapeDType(realType.dType) ~ " arg" ~ text(cast(int) i) 534 ~ " = " ~ escapeDType(realType.dType) ~ "(" ~ escapeDType(arg.name) ~ ");\n"; 535 } 536 //ret ~= "\t\t_GODOT_args[" ~ text(cast(int) i) ~ "] = " ~ escapeDType(arg.name) ~ ";\n"; 537 } 538 539 if (return_type.dType != "void") { 540 ret ~= "\t\treturn "; 541 } 542 else { 543 ret ~= "\t\t"; 544 } 545 546 // now write the normal D method(not a godot one) call with wrapped arguments in place of D strings 547 ret ~= name.snakeToCamel.escapeDType ~ "("; 548 foreach (ai, const arg; arguments) { 549 if (ai) { 550 ret ~= ", "; 551 } 552 if (redirectsTo.arguments[ai].type.isGodotStringType) { 553 ret ~= "arg" ~ text(cast(int) ai); 554 } 555 else { 556 ret ~= arg.name.escapeDType; 557 } 558 } 559 if (has_varargs) { 560 if (arguments.length != 0) 561 ret ~= ", "; 562 ret ~= "varArgs"; 563 } 564 ret ~= ");\n"; 565 return ret; 566 } 567 }