diff options
Diffstat (limited to 'modules')
26 files changed, 370 insertions, 149 deletions
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 0bf9f72a2c..42b08f8a68 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -2695,6 +2695,11 @@ Ref<Resource> ResourceFormatLoaderGDScript::load(const String &p_path, const Str Error err; Ref<GDScript> scr = GDScriptCache::get_full_script(p_path, err, "", p_cache_mode == CACHE_MODE_IGNORE); + if (err && scr.is_valid()) { + // If !scr.is_valid(), the error was likely from scr->load_source_code(), which already generates an error. + ERR_PRINT_ED(vformat(R"(Failed to load script "%s" with error "%s".)", p_path, error_names[err])); + } + if (r_error) { // Don't fail loading because of parsing error. *r_error = scr.is_valid() ? OK : err; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 2a52db4158..3366fa2eec 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -245,6 +245,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // MEMBERS. case GDScriptParser::IdentifierNode::MEMBER_VARIABLE: + case GDScriptParser::IdentifierNode::MEMBER_FUNCTION: + case GDScriptParser::IdentifierNode::MEMBER_SIGNAL: case GDScriptParser::IdentifierNode::INHERITED_VARIABLE: { // Try class members. if (_is_class_member_property(codegen, identifier)) { @@ -271,45 +273,44 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } } } - } break; - case GDScriptParser::IdentifierNode::MEMBER_FUNCTION: - case GDScriptParser::IdentifierNode::MEMBER_SIGNAL: { - // Try methods and signals (can be Callable and Signal). - // Search upwards through parent classes: - const GDScriptParser::ClassNode *base_class = codegen.class_node; - while (base_class != nullptr) { - if (base_class->has_member(identifier)) { - const GDScriptParser::ClassNode::Member &member = base_class->get_member(identifier); - if (member.type == GDScriptParser::ClassNode::Member::FUNCTION || member.type == GDScriptParser::ClassNode::Member::SIGNAL) { - // Get like it was a property. - GDScriptCodeGenerator::Address temp = codegen.add_temporary(); // TODO: Get type here. - GDScriptCodeGenerator::Address self(GDScriptCodeGenerator::Address::SELF); - - gen->write_get_named(temp, identifier, self); - return temp; + // Try methods and signals (can be Callable and Signal). + { + // Search upwards through parent classes: + const GDScriptParser::ClassNode *base_class = codegen.class_node; + while (base_class != nullptr) { + if (base_class->has_member(identifier)) { + const GDScriptParser::ClassNode::Member &member = base_class->get_member(identifier); + if (member.type == GDScriptParser::ClassNode::Member::FUNCTION || member.type == GDScriptParser::ClassNode::Member::SIGNAL) { + // Get like it was a property. + GDScriptCodeGenerator::Address temp = codegen.add_temporary(); // TODO: Get type here. + GDScriptCodeGenerator::Address self(GDScriptCodeGenerator::Address::SELF); + + gen->write_get_named(temp, identifier, self); + return temp; + } } + base_class = base_class->base_type.class_type; } - base_class = base_class->base_type.class_type; - } - // Try in native base. - GDScript *scr = codegen.script; - GDScriptNativeClass *nc = nullptr; - while (scr) { - if (scr->native.is_valid()) { - nc = scr->native.ptr(); + // Try in native base. + GDScript *scr = codegen.script; + GDScriptNativeClass *nc = nullptr; + while (scr) { + if (scr->native.is_valid()) { + nc = scr->native.ptr(); + } + scr = scr->_base; } - scr = scr->_base; - } - if (nc && (ClassDB::has_signal(nc->get_name(), identifier) || ClassDB::has_method(nc->get_name(), identifier))) { - // Get like it was a property. - GDScriptCodeGenerator::Address temp = codegen.add_temporary(); // TODO: Get type here. - GDScriptCodeGenerator::Address self(GDScriptCodeGenerator::Address::SELF); + if (nc && (ClassDB::has_signal(nc->get_name(), identifier) || ClassDB::has_method(nc->get_name(), identifier))) { + // Get like it was a property. + GDScriptCodeGenerator::Address temp = codegen.add_temporary(); // TODO: Get type here. + GDScriptCodeGenerator::Address self(GDScriptCodeGenerator::Address::SELF); - gen->write_get_named(temp, identifier, self); - return temp; + gen->write_get_named(temp, identifier, self); + return temp; + } } } break; case GDScriptParser::IdentifierNode::MEMBER_CONSTANT: @@ -319,6 +320,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code while (owner) { GDScript *scr = owner; GDScriptNativeClass *nc = nullptr; + while (scr) { if (scr->constants.has(identifier)) { return codegen.add_constant(scr->constants[identifier]); // TODO: Get type here. diff --git a/modules/gdscript/tests/scripts/runtime/features/first_class_callable_and_signal.gd b/modules/gdscript/tests/scripts/runtime/features/first_class_callable_and_signal.gd new file mode 100644 index 0000000000..f17fb9823d --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/first_class_callable_and_signal.gd @@ -0,0 +1,14 @@ +# GH-80157 + +extends Node + +func f(): + pass + +signal s() + +func test(): + print(f) + print(s) + print(get_child) + print(ready) diff --git a/modules/gdscript/tests/scripts/runtime/features/first_class_callable_and_signal.out b/modules/gdscript/tests/scripts/runtime/features/first_class_callable_and_signal.out new file mode 100644 index 0000000000..e5e9ff7043 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/first_class_callable_and_signal.out @@ -0,0 +1,5 @@ +GDTEST_OK +Node::f +Node::[signal]s +Node::get_child +Node::[signal]ready diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml index 5bc6081803..9b760a997a 100644 --- a/modules/gltf/doc_classes/GLTFDocument.xml +++ b/modules/gltf/doc_classes/GLTFDocument.xml @@ -1,11 +1,16 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFDocument" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> + Class for importing and exporting glTF files in and out of Godot. </brief_description> <description> - Append a glTF2 3d format from a file, buffer or scene and then write to the filesystem, buffer or scene. + GLTFDocument supports reading data from a glTF file, buffer, or Godot scene. This data can then be written to the filesystem, buffer, or used to create a Godot scene. + All of the data in a GLTF scene is stored in the [GLTFState] class. GLTFDocument processes state objects, but does not contain any scene data itself. + GLTFDocument can be extended with arbitrary functionality by extending the [GLTFDocumentExtension] class and registering it with GLTFDocument via [method register_gltf_document_extension]. This allows for custom data to be imported and exported. </description> <tutorials> + <link title="glTF 'What the duck?' guide">https://www.khronos.org/files/gltf20-reference-guide.pdf</link> + <link title="Khronos glTF specification">https://registry.khronos.org/glTF/</link> </tutorials> <methods> <method name="append_from_buffer"> diff --git a/modules/gltf/doc_classes/GLTFDocumentExtension.xml b/modules/gltf/doc_classes/GLTFDocumentExtension.xml index 927ffb6aae..bae980fb56 100644 --- a/modules/gltf/doc_classes/GLTFDocumentExtension.xml +++ b/modules/gltf/doc_classes/GLTFDocumentExtension.xml @@ -17,7 +17,7 @@ <param index="1" name="gltf_node" type="GLTFNode" /> <param index="2" name="scene_node" type="Node" /> <description> - Part of the export process. This method is run after [method _export_preflight] and before [method _export_node]. + Part of the export process. This method is run after [method _export_preflight] and before [method _export_preserialize]. Runs when converting the data from a Godot scene node. This method can be used to process the Godot scene node data into a format that can be used by [method _export_node]. </description> </method> @@ -28,7 +28,7 @@ <param index="2" name="json" type="Dictionary" /> <param index="3" name="node" type="Node" /> <description> - Part of the export process. This method is run after [method _convert_scene_node] and before [method _export_post]. + Part of the export process. This method is run after [method _export_preserialize] and before [method _export_post]. This method can be used to modify the final JSON of each node. </description> </method> @@ -49,6 +49,14 @@ The return value is used to determine if this [GLTFDocumentExtension] instance should be used for exporting a given GLTF file. If [constant OK], the export will use this [GLTFDocumentExtension] instance. If not overridden, [constant OK] is returned. </description> </method> + <method name="_export_preserialize" qualifiers="virtual"> + <return type="int" enum="Error" /> + <param index="0" name="state" type="GLTFState" /> + <description> + Part of the export process. This method is run after [method _convert_scene_node] and before [method _export_node]. + This method can be used to alter the state before performing serialization. It runs every time when generating a buffer with [method GLTFDocument.generate_buffer] or writing to the file system with [method GLTFDocument.write_to_filesystem]. + </description> + </method> <method name="_generate_scene_node" qualifiers="virtual"> <return type="Node3D" /> <param index="0" name="state" type="GLTFState" /> @@ -59,6 +67,12 @@ Runs when generating a Godot scene node from a GLTFNode. The returned node will be added to the scene tree. Multiple nodes can be generated in this step if they are added as a child of the returned node. </description> </method> + <method name="_get_image_file_extension" qualifiers="virtual"> + <return type="String" /> + <description> + Returns the file extension to use for saving image data into, for example, [code]".png"[/code]. If defined, when this extension is used to handle images, and the images are saved to a separate file, the image bytes will be copied to a file with this extension. If this is set, there should be a [ResourceImporter] class able to import the file. If not defined or empty, Godot will save the image into a PNG file. + </description> + </method> <method name="_get_supported_extensions" qualifiers="virtual"> <return type="PackedStringArray" /> <description> diff --git a/modules/gltf/extensions/gltf_document_extension.cpp b/modules/gltf/extensions/gltf_document_extension.cpp index 2804a8b0a2..11718ba78a 100644 --- a/modules/gltf/extensions/gltf_document_extension.cpp +++ b/modules/gltf/extensions/gltf_document_extension.cpp @@ -36,6 +36,7 @@ void GLTFDocumentExtension::_bind_methods() { GDVIRTUAL_BIND(_get_supported_extensions); GDVIRTUAL_BIND(_parse_node_extensions, "state", "gltf_node", "extensions"); GDVIRTUAL_BIND(_parse_image_data, "state", "image_data", "mime_type", "ret_image"); + GDVIRTUAL_BIND(_get_image_file_extension); GDVIRTUAL_BIND(_parse_texture_json, "state", "texture_json", "ret_gltf_texture"); GDVIRTUAL_BIND(_generate_scene_node, "state", "gltf_node", "scene_parent"); GDVIRTUAL_BIND(_import_post_parse, "state"); @@ -44,6 +45,7 @@ void GLTFDocumentExtension::_bind_methods() { // Export process. GDVIRTUAL_BIND(_export_preflight, "state", "root"); GDVIRTUAL_BIND(_convert_scene_node, "state", "gltf_node", "scene_node"); + GDVIRTUAL_BIND(_export_preserialize, "state"); GDVIRTUAL_BIND(_export_node, "state", "gltf_node", "json", "node"); GDVIRTUAL_BIND(_export_post, "state"); } @@ -78,6 +80,12 @@ Error GLTFDocumentExtension::parse_image_data(Ref<GLTFState> p_state, const Pack return err; } +String GLTFDocumentExtension::get_image_file_extension() { + String ret; + GDVIRTUAL_CALL(_get_image_file_extension, ret); + return ret; +} + Error GLTFDocumentExtension::parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture) { ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); ERR_FAIL_NULL_V(r_gltf_texture, ERR_INVALID_PARAMETER); @@ -134,6 +142,13 @@ void GLTFDocumentExtension::convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFN GDVIRTUAL_CALL(_convert_scene_node, p_state, p_gltf_node, p_scene_node); } +Error GLTFDocumentExtension::export_preserialize(Ref<GLTFState> p_state) { + ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + Error err = OK; + GDVIRTUAL_CALL(_export_preserialize, p_state, err); + return err; +} + Error GLTFDocumentExtension::export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_dict, Node *p_node) { ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER); diff --git a/modules/gltf/extensions/gltf_document_extension.h b/modules/gltf/extensions/gltf_document_extension.h index d922588a29..0a631bb6c5 100644 --- a/modules/gltf/extensions/gltf_document_extension.h +++ b/modules/gltf/extensions/gltf_document_extension.h @@ -45,6 +45,7 @@ public: virtual Vector<String> get_supported_extensions(); virtual Error parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &p_extensions); virtual Error parse_image_data(Ref<GLTFState> p_state, const PackedByteArray &p_image_data, const String &p_mime_type, Ref<Image> r_image); + virtual String get_image_file_extension(); virtual Error parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture); virtual Node3D *generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent); virtual Error import_post_parse(Ref<GLTFState> p_state); @@ -53,6 +54,7 @@ public: // Export process. virtual Error export_preflight(Ref<GLTFState> p_state, Node *p_root); virtual void convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node); + virtual Error export_preserialize(Ref<GLTFState> p_state); virtual Error export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_json, Node *p_node); virtual Error export_post(Ref<GLTFState> p_state); @@ -61,6 +63,7 @@ public: GDVIRTUAL0R(Vector<String>, _get_supported_extensions); GDVIRTUAL3R(Error, _parse_node_extensions, Ref<GLTFState>, Ref<GLTFNode>, Dictionary); GDVIRTUAL4R(Error, _parse_image_data, Ref<GLTFState>, PackedByteArray, String, Ref<Image>); + GDVIRTUAL0R(String, _get_image_file_extension); GDVIRTUAL3R(Error, _parse_texture_json, Ref<GLTFState>, Dictionary, Ref<GLTFTexture>); GDVIRTUAL3R(Node3D *, _generate_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *); GDVIRTUAL1R(Error, _import_post_parse, Ref<GLTFState>); @@ -69,6 +72,7 @@ public: // Export process. GDVIRTUAL2R(Error, _export_preflight, Ref<GLTFState>, Node *); GDVIRTUAL3(_convert_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *); + GDVIRTUAL1R(Error, _export_preserialize, Ref<GLTFState>); GDVIRTUAL4R(Error, _export_node, Ref<GLTFState>, Ref<GLTFNode>, Dictionary, Node *); GDVIRTUAL1R(Error, _export_post, Ref<GLTFState>); }; diff --git a/modules/gltf/extensions/gltf_document_extension_texture_webp.cpp b/modules/gltf/extensions/gltf_document_extension_texture_webp.cpp index ded4970968..73c869be3b 100644 --- a/modules/gltf/extensions/gltf_document_extension_texture_webp.cpp +++ b/modules/gltf/extensions/gltf_document_extension_texture_webp.cpp @@ -30,8 +30,6 @@ #include "gltf_document_extension_texture_webp.h" -#include "scene/3d/area_3d.h" - // Import process. Error GLTFDocumentExtensionTextureWebP::import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) { if (!p_extensions.has("EXT_texture_webp")) { @@ -53,6 +51,10 @@ Error GLTFDocumentExtensionTextureWebP::parse_image_data(Ref<GLTFState> p_state, return OK; } +String GLTFDocumentExtensionTextureWebP::get_image_file_extension() { + return ".webp"; +} + Error GLTFDocumentExtensionTextureWebP::parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture) { if (!p_texture_json.has("extensions")) { return OK; diff --git a/modules/gltf/extensions/gltf_document_extension_texture_webp.h b/modules/gltf/extensions/gltf_document_extension_texture_webp.h index 9abf09a41f..d2654aae8c 100644 --- a/modules/gltf/extensions/gltf_document_extension_texture_webp.h +++ b/modules/gltf/extensions/gltf_document_extension_texture_webp.h @@ -41,6 +41,7 @@ public: Error import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) override; Vector<String> get_supported_extensions() override; Error parse_image_data(Ref<GLTFState> p_state, const PackedByteArray &p_image_data, const String &p_mime_type, Ref<Image> r_image) override; + String get_image_file_extension() override; Error parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture) override; }; diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 4381a0e00d..d23b22049b 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -115,6 +115,12 @@ Error GLTFDocument::_serialize(Ref<GLTFState> p_state, const String &p_path) { p_state->buffers.push_back(Vector<uint8_t>()); } + for (Ref<GLTFDocumentExtension> ext : document_extensions) { + ERR_CONTINUE(ext.is_null()); + Error err = ext->export_preserialize(p_state); + ERR_CONTINUE(err != OK); + } + /* STEP CONVERT MESH INSTANCES */ _convert_mesh_instances(p_state); @@ -2998,7 +3004,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) { Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state, const String &p_path) { Array images; for (int i = 0; i < p_state->images.size(); i++) { - Dictionary d; + Dictionary image_dict; ERR_CONTINUE(p_state->images[i].is_null()); @@ -3031,8 +3037,8 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state, const String &p_pa p_state->buffer_views.push_back(bv); bvi = p_state->buffer_views.size() - 1; - d["bufferView"] = bvi; - d["mimeType"] = "image/png"; + image_dict["bufferView"] = bvi; + image_dict["mimeType"] = "image/png"; } else { ERR_FAIL_COND_V(p_path.is_empty(), ERR_INVALID_PARAMETER); String img_name = p_state->images[i]->get_name(); @@ -3041,17 +3047,17 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state, const String &p_pa } img_name = _gen_unique_name(p_state, img_name); img_name = img_name.pad_zeros(3) + ".png"; - String texture_dir = "textures"; - String path = p_path.get_base_dir(); - String new_texture_dir = path + "/" + texture_dir; - Ref<DirAccess> da = DirAccess::open(path); - if (!da->dir_exists(new_texture_dir)) { - da->make_dir(new_texture_dir); + String relative_texture_dir = "textures"; + String parent_path = p_path.get_base_dir(); + String full_texture_dir = parent_path + "/" + relative_texture_dir; + Ref<DirAccess> da = DirAccess::open(parent_path); + if (!da->dir_exists(full_texture_dir)) { + da->make_dir(full_texture_dir); } - image->save_png(new_texture_dir.path_join(img_name)); - d["uri"] = texture_dir.path_join(img_name).uri_encode(); + image->save_png(full_texture_dir.path_join(img_name)); + image_dict["uri"] = relative_texture_dir.path_join(img_name).uri_encode(); } - images.push_back(d); + images.push_back(image_dict); } print_verbose("Total images: " + itos(p_state->images.size())); @@ -3064,7 +3070,7 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state, const String &p_pa return OK; } -Ref<Image> GLTFDocument::_parse_image_bytes_into_image(Ref<GLTFState> p_state, const Vector<uint8_t> &p_bytes, const String &p_mime_type, int p_index) { +Ref<Image> GLTFDocument::_parse_image_bytes_into_image(Ref<GLTFState> p_state, const Vector<uint8_t> &p_bytes, const String &p_mime_type, int p_index, String &r_file_extension) { Ref<Image> r_image; r_image.instantiate(); // Check if any GLTFDocumentExtensions want to import this data as an image. @@ -3073,6 +3079,7 @@ Ref<Image> GLTFDocument::_parse_image_bytes_into_image(Ref<GLTFState> p_state, c Error err = ext->parse_image_data(p_state, p_bytes, p_mime_type, r_image); ERR_CONTINUE_MSG(err != OK, "GLTF: Encountered error " + itos(err) + " when parsing image " + itos(p_index) + " in file " + p_state->filename + ". Continuing."); if (!r_image->is_empty()) { + r_file_extension = ext->get_image_file_extension(); return r_image; } } @@ -3080,8 +3087,10 @@ Ref<Image> GLTFDocument::_parse_image_bytes_into_image(Ref<GLTFState> p_state, c // First we honor the mime types if they were defined. if (p_mime_type == "image/png") { // Load buffer as PNG. r_image->load_png_from_buffer(p_bytes); + r_file_extension = ".png"; } else if (p_mime_type == "image/jpeg") { // Loader buffer as JPEG. r_image->load_jpg_from_buffer(p_bytes); + r_file_extension = ".jpg"; } // If we didn't pass the above tests, we attempt loading as PNG and then JPEG directly. // This covers URIs with base64-encoded data with application/* type but @@ -3102,7 +3111,7 @@ Ref<Image> GLTFDocument::_parse_image_bytes_into_image(Ref<GLTFState> p_state, c return r_image; } -void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const String &p_mime_type, int p_index, Ref<Image> p_image) { +void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const Vector<uint8_t> &p_bytes, const String &p_file_extension, int p_index, Ref<Image> p_image) { GLTFState::GLTFHandleBinary handling = GLTFState::GLTFHandleBinary(p_state->handle_binary_image); if (p_image->is_empty() || handling == GLTFState::GLTFHandleBinary::HANDLE_BINARY_DISCARD_TEXTURES) { p_state->images.push_back(Ref<Texture2D>()); @@ -3119,11 +3128,11 @@ void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const String p_state->images.push_back(Ref<Texture2D>()); p_state->source_images.push_back(Ref<Image>()); } else { - Error err = OK; bool must_import = true; Vector<uint8_t> img_data = p_image->get_data(); Dictionary generator_parameters; - String file_path = p_state->get_base_path() + "/" + p_state->filename.get_basename() + "_" + p_image->get_name() + ".png"; + String file_path = p_state->get_base_path() + "/" + p_state->filename.get_basename() + "_" + p_image->get_name(); + file_path += p_file_extension.is_empty() ? ".png" : p_file_extension; if (FileAccess::exists(file_path + ".import")) { Ref<ConfigFile> config; config.instantiate(); @@ -3144,8 +3153,18 @@ void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const String } } if (must_import) { - err = p_image->save_png(file_path); - ERR_FAIL_COND(err != OK); + Error err = OK; + if (p_file_extension.is_empty()) { + // If a file extension was not specified, save the image data to a PNG file. + err = p_image->save_png(file_path); + ERR_FAIL_COND(err != OK); + } else { + // If a file extension was specified, save the original bytes to a file with that extension. + Ref<FileAccess> file = FileAccess::open(file_path, FileAccess::WRITE, &err); + ERR_FAIL_COND(err != OK); + file->store_buffer(p_bytes); + file->close(); + } // ResourceLoader::import will crash if not is_editor_hint(), so this case is protected above and will fall through to uncompressed. HashMap<StringName, Variant> custom_options; custom_options[SNAME("mipmaps/generate")] = true; @@ -3295,9 +3314,10 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p continue; } // Parse the image data from bytes into an Image resource and save if needed. - Ref<Image> img = _parse_image_bytes_into_image(p_state, data, mime_type, i); + String file_extension; + Ref<Image> img = _parse_image_bytes_into_image(p_state, data, mime_type, i, file_extension); img->set_name(image_name); - _parse_image_save_image(p_state, mime_type, i, img); + _parse_image_save_image(p_state, data, file_extension, i, img); } print_verbose("glTF: Total images: " + itos(p_state->images.size())); @@ -3312,16 +3332,16 @@ Error GLTFDocument::_serialize_textures(Ref<GLTFState> p_state) { Array textures; for (int32_t i = 0; i < p_state->textures.size(); i++) { - Dictionary d; - Ref<GLTFTexture> t = p_state->textures[i]; - ERR_CONTINUE(t->get_src_image() == -1); - d["source"] = t->get_src_image(); + Dictionary texture_dict; + Ref<GLTFTexture> gltf_texture = p_state->textures[i]; + ERR_CONTINUE(gltf_texture->get_src_image() == -1); + texture_dict["source"] = gltf_texture->get_src_image(); - GLTFTextureSamplerIndex sampler_index = t->get_sampler(); + GLTFTextureSamplerIndex sampler_index = gltf_texture->get_sampler(); if (sampler_index != -1) { - d["sampler"] = sampler_index; + texture_dict["sampler"] = sampler_index; } - textures.push_back(d); + textures.push_back(texture_dict); } p_state->json["textures"] = textures; @@ -3335,28 +3355,28 @@ Error GLTFDocument::_parse_textures(Ref<GLTFState> p_state) { const Array &textures = p_state->json["textures"]; for (GLTFTextureIndex i = 0; i < textures.size(); i++) { - const Dictionary &dict = textures[i]; - Ref<GLTFTexture> texture; - texture.instantiate(); + const Dictionary &texture_dict = textures[i]; + Ref<GLTFTexture> gltf_texture; + gltf_texture.instantiate(); // Check if any GLTFDocumentExtensions want to handle this texture JSON. for (Ref<GLTFDocumentExtension> ext : document_extensions) { ERR_CONTINUE(ext.is_null()); - Error err = ext->parse_texture_json(p_state, dict, texture); - ERR_CONTINUE_MSG(err != OK, "GLTF: Encountered error " + itos(err) + " when parsing texture JSON " + String(Variant(dict)) + " in file " + p_state->filename + ". Continuing."); - if (texture->get_src_image() != -1) { + Error err = ext->parse_texture_json(p_state, texture_dict, gltf_texture); + ERR_CONTINUE_MSG(err != OK, "GLTF: Encountered error " + itos(err) + " when parsing texture JSON " + String(Variant(texture_dict)) + " in file " + p_state->filename + ". Continuing."); + if (gltf_texture->get_src_image() != -1) { break; } } - if (texture->get_src_image() == -1) { + if (gltf_texture->get_src_image() == -1) { // No extensions handled it, so use the base GLTF source. // This may be the fallback, or the only option anyway. - ERR_FAIL_COND_V(!dict.has("source"), ERR_PARSE_ERROR); - texture->set_src_image(dict["source"]); + ERR_FAIL_COND_V(!texture_dict.has("source"), ERR_PARSE_ERROR); + gltf_texture->set_src_image(texture_dict["source"]); } - if (texture->get_sampler() == -1 && dict.has("sampler")) { - texture->set_sampler(dict["sampler"]); + if (gltf_texture->get_sampler() == -1 && texture_dict.has("sampler")) { + gltf_texture->set_sampler(texture_dict["sampler"]); } - p_state->textures.push_back(texture); + p_state->textures.push_back(gltf_texture); } return OK; @@ -7280,44 +7300,44 @@ Node *GLTFDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, boo return root; } -Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> r_state, uint32_t p_flags) { - ERR_FAIL_COND_V(r_state.is_null(), FAILED); - r_state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS; - r_state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS; - if (!r_state->buffers.size()) { - r_state->buffers.push_back(Vector<uint8_t>()); +Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> p_state, uint32_t p_flags) { + ERR_FAIL_COND_V(p_state.is_null(), FAILED); + p_state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS; + p_state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS; + if (!p_state->buffers.size()) { + p_state->buffers.push_back(Vector<uint8_t>()); } // Perform export preflight for document extensions. Only extensions that // return OK will be used for the rest of the export steps. document_extensions.clear(); for (Ref<GLTFDocumentExtension> ext : all_document_extensions) { ERR_CONTINUE(ext.is_null()); - Error err = ext->export_preflight(r_state, p_node); + Error err = ext->export_preflight(p_state, p_node); if (err == OK) { document_extensions.push_back(ext); } } // Add the root node(s) and their descendants to the state. - _convert_scene_node(r_state, p_node, -1, -1); + _convert_scene_node(p_state, p_node, -1, -1); return OK; } -Error GLTFDocument::append_from_buffer(PackedByteArray p_bytes, String p_base_path, Ref<GLTFState> r_state, uint32_t p_flags) { - ERR_FAIL_COND_V(r_state.is_null(), FAILED); +Error GLTFDocument::append_from_buffer(PackedByteArray p_bytes, String p_base_path, Ref<GLTFState> p_state, uint32_t p_flags) { + ERR_FAIL_COND_V(p_state.is_null(), FAILED); // TODO Add missing texture and missing .bin file paths to r_missing_deps 2021-09-10 fire Error err = FAILED; - r_state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS; - r_state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS; + p_state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS; + p_state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS; Ref<FileAccessMemory> file_access; file_access.instantiate(); file_access->open_custom(p_bytes.ptr(), p_bytes.size()); - r_state->base_path = p_base_path.get_base_dir(); - err = _parse(r_state, r_state->base_path, file_access); + p_state->base_path = p_base_path.get_base_dir(); + err = _parse(p_state, p_state->base_path, file_access); ERR_FAIL_COND_V(err != OK, err); for (Ref<GLTFDocumentExtension> ext : document_extensions) { ERR_CONTINUE(ext.is_null()); - err = ext->import_post_parse(r_state); + err = ext->import_post_parse(p_state); ERR_FAIL_COND_V(err != OK, err); } return OK; @@ -7436,14 +7456,14 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> p_state, const String &p_se return OK; } -Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> r_state, uint32_t p_flags, String p_base_path) { +Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint32_t p_flags, String p_base_path) { // TODO Add missing texture and missing .bin file paths to r_missing_deps 2021-09-10 fire - if (r_state == Ref<GLTFState>()) { - r_state.instantiate(); + if (p_state == Ref<GLTFState>()) { + p_state.instantiate(); } - r_state->filename = p_path.get_file().get_basename(); - r_state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS; - r_state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS; + p_state->filename = p_path.get_file().get_basename(); + p_state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS; + p_state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS; Error err; Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ, &err); ERR_FAIL_COND_V(err != OK, ERR_FILE_CANT_OPEN); @@ -7452,12 +7472,12 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> r_state, uint if (base_path.is_empty()) { base_path = p_path.get_base_dir(); } - r_state->base_path = base_path; - err = _parse(r_state, base_path, file); + p_state->base_path = base_path; + err = _parse(p_state, base_path, file); ERR_FAIL_COND_V(err != OK, err); for (Ref<GLTFDocumentExtension> ext : document_extensions) { ERR_CONTINUE(ext.is_null()); - err = ext->import_post_parse(r_state); + err = ext->import_post_parse(p_state); ERR_FAIL_COND_V(err != OK, err); } return OK; diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h index dfde53c9fb..f8bd156feb 100644 --- a/modules/gltf/gltf_document.h +++ b/modules/gltf/gltf_document.h @@ -151,8 +151,8 @@ private: Error _serialize_texture_samplers(Ref<GLTFState> p_state); Error _serialize_images(Ref<GLTFState> p_state, const String &p_path); Error _serialize_lights(Ref<GLTFState> p_state); - Ref<Image> _parse_image_bytes_into_image(Ref<GLTFState> p_state, const Vector<uint8_t> &p_bytes, const String &p_mime_type, int p_index); - void _parse_image_save_image(Ref<GLTFState> p_state, const String &p_mime_type, int p_index, Ref<Image> p_image); + Ref<Image> _parse_image_bytes_into_image(Ref<GLTFState> p_state, const Vector<uint8_t> &p_bytes, const String &p_mime_type, int p_index, String &r_file_extension); + void _parse_image_save_image(Ref<GLTFState> p_state, const Vector<uint8_t> &p_bytes, const String &p_file_extension, int p_index, Ref<Image> p_image); Error _parse_images(Ref<GLTFState> p_state, const String &p_base_path); Error _parse_textures(Ref<GLTFState> p_state); Error _parse_texture_samplers(Ref<GLTFState> p_state); @@ -293,9 +293,9 @@ private: static float get_max_component(const Color &p_color); public: - Error append_from_file(String p_path, Ref<GLTFState> r_state, uint32_t p_flags = 0, String p_base_path = String()); - Error append_from_buffer(PackedByteArray p_bytes, String p_base_path, Ref<GLTFState> r_state, uint32_t p_flags = 0); - Error append_from_scene(Node *p_node, Ref<GLTFState> r_state, uint32_t p_flags = 0); + Error append_from_file(String p_path, Ref<GLTFState> p_state, uint32_t p_flags = 0, String p_base_path = String()); + Error append_from_buffer(PackedByteArray p_bytes, String p_base_path, Ref<GLTFState> p_state, uint32_t p_flags = 0); + Error append_from_scene(Node *p_node, Ref<GLTFState> p_state, uint32_t p_flags = 0); public: Node *generate_scene(Ref<GLTFState> p_state, float p_bake_fps = 30.0f, bool p_trimming = false, bool p_remove_immutable_tracks = true); diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 1ed495943f..f592533a5a 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -1197,8 +1197,6 @@ void CSharpLanguage::_editor_init_callback() { // Add plugin to EditorNode and enable it EditorNode::add_editor_plugin(godotsharp_editor); - ED_SHORTCUT("mono/build_solution", TTR("Build Solution"), KeyModifierMask::ALT | Key::B); - ED_SHORTCUT_OVERRIDE("mono/build_solution", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::B); godotsharp_editor->enable_plugin(); get_singleton()->godotsharp_editor = godotsharp_editor; diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs index 1bb1b3227e..cc11132a55 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs @@ -114,7 +114,7 @@ namespace GodotTools.Build var toolBarHBox = new HBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill }; AddChild(toolBarHBox); - _buildMenuBtn = new MenuButton { Text = "Build", Icon = GetThemeIcon("Play", "EditorIcons") }; + _buildMenuBtn = new MenuButton { Text = "Build", Icon = GetThemeIcon("BuildCSharp", "EditorIcons") }; toolBarHBox.AddChild(_buildMenuBtn); var buildMenu = _buildMenuBtn.GetPopup(); @@ -184,7 +184,7 @@ namespace GodotTools.Build if (what == NotificationThemeChanged) { if (_buildMenuBtn != null) - _buildMenuBtn.Icon = GetThemeIcon("Play", "EditorIcons"); + _buildMenuBtn.Icon = GetThemeIcon("BuildCSharp", "EditorIcons"); if (_errorsBtn != null) _errorsBtn.Icon = GetThemeIcon("StatusError", "EditorIcons"); if (_warningsBtn != null) diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 622a155d37..cdf0a344d4 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -497,18 +497,20 @@ namespace GodotTools AddToolSubmenuItem("C#", _menuPopup); - var buildSolutionShortcut = (Shortcut)EditorShortcut("mono/build_solution"); - _toolBarBuildButton = new Button { - Text = "Build", - TooltipText = "Build Solution".TTR(), + Flat = true, + Icon = editorBaseControl.GetThemeIcon("BuildCSharp", "EditorIcons"), FocusMode = Control.FocusModeEnum.None, - Shortcut = buildSolutionShortcut, - ShortcutInTooltip = true + Shortcut = EditorDefShortcut("mono/build_solution", "Build Project".TTR(), (Key)KeyModifierMask.MaskAlt | Key.B), + ShortcutInTooltip = true, }; + EditorShortcutOverride("mono/build_solution", "macos", (Key)KeyModifierMask.MaskMeta | (Key)KeyModifierMask.MaskCtrl | Key.B); + _toolBarBuildButton.Pressed += BuildProjectPressed; - AddControlToContainer(CustomControlContainer.Toolbar, _toolBarBuildButton); + Internal.EditorPlugin_AddControlToEditorRunBar(_toolBarBuildButton); + // Move Build button so it appears to the left of the Play button. + _toolBarBuildButton.GetParent().MoveChild(_toolBarBuildButton, 0); if (File.Exists(GodotSharpDirs.ProjectCsProjPath)) { diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs index 45ae7eb86b..a6718e8fd5 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs @@ -29,11 +29,26 @@ namespace GodotTools.Internals return Variant.CreateTakingOwnershipOfDisposableValue(result); } - public static Variant EditorShortcut(string setting) + public static Shortcut EditorDefShortcut(string setting, string name, Key keycode = Key.None, bool physical = false) { using godot_string settingIn = Marshaling.ConvertStringToNative(setting); - Internal.godot_icall_Globals_EditorShortcut(settingIn, out godot_variant result); - return Variant.CreateTakingOwnershipOfDisposableValue(result); + using godot_string nameIn = Marshaling.ConvertStringToNative(name); + Internal.godot_icall_Globals_EditorDefShortcut(settingIn, nameIn, keycode, physical.ToGodotBool(), out godot_variant result); + return (Shortcut)Variant.CreateTakingOwnershipOfDisposableValue(result); + } + + public static Shortcut EditorGetShortcut(string setting) + { + using godot_string settingIn = Marshaling.ConvertStringToNative(setting); + Internal.godot_icall_Globals_EditorGetShortcut(settingIn, out godot_variant result); + return (Shortcut)Variant.CreateTakingOwnershipOfDisposableValue(result); + } + + public static void EditorShortcutOverride(string setting, string feature, Key keycode = Key.None, bool physical = false) + { + using godot_string settingIn = Marshaling.ConvertStringToNative(setting); + using godot_string featureIn = Marshaling.ConvertStringToNative(feature); + Internal.godot_icall_Globals_EditorShortcutOverride(settingIn, featureIn, keycode, physical.ToGodotBool()); } [SuppressMessage("ReSharper", "InconsistentNaming")] diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs index 3ea11750b7..90c443ebb8 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs @@ -54,6 +54,9 @@ namespace GodotTools.Internals public static void EditorRunStop() => godot_icall_Internal_EditorRunStop(); + public static void EditorPlugin_AddControlToEditorRunBar(Control control) => + godot_icall_Internal_EditorPlugin_AddControlToEditorRunBar(control.NativeInstance); + public static void ScriptEditorDebugger_ReloadScripts() => godot_icall_Internal_ScriptEditorDebugger_ReloadScripts(); @@ -137,6 +140,8 @@ namespace GodotTools.Internals private static partial void godot_icall_Internal_EditorRunStop(); + private static partial void godot_icall_Internal_EditorPlugin_AddControlToEditorRunBar(IntPtr p_control); + private static partial void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts(); private static partial void godot_icall_Internal_CodeCompletionRequest(int kind, in godot_string scriptFile, @@ -151,7 +156,13 @@ namespace GodotTools.Internals bool restartIfChanged, out godot_variant result); public static partial void - godot_icall_Globals_EditorShortcut(in godot_string setting, out godot_variant result); + godot_icall_Globals_EditorDefShortcut(in godot_string setting, in godot_string name, Key keycode, godot_bool physical, out godot_variant result); + + public static partial void + godot_icall_Globals_EditorGetShortcut(in godot_string setting, out godot_variant result); + + public static partial void + godot_icall_Globals_EditorShortcutOverride(in godot_string setting, in godot_string feature, Key keycode, godot_bool physical); public static partial void godot_icall_Globals_TTR(in godot_string text, out godot_string dest); diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index ba6b91b704..fc99f3ceda 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -168,6 +168,10 @@ void godot_icall_Internal_EditorRunStop() { EditorRunBar::get_singleton()->stop_playing(); } +void godot_icall_Internal_EditorPlugin_AddControlToEditorRunBar(Control *p_control) { + EditorRunBar::get_singleton()->get_buttons_container()->add_child(p_control); +} + void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts() { EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); if (ed) { @@ -199,12 +203,25 @@ void godot_icall_Globals_EditorDef(const godot_string *p_setting, const godot_va memnew_placement(r_result, Variant(result)); } -void godot_icall_Globals_EditorShortcut(const godot_string *p_setting, godot_variant *r_result) { +void godot_icall_Globals_EditorDefShortcut(const godot_string *p_setting, const godot_string *p_name, Key p_keycode, bool p_physical, godot_variant *r_result) { + String setting = *reinterpret_cast<const String *>(p_setting); + String name = *reinterpret_cast<const String *>(p_name); + Ref<Shortcut> result = ED_SHORTCUT(setting, name, p_keycode, p_physical); + memnew_placement(r_result, Variant(result)); +} + +void godot_icall_Globals_EditorGetShortcut(const godot_string *p_setting, Ref<Shortcut> *r_result) { String setting = *reinterpret_cast<const String *>(p_setting); Ref<Shortcut> result = ED_GET_SHORTCUT(setting); memnew_placement(r_result, Variant(result)); } +void godot_icall_Globals_EditorShortcutOverride(const godot_string *p_setting, const godot_string *p_feature, Key p_keycode, bool p_physical) { + String setting = *reinterpret_cast<const String *>(p_setting); + String feature = *reinterpret_cast<const String *>(p_feature); + ED_SHORTCUT_OVERRIDE(setting, feature, p_keycode, p_physical); +} + void godot_icall_Globals_TTR(const godot_string *p_text, godot_string *r_dest) { String text = *reinterpret_cast<const String *>(p_text); memnew_placement(r_dest, String(TTR(text))); @@ -251,12 +268,15 @@ static const void *unmanaged_callbacks[]{ (void *)godot_icall_Internal_EditorNodeShowScriptScreen, (void *)godot_icall_Internal_EditorRunPlay, (void *)godot_icall_Internal_EditorRunStop, + (void *)godot_icall_Internal_EditorPlugin_AddControlToEditorRunBar, (void *)godot_icall_Internal_ScriptEditorDebugger_ReloadScripts, (void *)godot_icall_Internal_CodeCompletionRequest, (void *)godot_icall_Globals_EditorScale, (void *)godot_icall_Globals_GlobalDef, (void *)godot_icall_Globals_EditorDef, - (void *)godot_icall_Globals_EditorShortcut, + (void *)godot_icall_Globals_EditorDefShortcut, + (void *)godot_icall_Globals_EditorGetShortcut, + (void *)godot_icall_Globals_EditorShortcutOverride, (void *)godot_icall_Globals_TTR, (void *)godot_icall_Utils_OS_GetPlatformName, (void *)godot_icall_Utils_OS_UnixFileHasExecutableAccess, diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs index c4161d2ded..57b292793a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs @@ -14,14 +14,46 @@ namespace Godot { private static void AppendTypeName(this StringBuilder sb, Type type) { - if (type.IsPrimitive) - sb.Append(type.Name); - else if (type == typeof(void)) + // Use the C# type keyword for built-in types. + // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/built-in-types + if (type == typeof(void)) sb.Append("void"); + else if (type == typeof(bool)) + sb.Append("bool"); + else if (type == typeof(byte)) + sb.Append("byte"); + else if (type == typeof(sbyte)) + sb.Append("sbyte"); + else if (type == typeof(char)) + sb.Append("char"); + else if (type == typeof(decimal)) + sb.Append("decimal"); + else if (type == typeof(double)) + sb.Append("double"); + else if (type == typeof(float)) + sb.Append("float"); + else if (type == typeof(int)) + sb.Append("int"); + else if (type == typeof(uint)) + sb.Append("uint"); + else if (type == typeof(nint)) + sb.Append("nint"); + else if (type == typeof(nuint)) + sb.Append("nuint"); + else if (type == typeof(long)) + sb.Append("long"); + else if (type == typeof(ulong)) + sb.Append("ulong"); + else if (type == typeof(short)) + sb.Append("short"); + else if (type == typeof(ushort)) + sb.Append("ushort"); + else if (type == typeof(object)) + sb.Append("object"); + else if (type == typeof(string)) + sb.Append("string"); else sb.Append(type); - - sb.Append(' '); } internal static void InstallTraceListener() @@ -70,13 +102,26 @@ namespace Godot } } + internal static unsafe StackFrame? GetCurrentStackFrame(int skipFrames = 0) + { + // We skip 2 frames: + // The first skipped frame is the current method. + // The second skipped frame is a method in NativeInterop.NativeFuncs. + var stackTrace = new StackTrace(skipFrames: 2 + skipFrames, fNeedFileInfo: true); + return stackTrace.GetFrame(0); + } + [UnmanagedCallersOnly] internal static unsafe void GetCurrentStackInfo(void* destVector) { try { var vector = (godot_stack_info_vector*)destVector; - var stackTrace = new StackTrace(skipFrames: 1, fNeedFileInfo: true); + + // We skip 2 frames: + // The first skipped frame is the current method. + // The second skipped frame is a method in NativeInterop.NativeFuncs. + var stackTrace = new StackTrace(skipFrames: 2, fNeedFileInfo: true); int frameCount = stackTrace.FrameCount; if (frameCount == 0) @@ -87,6 +132,14 @@ namespace Godot int i = 0; foreach (StackFrame frame in stackTrace.GetFrames()) { + var method = frame.GetMethod(); + + if (method is MethodInfo methodInfo && methodInfo.IsDefined(typeof(StackTraceHiddenAttribute))) + { + // Skip methods marked hidden from the stack trace. + continue; + } + string? fileName = frame.GetFileName(); int fileLineNumber = frame.GetFileLineNumber(); @@ -102,6 +155,9 @@ namespace Godot i++; } + + // Resize the vector again in case we skipped some frames. + vector->Resize(i); } catch (Exception e) { @@ -122,7 +178,10 @@ namespace Godot var sb = new StringBuilder(); if (methodBase is MethodInfo methodInfo) + { sb.AppendTypeName(methodInfo.ReturnType); + sb.Append(' '); + } sb.Append(methodBase.DeclaringType?.FullName ?? "<unknown>"); sb.Append('.'); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs index 9425b7424c..33ebb8171e 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Text; using Godot.NativeInterop; @@ -334,6 +335,21 @@ namespace Godot NativeFuncs.godotsharp_printt(godotStr); } + [StackTraceHidden] + private static void ErrPrintError(string message, godot_error_handler_type type = godot_error_handler_type.ERR_HANDLER_ERROR) + { + // Skip 1 frame to avoid current method. + var stackFrame = DebuggingUtils.GetCurrentStackFrame(skipFrames: 1); + string callerFilePath = ProjectSettings.LocalizePath(stackFrame.GetFileName()); + DebuggingUtils.GetStackFrameMethodDecl(stackFrame, out string callerName); + int callerLineNumber = stackFrame.GetFileLineNumber(); + + using godot_string messageStr = Marshaling.ConvertStringToNative(message); + using godot_string callerNameStr = Marshaling.ConvertStringToNative(callerName); + using godot_string callerFilePathStr = Marshaling.ConvertStringToNative(callerFilePath); + NativeFuncs.godotsharp_err_print_error(callerNameStr, callerFilePathStr, callerLineNumber, messageStr, p_type: type); + } + /// <summary> /// Pushes an error message to Godot's built-in debugger and to the OS terminal. /// @@ -347,8 +363,7 @@ namespace Godot /// <param name="message">Error message.</param> public static void PushError(string message) { - using var godotStr = Marshaling.ConvertStringToNative(message); - NativeFuncs.godotsharp_pusherror(godotStr); + ErrPrintError(message); } /// <summary> @@ -364,7 +379,7 @@ namespace Godot /// <param name="what">Arguments that form the error message.</param> public static void PushError(params object[] what) { - PushError(AppendPrintParams(what)); + ErrPrintError(AppendPrintParams(what)); } /// <summary> @@ -378,8 +393,7 @@ namespace Godot /// <param name="message">Warning message.</param> public static void PushWarning(string message) { - using var godotStr = Marshaling.ConvertStringToNative(message); - NativeFuncs.godotsharp_pushwarning(godotStr); + ErrPrintError(message, type: godot_error_handler_type.ERR_HANDLER_WARNING); } /// <summary> @@ -393,7 +407,7 @@ namespace Godot /// <param name="what">Arguments that form the warning message.</param> public static void PushWarning(params object[] what) { - PushWarning(AppendPrintParams(what)); + ErrPrintError(AppendPrintParams(what), type: godot_error_handler_type.ERR_HANDLER_WARNING); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs index ba2c232580..a656c5de90 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs @@ -95,7 +95,7 @@ namespace Godot.NativeInterop } NativeFuncs.godotsharp_internal_script_debugger_send_error(nFunc, nFile, line, - nErrorMsg, nExcMsg, p_warning: godot_bool.False, stackInfoVector); + nErrorMsg, nExcMsg, godot_error_handler_type.ERR_HANDLER_ERROR, stackInfoVector); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs index 1dddc82e85..d5d9404ed1 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs @@ -1134,4 +1134,13 @@ namespace Godot.NativeInterop get => _ptr != null ? *((int*)_ptr - 1) : 0; } } + + [SuppressMessage("ReSharper", "InconsistentNaming")] + public enum godot_error_handler_type + { + ERR_HANDLER_ERROR = 0, + ERR_HANDLER_WARNING, + ERR_HANDLER_SCRIPT, + ERR_HANDLER_SHADER, + } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index 3ec3d1e530..d42ee15657 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -58,7 +58,7 @@ namespace Godot.NativeInterop internal static partial void godotsharp_internal_script_debugger_send_error(in godot_string p_func, in godot_string p_file, int p_line, in godot_string p_err, in godot_string p_descr, - godot_bool p_warning, in DebuggingUtils.godot_stack_info_vector p_stack_info_vector); + godot_error_handler_type p_type, in DebuggingUtils.godot_stack_info_vector p_stack_info_vector); internal static partial godot_bool godotsharp_internal_script_debugger_is_active(); @@ -540,9 +540,7 @@ namespace Godot.NativeInterop internal static partial void godotsharp_var_to_str(in godot_variant p_var, out godot_string r_ret); - internal static partial void godotsharp_pusherror(in godot_string p_str); - - internal static partial void godotsharp_pushwarning(in godot_string p_str); + internal static partial void godotsharp_err_print_error(in godot_string p_function, in godot_string p_file, int p_line, in godot_string p_error, in godot_string p_message = default, godot_bool p_editor_notify = godot_bool.False, godot_error_handler_type p_type = godot_error_handler_type.ERR_HANDLER_ERROR); // Object diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index ee4de4e9f5..24a9d4030a 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -92,10 +92,10 @@ void godotsharp_stack_info_vector_destroy( void godotsharp_internal_script_debugger_send_error(const String *p_func, const String *p_file, int32_t p_line, const String *p_err, const String *p_descr, - bool p_warning, const Vector<ScriptLanguage::StackInfo> *p_stack_info_vector) { + ErrorHandlerType p_type, const Vector<ScriptLanguage::StackInfo> *p_stack_info_vector) { const String file = ProjectSettings::get_singleton()->localize_path(p_file->simplify_path()); EngineDebugger::get_script_debugger()->send_error(*p_func, file, p_line, *p_err, *p_descr, - true, p_warning ? ERR_HANDLER_WARNING : ERR_HANDLER_ERROR, *p_stack_info_vector); + true, p_type, *p_stack_info_vector); } bool godotsharp_internal_script_debugger_is_active() { @@ -1320,12 +1320,14 @@ void godotsharp_printraw(const godot_string *p_what) { OS::get_singleton()->print("%s", reinterpret_cast<const String *>(p_what)->utf8().get_data()); } -void godotsharp_pusherror(const godot_string *p_str) { - ERR_PRINT(*reinterpret_cast<const String *>(p_str)); -} - -void godotsharp_pushwarning(const godot_string *p_str) { - WARN_PRINT(*reinterpret_cast<const String *>(p_str)); +void godotsharp_err_print_error(const godot_string *p_function, const godot_string *p_file, int32_t p_line, const godot_string *p_error, const godot_string *p_message, bool p_editor_notify, ErrorHandlerType p_type) { + _err_print_error( + reinterpret_cast<const String *>(p_function)->utf8().get_data(), + reinterpret_cast<const String *>(p_file)->utf8().get_data(), + p_line, + reinterpret_cast<const String *>(p_error)->utf8().get_data(), + reinterpret_cast<const String *>(p_message)->utf8().get_data(), + p_editor_notify, p_type); } void godotsharp_var_to_str(const godot_variant *p_var, godot_string *r_ret) { @@ -1611,8 +1613,7 @@ static const void *unmanaged_callbacks[]{ (void *)godotsharp_str_to_var, (void *)godotsharp_var_to_bytes, (void *)godotsharp_var_to_str, - (void *)godotsharp_pusherror, - (void *)godotsharp_pushwarning, + (void *)godotsharp_err_print_error, (void *)godotsharp_object_to_string, }; diff --git a/modules/mono/icons/BuildCSharp.svg b/modules/mono/icons/BuildCSharp.svg new file mode 100644 index 0000000000..9d0102c35d --- /dev/null +++ b/modules/mono/icons/BuildCSharp.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M 9.6060193,0.78346667 C 8.6741914,0.96303367 7.6708299,1.5334576 6.9028943,1.9768256 l -2.1523438,1.244141 0.082031,0.138672 -0.3105469,-0.00781 -2.5839844,1.492188 1.9101563,3.308593 2.5820312,-1.490234 0.1425781,-0.255859 4.1875002,7.2539054 0.5,0.867187 c 0.415803,0.720194 1.331398,0.964165 2.050782,0.548829 0.719286,-0.415279 0.963839,-1.33001 0.548828,-2.048829 l -2,-3.4648424 -2.8808602,-4.990235 3.7070322,-2.101562 -0.265626,-0.439453 C 11.697382,0.83561667 10.650124,0.58226267 9.6060193,0.78346667 Z" fill="#e0e0e0"/></svg> diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 5e52f25cf4..247968e251 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -376,6 +376,12 @@ void GDMono::initialize() { godot_plugins_initialize_fn godot_plugins_initialize = nullptr; + // Check that the .NET assemblies directory exists before trying to use it. + if (!DirAccess::exists(GodotSharpDirs::get_api_assemblies_dir())) { + OS::get_singleton()->alert(vformat(RTR("Unable to find the .NET assemblies directory.\nMake sure the '%s' directory exists and contains the .NET assemblies."), GodotSharpDirs::get_api_assemblies_dir()), RTR(".NET assemblies not found")); + ERR_FAIL_MSG(".NET: Assemblies not found"); + } + if (!load_hostfxr(hostfxr_dll_handle)) { #if !defined(TOOLS_ENABLED) godot_plugins_initialize = try_load_native_aot_library(hostfxr_dll_handle); |
