diff options
Diffstat (limited to 'modules/mono/editor')
-rw-r--r-- | modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs | 4 | ||||
-rw-r--r-- | modules/mono/editor/GodotSharpTools/Editor/GodotSharpExport.cs | 3 | ||||
-rw-r--r-- | modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs | 5 | ||||
-rw-r--r-- | modules/mono/editor/bindings_generator.cpp | 1578 | ||||
-rw-r--r-- | modules/mono/editor/bindings_generator.h | 116 | ||||
-rw-r--r-- | modules/mono/editor/csharp_project.cpp | 2 | ||||
-rw-r--r-- | modules/mono/editor/godotsharp_builds.cpp | 40 | ||||
-rw-r--r-- | modules/mono/editor/godotsharp_builds.h | 2 | ||||
-rw-r--r-- | modules/mono/editor/godotsharp_editor.cpp | 18 | ||||
-rw-r--r-- | modules/mono/editor/godotsharp_editor.h | 6 | ||||
-rw-r--r-- | modules/mono/editor/godotsharp_export.cpp | 29 | ||||
-rw-r--r-- | modules/mono/editor/mono_bottom_panel.cpp | 5 | ||||
-rw-r--r-- | modules/mono/editor/script_class_parser.cpp | 14 |
13 files changed, 1273 insertions, 549 deletions
diff --git a/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs b/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs index 967e3bcc19..e5044feb75 100644 --- a/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs +++ b/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs @@ -186,7 +186,7 @@ namespace GodotSharpTools.Build private string BuildArguments(string loggerAssemblyPath, string loggerOutputDir, List<string> customProperties) { - string arguments = string.Format(@"""{0}"" /v:normal /t:Build ""/p:{1}"" ""/l:{2},{3};{4}""", + string arguments = string.Format(@"""{0}"" /v:normal /t:Rebuild ""/p:{1}"" ""/l:{2},{3};{4}""", solution, "Configuration=" + config, typeof(GodotBuildLogger).FullName, @@ -196,7 +196,7 @@ namespace GodotSharpTools.Build foreach (string customProperty in customProperties) { - arguments += " \"/p:" + customProperty + "\""; + arguments += " /p:" + customProperty; } return arguments; diff --git a/modules/mono/editor/GodotSharpTools/Editor/GodotSharpExport.cs b/modules/mono/editor/GodotSharpTools/Editor/GodotSharpExport.cs index e45dd2025b..44a43f0ddd 100644 --- a/modules/mono/editor/GodotSharpTools/Editor/GodotSharpExport.cs +++ b/modules/mono/editor/GodotSharpTools/Editor/GodotSharpExport.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Runtime.CompilerServices; namespace GodotSharpTools.Editor @@ -62,7 +63,7 @@ namespace GodotSharpTools.Editor { // OSX export templates are contained in a zip, so we place // our custom template inside it and let Godot do the rest. - return !featureSet.Contains("OSX"); + return !featureSet.Any(f => new[] {"OSX", "Android"}.Contains(f)); } [MethodImpl(MethodImplOptions.InternalCall)] diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs b/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs index 89279c69a6..f4ab11a222 100644 --- a/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs +++ b/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs @@ -80,7 +80,7 @@ namespace GodotSharpTools.Project toolsGroup.AddProperty("DebugSymbols", "true"); toolsGroup.AddProperty("DebugType", "portable"); toolsGroup.AddProperty("Optimize", "false"); - toolsGroup.AddProperty("DefineConstants", "DEBUG;TOOLS;"); + toolsGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;DEBUG;TOOLS;"); toolsGroup.AddProperty("ErrorReport", "prompt"); toolsGroup.AddProperty("WarningLevel", "4"); toolsGroup.AddProperty("ConsolePause", "false"); @@ -161,7 +161,7 @@ namespace GodotSharpTools.Project debugGroup.AddProperty("DebugSymbols", "true"); debugGroup.AddProperty("DebugType", "portable"); debugGroup.AddProperty("Optimize", "false"); - debugGroup.AddProperty("DefineConstants", "DEBUG;"); + debugGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;DEBUG;"); debugGroup.AddProperty("ErrorReport", "prompt"); debugGroup.AddProperty("WarningLevel", "4"); debugGroup.AddProperty("ConsolePause", "false"); @@ -170,6 +170,7 @@ namespace GodotSharpTools.Project releaseGroup.Condition = " '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "; releaseGroup.AddProperty("DebugType", "portable"); releaseGroup.AddProperty("Optimize", "true"); + releaseGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;"); releaseGroup.AddProperty("ErrorReport", "prompt"); releaseGroup.AddProperty("WarningLevel", "4"); releaseGroup.AddProperty("ConsolePause", "false"); diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 890bea0d1d..cd7774e7a1 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -97,13 +97,19 @@ #define C_METHOD_MONOARRAY_TO(m_type) C_NS_MONOMARSHAL "::mono_array_to_" #m_type #define C_METHOD_MONOARRAY_FROM(m_type) C_NS_MONOMARSHAL "::" #m_type "_to_mono_array" -#define BINDINGS_GENERATOR_VERSION UINT32_C(7) +#define BINDINGS_GENERATOR_VERSION UINT32_C(9) -const char *BindingsGenerator::TypeInterface::DEFAULT_VARARG_C_IN = "\t%0 %1_in = %1;\n"; +const char *BindingsGenerator::TypeInterface::DEFAULT_VARARG_C_IN("\t%0 %1_in = %1;\n"); -bool BindingsGenerator::verbose_output = false; +static String fix_doc_description(const String &p_bbcode) { -BindingsGenerator *BindingsGenerator::singleton = NULL; + // This seems to be the correct way to do this. It's the same EditorHelp does. + + return p_bbcode.dedent() + .replace("\t", "") + .replace("\r", "") + .strip_edges(); +} static String snake_to_pascal_case(const String &p_identifier, bool p_input_is_upper = false) { @@ -173,6 +179,457 @@ static String snake_to_camel_case(const String &p_identifier, bool p_input_is_up return ret; } +String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype) { + + // Based on the version in EditorHelp + + if (p_bbcode.empty()) + return String(); + + DocData *doc = EditorHelp::get_doc_data(); + + String bbcode = p_bbcode; + + StringBuilder xml_output; + + xml_output.append("<para>"); + + List<String> tag_stack; + bool code_tag = false; + + int pos = 0; + while (pos < bbcode.length()) { + int brk_pos = bbcode.find("[", pos); + + if (brk_pos < 0) + brk_pos = bbcode.length(); + + if (brk_pos > pos) { + String text = bbcode.substr(pos, brk_pos - pos); + if (code_tag || tag_stack.size() > 0) { + xml_output.append(text.xml_escape()); + } else { + Vector<String> lines = text.split("\n"); + for (int i = 0; i < lines.size(); i++) { + if (i != 0) + xml_output.append("<para>"); + + xml_output.append(lines[i].xml_escape()); + + if (i != lines.size() - 1) + xml_output.append("</para>\n"); + } + } + } + + if (brk_pos == bbcode.length()) + break; // nothing else to add + + int brk_end = bbcode.find("]", brk_pos + 1); + + if (brk_end == -1) { + String text = bbcode.substr(brk_pos, bbcode.length() - brk_pos); + if (code_tag || tag_stack.size() > 0) { + xml_output.append(text.xml_escape()); + } else { + Vector<String> lines = text.split("\n"); + for (int i = 0; i < lines.size(); i++) { + if (i != 0) + xml_output.append("<para>"); + + xml_output.append(lines[i].xml_escape()); + + if (i != lines.size() - 1) + xml_output.append("</para>\n"); + } + } + + break; + } + + String tag = bbcode.substr(brk_pos + 1, brk_end - brk_pos - 1); + + if (tag.begins_with("/")) { + bool tag_ok = tag_stack.size() && tag_stack.front()->get() == tag.substr(1, tag.length()); + + if (!tag_ok) { + xml_output.append("["); + pos = brk_pos + 1; + continue; + } + + tag_stack.pop_front(); + pos = brk_end + 1; + code_tag = false; + + if (tag == "/url") { + xml_output.append("</a>"); + } else if (tag == "/code") { + xml_output.append("</c>"); + } else if (tag == "/codeblock") { + xml_output.append("</code>"); + } + } else if (code_tag) { + xml_output.append("["); + pos = brk_pos + 1; + } else if (tag.begins_with("method ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ")) { + String link_target = tag.substr(tag.find(" ") + 1, tag.length()); + String link_tag = tag.substr(0, tag.find(" ")); + + Vector<String> link_target_parts = link_target.split("."); + + if (link_target_parts.size() <= 0 || link_target_parts.size() > 2) { + ERR_PRINTS("Invalid reference format: " + tag); + + xml_output.append("<c>"); + xml_output.append(tag); + xml_output.append("</c>"); + + pos = brk_end + 1; + continue; + } + + const TypeInterface *target_itype; + StringName target_cname; + + if (link_target_parts.size() == 2) { + target_itype = _get_type_or_null(TypeReference(link_target_parts[0])); + if (!target_itype) { + target_itype = _get_type_or_null(TypeReference("_" + link_target_parts[0])); + } + target_cname = link_target_parts[1]; + } else { + target_itype = p_itype; + target_cname = link_target_parts[0]; + } + + if (link_tag == "method") { + if (!target_itype || !target_itype->is_object_type) { + if (OS::get_singleton()->is_stdout_verbose()) { + if (target_itype) { + OS::get_singleton()->print("Cannot resolve method reference for non-Godot.Object type in documentation: %s\n", link_target.utf8().get_data()); + } else { + OS::get_singleton()->print("Cannot resolve type from method reference in documentation: %s\n", link_target.utf8().get_data()); + } + } + + // TODO Map what we can + xml_output.append("<c>"); + xml_output.append(link_target); + xml_output.append("</c>"); + } else { + const MethodInterface *target_imethod = target_itype->find_method_by_name(target_cname); + + if (target_imethod) { + xml_output.append("<see cref=\"" BINDINGS_NAMESPACE "."); + xml_output.append(target_itype->proxy_name); + xml_output.append("."); + xml_output.append(target_imethod->proxy_name); + xml_output.append("\"/>"); + } + } + } else if (link_tag == "member") { + if (!target_itype || !target_itype->is_object_type) { + if (OS::get_singleton()->is_stdout_verbose()) { + if (target_itype) { + OS::get_singleton()->print("Cannot resolve member reference for non-Godot.Object type in documentation: %s\n", link_target.utf8().get_data()); + } else { + OS::get_singleton()->print("Cannot resolve type from member reference in documentation: %s\n", link_target.utf8().get_data()); + } + } + + // TODO Map what we can + xml_output.append("<c>"); + xml_output.append(link_target); + xml_output.append("</c>"); + } else { + const PropertyInterface *target_iprop = target_itype->find_property_by_name(target_cname); + + if (target_iprop) { + xml_output.append("<see cref=\"" BINDINGS_NAMESPACE "."); + xml_output.append(target_itype->proxy_name); + xml_output.append("."); + xml_output.append(target_iprop->proxy_name); + xml_output.append("\"/>"); + } + } + } else if (link_tag == "signal") { + // We do not declare signals in any way in C#, so there is nothing to reference + xml_output.append("<c>"); + xml_output.append(link_target); + xml_output.append("</c>"); + } else if (link_tag == "enum") { + StringName search_cname = !target_itype ? target_cname : + StringName(target_itype->name + "." + (String)target_cname); + + const Map<StringName, TypeInterface>::Element *enum_match = enum_types.find(search_cname); + + if (!enum_match && search_cname != target_cname) { + enum_match = enum_types.find(target_cname); + } + + if (enum_match) { + const TypeInterface &target_enum_itype = enum_match->value(); + + xml_output.append("<see cref=\"" BINDINGS_NAMESPACE "."); + xml_output.append(target_enum_itype.proxy_name); // Includes nesting class if any + xml_output.append("\"/>"); + } else { + ERR_PRINTS("Cannot resolve enum reference in documentation: " + link_target); + + xml_output.append("<c>"); + xml_output.append(link_target); + xml_output.append("</c>"); + } + } else if (link_tag == "const") { + if (!target_itype || !target_itype->is_object_type) { + if (OS::get_singleton()->is_stdout_verbose()) { + if (target_itype) { + OS::get_singleton()->print("Cannot resolve constant reference for non-Godot.Object type in documentation: %s\n", link_target.utf8().get_data()); + } else { + OS::get_singleton()->print("Cannot resolve type from constant reference in documentation: %s\n", link_target.utf8().get_data()); + } + } + + // TODO Map what we can + xml_output.append("<c>"); + xml_output.append(link_target); + xml_output.append("</c>"); + } else if (!target_itype && target_cname == name_cache.type_at_GlobalScope) { + String target_name = (String)target_cname; + + // Try to find as a global constant + const ConstantInterface *target_iconst = find_constant_by_name(target_name, global_constants); + + if (target_iconst) { + // Found global constant + xml_output.append("<see cref=\"" BINDINGS_NAMESPACE "." BINDINGS_GLOBAL_SCOPE_CLASS "."); + xml_output.append(target_iconst->proxy_name); + xml_output.append("\"/>"); + } else { + // Try to find as global enum constant + const EnumInterface *target_ienum = NULL; + + for (const List<EnumInterface>::Element *E = global_enums.front(); E; E = E->next()) { + target_ienum = &E->get(); + target_iconst = find_constant_by_name(target_name, target_ienum->constants); + if (target_iconst) + break; + } + + if (target_iconst) { + xml_output.append("<see cref=\"" BINDINGS_NAMESPACE "."); + xml_output.append(target_ienum->cname); + xml_output.append("."); + xml_output.append(target_iconst->proxy_name); + xml_output.append("\"/>"); + } else { + ERR_PRINTS("Cannot resolve global constant reference in documentation: " + link_target); + + xml_output.append("<c>"); + xml_output.append(link_target); + xml_output.append("</c>"); + } + } + } else { + String target_name = (String)target_cname; + + // Try to find the constant in the current class + const ConstantInterface *target_iconst = find_constant_by_name(target_name, target_itype->constants); + + if (target_iconst) { + // Found constant in current class + xml_output.append("<see cref=\"" BINDINGS_NAMESPACE "."); + xml_output.append(target_itype->proxy_name); + xml_output.append("."); + xml_output.append(target_iconst->proxy_name); + xml_output.append("\"/>"); + } else { + // Try to find as enum constant in the current class + const EnumInterface *target_ienum = NULL; + + for (const List<EnumInterface>::Element *E = target_itype->enums.front(); E; E = E->next()) { + target_ienum = &E->get(); + target_iconst = find_constant_by_name(target_name, target_ienum->constants); + if (target_iconst) + break; + } + + if (target_iconst) { + xml_output.append("<see cref=\"" BINDINGS_NAMESPACE "."); + xml_output.append(target_itype->proxy_name); + xml_output.append("."); + xml_output.append(target_ienum->cname); + xml_output.append("."); + xml_output.append(target_iconst->proxy_name); + xml_output.append("\"/>"); + } else { + ERR_PRINTS("Cannot resolve constant reference in documentation: " + link_target); + + xml_output.append("<c>"); + xml_output.append(link_target); + xml_output.append("</c>"); + } + } + } + } + + pos = brk_end + 1; + } else if (doc->class_list.has(tag)) { + if (tag == "Array" || tag == "Dictionary") { + xml_output.append("<see cref=\"" BINDINGS_NAMESPACE_COLLECTIONS "."); + xml_output.append(tag); + xml_output.append("\"/>"); + } else if (tag == "bool" || tag == "int") { + xml_output.append("<see cref=\""); + xml_output.append(tag); + xml_output.append("\"/>"); + } else if (tag == "float") { + xml_output.append("<see cref=\"" +#ifdef REAL_T_IS_DOUBLE + "double" +#else + "float" +#endif + "\"/>"); + } else if (tag == "Variant") { + // We use System.Object for Variant, so there is no Variant type in C# + xml_output.append("<c>Variant</c>"); + } else if (tag == "String") { + xml_output.append("<see cref=\"string\"/>"); + } else if (tag == "Nil") { + xml_output.append("<see langword=\"null\"/>"); + } else if (tag.begins_with("@")) { + // @GlobalScope, @GDScript, etc + xml_output.append("<c>"); + xml_output.append(tag); + xml_output.append("</c>"); + } else if (tag == "PoolByteArray") { + xml_output.append("<see cref=\"byte\"/>"); + } else if (tag == "PoolIntArray") { + xml_output.append("<see cref=\"int\"/>"); + } else if (tag == "PoolRealArray") { +#ifdef REAL_T_IS_DOUBLE + xml_output.append("<see cref=\"double\"/>"); +#else + xml_output.append("<see cref=\"float\"/>"); +#endif + } else if (tag == "PoolStringArray") { + xml_output.append("<see cref=\"string\"/>"); + } else if (tag == "PoolVector2Array") { + xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Vector2\"/>"); + } else if (tag == "PoolVector3Array") { + xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Vector3\"/>"); + } else if (tag == "PoolColorArray") { + xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Color\"/>"); + } else { + const TypeInterface *target_itype = _get_type_or_null(TypeReference(tag)); + + if (!target_itype) { + target_itype = _get_type_or_null(TypeReference("_" + tag)); + } + + if (target_itype) { + xml_output.append("<see cref=\"" BINDINGS_NAMESPACE "."); + xml_output.append(target_itype->proxy_name); + xml_output.append("\"/>"); + } else { + ERR_PRINTS("Cannot resolve type reference in documentation: " + tag); + + xml_output.append("<c>"); + xml_output.append(tag); + xml_output.append("</c>"); + } + } + + pos = brk_end + 1; + } else if (tag == "b") { + // bold is not supported in xml comments + pos = brk_end + 1; + tag_stack.push_front(tag); + } else if (tag == "i") { + // italics is not supported in xml comments + pos = brk_end + 1; + tag_stack.push_front(tag); + } else if (tag == "code") { + xml_output.append("<c>"); + + code_tag = true; + pos = brk_end + 1; + tag_stack.push_front(tag); + } else if (tag == "codeblock") { + xml_output.append("<code>"); + + code_tag = true; + pos = brk_end + 1; + tag_stack.push_front(tag); + } else if (tag == "center") { + // center is alignment not supported in xml comments + pos = brk_end + 1; + tag_stack.push_front(tag); + } else if (tag == "br") { + xml_output.append("\n"); // FIXME: Should use <para> instead. Luckily this tag isn't used for now. + pos = brk_end + 1; + } else if (tag == "u") { + // underline is not supported in xml comments + pos = brk_end + 1; + tag_stack.push_front(tag); + } else if (tag == "s") { + // strikethrough is not supported in xml comments + pos = brk_end + 1; + tag_stack.push_front(tag); + } else if (tag == "url") { + int end = bbcode.find("[", brk_end); + if (end == -1) + end = bbcode.length(); + String url = bbcode.substr(brk_end + 1, end - brk_end - 1); + xml_output.append("<a href=\""); + xml_output.append(url); + xml_output.append("\">"); + xml_output.append(url); + + pos = brk_end + 1; + tag_stack.push_front(tag); + } else if (tag.begins_with("url=")) { + String url = tag.substr(4, tag.length()); + xml_output.append("<a href=\""); + xml_output.append(url); + xml_output.append("\">"); + + pos = brk_end + 1; + tag_stack.push_front("url"); + } else if (tag == "img") { + int end = bbcode.find("[", brk_end); + if (end == -1) + end = bbcode.length(); + String image = bbcode.substr(brk_end + 1, end - brk_end - 1); + + // Not supported. Just append the bbcode. + xml_output.append("[img]"); + xml_output.append(image); + xml_output.append("[/img]"); + + pos = end; + tag_stack.push_front(tag); + } else if (tag.begins_with("color=")) { + // Not supported. + pos = brk_end + 1; + tag_stack.push_front("color"); + } else if (tag.begins_with("font=")) { + // Not supported. + pos = brk_end + 1; + tag_stack.push_front("font"); + } else { + xml_output.append("["); // ignore + pos = brk_pos + 1; + } + } + + xml_output.append("</para>"); + + return xml_output.as_string(); +} + int BindingsGenerator::_determine_enum_prefix(const EnumInterface &p_ienum) { CRASH_COND(p_ienum.constants.empty()); @@ -295,44 +752,47 @@ void BindingsGenerator::_generate_method_icalls(const TypeInterface &p_itype) { } } -void BindingsGenerator::_generate_global_constants(List<String> &p_output) { +void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { // Constants (in partial GD class) - p_output.push_back("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); - p_output.push_back(INDENT1 "public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n" INDENT1 "{"); + p_output.append("\n#pragma warning disable CS1591 // Disable warning: " + "'Missing XML comment for publicly visible type or member'\n"); + + p_output.append("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); + p_output.append(INDENT1 "public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n" INDENT1 "{"); for (const List<ConstantInterface>::Element *E = global_constants.front(); E; E = E->next()) { const ConstantInterface &iconstant = E->get(); if (iconstant.const_doc && iconstant.const_doc->description.size()) { - p_output.push_back(MEMBER_BEGIN "/// <summary>\n"); + String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), NULL); + Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); - Vector<String> description_lines = iconstant.const_doc->description.split("\n"); + if (summary_lines.size()) { + p_output.append(MEMBER_BEGIN "/// <summary>\n"); - for (int i = 0; i < description_lines.size(); i++) { - String description_line = description_lines[i].strip_edges(); - if (description_line.size()) { - p_output.push_back(INDENT2 "/// "); - p_output.push_back(description_line.xml_escape()); - p_output.push_back("\n"); + for (int i = 0; i < summary_lines.size(); i++) { + p_output.append(INDENT2 "/// "); + p_output.append(summary_lines[i]); + p_output.append("\n"); } - } - p_output.push_back(INDENT2 "/// </summary>"); + p_output.append(INDENT2 "/// </summary>"); + } } - p_output.push_back(MEMBER_BEGIN "public const int "); - p_output.push_back(iconstant.proxy_name); - p_output.push_back(" = "); - p_output.push_back(itos(iconstant.value)); - p_output.push_back(";"); + p_output.append(MEMBER_BEGIN "public const int "); + p_output.append(iconstant.proxy_name); + p_output.append(" = "); + p_output.append(itos(iconstant.value)); + p_output.append(";"); } if (!global_constants.empty()) - p_output.push_back("\n"); + p_output.append("\n"); - p_output.push_back(INDENT1 CLOSE_BLOCK); // end of GD class + p_output.append(INDENT1 CLOSE_BLOCK); // end of GD class // Enums @@ -352,58 +812,56 @@ void BindingsGenerator::_generate_global_constants(List<String> &p_output) { CRASH_COND(enum_class_name != "Variant"); // Hard-coded... - if (verbose_output) { - WARN_PRINTS("Declaring global enum `" + enum_proxy_name + "` inside static class `" + enum_class_name + "`"); - } + _log("Declaring global enum `%s` inside static class `%s`\n", enum_proxy_name.utf8().get_data(), enum_class_name.utf8().get_data()); - p_output.push_back("\n" INDENT1 "public static partial class "); - p_output.push_back(enum_class_name); - p_output.push_back("\n" INDENT1 OPEN_BLOCK); + p_output.append("\n" INDENT1 "public static partial class "); + p_output.append(enum_class_name); + p_output.append("\n" INDENT1 OPEN_BLOCK); } - p_output.push_back("\n" INDENT1 "public enum "); - p_output.push_back(enum_proxy_name); - p_output.push_back("\n" INDENT1 OPEN_BLOCK); + p_output.append("\n" INDENT1 "public enum "); + p_output.append(enum_proxy_name); + p_output.append("\n" INDENT1 OPEN_BLOCK); for (const List<ConstantInterface>::Element *F = ienum.constants.front(); F; F = F->next()) { const ConstantInterface &iconstant = F->get(); if (iconstant.const_doc && iconstant.const_doc->description.size()) { - p_output.push_back(INDENT2 "/// <summary>\n"); + String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), NULL); + Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); - Vector<String> description_lines = iconstant.const_doc->description.split("\n"); + if (summary_lines.size()) { + p_output.append(INDENT2 "/// <summary>\n"); - for (int i = 0; i < description_lines.size(); i++) { - String description_line = description_lines[i].strip_edges(); - if (description_line.size()) { - p_output.push_back(INDENT2 "/// "); - p_output.push_back(description_line.xml_escape()); - p_output.push_back("\n"); + for (int i = 0; i < summary_lines.size(); i++) { + p_output.append(INDENT2 "/// "); + p_output.append(summary_lines[i]); + p_output.append("\n"); } - } - p_output.push_back(INDENT2 "/// </summary>\n"); + p_output.append(INDENT2 "/// </summary>\n"); + } } - p_output.push_back(INDENT2); - p_output.push_back(iconstant.proxy_name); - p_output.push_back(" = "); - p_output.push_back(itos(iconstant.value)); - p_output.push_back(F != ienum.constants.back() ? ",\n" : "\n"); + p_output.append(INDENT2); + p_output.append(iconstant.proxy_name); + p_output.append(" = "); + p_output.append(itos(iconstant.value)); + p_output.append(F != ienum.constants.back() ? ",\n" : "\n"); } - p_output.push_back(INDENT1 CLOSE_BLOCK); + p_output.append(INDENT1 CLOSE_BLOCK); if (enum_in_static_class) - p_output.push_back(INDENT1 CLOSE_BLOCK); + p_output.append(INDENT1 CLOSE_BLOCK); } - p_output.push_back(CLOSE_BLOCK); // end of namespace -} + p_output.append(CLOSE_BLOCK); // end of namespace -Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, DotNetSolution &r_solution, bool p_verbose_output) { + p_output.append("\n#pragma warning restore CS1591\n"); +} - verbose_output = p_verbose_output; +Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, DotNetSolution &r_solution) { String proj_dir = p_solution_dir.plus_file(CORE_API_ASSEMBLY_NAME); @@ -426,7 +884,7 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, // Generate source file for global scope constants and enums { - List<String> constants_source; + StringBuilder constants_source; _generate_global_constants(constants_source); String output_file = path_join(core_dir, BINDINGS_GLOBAL_SCOPE_CLASS "_constants.cs"); Error save_err = _save_file(output_file, constants_source); @@ -484,28 +942,28 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, compile_items.push_back(output_file); } - List<String> cs_icalls_content; - - cs_icalls_content.push_back("using System;\n" - "using System.Runtime.CompilerServices;\n" - "\n"); - cs_icalls_content.push_back("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); - cs_icalls_content.push_back(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS "\n" INDENT1 OPEN_BLOCK); - - cs_icalls_content.push_back(MEMBER_BEGIN "internal static ulong godot_api_hash = "); - cs_icalls_content.push_back(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + ";\n"); - cs_icalls_content.push_back(MEMBER_BEGIN "internal static uint bindings_version = "); - cs_icalls_content.push_back(String::num_uint64(BINDINGS_GENERATOR_VERSION) + ";\n"); - cs_icalls_content.push_back(MEMBER_BEGIN "internal static uint cs_glue_version = "); - cs_icalls_content.push_back(String::num_uint64(CS_GLUE_VERSION) + ";\n"); - -#define ADD_INTERNAL_CALL(m_icall) \ - if (!m_icall.editor_only) { \ - cs_icalls_content.push_back(MEMBER_BEGIN "[MethodImpl(MethodImplOptions.InternalCall)]\n"); \ - cs_icalls_content.push_back(INDENT2 "internal extern static "); \ - cs_icalls_content.push_back(m_icall.im_type_out + " "); \ - cs_icalls_content.push_back(m_icall.name + "("); \ - cs_icalls_content.push_back(m_icall.im_sig + ");\n"); \ + StringBuilder cs_icalls_content; + + cs_icalls_content.append("using System;\n" + "using System.Runtime.CompilerServices;\n" + "\n"); + cs_icalls_content.append("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); + cs_icalls_content.append(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS "\n" INDENT1 OPEN_BLOCK); + + cs_icalls_content.append(MEMBER_BEGIN "internal static ulong godot_api_hash = "); + cs_icalls_content.append(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + ";\n"); + cs_icalls_content.append(MEMBER_BEGIN "internal static uint bindings_version = "); + cs_icalls_content.append(String::num_uint64(BINDINGS_GENERATOR_VERSION) + ";\n"); + cs_icalls_content.append(MEMBER_BEGIN "internal static uint cs_glue_version = "); + cs_icalls_content.append(String::num_uint64(CS_GLUE_VERSION) + ";\n"); + +#define ADD_INTERNAL_CALL(m_icall) \ + if (!m_icall.editor_only) { \ + cs_icalls_content.append(MEMBER_BEGIN "[MethodImpl(MethodImplOptions.InternalCall)]\n"); \ + cs_icalls_content.append(INDENT2 "internal extern static "); \ + cs_icalls_content.append(m_icall.im_type_out + " "); \ + cs_icalls_content.append(m_icall.name + "("); \ + cs_icalls_content.append(m_icall.im_sig + ");\n"); \ } for (const List<InternalCall>::Element *E = core_custom_icalls.front(); E; E = E->next()) @@ -515,7 +973,7 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, #undef ADD_INTERNAL_CALL - cs_icalls_content.push_back(INDENT1 CLOSE_BLOCK CLOSE_BLOCK); + cs_icalls_content.append(INDENT1 CLOSE_BLOCK CLOSE_BLOCK); String internal_methods_file = path_join(core_dir, BINDINGS_CLASS_NATIVECALLS ".cs"); @@ -535,15 +993,12 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, r_solution.add_new_project(CORE_API_ASSEMBLY_NAME, proj_info); - if (verbose_output) - OS::get_singleton()->print("The solution and C# project for the Core API was generated successfully\n"); + _log("The solution and C# project for the Core API was generated successfully\n"); return OK; } -Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir, DotNetSolution &r_solution, bool p_verbose_output) { - - verbose_output = p_verbose_output; +Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir, DotNetSolution &r_solution) { String proj_dir = p_solution_dir.plus_file(EDITOR_API_ASSEMBLY_NAME); @@ -582,29 +1037,29 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir compile_items.push_back(output_file); } - List<String> cs_icalls_content; - - cs_icalls_content.push_back("using System;\n" - "using System.Runtime.CompilerServices;\n" - "\n"); - cs_icalls_content.push_back("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); - cs_icalls_content.push_back(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS_EDITOR "\n" INDENT1 OPEN_BLOCK); - - cs_icalls_content.push_back(INDENT2 "internal static ulong godot_api_hash = "); - cs_icalls_content.push_back(String::num_uint64(GDMono::get_singleton()->get_api_editor_hash()) + ";\n"); - cs_icalls_content.push_back(INDENT2 "internal static uint bindings_version = "); - cs_icalls_content.push_back(String::num_uint64(BINDINGS_GENERATOR_VERSION) + ";\n"); - cs_icalls_content.push_back(INDENT2 "internal static uint cs_glue_version = "); - cs_icalls_content.push_back(String::num_uint64(CS_GLUE_VERSION) + ";\n"); - cs_icalls_content.push_back("\n"); - -#define ADD_INTERNAL_CALL(m_icall) \ - if (m_icall.editor_only) { \ - cs_icalls_content.push_back(INDENT2 "[MethodImpl(MethodImplOptions.InternalCall)]\n"); \ - cs_icalls_content.push_back(INDENT2 "internal extern static "); \ - cs_icalls_content.push_back(m_icall.im_type_out + " "); \ - cs_icalls_content.push_back(m_icall.name + "("); \ - cs_icalls_content.push_back(m_icall.im_sig + ");\n"); \ + StringBuilder cs_icalls_content; + + cs_icalls_content.append("using System;\n" + "using System.Runtime.CompilerServices;\n" + "\n"); + cs_icalls_content.append("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); + cs_icalls_content.append(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS_EDITOR "\n" INDENT1 OPEN_BLOCK); + + cs_icalls_content.append(INDENT2 "internal static ulong godot_api_hash = "); + cs_icalls_content.append(String::num_uint64(GDMono::get_singleton()->get_api_editor_hash()) + ";\n"); + cs_icalls_content.append(INDENT2 "internal static uint bindings_version = "); + cs_icalls_content.append(String::num_uint64(BINDINGS_GENERATOR_VERSION) + ";\n"); + cs_icalls_content.append(INDENT2 "internal static uint cs_glue_version = "); + cs_icalls_content.append(String::num_uint64(CS_GLUE_VERSION) + ";\n"); + cs_icalls_content.append("\n"); + +#define ADD_INTERNAL_CALL(m_icall) \ + if (m_icall.editor_only) { \ + cs_icalls_content.append(INDENT2 "[MethodImpl(MethodImplOptions.InternalCall)]\n"); \ + cs_icalls_content.append(INDENT2 "internal extern static "); \ + cs_icalls_content.append(m_icall.im_type_out + " "); \ + cs_icalls_content.append(m_icall.name + "("); \ + cs_icalls_content.append(m_icall.im_sig + ");\n"); \ } for (const List<InternalCall>::Element *E = editor_custom_icalls.front(); E; E = E->next()) @@ -614,7 +1069,7 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir #undef ADD_INTERNAL_CALL - cs_icalls_content.push_back(INDENT1 CLOSE_BLOCK CLOSE_BLOCK); + cs_icalls_content.append(INDENT1 CLOSE_BLOCK CLOSE_BLOCK); String internal_methods_file = path_join(core_dir, BINDINGS_CLASS_NATIVECALLS_EDITOR ".cs"); @@ -634,13 +1089,12 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir r_solution.add_new_project(EDITOR_API_ASSEMBLY_NAME, proj_info); - if (verbose_output) - OS::get_singleton()->print("The solution and C# project for the Editor API was generated successfully\n"); + _log("The solution and C# project for the Editor API was generated successfully\n"); return OK; } -Error BindingsGenerator::generate_cs_api(const String &p_output_dir, bool p_verbose_output) { +Error BindingsGenerator::generate_cs_api(const String &p_output_dir) { DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); ERR_FAIL_COND_V(!da, ERR_CANT_CREATE); @@ -657,13 +1111,13 @@ Error BindingsGenerator::generate_cs_api(const String &p_output_dir, bool p_verb Error proj_err; - proj_err = generate_cs_core_project(p_output_dir, solution, p_verbose_output); + proj_err = generate_cs_core_project(p_output_dir, solution); if (proj_err != OK) { ERR_PRINT("Generation of the Core API C# project failed"); return proj_err; } - proj_err = generate_cs_editor_project(p_output_dir, solution, p_verbose_output); + proj_err = generate_cs_editor_project(p_output_dir, solution); if (proj_err != OK) { ERR_PRINT("Generation of the Editor API C# project failed"); return proj_err; @@ -702,62 +1156,64 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str List<InternalCall> &custom_icalls = itype.api_type == ClassDB::API_EDITOR ? editor_custom_icalls : core_custom_icalls; - if (verbose_output) - OS::get_singleton()->print("Generating %s.cs...\n", itype.proxy_name.utf8().get_data()); + _log("Generating %s.cs...\n", itype.proxy_name.utf8().get_data()); String ctor_method(ICALL_PREFIX + itype.proxy_name + "_Ctor"); // Used only for derived types - List<String> output; + StringBuilder output; - output.push_back("using System;\n"); // IntPtr - output.push_back("using System.Diagnostics;\n"); // DebuggerBrowsable + output.append("using System;\n"); // IntPtr + output.append("using System.Diagnostics;\n"); // DebuggerBrowsable - output.push_back("\n#pragma warning disable CS1591 // Disable warning: " - "'Missing XML comment for publicly visible type or member'\n"); + output.append("\n" + "#pragma warning disable CS1591 // Disable warning: " + "'Missing XML comment for publicly visible type or member'\n" + "#pragma warning disable CS1573 // Disable warning: " + "'Parameter has no matching param tag in the XML comment'\n"); - output.push_back("\nnamespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); + output.append("\nnamespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); const DocData::ClassDoc *class_doc = itype.class_doc; if (class_doc && class_doc->description.size()) { - output.push_back(INDENT1 "/// <summary>\n"); + String xml_summary = bbcode_to_xml(fix_doc_description(class_doc->description), &itype); + Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); - Vector<String> description_lines = class_doc->description.split("\n"); + if (summary_lines.size()) { + output.append(INDENT1 "/// <summary>\n"); - for (int i = 0; i < description_lines.size(); i++) { - String description_line = description_lines[i].strip_edges(); - if (description_line.size()) { - output.push_back(INDENT1 "/// "); - output.push_back(description_line.xml_escape()); - output.push_back("\n"); + for (int i = 0; i < summary_lines.size(); i++) { + output.append(INDENT1 "/// "); + output.append(summary_lines[i]); + output.append("\n"); } - } - output.push_back(INDENT1 "/// </summary>\n"); + output.append(INDENT1 "/// </summary>\n"); + } } - output.push_back(INDENT1 "public "); + output.append(INDENT1 "public "); if (itype.is_singleton) { - output.push_back("static partial class "); + output.append("static partial class "); } else { - output.push_back(itype.is_instantiable ? "partial class " : "abstract partial class "); + output.append(itype.is_instantiable ? "partial class " : "abstract partial class "); } - output.push_back(itype.proxy_name); + output.append(itype.proxy_name); if (itype.is_singleton) { - output.push_back("\n"); + output.append("\n"); } else if (is_derived_type) { if (obj_types.has(itype.base_name)) { - output.push_back(" : "); - output.push_back(obj_types[itype.base_name].proxy_name); - output.push_back("\n"); + output.append(" : "); + output.append(obj_types[itype.base_name].proxy_name); + output.append("\n"); } else { ERR_PRINTS("Base type '" + itype.base_name.operator String() + "' does not exist, for class " + itype.name); return ERR_INVALID_DATA; } } - output.push_back(INDENT1 "{"); + output.append(INDENT1 "{"); if (class_doc) { @@ -767,31 +1223,31 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str const ConstantInterface &iconstant = E->get(); if (iconstant.const_doc && iconstant.const_doc->description.size()) { - output.push_back(MEMBER_BEGIN "/// <summary>\n"); + String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), &itype); + Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); - Vector<String> description_lines = iconstant.const_doc->description.split("\n"); + if (summary_lines.size()) { + output.append(MEMBER_BEGIN "/// <summary>\n"); - for (int i = 0; i < description_lines.size(); i++) { - String description_line = description_lines[i].strip_edges(); - if (description_line.size()) { - output.push_back(INDENT2 "/// "); - output.push_back(description_line.xml_escape()); - output.push_back("\n"); + for (int i = 0; i < summary_lines.size(); i++) { + output.append(INDENT2 "/// "); + output.append(summary_lines[i]); + output.append("\n"); } - } - output.push_back(INDENT2 "/// </summary>"); + output.append(INDENT2 "/// </summary>"); + } } - output.push_back(MEMBER_BEGIN "public const int "); - output.push_back(iconstant.proxy_name); - output.push_back(" = "); - output.push_back(itos(iconstant.value)); - output.push_back(";"); + output.append(MEMBER_BEGIN "public const int "); + output.append(iconstant.proxy_name); + output.append(" = "); + output.append(itos(iconstant.value)); + output.append(";"); } if (itype.constants.size()) - output.push_back("\n"); + output.append("\n"); // Add enums @@ -800,38 +1256,38 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str ERR_FAIL_COND_V(ienum.constants.empty(), ERR_BUG); - output.push_back(MEMBER_BEGIN "public enum "); - output.push_back(ienum.cname.operator String()); - output.push_back(MEMBER_BEGIN OPEN_BLOCK); + output.append(MEMBER_BEGIN "public enum "); + output.append(ienum.cname.operator String()); + output.append(MEMBER_BEGIN OPEN_BLOCK); for (const List<ConstantInterface>::Element *F = ienum.constants.front(); F; F = F->next()) { const ConstantInterface &iconstant = F->get(); if (iconstant.const_doc && iconstant.const_doc->description.size()) { - output.push_back(INDENT3 "/// <summary>\n"); + String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), &itype); + Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); - Vector<String> description_lines = iconstant.const_doc->description.split("\n"); + if (summary_lines.size()) { + output.append(INDENT3 "/// <summary>\n"); - for (int i = 0; i < description_lines.size(); i++) { - String description_line = description_lines[i].strip_edges(); - if (description_line.size()) { - output.push_back(INDENT3 "/// "); - output.push_back(description_line.xml_escape()); - output.push_back("\n"); + for (int i = 0; i < summary_lines.size(); i++) { + output.append(INDENT3 "/// "); + output.append(summary_lines[i]); + output.append("\n"); } - } - output.push_back(INDENT3 "/// </summary>\n"); + output.append(INDENT3 "/// </summary>\n"); + } } - output.push_back(INDENT3); - output.push_back(iconstant.proxy_name); - output.push_back(" = "); - output.push_back(itos(iconstant.value)); - output.push_back(F != ienum.constants.back() ? ",\n" : "\n"); + output.append(INDENT3); + output.append(iconstant.proxy_name); + output.append(" = "); + output.append(itos(iconstant.value)); + output.append(F != ienum.constants.back() ? ",\n" : "\n"); } - output.push_back(INDENT2 CLOSE_BLOCK); + output.append(INDENT2 CLOSE_BLOCK); } // Add properties @@ -852,53 +1308,53 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str if (itype.is_singleton) { // Add the type name and the singleton pointer as static fields - output.push_back(MEMBER_BEGIN "private static Godot.Object singleton;\n"); - output.push_back(MEMBER_BEGIN "public static Godot.Object Singleton\n" INDENT2 "{\n" INDENT3 - "get\n" INDENT3 "{\n" INDENT4 "if (singleton == null)\n" INDENT5 - "singleton = Engine.GetSingleton(" BINDINGS_NATIVE_NAME_FIELD ");\n" INDENT4 - "return singleton;\n" INDENT3 "}\n" INDENT2 "}\n"); - - output.push_back(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \""); - output.push_back(itype.name); - output.push_back("\";\n"); - - output.push_back(INDENT2 "internal static IntPtr " BINDINGS_PTR_FIELD " = "); - output.push_back(itype.api_type == ClassDB::API_EDITOR ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); - output.push_back("." ICALL_PREFIX); - output.push_back(itype.name); - output.push_back(SINGLETON_ICALL_SUFFIX "();\n"); + output.append(MEMBER_BEGIN "private static Godot.Object singleton;\n"); + output.append(MEMBER_BEGIN "public static Godot.Object Singleton\n" INDENT2 "{\n" INDENT3 + "get\n" INDENT3 "{\n" INDENT4 "if (singleton == null)\n" INDENT5 + "singleton = Engine.GetSingleton(" BINDINGS_NATIVE_NAME_FIELD ");\n" INDENT4 + "return singleton;\n" INDENT3 "}\n" INDENT2 "}\n"); + + output.append(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \""); + output.append(itype.name); + output.append("\";\n"); + + output.append(INDENT2 "internal static IntPtr " BINDINGS_PTR_FIELD " = "); + output.append(itype.api_type == ClassDB::API_EDITOR ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); + output.append("." ICALL_PREFIX); + output.append(itype.name); + output.append(SINGLETON_ICALL_SUFFIX "();\n"); } else if (is_derived_type) { // Add member fields - output.push_back(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \""); - output.push_back(itype.name); - output.push_back("\";\n"); + output.append(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \""); + output.append(itype.name); + output.append("\";\n"); // Add default constructor if (itype.is_instantiable) { - output.push_back(MEMBER_BEGIN "public "); - output.push_back(itype.proxy_name); - output.push_back("() : this("); - output.push_back(itype.memory_own ? "true" : "false"); + output.append(MEMBER_BEGIN "public "); + output.append(itype.proxy_name); + output.append("() : this("); + output.append(itype.memory_own ? "true" : "false"); // The default constructor may also be called by the engine when instancing existing native objects // The engine will initialize the pointer field of the managed side before calling the constructor // This is why we only allocate a new native object from the constructor if the pointer field is not set - output.push_back(")\n" OPEN_BLOCK_L2 "if (" BINDINGS_PTR_FIELD " == IntPtr.Zero)\n" INDENT4 BINDINGS_PTR_FIELD " = "); - output.push_back(itype.api_type == ClassDB::API_EDITOR ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); - output.push_back("." + ctor_method); - output.push_back("(this);\n" CLOSE_BLOCK_L2); + output.append(")\n" OPEN_BLOCK_L2 "if (" BINDINGS_PTR_FIELD " == IntPtr.Zero)\n" INDENT4 BINDINGS_PTR_FIELD " = "); + output.append(itype.api_type == ClassDB::API_EDITOR ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); + output.append("." + ctor_method); + output.append("(this);\n" CLOSE_BLOCK_L2); } else { // Hide the constructor - output.push_back(MEMBER_BEGIN "internal "); - output.push_back(itype.proxy_name); - output.push_back("() {}\n"); + output.append(MEMBER_BEGIN "internal "); + output.append(itype.proxy_name); + output.append("() {}\n"); } // Add.. em.. trick constructor. Sort of. - output.push_back(MEMBER_BEGIN "internal "); - output.push_back(itype.proxy_name); - output.push_back("(bool " CS_FIELD_MEMORYOWN ") : base(" CS_FIELD_MEMORYOWN ") {}\n"); + output.append(MEMBER_BEGIN "internal "); + output.append(itype.proxy_name); + output.append("(bool " CS_FIELD_MEMORYOWN ") : base(" CS_FIELD_MEMORYOWN ") {}\n"); } int method_bind_count = 0; @@ -925,15 +1381,17 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str custom_icalls.push_back(ctor_icall); } - output.push_back(INDENT1 CLOSE_BLOCK /* class */ + output.append(INDENT1 CLOSE_BLOCK /* class */ CLOSE_BLOCK /* namespace */); - output.push_back("\n#pragma warning restore CS1591\n"); + output.append("\n" + "#pragma warning restore CS1591\n" + "#pragma warning restore CS1573\n"); return _save_file(p_output_file, output); } -Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInterface &p_itype, const PropertyInterface &p_iprop, List<String> &p_output) { +Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInterface &p_itype, const PropertyInterface &p_iprop, StringBuilder &p_output) { const MethodInterface *setter = p_itype.find_method_by_name(p_iprop.setter); @@ -978,86 +1436,96 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte const TypeInterface *prop_itype = _get_type_or_null(proptype_name); ERR_FAIL_NULL_V(prop_itype, ERR_BUG); // Property type not found - String prop_proxy_name = escape_csharp_keyword(snake_to_pascal_case(p_iprop.cname)); - - // Prevent property and enclosing type from sharing the same name - if (prop_proxy_name == p_itype.proxy_name) { - if (verbose_output) { - WARN_PRINTS("Name of property `" + prop_proxy_name + "` is ambiguous with the name of its class `" + - p_itype.proxy_name + "`. Renaming property to `" + prop_proxy_name + "_`"); - } - - prop_proxy_name += "_"; - } - if (p_iprop.prop_doc && p_iprop.prop_doc->description.size()) { - p_output.push_back(MEMBER_BEGIN "/// <summary>\n"); + String xml_summary = bbcode_to_xml(fix_doc_description(p_iprop.prop_doc->description), &p_itype); + Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); - Vector<String> description_lines = p_iprop.prop_doc->description.split("\n"); + if (summary_lines.size()) { + p_output.append(MEMBER_BEGIN "/// <summary>\n"); - for (int i = 0; i < description_lines.size(); i++) { - String description_line = description_lines[i].strip_edges(); - if (description_line.size()) { - p_output.push_back(INDENT2 "/// "); - p_output.push_back(description_line.xml_escape()); - p_output.push_back("\n"); + for (int i = 0; i < summary_lines.size(); i++) { + p_output.append(INDENT2 "/// "); + p_output.append(summary_lines[i]); + p_output.append("\n"); } - } - p_output.push_back(INDENT2 "/// </summary>"); + p_output.append(INDENT2 "/// </summary>"); + } } - p_output.push_back(MEMBER_BEGIN "public "); + p_output.append(MEMBER_BEGIN "public "); if (p_itype.is_singleton) - p_output.push_back("static "); + p_output.append("static "); - p_output.push_back(prop_itype->cs_type); - p_output.push_back(" "); - p_output.push_back(prop_proxy_name.replace("/", "__")); - p_output.push_back("\n" INDENT2 OPEN_BLOCK); + p_output.append(prop_itype->cs_type); + p_output.append(" "); + p_output.append(p_iprop.proxy_name); + p_output.append("\n" INDENT2 OPEN_BLOCK); if (getter) { - p_output.push_back(INDENT3 "get\n" OPEN_BLOCK_L3); - p_output.push_back("return "); - p_output.push_back(getter->proxy_name + "("); + p_output.append(INDENT3 "get\n" + + // TODO Remove this once we make accessor methods private/internal (they will no longer be marked as obsolete after that) + "#pragma warning disable CS0618 // Disable warning about obsolete method\n" + + OPEN_BLOCK_L3); + + p_output.append("return "); + p_output.append(getter->proxy_name + "("); if (p_iprop.index != -1) { const ArgumentInterface &idx_arg = getter->arguments.front()->get(); if (idx_arg.type.cname != name_cache.type_int) { // Assume the index parameter is an enum const TypeInterface *idx_arg_type = _get_type_or_null(idx_arg.type); CRASH_COND(idx_arg_type == NULL); - p_output.push_back("(" + idx_arg_type->proxy_name + ")" + itos(p_iprop.index)); + p_output.append("(" + idx_arg_type->proxy_name + ")" + itos(p_iprop.index)); } else { - p_output.push_back(itos(p_iprop.index)); + p_output.append(itos(p_iprop.index)); } } - p_output.push_back(");\n" CLOSE_BLOCK_L3); + p_output.append(");\n" + + CLOSE_BLOCK_L3 + + // TODO Remove this once we make accessor methods private/internal (they will no longer be marked as obsolete after that) + "#pragma warning restore CS0618\n"); } if (setter) { - p_output.push_back(INDENT3 "set\n" OPEN_BLOCK_L3); - p_output.push_back(setter->proxy_name + "("); + p_output.append(INDENT3 "set\n" + + // TODO Remove this once we make accessor methods private/internal (they will no longer be marked as obsolete after that) + "#pragma warning disable CS0618 // Disable warning about obsolete method\n" + + OPEN_BLOCK_L3); + + p_output.append(setter->proxy_name + "("); if (p_iprop.index != -1) { const ArgumentInterface &idx_arg = setter->arguments.front()->get(); if (idx_arg.type.cname != name_cache.type_int) { // Assume the index parameter is an enum const TypeInterface *idx_arg_type = _get_type_or_null(idx_arg.type); CRASH_COND(idx_arg_type == NULL); - p_output.push_back("(" + idx_arg_type->proxy_name + ")" + itos(p_iprop.index) + ", "); + p_output.append("(" + idx_arg_type->proxy_name + ")" + itos(p_iprop.index) + ", "); } else { - p_output.push_back(itos(p_iprop.index) + ", "); + p_output.append(itos(p_iprop.index) + ", "); } } - p_output.push_back("value);\n" CLOSE_BLOCK_L3); + p_output.append("value);\n" + + CLOSE_BLOCK_L3 + + // TODO Remove this once we make accessor methods private/internal (they will no longer be marked as obsolete after that) + "#pragma warning restore CS0618\n"); } - p_output.push_back(CLOSE_BLOCK_L2); + p_output.append(CLOSE_BLOCK_L2); return OK; } -Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, int &p_method_bind_count, List<String> &p_output) { +Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output) { const TypeInterface *return_type = _get_type_or_placeholder(p_imethod.return_type); @@ -1069,7 +1537,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf String icall_params = method_bind_field + ", "; icall_params += sformat(p_itype.cs_in, "this"); - List<String> default_args_doc; + StringBuilder default_args_doc; // Retrieve information from the arguments for (const List<ArgumentInterface>::Element *F = p_imethod.arguments.front(); F; F = F->next()) { @@ -1135,7 +1603,10 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf icall_params += arg_type->cs_in.empty() ? arg_in : sformat(arg_type->cs_in, arg_in); - default_args_doc.push_back(INDENT2 "/// <param name=\"" + iarg.name + "\">If the param is null, then the default value is " + def_arg + "</param>\n"); + // Apparently the name attribute must not include the @ + String param_tag_name = iarg.name.begins_with("@") ? iarg.name.substr(1, iarg.name.length()) : iarg.name; + + default_args_doc.append(INDENT2 "/// <param name=\"" + param_tag_name + "\">If the parameter is null, then the default value is " + def_arg + "</param>\n"); } else { icall_params += arg_type->cs_in.empty() ? iarg.name : sformat(arg_type->cs_in, iarg.name); } @@ -1144,61 +1615,67 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf // Generate method { if (!p_imethod.is_virtual && !p_imethod.requires_object_call) { - p_output.push_back(MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN "private static IntPtr "); - p_output.push_back(method_bind_field + " = Object." ICALL_GET_METHODBIND "(" BINDINGS_NATIVE_NAME_FIELD ", \""); - p_output.push_back(p_imethod.name); - p_output.push_back("\");\n"); + p_output.append(MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN "private static IntPtr "); + p_output.append(method_bind_field + " = Object." ICALL_GET_METHODBIND "(" BINDINGS_NATIVE_NAME_FIELD ", \""); + p_output.append(p_imethod.name); + p_output.append("\");\n"); } if (p_imethod.method_doc && p_imethod.method_doc->description.size()) { - p_output.push_back(MEMBER_BEGIN "/// <summary>\n"); + String xml_summary = bbcode_to_xml(fix_doc_description(p_imethod.method_doc->description), &p_itype); + Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); - Vector<String> description_lines = p_imethod.method_doc->description.split("\n"); + if (summary_lines.size() || default_args_doc.get_string_length()) { + p_output.append(MEMBER_BEGIN "/// <summary>\n"); - for (int i = 0; i < description_lines.size(); i++) { - String description_line = description_lines[i].strip_edges(); - if (description_line.size()) { - p_output.push_back(INDENT2 "/// "); - p_output.push_back(description_line.xml_escape()); - p_output.push_back("\n"); + for (int i = 0; i < summary_lines.size(); i++) { + p_output.append(INDENT2 "/// "); + p_output.append(summary_lines[i]); + p_output.append("\n"); } - } - for (List<String>::Element *E = default_args_doc.front(); E; E = E->next()) { - p_output.push_back(E->get().xml_escape()); + p_output.append(default_args_doc.as_string()); + p_output.append(INDENT2 "/// </summary>"); } - - p_output.push_back(INDENT2 "/// </summary>"); } if (!p_imethod.is_internal) { - p_output.push_back(MEMBER_BEGIN "[GodotMethod(\""); - p_output.push_back(p_imethod.name); - p_output.push_back("\")]"); + p_output.append(MEMBER_BEGIN "[GodotMethod(\""); + p_output.append(p_imethod.name); + p_output.append("\")]"); + } + + if (p_imethod.is_deprecated) { + if (p_imethod.deprecation_message.empty()) + WARN_PRINTS("An empty deprecation message is discouraged. Method: " + p_imethod.proxy_name); + + p_output.append(MEMBER_BEGIN "[Obsolete(\""); + p_output.append(p_imethod.deprecation_message); + p_output.append("\")]"); } - p_output.push_back(MEMBER_BEGIN); - p_output.push_back(p_imethod.is_internal ? "internal " : "public "); + p_output.append(MEMBER_BEGIN); + p_output.append(p_imethod.is_internal ? "internal " : "public "); if (p_itype.is_singleton) { - p_output.push_back("static "); + p_output.append("static "); } else if (p_imethod.is_virtual) { - p_output.push_back("virtual "); + p_output.append("virtual "); } - p_output.push_back(return_type->cs_type + " "); - p_output.push_back(p_imethod.proxy_name + "("); - p_output.push_back(arguments_sig + ")\n" OPEN_BLOCK_L2); + p_output.append(return_type->cs_type + " "); + p_output.append(p_imethod.proxy_name + "("); + p_output.append(arguments_sig + ")\n" OPEN_BLOCK_L2); if (p_imethod.is_virtual) { // Godot virtual method must be overridden, therefore we return a default value by default. if (return_type->cname == name_cache.type_void) { - p_output.push_back("return;\n" CLOSE_BLOCK_L2); + p_output.append("return;\n" CLOSE_BLOCK_L2); } else { - p_output.push_back("return default("); - p_output.push_back(return_type->cs_type); - p_output.push_back(");\n" CLOSE_BLOCK_L2); + p_output.append("return default("); + p_output.append(return_type->cs_type); + p_output.append(");\n" CLOSE_BLOCK_L2); } return OK; // Won't increment method bind count @@ -1207,16 +1684,16 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf if (p_imethod.requires_object_call) { // Fallback to Godot's object.Call(string, params) - p_output.push_back(CS_METHOD_CALL "(\""); - p_output.push_back(p_imethod.name); - p_output.push_back("\""); + p_output.append(CS_METHOD_CALL "(\""); + p_output.append(p_imethod.name); + p_output.append("\""); for (const List<ArgumentInterface>::Element *F = p_imethod.arguments.front(); F; F = F->next()) { - p_output.push_back(", "); - p_output.push_back(F->get().name); + p_output.append(", "); + p_output.append(F->get().name); } - p_output.push_back(");\n" CLOSE_BLOCK_L2); + p_output.append(");\n" CLOSE_BLOCK_L2); return OK; // Won't increment method bind count } @@ -1230,37 +1707,36 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf im_call += "." + im_icall->name + "(" + icall_params + ")"; if (p_imethod.arguments.size()) - p_output.push_back(cs_in_statements); + p_output.append(cs_in_statements); if (return_type->cname == name_cache.type_void) { - p_output.push_back(im_call + ";\n"); + p_output.append(im_call + ";\n"); } else if (return_type->cs_out.empty()) { - p_output.push_back("return " + im_call + ";\n"); + p_output.append("return " + im_call + ";\n"); } else { - p_output.push_back(sformat(return_type->cs_out, im_call, return_type->cs_type, return_type->im_type_out)); - p_output.push_back("\n"); + p_output.append(sformat(return_type->cs_out, im_call, return_type->cs_type, return_type->im_type_out)); + p_output.append("\n"); } - p_output.push_back(CLOSE_BLOCK_L2); + p_output.append(CLOSE_BLOCK_L2); } p_method_bind_count++; + return OK; } Error BindingsGenerator::generate_glue(const String &p_output_dir) { - verbose_output = true; - bool dir_exists = DirAccess::exists(p_output_dir); ERR_EXPLAIN("The output directory does not exist."); ERR_FAIL_COND_V(!dir_exists, ERR_FILE_BAD_PATH); - List<String> output; + StringBuilder output; - output.push_back("/* THIS FILE IS GENERATED DO NOT EDIT */\n"); - output.push_back("#include \"" GLUE_HEADER_FILE "\"\n"); - output.push_back("\n#ifdef MONO_GLUE_ENABLED\n"); + output.append("/* THIS FILE IS GENERATED DO NOT EDIT */\n"); + output.append("#include \"" GLUE_HEADER_FILE "\"\n"); + output.append("\n#ifdef MONO_GLUE_ENABLED\n"); generated_icall_funcs.clear(); @@ -1300,11 +1776,11 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) { if (!find_icall_by_name(singleton_icall.name, custom_icalls)) custom_icalls.push_back(singleton_icall); - output.push_back("Object* "); - output.push_back(singleton_icall_name); - output.push_back("() " OPEN_BLOCK "\treturn Engine::get_singleton()->get_singleton_object(\""); - output.push_back(itype.proxy_name); - output.push_back("\");\n" CLOSE_BLOCK "\n"); + output.append("Object* "); + output.append(singleton_icall_name); + output.append("() " OPEN_BLOCK "\treturn Engine::get_singleton()->get_singleton_object(\""); + output.append(itype.proxy_name); + output.append("\");\n" CLOSE_BLOCK "\n"); } if (is_derived_type && itype.is_instantiable) { @@ -1313,43 +1789,43 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) { if (!find_icall_by_name(ctor_icall.name, custom_icalls)) custom_icalls.push_back(ctor_icall); - output.push_back("Object* "); - output.push_back(ctor_method); - output.push_back("(MonoObject* obj) " OPEN_BLOCK - "\t" C_MACRO_OBJECT_CONSTRUCT "(instance, \""); - output.push_back(itype.name); - output.push_back("\");\n" - "\t" C_METHOD_TIE_MANAGED_TO_UNMANAGED "(obj, instance);\n" - "\treturn instance;\n" CLOSE_BLOCK "\n"); + output.append("Object* "); + output.append(ctor_method); + output.append("(MonoObject* obj) " OPEN_BLOCK + "\t" C_MACRO_OBJECT_CONSTRUCT "(instance, \""); + output.append(itype.name); + output.append("\");\n" + "\t" C_METHOD_TIE_MANAGED_TO_UNMANAGED "(obj, instance);\n" + "\treturn instance;\n" CLOSE_BLOCK "\n"); } } - output.push_back("namespace GodotSharpBindings\n" OPEN_BLOCK "\n"); + output.append("namespace GodotSharpBindings\n" OPEN_BLOCK "\n"); - output.push_back("uint64_t get_core_api_hash() { return "); - output.push_back(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + "U; }\n"); + output.append("uint64_t get_core_api_hash() { return "); + output.append(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + "U; }\n"); - output.push_back("#ifdef TOOLS_ENABLED\n" - "uint64_t get_editor_api_hash() { return "); - output.push_back(String::num_uint64(GDMono::get_singleton()->get_api_editor_hash()) + "U; }\n"); - output.push_back("#endif // TOOLS_ENABLED\n"); + output.append("#ifdef TOOLS_ENABLED\n" + "uint64_t get_editor_api_hash() { return "); + output.append(String::num_uint64(GDMono::get_singleton()->get_api_editor_hash()) + "U; }\n"); + output.append("#endif // TOOLS_ENABLED\n"); - output.push_back("uint32_t get_bindings_version() { return "); - output.push_back(String::num_uint64(BINDINGS_GENERATOR_VERSION) + "; }\n"); + output.append("uint32_t get_bindings_version() { return "); + output.append(String::num_uint64(BINDINGS_GENERATOR_VERSION) + "; }\n"); - output.push_back("\nvoid register_generated_icalls() " OPEN_BLOCK); - output.push_back("\tgodot_register_glue_header_icalls();\n"); + output.append("\nvoid register_generated_icalls() " OPEN_BLOCK); + output.append("\tgodot_register_glue_header_icalls();\n"); -#define ADD_INTERNAL_CALL_REGISTRATION(m_icall) \ - { \ - output.push_back("\tmono_add_internal_call("); \ - output.push_back("\"" BINDINGS_NAMESPACE "."); \ - output.push_back(m_icall.editor_only ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); \ - output.push_back("::"); \ - output.push_back(m_icall.name); \ - output.push_back("\", (void*)"); \ - output.push_back(m_icall.name); \ - output.push_back(");\n"); \ +#define ADD_INTERNAL_CALL_REGISTRATION(m_icall) \ + { \ + output.append("\tmono_add_internal_call("); \ + output.append("\"" BINDINGS_NAMESPACE "."); \ + output.append(m_icall.editor_only ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); \ + output.append("::"); \ + output.append(m_icall.name); \ + output.append("\", (void*)"); \ + output.append(m_icall.name); \ + output.append(");\n"); \ } bool tools_sequence = false; @@ -1358,11 +1834,11 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) { if (tools_sequence) { if (!E->get().editor_only) { tools_sequence = false; - output.push_back("#endif\n"); + output.append("#endif\n"); } } else { if (E->get().editor_only) { - output.push_back("#ifdef TOOLS_ENABLED\n"); + output.append("#ifdef TOOLS_ENABLED\n"); tools_sequence = true; } } @@ -1372,23 +1848,23 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) { if (tools_sequence) { tools_sequence = false; - output.push_back("#endif\n"); + output.append("#endif\n"); } - output.push_back("#ifdef TOOLS_ENABLED\n"); + output.append("#ifdef TOOLS_ENABLED\n"); for (const List<InternalCall>::Element *E = editor_custom_icalls.front(); E; E = E->next()) ADD_INTERNAL_CALL_REGISTRATION(E->get()); - output.push_back("#endif // TOOLS_ENABLED\n"); + output.append("#endif // TOOLS_ENABLED\n"); for (const List<InternalCall>::Element *E = method_icalls.front(); E; E = E->next()) { if (tools_sequence) { if (!E->get().editor_only) { tools_sequence = false; - output.push_back("#endif\n"); + output.append("#endif\n"); } } else { if (E->get().editor_only) { - output.push_back("#ifdef TOOLS_ENABLED\n"); + output.append("#ifdef TOOLS_ENABLED\n"); tools_sequence = true; } } @@ -1398,14 +1874,14 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) { if (tools_sequence) { tools_sequence = false; - output.push_back("#endif\n"); + output.append("#endif\n"); } #undef ADD_INTERNAL_CALL_REGISTRATION - output.push_back(CLOSE_BLOCK "\n} // namespace GodotSharpBindings\n"); + output.append(CLOSE_BLOCK "\n} // namespace GodotSharpBindings\n"); - output.push_back("\n#endif // MONO_GLUE_ENABLED\n"); + output.append("\n#endif // MONO_GLUE_ENABLED\n"); Error save_err = _save_file(path_join(p_output_dir, "mono_glue.gen.cpp"), output); if (save_err != OK) @@ -1420,23 +1896,20 @@ uint32_t BindingsGenerator::get_version() { return BINDINGS_GENERATOR_VERSION; } -Error BindingsGenerator::_save_file(const String &p_path, const List<String> &p_content) { +Error BindingsGenerator::_save_file(const String &p_path, const StringBuilder &p_content) { FileAccessRef file = FileAccess::open(p_path, FileAccess::WRITE); ERR_EXPLAIN("Cannot open file: " + p_path); ERR_FAIL_COND_V(!file, ERR_FILE_CANT_WRITE); - for (const List<String>::Element *E = p_content.front(); E; E = E->next()) { - file->store_string(E->get()); - } - + file->store_string(p_content.as_string()); file->close(); return OK; } -Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, List<String> &p_output) { +Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, StringBuilder &p_output) { if (p_imethod.is_virtual) return OK; // Ignore @@ -1492,15 +1965,15 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte generated_icall_funcs.push_back(im_icall); if (im_icall->editor_only) - p_output.push_back("#ifdef TOOLS_ENABLED\n"); + p_output.append("#ifdef TOOLS_ENABLED\n"); // Generate icall function - p_output.push_back(ret_void ? "void " : return_type->c_type_out + " "); - p_output.push_back(icall_method); - p_output.push_back("("); - p_output.push_back(c_func_sig); - p_output.push_back(") " OPEN_BLOCK); + p_output.append(ret_void ? "void " : return_type->c_type_out + " "); + p_output.append(icall_method); + p_output.append("("); + p_output.append(c_func_sig); + p_output.append(") " OPEN_BLOCK); String fail_ret = ret_void ? "" : ", " + (return_type->c_type_out.ends_with("*") ? "NULL" : return_type->c_type_out + "()"); @@ -1514,7 +1987,7 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte // the Variant alive until the method returns. Otherwise, if the returned Variant holds a RefPtr, // it could be deleted too early. This is the case with GDScript.new() which returns OBJECT. // Alternatively, we could just return Variant, but that would result in a worse API. - p_output.push_back("\tVariant " C_LOCAL_VARARG_RET ";\n"); + p_output.append("\tVariant " C_LOCAL_VARARG_RET ";\n"); } if (return_type->is_object_type) { @@ -1524,90 +1997,82 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte ptrcall_return_type = return_type->c_type; } - p_output.push_back("\t" + ptrcall_return_type); - p_output.push_back(" " C_LOCAL_RET); - p_output.push_back(initialization + ";\n"); - p_output.push_back("\tERR_FAIL_NULL_V(" CS_PARAM_INSTANCE); - p_output.push_back(fail_ret); - p_output.push_back(");\n"); + p_output.append("\t" + ptrcall_return_type); + p_output.append(" " C_LOCAL_RET); + p_output.append(initialization + ";\n"); + p_output.append("\tERR_FAIL_NULL_V(" CS_PARAM_INSTANCE); + p_output.append(fail_ret); + p_output.append(");\n"); } else { - p_output.push_back("\tERR_FAIL_NULL(" CS_PARAM_INSTANCE ");\n"); + p_output.append("\tERR_FAIL_NULL(" CS_PARAM_INSTANCE ");\n"); } if (p_imethod.arguments.size()) { if (p_imethod.is_vararg) { - String err_fail_macro = ret_void ? "ERR_FAIL_COND" : "ERR_FAIL_COND_V"; String vararg_arg = "arg" + argc_str; String real_argc_str = itos(p_imethod.arguments.size() - 1); // Arguments count without vararg - p_output.push_back("\tVector<Variant> varargs;\n" - "\tint vararg_length = mono_array_length("); - p_output.push_back(vararg_arg); - p_output.push_back(");\n\tint total_length = "); - p_output.push_back(real_argc_str); - p_output.push_back(" + vararg_length;\n\t"); - p_output.push_back(err_fail_macro); - p_output.push_back("(varargs.resize(vararg_length) != OK"); - p_output.push_back(fail_ret); - p_output.push_back(");\n\tVector<Variant*> " C_LOCAL_PTRCALL_ARGS ";\n\t"); - p_output.push_back(err_fail_macro); - p_output.push_back("(call_args.resize(total_length) != OK"); - p_output.push_back(fail_ret); - p_output.push_back(");\n"); - p_output.push_back(c_in_statements); - p_output.push_back("\tfor (int i = 0; i < vararg_length; i++) " OPEN_BLOCK - "\t\tMonoObject* elem = mono_array_get("); - p_output.push_back(vararg_arg); - p_output.push_back(", MonoObject*, i);\n" - "\t\tvarargs.set(i, GDMonoMarshal::mono_object_to_variant(elem));\n" - "\t\t" C_LOCAL_PTRCALL_ARGS ".set("); - p_output.push_back(real_argc_str); - p_output.push_back(" + i, &varargs.write[i]);\n\t" CLOSE_BLOCK); + p_output.append("\tint vararg_length = mono_array_length("); + p_output.append(vararg_arg); + p_output.append(");\n\tint total_length = "); + p_output.append(real_argc_str); + p_output.append(" + vararg_length;\n" + "\tArgumentsVector<Variant> varargs(vararg_length);\n" + "\tArgumentsVector<const Variant *> " C_LOCAL_PTRCALL_ARGS "(total_length);\n"); + p_output.append(c_in_statements); + p_output.append("\tfor (int i = 0; i < vararg_length; i++) " OPEN_BLOCK + "\t\tMonoObject* elem = mono_array_get("); + p_output.append(vararg_arg); + p_output.append(", MonoObject*, i);\n" + "\t\tvarargs.set(i, GDMonoMarshal::mono_object_to_variant(elem));\n" + "\t\t" C_LOCAL_PTRCALL_ARGS ".set("); + p_output.append(real_argc_str); + p_output.append(" + i, &varargs.get(i));\n\t" CLOSE_BLOCK); } else { - p_output.push_back(c_in_statements); - p_output.push_back("\tconst void* " C_LOCAL_PTRCALL_ARGS "["); - p_output.push_back(argc_str + "] = { "); - p_output.push_back(c_args_var_content + " };\n"); + p_output.append(c_in_statements); + p_output.append("\tconst void* " C_LOCAL_PTRCALL_ARGS "["); + p_output.append(argc_str + "] = { "); + p_output.append(c_args_var_content + " };\n"); } } if (p_imethod.is_vararg) { - p_output.push_back("\tVariant::CallError vcall_error;\n\t"); + p_output.append("\tVariant::CallError vcall_error;\n\t"); if (!ret_void) { // See the comment on the C_LOCAL_VARARG_RET declaration if (return_type->cname != name_cache.type_Variant) { - p_output.push_back(C_LOCAL_VARARG_RET " = "); + p_output.append(C_LOCAL_VARARG_RET " = "); } else { - p_output.push_back(C_LOCAL_RET " = "); + p_output.append(C_LOCAL_RET " = "); } } - p_output.push_back(CS_PARAM_METHODBIND "->call(" CS_PARAM_INSTANCE ", "); - p_output.push_back(p_imethod.arguments.size() ? "(const Variant**)" C_LOCAL_PTRCALL_ARGS ".ptr()" : "NULL"); - p_output.push_back(", total_length, vcall_error);\n"); + p_output.append(CS_PARAM_METHODBIND "->call(" CS_PARAM_INSTANCE ", "); + p_output.append(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ".ptr()" : "NULL"); + p_output.append(", total_length, vcall_error);\n"); // See the comment on the C_LOCAL_VARARG_RET declaration if (return_type->cname != name_cache.type_Variant) { - p_output.push_back("\t" C_LOCAL_RET " = " C_LOCAL_VARARG_RET ";\n"); + p_output.append("\t" C_LOCAL_RET " = " C_LOCAL_VARARG_RET ";\n"); } } else { - p_output.push_back("\t" CS_PARAM_METHODBIND "->ptrcall(" CS_PARAM_INSTANCE ", "); - p_output.push_back(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ", " : "NULL, "); - p_output.push_back(!ret_void ? "&" C_LOCAL_RET ");\n" : "NULL);\n"); + p_output.append("\t" CS_PARAM_METHODBIND "->ptrcall(" CS_PARAM_INSTANCE ", "); + p_output.append(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ", " : "NULL, "); + p_output.append(!ret_void ? "&" C_LOCAL_RET ");\n" : "NULL);\n"); } if (!ret_void) { if (return_type->c_out.empty()) - p_output.push_back("\treturn " C_LOCAL_RET ";\n"); + p_output.append("\treturn " C_LOCAL_RET ";\n"); else - p_output.push_back(sformat(return_type->c_out, return_type->c_type_out, C_LOCAL_RET, return_type->name)); + p_output.append(sformat(return_type->c_out, return_type->c_type_out, C_LOCAL_RET, return_type->name)); } - p_output.push_back(CLOSE_BLOCK "\n"); + p_output.append(CLOSE_BLOCK "\n"); if (im_icall->editor_only) - p_output.push_back("#endif // TOOLS_ENABLED\n"); + p_output.append("#endif // TOOLS_ENABLED\n"); } return OK; @@ -1660,6 +2125,58 @@ const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_placehol return &placeholder_types.insert(placeholder.cname, placeholder)->get(); } +StringName BindingsGenerator::_get_int_type_name_from_meta(GodotTypeInfo::Metadata p_meta) { + + switch (p_meta) { + case GodotTypeInfo::METADATA_INT_IS_INT8: + return "sbyte"; + break; + case GodotTypeInfo::METADATA_INT_IS_INT16: + return "short"; + break; + case GodotTypeInfo::METADATA_INT_IS_INT32: + return "int"; + break; + case GodotTypeInfo::METADATA_INT_IS_INT64: + return "long"; + break; + case GodotTypeInfo::METADATA_INT_IS_UINT8: + return "byte"; + break; + case GodotTypeInfo::METADATA_INT_IS_UINT16: + return "ushort"; + break; + case GodotTypeInfo::METADATA_INT_IS_UINT32: + return "uint"; + break; + case GodotTypeInfo::METADATA_INT_IS_UINT64: + return "ulong"; + break; + default: + // Assume INT32 + return "int"; + } +} + +StringName BindingsGenerator::_get_float_type_name_from_meta(GodotTypeInfo::Metadata p_meta) { + + switch (p_meta) { + case GodotTypeInfo::METADATA_REAL_IS_FLOAT: + return "float"; + break; + case GodotTypeInfo::METADATA_REAL_IS_DOUBLE: + return "double"; + break; + default: + // Assume real_t (float or double depending of REAL_T_IS_DOUBLE) +#ifdef REAL_T_IS_DOUBLE + return "double"; +#else + return "float"; +#endif + } +} + void BindingsGenerator::_populate_object_type_interfaces() { obj_types.clear(); @@ -1679,15 +2196,13 @@ void BindingsGenerator::_populate_object_type_interfaces() { } if (!ClassDB::is_class_exposed(type_cname)) { - if (verbose_output) - WARN_PRINTS("Ignoring type " + type_cname.operator String() + " because it's not exposed"); + _log("Ignoring type `%s` because it's not exposed\n", String(type_cname).utf8().get_data()); class_list.pop_front(); continue; } if (!ClassDB::is_class_enabled(type_cname)) { - if (verbose_output) - WARN_PRINTS("Ignoring type " + type_cname.operator String() + " because it's not enabled"); + _log("Ignoring type `%s` because it's not enabled\n", String(type_cname).utf8().get_data()); class_list.pop_front(); continue; } @@ -1715,10 +2230,12 @@ void BindingsGenerator::_populate_object_type_interfaces() { itype.im_type_in = "IntPtr"; itype.im_type_out = itype.proxy_name; + // Populate properties + List<PropertyInfo> property_list; ClassDB::get_property_list(type_cname, &property_list, true); - // Populate properties + Map<StringName, StringName> accessor_methods; for (const List<PropertyInfo>::Element *E = property_list.front(); E; E = E->next()) { const PropertyInfo &property = E->get(); @@ -1728,24 +2245,30 @@ void BindingsGenerator::_populate_object_type_interfaces() { PropertyInterface iprop; iprop.cname = property.name; - iprop.proxy_name = escape_csharp_keyword(snake_to_pascal_case(iprop.cname)); iprop.setter = ClassDB::get_property_setter(type_cname, iprop.cname); iprop.getter = ClassDB::get_property_getter(type_cname, iprop.cname); + if (iprop.setter != StringName()) + accessor_methods[iprop.setter] = iprop.cname; + if (iprop.getter != StringName()) + accessor_methods[iprop.getter] = iprop.cname; + bool valid = false; iprop.index = ClassDB::get_property_index(type_cname, iprop.cname, &valid); ERR_FAIL_COND(!valid); - // Prevent property and enclosing type from sharing the same name + iprop.proxy_name = escape_csharp_keyword(snake_to_pascal_case(iprop.cname)); + + // Prevent the property and its enclosing type from sharing the same name if (iprop.proxy_name == itype.proxy_name) { - if (verbose_output) { - WARN_PRINTS("Name of property `" + iprop.proxy_name + "` is ambiguous with the name of its class `" + - itype.proxy_name + "`. Renaming property to `" + iprop.proxy_name + "_`"); - } + _log("Name of property `%s` is ambiguous with the name of its enclosing class `%s`. Renaming property to `%s_`\n", + iprop.proxy_name.utf8().get_data(), itype.proxy_name.utf8().get_data(), iprop.proxy_name.utf8().get_data()); iprop.proxy_name += "_"; } + iprop.proxy_name = iprop.proxy_name.replace("/", "__"); // Some members have a slash... + iprop.prop_doc = NULL; for (int i = 0; i < itype.class_doc->properties.size(); i++) { @@ -1777,9 +2300,14 @@ void BindingsGenerator::_populate_object_type_interfaces() { if (method_info.name.empty()) continue; + String cname = method_info.name; + + if (blacklisted_methods.find(itype.cname) && blacklisted_methods[itype.cname].find(cname)) + continue; + MethodInterface imethod; imethod.name = method_info.name; - imethod.cname = imethod.name; + imethod.cname = cname; if (method_info.flags & METHOD_FLAG_VIRTUAL) imethod.is_virtual = true; @@ -1806,14 +2334,13 @@ void BindingsGenerator::_populate_object_type_interfaces() { // which could actually will return something different. // Let's put this to notify us if that ever happens. if (itype.cname != name_cache.type_Object || imethod.name != "free") { - if (verbose_output) { - WARN_PRINTS("Notification: New unexpected virtual non-overridable method found.\n" - "We only expected Object.free, but found " + - itype.name + "." + imethod.name); - } + ERR_PRINTS("Notification: New unexpected virtual non-overridable method found.\n" + "We only expected Object.free, but found " + + itype.name + "." + imethod.name); } } else { - ERR_PRINTS("Missing MethodBind for non-virtual method: " + itype.name + "." + imethod.name); + ERR_EXPLAIN("Missing MethodBind for non-virtual method: " + itype.name + "." + imethod.name); + ERR_FAIL(); } } else if (return_info.type == Variant::INT && return_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { imethod.return_type.cname = return_info.class_name; @@ -1827,7 +2354,13 @@ void BindingsGenerator::_populate_object_type_interfaces() { } else if (return_info.type == Variant::NIL) { imethod.return_type.cname = name_cache.type_void; } else { - imethod.return_type.cname = Variant::get_type_name(return_info.type); + if (return_info.type == Variant::INT) { + imethod.return_type.cname = _get_int_type_name_from_meta(m ? m->get_argument_meta(-1) : GodotTypeInfo::METADATA_NONE); + } else if (return_info.type == Variant::REAL) { + imethod.return_type.cname = _get_float_type_name_from_meta(m ? m->get_argument_meta(-1) : GodotTypeInfo::METADATA_NONE); + } else { + imethod.return_type.cname = Variant::get_type_name(return_info.type); + } } for (int i = 0; i < argc; i++) { @@ -1846,7 +2379,13 @@ void BindingsGenerator::_populate_object_type_interfaces() { } else if (arginfo.type == Variant::NIL) { iarg.type.cname = name_cache.type_Variant; } else { - iarg.type.cname = Variant::get_type_name(arginfo.type); + if (arginfo.type == Variant::INT) { + iarg.type.cname = _get_int_type_name_from_meta(m ? m->get_argument_meta(i) : GodotTypeInfo::METADATA_NONE); + } else if (arginfo.type == Variant::REAL) { + iarg.type.cname = _get_float_type_name_from_meta(m ? m->get_argument_meta(i) : GodotTypeInfo::METADATA_NONE); + } else { + iarg.type.cname = Variant::get_type_name(arginfo.type); + } } iarg.name = escape_csharp_keyword(snake_to_camel_case(iarg.name)); @@ -1867,16 +2406,24 @@ void BindingsGenerator::_populate_object_type_interfaces() { imethod.proxy_name = escape_csharp_keyword(snake_to_pascal_case(imethod.name)); - // Prevent naming the property and its enclosing type from sharing the same name + // Prevent the method and its enclosing type from sharing the same name if (imethod.proxy_name == itype.proxy_name) { - if (verbose_output) { - WARN_PRINTS("Name of method `" + imethod.proxy_name + "` is ambiguous with the name of its class `" + - itype.proxy_name + "`. Renaming method to `" + imethod.proxy_name + "_`"); - } + _log("Name of method `%s` is ambiguous with the name of its enclosing class `%s`. Renaming method to `%s_`\n", + imethod.proxy_name.utf8().get_data(), itype.proxy_name.utf8().get_data(), imethod.proxy_name.utf8().get_data()); imethod.proxy_name += "_"; } + Map<StringName, StringName>::Element *accessor = accessor_methods.find(imethod.cname); + if (accessor) { + const PropertyInterface *accessor_property = itype.find_property_by_name(accessor->value()); + + // We only deprecate an accessor method if it's in the same class as the property. It's easier this way, but also + // we don't know if an accessor method in a different class could have other purposes, so better leave those untouched. + imethod.is_deprecated = true; + imethod.deprecation_message = imethod.proxy_name + " is deprecated. Use the " + accessor_property->proxy_name + " property instead."; + } + if (itype.class_doc) { for (int i = 0; i < itype.class_doc->methods.size(); i++) { if (itype.class_doc->methods[i].name == imethod.name) { @@ -1903,8 +2450,8 @@ void BindingsGenerator::_populate_object_type_interfaces() { // Populate enums and constants - List<String> constant_list; - ClassDB::get_integer_constant_list(type_cname, &constant_list, true); + List<String> constants; + ClassDB::get_integer_constant_list(type_cname, &constants, true); const HashMap<StringName, List<StringName> > &enum_map = class_info->enum_map; const StringName *k = NULL; @@ -1919,13 +2466,13 @@ void BindingsGenerator::_populate_object_type_interfaces() { enum_proxy_cname = StringName(enum_proxy_name); } EnumInterface ienum(enum_proxy_cname); - const List<StringName> &constants = enum_map.get(*k); - for (const List<StringName>::Element *E = constants.front(); E; E = E->next()) { + const List<StringName> &enum_constants = enum_map.get(*k); + for (const List<StringName>::Element *E = enum_constants.front(); E; E = E->next()) { const StringName &constant_cname = E->get(); String constant_name = constant_cname.operator String(); int *value = class_info->constant_map.getptr(constant_cname); ERR_FAIL_NULL(value); - constant_list.erase(constant_name); + constants.erase(constant_name); ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), *value); @@ -1957,7 +2504,7 @@ void BindingsGenerator::_populate_object_type_interfaces() { enum_types.insert(enum_itype.cname, enum_itype); } - for (const List<String>::Element *E = constant_list.front(); E; E = E->next()) { + for (const List<String>::Element *E = constants.front(); E; E = E->next()) { const String &constant_name = E->get(); int *value = class_info->constant_map.getptr(StringName(E->get())); ERR_FAIL_NULL(value); @@ -2038,6 +2585,7 @@ void BindingsGenerator::_default_argument_from_variant(const Variant &p_val, Arg r_iarg.default_argument = "null"; break; } + FALLTHROUGH; case Variant::DICTIONARY: case Variant::_RID: r_iarg.default_argument = "new %s()"; @@ -2060,7 +2608,8 @@ void BindingsGenerator::_default_argument_from_variant(const Variant &p_val, Arg r_iarg.default_argument = Variant::get_type_name(p_val.get_type()) + ".Identity"; r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; break; - default: {} + default: { + } } if (r_iarg.def_param_mode == ArgumentInterface::CONSTANT && r_iarg.type.cname == name_cache.type_Variant && r_iarg.default_argument != "null") @@ -2102,7 +2651,6 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { // bool itype = TypeInterface::create_value_type(String("bool")); - { // MonoBoolean <---> bool itype.c_in = "\t%0 %1_in = (%0)%1;\n"; @@ -2116,45 +2664,73 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.im_type_out = itype.name; builtin_types.insert(itype.cname, itype); - // int - // C interface is the same as that of enums. Remember to apply any - // changes done here to TypeInterface::postsetup_enum_type as well - itype = TypeInterface::create_value_type(String("int")); - itype.c_arg_in = "&%s_in"; + // Integer types { - // The expected types for parameters and return value in ptrcall are 'int64_t' or 'uint64_t'. - itype.c_in = "\t%0 %1_in = (%0)%1;\n"; - itype.c_out = "\treturn (%0)%1;\n"; - itype.c_type = "int64_t"; + // C interface for 'uint32_t' is the same as that of enums. Remember to apply + // any of the changes done here to 'TypeInterface::postsetup_enum_type' as well. +#define INSERT_INT_TYPE(m_name, m_c_type_in_out, m_c_type) \ + { \ + itype = TypeInterface::create_value_type(String(m_name)); \ + { \ + itype.c_in = "\t%0 %1_in = (%0)%1;\n"; \ + itype.c_out = "\treturn (%0)%1;\n"; \ + itype.c_type = #m_c_type; \ + itype.c_arg_in = "&%s_in"; \ + } \ + itype.c_type_in = #m_c_type_in_out; \ + itype.c_type_out = itype.c_type_in; \ + itype.im_type_in = itype.name; \ + itype.im_type_out = itype.name; \ + builtin_types.insert(itype.cname, itype); \ } - itype.c_type_in = "int32_t"; - itype.c_type_out = itype.c_type_in; - itype.im_type_in = itype.name; - itype.im_type_out = itype.name; - builtin_types.insert(itype.cname, itype); - // real_t - itype = TypeInterface(); - itype.name = "float"; // The name is always "float" in Variant, even with REAL_T_IS_DOUBLE. - itype.cname = itype.name; -#ifdef REAL_T_IS_DOUBLE - itype.proxy_name = "double"; -#else - itype.proxy_name = "float"; -#endif + // The expected type for all integers in ptrcall is 'int64_t', so that's what we use for 'c_type' + + INSERT_INT_TYPE("sbyte", int8_t, int64_t); + INSERT_INT_TYPE("short", int16_t, int64_t); + INSERT_INT_TYPE("int", int32_t, int64_t); + INSERT_INT_TYPE("long", int64_t, int64_t); + INSERT_INT_TYPE("byte", uint8_t, int64_t); + INSERT_INT_TYPE("ushort", uint16_t, int64_t); + INSERT_INT_TYPE("uint", uint32_t, int64_t); + INSERT_INT_TYPE("ulong", uint64_t, int64_t); + } + + // Floating point types { - // The expected type for parameters and return value in ptrcall is 'double'. - itype.c_in = "\t%0 %1_in = (%0)%1;\n"; - itype.c_out = "\treturn (%0)%1;\n"; + // float + itype = TypeInterface(); + itype.name = "float"; + itype.cname = itype.name; + itype.proxy_name = "float"; + { + // The expected type for 'float' in ptrcall is 'double' + itype.c_in = "\t%0 %1_in = (%0)%1;\n"; + itype.c_out = "\treturn (%0)%1;\n"; + itype.c_type = "double"; + itype.c_type_in = "float"; + itype.c_type_out = "float"; + itype.c_arg_in = "&%s_in"; + } + itype.cs_type = itype.proxy_name; + itype.im_type_in = itype.proxy_name; + itype.im_type_out = itype.proxy_name; + builtin_types.insert(itype.cname, itype); + + // double + itype = TypeInterface(); + itype.name = "double"; + itype.cname = itype.name; + itype.proxy_name = "double"; itype.c_type = "double"; - itype.c_type_in = "real_t"; - itype.c_type_out = "real_t"; - itype.c_arg_in = "&%s_in"; + itype.c_type_in = "double"; + itype.c_type_out = "double"; + itype.c_arg_in = "&%s"; + itype.cs_type = itype.proxy_name; + itype.im_type_in = itype.proxy_name; + itype.im_type_out = itype.proxy_name; + builtin_types.insert(itype.cname, itype); } - itype.cs_type = itype.proxy_name; - itype.im_type_in = itype.proxy_name; - itype.im_type_out = itype.proxy_name; - builtin_types.insert(itype.cname, itype); // String itype = TypeInterface(); @@ -2404,12 +2980,32 @@ void BindingsGenerator::_populate_global_constants() { } } -void BindingsGenerator::initialize() { +void BindingsGenerator::_initialize_blacklisted_methods() { + + blacklisted_methods["Object"].push_back("to_string"); // there is already ToString + blacklisted_methods["Object"].push_back("_to_string"); // override ToString instead + blacklisted_methods["Object"].push_back("_init"); // never called in C# (TODO: implement it) +} + +void BindingsGenerator::_log(const char *p_format, ...) { + + if (log_print_enabled) { + va_list list; + + va_start(list, p_format); + OS::get_singleton()->print("%s", str_format(p_format, list).utf8().get_data()); + va_end(list); + } +} + +void BindingsGenerator::_initialize() { EditorHelp::generate_doc(); enum_types.clear(); + _initialize_blacklisted_methods(); + _populate_object_type_interfaces(); _populate_builtin_type_interfaces(); @@ -2427,38 +3023,33 @@ void BindingsGenerator::initialize() { void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) { const int NUM_OPTIONS = 2; - int options_left = NUM_OPTIONS; - String mono_glue_option = "--generate-mono-glue"; String cs_api_option = "--generate-cs-api"; - verbose_output = true; + String mono_glue_path; + String cs_api_path; + + int options_left = NUM_OPTIONS; const List<String>::Element *elem = p_cmdline_args.front(); while (elem && options_left) { - if (elem->get() == mono_glue_option) { - const List<String>::Element *path_elem = elem->next(); if (path_elem) { - if (get_singleton()->generate_glue(path_elem->get()) != OK) - ERR_PRINTS(mono_glue_option + ": Failed to generate mono glue"); + mono_glue_path = path_elem->get(); elem = elem->next(); } else { ERR_PRINTS(mono_glue_option + ": No output directory specified"); } --options_left; - } else if (elem->get() == cs_api_option) { - const List<String>::Element *path_elem = elem->next(); if (path_elem) { - if (get_singleton()->generate_cs_api(path_elem->get()) != OK) - ERR_PRINTS(cs_api_option + ": Failed to generate the C# API"); + cs_api_path = path_elem->get(); elem = elem->next(); } else { ERR_PRINTS(cs_api_option + ": No output directory specified"); @@ -2470,10 +3061,23 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) elem = elem->next(); } - verbose_output = false; + if (mono_glue_path.length() || cs_api_path.length()) { + BindingsGenerator bindings_generator; + bindings_generator.set_log_print_enabled(true); + + if (mono_glue_path.length()) { + if (bindings_generator.generate_glue(mono_glue_path) != OK) + ERR_PRINTS(mono_glue_option + ": Failed to generate mono glue"); + } - if (options_left != NUM_OPTIONS) + if (cs_api_path.length()) { + if (bindings_generator.generate_cs_api(cs_api_path) != OK) + ERR_PRINTS(cs_api_option + ": Failed to generate the C# API"); + } + + // Exit once done ::exit(0); + } } #endif diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index 8a1c942f6b..ffc73a7e3e 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -32,6 +32,7 @@ #define BINDINGS_GENERATOR_H #include "core/class_db.h" +#include "core/string_builder.h" #include "dotnet_solution.h" #include "editor/doc/doc_data.h" #include "editor/editor_help.h" @@ -87,8 +88,13 @@ class BindingsGenerator { StringName cname; bool is_enum; - TypeReference() { - is_enum = false; + TypeReference() : + is_enum(false) { + } + + TypeReference(const StringName &p_cname) : + cname(p_cname), + is_enum(false) { } }; @@ -153,17 +159,20 @@ class BindingsGenerator { const DocData::MethodDoc *method_doc; + bool is_deprecated; + String deprecation_message; + void add_argument(const ArgumentInterface &argument) { arguments.push_back(argument); } MethodInterface() { - return_type.cname = BindingsGenerator::get_singleton()->name_cache.type_void; is_vararg = false; is_virtual = false; requires_object_call = false; is_internal = false; method_doc = NULL; + is_deprecated = false; } }; @@ -321,6 +330,15 @@ class BindingsGenerator { return NULL; } + const PropertyInterface *find_property_by_name(const StringName &p_cname) const { + for (const List<PropertyInterface>::Element *E = properties.front(); E; E = E->next()) { + if (E->get().cname == p_cname) + return &E->get(); + } + + return NULL; + } + const PropertyInterface *find_property_by_proxy_name(const String &p_proxy_name) const { for (const List<PropertyInterface>::Element *E = properties.front(); E; E = E->next()) { if (E->get().proxy_name == p_proxy_name) @@ -385,8 +403,8 @@ class BindingsGenerator { } static void postsetup_enum_type(TypeInterface &r_enum_itype) { - // C interface is the same as that of 'int'. Remember to apply any - // changes done here to the 'int' type interface as well + // C interface for enums is the same as that of 'uint32_t'. Remember to apply + // any of the changes done here to the 'uint32_t' type interface as well. r_enum_itype.c_arg_in = "&%s_in"; { @@ -454,7 +472,7 @@ class BindingsGenerator { } }; - static bool verbose_output; + bool log_print_enabled; OrderedHashMap<StringName, TypeInterface> obj_types; @@ -473,29 +491,58 @@ class BindingsGenerator { List<InternalCall> core_custom_icalls; List<InternalCall> editor_custom_icalls; + Map<StringName, List<StringName> > blacklisted_methods; + + void _initialize_blacklisted_methods(); + struct NameCache { StringName type_void; - StringName type_int; StringName type_Array; StringName type_Dictionary; StringName type_Variant; StringName type_VarArg; StringName type_Object; StringName type_Reference; + StringName type_String; + StringName type_at_GlobalScope; StringName enum_Error; + StringName type_sbyte; + StringName type_short; + StringName type_int; + StringName type_long; + StringName type_byte; + StringName type_ushort; + StringName type_uint; + StringName type_ulong; + StringName type_float; + StringName type_double; + NameCache() { type_void = StaticCString::create("void"); - type_int = StaticCString::create("int"); type_Array = StaticCString::create("Array"); type_Dictionary = StaticCString::create("Dictionary"); type_Variant = StaticCString::create("Variant"); type_VarArg = StaticCString::create("VarArg"); type_Object = StaticCString::create("Object"); type_Reference = StaticCString::create("Reference"); + type_String = StaticCString::create("String"); + type_at_GlobalScope = StaticCString::create("@GlobalScope"); enum_Error = StaticCString::create("Error"); + + type_sbyte = StaticCString::create("sbyte"); + type_short = StaticCString::create("short"); + type_int = StaticCString::create("int"); + type_long = StaticCString::create("long"); + type_byte = StaticCString::create("byte"); + type_ushort = StaticCString::create("ushort"); + type_uint = StaticCString::create("uint"); + type_ulong = StaticCString::create("ulong"); + type_float = StaticCString::create("float"); + type_double = StaticCString::create("double"); } + private: NameCache(const NameCache &); NameCache &operator=(const NameCache &); }; @@ -511,6 +558,15 @@ class BindingsGenerator { return NULL; } + const ConstantInterface *find_constant_by_name(const String &p_name, const List<ConstantInterface> &p_constants) const { + for (const List<ConstantInterface>::Element *E = p_constants.front(); E; E = E->next()) { + if (E->get().name == p_name) + return &E->get(); + } + + return NULL; + } + inline String get_unique_sig(const TypeInterface &p_type) { if (p_type.is_reference) return "Ref"; @@ -522,6 +578,8 @@ class BindingsGenerator { return p_type.name; } + String bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype); + int _determine_enum_prefix(const EnumInterface &p_ienum); void _apply_prefix_to_enum_constants(EnumInterface &p_ienum, int p_prefix_length); @@ -530,6 +588,9 @@ class BindingsGenerator { const TypeInterface *_get_type_or_null(const TypeReference &p_typeref); const TypeInterface *_get_type_or_placeholder(const TypeReference &p_typeref); + StringName _get_int_type_name_from_meta(GodotTypeInfo::Metadata p_meta); + StringName _get_float_type_name_from_meta(GodotTypeInfo::Metadata p_meta); + void _default_argument_from_variant(const Variant &p_val, ArgumentInterface &r_iarg); void _populate_object_type_interfaces(); @@ -539,42 +600,35 @@ class BindingsGenerator { Error _generate_cs_type(const TypeInterface &itype, const String &p_output_file); - Error _generate_cs_property(const TypeInterface &p_itype, const PropertyInterface &p_iprop, List<String> &p_output); - Error _generate_cs_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, int &p_method_bind_count, List<String> &p_output); + Error _generate_cs_property(const TypeInterface &p_itype, const PropertyInterface &p_iprop, StringBuilder &p_output); + Error _generate_cs_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output); - void _generate_global_constants(List<String> &p_output); + void _generate_global_constants(StringBuilder &p_output); - Error _generate_glue_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, List<String> &p_output); + Error _generate_glue_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, StringBuilder &p_output); - Error _save_file(const String &p_path, const List<String> &p_content); + Error _save_file(const String &p_path, const StringBuilder &p_content); - BindingsGenerator() {} + void _log(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3; - BindingsGenerator(const BindingsGenerator &); - BindingsGenerator &operator=(const BindingsGenerator &); - - friend class CSharpLanguage; - static BindingsGenerator *singleton; + void _initialize(); public: - Error generate_cs_core_project(const String &p_solution_dir, DotNetSolution &r_solution, bool p_verbose_output = true); - Error generate_cs_editor_project(const String &p_solution_dir, DotNetSolution &r_solution, bool p_verbose_output = true); - Error generate_cs_api(const String &p_output_dir, bool p_verbose_output = true); + Error generate_cs_core_project(const String &p_solution_dir, DotNetSolution &r_solution); + Error generate_cs_editor_project(const String &p_solution_dir, DotNetSolution &r_solution); + Error generate_cs_api(const String &p_output_dir); Error generate_glue(const String &p_output_dir); + void set_log_print_enabled(bool p_enabled) { log_print_enabled = p_enabled; } + static uint32_t get_version(); - void initialize(); + static void handle_cmdline_args(const List<String> &p_cmdline_args); - _FORCE_INLINE_ static BindingsGenerator *get_singleton() { - if (!singleton) { - singleton = memnew(BindingsGenerator); - singleton->initialize(); - } - return singleton; + BindingsGenerator() : + log_print_enabled(true) { + _initialize(); } - - static void handle_cmdline_args(const List<String> &p_cmdline_args); }; #endif diff --git a/modules/mono/editor/csharp_project.cpp b/modules/mono/editor/csharp_project.cpp index beeff51bc2..fe79286556 100644 --- a/modules/mono/editor/csharp_project.cpp +++ b/modules/mono/editor/csharp_project.cpp @@ -158,7 +158,7 @@ Error generate_scripts_metadata(const String &p_project_path, const String &p_ou PoolStringArray project_files = GDMonoMarshal::mono_array_to_PoolStringArray(ret); PoolStringArray::Read r = project_files.read(); - Dictionary old_dict = CSharpLanguage::get_singleton()->get_scripts_metadata(); + Dictionary old_dict = CSharpLanguage::get_singleton()->get_scripts_metadata_or_nothing(); Dictionary new_dict; for (int i = 0; i < project_files.size(); i++) { diff --git a/modules/mono/editor/godotsharp_builds.cpp b/modules/mono/editor/godotsharp_builds.cpp index 00c780d1b7..a962d6df27 100644 --- a/modules/mono/editor/godotsharp_builds.cpp +++ b/modules/mono/editor/godotsharp_builds.cpp @@ -30,6 +30,7 @@ #include "godotsharp_builds.h" +#include "core/os/os.h" #include "core/vector.h" #include "main/main.h" @@ -323,10 +324,13 @@ bool GodotSharpBuilds::make_api_assembly(APIAssembly::Type p_api_type) { String api_sln_file = api_sln_dir.plus_file(API_SOLUTION_NAME ".sln"); if (!DirAccess::exists(api_sln_dir) || !FileAccess::exists(api_sln_file)) { - BindingsGenerator *gen = BindingsGenerator::get_singleton(); - bool gen_verbose = OS::get_singleton()->is_stdout_verbose(); + BindingsGenerator bindings_generator; - Error err = gen->generate_cs_api(api_sln_dir, gen_verbose); + if (!OS::get_singleton()->is_stdout_verbose()) { + bindings_generator.set_log_print_enabled(false); + } + + Error err = bindings_generator.generate_cs_api(api_sln_dir); if (err != OK) { show_build_error_dialog("Failed to generate " API_SOLUTION_NAME " solution. Error: " + itos(err)); return false; @@ -348,7 +352,7 @@ bool GodotSharpBuilds::make_api_assembly(APIAssembly::Type p_api_type) { return true; } -bool GodotSharpBuilds::build_project_blocking(const String &p_config) { +bool GodotSharpBuilds::build_project_blocking(const String &p_config, const Vector<String> &p_godot_defines) { if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path())) return true; // No solution to build @@ -363,6 +367,29 @@ bool GodotSharpBuilds::build_project_blocking(const String &p_config) { pr.step("Building project solution", 0); MonoBuildInfo build_info(GodotSharpDirs::get_project_sln_path(), p_config); + + // Add Godot defines +#ifdef WINDOWS_ENABLED + String constants = "GodotDefineConstants=\""; +#else + String constants = "GodotDefineConstants=\\\""; +#endif + + for (int i = 0; i < p_godot_defines.size(); i++) { + constants += "GODOT_" + p_godot_defines[i].to_upper().replace("-", "_").replace(" ", "_").replace(";", "_") + ";"; + } + +#ifdef REAL_T_IS_DOUBLE + constants += "GODOT_REAL_T_IS_DOUBLE;"; +#endif + +#ifdef WINDOWS_ENABLED + constants += "\""; +#else + constants += "\\\""; +#endif + build_info.custom_props.push_back(constants); + if (!GodotSharpBuilds::get_singleton()->build(build_info)) { GodotSharpBuilds::show_build_error_dialog("Failed to build project solution"); return false; @@ -390,7 +417,10 @@ bool GodotSharpBuilds::editor_build_callback() { ERR_FAIL_COND_V(copy_err != OK, false); } - return build_project_blocking("Tools"); + Vector<String> godot_defines; + godot_defines.push_back(OS::get_singleton()->get_name()); + godot_defines.push_back(sizeof(void *) == 4 ? "32" : "64"); + return build_project_blocking("Tools", godot_defines); } GodotSharpBuilds *GodotSharpBuilds::singleton = NULL; diff --git a/modules/mono/editor/godotsharp_builds.h b/modules/mono/editor/godotsharp_builds.h index 652d30538a..2e9050e12e 100644 --- a/modules/mono/editor/godotsharp_builds.h +++ b/modules/mono/editor/godotsharp_builds.h @@ -92,7 +92,7 @@ public: static bool make_api_assembly(APIAssembly::Type p_api_type); - static bool build_project_blocking(const String &p_config); + static bool build_project_blocking(const String &p_config, const Vector<String> &p_godot_defines); static bool editor_build_callback(); diff --git a/modules/mono/editor/godotsharp_editor.cpp b/modules/mono/editor/godotsharp_editor.cpp index c7bb72c1fb..9d42528927 100644 --- a/modules/mono/editor/godotsharp_editor.cpp +++ b/modules/mono/editor/godotsharp_editor.cpp @@ -457,12 +457,12 @@ GodotSharpEditor::GodotSharpEditor(EditorNode *p_editor) { about_label->set_v_size_flags(Control::SIZE_EXPAND_FILL); about_label->set_autowrap(true); String about_text = - String("C# support in Godot Engine is a brand new feature and a work in progress.\n") + - "It is currently in an alpha stage and is not suitable for use in production.\n\n" + - "As of Godot 3.1, C# support is not feature-complete and may crash in some situations. " + - "Bugs and usability issues will be addressed gradually over future 3.x releases, " + - "including compatibility breaking changes as new features are implemented for a better overall C# experience.\n\n" + - "If you experience issues with this Mono build, please report them on Godot's issue tracker with details about your system, Mono version, IDE, etc:\n\n" + + String("C# support in Godot Engine is in late alpha stage and, while already usable, ") + + "it is not meant for use in production.\n\n" + + "Projects can be exported to Linux, macOS and Windows, but not yet to mobile or web platforms. " + + "Bugs and usability issues will be addressed gradually over future releases, " + + "potentially including compatibility breaking changes as new features are implemented for a better overall C# experience.\n\n" + + "If you experience issues with this Mono build, please report them on Godot's issue tracker with details about your system, MSBuild version, IDE, etc.:\n\n" + " https://github.com/godotengine/godot/issues\n\n" + "Your critical feedback at this stage will play a great role in shaping the C# support in future releases, so thank you!"; about_label->set_text(about_text); @@ -502,11 +502,11 @@ GodotSharpEditor::GodotSharpEditor(EditorNode *p_editor) { String settings_hint_str = "Disabled"; -#ifdef WINDOWS_ENABLED +#if defined(WINDOWS_ENABLED) settings_hint_str += ",MonoDevelop,Visual Studio Code"; -#elif OSX_ENABLED +#elif defined(OSX_ENABLED) settings_hint_str += ",Visual Studio,MonoDevelop,Visual Studio Code"; -#elif UNIX_ENABLED +#elif defined(UNIX_ENABLED) settings_hint_str += ",MonoDevelop,Visual Studio Code"; #endif diff --git a/modules/mono/editor/godotsharp_editor.h b/modules/mono/editor/godotsharp_editor.h index cf0d2aec84..d9523c384c 100644 --- a/modules/mono/editor/godotsharp_editor.h +++ b/modules/mono/editor/godotsharp_editor.h @@ -81,15 +81,15 @@ public: enum ExternalEditor { EDITOR_NONE, -#ifdef WINDOWS_ENABLED +#if defined(WINDOWS_ENABLED) //EDITOR_VISUALSTUDIO, // TODO EDITOR_MONODEVELOP, EDITOR_VSCODE -#elif OSX_ENABLED +#elif defined(OSX_ENABLED) EDITOR_VISUALSTUDIO_MAC, EDITOR_MONODEVELOP, EDITOR_VSCODE -#elif UNIX_ENABLED +#elif defined(UNIX_ENABLED) EDITOR_MONODEVELOP, EDITOR_VSCODE #endif diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp index ee5fed1a0c..126178125f 100644 --- a/modules/mono/editor/godotsharp_export.cpp +++ b/modules/mono/editor/godotsharp_export.cpp @@ -94,7 +94,12 @@ void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug ERR_FAIL_COND(!_add_file(scripts_metadata_path, scripts_metadata_path)); - ERR_FAIL_COND(!GodotSharpBuilds::build_project_blocking(build_config)); + // Turn export features into defines + Vector<String> godot_defines; + for (Set<String>::Element *E = p_features.front(); E; E = E->next()) { + godot_defines.push_back(E->get()); + } + ERR_FAIL_COND(!GodotSharpBuilds::build_project_blocking(build_config, godot_defines)); // Add dependency assemblies @@ -120,11 +125,21 @@ void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug bool load_success = GDMono::get_singleton()->load_assembly_from(project_dll_name, project_dll_src_path, &scripts_assembly, /* refonly: */ true); - ERR_EXPLAIN("Cannot load refonly assembly: " + project_dll_name); + ERR_EXPLAIN("Cannot load assembly (refonly): " + project_dll_name); ERR_FAIL_COND(!load_success); Vector<String> search_dirs; - GDMonoAssembly::fill_search_dirs(search_dirs, build_config); + String templates_dir = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG); + String android_bcl_dir = templates_dir.plus_file("android-bcl"); + + String custom_lib_dir; + + if (p_features.find("Android") && DirAccess::exists(android_bcl_dir)) { + custom_lib_dir = android_bcl_dir; + } + + GDMonoAssembly::fill_search_dirs(search_dirs, build_config, custom_lib_dir); + Error depend_error = _get_assembly_dependencies(scripts_assembly, search_dirs, dependencies); ERR_FAIL_COND(depend_error != OK); } @@ -147,7 +162,7 @@ void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug int i = 0; for (const Set<String>::Element *E = p_features.front(); E; E = E->next()) { MonoString *boxed = GDMonoMarshal::mono_string_from_godot(E->get()); - mono_array_set(features, MonoString *, i, boxed); + mono_array_setref(features, i, boxed); i++; } @@ -229,8 +244,10 @@ Error GodotSharpExport::_get_assembly_dependencies(GDMonoAssembly *p_assembly, c r_dependencies.insert(ref_name, ref_assembly->get_path()); Error err = _get_assembly_dependencies(ref_assembly, p_search_dirs, r_dependencies); - if (err != OK) - return err; + if (err != OK) { + ERR_EXPLAIN("Cannot load one of the dependencies for the assembly: " + ref_name); + ERR_FAIL_V(err); + } } return OK; diff --git a/modules/mono/editor/mono_bottom_panel.cpp b/modules/mono/editor/mono_bottom_panel.cpp index 21ce9ca5c4..5d9e39b6c2 100644 --- a/modules/mono/editor/mono_bottom_panel.cpp +++ b/modules/mono/editor/mono_bottom_panel.cpp @@ -175,7 +175,10 @@ void MonoBottomPanel::_build_project_pressed() { ERR_FAIL_COND(copy_err != OK); } - bool build_success = GodotSharpBuilds::get_singleton()->build_project_blocking("Tools"); + Vector<String> godot_defines; + godot_defines.push_back(OS::get_singleton()->get_name()); + godot_defines.push_back((sizeof(void *) == 4 ? "32" : "64")); + bool build_success = GodotSharpBuilds::get_singleton()->build_project_blocking("Tools", godot_defines); if (build_success) { // Notify running game for hot-reload diff --git a/modules/mono/editor/script_class_parser.cpp b/modules/mono/editor/script_class_parser.cpp index 6b2ec5cc20..dfb652a7aa 100644 --- a/modules/mono/editor/script_class_parser.cpp +++ b/modules/mono/editor/script_class_parser.cpp @@ -266,6 +266,20 @@ Error ScriptClassParser::_skip_generic_type_params() { if (tk == TK_IDENTIFIER) { tk = get_token(); + // Type specifications can end with "?" to denote nullable types, such as IList<int?> + if (tk == TK_SYMBOL) { + tk = get_token(); + if (value.operator String() != "?") { + error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found unexpected symbol '" + value + "'"; + error = true; + return ERR_PARSE_ERROR; + } + if (tk != TK_OP_GREATER && tk != TK_COMMA) { + error_str = "Nullable type symbol '?' is only allowed after an identifier, but found " + get_token_name(tk) + " next."; + error = true; + return ERR_PARSE_ERROR; + } + } if (tk == TK_PERIOD) { while (true) { |