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