Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/modules/mono/editor/bindings_generator.cpp
10278 views
1
/**************************************************************************/
2
/* bindings_generator.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "bindings_generator.h"
32
33
#ifdef DEBUG_ENABLED
34
35
#include "../godotsharp_defs.h"
36
#include "../utils/naming_utils.h"
37
#include "../utils/path_utils.h"
38
#include "../utils/string_utils.h"
39
40
#include "core/config/engine.h"
41
#include "core/core_constants.h"
42
#include "core/io/compression.h"
43
#include "core/io/dir_access.h"
44
#include "core/io/file_access.h"
45
#include "core/os/os.h"
46
#include "main/main.h"
47
48
StringBuilder &operator<<(StringBuilder &r_sb, const String &p_string) {
49
r_sb.append(p_string);
50
return r_sb;
51
}
52
53
StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) {
54
r_sb.append(p_cstring);
55
return r_sb;
56
}
57
58
#define CS_INDENT " " // 4 whitespaces
59
60
#define INDENT1 CS_INDENT
61
#define INDENT2 INDENT1 INDENT1
62
#define INDENT3 INDENT2 INDENT1
63
#define INDENT4 INDENT3 INDENT1
64
65
#define MEMBER_BEGIN "\n" INDENT1
66
67
#define OPEN_BLOCK "{\n"
68
#define CLOSE_BLOCK "}\n"
69
70
#define OPEN_BLOCK_L1 INDENT1 OPEN_BLOCK
71
#define OPEN_BLOCK_L2 INDENT2 OPEN_BLOCK
72
#define OPEN_BLOCK_L3 INDENT3 OPEN_BLOCK
73
#define CLOSE_BLOCK_L1 INDENT1 CLOSE_BLOCK
74
#define CLOSE_BLOCK_L2 INDENT2 CLOSE_BLOCK
75
#define CLOSE_BLOCK_L3 INDENT3 CLOSE_BLOCK
76
77
#define BINDINGS_GLOBAL_SCOPE_CLASS "GD"
78
#define BINDINGS_NATIVE_NAME_FIELD "NativeName"
79
80
#define BINDINGS_CLASS_CONSTRUCTOR "Constructors"
81
#define BINDINGS_CLASS_CONSTRUCTOR_EDITOR "EditorConstructors"
82
#define BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY "BuiltInMethodConstructors"
83
84
#define CS_PARAM_MEMORYOWN "memoryOwn"
85
#define CS_PARAM_METHODBIND "method"
86
#define CS_PARAM_INSTANCE "ptr"
87
#define CS_STATIC_METHOD_GETINSTANCE "GetPtr"
88
#define CS_METHOD_CALL "Call"
89
#define CS_PROPERTY_SINGLETON "Singleton"
90
#define CS_SINGLETON_INSTANCE_SUFFIX "Instance"
91
#define CS_METHOD_INVOKE_GODOT_CLASS_METHOD "InvokeGodotClassMethod"
92
#define CS_METHOD_HAS_GODOT_CLASS_METHOD "HasGodotClassMethod"
93
#define CS_METHOD_HAS_GODOT_CLASS_SIGNAL "HasGodotClassSignal"
94
95
#define CS_STATIC_FIELD_NATIVE_CTOR "NativeCtor"
96
#define CS_STATIC_FIELD_METHOD_BIND_PREFIX "MethodBind"
97
#define CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX "MethodProxyName_"
98
#define CS_STATIC_FIELD_SIGNAL_PROXY_NAME_PREFIX "SignalProxyName_"
99
100
#define ICALL_PREFIX "godot_icall_"
101
#define ICALL_CLASSDB_GET_METHOD "ClassDB_get_method"
102
#define ICALL_CLASSDB_GET_METHOD_WITH_COMPATIBILITY "ClassDB_get_method_with_compatibility"
103
#define ICALL_CLASSDB_GET_CONSTRUCTOR "ClassDB_get_constructor"
104
105
#define C_LOCAL_RET "ret"
106
#define C_LOCAL_VARARG_RET "vararg_ret"
107
#define C_LOCAL_PTRCALL_ARGS "call_args"
108
109
#define C_CLASS_NATIVE_FUNCS "NativeFuncs"
110
#define C_NS_MONOUTILS "InteropUtils"
111
#define C_METHOD_UNMANAGED_GET_MANAGED C_NS_MONOUTILS ".UnmanagedGetManaged"
112
#define C_METHOD_ENGINE_GET_SINGLETON C_NS_MONOUTILS ".EngineGetSingleton"
113
114
#define C_NS_MONOMARSHAL "Marshaling"
115
#define C_METHOD_MONOSTR_TO_GODOT C_NS_MONOMARSHAL ".ConvertStringToNative"
116
#define C_METHOD_MONOSTR_FROM_GODOT C_NS_MONOMARSHAL ".ConvertStringToManaged"
117
#define C_METHOD_MONOARRAY_TO(m_type) C_NS_MONOMARSHAL ".ConvertSystemArrayToNative" #m_type
118
#define C_METHOD_MONOARRAY_FROM(m_type) C_NS_MONOMARSHAL ".ConvertNative" #m_type "ToSystemArray"
119
#define C_METHOD_MANAGED_TO_CALLABLE C_NS_MONOMARSHAL ".ConvertCallableToNative"
120
#define C_METHOD_MANAGED_FROM_CALLABLE C_NS_MONOMARSHAL ".ConvertCallableToManaged"
121
#define C_METHOD_MANAGED_TO_SIGNAL C_NS_MONOMARSHAL ".ConvertSignalToNative"
122
#define C_METHOD_MANAGED_FROM_SIGNAL C_NS_MONOMARSHAL ".ConvertSignalToManaged"
123
124
// Types that will be ignored by the generator and won't be available in C#.
125
// This must be kept in sync with `ignored_types` in csharp_script.cpp
126
const Vector<String> ignored_types = {};
127
128
// Special [code] keywords to wrap with <see langword="code"/> instead of <c>code</c>.
129
// Don't check against all C# reserved words, as many cases are GDScript-specific.
130
const Vector<String> langword_check = { "true", "false", "null" };
131
132
// The following properties currently need to be defined with `new` to avoid warnings. We treat
133
// them as a special case instead of silencing the warnings altogether, to be warned if more
134
// shadowing appears.
135
const Vector<String> prop_allowed_inherited_member_hiding = {
136
"ArrayMesh.BlendShapeMode",
137
"Button.TextDirection",
138
"Label.TextDirection",
139
"LineEdit.TextDirection",
140
"LinkButton.TextDirection",
141
"MenuBar.TextDirection",
142
"RichTextLabel.TextDirection",
143
"TextEdit.TextDirection",
144
"FoldableContainer.TextDirection",
145
"VisualShaderNodeReroute.PortType",
146
// The following instances are uniquely egregious violations, hiding `GetType()` from `object`.
147
// Included for the sake of CI, with the understanding that they *deserve* warnings.
148
"GltfAccessor.GetType",
149
"GltfAccessor.MethodName.GetType",
150
};
151
152
void BindingsGenerator::TypeInterface::postsetup_enum_type(BindingsGenerator::TypeInterface &r_enum_itype) {
153
// C interface for enums is the same as that of 'uint32_t'. Remember to apply
154
// any of the changes done here to the 'uint32_t' type interface as well.
155
156
r_enum_itype.cs_type = r_enum_itype.proxy_name;
157
r_enum_itype.cs_in_expr = "(int)%0";
158
r_enum_itype.cs_out = "%5return (%2)%0(%1);";
159
160
{
161
// The expected types for parameters and return value in ptrcall are 'int64_t' or 'uint64_t'.
162
r_enum_itype.c_in = "%5%0 %1_in = %1;\n";
163
r_enum_itype.c_out = "%5return (%0)(%1);\n";
164
r_enum_itype.c_type = "long";
165
r_enum_itype.c_arg_in = "&%s_in";
166
}
167
r_enum_itype.c_type_in = "int";
168
r_enum_itype.c_type_out = r_enum_itype.c_type_in;
169
r_enum_itype.class_doc = &EditorHelp::get_doc_data()->class_list[r_enum_itype.proxy_name];
170
}
171
172
static String fix_doc_description(const String &p_bbcode) {
173
// This seems to be the correct way to do this. It's the same EditorHelp does.
174
175
return p_bbcode.dedent()
176
.remove_chars("\r")
177
.strip_edges();
178
}
179
180
String BindingsGenerator::bbcode_to_text(const String &p_bbcode, const TypeInterface *p_itype) {
181
// Based on the version in EditorHelp.
182
183
if (p_bbcode.is_empty()) {
184
return String();
185
}
186
187
DocTools *doc = EditorHelp::get_doc_data();
188
189
String bbcode = p_bbcode;
190
191
StringBuilder output;
192
193
List<String> tag_stack;
194
bool code_tag = false;
195
196
int pos = 0;
197
while (pos < bbcode.length()) {
198
int brk_pos = bbcode.find_char('[', pos);
199
200
if (brk_pos < 0) {
201
brk_pos = bbcode.length();
202
}
203
204
if (brk_pos > pos) {
205
String text = bbcode.substr(pos, brk_pos - pos);
206
if (code_tag || tag_stack.size() > 0) {
207
output.append("'" + text + "'");
208
} else {
209
output.append(text);
210
}
211
}
212
213
if (brk_pos == bbcode.length()) {
214
// Nothing else to add.
215
break;
216
}
217
218
int brk_end = bbcode.find_char(']', brk_pos + 1);
219
220
if (brk_end == -1) {
221
String text = bbcode.substr(brk_pos);
222
if (code_tag || tag_stack.size() > 0) {
223
output.append("'" + text + "'");
224
}
225
226
break;
227
}
228
229
String tag = bbcode.substr(brk_pos + 1, brk_end - brk_pos - 1);
230
231
if (tag.begins_with("/")) {
232
bool tag_ok = tag_stack.size() && tag_stack.front()->get() == tag.substr(1);
233
234
if (!tag_ok) {
235
output.append("]");
236
pos = brk_pos + 1;
237
continue;
238
}
239
240
tag_stack.pop_front();
241
pos = brk_end + 1;
242
code_tag = false;
243
} else if (code_tag) {
244
output.append("[");
245
pos = brk_pos + 1;
246
} else if (tag.begins_with("method ") || tag.begins_with("constructor ") || tag.begins_with("operator ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("theme_item ") || tag.begins_with("param ")) {
247
const int tag_end = tag.find_char(' ');
248
const String link_tag = tag.substr(0, tag_end);
249
const String link_target = tag.substr(tag_end + 1).lstrip(" ");
250
251
const Vector<String> link_target_parts = link_target.split(".");
252
253
if (link_target_parts.is_empty() || link_target_parts.size() > 2) {
254
ERR_PRINT("Invalid reference format: '" + tag + "'.");
255
256
output.append(tag);
257
258
pos = brk_end + 1;
259
continue;
260
}
261
262
const TypeInterface *target_itype;
263
StringName target_cname;
264
265
if (link_target_parts.size() == 2) {
266
target_itype = _get_type_or_null(TypeReference(link_target_parts[0]));
267
if (!target_itype) {
268
target_itype = _get_type_or_null(TypeReference("_" + link_target_parts[0]));
269
}
270
target_cname = link_target_parts[1];
271
} else {
272
target_itype = p_itype;
273
target_cname = link_target_parts[0];
274
}
275
276
if (link_tag == "method") {
277
_append_text_method(output, target_itype, target_cname, link_target, link_target_parts);
278
} else if (link_tag == "constructor") {
279
// TODO: Support constructors?
280
_append_text_undeclared(output, link_target);
281
} else if (link_tag == "operator") {
282
// TODO: Support operators?
283
_append_text_undeclared(output, link_target);
284
} else if (link_tag == "member") {
285
_append_text_member(output, target_itype, target_cname, link_target, link_target_parts);
286
} else if (link_tag == "signal") {
287
_append_text_signal(output, target_itype, target_cname, link_target, link_target_parts);
288
} else if (link_tag == "enum") {
289
_append_text_enum(output, target_itype, target_cname, link_target, link_target_parts);
290
} else if (link_tag == "constant") {
291
_append_text_constant(output, target_itype, target_cname, link_target, link_target_parts);
292
} else if (link_tag == "param") {
293
_append_text_param(output, link_target);
294
} else if (link_tag == "theme_item") {
295
// We do not declare theme_items in any way in C#, so there is nothing to reference.
296
_append_text_undeclared(output, link_target);
297
}
298
299
pos = brk_end + 1;
300
} else if (doc->class_list.has(tag)) {
301
if (tag == "Array" || tag == "Dictionary") {
302
output.append("'" BINDINGS_NAMESPACE_COLLECTIONS ".");
303
output.append(tag);
304
output.append("'");
305
} else if (tag == "bool" || tag == "int") {
306
output.append(tag);
307
} else if (tag == "float") {
308
output.append(
309
#ifdef REAL_T_IS_DOUBLE
310
"double"
311
#else
312
"float"
313
#endif
314
);
315
} else if (tag == "Variant") {
316
output.append("'Godot.Variant'");
317
} else if (tag == "String") {
318
output.append("string");
319
} else if (tag == "Nil") {
320
output.append("null");
321
} else if (tag.begins_with("@")) {
322
// @GlobalScope, @GDScript, etc.
323
output.append("'" + tag + "'");
324
} else if (tag == "PackedByteArray") {
325
output.append("byte[]");
326
} else if (tag == "PackedInt32Array") {
327
output.append("int[]");
328
} else if (tag == "PackedInt64Array") {
329
output.append("long[]");
330
} else if (tag == "PackedFloat32Array") {
331
output.append("float[]");
332
} else if (tag == "PackedFloat64Array") {
333
output.append("double[]");
334
} else if (tag == "PackedStringArray") {
335
output.append("string[]");
336
} else if (tag == "PackedVector2Array") {
337
output.append("'" BINDINGS_NAMESPACE ".Vector2[]'");
338
} else if (tag == "PackedVector3Array") {
339
output.append("'" BINDINGS_NAMESPACE ".Vector3[]'");
340
} else if (tag == "PackedColorArray") {
341
output.append("'" BINDINGS_NAMESPACE ".Color[]'");
342
} else if (tag == "PackedVector4Array") {
343
output.append("'" BINDINGS_NAMESPACE ".Vector4[]'");
344
} else {
345
const TypeInterface *target_itype = _get_type_or_null(TypeReference(tag));
346
347
if (!target_itype) {
348
target_itype = _get_type_or_null(TypeReference("_" + tag));
349
}
350
351
if (target_itype) {
352
output.append("'" + target_itype->proxy_name + "'");
353
} else {
354
ERR_PRINT("Cannot resolve type reference in documentation: '" + tag + "'.");
355
output.append("'" + tag + "'");
356
}
357
}
358
359
pos = brk_end + 1;
360
} else if (tag == "b") {
361
// Bold is not supported.
362
pos = brk_end + 1;
363
tag_stack.push_front(tag);
364
} else if (tag == "i") {
365
// Italic is not supported.
366
pos = brk_end + 1;
367
tag_stack.push_front(tag);
368
} else if (tag == "code" || tag.begins_with("code ")) {
369
code_tag = true;
370
pos = brk_end + 1;
371
tag_stack.push_front("code");
372
} else if (tag == "kbd") {
373
// Keyboard combinations are not supported.
374
pos = brk_end + 1;
375
tag_stack.push_front(tag);
376
} else if (tag == "center") {
377
// Center alignment is not supported.
378
pos = brk_end + 1;
379
tag_stack.push_front(tag);
380
} else if (tag == "br") {
381
// Break is not supported.
382
pos = brk_end + 1;
383
tag_stack.push_front(tag);
384
} else if (tag == "u") {
385
// Underline is not supported.
386
pos = brk_end + 1;
387
tag_stack.push_front(tag);
388
} else if (tag == "s") {
389
// Strikethrough is not supported.
390
pos = brk_end + 1;
391
tag_stack.push_front(tag);
392
} else if (tag == "url") {
393
int end = bbcode.find_char('[', brk_end);
394
if (end == -1) {
395
end = bbcode.length();
396
}
397
String url = bbcode.substr(brk_end + 1, end - brk_end - 1);
398
// Not supported. Just append the url.
399
output.append(url);
400
401
pos = brk_end + 1;
402
tag_stack.push_front(tag);
403
} else if (tag.begins_with("url=")) {
404
String url = tag.substr(4);
405
// Not supported. Just append the url.
406
output.append(url);
407
408
pos = brk_end + 1;
409
tag_stack.push_front("url");
410
} else if (tag == "img") {
411
int end = bbcode.find_char('[', brk_end);
412
if (end == -1) {
413
end = bbcode.length();
414
}
415
String image = bbcode.substr(brk_end + 1, end - brk_end - 1);
416
417
// Not supported. Just append the bbcode.
418
output.append("[img]");
419
output.append(image);
420
output.append("[/img]");
421
422
pos = end;
423
tag_stack.push_front(tag);
424
} else if (tag.begins_with("color=")) {
425
// Not supported.
426
pos = brk_end + 1;
427
tag_stack.push_front("color");
428
} else if (tag.begins_with("font=")) {
429
// Not supported.
430
pos = brk_end + 1;
431
tag_stack.push_front("font");
432
} else {
433
// Ignore unrecognized tag.
434
output.append("[");
435
pos = brk_pos + 1;
436
}
437
}
438
439
return output.as_string();
440
}
441
442
String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype, bool p_is_signal) {
443
// Based on the version in EditorHelp.
444
445
if (p_bbcode.is_empty()) {
446
return String();
447
}
448
449
DocTools *doc = EditorHelp::get_doc_data();
450
451
String bbcode = p_bbcode;
452
453
StringBuilder xml_output;
454
455
xml_output.append("<para>");
456
457
List<String> tag_stack;
458
bool code_tag = false;
459
bool line_del = false;
460
461
int pos = 0;
462
while (pos < bbcode.length()) {
463
int brk_pos = bbcode.find_char('[', pos);
464
465
if (brk_pos < 0) {
466
brk_pos = bbcode.length();
467
}
468
469
if (brk_pos > pos) {
470
if (!line_del) {
471
String text = bbcode.substr(pos, brk_pos - pos);
472
if (code_tag || tag_stack.size() > 0) {
473
xml_output.append(text.xml_escape());
474
} else {
475
Vector<String> lines = text.split("\n");
476
for (int i = 0; i < lines.size(); i++) {
477
if (i != 0) {
478
xml_output.append("<para>");
479
}
480
481
xml_output.append(lines[i].xml_escape());
482
483
if (i != lines.size() - 1) {
484
xml_output.append("</para>\n");
485
}
486
}
487
}
488
}
489
}
490
491
if (brk_pos == bbcode.length()) {
492
// Nothing else to add.
493
break;
494
}
495
496
int brk_end = bbcode.find_char(']', brk_pos + 1);
497
498
if (brk_end == -1) {
499
if (!line_del) {
500
String text = bbcode.substr(brk_pos);
501
if (code_tag || tag_stack.size() > 0) {
502
xml_output.append(text.xml_escape());
503
} else {
504
Vector<String> lines = text.split("\n");
505
for (int i = 0; i < lines.size(); i++) {
506
if (i != 0) {
507
xml_output.append("<para>");
508
}
509
510
xml_output.append(lines[i].xml_escape());
511
512
if (i != lines.size() - 1) {
513
xml_output.append("</para>\n");
514
}
515
}
516
}
517
}
518
519
break;
520
}
521
522
String tag = bbcode.substr(brk_pos + 1, brk_end - brk_pos - 1);
523
524
if (tag.begins_with("/")) {
525
bool tag_ok = tag_stack.size() && tag_stack.front()->get() == tag.substr(1);
526
527
if (!tag_ok) {
528
if (!line_del) {
529
xml_output.append("[");
530
}
531
pos = brk_pos + 1;
532
continue;
533
}
534
535
tag_stack.pop_front();
536
pos = brk_end + 1;
537
code_tag = false;
538
539
if (tag == "/url") {
540
xml_output.append("</a>");
541
} else if (tag == "/code") {
542
xml_output.append("</c>");
543
} else if (tag == "/codeblock") {
544
xml_output.append("</code>");
545
} else if (tag == "/b") {
546
xml_output.append("</b>");
547
} else if (tag == "/i") {
548
xml_output.append("</i>");
549
} else if (tag == "/csharp") {
550
xml_output.append("</code>");
551
line_del = true;
552
} else if (tag == "/codeblocks") {
553
line_del = false;
554
}
555
} else if (code_tag) {
556
xml_output.append("[");
557
pos = brk_pos + 1;
558
} else if (tag.begins_with("method ") || tag.begins_with("constructor ") || tag.begins_with("operator ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("theme_item ") || tag.begins_with("param ")) {
559
const int tag_end = tag.find_char(' ');
560
const String link_tag = tag.substr(0, tag_end);
561
const String link_target = tag.substr(tag_end + 1).lstrip(" ");
562
563
const Vector<String> link_target_parts = link_target.split(".");
564
565
if (link_target_parts.is_empty() || link_target_parts.size() > 2) {
566
ERR_PRINT("Invalid reference format: '" + tag + "'.");
567
568
xml_output.append("<c>");
569
xml_output.append(tag);
570
xml_output.append("</c>");
571
572
pos = brk_end + 1;
573
continue;
574
}
575
576
const TypeInterface *target_itype;
577
StringName target_cname;
578
579
if (link_target_parts.size() == 2) {
580
target_itype = _get_type_or_null(TypeReference(link_target_parts[0]));
581
if (!target_itype) {
582
target_itype = _get_type_or_null(TypeReference("_" + link_target_parts[0]));
583
}
584
target_cname = link_target_parts[1];
585
} else {
586
target_itype = p_itype;
587
target_cname = link_target_parts[0];
588
}
589
590
if (link_tag == "method") {
591
_append_xml_method(xml_output, target_itype, target_cname, link_target, link_target_parts, p_itype);
592
} else if (link_tag == "constructor") {
593
// TODO: Support constructors?
594
_append_xml_undeclared(xml_output, link_target);
595
} else if (link_tag == "operator") {
596
// TODO: Support operators?
597
_append_xml_undeclared(xml_output, link_target);
598
} else if (link_tag == "member") {
599
_append_xml_member(xml_output, target_itype, target_cname, link_target, link_target_parts, p_itype);
600
} else if (link_tag == "signal") {
601
_append_xml_signal(xml_output, target_itype, target_cname, link_target, link_target_parts, p_itype);
602
} else if (link_tag == "enum") {
603
_append_xml_enum(xml_output, target_itype, target_cname, link_target, link_target_parts, p_itype);
604
} else if (link_tag == "constant") {
605
_append_xml_constant(xml_output, target_itype, target_cname, link_target, link_target_parts);
606
} else if (link_tag == "param") {
607
_append_xml_param(xml_output, link_target, p_is_signal);
608
} else if (link_tag == "theme_item") {
609
// We do not declare theme_items in any way in C#, so there is nothing to reference.
610
_append_xml_undeclared(xml_output, link_target);
611
}
612
613
pos = brk_end + 1;
614
} else if (doc->class_list.has(tag)) {
615
if (tag == "Array" || tag == "Dictionary") {
616
xml_output.append("<see cref=\"" BINDINGS_NAMESPACE_COLLECTIONS ".");
617
xml_output.append(tag);
618
xml_output.append("\"/>");
619
} else if (tag == "bool" || tag == "int") {
620
xml_output.append("<see cref=\"");
621
xml_output.append(tag);
622
xml_output.append("\"/>");
623
} else if (tag == "float") {
624
xml_output.append("<see cref=\""
625
#ifdef REAL_T_IS_DOUBLE
626
"double"
627
#else
628
"float"
629
#endif
630
"\"/>");
631
} else if (tag == "Variant") {
632
xml_output.append("<see cref=\"Godot.Variant\"/>");
633
} else if (tag == "String") {
634
xml_output.append("<see cref=\"string\"/>");
635
} else if (tag == "Nil") {
636
xml_output.append("<see langword=\"null\"/>");
637
} else if (tag.begins_with("@")) {
638
// @GlobalScope, @GDScript, etc.
639
xml_output.append("<c>");
640
xml_output.append(tag);
641
xml_output.append("</c>");
642
} else if (tag == "PackedByteArray") {
643
xml_output.append("<see cref=\"byte\"/>[]");
644
} else if (tag == "PackedInt32Array") {
645
xml_output.append("<see cref=\"int\"/>[]");
646
} else if (tag == "PackedInt64Array") {
647
xml_output.append("<see cref=\"long\"/>[]");
648
} else if (tag == "PackedFloat32Array") {
649
xml_output.append("<see cref=\"float\"/>[]");
650
} else if (tag == "PackedFloat64Array") {
651
xml_output.append("<see cref=\"double\"/>[]");
652
} else if (tag == "PackedStringArray") {
653
xml_output.append("<see cref=\"string\"/>[]");
654
} else if (tag == "PackedVector2Array") {
655
xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Vector2\"/>[]");
656
} else if (tag == "PackedVector3Array") {
657
xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Vector3\"/>[]");
658
} else if (tag == "PackedColorArray") {
659
xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Color\"/>[]");
660
} else if (tag == "PackedVector4Array") {
661
xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Vector4\"/>[]");
662
} else {
663
const TypeInterface *target_itype = _get_type_or_null(TypeReference(tag));
664
665
if (!target_itype) {
666
target_itype = _get_type_or_null(TypeReference("_" + tag));
667
}
668
669
if (target_itype) {
670
if (!_validate_api_type(target_itype, p_itype)) {
671
_append_xml_undeclared(xml_output, target_itype->proxy_name);
672
} else {
673
xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");
674
xml_output.append(target_itype->proxy_name);
675
xml_output.append("\"/>");
676
}
677
} else {
678
ERR_PRINT("Cannot resolve type reference in documentation: '" + tag + "'.");
679
680
xml_output.append("<c>");
681
xml_output.append(tag);
682
xml_output.append("</c>");
683
}
684
}
685
686
pos = brk_end + 1;
687
} else if (tag == "b") {
688
xml_output.append("<b>");
689
690
pos = brk_end + 1;
691
tag_stack.push_front(tag);
692
} else if (tag == "i") {
693
xml_output.append("<i>");
694
695
pos = brk_end + 1;
696
tag_stack.push_front(tag);
697
} else if (tag == "code" || tag.begins_with("code ")) {
698
int end = bbcode.find_char('[', brk_end);
699
if (end == -1) {
700
end = bbcode.length();
701
}
702
String code = bbcode.substr(brk_end + 1, end - brk_end - 1);
703
if (langword_check.has(code)) {
704
xml_output.append("<see langword=\"");
705
xml_output.append(code);
706
xml_output.append("\"/>");
707
708
pos = brk_end + code.length() + 8;
709
} else {
710
xml_output.append("<c>");
711
712
code_tag = true;
713
pos = brk_end + 1;
714
tag_stack.push_front("code");
715
}
716
} else if (tag == "codeblock" || tag.begins_with("codeblock ")) {
717
xml_output.append("<code>");
718
719
code_tag = true;
720
pos = brk_end + 1;
721
tag_stack.push_front("codeblock");
722
} else if (tag == "codeblocks") {
723
line_del = true;
724
pos = brk_end + 1;
725
tag_stack.push_front(tag);
726
} else if (tag == "csharp" || tag.begins_with("csharp ")) {
727
xml_output.append("<code>");
728
729
line_del = false;
730
code_tag = true;
731
pos = brk_end + 1;
732
tag_stack.push_front("csharp");
733
} else if (tag == "kbd") {
734
// Keyboard combinations are not supported in xml comments.
735
pos = brk_end + 1;
736
tag_stack.push_front(tag);
737
} else if (tag == "center") {
738
// Center alignment is not supported in xml comments.
739
pos = brk_end + 1;
740
tag_stack.push_front(tag);
741
} else if (tag == "br") {
742
xml_output.append("\n"); // FIXME: Should use <para> instead. Luckily this tag isn't used for now.
743
pos = brk_end + 1;
744
} else if (tag == "u") {
745
// Underline is not supported in Rider xml comments.
746
pos = brk_end + 1;
747
tag_stack.push_front(tag);
748
} else if (tag == "s") {
749
// Strikethrough is not supported in xml comments.
750
pos = brk_end + 1;
751
tag_stack.push_front(tag);
752
} else if (tag == "url") {
753
int end = bbcode.find_char('[', brk_end);
754
if (end == -1) {
755
end = bbcode.length();
756
}
757
String url = bbcode.substr(brk_end + 1, end - brk_end - 1);
758
xml_output.append("<a href=\"");
759
xml_output.append(url);
760
xml_output.append("\">");
761
xml_output.append(url);
762
763
pos = brk_end + 1;
764
tag_stack.push_front(tag);
765
} else if (tag.begins_with("url=")) {
766
String url = tag.substr(4);
767
xml_output.append("<a href=\"");
768
xml_output.append(url);
769
xml_output.append("\">");
770
771
pos = brk_end + 1;
772
tag_stack.push_front("url");
773
} else if (tag == "img") {
774
int end = bbcode.find_char('[', brk_end);
775
if (end == -1) {
776
end = bbcode.length();
777
}
778
String image = bbcode.substr(brk_end + 1, end - brk_end - 1);
779
780
// Not supported. Just append the bbcode.
781
xml_output.append("[img]");
782
xml_output.append(image);
783
xml_output.append("[/img]");
784
785
pos = end;
786
tag_stack.push_front(tag);
787
} else if (tag.begins_with("color=")) {
788
// Not supported.
789
pos = brk_end + 1;
790
tag_stack.push_front("color");
791
} else if (tag.begins_with("font=")) {
792
// Not supported.
793
pos = brk_end + 1;
794
tag_stack.push_front("font");
795
} else {
796
if (!line_del) {
797
// Ignore unrecognized tag.
798
xml_output.append("[");
799
}
800
pos = brk_pos + 1;
801
}
802
}
803
804
xml_output.append("</para>");
805
806
return xml_output.as_string();
807
}
808
809
void BindingsGenerator::_append_text_method(StringBuilder &p_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts) {
810
if (p_link_target_parts[0] == name_cache.type_at_GlobalScope) {
811
if (OS::get_singleton()->is_stdout_verbose()) {
812
OS::get_singleton()->print("Cannot resolve @GlobalScope method reference in documentation: %s\n", p_link_target.utf8().get_data());
813
}
814
815
// TODO Map what we can
816
_append_text_undeclared(p_output, p_link_target);
817
} else if (!p_target_itype || !p_target_itype->is_object_type) {
818
if (OS::get_singleton()->is_stdout_verbose()) {
819
if (p_target_itype) {
820
OS::get_singleton()->print("Cannot resolve method reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data());
821
} else {
822
OS::get_singleton()->print("Cannot resolve type from method reference in documentation: %s\n", p_link_target.utf8().get_data());
823
}
824
}
825
826
// TODO Map what we can
827
_append_text_undeclared(p_output, p_link_target);
828
} else {
829
if (p_target_cname == "_init") {
830
// The _init method is not declared in C#, reference the constructor instead
831
p_output.append("'new " BINDINGS_NAMESPACE ".");
832
p_output.append(p_target_itype->proxy_name);
833
p_output.append("()'");
834
} else {
835
const MethodInterface *target_imethod = p_target_itype->find_method_by_name(p_target_cname);
836
837
if (target_imethod) {
838
p_output.append("'" BINDINGS_NAMESPACE ".");
839
p_output.append(p_target_itype->proxy_name);
840
p_output.append(".");
841
p_output.append(target_imethod->proxy_name);
842
p_output.append("(");
843
bool first_key = true;
844
for (const ArgumentInterface &iarg : target_imethod->arguments) {
845
const TypeInterface *arg_type = _get_type_or_null(iarg.type);
846
847
if (first_key) {
848
first_key = false;
849
} else {
850
p_output.append(", ");
851
}
852
if (!arg_type) {
853
ERR_PRINT("Cannot resolve argument type in documentation: '" + p_link_target + "'.");
854
p_output.append(iarg.type.cname);
855
continue;
856
}
857
if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {
858
p_output.append("Nullable<");
859
}
860
String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);
861
p_output.append(arg_cs_type.replacen("params ", ""));
862
if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {
863
p_output.append(">");
864
}
865
}
866
p_output.append(")'");
867
} else {
868
if (!p_target_itype->is_intentionally_ignored(p_link_target)) {
869
ERR_PRINT("Cannot resolve method reference in documentation: '" + p_link_target + "'.");
870
}
871
872
_append_text_undeclared(p_output, p_link_target);
873
}
874
}
875
}
876
}
877
878
void BindingsGenerator::_append_text_member(StringBuilder &p_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts) {
879
if (p_link_target.contains_char('/')) {
880
// Properties with '/' (slash) in the name are not declared in C#, so there is nothing to reference.
881
_append_text_undeclared(p_output, p_link_target);
882
} else if (!p_target_itype || !p_target_itype->is_object_type) {
883
if (OS::get_singleton()->is_stdout_verbose()) {
884
if (p_target_itype) {
885
OS::get_singleton()->print("Cannot resolve member reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data());
886
} else {
887
OS::get_singleton()->print("Cannot resolve type from member reference in documentation: %s\n", p_link_target.utf8().get_data());
888
}
889
}
890
891
// TODO Map what we can
892
_append_text_undeclared(p_output, p_link_target);
893
} else {
894
const TypeInterface *current_itype = p_target_itype;
895
const PropertyInterface *target_iprop = nullptr;
896
897
while (target_iprop == nullptr && current_itype != nullptr) {
898
target_iprop = current_itype->find_property_by_name(p_target_cname);
899
if (target_iprop == nullptr) {
900
current_itype = _get_type_or_null(TypeReference(current_itype->base_name));
901
}
902
}
903
904
if (target_iprop) {
905
p_output.append("'" BINDINGS_NAMESPACE ".");
906
p_output.append(current_itype->proxy_name);
907
p_output.append(".");
908
p_output.append(target_iprop->proxy_name);
909
p_output.append("'");
910
} else {
911
if (!p_target_itype->is_intentionally_ignored(p_link_target)) {
912
ERR_PRINT("Cannot resolve member reference in documentation: '" + p_link_target + "'.");
913
}
914
915
_append_text_undeclared(p_output, p_link_target);
916
}
917
}
918
}
919
920
void BindingsGenerator::_append_text_signal(StringBuilder &p_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts) {
921
if (!p_target_itype || !p_target_itype->is_object_type) {
922
if (OS::get_singleton()->is_stdout_verbose()) {
923
if (p_target_itype) {
924
OS::get_singleton()->print("Cannot resolve signal reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data());
925
} else {
926
OS::get_singleton()->print("Cannot resolve type from signal reference in documentation: %s\n", p_link_target.utf8().get_data());
927
}
928
}
929
930
// TODO Map what we can
931
_append_text_undeclared(p_output, p_link_target);
932
} else {
933
const SignalInterface *target_isignal = p_target_itype->find_signal_by_name(p_target_cname);
934
935
if (target_isignal) {
936
p_output.append("'" BINDINGS_NAMESPACE ".");
937
p_output.append(p_target_itype->proxy_name);
938
p_output.append(".");
939
p_output.append(target_isignal->proxy_name);
940
p_output.append("'");
941
} else {
942
if (!p_target_itype->is_intentionally_ignored(p_link_target)) {
943
ERR_PRINT("Cannot resolve signal reference in documentation: '" + p_link_target + "'.");
944
}
945
946
_append_text_undeclared(p_output, p_link_target);
947
}
948
}
949
}
950
951
void BindingsGenerator::_append_text_enum(StringBuilder &p_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts) {
952
const StringName search_cname = !p_target_itype ? p_target_cname : StringName(p_target_itype->name + "." + (String)p_target_cname);
953
954
HashMap<StringName, TypeInterface>::ConstIterator enum_match = enum_types.find(search_cname);
955
956
if (!enum_match && search_cname != p_target_cname) {
957
enum_match = enum_types.find(p_target_cname);
958
}
959
960
if (enum_match) {
961
const TypeInterface &target_enum_itype = enum_match->value;
962
963
p_output.append("'" BINDINGS_NAMESPACE ".");
964
p_output.append(target_enum_itype.proxy_name); // Includes nesting class if any
965
p_output.append("'");
966
} else {
967
if (p_target_itype == nullptr || !p_target_itype->is_intentionally_ignored(p_link_target)) {
968
ERR_PRINT("Cannot resolve enum reference in documentation: '" + p_link_target + "'.");
969
}
970
971
_append_text_undeclared(p_output, p_link_target);
972
}
973
}
974
975
void BindingsGenerator::_append_text_constant(StringBuilder &p_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts) {
976
if (p_link_target_parts[0] == name_cache.type_at_GlobalScope) {
977
_append_text_constant_in_global_scope(p_output, p_target_cname, p_link_target);
978
} else if (!p_target_itype || !p_target_itype->is_object_type) {
979
// Search in @GlobalScope as a last resort if no class was specified
980
if (p_link_target_parts.size() == 1) {
981
_append_text_constant_in_global_scope(p_output, p_target_cname, p_link_target);
982
return;
983
}
984
985
if (OS::get_singleton()->is_stdout_verbose()) {
986
if (p_target_itype) {
987
OS::get_singleton()->print("Cannot resolve constant reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data());
988
} else {
989
OS::get_singleton()->print("Cannot resolve type from constant reference in documentation: %s\n", p_link_target.utf8().get_data());
990
}
991
}
992
993
// TODO Map what we can
994
_append_text_undeclared(p_output, p_link_target);
995
} else {
996
// Try to find the constant in the current class
997
if (p_target_itype->is_singleton_instance) {
998
// Constants and enums are declared in the static singleton class.
999
p_target_itype = &obj_types[p_target_itype->cname];
1000
}
1001
1002
const ConstantInterface *target_iconst = find_constant_by_name(p_target_cname, p_target_itype->constants);
1003
1004
if (target_iconst) {
1005
// Found constant in current class
1006
p_output.append("'" BINDINGS_NAMESPACE ".");
1007
p_output.append(p_target_itype->proxy_name);
1008
p_output.append(".");
1009
p_output.append(target_iconst->proxy_name);
1010
p_output.append("'");
1011
} else {
1012
// Try to find as enum constant in the current class
1013
const EnumInterface *target_ienum = nullptr;
1014
1015
for (const EnumInterface &ienum : p_target_itype->enums) {
1016
target_ienum = &ienum;
1017
target_iconst = find_constant_by_name(p_target_cname, target_ienum->constants);
1018
if (target_iconst) {
1019
break;
1020
}
1021
}
1022
1023
if (target_iconst) {
1024
p_output.append("'" BINDINGS_NAMESPACE ".");
1025
p_output.append(p_target_itype->proxy_name);
1026
p_output.append(".");
1027
p_output.append(target_ienum->proxy_name);
1028
p_output.append(".");
1029
p_output.append(target_iconst->proxy_name);
1030
p_output.append("'");
1031
} else if (p_link_target_parts.size() == 1) {
1032
// Also search in @GlobalScope as a last resort if no class was specified
1033
_append_text_constant_in_global_scope(p_output, p_target_cname, p_link_target);
1034
} else {
1035
if (!p_target_itype->is_intentionally_ignored(p_link_target)) {
1036
ERR_PRINT("Cannot resolve constant reference in documentation: '" + p_link_target + "'.");
1037
}
1038
1039
_append_xml_undeclared(p_output, p_link_target);
1040
}
1041
}
1042
}
1043
}
1044
1045
void BindingsGenerator::_append_text_constant_in_global_scope(StringBuilder &p_output, const String &p_target_cname, const String &p_link_target) {
1046
// Try to find as a global constant
1047
const ConstantInterface *target_iconst = find_constant_by_name(p_target_cname, global_constants);
1048
1049
if (target_iconst) {
1050
// Found global constant
1051
p_output.append("'" BINDINGS_NAMESPACE "." BINDINGS_GLOBAL_SCOPE_CLASS ".");
1052
p_output.append(target_iconst->proxy_name);
1053
p_output.append("'");
1054
} else {
1055
// Try to find as global enum constant
1056
const EnumInterface *target_ienum = nullptr;
1057
1058
for (const EnumInterface &ienum : global_enums) {
1059
target_ienum = &ienum;
1060
target_iconst = find_constant_by_name(p_target_cname, target_ienum->constants);
1061
if (target_iconst) {
1062
break;
1063
}
1064
}
1065
1066
if (target_iconst) {
1067
p_output.append("'" BINDINGS_NAMESPACE ".");
1068
p_output.append(target_ienum->proxy_name);
1069
p_output.append(".");
1070
p_output.append(target_iconst->proxy_name);
1071
p_output.append("'");
1072
} else {
1073
ERR_PRINT("Cannot resolve global constant reference in documentation: '" + p_link_target + "'.");
1074
_append_text_undeclared(p_output, p_link_target);
1075
}
1076
}
1077
}
1078
1079
void BindingsGenerator::_append_text_param(StringBuilder &p_output, const String &p_link_target) {
1080
const String link_target = snake_to_camel_case(p_link_target);
1081
p_output.append("'" + link_target + "'");
1082
}
1083
1084
void BindingsGenerator::_append_text_undeclared(StringBuilder &p_output, const String &p_link_target) {
1085
p_output.append("'" + p_link_target + "'");
1086
}
1087
1088
void BindingsGenerator::_append_xml_method(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts, const TypeInterface *p_source_itype) {
1089
if (p_link_target_parts[0] == name_cache.type_at_GlobalScope) {
1090
if (OS::get_singleton()->is_stdout_verbose()) {
1091
OS::get_singleton()->print("Cannot resolve @GlobalScope method reference in documentation: %s\n", p_link_target.utf8().get_data());
1092
}
1093
1094
// TODO Map what we can
1095
_append_xml_undeclared(p_xml_output, p_link_target);
1096
} else if (!p_target_itype || !p_target_itype->is_object_type) {
1097
if (OS::get_singleton()->is_stdout_verbose()) {
1098
if (p_target_itype) {
1099
OS::get_singleton()->print("Cannot resolve method reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data());
1100
} else {
1101
OS::get_singleton()->print("Cannot resolve type from method reference in documentation: %s\n", p_link_target.utf8().get_data());
1102
}
1103
}
1104
1105
// TODO Map what we can
1106
_append_xml_undeclared(p_xml_output, p_link_target);
1107
} else {
1108
if (p_target_cname == "_init") {
1109
// The _init method is not declared in C#, reference the constructor instead
1110
p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");
1111
p_xml_output.append(p_target_itype->proxy_name);
1112
p_xml_output.append(".");
1113
p_xml_output.append(p_target_itype->proxy_name);
1114
p_xml_output.append("()\"/>");
1115
} else {
1116
const MethodInterface *target_imethod = p_target_itype->find_method_by_name(p_target_cname);
1117
1118
if (target_imethod) {
1119
const String method_name = p_target_itype->proxy_name + "." + target_imethod->proxy_name;
1120
if (!_validate_api_type(p_target_itype, p_source_itype)) {
1121
_append_xml_undeclared(p_xml_output, method_name);
1122
} else {
1123
p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");
1124
p_xml_output.append(method_name);
1125
p_xml_output.append("(");
1126
bool first_key = true;
1127
for (const ArgumentInterface &iarg : target_imethod->arguments) {
1128
const TypeInterface *arg_type = _get_type_or_null(iarg.type);
1129
1130
if (first_key) {
1131
first_key = false;
1132
} else {
1133
p_xml_output.append(", ");
1134
}
1135
if (!arg_type) {
1136
ERR_PRINT("Cannot resolve argument type in documentation: '" + p_link_target + "'.");
1137
p_xml_output.append(iarg.type.cname);
1138
continue;
1139
}
1140
if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {
1141
p_xml_output.append("Nullable{");
1142
}
1143
String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);
1144
p_xml_output.append(arg_cs_type.replacen("<", "{").replacen(">", "}").replacen("params ", ""));
1145
if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {
1146
p_xml_output.append("}");
1147
}
1148
}
1149
p_xml_output.append(")\"/>");
1150
}
1151
} else {
1152
if (!p_target_itype->is_intentionally_ignored(p_link_target)) {
1153
ERR_PRINT("Cannot resolve method reference in documentation: '" + p_link_target + "'.");
1154
}
1155
1156
_append_xml_undeclared(p_xml_output, p_link_target);
1157
}
1158
}
1159
}
1160
}
1161
1162
void BindingsGenerator::_append_xml_member(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts, const TypeInterface *p_source_itype) {
1163
if (p_link_target.contains_char('/')) {
1164
// Properties with '/' (slash) in the name are not declared in C#, so there is nothing to reference.
1165
_append_xml_undeclared(p_xml_output, p_link_target);
1166
} else if (!p_target_itype || !p_target_itype->is_object_type) {
1167
if (OS::get_singleton()->is_stdout_verbose()) {
1168
if (p_target_itype) {
1169
OS::get_singleton()->print("Cannot resolve member reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data());
1170
} else {
1171
OS::get_singleton()->print("Cannot resolve type from member reference in documentation: %s\n", p_link_target.utf8().get_data());
1172
}
1173
}
1174
1175
// TODO Map what we can
1176
_append_xml_undeclared(p_xml_output, p_link_target);
1177
} else {
1178
const TypeInterface *current_itype = p_target_itype;
1179
const PropertyInterface *target_iprop = nullptr;
1180
1181
while (target_iprop == nullptr && current_itype != nullptr) {
1182
target_iprop = current_itype->find_property_by_name(p_target_cname);
1183
if (target_iprop == nullptr) {
1184
current_itype = _get_type_or_null(TypeReference(current_itype->base_name));
1185
}
1186
}
1187
1188
if (target_iprop) {
1189
const String member_name = current_itype->proxy_name + "." + target_iprop->proxy_name;
1190
if (!_validate_api_type(p_target_itype, p_source_itype)) {
1191
_append_xml_undeclared(p_xml_output, member_name);
1192
} else {
1193
p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");
1194
p_xml_output.append(member_name);
1195
p_xml_output.append("\"/>");
1196
}
1197
} else {
1198
if (!p_target_itype->is_intentionally_ignored(p_link_target)) {
1199
ERR_PRINT("Cannot resolve member reference in documentation: '" + p_link_target + "'.");
1200
}
1201
1202
_append_xml_undeclared(p_xml_output, p_link_target);
1203
}
1204
}
1205
}
1206
1207
void BindingsGenerator::_append_xml_signal(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts, const TypeInterface *p_source_itype) {
1208
if (!p_target_itype || !p_target_itype->is_object_type) {
1209
if (OS::get_singleton()->is_stdout_verbose()) {
1210
if (p_target_itype) {
1211
OS::get_singleton()->print("Cannot resolve signal reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data());
1212
} else {
1213
OS::get_singleton()->print("Cannot resolve type from signal reference in documentation: %s\n", p_link_target.utf8().get_data());
1214
}
1215
}
1216
1217
// TODO Map what we can
1218
_append_xml_undeclared(p_xml_output, p_link_target);
1219
} else {
1220
const SignalInterface *target_isignal = p_target_itype->find_signal_by_name(p_target_cname);
1221
1222
if (target_isignal) {
1223
const String signal_name = p_target_itype->proxy_name + "." + target_isignal->proxy_name;
1224
if (!_validate_api_type(p_target_itype, p_source_itype)) {
1225
_append_xml_undeclared(p_xml_output, signal_name);
1226
} else {
1227
p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");
1228
p_xml_output.append(signal_name);
1229
p_xml_output.append("\"/>");
1230
}
1231
} else {
1232
if (!p_target_itype->is_intentionally_ignored(p_link_target)) {
1233
ERR_PRINT("Cannot resolve signal reference in documentation: '" + p_link_target + "'.");
1234
}
1235
1236
_append_xml_undeclared(p_xml_output, p_link_target);
1237
}
1238
}
1239
}
1240
1241
void BindingsGenerator::_append_xml_enum(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts, const TypeInterface *p_source_itype) {
1242
const StringName search_cname = !p_target_itype ? p_target_cname : StringName(p_target_itype->name + "." + (String)p_target_cname);
1243
1244
HashMap<StringName, TypeInterface>::ConstIterator enum_match = enum_types.find(search_cname);
1245
1246
if (!enum_match && search_cname != p_target_cname) {
1247
enum_match = enum_types.find(p_target_cname);
1248
}
1249
1250
if (enum_match) {
1251
const TypeInterface &target_enum_itype = enum_match->value;
1252
1253
if (!_validate_api_type(p_target_itype, p_source_itype)) {
1254
_append_xml_undeclared(p_xml_output, target_enum_itype.proxy_name);
1255
} else {
1256
p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");
1257
p_xml_output.append(target_enum_itype.proxy_name); // Includes nesting class if any
1258
p_xml_output.append("\"/>");
1259
}
1260
} else {
1261
if (p_target_itype == nullptr || !p_target_itype->is_intentionally_ignored(p_link_target)) {
1262
ERR_PRINT("Cannot resolve enum reference in documentation: '" + p_link_target + "'.");
1263
}
1264
1265
_append_xml_undeclared(p_xml_output, p_link_target);
1266
}
1267
}
1268
1269
void BindingsGenerator::_append_xml_constant(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts) {
1270
if (p_link_target_parts[0] == name_cache.type_at_GlobalScope) {
1271
_append_xml_constant_in_global_scope(p_xml_output, p_target_cname, p_link_target);
1272
} else if (!p_target_itype || !p_target_itype->is_object_type) {
1273
// Search in @GlobalScope as a last resort if no class was specified
1274
if (p_link_target_parts.size() == 1) {
1275
_append_xml_constant_in_global_scope(p_xml_output, p_target_cname, p_link_target);
1276
return;
1277
}
1278
1279
if (OS::get_singleton()->is_stdout_verbose()) {
1280
if (p_target_itype) {
1281
OS::get_singleton()->print("Cannot resolve constant reference for non-GodotObject type in documentation: %s\n", p_link_target.utf8().get_data());
1282
} else {
1283
OS::get_singleton()->print("Cannot resolve type from constant reference in documentation: %s\n", p_link_target.utf8().get_data());
1284
}
1285
}
1286
1287
// TODO Map what we can
1288
_append_xml_undeclared(p_xml_output, p_link_target);
1289
} else {
1290
// Try to find the constant in the current class
1291
if (p_target_itype->is_singleton_instance) {
1292
// Constants and enums are declared in the static singleton class.
1293
p_target_itype = &obj_types[p_target_itype->cname];
1294
}
1295
1296
const ConstantInterface *target_iconst = find_constant_by_name(p_target_cname, p_target_itype->constants);
1297
1298
if (target_iconst) {
1299
// Found constant in current class
1300
p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");
1301
p_xml_output.append(p_target_itype->proxy_name);
1302
p_xml_output.append(".");
1303
p_xml_output.append(target_iconst->proxy_name);
1304
p_xml_output.append("\"/>");
1305
} else {
1306
// Try to find as enum constant in the current class
1307
const EnumInterface *target_ienum = nullptr;
1308
1309
for (const EnumInterface &ienum : p_target_itype->enums) {
1310
target_ienum = &ienum;
1311
target_iconst = find_constant_by_name(p_target_cname, target_ienum->constants);
1312
if (target_iconst) {
1313
break;
1314
}
1315
}
1316
1317
if (target_iconst) {
1318
p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");
1319
p_xml_output.append(p_target_itype->proxy_name);
1320
p_xml_output.append(".");
1321
p_xml_output.append(target_ienum->proxy_name);
1322
p_xml_output.append(".");
1323
p_xml_output.append(target_iconst->proxy_name);
1324
p_xml_output.append("\"/>");
1325
} else if (p_link_target_parts.size() == 1) {
1326
// Also search in @GlobalScope as a last resort if no class was specified
1327
_append_xml_constant_in_global_scope(p_xml_output, p_target_cname, p_link_target);
1328
} else {
1329
if (!p_target_itype->is_intentionally_ignored(p_link_target)) {
1330
ERR_PRINT("Cannot resolve constant reference in documentation: '" + p_link_target + "'.");
1331
}
1332
1333
_append_xml_undeclared(p_xml_output, p_link_target);
1334
}
1335
}
1336
}
1337
}
1338
1339
void BindingsGenerator::_append_xml_constant_in_global_scope(StringBuilder &p_xml_output, const String &p_target_cname, const String &p_link_target) {
1340
// Try to find as a global constant
1341
const ConstantInterface *target_iconst = find_constant_by_name(p_target_cname, global_constants);
1342
1343
if (target_iconst) {
1344
// Found global constant
1345
p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE "." BINDINGS_GLOBAL_SCOPE_CLASS ".");
1346
p_xml_output.append(target_iconst->proxy_name);
1347
p_xml_output.append("\"/>");
1348
} else {
1349
// Try to find as global enum constant
1350
const EnumInterface *target_ienum = nullptr;
1351
1352
for (const EnumInterface &ienum : global_enums) {
1353
target_ienum = &ienum;
1354
target_iconst = find_constant_by_name(p_target_cname, target_ienum->constants);
1355
if (target_iconst) {
1356
break;
1357
}
1358
}
1359
1360
if (target_iconst) {
1361
p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".");
1362
p_xml_output.append(target_ienum->proxy_name);
1363
p_xml_output.append(".");
1364
p_xml_output.append(target_iconst->proxy_name);
1365
p_xml_output.append("\"/>");
1366
} else {
1367
ERR_PRINT("Cannot resolve global constant reference in documentation: '" + p_link_target + "'.");
1368
_append_xml_undeclared(p_xml_output, p_link_target);
1369
}
1370
}
1371
}
1372
1373
void BindingsGenerator::_append_xml_param(StringBuilder &p_xml_output, const String &p_link_target, bool p_is_signal) {
1374
const String link_target = snake_to_camel_case(p_link_target);
1375
1376
if (!p_is_signal) {
1377
p_xml_output.append("<paramref name=\"");
1378
p_xml_output.append(link_target);
1379
p_xml_output.append("\"/>");
1380
} else {
1381
// Documentation in C# is added to an event, not the delegate itself;
1382
// as such, we treat these parameters as codeblocks instead.
1383
// See: https://github.com/godotengine/godot/pull/65529
1384
_append_xml_undeclared(p_xml_output, link_target);
1385
}
1386
}
1387
1388
void BindingsGenerator::_append_xml_undeclared(StringBuilder &p_xml_output, const String &p_link_target) {
1389
p_xml_output.append("<c>");
1390
p_xml_output.append(p_link_target);
1391
p_xml_output.append("</c>");
1392
}
1393
1394
bool BindingsGenerator::_validate_api_type(const TypeInterface *p_target_itype, const TypeInterface *p_source_itype) {
1395
static constexpr const char *api_types[5] = {
1396
"Core",
1397
"Editor",
1398
"Extension",
1399
"Editor Extension",
1400
"None",
1401
};
1402
1403
const ClassDB::APIType target_api = p_target_itype ? p_target_itype->api_type : ClassDB::API_NONE;
1404
ERR_FAIL_INDEX_V((int)target_api, 5, false);
1405
const ClassDB::APIType source_api = p_source_itype ? p_source_itype->api_type : ClassDB::API_NONE;
1406
ERR_FAIL_INDEX_V((int)source_api, 5, false);
1407
bool validate = false;
1408
1409
switch (target_api) {
1410
case ClassDB::API_NONE:
1411
case ClassDB::API_CORE:
1412
default:
1413
validate = true;
1414
break;
1415
case ClassDB::API_EDITOR:
1416
validate = source_api == ClassDB::API_EDITOR || source_api == ClassDB::API_EDITOR_EXTENSION;
1417
break;
1418
case ClassDB::API_EXTENSION:
1419
validate = source_api == ClassDB::API_EXTENSION || source_api == ClassDB::API_EDITOR_EXTENSION;
1420
break;
1421
case ClassDB::API_EDITOR_EXTENSION:
1422
validate = source_api == ClassDB::API_EDITOR_EXTENSION;
1423
break;
1424
}
1425
if (!validate) {
1426
const String target_name = p_target_itype ? p_target_itype->proxy_name : "@GlobalScope";
1427
const String source_name = p_source_itype ? p_source_itype->proxy_name : "@GlobalScope";
1428
WARN_PRINT(vformat("Type '%s' has API level '%s'; it cannot be referenced by type '%s' with API level '%s'.",
1429
target_name, api_types[target_api], source_name, api_types[source_api]));
1430
}
1431
return validate;
1432
}
1433
1434
int BindingsGenerator::_determine_enum_prefix(const EnumInterface &p_ienum) {
1435
CRASH_COND(p_ienum.constants.is_empty());
1436
1437
const ConstantInterface &front_iconstant = p_ienum.constants.front()->get();
1438
Vector<String> front_parts = front_iconstant.name.split("_", /* p_allow_empty: */ true);
1439
int candidate_len = front_parts.size() - 1;
1440
1441
if (candidate_len == 0) {
1442
return 0;
1443
}
1444
1445
for (const ConstantInterface &iconstant : p_ienum.constants) {
1446
Vector<String> parts = iconstant.name.split("_", /* p_allow_empty: */ true);
1447
1448
int i;
1449
for (i = 0; i < candidate_len && i < parts.size(); i++) {
1450
if (front_parts[i] != parts[i]) {
1451
// HARDCODED: Some Flag enums have the prefix 'FLAG_' for everything except 'FLAGS_DEFAULT' (same for 'METHOD_FLAG_' and'METHOD_FLAGS_DEFAULT').
1452
bool hardcoded_exc = (i == candidate_len - 1 && ((front_parts[i] == "FLAGS" && parts[i] == "FLAG") || (front_parts[i] == "FLAG" && parts[i] == "FLAGS")));
1453
if (!hardcoded_exc) {
1454
break;
1455
}
1456
}
1457
}
1458
candidate_len = i;
1459
1460
if (candidate_len == 0) {
1461
return 0;
1462
}
1463
}
1464
1465
return candidate_len;
1466
}
1467
1468
void BindingsGenerator::_apply_prefix_to_enum_constants(BindingsGenerator::EnumInterface &p_ienum, int p_prefix_length) {
1469
if (p_prefix_length > 0) {
1470
for (ConstantInterface &iconstant : p_ienum.constants) {
1471
int curr_prefix_length = p_prefix_length;
1472
1473
String constant_name = iconstant.name;
1474
1475
Vector<String> parts = constant_name.split("_", /* p_allow_empty: */ true);
1476
1477
if (parts.size() <= curr_prefix_length) {
1478
continue;
1479
}
1480
1481
if (is_digit(parts[curr_prefix_length][0])) {
1482
// The name of enum constants may begin with a numeric digit when strip from the enum prefix,
1483
// so we make the prefix for this constant one word shorter in those cases.
1484
for (curr_prefix_length = curr_prefix_length - 1; curr_prefix_length > 0; curr_prefix_length--) {
1485
if (!is_digit(parts[curr_prefix_length][0])) {
1486
break;
1487
}
1488
}
1489
}
1490
1491
constant_name = "";
1492
for (int i = curr_prefix_length; i < parts.size(); i++) {
1493
if (i > curr_prefix_length) {
1494
constant_name += "_";
1495
}
1496
constant_name += parts[i];
1497
}
1498
1499
iconstant.proxy_name = snake_to_pascal_case(constant_name, true);
1500
}
1501
}
1502
}
1503
1504
Error BindingsGenerator::_populate_method_icalls_table(const TypeInterface &p_itype) {
1505
for (const MethodInterface &imethod : p_itype.methods) {
1506
if (imethod.is_virtual) {
1507
continue;
1508
}
1509
1510
const TypeInterface *return_type = _get_type_or_null(imethod.return_type);
1511
ERR_FAIL_NULL_V_MSG(return_type, ERR_BUG, "Return type '" + imethod.return_type.cname + "' was not found.");
1512
1513
String im_unique_sig = get_ret_unique_sig(return_type) + ",CallMethodBind";
1514
1515
if (!imethod.is_static) {
1516
im_unique_sig += ",CallInstance";
1517
}
1518
1519
// Get arguments information
1520
for (const ArgumentInterface &iarg : imethod.arguments) {
1521
const TypeInterface *arg_type = _get_type_or_null(iarg.type);
1522
ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");
1523
1524
im_unique_sig += ",";
1525
im_unique_sig += get_arg_unique_sig(*arg_type);
1526
}
1527
1528
// godot_icall_{argc}_{icallcount}
1529
String icall_method = ICALL_PREFIX;
1530
icall_method += itos(imethod.arguments.size());
1531
icall_method += "_";
1532
icall_method += itos(method_icalls.size());
1533
1534
InternalCall im_icall = InternalCall(p_itype.api_type, icall_method, im_unique_sig);
1535
1536
im_icall.is_vararg = imethod.is_vararg;
1537
im_icall.is_static = imethod.is_static;
1538
im_icall.return_type = imethod.return_type;
1539
1540
for (const List<ArgumentInterface>::Element *F = imethod.arguments.front(); F; F = F->next()) {
1541
im_icall.argument_types.push_back(F->get().type);
1542
}
1543
1544
List<InternalCall>::Element *match = method_icalls.find(im_icall);
1545
1546
if (match) {
1547
if (p_itype.api_type != ClassDB::API_EDITOR) {
1548
match->get().editor_only = false;
1549
}
1550
method_icalls_map.insert(&imethod, &match->get());
1551
} else {
1552
List<InternalCall>::Element *added = method_icalls.push_back(im_icall);
1553
method_icalls_map.insert(&imethod, &added->get());
1554
}
1555
}
1556
1557
return OK;
1558
}
1559
1560
void BindingsGenerator::_generate_array_extensions(StringBuilder &p_output) {
1561
p_output.append("namespace " BINDINGS_NAMESPACE ";\n\n");
1562
p_output.append("using System;\n\n");
1563
// The class where we put the extensions doesn't matter, so just use "GD".
1564
p_output.append("public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n{");
1565
1566
#define ARRAY_IS_EMPTY(m_type) \
1567
p_output.append("\n" INDENT1 "/// <summary>\n"); \
1568
p_output.append(INDENT1 "/// Returns true if this " #m_type " array is empty or doesn't exist.\n"); \
1569
p_output.append(INDENT1 "/// </summary>\n"); \
1570
p_output.append(INDENT1 "/// <param name=\"instance\">The " #m_type " array check.</param>\n"); \
1571
p_output.append(INDENT1 "/// <returns>Whether or not the array is empty.</returns>\n"); \
1572
p_output.append(INDENT1 "public static bool IsEmpty(this " #m_type "[] instance)\n"); \
1573
p_output.append(OPEN_BLOCK_L1); \
1574
p_output.append(INDENT2 "return instance == null || instance.Length == 0;\n"); \
1575
p_output.append(INDENT1 CLOSE_BLOCK);
1576
1577
#define ARRAY_JOIN(m_type) \
1578
p_output.append("\n" INDENT1 "/// <summary>\n"); \
1579
p_output.append(INDENT1 "/// Converts this " #m_type " array to a string delimited by the given string.\n"); \
1580
p_output.append(INDENT1 "/// </summary>\n"); \
1581
p_output.append(INDENT1 "/// <param name=\"instance\">The " #m_type " array to convert.</param>\n"); \
1582
p_output.append(INDENT1 "/// <param name=\"delimiter\">The delimiter to use between items.</param>\n"); \
1583
p_output.append(INDENT1 "/// <returns>A single string with all items.</returns>\n"); \
1584
p_output.append(INDENT1 "public static string Join(this " #m_type "[] instance, string delimiter = \", \")\n"); \
1585
p_output.append(OPEN_BLOCK_L1); \
1586
p_output.append(INDENT2 "return String.Join(delimiter, instance);\n"); \
1587
p_output.append(INDENT1 CLOSE_BLOCK);
1588
1589
#define ARRAY_STRINGIFY(m_type) \
1590
p_output.append("\n" INDENT1 "/// <summary>\n"); \
1591
p_output.append(INDENT1 "/// Converts this " #m_type " array to a string with brackets.\n"); \
1592
p_output.append(INDENT1 "/// </summary>\n"); \
1593
p_output.append(INDENT1 "/// <param name=\"instance\">The " #m_type " array to convert.</param>\n"); \
1594
p_output.append(INDENT1 "/// <returns>A single string with all items.</returns>\n"); \
1595
p_output.append(INDENT1 "public static string Stringify(this " #m_type "[] instance)\n"); \
1596
p_output.append(OPEN_BLOCK_L1); \
1597
p_output.append(INDENT2 "return \"[\" + instance.Join() + \"]\";\n"); \
1598
p_output.append(INDENT1 CLOSE_BLOCK);
1599
1600
#define ARRAY_ALL(m_type) \
1601
ARRAY_IS_EMPTY(m_type) \
1602
ARRAY_JOIN(m_type) \
1603
ARRAY_STRINGIFY(m_type)
1604
1605
ARRAY_ALL(byte);
1606
ARRAY_ALL(int);
1607
ARRAY_ALL(long);
1608
ARRAY_ALL(float);
1609
ARRAY_ALL(double);
1610
ARRAY_ALL(string);
1611
ARRAY_ALL(Color);
1612
ARRAY_ALL(Vector2);
1613
ARRAY_ALL(Vector2I);
1614
ARRAY_ALL(Vector3);
1615
ARRAY_ALL(Vector3I);
1616
ARRAY_ALL(Vector4);
1617
ARRAY_ALL(Vector4I);
1618
1619
#undef ARRAY_ALL
1620
#undef ARRAY_IS_EMPTY
1621
#undef ARRAY_JOIN
1622
#undef ARRAY_STRINGIFY
1623
1624
p_output.append(CLOSE_BLOCK); // End of GD class.
1625
}
1626
1627
void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
1628
// Constants (in partial GD class)
1629
1630
p_output.append("namespace " BINDINGS_NAMESPACE ";\n\n");
1631
1632
p_output.append("public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n" OPEN_BLOCK);
1633
1634
for (const ConstantInterface &iconstant : global_constants) {
1635
if (iconstant.const_doc && iconstant.const_doc->description.size()) {
1636
String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), nullptr);
1637
Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
1638
1639
if (summary_lines.size()) {
1640
p_output.append(MEMBER_BEGIN "/// <summary>\n");
1641
1642
for (int i = 0; i < summary_lines.size(); i++) {
1643
p_output.append(INDENT1 "/// ");
1644
p_output.append(summary_lines[i]);
1645
p_output.append("\n");
1646
}
1647
1648
p_output.append(INDENT1 "/// </summary>");
1649
}
1650
}
1651
1652
p_output.append(MEMBER_BEGIN "public const long ");
1653
p_output.append(iconstant.proxy_name);
1654
p_output.append(" = ");
1655
p_output.append(itos(iconstant.value));
1656
p_output.append(";");
1657
}
1658
1659
if (!global_constants.is_empty()) {
1660
p_output.append("\n");
1661
}
1662
1663
p_output.append(CLOSE_BLOCK); // end of GD class
1664
1665
// Enums
1666
1667
for (const EnumInterface &ienum : global_enums) {
1668
CRASH_COND(ienum.constants.is_empty());
1669
1670
String enum_proxy_name = ienum.proxy_name;
1671
1672
bool enum_in_static_class = false;
1673
1674
if (enum_proxy_name.find_char('.') > 0) {
1675
enum_in_static_class = true;
1676
String enum_class_name = enum_proxy_name.get_slicec('.', 0);
1677
enum_proxy_name = enum_proxy_name.get_slicec('.', 1);
1678
1679
CRASH_COND(enum_class_name != "Variant"); // Hard-coded...
1680
1681
_log("Declaring global enum '%s' inside struct '%s'\n", enum_proxy_name.utf8().get_data(), enum_class_name.utf8().get_data());
1682
1683
p_output << "\npublic partial struct " << enum_class_name << "\n" OPEN_BLOCK;
1684
}
1685
1686
const String maybe_indent = !enum_in_static_class ? "" : INDENT1;
1687
1688
if (ienum.is_flags) {
1689
p_output << "\n"
1690
<< maybe_indent << "[System.Flags]";
1691
}
1692
1693
p_output << "\n"
1694
<< maybe_indent << "public enum " << enum_proxy_name << " : long"
1695
<< "\n"
1696
<< maybe_indent << OPEN_BLOCK;
1697
1698
for (const ConstantInterface &iconstant : ienum.constants) {
1699
if (iconstant.const_doc && iconstant.const_doc->description.size()) {
1700
String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), nullptr);
1701
Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
1702
1703
if (summary_lines.size()) {
1704
p_output << maybe_indent << INDENT1 "/// <summary>\n";
1705
1706
for (int i = 0; i < summary_lines.size(); i++) {
1707
p_output << maybe_indent << INDENT1 "/// " << summary_lines[i] << "\n";
1708
}
1709
1710
p_output << maybe_indent << INDENT1 "/// </summary>\n";
1711
}
1712
}
1713
1714
p_output << maybe_indent << INDENT1
1715
<< iconstant.proxy_name
1716
<< " = "
1717
<< itos(iconstant.value)
1718
<< ",\n";
1719
}
1720
1721
p_output << maybe_indent << CLOSE_BLOCK;
1722
1723
if (enum_in_static_class) {
1724
p_output << CLOSE_BLOCK;
1725
}
1726
}
1727
}
1728
1729
Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir) {
1730
ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);
1731
1732
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
1733
ERR_FAIL_COND_V(da.is_null(), ERR_CANT_CREATE);
1734
1735
if (!DirAccess::exists(p_proj_dir)) {
1736
Error err = da->make_dir_recursive(p_proj_dir);
1737
ERR_FAIL_COND_V_MSG(err != OK, ERR_CANT_CREATE, "Cannot create directory '" + p_proj_dir + "'.");
1738
}
1739
1740
da->change_dir(p_proj_dir);
1741
da->make_dir("Generated");
1742
da->make_dir("Generated/GodotObjects");
1743
1744
String base_gen_dir = Path::join(p_proj_dir, "Generated");
1745
String godot_objects_gen_dir = Path::join(base_gen_dir, "GodotObjects");
1746
1747
Vector<String> compile_items;
1748
1749
// Generate source file for global scope constants and enums
1750
{
1751
StringBuilder constants_source;
1752
_generate_global_constants(constants_source);
1753
String output_file = Path::join(base_gen_dir, BINDINGS_GLOBAL_SCOPE_CLASS "_constants.cs");
1754
Error save_err = _save_file(output_file, constants_source);
1755
if (save_err != OK) {
1756
return save_err;
1757
}
1758
1759
compile_items.push_back(output_file);
1760
}
1761
1762
// Generate source file for array extensions
1763
{
1764
StringBuilder extensions_source;
1765
_generate_array_extensions(extensions_source);
1766
String output_file = Path::join(base_gen_dir, BINDINGS_GLOBAL_SCOPE_CLASS "_extensions.cs");
1767
Error save_err = _save_file(output_file, extensions_source);
1768
if (save_err != OK) {
1769
return save_err;
1770
}
1771
1772
compile_items.push_back(output_file);
1773
}
1774
1775
for (const KeyValue<StringName, TypeInterface> &E : obj_types) {
1776
const TypeInterface &itype = E.value;
1777
1778
if (itype.api_type == ClassDB::API_EDITOR) {
1779
continue;
1780
}
1781
1782
String output_file = Path::join(godot_objects_gen_dir, itype.proxy_name + ".cs");
1783
Error err = _generate_cs_type(itype, output_file);
1784
1785
if (err == ERR_SKIP) {
1786
continue;
1787
}
1788
1789
if (err != OK) {
1790
return err;
1791
}
1792
1793
compile_items.push_back(output_file);
1794
}
1795
1796
// Generate source file for built-in type constructor dictionary.
1797
1798
{
1799
StringBuilder cs_built_in_ctors_content;
1800
1801
cs_built_in_ctors_content.append("namespace " BINDINGS_NAMESPACE ";\n\n");
1802
cs_built_in_ctors_content.append("using System;\n"
1803
"using System.Collections.Generic;\n"
1804
"\n");
1805
cs_built_in_ctors_content.append("internal static class " BINDINGS_CLASS_CONSTRUCTOR "\n{");
1806
1807
cs_built_in_ctors_content.append(MEMBER_BEGIN "internal static readonly Dictionary<string, Func<IntPtr, GodotObject>> " BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY ";\n");
1808
1809
cs_built_in_ctors_content.append(MEMBER_BEGIN "public static GodotObject Invoke(string nativeTypeNameStr, IntPtr nativeObjectPtr)\n");
1810
cs_built_in_ctors_content.append(INDENT1 OPEN_BLOCK);
1811
cs_built_in_ctors_content.append(INDENT2 "if (!" BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY ".TryGetValue(nativeTypeNameStr, out var constructor))\n");
1812
cs_built_in_ctors_content.append(INDENT3 "throw new InvalidOperationException(\"Wrapper class not found for type: \" + nativeTypeNameStr);\n");
1813
cs_built_in_ctors_content.append(INDENT2 "return constructor(nativeObjectPtr);\n");
1814
cs_built_in_ctors_content.append(INDENT1 CLOSE_BLOCK);
1815
1816
cs_built_in_ctors_content.append(MEMBER_BEGIN "static " BINDINGS_CLASS_CONSTRUCTOR "()\n");
1817
cs_built_in_ctors_content.append(INDENT1 OPEN_BLOCK);
1818
cs_built_in_ctors_content.append(INDENT2 BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY " = new();\n");
1819
1820
for (const KeyValue<StringName, TypeInterface> &E : obj_types) {
1821
const TypeInterface &itype = E.value;
1822
1823
if (itype.api_type != ClassDB::API_CORE || itype.is_singleton_instance) {
1824
continue;
1825
}
1826
1827
if (itype.is_deprecated) {
1828
cs_built_in_ctors_content.append("#pragma warning disable CS0618\n");
1829
}
1830
1831
cs_built_in_ctors_content.append(INDENT2 BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY ".Add(\"");
1832
cs_built_in_ctors_content.append(itype.name);
1833
cs_built_in_ctors_content.append("\", " CS_PARAM_INSTANCE " => new ");
1834
cs_built_in_ctors_content.append(itype.proxy_name);
1835
if (itype.is_singleton && !itype.is_compat_singleton) {
1836
cs_built_in_ctors_content.append("Instance");
1837
}
1838
cs_built_in_ctors_content.append("(" CS_PARAM_INSTANCE "));\n");
1839
1840
if (itype.is_deprecated) {
1841
cs_built_in_ctors_content.append("#pragma warning restore CS0618\n");
1842
}
1843
}
1844
1845
cs_built_in_ctors_content.append(INDENT1 CLOSE_BLOCK);
1846
1847
cs_built_in_ctors_content.append(CLOSE_BLOCK);
1848
1849
String constructors_file = Path::join(base_gen_dir, BINDINGS_CLASS_CONSTRUCTOR ".cs");
1850
Error err = _save_file(constructors_file, cs_built_in_ctors_content);
1851
1852
if (err != OK) {
1853
return err;
1854
}
1855
1856
compile_items.push_back(constructors_file);
1857
}
1858
1859
// Generate native calls
1860
1861
StringBuilder cs_icalls_content;
1862
1863
cs_icalls_content.append("namespace " BINDINGS_NAMESPACE ";\n\n");
1864
cs_icalls_content.append("using System;\n"
1865
"using System.Diagnostics.CodeAnalysis;\n"
1866
"using System.Runtime.InteropServices;\n"
1867
"using Godot.NativeInterop;\n"
1868
"\n");
1869
cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\n");
1870
cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"RedundantUnsafeContext\")]\n");
1871
cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"RedundantNameQualifier\")]\n");
1872
cs_icalls_content.append("[System.Runtime.CompilerServices.SkipLocalsInit]\n");
1873
cs_icalls_content.append("internal static class " BINDINGS_CLASS_NATIVECALLS "\n{");
1874
1875
cs_icalls_content.append(MEMBER_BEGIN "internal static ulong godot_api_hash = ");
1876
cs_icalls_content.append(String::num_uint64(ClassDB::get_api_hash(ClassDB::API_CORE)) + ";\n");
1877
1878
cs_icalls_content.append(MEMBER_BEGIN "private const int VarArgsSpanThreshold = 10;\n");
1879
1880
for (const InternalCall &icall : method_icalls) {
1881
if (icall.editor_only) {
1882
continue;
1883
}
1884
Error err = _generate_cs_native_calls(icall, cs_icalls_content);
1885
if (err != OK) {
1886
return err;
1887
}
1888
}
1889
1890
cs_icalls_content.append(CLOSE_BLOCK);
1891
1892
String internal_methods_file = Path::join(base_gen_dir, BINDINGS_CLASS_NATIVECALLS ".cs");
1893
1894
Error err = _save_file(internal_methods_file, cs_icalls_content);
1895
if (err != OK) {
1896
return err;
1897
}
1898
1899
compile_items.push_back(internal_methods_file);
1900
1901
// Generate GeneratedIncludes.props
1902
1903
StringBuilder includes_props_content;
1904
includes_props_content.append("<Project>\n"
1905
" <ItemGroup>\n");
1906
1907
for (int i = 0; i < compile_items.size(); i++) {
1908
String include = Path::relative_to(compile_items[i], p_proj_dir).replace_char('/', '\\');
1909
includes_props_content.append(" <Compile Include=\"" + include + "\" />\n");
1910
}
1911
1912
includes_props_content.append(" </ItemGroup>\n"
1913
"</Project>\n");
1914
1915
String includes_props_file = Path::join(base_gen_dir, "GeneratedIncludes.props");
1916
1917
err = _save_file(includes_props_file, includes_props_content);
1918
if (err != OK) {
1919
return err;
1920
}
1921
1922
return OK;
1923
}
1924
1925
Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir) {
1926
ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);
1927
1928
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
1929
ERR_FAIL_COND_V(da.is_null(), ERR_CANT_CREATE);
1930
1931
if (!DirAccess::exists(p_proj_dir)) {
1932
Error err = da->make_dir_recursive(p_proj_dir);
1933
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
1934
}
1935
1936
da->change_dir(p_proj_dir);
1937
da->make_dir("Generated");
1938
da->make_dir("Generated/GodotObjects");
1939
1940
String base_gen_dir = Path::join(p_proj_dir, "Generated");
1941
String godot_objects_gen_dir = Path::join(base_gen_dir, "GodotObjects");
1942
1943
Vector<String> compile_items;
1944
1945
for (const KeyValue<StringName, TypeInterface> &E : obj_types) {
1946
const TypeInterface &itype = E.value;
1947
1948
if (itype.api_type != ClassDB::API_EDITOR) {
1949
continue;
1950
}
1951
1952
String output_file = Path::join(godot_objects_gen_dir, itype.proxy_name + ".cs");
1953
Error err = _generate_cs_type(itype, output_file);
1954
1955
if (err == ERR_SKIP) {
1956
continue;
1957
}
1958
1959
if (err != OK) {
1960
return err;
1961
}
1962
1963
compile_items.push_back(output_file);
1964
}
1965
1966
// Generate source file for editor type constructor dictionary.
1967
1968
{
1969
StringBuilder cs_built_in_ctors_content;
1970
1971
cs_built_in_ctors_content.append("namespace " BINDINGS_NAMESPACE ";\n\n");
1972
cs_built_in_ctors_content.append("internal static class " BINDINGS_CLASS_CONSTRUCTOR_EDITOR "\n{");
1973
1974
cs_built_in_ctors_content.append(MEMBER_BEGIN "private static void AddEditorConstructors()\n");
1975
cs_built_in_ctors_content.append(INDENT1 OPEN_BLOCK);
1976
cs_built_in_ctors_content.append(INDENT2 "var builtInMethodConstructors = " BINDINGS_CLASS_CONSTRUCTOR "." BINDINGS_CLASS_CONSTRUCTOR_DICTIONARY ";\n");
1977
1978
for (const KeyValue<StringName, TypeInterface> &E : obj_types) {
1979
const TypeInterface &itype = E.value;
1980
1981
if (itype.api_type != ClassDB::API_EDITOR || itype.is_singleton_instance) {
1982
continue;
1983
}
1984
1985
if (itype.is_deprecated) {
1986
cs_built_in_ctors_content.append("#pragma warning disable CS0618\n");
1987
}
1988
1989
cs_built_in_ctors_content.append(INDENT2 "builtInMethodConstructors.Add(\"");
1990
cs_built_in_ctors_content.append(itype.name);
1991
cs_built_in_ctors_content.append("\", " CS_PARAM_INSTANCE " => new ");
1992
cs_built_in_ctors_content.append(itype.proxy_name);
1993
if (itype.is_singleton && !itype.is_compat_singleton) {
1994
cs_built_in_ctors_content.append("Instance");
1995
}
1996
cs_built_in_ctors_content.append("(" CS_PARAM_INSTANCE "));\n");
1997
1998
if (itype.is_deprecated) {
1999
cs_built_in_ctors_content.append("#pragma warning restore CS0618\n");
2000
}
2001
}
2002
2003
cs_built_in_ctors_content.append(INDENT1 CLOSE_BLOCK);
2004
2005
cs_built_in_ctors_content.append(CLOSE_BLOCK);
2006
2007
String constructors_file = Path::join(base_gen_dir, BINDINGS_CLASS_CONSTRUCTOR_EDITOR ".cs");
2008
Error err = _save_file(constructors_file, cs_built_in_ctors_content);
2009
2010
if (err != OK) {
2011
return err;
2012
}
2013
2014
compile_items.push_back(constructors_file);
2015
}
2016
2017
// Generate native calls
2018
2019
StringBuilder cs_icalls_content;
2020
2021
cs_icalls_content.append("namespace " BINDINGS_NAMESPACE ";\n\n");
2022
cs_icalls_content.append("using System;\n"
2023
"using System.Diagnostics.CodeAnalysis;\n"
2024
"using System.Runtime.InteropServices;\n"
2025
"using Godot.NativeInterop;\n"
2026
"\n");
2027
cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\n");
2028
cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"RedundantUnsafeContext\")]\n");
2029
cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"RedundantNameQualifier\")]\n");
2030
cs_icalls_content.append("[System.Runtime.CompilerServices.SkipLocalsInit]\n");
2031
cs_icalls_content.append("internal static class " BINDINGS_CLASS_NATIVECALLS_EDITOR "\n" OPEN_BLOCK);
2032
2033
cs_icalls_content.append(INDENT1 "internal static ulong godot_api_hash = ");
2034
cs_icalls_content.append(String::num_uint64(ClassDB::get_api_hash(ClassDB::API_EDITOR)) + ";\n");
2035
2036
cs_icalls_content.append(MEMBER_BEGIN "private const int VarArgsSpanThreshold = 10;\n");
2037
2038
cs_icalls_content.append("\n");
2039
2040
for (const InternalCall &icall : method_icalls) {
2041
if (!icall.editor_only) {
2042
continue;
2043
}
2044
Error err = _generate_cs_native_calls(icall, cs_icalls_content);
2045
if (err != OK) {
2046
return err;
2047
}
2048
}
2049
2050
cs_icalls_content.append(CLOSE_BLOCK);
2051
2052
String internal_methods_file = Path::join(base_gen_dir, BINDINGS_CLASS_NATIVECALLS_EDITOR ".cs");
2053
2054
Error err = _save_file(internal_methods_file, cs_icalls_content);
2055
if (err != OK) {
2056
return err;
2057
}
2058
2059
compile_items.push_back(internal_methods_file);
2060
2061
// Generate GeneratedIncludes.props
2062
2063
StringBuilder includes_props_content;
2064
includes_props_content.append("<Project>\n"
2065
" <ItemGroup>\n");
2066
2067
for (int i = 0; i < compile_items.size(); i++) {
2068
String include = Path::relative_to(compile_items[i], p_proj_dir).replace_char('/', '\\');
2069
includes_props_content.append(" <Compile Include=\"" + include + "\" />\n");
2070
}
2071
2072
includes_props_content.append(" </ItemGroup>\n"
2073
"</Project>\n");
2074
2075
String includes_props_file = Path::join(base_gen_dir, "GeneratedIncludes.props");
2076
2077
err = _save_file(includes_props_file, includes_props_content);
2078
if (err != OK) {
2079
return err;
2080
}
2081
2082
return OK;
2083
}
2084
2085
Error BindingsGenerator::generate_cs_api(const String &p_output_dir) {
2086
ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);
2087
2088
String output_dir = Path::abspath(Path::realpath(p_output_dir));
2089
2090
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
2091
ERR_FAIL_COND_V(da.is_null(), ERR_CANT_CREATE);
2092
2093
if (!DirAccess::exists(output_dir)) {
2094
Error err = da->make_dir_recursive(output_dir);
2095
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
2096
}
2097
2098
Error proj_err;
2099
2100
// Generate GodotSharp source files
2101
2102
String core_proj_dir = output_dir.path_join(CORE_API_ASSEMBLY_NAME);
2103
2104
proj_err = generate_cs_core_project(core_proj_dir);
2105
if (proj_err != OK) {
2106
ERR_PRINT("Generation of the Core API C# project failed.");
2107
return proj_err;
2108
}
2109
2110
// Generate GodotSharpEditor source files
2111
2112
String editor_proj_dir = output_dir.path_join(EDITOR_API_ASSEMBLY_NAME);
2113
2114
proj_err = generate_cs_editor_project(editor_proj_dir);
2115
if (proj_err != OK) {
2116
ERR_PRINT("Generation of the Editor API C# project failed.");
2117
return proj_err;
2118
}
2119
2120
_log("The Godot API sources were successfully generated\n");
2121
2122
return OK;
2123
}
2124
2125
// FIXME: There are some members that hide other inherited members.
2126
// - In the case of both members being the same kind, the new one must be declared
2127
// explicitly as 'new' to avoid the warning (and we must print a message about it).
2128
// - In the case of both members being of a different kind, then the new one must
2129
// be renamed to avoid the name collision (and we must print a warning about it).
2130
// - Csc warning e.g.:
2131
// ObjectType/LineEdit.cs(140,38): warning CS0108: 'LineEdit.FocusMode' hides inherited member 'Control.FocusMode'. Use the new keyword if hiding was intended.
2132
Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const String &p_output_file) {
2133
CRASH_COND(!itype.is_object_type);
2134
2135
bool is_derived_type = itype.base_name != StringName();
2136
2137
if (!is_derived_type) {
2138
// Some GodotObject assertions
2139
CRASH_COND(itype.cname != name_cache.type_Object);
2140
CRASH_COND(!itype.is_instantiable);
2141
CRASH_COND(itype.api_type != ClassDB::API_CORE);
2142
CRASH_COND(itype.is_ref_counted);
2143
CRASH_COND(itype.is_singleton);
2144
}
2145
2146
_log("Generating %s.cs...\n", itype.proxy_name.utf8().get_data());
2147
2148
StringBuilder output;
2149
2150
output.append("namespace " BINDINGS_NAMESPACE ";\n\n");
2151
2152
output.append("using System;\n"); // IntPtr
2153
output.append("using System.ComponentModel;\n"); // EditorBrowsable
2154
output.append("using System.Diagnostics;\n"); // DebuggerBrowsable
2155
output.append("using Godot.NativeInterop;\n");
2156
2157
output.append("\n#nullable disable\n");
2158
2159
const DocData::ClassDoc *class_doc = itype.class_doc;
2160
2161
if (class_doc && class_doc->description.size()) {
2162
String xml_summary = bbcode_to_xml(fix_doc_description(class_doc->description), &itype);
2163
Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
2164
2165
if (summary_lines.size()) {
2166
output.append("/// <summary>\n");
2167
2168
for (int i = 0; i < summary_lines.size(); i++) {
2169
output.append("/// ");
2170
output.append(summary_lines[i]);
2171
output.append("\n");
2172
}
2173
2174
output.append("/// </summary>\n");
2175
}
2176
}
2177
2178
if (itype.is_deprecated) {
2179
output.append("[Obsolete(\"");
2180
output.append(bbcode_to_text(itype.deprecation_message, &itype));
2181
output.append("\")]\n");
2182
}
2183
2184
// We generate a `GodotClassName` attribute if the engine class name is not the same as the
2185
// generated C# class name. This allows introspection code to find the name associated with
2186
// the class. If the attribute is not present, the C# class name can be used instead.
2187
if (itype.name != itype.proxy_name) {
2188
output << "[GodotClassName(\"" << itype.name << "\")]\n";
2189
}
2190
2191
output.append("public ");
2192
if (itype.is_singleton) {
2193
output.append("static partial class ");
2194
} else {
2195
// Even if the class is not instantiable, we can't declare it abstract because
2196
// the engine can still instantiate them and return them via the scripting API.
2197
// Example: `SceneTreeTimer` returned from `SceneTree.create_timer`.
2198
// See the reverted commit: ef5672d3f94a7321ed779c922088bb72adbb1521
2199
output.append("partial class ");
2200
}
2201
output.append(itype.proxy_name);
2202
2203
if (is_derived_type && !itype.is_singleton) {
2204
if (obj_types.has(itype.base_name)) {
2205
TypeInterface base_type = obj_types[itype.base_name];
2206
output.append(" : ");
2207
output.append(base_type.proxy_name);
2208
if (base_type.is_singleton) {
2209
// If the type is a singleton, use the instance type.
2210
output.append(CS_SINGLETON_INSTANCE_SUFFIX);
2211
}
2212
} else {
2213
ERR_PRINT("Base type '" + itype.base_name.operator String() + "' does not exist, for class '" + itype.name + "'.");
2214
return ERR_INVALID_DATA;
2215
}
2216
}
2217
2218
output.append("\n{");
2219
2220
// Add constants
2221
2222
for (const ConstantInterface &iconstant : itype.constants) {
2223
if (iconstant.const_doc && iconstant.const_doc->description.size()) {
2224
String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), &itype);
2225
Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
2226
2227
if (summary_lines.size()) {
2228
output.append(MEMBER_BEGIN "/// <summary>\n");
2229
2230
for (int i = 0; i < summary_lines.size(); i++) {
2231
output.append(INDENT1 "/// ");
2232
output.append(summary_lines[i]);
2233
output.append("\n");
2234
}
2235
2236
output.append(INDENT1 "/// </summary>");
2237
}
2238
}
2239
2240
if (iconstant.is_deprecated) {
2241
output.append(MEMBER_BEGIN "[Obsolete(\"");
2242
output.append(bbcode_to_text(iconstant.deprecation_message, &itype));
2243
output.append("\")]");
2244
}
2245
2246
output.append(MEMBER_BEGIN "public const long ");
2247
output.append(iconstant.proxy_name);
2248
output.append(" = ");
2249
output.append(itos(iconstant.value));
2250
output.append(";");
2251
}
2252
2253
if (itype.constants.size()) {
2254
output.append("\n");
2255
}
2256
2257
// Add enums
2258
2259
for (const EnumInterface &ienum : itype.enums) {
2260
ERR_FAIL_COND_V(ienum.constants.is_empty(), ERR_BUG);
2261
2262
if (ienum.is_flags) {
2263
output.append(MEMBER_BEGIN "[System.Flags]");
2264
}
2265
2266
output.append(MEMBER_BEGIN "public enum ");
2267
output.append(ienum.proxy_name);
2268
output.append(" : long");
2269
output.append(MEMBER_BEGIN OPEN_BLOCK);
2270
2271
const ConstantInterface &last = ienum.constants.back()->get();
2272
for (const ConstantInterface &iconstant : ienum.constants) {
2273
if (iconstant.const_doc && iconstant.const_doc->description.size()) {
2274
String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), &itype);
2275
Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
2276
2277
if (summary_lines.size()) {
2278
output.append(INDENT2 "/// <summary>\n");
2279
2280
for (int i = 0; i < summary_lines.size(); i++) {
2281
output.append(INDENT2 "/// ");
2282
output.append(summary_lines[i]);
2283
output.append("\n");
2284
}
2285
2286
output.append(INDENT2 "/// </summary>\n");
2287
}
2288
}
2289
2290
if (iconstant.is_deprecated) {
2291
output.append(INDENT2 "[Obsolete(\"");
2292
output.append(bbcode_to_text(iconstant.deprecation_message, &itype));
2293
output.append("\")]\n");
2294
}
2295
2296
output.append(INDENT2);
2297
output.append(iconstant.proxy_name);
2298
output.append(" = ");
2299
output.append(itos(iconstant.value));
2300
output.append(&iconstant != &last ? ",\n" : "\n");
2301
}
2302
2303
output.append(INDENT1 CLOSE_BLOCK);
2304
}
2305
2306
// Add properties
2307
2308
for (const PropertyInterface &iprop : itype.properties) {
2309
Error prop_err = _generate_cs_property(itype, iprop, output);
2310
ERR_FAIL_COND_V_MSG(prop_err != OK, prop_err,
2311
"Failed to generate property '" + iprop.cname.operator String() +
2312
"' for class '" + itype.name + "'.");
2313
}
2314
2315
// Add native name static field and cached type.
2316
2317
if (is_derived_type && !itype.is_singleton) {
2318
output << MEMBER_BEGIN "private static readonly System.Type CachedType = typeof(" << itype.proxy_name << ");\n";
2319
}
2320
2321
output.append(MEMBER_BEGIN "private static readonly StringName " BINDINGS_NATIVE_NAME_FIELD " = \"");
2322
output.append(itype.name);
2323
output.append("\";\n");
2324
2325
if (itype.is_singleton || itype.is_compat_singleton) {
2326
// Add the Singleton static property.
2327
2328
String instance_type_name;
2329
2330
if (itype.is_singleton) {
2331
StringName instance_name = itype.name + CS_SINGLETON_INSTANCE_SUFFIX;
2332
instance_type_name = obj_types.has(instance_name)
2333
? obj_types[instance_name].proxy_name
2334
: "GodotObject";
2335
} else {
2336
instance_type_name = itype.proxy_name;
2337
}
2338
2339
output.append(MEMBER_BEGIN "private static " + instance_type_name + " singleton;\n");
2340
2341
output << MEMBER_BEGIN "public static " + instance_type_name + " " CS_PROPERTY_SINGLETON " =>\n"
2342
<< INDENT2 "singleton \?\?= (" + instance_type_name + ")"
2343
<< C_METHOD_ENGINE_GET_SINGLETON "(\"" << itype.name << "\");\n";
2344
}
2345
2346
if (!itype.is_singleton) {
2347
// IMPORTANT: We also generate the static fields for GodotObject instead of declaring
2348
// them manually in the `GodotObject.base.cs` partial class declaration, because they're
2349
// required by other static fields in this generated partial class declaration.
2350
// Static fields are initialized in order of declaration, but when they're in different
2351
// partial class declarations then it becomes harder to tell (Rider warns about this).
2352
2353
if (itype.is_instantiable) {
2354
// Add native constructor static field
2355
2356
output << MEMBER_BEGIN << "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n"
2357
<< INDENT1 "private static readonly unsafe delegate* unmanaged<godot_bool, IntPtr> "
2358
<< CS_STATIC_FIELD_NATIVE_CTOR " = " ICALL_CLASSDB_GET_CONSTRUCTOR
2359
<< "(" BINDINGS_NATIVE_NAME_FIELD ");\n";
2360
}
2361
2362
if (is_derived_type) {
2363
// Add default constructor
2364
if (itype.is_instantiable) {
2365
output << MEMBER_BEGIN "public " << itype.proxy_name << "() : this("
2366
<< (itype.memory_own ? "true" : "false") << ")\n" OPEN_BLOCK_L1
2367
<< INDENT2 "unsafe\n" INDENT2 OPEN_BLOCK
2368
<< INDENT3 "ConstructAndInitialize(" CS_STATIC_FIELD_NATIVE_CTOR ", "
2369
<< BINDINGS_NATIVE_NAME_FIELD ", CachedType, refCounted: "
2370
<< (itype.is_ref_counted ? "true" : "false") << ");\n"
2371
<< CLOSE_BLOCK_L2 CLOSE_BLOCK_L1;
2372
} else {
2373
// Hide the constructor
2374
output << MEMBER_BEGIN "internal " << itype.proxy_name << "() : this("
2375
<< (itype.memory_own ? "true" : "false") << ")\n" OPEN_BLOCK_L1
2376
<< INDENT2 "unsafe\n" INDENT2 OPEN_BLOCK
2377
<< INDENT3 "ConstructAndInitialize(null, "
2378
<< BINDINGS_NATIVE_NAME_FIELD ", CachedType, refCounted: "
2379
<< (itype.is_ref_counted ? "true" : "false") << ");\n"
2380
<< CLOSE_BLOCK_L2 CLOSE_BLOCK_L1;
2381
}
2382
2383
output << MEMBER_BEGIN "internal " << itype.proxy_name << "(IntPtr " CS_PARAM_INSTANCE ") : this("
2384
<< (itype.memory_own ? "true" : "false") << ")\n" OPEN_BLOCK_L1
2385
<< INDENT2 "NativePtr = " CS_PARAM_INSTANCE ";\n"
2386
<< INDENT2 "unsafe\n" INDENT2 OPEN_BLOCK
2387
<< INDENT3 "ConstructAndInitialize(null, "
2388
<< BINDINGS_NATIVE_NAME_FIELD ", CachedType, refCounted: "
2389
<< (itype.is_ref_counted ? "true" : "false") << ");\n"
2390
<< CLOSE_BLOCK_L2 CLOSE_BLOCK_L1;
2391
2392
// Add.. em.. trick constructor. Sort of.
2393
output.append(MEMBER_BEGIN "internal ");
2394
output.append(itype.proxy_name);
2395
output.append("(bool " CS_PARAM_MEMORYOWN ") : base(" CS_PARAM_MEMORYOWN ") { }\n");
2396
}
2397
}
2398
2399
// Methods
2400
2401
int method_bind_count = 0;
2402
for (const MethodInterface &imethod : itype.methods) {
2403
Error method_err = _generate_cs_method(itype, imethod, method_bind_count, output, false);
2404
ERR_FAIL_COND_V_MSG(method_err != OK, method_err,
2405
"Failed to generate method '" + imethod.name + "' for class '" + itype.name + "'.");
2406
if (imethod.is_internal) {
2407
// No need to generate span overloads for internal methods.
2408
continue;
2409
}
2410
2411
method_err = _generate_cs_method(itype, imethod, method_bind_count, output, true);
2412
ERR_FAIL_COND_V_MSG(method_err != OK, method_err,
2413
"Failed to generate span overload method '" + imethod.name + "' for class '" + itype.name + "'.");
2414
}
2415
2416
// Signals
2417
2418
for (const SignalInterface &isignal : itype.signals_) {
2419
Error method_err = _generate_cs_signal(itype, isignal, output);
2420
ERR_FAIL_COND_V_MSG(method_err != OK, method_err,
2421
"Failed to generate signal '" + isignal.name + "' for class '" + itype.name + "'.");
2422
}
2423
2424
// Script members look-up
2425
2426
if (!itype.is_singleton && (is_derived_type || itype.has_virtual_methods)) {
2427
// Generate method names cache fields
2428
2429
for (const MethodInterface &imethod : itype.methods) {
2430
if (!imethod.is_virtual) {
2431
continue;
2432
}
2433
2434
output << MEMBER_BEGIN "// ReSharper disable once InconsistentNaming\n"
2435
<< INDENT1 "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n"
2436
<< INDENT1 "private static readonly StringName "
2437
<< CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name
2438
<< " = \"" << imethod.proxy_name << "\";\n";
2439
}
2440
2441
// Generate signal names cache fields
2442
2443
for (const SignalInterface &isignal : itype.signals_) {
2444
output << MEMBER_BEGIN "// ReSharper disable once InconsistentNaming\n"
2445
<< INDENT1 "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n"
2446
<< INDENT1 "private static readonly StringName "
2447
<< CS_STATIC_FIELD_SIGNAL_PROXY_NAME_PREFIX << isignal.name
2448
<< " = \"" << isignal.proxy_name << "\";\n";
2449
}
2450
2451
// TODO: Only generate HasGodotClassMethod and InvokeGodotClassMethod if there's any method
2452
2453
// Generate InvokeGodotClassMethod
2454
2455
output << MEMBER_BEGIN "/// <summary>\n"
2456
<< INDENT1 "/// Invokes the method with the given name, using the given arguments.\n"
2457
<< INDENT1 "/// This method is used by Godot to invoke methods from the engine side.\n"
2458
<< INDENT1 "/// Do not call or override this method.\n"
2459
<< INDENT1 "/// </summary>\n"
2460
<< INDENT1 "/// <param name=\"method\">Name of the method to invoke.</param>\n"
2461
<< INDENT1 "/// <param name=\"args\">Arguments to use with the invoked method.</param>\n"
2462
<< INDENT1 "/// <param name=\"ret\">Value returned by the invoked method.</param>\n";
2463
2464
// Avoid raising diagnostics because of calls to obsolete methods.
2465
output << "#pragma warning disable CS0618 // Member is obsolete\n";
2466
2467
output << INDENT1 "protected internal " << (is_derived_type ? "override" : "virtual")
2468
<< " bool " CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(in godot_string_name method, "
2469
<< "NativeVariantPtrArgs args, out godot_variant ret)\n"
2470
<< INDENT1 "{\n";
2471
2472
for (const MethodInterface &imethod : itype.methods) {
2473
if (!imethod.is_virtual) {
2474
continue;
2475
}
2476
2477
// We also call HasGodotClassMethod to ensure the method is overridden and avoid calling
2478
// the stub implementation. This solution adds some extra overhead to calls, but it's
2479
// much simpler than other solutions. This won't be a problem once we move to function
2480
// pointers of generated wrappers for each method, as lookup will only happen once.
2481
2482
// We check both native names (snake_case) and proxy names (PascalCase)
2483
output << INDENT2 "if ((method == " << CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name
2484
<< " || method == MethodName." << imethod.proxy_name
2485
<< ") && args.Count == " << itos(imethod.arguments.size())
2486
<< " && " << CS_METHOD_HAS_GODOT_CLASS_METHOD << "((godot_string_name)"
2487
<< CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name << ".NativeValue))\n"
2488
<< INDENT2 "{\n";
2489
2490
if (imethod.return_type.cname != name_cache.type_void) {
2491
output << INDENT3 "var callRet = ";
2492
} else {
2493
output << INDENT3;
2494
}
2495
2496
output << imethod.proxy_name << "(";
2497
2498
int i = 0;
2499
for (List<BindingsGenerator::ArgumentInterface>::ConstIterator itr = imethod.arguments.begin(); itr != imethod.arguments.end(); ++itr, ++i) {
2500
const ArgumentInterface &iarg = *itr;
2501
2502
const TypeInterface *arg_type = _get_type_or_null(iarg.type);
2503
ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");
2504
2505
if (i != 0) {
2506
output << ", ";
2507
}
2508
2509
if (arg_type->cname == name_cache.type_Array_generic || arg_type->cname == name_cache.type_Dictionary_generic) {
2510
String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);
2511
2512
output << "new " << arg_cs_type << "(" << sformat(arg_type->cs_variant_to_managed, "args[" + itos(i) + "]", arg_type->cs_type, arg_type->name) << ")";
2513
} else {
2514
output << sformat(arg_type->cs_variant_to_managed,
2515
"args[" + itos(i) + "]", arg_type->cs_type, arg_type->name);
2516
}
2517
}
2518
2519
output << ");\n";
2520
2521
if (imethod.return_type.cname != name_cache.type_void) {
2522
const TypeInterface *return_type = _get_type_or_null(imethod.return_type);
2523
ERR_FAIL_NULL_V_MSG(return_type, ERR_BUG, "Return type '" + imethod.return_type.cname + "' was not found.");
2524
2525
output << INDENT3 "ret = "
2526
<< sformat(return_type->cs_managed_to_variant, "callRet", return_type->cs_type, return_type->name)
2527
<< ";\n"
2528
<< INDENT3 "return true;\n";
2529
} else {
2530
output << INDENT3 "ret = default;\n"
2531
<< INDENT3 "return true;\n";
2532
}
2533
2534
output << INDENT2 "}\n";
2535
}
2536
2537
if (is_derived_type) {
2538
output << INDENT2 "return base." CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(method, args, out ret);\n";
2539
} else {
2540
output << INDENT2 "ret = default;\n"
2541
<< INDENT2 "return false;\n";
2542
}
2543
2544
output << INDENT1 "}\n";
2545
2546
output << "#pragma warning restore CS0618\n";
2547
2548
// Generate HasGodotClassMethod
2549
2550
output << MEMBER_BEGIN "/// <summary>\n"
2551
<< INDENT1 "/// Check if the type contains a method with the given name.\n"
2552
<< INDENT1 "/// This method is used by Godot to check if a method exists before invoking it.\n"
2553
<< INDENT1 "/// Do not call or override this method.\n"
2554
<< INDENT1 "/// </summary>\n"
2555
<< INDENT1 "/// <param name=\"method\">Name of the method to check for.</param>\n";
2556
2557
output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual")
2558
<< " bool " CS_METHOD_HAS_GODOT_CLASS_METHOD "(in godot_string_name method)\n"
2559
<< INDENT1 "{\n";
2560
2561
for (const MethodInterface &imethod : itype.methods) {
2562
if (!imethod.is_virtual) {
2563
continue;
2564
}
2565
2566
// We check for native names (snake_case). If we detect one, we call HasGodotClassMethod
2567
// again, but this time with the respective proxy name (PascalCase). It's the job of
2568
// user derived classes to override the method and check for those. Our C# source
2569
// generators take care of generating those override methods.
2570
output << INDENT2 "if (method == MethodName." << imethod.proxy_name
2571
<< ")\n" INDENT2 "{\n"
2572
<< INDENT3 "if (" CS_METHOD_HAS_GODOT_CLASS_METHOD "("
2573
<< CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name
2574
<< ".NativeValue.DangerousSelfRef))\n" INDENT3 "{\n"
2575
<< INDENT4 "return true;\n"
2576
<< INDENT3 "}\n" INDENT2 "}\n";
2577
}
2578
2579
if (is_derived_type) {
2580
output << INDENT2 "return base." CS_METHOD_HAS_GODOT_CLASS_METHOD "(method);\n";
2581
} else {
2582
output << INDENT2 "return false;\n";
2583
}
2584
2585
output << INDENT1 "}\n";
2586
2587
// Generate HasGodotClassSignal
2588
2589
output << MEMBER_BEGIN "/// <summary>\n"
2590
<< INDENT1 "/// Check if the type contains a signal with the given name.\n"
2591
<< INDENT1 "/// This method is used by Godot to check if a signal exists before raising it.\n"
2592
<< INDENT1 "/// Do not call or override this method.\n"
2593
<< INDENT1 "/// </summary>\n"
2594
<< INDENT1 "/// <param name=\"signal\">Name of the signal to check for.</param>\n";
2595
2596
output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual")
2597
<< " bool " CS_METHOD_HAS_GODOT_CLASS_SIGNAL "(in godot_string_name signal)\n"
2598
<< INDENT1 "{\n";
2599
2600
for (const SignalInterface &isignal : itype.signals_) {
2601
// We check for native names (snake_case). If we detect one, we call HasGodotClassSignal
2602
// again, but this time with the respective proxy name (PascalCase). It's the job of
2603
// user derived classes to override the method and check for those. Our C# source
2604
// generators take care of generating those override methods.
2605
output << INDENT2 "if (signal == SignalName." << isignal.proxy_name
2606
<< ")\n" INDENT2 "{\n"
2607
<< INDENT3 "if (" CS_METHOD_HAS_GODOT_CLASS_SIGNAL "("
2608
<< CS_STATIC_FIELD_SIGNAL_PROXY_NAME_PREFIX << isignal.name
2609
<< ".NativeValue.DangerousSelfRef))\n" INDENT3 "{\n"
2610
<< INDENT4 "return true;\n"
2611
<< INDENT3 "}\n" INDENT2 "}\n";
2612
}
2613
2614
if (is_derived_type) {
2615
output << INDENT2 "return base." CS_METHOD_HAS_GODOT_CLASS_SIGNAL "(signal);\n";
2616
} else {
2617
output << INDENT2 "return false;\n";
2618
}
2619
2620
output << INDENT1 "}\n";
2621
}
2622
2623
//Generate StringName for all class members
2624
bool is_inherit = !itype.is_singleton && obj_types.has(itype.base_name);
2625
//PropertyName
2626
output << MEMBER_BEGIN "/// <summary>\n"
2627
<< INDENT1 "/// Cached StringNames for the properties and fields contained in this class, for fast lookup.\n"
2628
<< INDENT1 "/// </summary>\n";
2629
if (is_inherit) {
2630
output << INDENT1 "public new class PropertyName : " << obj_types[itype.base_name].proxy_name << ".PropertyName";
2631
} else {
2632
output << INDENT1 "public class PropertyName";
2633
}
2634
output << "\n"
2635
<< INDENT1 "{\n";
2636
for (const PropertyInterface &iprop : itype.properties) {
2637
output << INDENT2 "/// <summary>\n"
2638
<< INDENT2 "/// Cached name for the '" << iprop.cname << "' property.\n"
2639
<< INDENT2 "/// </summary>\n"
2640
<< INDENT2 "public static "
2641
<< (prop_allowed_inherited_member_hiding.has(itype.proxy_name + ".PropertyName." + iprop.proxy_name) ? "new " : "")
2642
<< "readonly StringName " << iprop.proxy_name << " = \"" << iprop.cname << "\";\n";
2643
}
2644
output << INDENT1 "}\n";
2645
//MethodName
2646
output << MEMBER_BEGIN "/// <summary>\n"
2647
<< INDENT1 "/// Cached StringNames for the methods contained in this class, for fast lookup.\n"
2648
<< INDENT1 "/// </summary>\n";
2649
if (is_inherit) {
2650
output << INDENT1 "public new class MethodName : " << obj_types[itype.base_name].proxy_name << ".MethodName";
2651
} else {
2652
output << INDENT1 "public class MethodName";
2653
}
2654
output << "\n"
2655
<< INDENT1 "{\n";
2656
HashMap<String, StringName> method_names;
2657
for (const MethodInterface &imethod : itype.methods) {
2658
if (method_names.has(imethod.proxy_name)) {
2659
ERR_FAIL_COND_V_MSG(method_names[imethod.proxy_name] != imethod.cname, ERR_BUG, "Method name '" + imethod.proxy_name + "' already exists with a different value.");
2660
continue;
2661
}
2662
method_names[imethod.proxy_name] = imethod.cname;
2663
output << INDENT2 "/// <summary>\n"
2664
<< INDENT2 "/// Cached name for the '" << imethod.cname << "' method.\n"
2665
<< INDENT2 "/// </summary>\n"
2666
<< INDENT2 "public static "
2667
<< (prop_allowed_inherited_member_hiding.has(itype.proxy_name + ".MethodName." + imethod.proxy_name) ? "new " : "")
2668
<< "readonly StringName " << imethod.proxy_name << " = \"" << imethod.cname << "\";\n";
2669
}
2670
output << INDENT1 "}\n";
2671
//SignalName
2672
output << MEMBER_BEGIN "/// <summary>\n"
2673
<< INDENT1 "/// Cached StringNames for the signals contained in this class, for fast lookup.\n"
2674
<< INDENT1 "/// </summary>\n";
2675
if (is_inherit) {
2676
output << INDENT1 "public new class SignalName : " << obj_types[itype.base_name].proxy_name << ".SignalName";
2677
} else {
2678
output << INDENT1 "public class SignalName";
2679
}
2680
output << "\n"
2681
<< INDENT1 "{\n";
2682
for (const SignalInterface &isignal : itype.signals_) {
2683
output << INDENT2 "/// <summary>\n"
2684
<< INDENT2 "/// Cached name for the '" << isignal.cname << "' signal.\n"
2685
<< INDENT2 "/// </summary>\n"
2686
<< INDENT2 "public static "
2687
<< (prop_allowed_inherited_member_hiding.has(itype.proxy_name + ".SignalName." + isignal.proxy_name) ? "new " : "")
2688
<< "readonly StringName " << isignal.proxy_name << " = \"" << isignal.cname << "\";\n";
2689
}
2690
output << INDENT1 "}\n";
2691
2692
output.append(CLOSE_BLOCK /* class */);
2693
2694
return _save_file(p_output_file, output);
2695
}
2696
2697
Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInterface &p_itype, const PropertyInterface &p_iprop, StringBuilder &p_output) {
2698
const MethodInterface *setter = p_itype.find_method_by_name(p_iprop.setter);
2699
2700
// Search it in base types too
2701
const TypeInterface *current_type = &p_itype;
2702
while (!setter && current_type->base_name != StringName()) {
2703
HashMap<StringName, TypeInterface>::Iterator base_match = obj_types.find(current_type->base_name);
2704
ERR_FAIL_COND_V_MSG(!base_match, ERR_BUG, "Type not found '" + current_type->base_name + "'. Inherited by '" + current_type->name + "'.");
2705
current_type = &base_match->value;
2706
setter = current_type->find_method_by_name(p_iprop.setter);
2707
}
2708
2709
const MethodInterface *getter = p_itype.find_method_by_name(p_iprop.getter);
2710
2711
// Search it in base types too
2712
current_type = &p_itype;
2713
while (!getter && current_type->base_name != StringName()) {
2714
HashMap<StringName, TypeInterface>::Iterator base_match = obj_types.find(current_type->base_name);
2715
ERR_FAIL_COND_V_MSG(!base_match, ERR_BUG, "Type not found '" + current_type->base_name + "'. Inherited by '" + current_type->name + "'.");
2716
current_type = &base_match->value;
2717
getter = current_type->find_method_by_name(p_iprop.getter);
2718
}
2719
2720
ERR_FAIL_COND_V(!setter && !getter, ERR_BUG);
2721
2722
if (setter) {
2723
int setter_argc = p_iprop.index != -1 ? 2 : 1;
2724
ERR_FAIL_COND_V(setter->arguments.size() != setter_argc, ERR_BUG);
2725
}
2726
2727
if (getter) {
2728
int getter_argc = p_iprop.index != -1 ? 1 : 0;
2729
ERR_FAIL_COND_V(getter->arguments.size() != getter_argc, ERR_BUG);
2730
}
2731
2732
if (getter && setter) {
2733
const ArgumentInterface &setter_first_arg = setter->arguments.back()->get();
2734
if (getter->return_type.cname != setter_first_arg.type.cname) {
2735
ERR_FAIL_V_MSG(ERR_BUG,
2736
"Return type from getter doesn't match first argument of setter for property: '" +
2737
p_itype.name + "." + String(p_iprop.cname) + "'.");
2738
}
2739
}
2740
2741
const TypeReference &proptype_name = getter ? getter->return_type : setter->arguments.back()->get().type;
2742
2743
const TypeInterface *prop_itype = _get_type_or_singleton_or_null(proptype_name);
2744
ERR_FAIL_NULL_V_MSG(prop_itype, ERR_BUG, "Property type '" + proptype_name.cname + "' was not found.");
2745
2746
ERR_FAIL_COND_V_MSG(prop_itype->is_singleton, ERR_BUG,
2747
"Property type is a singleton: '" + p_itype.name + "." + String(p_iprop.cname) + "'.");
2748
2749
if (p_itype.api_type == ClassDB::API_CORE) {
2750
ERR_FAIL_COND_V_MSG(prop_itype->api_type == ClassDB::API_EDITOR, ERR_BUG,
2751
"Property '" + p_itype.name + "." + String(p_iprop.cname) + "' has type '" + prop_itype->name +
2752
"' from the editor API. Core API cannot have dependencies on the editor API.");
2753
}
2754
2755
if (p_iprop.prop_doc && p_iprop.prop_doc->description.size()) {
2756
String xml_summary = bbcode_to_xml(fix_doc_description(p_iprop.prop_doc->description), &p_itype);
2757
Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
2758
2759
if (summary_lines.size()) {
2760
p_output.append(MEMBER_BEGIN "/// <summary>\n");
2761
2762
for (int i = 0; i < summary_lines.size(); i++) {
2763
p_output.append(INDENT1 "/// ");
2764
p_output.append(summary_lines[i]);
2765
p_output.append("\n");
2766
}
2767
2768
p_output.append(INDENT1 "/// </summary>");
2769
}
2770
}
2771
2772
if (p_iprop.is_deprecated) {
2773
p_output.append(MEMBER_BEGIN "[Obsolete(\"");
2774
p_output.append(bbcode_to_text(p_iprop.deprecation_message, &p_itype));
2775
p_output.append("\")]");
2776
}
2777
2778
if (p_iprop.is_hidden) {
2779
p_output.append(MEMBER_BEGIN "[EditorBrowsable(EditorBrowsableState.Never)]");
2780
}
2781
2782
p_output.append(MEMBER_BEGIN "public ");
2783
2784
if (prop_allowed_inherited_member_hiding.has(p_itype.proxy_name + "." + p_iprop.proxy_name)) {
2785
p_output.append("new ");
2786
}
2787
2788
if (p_itype.is_singleton) {
2789
p_output.append("static ");
2790
}
2791
2792
String prop_cs_type = prop_itype->cs_type + _get_generic_type_parameters(*prop_itype, proptype_name.generic_type_parameters);
2793
2794
p_output.append(prop_cs_type);
2795
p_output.append(" ");
2796
p_output.append(p_iprop.proxy_name);
2797
p_output.append("\n" OPEN_BLOCK_L1);
2798
2799
if (getter) {
2800
p_output.append(INDENT2 "get\n" OPEN_BLOCK_L2 INDENT3);
2801
2802
p_output.append("return ");
2803
p_output.append(getter->proxy_name + "(");
2804
if (p_iprop.index != -1) {
2805
const ArgumentInterface &idx_arg = getter->arguments.front()->get();
2806
if (idx_arg.type.cname != name_cache.type_int) {
2807
// Assume the index parameter is an enum
2808
const TypeInterface *idx_arg_type = _get_type_or_null(idx_arg.type);
2809
CRASH_COND(idx_arg_type == nullptr);
2810
p_output.append("(" + idx_arg_type->proxy_name + ")(" + itos(p_iprop.index) + ")");
2811
} else {
2812
p_output.append(itos(p_iprop.index));
2813
}
2814
}
2815
p_output.append(");\n" CLOSE_BLOCK_L2);
2816
}
2817
2818
if (setter) {
2819
p_output.append(INDENT2 "set\n" OPEN_BLOCK_L2 INDENT3);
2820
2821
p_output.append(setter->proxy_name + "(");
2822
if (p_iprop.index != -1) {
2823
const ArgumentInterface &idx_arg = setter->arguments.front()->get();
2824
if (idx_arg.type.cname != name_cache.type_int) {
2825
// Assume the index parameter is an enum
2826
const TypeInterface *idx_arg_type = _get_type_or_null(idx_arg.type);
2827
CRASH_COND(idx_arg_type == nullptr);
2828
p_output.append("(" + idx_arg_type->proxy_name + ")(" + itos(p_iprop.index) + "), ");
2829
} else {
2830
p_output.append(itos(p_iprop.index) + ", ");
2831
}
2832
}
2833
p_output.append("value);\n" CLOSE_BLOCK_L2);
2834
}
2835
2836
p_output.append(CLOSE_BLOCK_L1);
2837
2838
return OK;
2839
}
2840
2841
Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output, bool p_use_span) {
2842
const TypeInterface *return_type = _get_type_or_singleton_or_null(p_imethod.return_type);
2843
ERR_FAIL_NULL_V_MSG(return_type, ERR_BUG, "Return type '" + p_imethod.return_type.cname + "' was not found.");
2844
2845
ERR_FAIL_COND_V_MSG(return_type->is_singleton, ERR_BUG,
2846
"Method return type is a singleton: '" + p_itype.name + "." + p_imethod.name + "'.");
2847
2848
if (p_itype.api_type == ClassDB::API_CORE) {
2849
ERR_FAIL_COND_V_MSG(return_type->api_type == ClassDB::API_EDITOR, ERR_BUG,
2850
"Method '" + p_itype.name + "." + p_imethod.name + "' has return type '" + return_type->name +
2851
"' from the editor API. Core API cannot have dependencies on the editor API.");
2852
}
2853
2854
if (p_imethod.is_virtual && p_use_span) {
2855
return OK;
2856
}
2857
2858
bool has_span_argument = false;
2859
2860
if (p_use_span) {
2861
if (p_imethod.is_vararg) {
2862
has_span_argument = true;
2863
} else {
2864
for (const ArgumentInterface &iarg : p_imethod.arguments) {
2865
const TypeInterface *arg_type = _get_type_or_singleton_or_null(iarg.type);
2866
ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");
2867
2868
if (arg_type->is_span_compatible) {
2869
has_span_argument = true;
2870
break;
2871
}
2872
}
2873
}
2874
2875
if (has_span_argument) {
2876
// Span overloads use the same method bind as the array overloads.
2877
// Since both overloads are generated one after the other, we can decrease the count here
2878
// to ensure the span overload uses the same method bind.
2879
p_method_bind_count--;
2880
}
2881
}
2882
2883
String method_bind_field = CS_STATIC_FIELD_METHOD_BIND_PREFIX + itos(p_method_bind_count);
2884
2885
String arguments_sig;
2886
StringBuilder cs_in_statements;
2887
bool cs_in_expr_is_unsafe = false;
2888
2889
String icall_params = method_bind_field;
2890
2891
if (!p_imethod.is_static) {
2892
String self_reference = "this";
2893
if (p_itype.is_singleton) {
2894
self_reference = CS_PROPERTY_SINGLETON;
2895
}
2896
2897
if (p_itype.cs_in.size()) {
2898
cs_in_statements << sformat(p_itype.cs_in, p_itype.c_type, self_reference,
2899
String(), String(), String(), INDENT2);
2900
}
2901
2902
icall_params += ", " + sformat(p_itype.cs_in_expr, self_reference);
2903
}
2904
2905
StringBuilder default_args_doc;
2906
2907
// Retrieve information from the arguments
2908
const ArgumentInterface &first = p_imethod.arguments.front()->get();
2909
for (const ArgumentInterface &iarg : p_imethod.arguments) {
2910
const TypeInterface *arg_type = _get_type_or_singleton_or_null(iarg.type);
2911
ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");
2912
2913
ERR_FAIL_COND_V_MSG(arg_type->is_singleton, ERR_BUG,
2914
"Argument type is a singleton: '" + iarg.name + "' of method '" + p_itype.name + "." + p_imethod.name + "'.");
2915
2916
if (p_itype.api_type == ClassDB::API_CORE) {
2917
ERR_FAIL_COND_V_MSG(arg_type->api_type == ClassDB::API_EDITOR, ERR_BUG,
2918
"Argument '" + iarg.name + "' of method '" + p_itype.name + "." + p_imethod.name + "' has type '" +
2919
arg_type->name + "' from the editor API. Core API cannot have dependencies on the editor API.");
2920
}
2921
2922
if (iarg.default_argument.size()) {
2923
CRASH_COND_MSG(!_arg_default_value_is_assignable_to_type(iarg.def_param_value, *arg_type),
2924
"Invalid default value for parameter '" + iarg.name + "' of method '" + p_itype.name + "." + p_imethod.name + "'.");
2925
}
2926
2927
String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);
2928
2929
bool use_span_for_arg = p_use_span && arg_type->is_span_compatible;
2930
2931
// Add the current arguments to the signature
2932
// If the argument has a default value which is not a constant, we will make it Nullable
2933
{
2934
if (&iarg != &first) {
2935
arguments_sig += ", ";
2936
}
2937
2938
if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {
2939
arguments_sig += "Nullable<";
2940
}
2941
2942
if (use_span_for_arg) {
2943
arguments_sig += arg_type->c_type_in;
2944
} else {
2945
arguments_sig += arg_cs_type;
2946
}
2947
2948
if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {
2949
arguments_sig += "> ";
2950
} else {
2951
arguments_sig += " ";
2952
}
2953
2954
arguments_sig += iarg.name;
2955
2956
if (!p_use_span && !p_imethod.is_compat && iarg.default_argument.size()) {
2957
if (iarg.def_param_mode != ArgumentInterface::CONSTANT) {
2958
arguments_sig += " = null";
2959
} else {
2960
arguments_sig += " = " + sformat(iarg.default_argument, arg_type->cs_type);
2961
}
2962
}
2963
}
2964
2965
icall_params += ", ";
2966
2967
if (iarg.default_argument.size() && iarg.def_param_mode != ArgumentInterface::CONSTANT && !use_span_for_arg) {
2968
// The default value of an argument must be constant. Otherwise we make it Nullable and do the following:
2969
// Type arg_in = arg.HasValue ? arg.Value : <non-const default value>;
2970
String arg_or_defval_local = iarg.name;
2971
arg_or_defval_local += "OrDefVal";
2972
2973
cs_in_statements << INDENT2 << arg_cs_type << " " << arg_or_defval_local << " = " << iarg.name;
2974
2975
if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {
2976
cs_in_statements << ".HasValue ? ";
2977
} else {
2978
cs_in_statements << " != null ? ";
2979
}
2980
2981
cs_in_statements << iarg.name;
2982
2983
if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {
2984
cs_in_statements << ".Value : ";
2985
} else {
2986
cs_in_statements << " : ";
2987
}
2988
2989
String cs_type = arg_cs_type;
2990
if (cs_type.ends_with("[]")) {
2991
cs_type = cs_type.substr(0, cs_type.length() - 2);
2992
}
2993
2994
String def_arg = sformat(iarg.default_argument, cs_type);
2995
2996
cs_in_statements << def_arg << ";\n";
2997
2998
if (arg_type->cs_in.size()) {
2999
cs_in_statements << sformat(arg_type->cs_in, arg_type->c_type, arg_or_defval_local,
3000
String(), String(), String(), INDENT2);
3001
}
3002
3003
if (arg_type->cs_in_expr.is_empty()) {
3004
icall_params += arg_or_defval_local;
3005
} else {
3006
icall_params += sformat(arg_type->cs_in_expr, arg_or_defval_local, arg_type->c_type);
3007
}
3008
3009
// Apparently the name attribute must not include the @
3010
String param_tag_name = iarg.name.begins_with("@") ? iarg.name.substr(1) : iarg.name;
3011
// Escape < and > in the attribute default value
3012
String param_def_arg = def_arg.replacen("<", "&lt;").replacen(">", "&gt;");
3013
3014
default_args_doc.append(MEMBER_BEGIN "/// <param name=\"" + param_tag_name + "\">If the parameter is null, then the default value is <c>" + param_def_arg + "</c>.</param>");
3015
} else {
3016
if (arg_type->cs_in.size()) {
3017
cs_in_statements << sformat(arg_type->cs_in, arg_type->c_type, iarg.name,
3018
String(), String(), String(), INDENT2);
3019
}
3020
3021
icall_params += arg_type->cs_in_expr.is_empty() ? iarg.name : sformat(arg_type->cs_in_expr, iarg.name, arg_type->c_type);
3022
}
3023
3024
cs_in_expr_is_unsafe |= arg_type->cs_in_expr_is_unsafe;
3025
}
3026
3027
if (p_use_span && !has_span_argument) {
3028
return OK;
3029
}
3030
3031
// Collect caller name for MethodBind
3032
if (p_imethod.is_vararg) {
3033
icall_params += ", (godot_string_name)MethodName." + p_imethod.proxy_name + ".NativeValue";
3034
}
3035
3036
// Generate method
3037
{
3038
if (!p_imethod.is_virtual && !p_imethod.requires_object_call && !p_use_span) {
3039
p_output << MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n"
3040
<< INDENT1 "private static readonly IntPtr " << method_bind_field << " = ";
3041
3042
if (p_itype.is_singleton) {
3043
// Singletons are static classes. They don't derive GodotObject,
3044
// so we need to specify the type to call the static method.
3045
p_output << "GodotObject.";
3046
}
3047
3048
p_output << ICALL_CLASSDB_GET_METHOD_WITH_COMPATIBILITY "(" BINDINGS_NATIVE_NAME_FIELD ", MethodName."
3049
<< p_imethod.proxy_name << ", " << itos(p_imethod.hash) << "ul"
3050
<< ");\n";
3051
}
3052
3053
if (p_imethod.method_doc && p_imethod.method_doc->description.size()) {
3054
String xml_summary = bbcode_to_xml(fix_doc_description(p_imethod.method_doc->description), &p_itype);
3055
Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
3056
3057
if (summary_lines.size()) {
3058
p_output.append(MEMBER_BEGIN "/// <summary>\n");
3059
3060
for (int i = 0; i < summary_lines.size(); i++) {
3061
p_output.append(INDENT1 "/// ");
3062
p_output.append(summary_lines[i]);
3063
p_output.append("\n");
3064
}
3065
3066
p_output.append(INDENT1 "/// </summary>");
3067
}
3068
}
3069
3070
if (default_args_doc.get_string_length()) {
3071
p_output.append(default_args_doc.as_string());
3072
}
3073
3074
if (p_imethod.is_deprecated) {
3075
p_output.append(MEMBER_BEGIN "[Obsolete(\"");
3076
p_output.append(bbcode_to_text(p_imethod.deprecation_message, &p_itype));
3077
p_output.append("\")]");
3078
}
3079
3080
if (p_imethod.is_hidden) {
3081
p_output.append(MEMBER_BEGIN "[EditorBrowsable(EditorBrowsableState.Never)]");
3082
}
3083
3084
p_output.append(MEMBER_BEGIN);
3085
p_output.append(p_imethod.is_internal ? "internal " : "public ");
3086
3087
if (prop_allowed_inherited_member_hiding.has(p_itype.proxy_name + "." + p_imethod.proxy_name)) {
3088
p_output.append("new ");
3089
}
3090
3091
if (p_itype.is_singleton || p_imethod.is_static) {
3092
p_output.append("static ");
3093
} else if (p_imethod.is_virtual) {
3094
p_output.append("virtual ");
3095
}
3096
3097
if (cs_in_expr_is_unsafe) {
3098
p_output.append("unsafe ");
3099
}
3100
3101
String return_cs_type = return_type->cs_type + _get_generic_type_parameters(*return_type, p_imethod.return_type.generic_type_parameters);
3102
3103
p_output.append(return_cs_type + " ");
3104
p_output.append(p_imethod.proxy_name + "(");
3105
p_output.append(arguments_sig + ")\n" OPEN_BLOCK_L1);
3106
3107
if (p_imethod.is_virtual) {
3108
// Godot virtual method must be overridden, therefore we return a default value by default.
3109
3110
if (return_type->cname == name_cache.type_void) {
3111
p_output.append(CLOSE_BLOCK_L1);
3112
} else {
3113
p_output.append(INDENT2 "return default;\n" CLOSE_BLOCK_L1);
3114
}
3115
3116
return OK; // Won't increment method bind count
3117
}
3118
3119
if (p_imethod.requires_object_call) {
3120
// Fallback to Godot's object.Call(string, params)
3121
3122
p_output.append(INDENT2 CS_METHOD_CALL "(");
3123
p_output.append("MethodName." + p_imethod.proxy_name);
3124
3125
for (const ArgumentInterface &iarg : p_imethod.arguments) {
3126
p_output.append(", ");
3127
p_output.append(iarg.name);
3128
}
3129
3130
p_output.append(");\n" CLOSE_BLOCK_L1);
3131
3132
return OK; // Won't increment method bind count
3133
}
3134
3135
HashMap<const MethodInterface *, const InternalCall *>::ConstIterator match = method_icalls_map.find(&p_imethod);
3136
ERR_FAIL_NULL_V(match, ERR_BUG);
3137
3138
const InternalCall *im_icall = match->value;
3139
3140
String im_call = im_icall->editor_only ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS;
3141
im_call += ".";
3142
im_call += im_icall->name;
3143
3144
if (p_imethod.arguments.size() && cs_in_statements.get_string_length() > 0) {
3145
p_output.append(cs_in_statements.as_string());
3146
}
3147
3148
if (return_type->cname == name_cache.type_void) {
3149
p_output << INDENT2 << im_call << "(" << icall_params << ");\n";
3150
} else if (return_type->cs_out.is_empty()) {
3151
p_output << INDENT2 "return " << im_call << "(" << icall_params << ");\n";
3152
} else {
3153
p_output.append(sformat(return_type->cs_out, im_call, icall_params,
3154
return_cs_type, return_type->c_type_out, String(), INDENT2));
3155
p_output.append("\n");
3156
}
3157
3158
p_output.append(CLOSE_BLOCK_L1);
3159
}
3160
3161
p_method_bind_count++;
3162
3163
return OK;
3164
}
3165
3166
Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::SignalInterface &p_isignal, StringBuilder &p_output) {
3167
String arguments_sig;
3168
3169
// Retrieve information from the arguments
3170
const ArgumentInterface &first = p_isignal.arguments.front()->get();
3171
for (const ArgumentInterface &iarg : p_isignal.arguments) {
3172
const TypeInterface *arg_type = _get_type_or_singleton_or_null(iarg.type);
3173
ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");
3174
3175
ERR_FAIL_COND_V_MSG(arg_type->is_singleton, ERR_BUG,
3176
"Argument type is a singleton: '" + iarg.name + "' of signal '" + p_itype.name + "." + p_isignal.name + "'.");
3177
3178
if (p_itype.api_type == ClassDB::API_CORE) {
3179
ERR_FAIL_COND_V_MSG(arg_type->api_type == ClassDB::API_EDITOR, ERR_BUG,
3180
"Argument '" + iarg.name + "' of signal '" + p_itype.name + "." + p_isignal.name + "' has type '" +
3181
arg_type->name + "' from the editor API. Core API cannot have dependencies on the editor API.");
3182
}
3183
3184
// Add the current arguments to the signature
3185
3186
if (&iarg != &first) {
3187
arguments_sig += ", ";
3188
}
3189
3190
String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);
3191
3192
arguments_sig += arg_cs_type;
3193
arguments_sig += " ";
3194
arguments_sig += iarg.name;
3195
}
3196
3197
// Generate signal
3198
{
3199
bool is_parameterless = p_isignal.arguments.is_empty();
3200
3201
// Delegate name is [SignalName]EventHandler
3202
String delegate_name = is_parameterless ? "Action" : p_isignal.proxy_name + "EventHandler";
3203
3204
if (!is_parameterless) {
3205
p_output.append(MEMBER_BEGIN "/// <summary>\n");
3206
p_output.append(INDENT1 "/// ");
3207
p_output.append("Represents the method that handles the ");
3208
p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "." + p_isignal.proxy_name + "\"/>");
3209
p_output.append(" event of a ");
3210
p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "\"/>");
3211
p_output.append(" class.\n");
3212
p_output.append(INDENT1 "/// </summary>");
3213
3214
// Generate delegate
3215
if (p_isignal.is_deprecated) {
3216
p_output.append(MEMBER_BEGIN "[Obsolete(\"");
3217
p_output.append(bbcode_to_text(p_isignal.deprecation_message, &p_itype));
3218
p_output.append("\")]");
3219
}
3220
p_output.append(MEMBER_BEGIN "public delegate void ");
3221
p_output.append(delegate_name);
3222
p_output.append("(");
3223
p_output.append(arguments_sig);
3224
p_output.append(");\n");
3225
3226
// Generate Callable trampoline for the delegate
3227
if (p_isignal.is_deprecated) {
3228
p_output.append(MEMBER_BEGIN "[Obsolete(\"");
3229
p_output.append(bbcode_to_text(p_isignal.deprecation_message, &p_itype));
3230
p_output.append("\")]");
3231
}
3232
p_output << MEMBER_BEGIN "private static void " << p_isignal.proxy_name << "Trampoline"
3233
<< "(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)\n"
3234
<< INDENT1 "{\n"
3235
<< INDENT2 "Callable.ThrowIfArgCountMismatch(args, " << itos(p_isignal.arguments.size()) << ");\n"
3236
<< INDENT2 "((" << delegate_name << ")delegateObj)(";
3237
3238
int idx = 0;
3239
for (const ArgumentInterface &iarg : p_isignal.arguments) {
3240
const TypeInterface *arg_type = _get_type_or_null(iarg.type);
3241
ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");
3242
3243
if (idx != 0) {
3244
p_output << ", ";
3245
}
3246
3247
if (arg_type->cname == name_cache.type_Array_generic || arg_type->cname == name_cache.type_Dictionary_generic) {
3248
String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);
3249
3250
p_output << "new " << arg_cs_type << "(" << sformat(arg_type->cs_variant_to_managed, "args[" + itos(idx) + "]", arg_type->cs_type, arg_type->name) << ")";
3251
} else {
3252
p_output << sformat(arg_type->cs_variant_to_managed,
3253
"args[" + itos(idx) + "]", arg_type->cs_type, arg_type->name);
3254
}
3255
3256
idx++;
3257
}
3258
3259
p_output << ");\n"
3260
<< INDENT2 "ret = default;\n"
3261
<< INDENT1 "}\n";
3262
}
3263
3264
if (p_isignal.method_doc && p_isignal.method_doc->description.size()) {
3265
String xml_summary = bbcode_to_xml(fix_doc_description(p_isignal.method_doc->description), &p_itype, true);
3266
Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
3267
3268
if (summary_lines.size()) {
3269
p_output.append(MEMBER_BEGIN "/// <summary>\n");
3270
3271
for (int i = 0; i < summary_lines.size(); i++) {
3272
p_output.append(INDENT1 "/// ");
3273
p_output.append(summary_lines[i]);
3274
p_output.append("\n");
3275
}
3276
3277
p_output.append(INDENT1 "/// </summary>");
3278
}
3279
}
3280
3281
// TODO:
3282
// Could we assume the StringName instance of signal name will never be freed (it's stored in ClassDB) before the managed world is unloaded?
3283
// If so, we could store the pointer we get from `data_unique_pointer()` instead of allocating StringName here.
3284
3285
// Generate event
3286
if (p_isignal.is_deprecated) {
3287
p_output.append(MEMBER_BEGIN "[Obsolete(\"");
3288
p_output.append(bbcode_to_text(p_isignal.deprecation_message, &p_itype));
3289
p_output.append("\")]");
3290
}
3291
p_output.append(MEMBER_BEGIN "public ");
3292
3293
if (p_itype.is_singleton) {
3294
p_output.append("static ");
3295
}
3296
3297
if (!is_parameterless) {
3298
// `unsafe` is needed for taking the trampoline's function pointer
3299
p_output << "unsafe ";
3300
}
3301
3302
p_output.append("event ");
3303
p_output.append(delegate_name);
3304
p_output.append(" ");
3305
p_output.append(p_isignal.proxy_name);
3306
p_output.append("\n" OPEN_BLOCK_L1 INDENT2);
3307
3308
if (p_itype.is_singleton) {
3309
p_output.append("add => " CS_PROPERTY_SINGLETON ".Connect(SignalName.");
3310
} else {
3311
p_output.append("add => Connect(SignalName.");
3312
}
3313
3314
if (is_parameterless) {
3315
// Delegate type is Action. No need for custom trampoline.
3316
p_output << p_isignal.proxy_name << ", Callable.From(value));\n";
3317
} else {
3318
p_output << p_isignal.proxy_name
3319
<< ", Callable.CreateWithUnsafeTrampoline(value, &" << p_isignal.proxy_name << "Trampoline));\n";
3320
}
3321
3322
if (p_itype.is_singleton) {
3323
p_output.append(INDENT2 "remove => " CS_PROPERTY_SINGLETON ".Disconnect(SignalName.");
3324
} else {
3325
p_output.append(INDENT2 "remove => Disconnect(SignalName.");
3326
}
3327
3328
if (is_parameterless) {
3329
// Delegate type is Action. No need for custom trampoline.
3330
p_output << p_isignal.proxy_name << ", Callable.From(value));\n";
3331
} else {
3332
p_output << p_isignal.proxy_name
3333
<< ", Callable.CreateWithUnsafeTrampoline(value, &" << p_isignal.proxy_name << "Trampoline));\n";
3334
}
3335
3336
p_output.append(CLOSE_BLOCK_L1);
3337
3338
// Generate EmitSignal{EventName} method to raise the event.
3339
if (!p_itype.is_singleton) {
3340
if (p_isignal.is_deprecated) {
3341
p_output.append(MEMBER_BEGIN "[Obsolete(\"");
3342
p_output.append(bbcode_to_text(p_isignal.deprecation_message, &p_itype));
3343
p_output.append("\")]");
3344
}
3345
p_output.append(MEMBER_BEGIN "protected void ");
3346
p_output << "EmitSignal" << p_isignal.proxy_name;
3347
if (is_parameterless) {
3348
p_output.append("()\n" OPEN_BLOCK_L1 INDENT2);
3349
p_output << "EmitSignal(SignalName." << p_isignal.proxy_name << ");\n";
3350
p_output.append(CLOSE_BLOCK_L1);
3351
} else {
3352
p_output.append("(");
3353
3354
StringBuilder cs_emitsignal_params;
3355
3356
int idx = 0;
3357
for (const ArgumentInterface &iarg : p_isignal.arguments) {
3358
const TypeInterface *arg_type = _get_type_or_null(iarg.type);
3359
ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");
3360
3361
if (idx != 0) {
3362
p_output << ", ";
3363
cs_emitsignal_params << ", ";
3364
}
3365
3366
String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);
3367
3368
p_output << arg_cs_type << " " << iarg.name;
3369
3370
if (arg_type->is_enum) {
3371
cs_emitsignal_params << "(long)";
3372
}
3373
3374
cs_emitsignal_params << iarg.name;
3375
3376
idx++;
3377
}
3378
3379
p_output.append(")\n" OPEN_BLOCK_L1 INDENT2);
3380
p_output << "EmitSignal(SignalName." << p_isignal.proxy_name << ", " << cs_emitsignal_params << ");\n";
3381
p_output.append(CLOSE_BLOCK_L1);
3382
}
3383
}
3384
}
3385
3386
return OK;
3387
}
3388
3389
Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall, StringBuilder &r_output) {
3390
bool ret_void = p_icall.return_type.cname == name_cache.type_void;
3391
3392
const TypeInterface *return_type = _get_type_or_null(p_icall.return_type);
3393
ERR_FAIL_NULL_V_MSG(return_type, ERR_BUG, "Return type '" + p_icall.return_type.cname + "' was not found.");
3394
3395
StringBuilder c_func_sig;
3396
StringBuilder c_in_statements;
3397
StringBuilder c_args_var_content;
3398
3399
c_func_sig << "IntPtr " CS_PARAM_METHODBIND;
3400
3401
if (!p_icall.is_static) {
3402
c_func_sig += ", IntPtr " CS_PARAM_INSTANCE;
3403
}
3404
3405
// Get arguments information
3406
int i = 0;
3407
for (const TypeReference &arg_type_ref : p_icall.argument_types) {
3408
const TypeInterface *arg_type = _get_type_or_null(arg_type_ref);
3409
ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + arg_type_ref.cname + "' was not found.");
3410
3411
String c_param_name = "arg" + itos(i + 1);
3412
3413
if (p_icall.is_vararg) {
3414
if (i < p_icall.get_arguments_count() - 1) {
3415
String c_in_vararg = arg_type->c_in_vararg;
3416
3417
if (arg_type->is_object_type) {
3418
c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromGodotObjectPtr(%1);\n";
3419
}
3420
3421
ERR_FAIL_COND_V_MSG(c_in_vararg.is_empty(), ERR_BUG,
3422
"VarArg support not implemented for parameter type: " + arg_type->name);
3423
3424
c_in_statements
3425
<< sformat(c_in_vararg, return_type->c_type, c_param_name,
3426
String(), String(), String(), INDENT3)
3427
<< INDENT3 C_LOCAL_PTRCALL_ARGS "[" << itos(i)
3428
<< "] = new IntPtr(&" << c_param_name << "_in);\n";
3429
}
3430
} else {
3431
if (i > 0) {
3432
c_args_var_content << ", ";
3433
}
3434
if (arg_type->c_in.size()) {
3435
c_in_statements << sformat(arg_type->c_in, arg_type->c_type, c_param_name,
3436
String(), String(), String(), INDENT2);
3437
}
3438
c_args_var_content << sformat(arg_type->c_arg_in, c_param_name);
3439
}
3440
3441
c_func_sig << ", " << arg_type->c_type_in << " " << c_param_name;
3442
3443
i++;
3444
}
3445
3446
// Collect caller name for MethodBind
3447
if (p_icall.is_vararg) {
3448
c_func_sig << ", godot_string_name caller";
3449
}
3450
3451
String icall_method = p_icall.name;
3452
3453
// Generate icall function
3454
3455
r_output << MEMBER_BEGIN "internal static unsafe " << (ret_void ? "void" : return_type->c_type_out) << " "
3456
<< icall_method << "(" << c_func_sig.as_string() << ")\n" OPEN_BLOCK_L1;
3457
3458
if (!p_icall.is_static) {
3459
r_output << INDENT2 "ExceptionUtils.ThrowIfNullPtr(" CS_PARAM_INSTANCE ");\n";
3460
}
3461
3462
if (!ret_void && (!p_icall.is_vararg || return_type->cname != name_cache.type_Variant)) {
3463
String ptrcall_return_type;
3464
String initialization;
3465
3466
if (return_type->is_object_type) {
3467
ptrcall_return_type = return_type->is_ref_counted ? "godot_ref" : return_type->c_type;
3468
initialization = " = default";
3469
} else {
3470
ptrcall_return_type = return_type->c_type;
3471
}
3472
3473
r_output << INDENT2;
3474
3475
if (return_type->is_ref_counted || return_type->c_type_is_disposable_struct) {
3476
r_output << "using ";
3477
3478
if (initialization.is_empty()) {
3479
initialization = " = default";
3480
}
3481
} else if (return_type->c_ret_needs_default_initialization) {
3482
initialization = " = default";
3483
}
3484
3485
r_output << ptrcall_return_type << " " C_LOCAL_RET << initialization << ";\n";
3486
}
3487
3488
String argc_str = itos(p_icall.get_arguments_count());
3489
3490
auto generate_call_and_return_stmts = [&](const char *base_indent) {
3491
if (p_icall.is_vararg) {
3492
// MethodBind Call
3493
r_output << base_indent;
3494
3495
// VarArg methods always return Variant, but there are some cases in which MethodInfo provides
3496
// a specific return type. We trust this information is valid. We need a temporary local to keep
3497
// the Variant alive until the method returns. Otherwise, if the returned Variant holds a RefPtr,
3498
// it could be deleted too early. This is the case with GDScript.new() which returns OBJECT.
3499
// Alternatively, we could just return Variant, but that would result in a worse API.
3500
3501
if (!ret_void) {
3502
if (return_type->cname != name_cache.type_Variant) {
3503
// Usually the return value takes ownership, but in this case the variant is only used
3504
// for conversion to another return type. As such, the local variable takes ownership.
3505
r_output << "using godot_variant " << C_LOCAL_VARARG_RET " = ";
3506
} else {
3507
// Variant's [c_out] takes ownership of the variant value
3508
r_output << "godot_variant " << C_LOCAL_RET " = ";
3509
}
3510
}
3511
3512
r_output << C_CLASS_NATIVE_FUNCS ".godotsharp_method_bind_call("
3513
<< CS_PARAM_METHODBIND ", " << (p_icall.is_static ? "IntPtr.Zero" : CS_PARAM_INSTANCE)
3514
<< ", " << (p_icall.get_arguments_count() ? "(godot_variant**)" C_LOCAL_PTRCALL_ARGS : "null")
3515
<< ", total_length, out godot_variant_call_error vcall_error);\n";
3516
3517
r_output << base_indent << "ExceptionUtils.DebugCheckCallError(caller"
3518
<< ", " << (p_icall.is_static ? "IntPtr.Zero" : CS_PARAM_INSTANCE)
3519
<< ", " << (p_icall.get_arguments_count() ? "(godot_variant**)" C_LOCAL_PTRCALL_ARGS : "null")
3520
<< ", total_length, vcall_error);\n";
3521
3522
if (!ret_void) {
3523
if (return_type->cname != name_cache.type_Variant) {
3524
if (return_type->cname == name_cache.enum_Error) {
3525
r_output << base_indent << C_LOCAL_RET " = VariantUtils.ConvertToInt64(" C_LOCAL_VARARG_RET ");\n";
3526
} else {
3527
// TODO: Use something similar to c_in_vararg (see usage above, with error if not implemented)
3528
CRASH_NOW_MSG("Custom VarArg return type not implemented: " + return_type->name);
3529
r_output << base_indent << C_LOCAL_RET " = " C_LOCAL_VARARG_RET ";\n";
3530
}
3531
}
3532
}
3533
} else {
3534
// MethodBind PtrCall
3535
r_output << base_indent << C_CLASS_NATIVE_FUNCS ".godotsharp_method_bind_ptrcall("
3536
<< CS_PARAM_METHODBIND ", " << (p_icall.is_static ? "IntPtr.Zero" : CS_PARAM_INSTANCE)
3537
<< ", " << (p_icall.get_arguments_count() ? C_LOCAL_PTRCALL_ARGS : "null")
3538
<< ", " << (!ret_void ? "&" C_LOCAL_RET ");\n" : "null);\n");
3539
}
3540
3541
// Return statement
3542
3543
if (!ret_void) {
3544
if (return_type->c_out.is_empty()) {
3545
r_output << base_indent << "return " C_LOCAL_RET ";\n";
3546
} else {
3547
r_output << sformat(return_type->c_out, return_type->c_type_out, C_LOCAL_RET,
3548
return_type->name, String(), String(), base_indent);
3549
}
3550
}
3551
};
3552
3553
if (p_icall.get_arguments_count()) {
3554
if (p_icall.is_vararg) {
3555
String vararg_arg = "arg" + argc_str;
3556
String real_argc_str = itos(p_icall.get_arguments_count() - 1); // Arguments count without vararg
3557
3558
p_icall.get_arguments_count();
3559
3560
r_output << INDENT2 "int vararg_length = " << vararg_arg << ".Length;\n"
3561
<< INDENT2 "int total_length = " << real_argc_str << " + vararg_length;\n";
3562
3563
r_output << INDENT2 "Span<godot_variant.movable> varargs_span = vararg_length <= VarArgsSpanThreshold ?\n"
3564
<< INDENT3 "stackalloc godot_variant.movable[VarArgsSpanThreshold] :\n"
3565
<< INDENT3 "new godot_variant.movable[vararg_length];\n";
3566
3567
r_output << INDENT2 "Span<IntPtr> " C_LOCAL_PTRCALL_ARGS "_span = total_length <= VarArgsSpanThreshold ?\n"
3568
<< INDENT3 "stackalloc IntPtr[VarArgsSpanThreshold] :\n"
3569
<< INDENT3 "new IntPtr[total_length];\n";
3570
3571
r_output << INDENT2 "fixed (godot_variant.movable* varargs = &MemoryMarshal.GetReference(varargs_span))\n"
3572
<< INDENT2 "fixed (IntPtr* " C_LOCAL_PTRCALL_ARGS " = "
3573
"&MemoryMarshal.GetReference(" C_LOCAL_PTRCALL_ARGS "_span))\n"
3574
<< OPEN_BLOCK_L2;
3575
3576
r_output << c_in_statements.as_string();
3577
3578
r_output << INDENT3 "for (int i = 0; i < vararg_length; i++)\n" OPEN_BLOCK_L3
3579
<< INDENT4 "varargs[i] = " << vararg_arg << "[i].NativeVar;\n"
3580
<< INDENT4 C_LOCAL_PTRCALL_ARGS "[" << real_argc_str << " + i] = new IntPtr(&varargs[i]);\n"
3581
<< CLOSE_BLOCK_L3;
3582
3583
generate_call_and_return_stmts(INDENT3);
3584
3585
r_output << CLOSE_BLOCK_L2;
3586
} else {
3587
r_output << c_in_statements.as_string();
3588
3589
r_output << INDENT2 "void** " C_LOCAL_PTRCALL_ARGS " = stackalloc void*["
3590
<< argc_str << "] { " << c_args_var_content.as_string() << " };\n";
3591
3592
generate_call_and_return_stmts(INDENT2);
3593
}
3594
} else {
3595
generate_call_and_return_stmts(INDENT2);
3596
}
3597
3598
r_output << CLOSE_BLOCK_L1;
3599
3600
return OK;
3601
}
3602
3603
Error BindingsGenerator::_save_file(const String &p_path, const StringBuilder &p_content) {
3604
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE);
3605
ERR_FAIL_COND_V_MSG(file.is_null(), ERR_FILE_CANT_WRITE, "Cannot open file: '" + p_path + "'.");
3606
3607
file->store_string(p_content.as_string());
3608
3609
return OK;
3610
}
3611
3612
const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_null(const TypeReference &p_typeref) {
3613
HashMap<StringName, TypeInterface>::ConstIterator builtin_type_match = builtin_types.find(p_typeref.cname);
3614
3615
if (builtin_type_match) {
3616
return &builtin_type_match->value;
3617
}
3618
3619
HashMap<StringName, TypeInterface>::ConstIterator obj_type_match = obj_types.find(p_typeref.cname);
3620
3621
if (obj_type_match) {
3622
return &obj_type_match->value;
3623
}
3624
3625
if (p_typeref.is_enum) {
3626
HashMap<StringName, TypeInterface>::ConstIterator enum_match = enum_types.find(p_typeref.cname);
3627
3628
if (enum_match) {
3629
return &enum_match->value;
3630
}
3631
3632
// Enum not found. Most likely because none of its constants were bound, so it's empty. That's fine. Use int instead.
3633
HashMap<StringName, TypeInterface>::ConstIterator int_match = builtin_types.find(name_cache.type_int);
3634
ERR_FAIL_NULL_V(int_match, nullptr);
3635
return &int_match->value;
3636
}
3637
3638
return nullptr;
3639
}
3640
3641
const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_singleton_or_null(const TypeReference &p_typeref) {
3642
const TypeInterface *itype = _get_type_or_null(p_typeref);
3643
if (itype == nullptr) {
3644
return nullptr;
3645
}
3646
3647
if (itype->is_singleton) {
3648
StringName instance_type_name = itype->name + CS_SINGLETON_INSTANCE_SUFFIX;
3649
itype = &obj_types.find(instance_type_name)->value;
3650
}
3651
3652
return itype;
3653
}
3654
3655
const String BindingsGenerator::_get_generic_type_parameters(const TypeInterface &p_itype, const List<TypeReference> &p_generic_type_parameters) {
3656
if (p_generic_type_parameters.is_empty()) {
3657
return "";
3658
}
3659
3660
ERR_FAIL_COND_V_MSG(p_itype.type_parameter_count != p_generic_type_parameters.size(), "",
3661
"Generic type parameter count mismatch for type '" + p_itype.name + "'." +
3662
" Found " + itos(p_generic_type_parameters.size()) + ", but requires " +
3663
itos(p_itype.type_parameter_count) + ".");
3664
3665
int i = 0;
3666
String params = "<";
3667
for (const TypeReference &param_type : p_generic_type_parameters) {
3668
const TypeInterface *param_itype = _get_type_or_singleton_or_null(param_type);
3669
ERR_FAIL_NULL_V_MSG(param_itype, "", "Parameter type '" + param_type.cname + "' was not found.");
3670
3671
ERR_FAIL_COND_V_MSG(param_itype->is_singleton, "",
3672
"Generic type parameter is a singleton: '" + param_itype->name + "'.");
3673
3674
if (p_itype.api_type == ClassDB::API_CORE) {
3675
ERR_FAIL_COND_V_MSG(param_itype->api_type == ClassDB::API_EDITOR, "",
3676
"Generic type parameter '" + param_itype->name + "' has type from the editor API." +
3677
" Core API cannot have dependencies on the editor API.");
3678
}
3679
3680
params += param_itype->cs_type;
3681
if (i < p_generic_type_parameters.size() - 1) {
3682
params += ", ";
3683
}
3684
3685
i++;
3686
}
3687
params += ">";
3688
3689
return params;
3690
}
3691
3692
StringName BindingsGenerator::_get_type_name_from_meta(Variant::Type p_type, GodotTypeInfo::Metadata p_meta) {
3693
if (p_type == Variant::INT) {
3694
return _get_int_type_name_from_meta(p_meta);
3695
} else if (p_type == Variant::FLOAT) {
3696
return _get_float_type_name_from_meta(p_meta);
3697
} else {
3698
return Variant::get_type_name(p_type);
3699
}
3700
}
3701
3702
StringName BindingsGenerator::_get_int_type_name_from_meta(GodotTypeInfo::Metadata p_meta) {
3703
switch (p_meta) {
3704
case GodotTypeInfo::METADATA_INT_IS_INT8:
3705
return "sbyte";
3706
break;
3707
case GodotTypeInfo::METADATA_INT_IS_INT16:
3708
return "short";
3709
break;
3710
case GodotTypeInfo::METADATA_INT_IS_INT32:
3711
return "int";
3712
break;
3713
case GodotTypeInfo::METADATA_INT_IS_INT64:
3714
return "long";
3715
break;
3716
case GodotTypeInfo::METADATA_INT_IS_UINT8:
3717
return "byte";
3718
break;
3719
case GodotTypeInfo::METADATA_INT_IS_UINT16:
3720
return "ushort";
3721
break;
3722
case GodotTypeInfo::METADATA_INT_IS_UINT32:
3723
return "uint";
3724
break;
3725
case GodotTypeInfo::METADATA_INT_IS_UINT64:
3726
return "ulong";
3727
break;
3728
case GodotTypeInfo::METADATA_INT_IS_CHAR16:
3729
return "char";
3730
break;
3731
case GodotTypeInfo::METADATA_INT_IS_CHAR32:
3732
// To prevent breaking compatibility, C# bindings need to keep using `long`.
3733
return "long";
3734
default:
3735
// Assume INT64
3736
return "long";
3737
}
3738
}
3739
3740
StringName BindingsGenerator::_get_float_type_name_from_meta(GodotTypeInfo::Metadata p_meta) {
3741
switch (p_meta) {
3742
case GodotTypeInfo::METADATA_REAL_IS_FLOAT:
3743
return "float";
3744
break;
3745
case GodotTypeInfo::METADATA_REAL_IS_DOUBLE:
3746
return "double";
3747
break;
3748
default:
3749
// Assume FLOAT64
3750
return "double";
3751
}
3752
}
3753
3754
bool BindingsGenerator::_arg_default_value_is_assignable_to_type(const Variant &p_val, const TypeInterface &p_arg_type) {
3755
if (p_arg_type.name == name_cache.type_Variant) {
3756
// Variant can take anything
3757
return true;
3758
}
3759
3760
switch (p_val.get_type()) {
3761
case Variant::NIL:
3762
return p_arg_type.is_object_type ||
3763
name_cache.is_nullable_type(p_arg_type.name);
3764
case Variant::BOOL:
3765
return p_arg_type.name == name_cache.type_bool;
3766
case Variant::INT:
3767
return p_arg_type.name == name_cache.type_sbyte ||
3768
p_arg_type.name == name_cache.type_short ||
3769
p_arg_type.name == name_cache.type_int ||
3770
p_arg_type.name == name_cache.type_byte ||
3771
p_arg_type.name == name_cache.type_ushort ||
3772
p_arg_type.name == name_cache.type_uint ||
3773
p_arg_type.name == name_cache.type_long ||
3774
p_arg_type.name == name_cache.type_ulong ||
3775
p_arg_type.name == name_cache.type_float ||
3776
p_arg_type.name == name_cache.type_double ||
3777
p_arg_type.is_enum;
3778
case Variant::FLOAT:
3779
return p_arg_type.name == name_cache.type_float ||
3780
p_arg_type.name == name_cache.type_double;
3781
case Variant::STRING:
3782
case Variant::STRING_NAME:
3783
return p_arg_type.name == name_cache.type_String ||
3784
p_arg_type.name == name_cache.type_StringName ||
3785
p_arg_type.name == name_cache.type_NodePath;
3786
case Variant::NODE_PATH:
3787
return p_arg_type.name == name_cache.type_NodePath;
3788
case Variant::TRANSFORM2D:
3789
case Variant::TRANSFORM3D:
3790
case Variant::BASIS:
3791
case Variant::QUATERNION:
3792
case Variant::PLANE:
3793
case Variant::AABB:
3794
case Variant::COLOR:
3795
case Variant::VECTOR2:
3796
case Variant::RECT2:
3797
case Variant::VECTOR3:
3798
case Variant::VECTOR4:
3799
case Variant::PROJECTION:
3800
case Variant::RID:
3801
case Variant::PACKED_BYTE_ARRAY:
3802
case Variant::PACKED_INT32_ARRAY:
3803
case Variant::PACKED_INT64_ARRAY:
3804
case Variant::PACKED_FLOAT32_ARRAY:
3805
case Variant::PACKED_FLOAT64_ARRAY:
3806
case Variant::PACKED_STRING_ARRAY:
3807
case Variant::PACKED_VECTOR2_ARRAY:
3808
case Variant::PACKED_VECTOR3_ARRAY:
3809
case Variant::PACKED_VECTOR4_ARRAY:
3810
case Variant::PACKED_COLOR_ARRAY:
3811
case Variant::CALLABLE:
3812
case Variant::SIGNAL:
3813
return p_arg_type.name == Variant::get_type_name(p_val.get_type());
3814
case Variant::ARRAY:
3815
return p_arg_type.name == Variant::get_type_name(p_val.get_type()) || p_arg_type.cname == name_cache.type_Array_generic;
3816
case Variant::DICTIONARY:
3817
return p_arg_type.name == Variant::get_type_name(p_val.get_type()) || p_arg_type.cname == name_cache.type_Dictionary_generic;
3818
case Variant::OBJECT:
3819
return p_arg_type.is_object_type;
3820
case Variant::VECTOR2I:
3821
return p_arg_type.name == name_cache.type_Vector2 ||
3822
p_arg_type.name == Variant::get_type_name(p_val.get_type());
3823
case Variant::RECT2I:
3824
return p_arg_type.name == name_cache.type_Rect2 ||
3825
p_arg_type.name == Variant::get_type_name(p_val.get_type());
3826
case Variant::VECTOR3I:
3827
return p_arg_type.name == name_cache.type_Vector3 ||
3828
p_arg_type.name == Variant::get_type_name(p_val.get_type());
3829
case Variant::VECTOR4I:
3830
return p_arg_type.name == name_cache.type_Vector4 ||
3831
p_arg_type.name == Variant::get_type_name(p_val.get_type());
3832
case Variant::VARIANT_MAX:
3833
CRASH_NOW_MSG("Unexpected Variant type: " + itos(p_val.get_type()));
3834
break;
3835
}
3836
3837
return false;
3838
}
3839
3840
bool method_has_ptr_parameter(MethodInfo p_method_info) {
3841
if (p_method_info.return_val.type == Variant::INT && p_method_info.return_val.hint == PROPERTY_HINT_INT_IS_POINTER) {
3842
return true;
3843
}
3844
for (PropertyInfo arg : p_method_info.arguments) {
3845
if (arg.type == Variant::INT && arg.hint == PROPERTY_HINT_INT_IS_POINTER) {
3846
return true;
3847
}
3848
}
3849
return false;
3850
}
3851
3852
struct SortMethodWithHashes {
3853
_FORCE_INLINE_ bool operator()(const Pair<MethodInfo, uint32_t> &p_a, const Pair<MethodInfo, uint32_t> &p_b) const {
3854
return p_a.first < p_b.first;
3855
}
3856
};
3857
3858
bool BindingsGenerator::_populate_object_type_interfaces() {
3859
obj_types.clear();
3860
3861
List<StringName> class_list;
3862
ClassDB::get_class_list(&class_list);
3863
class_list.sort_custom<StringName::AlphCompare>();
3864
3865
while (class_list.size()) {
3866
StringName type_cname = class_list.front()->get();
3867
3868
ClassDB::APIType api_type = ClassDB::get_api_type(type_cname);
3869
3870
if (api_type == ClassDB::API_NONE) {
3871
class_list.pop_front();
3872
continue;
3873
}
3874
3875
if (ignored_types.has(type_cname)) {
3876
_log("Ignoring type '%s' because it's in the list of ignored types\n", String(type_cname).utf8().get_data());
3877
class_list.pop_front();
3878
continue;
3879
}
3880
3881
if (!ClassDB::is_class_exposed(type_cname)) {
3882
_log("Ignoring type '%s' because it's not exposed\n", String(type_cname).utf8().get_data());
3883
class_list.pop_front();
3884
continue;
3885
}
3886
3887
if (!ClassDB::is_class_enabled(type_cname)) {
3888
_log("Ignoring type '%s' because it's not enabled\n", String(type_cname).utf8().get_data());
3889
class_list.pop_front();
3890
continue;
3891
}
3892
3893
ClassDB::ClassInfo *class_info = ClassDB::classes.getptr(type_cname);
3894
3895
TypeInterface itype = TypeInterface::create_object_type(type_cname, pascal_to_pascal_case(type_cname), api_type);
3896
3897
itype.base_name = ClassDB::get_parent_class(type_cname);
3898
itype.is_singleton = Engine::get_singleton()->has_singleton(type_cname);
3899
itype.is_instantiable = class_info->creation_func && !itype.is_singleton;
3900
itype.is_ref_counted = ClassDB::is_parent_class(type_cname, name_cache.type_RefCounted);
3901
itype.memory_own = itype.is_ref_counted;
3902
3903
if (itype.class_doc) {
3904
itype.is_deprecated = itype.class_doc->is_deprecated;
3905
itype.deprecation_message = itype.class_doc->deprecated_message;
3906
3907
if (itype.is_deprecated && itype.deprecation_message.is_empty()) {
3908
WARN_PRINT("An empty deprecation message is discouraged. Type: '" + itype.proxy_name + "'.");
3909
itype.deprecation_message = "This class is deprecated.";
3910
}
3911
}
3912
3913
if (itype.is_singleton && compat_singletons.has(itype.cname)) {
3914
itype.is_singleton = false;
3915
itype.is_compat_singleton = true;
3916
}
3917
3918
itype.c_out = "%5return ";
3919
itype.c_out += C_METHOD_UNMANAGED_GET_MANAGED;
3920
itype.c_out += itype.is_ref_counted ? "(%1.Reference);\n" : "(%1);\n";
3921
3922
itype.cs_type = itype.proxy_name;
3923
3924
itype.cs_in_expr = "GodotObject." CS_STATIC_METHOD_GETINSTANCE "(%0)";
3925
3926
itype.cs_out = "%5return (%2)%0(%1);";
3927
3928
itype.c_arg_in = "&%s";
3929
itype.c_type = "IntPtr";
3930
itype.c_type_in = itype.c_type;
3931
itype.c_type_out = "GodotObject";
3932
3933
// Populate properties
3934
3935
List<PropertyInfo> property_list;
3936
ClassDB::get_property_list(type_cname, &property_list, true);
3937
3938
HashMap<StringName, StringName> accessor_methods;
3939
3940
for (const PropertyInfo &property : property_list) {
3941
if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY || (property.type == Variant::NIL && property.usage & PROPERTY_USAGE_ARRAY)) {
3942
continue;
3943
}
3944
3945
if (property.name.contains_char('/')) {
3946
// Ignore properties with '/' (slash) in the name. These are only meant for use in the inspector.
3947
continue;
3948
}
3949
3950
PropertyInterface iprop;
3951
iprop.cname = property.name;
3952
iprop.setter = ClassDB::get_property_setter(type_cname, iprop.cname);
3953
iprop.getter = ClassDB::get_property_getter(type_cname, iprop.cname);
3954
3955
// If the property is internal hide it; otherwise, hide the getter and setter.
3956
if (property.usage & PROPERTY_USAGE_INTERNAL) {
3957
iprop.is_hidden = true;
3958
} else {
3959
if (iprop.setter != StringName()) {
3960
accessor_methods[iprop.setter] = iprop.cname;
3961
}
3962
if (iprop.getter != StringName()) {
3963
accessor_methods[iprop.getter] = iprop.cname;
3964
}
3965
}
3966
3967
bool valid = false;
3968
iprop.index = ClassDB::get_property_index(type_cname, iprop.cname, &valid);
3969
ERR_FAIL_COND_V_MSG(!valid, false, "Invalid property: '" + itype.name + "." + String(iprop.cname) + "'.");
3970
3971
iprop.proxy_name = escape_csharp_keyword(snake_to_pascal_case(iprop.cname));
3972
3973
// Prevent the property and its enclosing type from sharing the same name
3974
if (iprop.proxy_name == itype.proxy_name) {
3975
_log("Name of property '%s' is ambiguous with the name of its enclosing class '%s'. Renaming property to '%s_'\n",
3976
iprop.proxy_name.utf8().get_data(), itype.proxy_name.utf8().get_data(), iprop.proxy_name.utf8().get_data());
3977
3978
iprop.proxy_name += "_";
3979
}
3980
3981
iprop.prop_doc = nullptr;
3982
3983
for (int i = 0; i < itype.class_doc->properties.size(); i++) {
3984
const DocData::PropertyDoc &prop_doc = itype.class_doc->properties[i];
3985
3986
if (prop_doc.name == iprop.cname) {
3987
iprop.prop_doc = &prop_doc;
3988
break;
3989
}
3990
}
3991
3992
if (iprop.prop_doc) {
3993
iprop.is_deprecated = iprop.prop_doc->is_deprecated;
3994
iprop.deprecation_message = iprop.prop_doc->deprecated_message;
3995
3996
if (iprop.is_deprecated && iprop.deprecation_message.is_empty()) {
3997
WARN_PRINT("An empty deprecation message is discouraged. Property: '" + itype.proxy_name + "." + iprop.proxy_name + "'.");
3998
iprop.deprecation_message = "This property is deprecated.";
3999
}
4000
}
4001
4002
itype.properties.push_back(iprop);
4003
}
4004
4005
// Populate methods
4006
4007
List<MethodInfo> virtual_method_list;
4008
ClassDB::get_virtual_methods(type_cname, &virtual_method_list, true);
4009
4010
List<Pair<MethodInfo, uint32_t>> method_list_with_hashes;
4011
ClassDB::get_method_list_with_compatibility(type_cname, &method_list_with_hashes, true);
4012
method_list_with_hashes.sort_custom<SortMethodWithHashes>();
4013
4014
List<MethodInterface> compat_methods;
4015
for (const Pair<MethodInfo, uint32_t> &E : method_list_with_hashes) {
4016
const MethodInfo &method_info = E.first;
4017
const uint32_t hash = E.second;
4018
4019
if (method_info.name.is_empty()) {
4020
continue;
4021
}
4022
4023
String cname = method_info.name;
4024
4025
if (blacklisted_methods.find(itype.cname) && blacklisted_methods[itype.cname].find(cname)) {
4026
continue;
4027
}
4028
4029
if (method_has_ptr_parameter(method_info)) {
4030
// Pointers are not supported.
4031
itype.ignored_members.insert(method_info.name);
4032
continue;
4033
}
4034
4035
MethodInterface imethod;
4036
imethod.name = method_info.name;
4037
imethod.cname = cname;
4038
imethod.hash = hash;
4039
4040
if (method_info.flags & METHOD_FLAG_STATIC) {
4041
imethod.is_static = true;
4042
}
4043
4044
if (method_info.flags & METHOD_FLAG_VIRTUAL) {
4045
imethod.is_virtual = true;
4046
itype.has_virtual_methods = true;
4047
}
4048
4049
PropertyInfo return_info = method_info.return_val;
4050
4051
MethodBind *m = nullptr;
4052
4053
if (!imethod.is_virtual) {
4054
bool method_exists = false;
4055
m = ClassDB::get_method_with_compatibility(type_cname, method_info.name, hash, &method_exists, &imethod.is_compat);
4056
4057
if (unlikely(!method_exists)) {
4058
ERR_FAIL_COND_V_MSG(!virtual_method_list.find(method_info), false,
4059
"Missing MethodBind for non-virtual method: '" + itype.name + "." + imethod.name + "'.");
4060
}
4061
}
4062
4063
imethod.is_vararg = m && m->is_vararg();
4064
4065
if (!m && !imethod.is_virtual) {
4066
ERR_FAIL_COND_V_MSG(!virtual_method_list.find(method_info), false,
4067
"Missing MethodBind for non-virtual method: '" + itype.name + "." + imethod.name + "'.");
4068
4069
// A virtual method without the virtual flag. This is a special case.
4070
4071
// There is no method bind, so let's fallback to Godot's object.Call(string, params)
4072
imethod.requires_object_call = true;
4073
4074
// The method Object.free is registered as a virtual method, but without the virtual flag.
4075
// This is because this method is not supposed to be overridden, but called.
4076
// We assume the return type is void.
4077
imethod.return_type.cname = name_cache.type_void;
4078
4079
// Actually, more methods like this may be added in the future, which could return
4080
// something different. Let's put this check to notify us if that ever happens.
4081
if (itype.cname != name_cache.type_Object || imethod.name != "free") {
4082
WARN_PRINT("Notification: New unexpected virtual non-overridable method found."
4083
" We only expected Object.free, but found '" +
4084
itype.name + "." + imethod.name + "'.");
4085
}
4086
} else if (return_info.type == Variant::INT && return_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) {
4087
imethod.return_type.cname = return_info.class_name;
4088
imethod.return_type.is_enum = true;
4089
} else if (return_info.class_name != StringName()) {
4090
imethod.return_type.cname = return_info.class_name;
4091
4092
bool bad_reference_hint = !imethod.is_virtual && return_info.hint != PROPERTY_HINT_RESOURCE_TYPE &&
4093
ClassDB::is_parent_class(return_info.class_name, name_cache.type_RefCounted);
4094
ERR_FAIL_COND_V_MSG(bad_reference_hint, false,
4095
String() + "Return type is reference but hint is not '" _STR(PROPERTY_HINT_RESOURCE_TYPE) "'." +
4096
" Are you returning a reference type by pointer? Method: '" + itype.name + "." + imethod.name + "'.");
4097
} else if (return_info.type == Variant::ARRAY && return_info.hint == PROPERTY_HINT_ARRAY_TYPE) {
4098
imethod.return_type.cname = Variant::get_type_name(return_info.type) + "_@generic";
4099
imethod.return_type.generic_type_parameters.push_back(TypeReference(return_info.hint_string));
4100
} else if (return_info.type == Variant::DICTIONARY && return_info.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
4101
imethod.return_type.cname = Variant::get_type_name(return_info.type) + "_@generic";
4102
Vector<String> split = return_info.hint_string.split(";");
4103
imethod.return_type.generic_type_parameters.push_back(TypeReference(split.get(0)));
4104
imethod.return_type.generic_type_parameters.push_back(TypeReference(split.get(1)));
4105
} else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) {
4106
imethod.return_type.cname = return_info.hint_string;
4107
} else if (return_info.type == Variant::NIL && return_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
4108
imethod.return_type.cname = name_cache.type_Variant;
4109
} else if (return_info.type == Variant::NIL) {
4110
imethod.return_type.cname = name_cache.type_void;
4111
} else {
4112
imethod.return_type.cname = _get_type_name_from_meta(return_info.type, m ? m->get_argument_meta(-1) : (GodotTypeInfo::Metadata)method_info.return_val_metadata);
4113
}
4114
4115
for (int64_t idx = 0; idx < method_info.arguments.size(); ++idx) {
4116
const PropertyInfo &arginfo = method_info.arguments[idx];
4117
4118
String orig_arg_name = arginfo.name;
4119
4120
ArgumentInterface iarg;
4121
iarg.name = orig_arg_name;
4122
4123
if (arginfo.type == Variant::INT && arginfo.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) {
4124
iarg.type.cname = arginfo.class_name;
4125
iarg.type.is_enum = true;
4126
} else if (arginfo.class_name != StringName()) {
4127
iarg.type.cname = arginfo.class_name;
4128
} else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
4129
iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";
4130
iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string));
4131
} else if (arginfo.type == Variant::DICTIONARY && arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
4132
iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";
4133
Vector<String> split = arginfo.hint_string.split(";");
4134
iarg.type.generic_type_parameters.push_back(TypeReference(split.get(0)));
4135
iarg.type.generic_type_parameters.push_back(TypeReference(split.get(1)));
4136
} else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
4137
iarg.type.cname = arginfo.hint_string;
4138
} else if (arginfo.type == Variant::NIL) {
4139
iarg.type.cname = name_cache.type_Variant;
4140
} else {
4141
iarg.type.cname = _get_type_name_from_meta(arginfo.type, m ? m->get_argument_meta(idx) : (GodotTypeInfo::Metadata)method_info.get_argument_meta(idx));
4142
}
4143
4144
iarg.name = escape_csharp_keyword(snake_to_camel_case(iarg.name));
4145
4146
if (m && m->has_default_argument(idx)) {
4147
bool defval_ok = _arg_default_value_from_variant(m->get_default_argument(idx), iarg);
4148
ERR_FAIL_COND_V_MSG(!defval_ok, false,
4149
"Cannot determine default value for argument '" + orig_arg_name + "' of method '" + itype.name + "." + imethod.name + "'.");
4150
}
4151
4152
imethod.add_argument(iarg);
4153
}
4154
4155
if (imethod.is_vararg) {
4156
ArgumentInterface ivararg;
4157
ivararg.type.cname = name_cache.type_VarArg;
4158
ivararg.name = "@args";
4159
imethod.add_argument(ivararg);
4160
}
4161
4162
imethod.proxy_name = escape_csharp_keyword(snake_to_pascal_case(imethod.name));
4163
4164
// Prevent the method and its enclosing type from sharing the same name
4165
if (imethod.proxy_name == itype.proxy_name) {
4166
_log("Name of method '%s' is ambiguous with the name of its enclosing class '%s'. Renaming method to '%s_'\n",
4167
imethod.proxy_name.utf8().get_data(), itype.proxy_name.utf8().get_data(), imethod.proxy_name.utf8().get_data());
4168
4169
imethod.proxy_name += "_";
4170
}
4171
4172
HashMap<StringName, StringName>::Iterator accessor = accessor_methods.find(imethod.cname);
4173
if (accessor) {
4174
// We only hide an accessor method if it's in the same class as the property.
4175
// It's easier this way, but also we don't know if an accessor method in a different class
4176
// could have other purposes, so better leave those untouched.
4177
imethod.is_hidden = true;
4178
}
4179
4180
if (itype.class_doc) {
4181
for (int i = 0; i < itype.class_doc->methods.size(); i++) {
4182
if (itype.class_doc->methods[i].name == imethod.name) {
4183
imethod.method_doc = &itype.class_doc->methods[i];
4184
break;
4185
}
4186
}
4187
}
4188
4189
if (imethod.method_doc) {
4190
imethod.is_deprecated = imethod.method_doc->is_deprecated;
4191
imethod.deprecation_message = imethod.method_doc->deprecated_message;
4192
4193
if (imethod.is_deprecated && imethod.deprecation_message.is_empty()) {
4194
WARN_PRINT("An empty deprecation message is discouraged. Method: '" + itype.proxy_name + "." + imethod.proxy_name + "'.");
4195
imethod.deprecation_message = "This method is deprecated.";
4196
}
4197
}
4198
4199
ERR_FAIL_COND_V_MSG(itype.find_property_by_name(imethod.cname), false,
4200
"Method name conflicts with property: '" + itype.name + "." + imethod.name + "'.");
4201
4202
// Compat methods aren't added to the type yet, they need to be checked for conflicts
4203
// after all the non-compat methods have been added. The compat methods are added in
4204
// reverse so the most recently added ones take precedence over older compat methods.
4205
if (imethod.is_compat) {
4206
// If the method references deprecated types, mark the method as deprecated as well.
4207
for (const ArgumentInterface &iarg : imethod.arguments) {
4208
String arg_type_name = iarg.type.cname;
4209
String doc_name = arg_type_name.begins_with("_") ? arg_type_name.substr(1) : arg_type_name;
4210
const DocData::ClassDoc &class_doc = EditorHelp::get_doc_data()->class_list[doc_name];
4211
if (class_doc.is_deprecated) {
4212
imethod.is_deprecated = true;
4213
imethod.deprecation_message = "This method overload is deprecated.";
4214
break;
4215
}
4216
}
4217
4218
imethod.is_hidden = true;
4219
compat_methods.push_front(imethod);
4220
continue;
4221
}
4222
4223
// Methods starting with an underscore are ignored unless they're used as a property setter or getter
4224
if (!imethod.is_virtual && imethod.name[0] == '_') {
4225
for (const PropertyInterface &iprop : itype.properties) {
4226
if (iprop.setter == imethod.name || iprop.getter == imethod.name) {
4227
imethod.is_internal = true;
4228
itype.methods.push_back(imethod);
4229
break;
4230
}
4231
}
4232
} else {
4233
itype.methods.push_back(imethod);
4234
}
4235
}
4236
4237
// Add compat methods that don't conflict with other methods in the type.
4238
for (const MethodInterface &imethod : compat_methods) {
4239
if (_method_has_conflicting_signature(imethod, itype)) {
4240
WARN_PRINT("Method '" + imethod.name + "' conflicts with an already existing method in type '" + itype.name + "' and has been ignored.");
4241
continue;
4242
}
4243
itype.methods.push_back(imethod);
4244
}
4245
4246
// Populate signals
4247
4248
const HashMap<StringName, MethodInfo> &signal_map = class_info->signal_map;
4249
4250
for (const KeyValue<StringName, MethodInfo> &E : signal_map) {
4251
SignalInterface isignal;
4252
4253
const MethodInfo &method_info = E.value;
4254
4255
isignal.name = method_info.name;
4256
isignal.cname = method_info.name;
4257
4258
for (int64_t idx = 0; idx < method_info.arguments.size(); ++idx) {
4259
const PropertyInfo &arginfo = method_info.arguments[idx];
4260
4261
String orig_arg_name = arginfo.name;
4262
4263
ArgumentInterface iarg;
4264
iarg.name = orig_arg_name;
4265
4266
if (arginfo.type == Variant::INT && arginfo.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) {
4267
iarg.type.cname = arginfo.class_name;
4268
iarg.type.is_enum = true;
4269
} else if (arginfo.class_name != StringName()) {
4270
iarg.type.cname = arginfo.class_name;
4271
} else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
4272
iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";
4273
iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string));
4274
} else if (arginfo.type == Variant::DICTIONARY && arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
4275
iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";
4276
Vector<String> split = arginfo.hint_string.split(";");
4277
iarg.type.generic_type_parameters.push_back(TypeReference(split.get(0)));
4278
iarg.type.generic_type_parameters.push_back(TypeReference(split.get(1)));
4279
} else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
4280
iarg.type.cname = arginfo.hint_string;
4281
} else if (arginfo.type == Variant::NIL) {
4282
iarg.type.cname = name_cache.type_Variant;
4283
} else {
4284
iarg.type.cname = _get_type_name_from_meta(arginfo.type, (GodotTypeInfo::Metadata)method_info.get_argument_meta(idx));
4285
}
4286
4287
iarg.name = escape_csharp_keyword(snake_to_camel_case(iarg.name));
4288
4289
isignal.add_argument(iarg);
4290
}
4291
4292
isignal.proxy_name = escape_csharp_keyword(snake_to_pascal_case(isignal.name));
4293
4294
// Prevent the signal and its enclosing type from sharing the same name
4295
if (isignal.proxy_name == itype.proxy_name) {
4296
_log("Name of signal '%s' is ambiguous with the name of its enclosing class '%s'. Renaming signal to '%s_'\n",
4297
isignal.proxy_name.utf8().get_data(), itype.proxy_name.utf8().get_data(), isignal.proxy_name.utf8().get_data());
4298
4299
isignal.proxy_name += "_";
4300
}
4301
4302
if (itype.find_property_by_proxy_name(isignal.proxy_name) || itype.find_method_by_proxy_name(isignal.proxy_name)) {
4303
// ClassDB allows signal names that conflict with method or property names.
4304
// While registering a signal with a conflicting name is considered wrong,
4305
// it may still happen and it may take some time until someone fixes the name.
4306
// We can't allow the bindings to be in a broken state while we wait for a fix;
4307
// that's why we must handle this possibility by renaming the signal.
4308
isignal.proxy_name += "Signal";
4309
}
4310
4311
if (itype.class_doc) {
4312
for (int i = 0; i < itype.class_doc->signals.size(); i++) {
4313
const DocData::MethodDoc &signal_doc = itype.class_doc->signals[i];
4314
if (signal_doc.name == isignal.name) {
4315
isignal.method_doc = &signal_doc;
4316
break;
4317
}
4318
}
4319
}
4320
4321
if (isignal.method_doc) {
4322
isignal.is_deprecated = isignal.method_doc->is_deprecated;
4323
isignal.deprecation_message = isignal.method_doc->deprecated_message;
4324
4325
if (isignal.is_deprecated && isignal.deprecation_message.is_empty()) {
4326
WARN_PRINT("An empty deprecation message is discouraged. Signal: '" + itype.proxy_name + "." + isignal.proxy_name + "'.");
4327
isignal.deprecation_message = "This signal is deprecated.";
4328
}
4329
}
4330
4331
itype.signals_.push_back(isignal);
4332
}
4333
4334
// Populate enums and constants
4335
4336
List<String> constants;
4337
ClassDB::get_integer_constant_list(type_cname, &constants, true);
4338
4339
const HashMap<StringName, ClassDB::ClassInfo::EnumInfo> &enum_map = class_info->enum_map;
4340
4341
for (const KeyValue<StringName, ClassDB::ClassInfo::EnumInfo> &E : enum_map) {
4342
StringName enum_proxy_cname = E.key;
4343
String enum_proxy_name = pascal_to_pascal_case(enum_proxy_cname.operator String());
4344
if (itype.find_property_by_proxy_name(enum_proxy_name) || itype.find_method_by_proxy_name(enum_proxy_name) || itype.find_signal_by_proxy_name(enum_proxy_name)) {
4345
// In case the enum name conflicts with other PascalCase members,
4346
// we append 'Enum' to the enum name in those cases.
4347
// We have several conflicts between enums and PascalCase properties.
4348
enum_proxy_name += "Enum";
4349
enum_proxy_cname = StringName(enum_proxy_name);
4350
}
4351
EnumInterface ienum(enum_proxy_cname, enum_proxy_name, E.value.is_bitfield);
4352
const List<StringName> &enum_constants = E.value.constants;
4353
for (const StringName &constant_cname : enum_constants) {
4354
String constant_name = constant_cname.operator String();
4355
int64_t *value = class_info->constant_map.getptr(constant_cname);
4356
ERR_FAIL_NULL_V(value, false);
4357
constants.erase(constant_name);
4358
4359
ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), *value);
4360
4361
iconstant.const_doc = nullptr;
4362
for (int i = 0; i < itype.class_doc->constants.size(); i++) {
4363
const DocData::ConstantDoc &const_doc = itype.class_doc->constants[i];
4364
4365
if (const_doc.name == iconstant.name) {
4366
iconstant.const_doc = &const_doc;
4367
break;
4368
}
4369
}
4370
4371
if (iconstant.const_doc) {
4372
iconstant.is_deprecated = iconstant.const_doc->is_deprecated;
4373
iconstant.deprecation_message = iconstant.const_doc->deprecated_message;
4374
4375
if (iconstant.is_deprecated && iconstant.deprecation_message.is_empty()) {
4376
WARN_PRINT("An empty deprecation message is discouraged. Enum member: '" + itype.proxy_name + "." + ienum.proxy_name + "." + iconstant.proxy_name + "'.");
4377
iconstant.deprecation_message = "This enum member is deprecated.";
4378
}
4379
}
4380
4381
ienum.constants.push_back(iconstant);
4382
}
4383
4384
int prefix_length = _determine_enum_prefix(ienum);
4385
4386
_apply_prefix_to_enum_constants(ienum, prefix_length);
4387
4388
itype.enums.push_back(ienum);
4389
4390
TypeInterface enum_itype;
4391
enum_itype.is_enum = true;
4392
enum_itype.name = itype.name + "." + String(E.key);
4393
enum_itype.cname = StringName(enum_itype.name);
4394
enum_itype.proxy_name = itype.proxy_name + "." + enum_proxy_name;
4395
TypeInterface::postsetup_enum_type(enum_itype);
4396
enum_types.insert(enum_itype.cname, enum_itype);
4397
}
4398
4399
for (const String &constant_name : constants) {
4400
int64_t *value = class_info->constant_map.getptr(StringName(constant_name));
4401
ERR_FAIL_NULL_V(value, false);
4402
4403
String constant_proxy_name = snake_to_pascal_case(constant_name, true);
4404
4405
if (itype.find_property_by_proxy_name(constant_proxy_name) || itype.find_method_by_proxy_name(constant_proxy_name) || itype.find_signal_by_proxy_name(constant_proxy_name)) {
4406
// In case the constant name conflicts with other PascalCase members,
4407
// we append 'Constant' to the constant name in those cases.
4408
constant_proxy_name += "Constant";
4409
}
4410
4411
ConstantInterface iconstant(constant_name, constant_proxy_name, *value);
4412
4413
iconstant.const_doc = nullptr;
4414
for (int i = 0; i < itype.class_doc->constants.size(); i++) {
4415
const DocData::ConstantDoc &const_doc = itype.class_doc->constants[i];
4416
4417
if (const_doc.name == iconstant.name) {
4418
iconstant.const_doc = &const_doc;
4419
break;
4420
}
4421
}
4422
4423
if (iconstant.const_doc) {
4424
iconstant.is_deprecated = iconstant.const_doc->is_deprecated;
4425
iconstant.deprecation_message = iconstant.const_doc->deprecated_message;
4426
4427
if (iconstant.is_deprecated && iconstant.deprecation_message.is_empty()) {
4428
WARN_PRINT("An empty deprecation message is discouraged. Constant: '" + itype.proxy_name + "." + iconstant.proxy_name + "'.");
4429
iconstant.deprecation_message = "This constant is deprecated.";
4430
}
4431
}
4432
4433
itype.constants.push_back(iconstant);
4434
}
4435
4436
obj_types.insert(itype.cname, itype);
4437
4438
if (itype.is_singleton) {
4439
// Add singleton instance type.
4440
itype.proxy_name += CS_SINGLETON_INSTANCE_SUFFIX;
4441
itype.is_singleton = false;
4442
itype.is_singleton_instance = true;
4443
4444
// Remove constants and enums, those will remain in the static class.
4445
itype.constants.clear();
4446
itype.enums.clear();
4447
4448
obj_types.insert(itype.name + CS_SINGLETON_INSTANCE_SUFFIX, itype);
4449
}
4450
4451
class_list.pop_front();
4452
}
4453
4454
return true;
4455
}
4456
4457
static String _get_vector2_cs_ctor_args(const Vector2 &p_vec2) {
4458
return String::num_real(p_vec2.x, true) + "f, " +
4459
String::num_real(p_vec2.y, true) + "f";
4460
}
4461
4462
static String _get_vector3_cs_ctor_args(const Vector3 &p_vec3) {
4463
return String::num_real(p_vec3.x, true) + "f, " +
4464
String::num_real(p_vec3.y, true) + "f, " +
4465
String::num_real(p_vec3.z, true) + "f";
4466
}
4467
4468
static String _get_vector4_cs_ctor_args(const Vector4 &p_vec4) {
4469
return String::num_real(p_vec4.x, true) + "f, " +
4470
String::num_real(p_vec4.y, true) + "f, " +
4471
String::num_real(p_vec4.z, true) + "f, " +
4472
String::num_real(p_vec4.w, true) + "f";
4473
}
4474
4475
static String _get_vector2i_cs_ctor_args(const Vector2i &p_vec2i) {
4476
return itos(p_vec2i.x) + ", " + itos(p_vec2i.y);
4477
}
4478
4479
static String _get_vector3i_cs_ctor_args(const Vector3i &p_vec3i) {
4480
return itos(p_vec3i.x) + ", " + itos(p_vec3i.y) + ", " + itos(p_vec3i.z);
4481
}
4482
4483
static String _get_vector4i_cs_ctor_args(const Vector4i &p_vec4i) {
4484
return itos(p_vec4i.x) + ", " + itos(p_vec4i.y) + ", " + itos(p_vec4i.z) + ", " + itos(p_vec4i.w);
4485
}
4486
4487
static String _get_color_cs_ctor_args(const Color &p_color) {
4488
return String::num(p_color.r, 4) + "f, " +
4489
String::num(p_color.g, 4) + "f, " +
4490
String::num(p_color.b, 4) + "f, " +
4491
String::num(p_color.a, 4) + "f";
4492
}
4493
4494
bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, ArgumentInterface &r_iarg) {
4495
r_iarg.def_param_value = p_val;
4496
4497
switch (p_val.get_type()) {
4498
case Variant::NIL:
4499
// Either Object type or Variant
4500
r_iarg.default_argument = "default";
4501
break;
4502
// Atomic types
4503
case Variant::BOOL:
4504
r_iarg.default_argument = bool(p_val) ? "true" : "false";
4505
break;
4506
case Variant::INT:
4507
if (r_iarg.type.cname != name_cache.type_int) {
4508
r_iarg.default_argument = "(%s)(" + p_val.operator String() + ")";
4509
} else {
4510
r_iarg.default_argument = p_val.operator String();
4511
}
4512
break;
4513
case Variant::FLOAT:
4514
r_iarg.default_argument = p_val.operator String();
4515
4516
if (r_iarg.type.cname == name_cache.type_float) {
4517
r_iarg.default_argument += "f";
4518
}
4519
break;
4520
case Variant::STRING:
4521
case Variant::STRING_NAME:
4522
case Variant::NODE_PATH:
4523
if (r_iarg.type.cname == name_cache.type_StringName || r_iarg.type.cname == name_cache.type_NodePath) {
4524
if (r_iarg.default_argument.length() > 0) {
4525
r_iarg.default_argument = "(%s)\"" + p_val.operator String() + "\"";
4526
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF;
4527
} else {
4528
// No need for a special `in` statement to change `null` to `""`. Marshaling takes care of this already.
4529
r_iarg.default_argument = "null";
4530
}
4531
} else {
4532
CRASH_COND(r_iarg.type.cname != name_cache.type_String);
4533
r_iarg.default_argument = "\"" + p_val.operator String() + "\"";
4534
}
4535
break;
4536
case Variant::PLANE: {
4537
Plane plane = p_val.operator Plane();
4538
r_iarg.default_argument = "new Plane(new Vector3(" +
4539
_get_vector3_cs_ctor_args(plane.normal) + "), " + rtos(plane.d) + "f)";
4540
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
4541
} break;
4542
case Variant::AABB: {
4543
AABB aabb = p_val.operator ::AABB();
4544
r_iarg.default_argument = "new Aabb(new Vector3(" +
4545
_get_vector3_cs_ctor_args(aabb.position) + "), new Vector3(" +
4546
_get_vector3_cs_ctor_args(aabb.size) + "))";
4547
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
4548
} break;
4549
case Variant::RECT2: {
4550
Rect2 rect = p_val.operator Rect2();
4551
r_iarg.default_argument = "new Rect2(new Vector2(" +
4552
_get_vector2_cs_ctor_args(rect.position) + "), new Vector2(" +
4553
_get_vector2_cs_ctor_args(rect.size) + "))";
4554
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
4555
} break;
4556
case Variant::RECT2I: {
4557
Rect2i rect = p_val.operator Rect2i();
4558
r_iarg.default_argument = "new Rect2I(new Vector2I(" +
4559
_get_vector2i_cs_ctor_args(rect.position) + "), new Vector2I(" +
4560
_get_vector2i_cs_ctor_args(rect.size) + "))";
4561
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
4562
} break;
4563
case Variant::COLOR:
4564
r_iarg.default_argument = "new Color(" + _get_color_cs_ctor_args(p_val.operator Color()) + ")";
4565
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
4566
break;
4567
case Variant::VECTOR2:
4568
r_iarg.default_argument = "new Vector2(" + _get_vector2_cs_ctor_args(p_val.operator Vector2()) + ")";
4569
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
4570
break;
4571
case Variant::VECTOR2I:
4572
r_iarg.default_argument = "new Vector2I(" + _get_vector2i_cs_ctor_args(p_val.operator Vector2i()) + ")";
4573
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
4574
break;
4575
case Variant::VECTOR3:
4576
r_iarg.default_argument = "new Vector3(" + _get_vector3_cs_ctor_args(p_val.operator Vector3()) + ")";
4577
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
4578
break;
4579
case Variant::VECTOR3I:
4580
r_iarg.default_argument = "new Vector3I(" + _get_vector3i_cs_ctor_args(p_val.operator Vector3i()) + ")";
4581
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
4582
break;
4583
case Variant::VECTOR4:
4584
r_iarg.default_argument = "new Vector4(" + _get_vector4_cs_ctor_args(p_val.operator Vector4()) + ")";
4585
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
4586
break;
4587
case Variant::VECTOR4I:
4588
r_iarg.default_argument = "new Vector4I(" + _get_vector4i_cs_ctor_args(p_val.operator Vector4i()) + ")";
4589
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
4590
break;
4591
case Variant::OBJECT:
4592
ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false,
4593
"Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value.");
4594
4595
r_iarg.default_argument = "null";
4596
break;
4597
case Variant::DICTIONARY:
4598
ERR_FAIL_COND_V_MSG(!p_val.operator Dictionary().is_empty(), false,
4599
"Default value of type 'Dictionary' must be an empty dictionary.");
4600
// The [cs_in] expression already interprets null values as empty dictionaries.
4601
r_iarg.default_argument = "null";
4602
r_iarg.def_param_mode = ArgumentInterface::CONSTANT;
4603
break;
4604
case Variant::RID:
4605
ERR_FAIL_COND_V_MSG(r_iarg.type.cname != name_cache.type_RID, false,
4606
"Parameter of type '" + String(r_iarg.type.cname) + "' cannot have a default value of type '" + String(name_cache.type_RID) + "'.");
4607
4608
ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false,
4609
"Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value.");
4610
4611
r_iarg.default_argument = "default";
4612
break;
4613
case Variant::ARRAY:
4614
ERR_FAIL_COND_V_MSG(!p_val.operator Array().is_empty(), false,
4615
"Default value of type 'Array' must be an empty array.");
4616
// The [cs_in] expression already interprets null values as empty arrays.
4617
r_iarg.default_argument = "null";
4618
r_iarg.def_param_mode = ArgumentInterface::CONSTANT;
4619
break;
4620
case Variant::PACKED_BYTE_ARRAY:
4621
case Variant::PACKED_INT32_ARRAY:
4622
case Variant::PACKED_INT64_ARRAY:
4623
case Variant::PACKED_FLOAT32_ARRAY:
4624
case Variant::PACKED_FLOAT64_ARRAY:
4625
case Variant::PACKED_STRING_ARRAY:
4626
case Variant::PACKED_VECTOR2_ARRAY:
4627
case Variant::PACKED_VECTOR3_ARRAY:
4628
case Variant::PACKED_VECTOR4_ARRAY:
4629
case Variant::PACKED_COLOR_ARRAY:
4630
r_iarg.default_argument = "Array.Empty<%s>()";
4631
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF;
4632
break;
4633
case Variant::TRANSFORM2D: {
4634
Transform2D transform = p_val.operator Transform2D();
4635
if (transform == Transform2D()) {
4636
r_iarg.default_argument = "Transform2D.Identity";
4637
} else {
4638
r_iarg.default_argument = "new Transform2D(new Vector2(" +
4639
_get_vector2_cs_ctor_args(transform.columns[0]) + "), new Vector2(" +
4640
_get_vector2_cs_ctor_args(transform.columns[1]) + "), new Vector2(" +
4641
_get_vector2_cs_ctor_args(transform.columns[2]) + "))";
4642
}
4643
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
4644
} break;
4645
case Variant::TRANSFORM3D: {
4646
Transform3D transform = p_val.operator Transform3D();
4647
if (transform == Transform3D()) {
4648
r_iarg.default_argument = "Transform3D.Identity";
4649
} else {
4650
Basis basis = transform.basis;
4651
r_iarg.default_argument = "new Transform3D(new Vector3(" +
4652
_get_vector3_cs_ctor_args(basis.get_column(0)) + "), new Vector3(" +
4653
_get_vector3_cs_ctor_args(basis.get_column(1)) + "), new Vector3(" +
4654
_get_vector3_cs_ctor_args(basis.get_column(2)) + "), new Vector3(" +
4655
_get_vector3_cs_ctor_args(transform.origin) + "))";
4656
}
4657
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
4658
} break;
4659
case Variant::PROJECTION: {
4660
Projection projection = p_val.operator Projection();
4661
if (projection == Projection()) {
4662
r_iarg.default_argument = "Projection.Identity";
4663
} else {
4664
r_iarg.default_argument = "new Projection(new Vector4(" +
4665
_get_vector4_cs_ctor_args(projection.columns[0]) + "), new Vector4(" +
4666
_get_vector4_cs_ctor_args(projection.columns[1]) + "), new Vector4(" +
4667
_get_vector4_cs_ctor_args(projection.columns[2]) + "), new Vector4(" +
4668
_get_vector4_cs_ctor_args(projection.columns[3]) + "))";
4669
}
4670
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
4671
} break;
4672
case Variant::BASIS: {
4673
Basis basis = p_val.operator Basis();
4674
if (basis == Basis()) {
4675
r_iarg.default_argument = "Basis.Identity";
4676
} else {
4677
r_iarg.default_argument = "new Basis(new Vector3(" +
4678
_get_vector3_cs_ctor_args(basis.get_column(0)) + "), new Vector3(" +
4679
_get_vector3_cs_ctor_args(basis.get_column(1)) + "), new Vector3(" +
4680
_get_vector3_cs_ctor_args(basis.get_column(2)) + "))";
4681
}
4682
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
4683
} break;
4684
case Variant::QUATERNION: {
4685
Quaternion quaternion = p_val.operator Quaternion();
4686
if (quaternion == Quaternion()) {
4687
r_iarg.default_argument = "Quaternion.Identity";
4688
} else {
4689
r_iarg.default_argument = "new Quaternion(" +
4690
String::num_real(quaternion.x, false) + "f, " +
4691
String::num_real(quaternion.y, false) + "f, " +
4692
String::num_real(quaternion.z, false) + "f, " +
4693
String::num_real(quaternion.w, false) + "f)";
4694
}
4695
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
4696
} break;
4697
case Variant::CALLABLE:
4698
ERR_FAIL_COND_V_MSG(r_iarg.type.cname != name_cache.type_Callable, false,
4699
"Parameter of type '" + String(r_iarg.type.cname) + "' cannot have a default value of type '" + String(name_cache.type_Callable) + "'.");
4700
ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false,
4701
"Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value.");
4702
r_iarg.default_argument = "default";
4703
break;
4704
case Variant::SIGNAL:
4705
ERR_FAIL_COND_V_MSG(r_iarg.type.cname != name_cache.type_Signal, false,
4706
"Parameter of type '" + String(r_iarg.type.cname) + "' cannot have a default value of type '" + String(name_cache.type_Signal) + "'.");
4707
ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false,
4708
"Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value.");
4709
r_iarg.default_argument = "default";
4710
break;
4711
case Variant::VARIANT_MAX:
4712
ERR_FAIL_V_MSG(false, "Unexpected Variant type: " + itos(p_val.get_type()));
4713
break;
4714
}
4715
4716
if (r_iarg.def_param_mode == ArgumentInterface::CONSTANT && r_iarg.type.cname == name_cache.type_Variant && r_iarg.default_argument != "default") {
4717
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
4718
}
4719
4720
return true;
4721
}
4722
4723
void BindingsGenerator::_populate_builtin_type_interfaces() {
4724
builtin_types.clear();
4725
4726
TypeInterface itype;
4727
4728
#define INSERT_STRUCT_TYPE(m_type, m_proxy_name) \
4729
{ \
4730
itype = TypeInterface::create_value_type(String(#m_type), String(#m_proxy_name)); \
4731
itype.cs_in_expr = "&%0"; \
4732
itype.cs_in_expr_is_unsafe = true; \
4733
builtin_types.insert(itype.cname, itype); \
4734
}
4735
4736
INSERT_STRUCT_TYPE(Vector2, Vector2)
4737
INSERT_STRUCT_TYPE(Vector2i, Vector2I)
4738
INSERT_STRUCT_TYPE(Rect2, Rect2)
4739
INSERT_STRUCT_TYPE(Rect2i, Rect2I)
4740
INSERT_STRUCT_TYPE(Transform2D, Transform2D)
4741
INSERT_STRUCT_TYPE(Vector3, Vector3)
4742
INSERT_STRUCT_TYPE(Vector3i, Vector3I)
4743
INSERT_STRUCT_TYPE(Basis, Basis)
4744
INSERT_STRUCT_TYPE(Quaternion, Quaternion)
4745
INSERT_STRUCT_TYPE(Transform3D, Transform3D)
4746
INSERT_STRUCT_TYPE(AABB, Aabb)
4747
INSERT_STRUCT_TYPE(Color, Color)
4748
INSERT_STRUCT_TYPE(Plane, Plane)
4749
INSERT_STRUCT_TYPE(Vector4, Vector4)
4750
INSERT_STRUCT_TYPE(Vector4i, Vector4I)
4751
INSERT_STRUCT_TYPE(Projection, Projection)
4752
4753
#undef INSERT_STRUCT_TYPE
4754
4755
// bool
4756
itype = TypeInterface::create_value_type(String("bool"));
4757
itype.cs_in_expr = "%0.ToGodotBool()";
4758
itype.cs_out = "%5return %0(%1).ToBool();";
4759
itype.c_type = "godot_bool";
4760
itype.c_type_in = itype.c_type;
4761
itype.c_type_out = itype.c_type;
4762
itype.c_arg_in = "&%s";
4763
itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromBool(%1);\n";
4764
builtin_types.insert(itype.cname, itype);
4765
4766
// Integer types
4767
{
4768
// C interface for 'uint32_t' is the same as that of enums. Remember to apply
4769
// any of the changes done here to 'TypeInterface::postsetup_enum_type' as well.
4770
#define INSERT_INT_TYPE(m_name, m_int_struct_name) \
4771
{ \
4772
itype = TypeInterface::create_value_type(String(m_name)); \
4773
if (itype.name != "long" && itype.name != "ulong") { \
4774
itype.c_in = "%5%0 %1_in = %1;\n"; \
4775
itype.c_out = "%5return (%0)(%1);\n"; \
4776
itype.c_type = "long"; \
4777
itype.c_arg_in = "&%s_in"; \
4778
} else { \
4779
itype.c_arg_in = "&%s"; \
4780
} \
4781
itype.c_type_in = itype.name; \
4782
itype.c_type_out = itype.name; \
4783
itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromInt(%1);\n"; \
4784
builtin_types.insert(itype.cname, itype); \
4785
}
4786
4787
// The expected type for all integers in ptrcall is 'int64_t', so that's what we use for 'c_type'
4788
4789
INSERT_INT_TYPE("sbyte", "Int8");
4790
INSERT_INT_TYPE("short", "Int16");
4791
INSERT_INT_TYPE("int", "Int32");
4792
INSERT_INT_TYPE("long", "Int64");
4793
INSERT_INT_TYPE("byte", "UInt8");
4794
INSERT_INT_TYPE("ushort", "UInt16");
4795
INSERT_INT_TYPE("uint", "UInt32");
4796
INSERT_INT_TYPE("ulong", "UInt64");
4797
4798
#undef INSERT_INT_TYPE
4799
}
4800
4801
// Floating point types
4802
{
4803
// float
4804
itype = TypeInterface();
4805
itype.name = "float";
4806
itype.cname = itype.name;
4807
itype.proxy_name = "float";
4808
itype.cs_type = itype.proxy_name;
4809
{
4810
// The expected type for 'float' in ptrcall is 'double'
4811
itype.c_in = "%5%0 %1_in = %1;\n";
4812
itype.c_out = "%5return (%0)%1;\n";
4813
itype.c_type = "double";
4814
itype.c_arg_in = "&%s_in";
4815
}
4816
itype.c_type_in = itype.proxy_name;
4817
itype.c_type_out = itype.proxy_name;
4818
itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromFloat(%1);\n";
4819
builtin_types.insert(itype.cname, itype);
4820
4821
// double
4822
itype = TypeInterface();
4823
itype.name = "double";
4824
itype.cname = itype.name;
4825
itype.proxy_name = "double";
4826
itype.cs_type = itype.proxy_name;
4827
itype.c_type = "double";
4828
itype.c_arg_in = "&%s";
4829
itype.c_type_in = itype.proxy_name;
4830
itype.c_type_out = itype.proxy_name;
4831
itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromFloat(%1);\n";
4832
builtin_types.insert(itype.cname, itype);
4833
}
4834
4835
// String
4836
itype = TypeInterface();
4837
itype.name = "String";
4838
itype.cname = itype.name;
4839
itype.proxy_name = "string";
4840
itype.cs_type = itype.proxy_name;
4841
itype.c_in = "%5using %0 %1_in = " C_METHOD_MONOSTR_TO_GODOT "(%1);\n";
4842
itype.c_out = "%5return " C_METHOD_MONOSTR_FROM_GODOT "(%1);\n";
4843
itype.c_arg_in = "&%s_in";
4844
itype.c_type = "godot_string";
4845
itype.c_type_in = itype.cs_type;
4846
itype.c_type_out = itype.cs_type;
4847
itype.c_type_is_disposable_struct = true;
4848
itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromString(%1);\n";
4849
builtin_types.insert(itype.cname, itype);
4850
4851
// StringName
4852
itype = TypeInterface();
4853
itype.name = "StringName";
4854
itype.cname = itype.name;
4855
itype.proxy_name = "StringName";
4856
itype.cs_type = itype.proxy_name;
4857
itype.cs_in_expr = "(%1)(%0?.NativeValue ?? default)";
4858
// Cannot pass null StringName to ptrcall
4859
itype.c_out = "%5return %0.CreateTakingOwnershipOfDisposableValue(%1);\n";
4860
itype.c_arg_in = "&%s";
4861
itype.c_type = "godot_string_name";
4862
itype.c_type_in = itype.c_type;
4863
itype.c_type_out = itype.cs_type;
4864
itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromStringName(%1);\n";
4865
itype.c_type_is_disposable_struct = false; // [c_out] takes ownership
4866
itype.c_ret_needs_default_initialization = true;
4867
builtin_types.insert(itype.cname, itype);
4868
4869
// NodePath
4870
itype = TypeInterface();
4871
itype.name = "NodePath";
4872
itype.cname = itype.name;
4873
itype.proxy_name = "NodePath";
4874
itype.cs_type = itype.proxy_name;
4875
itype.cs_in_expr = "(%1)(%0?.NativeValue ?? default)";
4876
// Cannot pass null NodePath to ptrcall
4877
itype.c_out = "%5return %0.CreateTakingOwnershipOfDisposableValue(%1);\n";
4878
itype.c_arg_in = "&%s";
4879
itype.c_type = "godot_node_path";
4880
itype.c_type_in = itype.c_type;
4881
itype.c_type_out = itype.cs_type;
4882
itype.c_type_is_disposable_struct = false; // [c_out] takes ownership
4883
itype.c_ret_needs_default_initialization = true;
4884
builtin_types.insert(itype.cname, itype);
4885
4886
// RID
4887
itype = TypeInterface();
4888
itype.name = "RID";
4889
itype.cname = itype.name;
4890
itype.proxy_name = "Rid";
4891
itype.cs_type = itype.proxy_name;
4892
itype.c_arg_in = "&%s";
4893
itype.c_type = itype.cs_type;
4894
itype.c_type_in = itype.c_type;
4895
itype.c_type_out = itype.c_type;
4896
builtin_types.insert(itype.cname, itype);
4897
4898
// Variant
4899
itype = TypeInterface();
4900
itype.name = "Variant";
4901
itype.cname = itype.name;
4902
itype.proxy_name = "Variant";
4903
itype.cs_type = itype.proxy_name;
4904
itype.c_in = "%5%0 %1_in = (%0)%1.NativeVar;\n";
4905
itype.c_out = "%5return Variant.CreateTakingOwnershipOfDisposableValue(%1);\n";
4906
itype.c_arg_in = "&%s_in";
4907
itype.c_type = "godot_variant";
4908
itype.c_type_in = itype.cs_type;
4909
itype.c_type_out = itype.cs_type;
4910
itype.c_type_is_disposable_struct = false; // [c_out] takes ownership
4911
itype.c_ret_needs_default_initialization = true;
4912
builtin_types.insert(itype.cname, itype);
4913
4914
// Callable
4915
itype = TypeInterface::create_value_type(String("Callable"));
4916
itype.cs_in_expr = "%0";
4917
itype.c_in = "%5using %0 %1_in = " C_METHOD_MANAGED_TO_CALLABLE "(in %1);\n";
4918
itype.c_out = "%5return " C_METHOD_MANAGED_FROM_CALLABLE "(in %1);\n";
4919
itype.c_arg_in = "&%s_in";
4920
itype.c_type = "godot_callable";
4921
itype.c_type_in = "in " + itype.cs_type;
4922
itype.c_type_out = itype.cs_type;
4923
itype.c_type_is_disposable_struct = true;
4924
builtin_types.insert(itype.cname, itype);
4925
4926
// Signal
4927
itype = TypeInterface();
4928
itype.name = "Signal";
4929
itype.cname = itype.name;
4930
itype.proxy_name = "Signal";
4931
itype.cs_type = itype.proxy_name;
4932
itype.cs_in_expr = "%0";
4933
itype.c_in = "%5using %0 %1_in = " C_METHOD_MANAGED_TO_SIGNAL "(in %1);\n";
4934
itype.c_out = "%5return " C_METHOD_MANAGED_FROM_SIGNAL "(in %1);\n";
4935
itype.c_arg_in = "&%s_in";
4936
itype.c_type = "godot_signal";
4937
itype.c_type_in = "in " + itype.cs_type;
4938
itype.c_type_out = itype.cs_type;
4939
itype.c_type_is_disposable_struct = true;
4940
builtin_types.insert(itype.cname, itype);
4941
4942
// VarArg (fictitious type to represent variable arguments)
4943
itype = TypeInterface();
4944
itype.name = "VarArg";
4945
itype.cname = itype.name;
4946
itype.proxy_name = "ReadOnlySpan<Variant>";
4947
itype.cs_type = "params Variant[]";
4948
itype.cs_in_expr = "%0";
4949
// c_type, c_in and c_arg_in are hard-coded in the generator.
4950
// c_out and c_type_out are not applicable to VarArg.
4951
itype.c_arg_in = "&%s_in";
4952
itype.c_type_in = "ReadOnlySpan<Variant>";
4953
itype.is_span_compatible = true;
4954
builtin_types.insert(itype.cname, itype);
4955
4956
#define INSERT_ARRAY_FULL(m_name, m_type, m_managed_type, m_proxy_t) \
4957
{ \
4958
itype = TypeInterface(); \
4959
itype.name = #m_name; \
4960
itype.cname = itype.name; \
4961
itype.proxy_name = #m_proxy_t "[]"; \
4962
itype.cs_type = itype.proxy_name; \
4963
itype.c_in = "%5using %0 %1_in = " C_METHOD_MONOARRAY_TO(m_type) "(%1);\n"; \
4964
itype.c_out = "%5return " C_METHOD_MONOARRAY_FROM(m_type) "(%1);\n"; \
4965
itype.c_arg_in = "&%s_in"; \
4966
itype.c_type = #m_managed_type; \
4967
itype.c_type_in = "ReadOnlySpan<" #m_proxy_t ">"; \
4968
itype.c_type_out = itype.proxy_name; \
4969
itype.c_type_is_disposable_struct = true; \
4970
itype.is_span_compatible = true; \
4971
builtin_types.insert(itype.name, itype); \
4972
}
4973
4974
#define INSERT_ARRAY(m_type, m_managed_type, m_proxy_t) INSERT_ARRAY_FULL(m_type, m_type, m_managed_type, m_proxy_t)
4975
4976
INSERT_ARRAY(PackedInt32Array, godot_packed_int32_array, int);
4977
INSERT_ARRAY(PackedInt64Array, godot_packed_int64_array, long);
4978
INSERT_ARRAY_FULL(PackedByteArray, PackedByteArray, godot_packed_byte_array, byte);
4979
4980
INSERT_ARRAY(PackedFloat32Array, godot_packed_float32_array, float);
4981
INSERT_ARRAY(PackedFloat64Array, godot_packed_float64_array, double);
4982
4983
INSERT_ARRAY(PackedStringArray, godot_packed_string_array, string);
4984
4985
INSERT_ARRAY(PackedColorArray, godot_packed_color_array, Color);
4986
INSERT_ARRAY(PackedVector2Array, godot_packed_vector2_array, Vector2);
4987
INSERT_ARRAY(PackedVector3Array, godot_packed_vector3_array, Vector3);
4988
INSERT_ARRAY(PackedVector4Array, godot_packed_vector4_array, Vector4);
4989
4990
#undef INSERT_ARRAY
4991
4992
// Array
4993
itype = TypeInterface();
4994
itype.name = "Array";
4995
itype.cname = itype.name;
4996
itype.proxy_name = itype.name;
4997
itype.type_parameter_count = 1;
4998
itype.cs_type = BINDINGS_NAMESPACE_COLLECTIONS "." + itype.proxy_name;
4999
itype.cs_in_expr = "(%1)(%0 ?? new()).NativeValue";
5000
itype.c_out = "%5return %0.CreateTakingOwnershipOfDisposableValue(%1);\n";
5001
itype.c_arg_in = "&%s";
5002
itype.c_type = "godot_array";
5003
itype.c_type_in = itype.c_type;
5004
itype.c_type_out = itype.cs_type;
5005
itype.c_type_is_disposable_struct = false; // [c_out] takes ownership
5006
itype.c_ret_needs_default_initialization = true;
5007
builtin_types.insert(itype.cname, itype);
5008
5009
// Array_@generic
5010
// Reuse Array's itype
5011
itype.name = "Array_@generic";
5012
itype.cname = itype.name;
5013
itype.cs_out = "%5return new %2(%0(%1));";
5014
// For generic Godot collections, Variant.From<T>/As<T> is slower, so we need this special case
5015
itype.cs_variant_to_managed = "VariantUtils.ConvertToArray(%0)";
5016
itype.cs_managed_to_variant = "VariantUtils.CreateFromArray(%0)";
5017
builtin_types.insert(itype.cname, itype);
5018
5019
// Dictionary
5020
itype = TypeInterface();
5021
itype.name = "Dictionary";
5022
itype.cname = itype.name;
5023
itype.proxy_name = itype.name;
5024
itype.type_parameter_count = 2;
5025
itype.cs_type = BINDINGS_NAMESPACE_COLLECTIONS "." + itype.proxy_name;
5026
itype.cs_in_expr = "(%1)(%0 ?? new()).NativeValue";
5027
itype.c_out = "%5return %0.CreateTakingOwnershipOfDisposableValue(%1);\n";
5028
itype.c_arg_in = "&%s";
5029
itype.c_type = "godot_dictionary";
5030
itype.c_type_in = itype.c_type;
5031
itype.c_type_out = itype.cs_type;
5032
itype.c_type_is_disposable_struct = false; // [c_out] takes ownership
5033
itype.c_ret_needs_default_initialization = true;
5034
builtin_types.insert(itype.cname, itype);
5035
5036
// Dictionary_@generic
5037
// Reuse Dictionary's itype
5038
itype.name = "Dictionary_@generic";
5039
itype.cname = itype.name;
5040
itype.cs_out = "%5return new %2(%0(%1));";
5041
// For generic Godot collections, Variant.From<T>/As<T> is slower, so we need this special case
5042
itype.cs_variant_to_managed = "VariantUtils.ConvertToDictionary(%0)";
5043
itype.cs_managed_to_variant = "VariantUtils.CreateFromDictionary(%0)";
5044
builtin_types.insert(itype.cname, itype);
5045
5046
// void (fictitious type to represent the return type of methods that do not return anything)
5047
itype = TypeInterface();
5048
itype.name = "void";
5049
itype.cname = itype.name;
5050
itype.proxy_name = itype.name;
5051
itype.cs_type = itype.proxy_name;
5052
itype.c_type = itype.proxy_name;
5053
itype.c_type_in = itype.c_type;
5054
itype.c_type_out = itype.c_type;
5055
builtin_types.insert(itype.cname, itype);
5056
}
5057
5058
void BindingsGenerator::_populate_global_constants() {
5059
int global_constants_count = CoreConstants::get_global_constant_count();
5060
5061
if (global_constants_count > 0) {
5062
HashMap<String, DocData::ClassDoc>::Iterator match = EditorHelp::get_doc_data()->class_list.find("@GlobalScope");
5063
5064
CRASH_COND_MSG(!match, "Could not find '@GlobalScope' in DocData.");
5065
5066
const DocData::ClassDoc &global_scope_doc = match->value;
5067
5068
for (int i = 0; i < global_constants_count; i++) {
5069
String constant_name = CoreConstants::get_global_constant_name(i);
5070
5071
const DocData::ConstantDoc *const_doc = nullptr;
5072
for (int j = 0; j < global_scope_doc.constants.size(); j++) {
5073
const DocData::ConstantDoc &curr_const_doc = global_scope_doc.constants[j];
5074
5075
if (curr_const_doc.name == constant_name) {
5076
const_doc = &curr_const_doc;
5077
break;
5078
}
5079
}
5080
5081
int64_t constant_value = CoreConstants::get_global_constant_value(i);
5082
StringName enum_name = CoreConstants::get_global_constant_enum(i);
5083
5084
ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), constant_value);
5085
iconstant.const_doc = const_doc;
5086
5087
if (enum_name != StringName()) {
5088
EnumInterface ienum(enum_name, pascal_to_pascal_case(enum_name.operator String()), CoreConstants::is_global_constant_bitfield(i));
5089
List<EnumInterface>::Element *enum_match = global_enums.find(ienum);
5090
if (enum_match) {
5091
enum_match->get().constants.push_back(iconstant);
5092
} else {
5093
ienum.constants.push_back(iconstant);
5094
global_enums.push_back(ienum);
5095
}
5096
} else {
5097
global_constants.push_back(iconstant);
5098
}
5099
}
5100
5101
for (EnumInterface &ienum : global_enums) {
5102
TypeInterface enum_itype;
5103
enum_itype.is_enum = true;
5104
enum_itype.name = ienum.cname.operator String();
5105
enum_itype.cname = ienum.cname;
5106
enum_itype.proxy_name = ienum.proxy_name;
5107
TypeInterface::postsetup_enum_type(enum_itype);
5108
enum_types.insert(enum_itype.cname, enum_itype);
5109
5110
int prefix_length = _determine_enum_prefix(ienum);
5111
5112
// HARDCODED: The Error enum have the prefix 'ERR_' for everything except 'OK' and 'FAILED'.
5113
if (ienum.cname == name_cache.enum_Error) {
5114
if (prefix_length > 0) { // Just in case it ever changes
5115
ERR_PRINT("Prefix for enum '" _STR(Error) "' is not empty.");
5116
}
5117
5118
prefix_length = 1; // 'ERR_'
5119
}
5120
5121
_apply_prefix_to_enum_constants(ienum, prefix_length);
5122
}
5123
}
5124
5125
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
5126
if (i == Variant::OBJECT) {
5127
continue;
5128
}
5129
5130
const Variant::Type type = Variant::Type(i);
5131
5132
List<StringName> enum_names;
5133
Variant::get_enums_for_type(type, &enum_names);
5134
5135
for (const StringName &enum_name : enum_names) {
5136
TypeInterface enum_itype;
5137
enum_itype.is_enum = true;
5138
enum_itype.name = Variant::get_type_name(type) + "." + enum_name;
5139
enum_itype.cname = enum_itype.name;
5140
enum_itype.proxy_name = pascal_to_pascal_case(enum_itype.name);
5141
TypeInterface::postsetup_enum_type(enum_itype);
5142
enum_types.insert(enum_itype.cname, enum_itype);
5143
}
5144
}
5145
}
5146
5147
bool BindingsGenerator::_method_has_conflicting_signature(const MethodInterface &p_imethod, const TypeInterface &p_itype) {
5148
// Compare p_imethod with all the methods already registered in p_itype.
5149
for (const MethodInterface &method : p_itype.methods) {
5150
if (method.proxy_name == p_imethod.proxy_name) {
5151
if (_method_has_conflicting_signature(p_imethod, method)) {
5152
return true;
5153
}
5154
}
5155
}
5156
5157
return false;
5158
}
5159
5160
bool BindingsGenerator::_method_has_conflicting_signature(const MethodInterface &p_imethod_left, const MethodInterface &p_imethod_right) {
5161
// Check if a method already exists in p_itype with a method signature that would conflict with p_imethod.
5162
// The return type is ignored because only changing the return type is not enough to avoid conflicts.
5163
// The const keyword is also ignored since it doesn't generate different C# code.
5164
5165
if (p_imethod_left.arguments.size() != p_imethod_right.arguments.size()) {
5166
// Different argument count, so no conflict.
5167
return false;
5168
}
5169
5170
List<BindingsGenerator::ArgumentInterface>::ConstIterator left_itr = p_imethod_left.arguments.begin();
5171
List<BindingsGenerator::ArgumentInterface>::ConstIterator right_itr = p_imethod_right.arguments.begin();
5172
for (; left_itr != p_imethod_left.arguments.end(); ++left_itr, ++right_itr) {
5173
const ArgumentInterface &iarg_left = *left_itr;
5174
const ArgumentInterface &iarg_right = *right_itr;
5175
5176
if (iarg_left.type.cname != iarg_right.type.cname) {
5177
// Different types for arguments in the same position, so no conflict.
5178
return false;
5179
}
5180
5181
if (iarg_left.def_param_mode != iarg_right.def_param_mode) {
5182
// If the argument is a value type and nullable, it will be 'Nullable<T>' instead of 'T'
5183
// and will not create a conflict.
5184
if (iarg_left.def_param_mode == ArgumentInterface::NULLABLE_VAL || iarg_right.def_param_mode == ArgumentInterface::NULLABLE_VAL) {
5185
return false;
5186
}
5187
}
5188
}
5189
5190
return true;
5191
}
5192
5193
void BindingsGenerator::_initialize_blacklisted_methods() {
5194
blacklisted_methods["Object"].push_back("to_string"); // there is already ToString
5195
blacklisted_methods["Object"].push_back("_to_string"); // override ToString instead
5196
blacklisted_methods["Object"].push_back("_init"); // never called in C# (TODO: implement it)
5197
}
5198
5199
void BindingsGenerator::_initialize_compat_singletons() {
5200
compat_singletons.insert("EditorInterface");
5201
}
5202
5203
void BindingsGenerator::_log(const char *p_format, ...) {
5204
if (log_print_enabled) {
5205
va_list list;
5206
5207
va_start(list, p_format);
5208
OS::get_singleton()->print("%s", str_format(p_format, list).utf8().get_data());
5209
va_end(list);
5210
}
5211
}
5212
5213
void BindingsGenerator::_initialize() {
5214
initialized = false;
5215
5216
EditorHelp::generate_doc(false);
5217
5218
enum_types.clear();
5219
5220
_initialize_blacklisted_methods();
5221
5222
_initialize_compat_singletons();
5223
5224
bool obj_type_ok = _populate_object_type_interfaces();
5225
ERR_FAIL_COND_MSG(!obj_type_ok, "Failed to generate object type interfaces");
5226
5227
_populate_builtin_type_interfaces();
5228
5229
_populate_global_constants();
5230
5231
// Generate internal calls (after populating type interfaces and global constants)
5232
5233
for (const KeyValue<StringName, TypeInterface> &E : obj_types) {
5234
const TypeInterface &itype = E.value;
5235
Error err = _populate_method_icalls_table(itype);
5236
ERR_FAIL_COND_MSG(err != OK, "Failed to generate icalls table for type: " + itype.name);
5237
}
5238
5239
initialized = true;
5240
}
5241
5242
static String generate_all_glue_option = "--generate-mono-glue";
5243
5244
static void handle_cmdline_options(String glue_dir_path) {
5245
BindingsGenerator bindings_generator;
5246
bindings_generator.set_log_print_enabled(true);
5247
5248
if (!bindings_generator.is_initialized()) {
5249
ERR_PRINT("Failed to initialize the bindings generator");
5250
return;
5251
}
5252
5253
CRASH_COND(glue_dir_path.is_empty());
5254
5255
if (bindings_generator.generate_cs_api(glue_dir_path.path_join(API_SOLUTION_NAME)) != OK) {
5256
ERR_PRINT(generate_all_glue_option + ": Failed to generate the C# API.");
5257
}
5258
}
5259
5260
static void cleanup_and_exit_godot() {
5261
// Exit once done.
5262
Main::cleanup(true);
5263
::exit(0);
5264
}
5265
5266
void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) {
5267
String glue_dir_path;
5268
5269
const List<String>::Element *elem = p_cmdline_args.front();
5270
5271
while (elem) {
5272
if (elem->get() == generate_all_glue_option) {
5273
const List<String>::Element *path_elem = elem->next();
5274
5275
if (path_elem) {
5276
glue_dir_path = path_elem->get();
5277
elem = elem->next();
5278
} else {
5279
ERR_PRINT(generate_all_glue_option + ": No output directory specified (expected path to '{GODOT_ROOT}/modules/mono/glue').");
5280
// Exit once done with invalid command line arguments.
5281
cleanup_and_exit_godot();
5282
}
5283
5284
break;
5285
}
5286
5287
elem = elem->next();
5288
}
5289
5290
if (glue_dir_path.length()) {
5291
if (Engine::get_singleton()->is_editor_hint() ||
5292
Engine::get_singleton()->is_project_manager_hint()) {
5293
handle_cmdline_options(glue_dir_path);
5294
} else {
5295
// Running from a project folder, which doesn't make sense and crashes.
5296
ERR_PRINT(generate_all_glue_option + ": Cannot generate Mono glue while running a game project. Change current directory or enable --editor.");
5297
}
5298
// Exit once done.
5299
cleanup_and_exit_godot();
5300
}
5301
}
5302
5303
#endif // DEBUG_ENABLED
5304
5305