1 module godot.util.generator.methods; 2 3 import godot.util.tools.string; 4 import godot.util.generator.classes, 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; 10 import std.path; 11 import std.conv : text; 12 import std.string; 13 14 import std.typecons; 15 16 class GodotMethod { 17 @serdeOptional 18 string name; // constructors doesn't have name 19 @serdeOptional @serdeKeys("return_type", "return_value") 20 Type return_type; 21 @serdeOptional 22 bool is_editor; 23 @serdeOptional 24 bool is_noscript; 25 @serdeOptional 26 bool is_const; 27 @serdeOptional 28 bool is_virtual; 29 @serdeOptional 30 bool is_static; 31 @serdeOptional 32 string category; 33 @serdeOptional @serdeKeys("is_vararg", "has_varargs") 34 bool has_varargs; 35 @serdeOptional 36 bool is_from_script; 37 @serdeOptional 38 uint hash; 39 @serdeOptional 40 GodotArgument[] arguments; 41 42 void finalizeDeserialization(Asdf data) { 43 if (!return_type) 44 return_type = Type.get("void"); 45 foreach (i, ref a; arguments) { 46 a.index = i; 47 a.parent = this; 48 } 49 } 50 51 @serdeIgnore: 52 GodotClass parent; 53 54 string ddoc; 55 56 Constructor isConstructor() const { 57 return null; 58 } 59 // Operator isOperator() { return null; } 60 // Indexer isIndexer() { return null; } 61 62 bool same(in GodotMethod other) const { 63 return name == other.name && is_const == other.is_const; 64 } 65 66 string templateArgsString() const { 67 string ret = ""; 68 bool first = true; // track first arg to skip comma 69 foreach (i, ref a; arguments) { 70 if (a.type.acceptImplicit) { 71 if (first) 72 first = false; 73 else 74 ret ~= ", "; 75 ret ~= text(a.type.godotType, "Arg", i); 76 } 77 } 78 if (has_varargs) { 79 if (!first) 80 ret ~= ", "; 81 ret ~= "VarArgs..."; 82 } 83 // template parens only if it actually is a template 84 if (ret.length != 0) 85 ret = text("(", ret, ")"); 86 return ret; 87 } 88 89 string argsString() const { 90 string ret = "("; 91 92 foreach (i, ref a; arguments) { 93 if (i != 0) 94 ret ~= ", "; 95 if (a.type.acceptImplicit) 96 ret ~= text(a.type.dCallParamPrefix, 97 a.type.godotType, "Arg", i); 98 else 99 ret ~= text(a.type.dCallParamPrefix, a.type.dType); 100 101 ret ~= " " ~ a.name.escapeD; 102 103 // HACK: look at GodotArgument 104 // FIXME: Causes forward reference 105 // if (a.default_value != "\0") { 106 // ret ~= " = " ~ escapeDefault(a.type, a.default_value); 107 // } 108 } 109 if (has_varargs) { 110 if (arguments.length != 0) 111 ret ~= ", "; 112 ret ~= "VarArgs varArgs"; 113 } 114 ret ~= ")"; 115 return ret; 116 } 117 118 /++ 119 Outputs binding method declaration with meta information. 120 e.g.: 121 122 @GodotName("insert") @MethodHash(0) GodotMethod!(long, long, Variant) method_insert; 123 +/ 124 string binding() const { 125 string ret; 126 127 ret ~= "\t\t@GodotName(\"" ~ name ~ "\") GodotMethod!(" ~ return_type.dType; 128 foreach (ai, const a; arguments) { 129 ret ~= ", " ~ a.type.dType; 130 } 131 if (has_varargs) 132 ret ~= ", GodotVarArgs"; 133 ret ~= ") " ~ wrapperIdentifier ~ ";\n"; 134 135 return ret; 136 } 137 138 /// Function pointer name for this method 139 /// "constructor_new_0", "method_normalize", ... 140 string wrapperIdentifier() const { 141 return funKindName ~ "_" ~ name.snakeToCamel.escapeD; 142 } 143 144 /// Function type name used in some cases: like "method", "ctor", "getter", etc... 145 string funKindName() const { 146 return "method"; 147 } 148 149 /++ 150 Formats whole method including function signature and body with implementation. 151 e.g.: 152 153 Array slice(in long begin, in long end, in long step, in bool deep) const 154 { 155 if (!GDNativeGDNativeClassBinding.method_slice) 156 GDNativeClassBinding.slice = _godot_api.get_method_bind("Class", "Method", 42); 157 return callBuiltinMethod!(Array)(cast(GDNativePtrBuiltInMethod) GDNativeClassBinding.slice.mb, cast(void*) &_godot_object, cast() begin, cast() end, cast() step, cast() deep); 158 } 159 +/ 160 string source() const { 161 string ret; 162 163 // ddoc comment (if any) 164 ret ~= "\t/**\n\t" ~ ddoc.replace("\n", "\n\t") ~ "\n\t*/\n"; 165 ret ~= "\t"; 166 167 ret ~= signature(); 168 169 ret ~= "\n\t{\n"; 170 171 ret ~= body_(); 172 173 ret ~= "\t}\n"; 174 175 return ret; 176 } 177 178 /// Formats function signature, e.g. 179 /// Array slice(in long begin, in long end, in long step, in bool deep) const 180 string signature() const { 181 string ret; 182 183 // optional static modifier 184 if (isConstructor) 185 ret ~= "static "; 186 // note that even though it strips constness of return type the method is still marked const 187 // const in D is transitive, which means compiler should disallow modifying returned reference types 188 ret ~= return_type.stripConst.dRef ~ " "; 189 // none of the types (Classes/Core/Primitive) are pointers in D 190 // Classes are reference types; the others are passed by value. 191 ret ~= name.snakeToCamel.escapeD; 192 193 ret ~= templateArgsString; 194 ret ~= argsString; 195 196 // function const attribute 197 if (is_const) 198 ret ~= " const"; 199 else if (name == "callv" && parent.name.godotType == "Object") 200 ret ~= " const"; /// HACK 201 202 return ret; 203 } 204 205 /// Formats body containing implementation, omitting outer braces 206 string body_() const { 207 string ret; 208 209 // load function pointer 210 ret ~= "\t\tif (!GDNativeClassBinding." ~ wrapperIdentifier ~ ".mb)\n"; 211 ret ~= "\t\t\t" ~ loader() ~ "\n"; 212 213 if (is_virtual || has_varargs) { 214 // keep it like this for now, serves as example. 215 // function will put normal arguments first, then varargs 216 // next, in order to call that function we need actually array of pointers 217 // after that we call the function with array of pointers instead of plain args array 218 version (none) 219 if (name == "emit_signal") { 220 // two tabs 221 ret ~= ` Variant[varArgs.length+2] _GODOT_args; 222 _GODOT_args[0] = String("emit_signal"); 223 _GODOT_args[1] = signal; 224 foreach(vai, VA; VarArgs) 225 { 226 _GODOT_args[vai+2] = Variant(varArgs[vai]); 227 } 228 Variant*[varArgs.length+2] _args; 229 foreach(i; 0.._GODOT_args.length) 230 { 231 _args[i] = &_GODOT_args[i]; 232 } 233 Variant ret; 234 GDNativeCallError err; 235 _godot_api.object_method_bind_call(GDNativeClassBinding.method_emitSignal.mb, _godot_object.ptr, cast(void**) _args.ptr, _GODOT_args.length, cast(void*) &ret, &err); 236 debug if (int code = ret.as!int()) 237 { 238 import godot.api; 239 print("signal error: ", signal, " code: ", code); 240 } 241 return cast(GodotError) err.error;`; 242 } 243 244 // static array must have at least 1 element 245 import std.algorithm : max; 246 247 int argsLength = max(1, (cast(int) arguments.length)); 248 // choose between varargs and regular function for arguments 249 if (has_varargs) { 250 ret ~= "\t\tVariant[varArgs.length+" ~ text(argsLength) ~ "] _GODOT_args;\n"; 251 ret ~= "\t\tVariant*[varArgs.length+" ~ text(argsLength) ~ "] _args;\n"; 252 } else { 253 ret ~= "\t\tVariant[" ~ text(argsLength) ~ "] _GODOT_args;\n"; 254 ret ~= "\t\tVariant*[" ~ text(argsLength) ~ "] _args;\n"; 255 256 } 257 foreach (i, const a; arguments) { 258 // gathers normal parameters in variant array to be later used as pointers 259 ret ~= "\t\t_GODOT_args[" ~ text(cast(int) i) ~ "] = " ~ escapeD(a.name) ~ ";\n"; 260 } 261 262 if (has_varargs) { 263 // copy varargs after regular args 264 ret ~= "\t\tforeach(vai, VA; VarArgs)\n"; 265 ret ~= "\t\t{\n"; 266 ret ~= "\t\t\t_GODOT_args[vai+" ~ text( 267 cast(int) arguments.length) ~ "] = Variant(varArgs[vai]);\n"; 268 ret ~= "\t\t}\n"; 269 } 270 271 // make pointer array 272 ret ~= "\t\tforeach(i; 0.._GODOT_args.length)\n"; 273 ret ~= "\t\t{\n"; 274 ret ~= "\t\t\t_args[i] = &_GODOT_args[i];\n"; 275 ret ~= "\t\t}\n"; 276 277 //ret ~= "\t\tStringName _GODOT_method_name = StringName(\""~name~"\");\n"; 278 279 ret ~= "\t\tVariant ret;\n"; 280 ret ~= "\t\tGDNativeCallError err;\n"; 281 ret ~= "\t\t_godot_api.object_method_bind_call(GDNativeClassBinding." ~ wrapperIdentifier ~ ".mb, cast(void*) _godot_object.ptr, cast(void**) _args.ptr, _GODOT_args.length, cast(void*) &ret, &err);\n"; 282 ret ~= "\t\t"; 283 if (return_type.dType != "void") { 284 ret ~= "return "; 285 if (return_type.dType != "Variant") 286 ret ~= "ret.as!(RefOrT!(" ~ return_type.stripConst.dType ~ "))"; 287 else 288 ret ~= "ret"; 289 ret ~= ";\n"; 290 } 291 } // end varargs/virtual impl 292 else { 293 // add temp variable for static ctor 294 if (isConstructor) { 295 if (parent.name.canBeCopied) 296 ret ~= parent.name.dType; 297 else 298 ret ~= parent.name.opaqueType; 299 ret ~= " _godot_object;\n\t\t"; 300 } 301 // omit return for constructors, it will be wrapped and returned later 302 if (return_type.dType != "void" && !(isConstructor && parent.name.isCoreType)) 303 ret ~= "return "; 304 ret ~= callType() ~ "!(" ~ return_type.dType ~ ")("; 305 if (parent.isBuiltinClass) 306 ret ~= "cast(GDNativePtrBuiltInMethod) "; 307 ret ~= "GDNativeClassBinding." ~ wrapperIdentifier; 308 if (parent.isBuiltinClass) // Adds method pointer accessor instead of template itself 309 ret ~= ".mb"; 310 ret ~= ", "; 311 if (parent.isBuiltinClass) 312 ret ~= "cast(void*) &_godot_object"; 313 else 314 ret ~= "_godot_object"; 315 foreach (ai, const a; arguments) { 316 ret ~= ", cast() " ~ a.name.escapeD; // FIXME: const cast hack 317 } 318 ret ~= ");\n"; 319 // wrap temporary object 320 if (isConstructor) { 321 if (parent.name.canBeCopied) 322 ret ~= "\t\treturn _godot_object;\n"; 323 else 324 ret ~= "\t\treturn " ~ return_type.dType ~ "(_godot_object);\n"; 325 } 326 } // end normal method impl 327 328 return ret; 329 } 330 331 /// call type wrapper, "ptrcall", "callv", "callBuiltinMethod", etc... 332 string callType() const { 333 if (parent.isBuiltinClass) 334 return "callBuiltinMethod"; 335 //if (has_varargs) 336 // return "callv"; 337 return "ptrcall"; 338 } 339 340 /// formats function pointer loader, e.g. 341 /// GDNativeClassBinding.method_append.mb = _godot_api.clasdb_get_methodbind("class", "method", hash); 342 string loader() const { 343 // probably better to move in its own subclass 344 if (parent.isBuiltinClass) { 345 return format(`GDNativeClassBinding.%s.mb = _godot_api.variant_get_ptr_builtin_method(%s, "%s", %d);`, 346 wrapperIdentifier, 347 parent.name.toNativeVariantType, 348 name, 349 hash 350 ); 351 } 352 353 return format(`GDNativeClassBinding.%s.mb = _godot_api.classdb_get_method_bind("%s", "%s", %d);`, 354 wrapperIdentifier, 355 parent.name.godotType, 356 name, 357 hash, 358 ); 359 } 360 } 361 362 struct GodotArgument { 363 string name; 364 Type type; 365 366 // HACK: when godot doesn't want to specifically 367 // tell you default it leaves it empty ("default_value": "") 368 // so when asdf hits it sets default_value to [] 369 // which is the same as if it's undefined 370 @serdeOptional 371 string default_value = "\0"; 372 373 @serdeIgnore: 374 375 size_t index; 376 GodotMethod parent; 377 } 378 379 class GodotProperty { 380 string name; 381 Type type; 382 string getter, setter; 383 int index; 384 385 @serdeIgnore: 386 387 string ddoc; 388 389 string getterSource(in GodotMethod m) const { 390 string ret; 391 ret ~= "\t/**\n\t" ~ ddoc.replace("\n", "\n\t") ~ "\n\t*/\n"; 392 ret ~= "\t@property " ~ m.return_type.dType ~ " " ~ name.replace("/", "_") 393 .snakeToCamel.escapeD ~ "()\n\t{\n"; /// TODO: const? 394 ret ~= "\t\treturn " ~ getter.snakeToCamel.escapeD ~ "("; 395 if (index != -1) { 396 // add cast to enum types 397 if (m.arguments[0].type.isEnum) 398 ret ~= "cast(" ~ m.arguments[0].type.dType ~ ") "; 399 ret ~= text(index); 400 } 401 ret ~= ");\n"; 402 ret ~= "\t}\n"; 403 return ret; 404 } 405 406 string setterSource(in GodotMethod m) const { 407 string ret; 408 ret ~= "\t/// ditto\n"; 409 ret ~= "\t@property void " ~ name.replace("/", "_") 410 .snakeToCamel.escapeD ~ "(" ~ m.arguments[$ - 1].type.dType ~ " v)\n\t{\n"; 411 ret ~= "\t\t" ~ setter.snakeToCamel.escapeD ~ "("; 412 if (index != -1) { 413 // add cast to enum types 414 if (m.arguments[0].type.isEnum) 415 ret ~= "cast(" ~ m.arguments[0].type.dType ~ ") "; 416 ret ~= text(index) ~ ", "; 417 } 418 ret ~= "v);\n"; 419 ret ~= "\t}\n"; 420 return ret; 421 } 422 }