diff options
52 files changed, 854 insertions, 1278 deletions
diff --git a/core/doc_data.h b/core/doc_data.h index 0fe7414b98..b8c92a4b67 100644 --- a/core/doc_data.h +++ b/core/doc_data.h @@ -532,6 +532,42 @@ public: } }; + struct EnumDoc { + String description; + bool is_deprecated = false; + bool is_experimental = false; + static EnumDoc from_dict(const Dictionary &p_dict) { + EnumDoc doc; + + if (p_dict.has("description")) { + doc.description = p_dict["description"]; + } + + if (p_dict.has("is_deprecated")) { + doc.is_deprecated = p_dict["is_deprecated"]; + } + + if (p_dict.has("is_experimental")) { + doc.is_experimental = p_dict["is_experimental"]; + } + + return doc; + } + static Dictionary to_dict(const EnumDoc &p_doc) { + Dictionary dict; + + if (!p_doc.description.is_empty()) { + dict["description"] = p_doc.description; + } + + dict["is_deprecated"] = p_doc.is_deprecated; + + dict["is_experimental"] = p_doc.is_experimental; + + return dict; + } + }; + struct ClassDoc { String name; String inherits; @@ -543,7 +579,7 @@ public: Vector<MethodDoc> operators; Vector<MethodDoc> signals; Vector<ConstantDoc> constants; - HashMap<String, String> enums; + HashMap<String, EnumDoc> enums; Vector<PropertyDoc> properties; Vector<MethodDoc> annotations; Vector<ThemeItemDoc> theme_properties; @@ -626,7 +662,7 @@ public: enums = p_dict["enums"]; } for (int i = 0; i < enums.size(); i++) { - doc.enums[enums.get_key_at_index(i)] = enums.get_value_at_index(i); + doc.enums[enums.get_key_at_index(i)] = EnumDoc::from_dict(enums.get_value_at_index(i)); } Array properties; @@ -740,8 +776,8 @@ public: if (!p_doc.enums.is_empty()) { Dictionary enums; - for (const KeyValue<String, String> &E : p_doc.enums) { - enums[E.key] = E.value; + for (const KeyValue<String, EnumDoc> &E : p_doc.enums) { + enums[E.key] = EnumDoc::to_dict(E.value); } dict["enums"] = enums; } diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 1fe662b1fa..df0253349c 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -275,10 +275,10 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin #ifdef TOOLS_ENABLED Ref<FileAccess> file_check = FileAccess::create(FileAccess::ACCESS_RESOURCES); - ERR_FAIL_COND_V_MSG(!file_check->file_exists(p_path), Ref<Resource>(), "Resource file not found: " + p_path + "."); + ERR_FAIL_COND_V_MSG(!file_check->file_exists(p_path), Ref<Resource>(), vformat("Resource file not found: %s (expected type: %s)", p_path, p_type_hint)); #endif - ERR_FAIL_V_MSG(Ref<Resource>(), "No loader found for resource: " + p_path + "."); + ERR_FAIL_V_MSG(Ref<Resource>(), vformat("No loader found for resource: %s (expected type: %s)", p_path, p_type_hint)); } void ResourceLoader::_thread_load_function(void *p_userdata) { diff --git a/core/object/message_queue.cpp b/core/object/message_queue.cpp index dd7aba1384..506f8291eb 100644 --- a/core/object/message_queue.cpp +++ b/core/object/message_queue.cpp @@ -35,10 +35,6 @@ #include "core/object/class_db.h" #include "core/object/script_language.h" -#ifdef DEBUG_ENABLED -#include "core/config/engine.h" -#endif - #ifdef DEV_ENABLED // Includes sanity checks to ensure that a queue set as a thread singleton override // is only ever called from the thread it was set for. @@ -320,34 +316,25 @@ Error CallQueue::flush() { Object *target = message->callable.get_object(); UNLOCK_MUTEX; -#ifdef DEBUG_ENABLED - if (!message->callable.is_valid()) { - // The editor would cause many of these. - if (!Engine::get_singleton()->is_editor_hint()) { - ERR_PRINT("Trying to execute a deferred call/notification/set on a previously freed instance. Consider using queue_free() instead of free()."); - } - } else -#endif - { - switch (message->type & FLAG_MASK) { - case TYPE_CALL: { - if (target || (message->type & FLAG_NULL_IS_OK)) { - Variant *args = (Variant *)(message + 1); - _call_function(message->callable, args, message->args, message->type & FLAG_SHOW_ERROR); - } - } break; - case TYPE_NOTIFICATION: { - if (target) { - target->notification(message->notification); - } - } break; - case TYPE_SET: { - if (target) { - Variant *arg = (Variant *)(message + 1); - target->set(message->callable.get_method(), *arg); - } - } break; - } + + switch (message->type & FLAG_MASK) { + case TYPE_CALL: { + if (target || (message->type & FLAG_NULL_IS_OK)) { + Variant *args = (Variant *)(message + 1); + _call_function(message->callable, args, message->args, message->type & FLAG_SHOW_ERROR); + } + } break; + case TYPE_NOTIFICATION: { + if (target) { + target->notification(message->notification); + } + } break; + case TYPE_SET: { + if (target) { + Variant *arg = (Variant *)(message + 1); + target->set(message->callable.get_method(), *arg); + } + } break; } if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) { diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 02f001edc9..d240b6ef48 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -430,6 +430,12 @@ <member name="debug/gdscript/warnings/confusable_identifier" type="int" setter="" getter="" default="1"> When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when an identifier contains characters that can be confused with something else, like when mixing different alphabets. </member> + <member name="debug/gdscript/warnings/confusable_local_declaration" type="int" setter="" getter="" default="1"> + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when an identifier declared in the nested block has the same name as an identifier declared below in the parent block. + </member> + <member name="debug/gdscript/warnings/confusable_local_usage" type="int" setter="" getter="" default="1"> + When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when an identifier that will be shadowed below in the block is used. + </member> <member name="debug/gdscript/warnings/constant_used_as_function" type="int" setter="" getter="" default="1"> When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a constant is used as a function. </member> diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index 64d54b3ba0..02c1286392 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -27,6 +27,13 @@ Bakes the material data of the Mesh passed in the [param base] parameter with optional [param material_overrides] to a set of [Image]s of size [param image_size]. Returns an array of [Image]s containing material properties as specified in [enum BakeChannels]. </description> </method> + <method name="call_on_render_thread"> + <return type="void" /> + <param index="0" name="callable" type="Callable" /> + <description> + As the RenderingServer actual logic may run on an separate thread, accessing its internals from the main (or any other) thread will result in errors. To make it easier to run code that can safely access the rendering internals (such as [RenderingDevice] and similar RD classes), push a callable via this function so it will be executed on the render thread. + </description> + </method> <method name="camera_attributes_create"> <return type="RID" /> <description> diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index d8ddfe5c32..6791a9db5e 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -1738,7 +1738,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ RenderDataGLES3 render_data; { render_data.render_buffers = rb; - render_data.transparent_bg = rb.is_valid() ? rb->is_transparent : false; + render_data.transparent_bg = rb.is_valid() ? rt->is_transparent : false; // Our first camera is used by default render_data.cam_transform = p_camera_data->main_transform; render_data.inv_cam_transform = render_data.cam_transform.affine_inverse(); @@ -1984,6 +1984,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ } if (!keep_color) { + clear_color.a = render_data.transparent_bg ? 0.0f : 1.0f; glClearBufferfv(GL_COLOR, 0, clear_color.components); } RENDER_TIMESTAMP("Render Opaque Pass"); diff --git a/drivers/gles3/storage/render_scene_buffers_gles3.cpp b/drivers/gles3/storage/render_scene_buffers_gles3.cpp index 76c0097bab..829574cae0 100644 --- a/drivers/gles3/storage/render_scene_buffers_gles3.cpp +++ b/drivers/gles3/storage/render_scene_buffers_gles3.cpp @@ -38,8 +38,6 @@ RenderSceneBuffersGLES3::~RenderSceneBuffersGLES3() { } void RenderSceneBuffersGLES3::configure(const RenderSceneBuffersConfiguration *p_config) { - GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton(); - //internal_size.x = p_config->get_internal_size().x; // ignore for now //internal_size.y = p_config->get_internal_size().y; width = p_config->get_target_size().x; @@ -54,10 +52,6 @@ void RenderSceneBuffersGLES3::configure(const RenderSceneBuffersConfiguration *p view_count = p_config->get_view_count(); free_render_buffer_data(); - - GLES3::RenderTarget *rt = texture_storage->get_render_target(render_target); - - is_transparent = rt->is_transparent; } void RenderSceneBuffersGLES3::free_render_buffer_data() { diff --git a/drivers/gles3/storage/render_scene_buffers_gles3.h b/drivers/gles3/storage/render_scene_buffers_gles3.h index 64b95c417c..aa98797ed1 100644 --- a/drivers/gles3/storage/render_scene_buffers_gles3.h +++ b/drivers/gles3/storage/render_scene_buffers_gles3.h @@ -58,8 +58,6 @@ public: //bool use_debanding = false; uint32_t view_count = 1; - bool is_transparent = false; - RID render_target; //built-in textures used for ping pong image processing and blurring diff --git a/drivers/vulkan/rendering_device_vulkan.cpp b/drivers/vulkan/rendering_device_vulkan.cpp index be6f8f3580..cd18d84837 100644 --- a/drivers/vulkan/rendering_device_vulkan.cpp +++ b/drivers/vulkan/rendering_device_vulkan.cpp @@ -4687,7 +4687,7 @@ Vector<uint8_t> RenderingDeviceVulkan::shader_compile_binary_from_spirv(const Ve "Number of uniform sets is larger than what is supported by the hardware (" + itos(limits.maxBoundDescriptorSets) + ")."); // Collect reflection data into binary data. - RenderingDeviceVulkanShaderBinaryData binary_data; + RenderingDeviceVulkanShaderBinaryData binary_data{}; Vector<Vector<RenderingDeviceVulkanShaderBinaryDataBinding>> uniform_info; // Set bindings. Vector<RenderingDeviceVulkanShaderBinarySpecializationConstant> specialization_constants; { diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index cde74e0854..6c5c99698d 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -891,7 +891,7 @@ void CodeTextEditor::_line_col_changed() { sb.append(itos(positional_column + 1).lpad(3)); sb.append(" | "); - sb.append(text_editor->is_indent_using_spaces() ? "Spaces" : "Tabs"); + sb.append(text_editor->is_indent_using_spaces() ? TTR("Spaces", "Indentation") : TTR("Tabs", "Indentation")); line_and_col_txt->set_text(sb.as_string()); diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index c9a95df34e..7573fcd21e 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -1059,6 +1059,7 @@ void EditorHelp::_update_doc() { if (cd.properties[i].is_deprecated) { DEPRECATED_DOC_TAG; } + if (cd.properties[i].is_experimental) { EXPERIMENTAL_DOC_TAG; } @@ -1303,6 +1304,7 @@ void EditorHelp::_update_doc() { if (cd.signals[i].is_deprecated) { DEPRECATED_DOC_TAG; } + if (cd.signals[i].is_experimental) { EXPERIMENTAL_DOC_TAG; } @@ -1363,6 +1365,7 @@ void EditorHelp::_update_doc() { enum_line[E.key] = class_desc->get_paragraph_count() - 2; _push_code_font(); + class_desc->push_color(theme_cache.title_color); if (E.value.size() && E.value[0].is_bitfield) { class_desc->add_text("flags "); @@ -1379,21 +1382,32 @@ void EditorHelp::_update_doc() { class_desc->push_color(theme_cache.headline_color); class_desc->add_text(e); class_desc->pop(); - _pop_code_font(); class_desc->push_color(theme_cache.symbol_color); class_desc->add_text(":"); class_desc->pop(); + if (cd.enums.has(e)) { + if (cd.enums[e].is_deprecated) { + DEPRECATED_DOC_TAG; + } + + if (cd.enums[e].is_experimental) { + EXPERIMENTAL_DOC_TAG; + } + } + + _pop_code_font(); + class_desc->add_newline(); class_desc->add_newline(); // Enum description. - if (e != "@unnamed_enums" && cd.enums.has(e) && !cd.enums[e].strip_edges().is_empty()) { + if (e != "@unnamed_enums" && cd.enums.has(e) && !cd.enums[e].description.strip_edges().is_empty()) { class_desc->push_color(theme_cache.text_color); _push_normal_font(); class_desc->push_indent(1); - _add_text(cd.enums[e]); + _add_text(cd.enums[e].description); class_desc->pop(); _pop_normal_font(); class_desc->pop(); @@ -1417,6 +1431,7 @@ void EditorHelp::_update_doc() { constant_line[enum_list[i].name] = class_desc->get_paragraph_count() - 2; _push_code_font(); + _add_bulletpoint(); class_desc->push_color(theme_cache.headline_color); _add_text(enum_list[i].name); @@ -1427,7 +1442,6 @@ void EditorHelp::_update_doc() { class_desc->push_color(theme_cache.value_color); _add_text(_fix_constant(enum_list[i].value)); class_desc->pop(); - _pop_code_font(); if (enum_list[i].is_deprecated) { DEPRECATED_DOC_TAG; @@ -1437,6 +1451,8 @@ void EditorHelp::_update_doc() { EXPERIMENTAL_DOC_TAG; } + _pop_code_font(); + class_desc->add_newline(); if (!enum_list[i].description.strip_edges().is_empty()) { @@ -1503,8 +1519,6 @@ void EditorHelp::_update_doc() { _add_text(_fix_constant(constants[i].value)); class_desc->pop(); - _pop_code_font(); - if (constants[i].is_deprecated) { DEPRECATED_DOC_TAG; } @@ -1513,6 +1527,8 @@ void EditorHelp::_update_doc() { EXPERIMENTAL_DOC_TAG; } + _pop_code_font(); + class_desc->add_newline(); if (!constants[i].description.strip_edges().is_empty()) { @@ -1692,6 +1708,7 @@ void EditorHelp::_update_doc() { if (cd.properties[i].is_deprecated) { DEPRECATED_DOC_TAG; } + if (cd.properties[i].is_experimental) { EXPERIMENTAL_DOC_TAG; } diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index a8187c27b6..074dd2ffe0 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -3620,7 +3620,9 @@ void EditorNode::set_current_scene(int p_idx) { _update_title(); _update_scene_tabs(); - call_deferred(SNAME("_set_main_scene_state"), state, get_edited_scene()); // Do after everything else is done setting up. + if (tabs_to_close.is_empty()) { + call_deferred(SNAME("_set_main_scene_state"), state, get_edited_scene()); // Do after everything else is done setting up. + } } void EditorNode::setup_color_picker(ColorPicker *p_picker) { @@ -4988,10 +4990,7 @@ void EditorNode::_save_open_scenes_to_config(Ref<ConfigFile> p_layout) { p_layout->set_value(EDITOR_NODE_CONFIG_SECTION, "open_scenes", scenes); String currently_edited_scene_path = editor_data.get_scene_path(editor_data.get_edited_scene()); - // Don't save a bad path to the config. - if (!currently_edited_scene_path.is_empty()) { - p_layout->set_value(EDITOR_NODE_CONFIG_SECTION, "current_scene", currently_edited_scene_path); - } + p_layout->set_value(EDITOR_NODE_CONFIG_SECTION, "current_scene", currently_edited_scene_path); } void EditorNode::save_editor_layout_delayed() { @@ -5400,7 +5399,9 @@ void EditorNode::_load_open_scenes_from_config(Ref<ConfigFile> p_layout) { if (p_layout->has_section_key(EDITOR_NODE_CONFIG_SECTION, "current_scene")) { String current_scene = p_layout->get_value(EDITOR_NODE_CONFIG_SECTION, "current_scene"); int current_scene_idx = scenes.find(current_scene); - set_current_scene(current_scene_idx); + if (current_scene_idx >= 0) { + set_current_scene(current_scene_idx); + } } save_editor_layout_delayed(); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 321c0f34b5..6c28f5f163 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -2361,7 +2361,7 @@ void ScriptTextEditor::register_editor() { ED_SHORTCUT("script_text_editor/indent", TTR("Indent"), Key::NONE); ED_SHORTCUT("script_text_editor/unindent", TTR("Unindent"), KeyModifierMask::SHIFT | Key::TAB); - ED_SHORTCUT("script_text_editor/toggle_comment", TTR("Toggle Comment"), KeyModifierMask::CMD_OR_CTRL | Key::K); + ED_SHORTCUT_ARRAY("script_text_editor/toggle_comment", TTR("Toggle Comment"), { int32_t(KeyModifierMask::CMD_OR_CTRL | Key::K), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::SLASH) }); ED_SHORTCUT("script_text_editor/toggle_fold_line", TTR("Fold/Unfold Line"), KeyModifierMask::ALT | Key::F); ED_SHORTCUT_OVERRIDE("script_text_editor/toggle_fold_line", "macos", KeyModifierMask::CTRL | KeyModifierMask::META | Key::F); ED_SHORTCUT("script_text_editor/fold_all_lines", TTR("Fold All Lines"), Key::NONE); diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 691adcdb7a..7d2950f6f6 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -1977,10 +1977,10 @@ void ProjectManager::_notification(int p_what) { real_t size = get_size().x / EDSCALE; // Adjust names of tabs to fit the new size. if (size < 650) { - local_projects_hb->set_name(TTR("Local")); + local_projects_vb->set_name(TTR("Local")); asset_library->set_name(TTR("Asset Library")); } else { - local_projects_hb->set_name(TTR("Local Projects")); + local_projects_vb->set_name(TTR("Local Projects")); asset_library->set_name(TTR("Asset Library Projects")); } } @@ -2845,19 +2845,39 @@ ProjectManager::ProjectManager() { tabs->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); tabs->connect("tab_changed", callable_mp(this, &ProjectManager::_on_tab_changed)); - local_projects_hb = memnew(HBoxContainer); - local_projects_hb->set_name(TTR("Local Projects")); - tabs->add_child(local_projects_hb); + local_projects_vb = memnew(VBoxContainer); + local_projects_vb->set_name(TTR("Local Projects")); + tabs->add_child(local_projects_vb); { - // Projects + search bar - VBoxContainer *search_tree_vb = memnew(VBoxContainer); - local_projects_hb->add_child(search_tree_vb); - search_tree_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); - + // A bar at top with buttons and options. HBoxContainer *hb = memnew(HBoxContainer); hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); - search_tree_vb->add_child(hb); + local_projects_vb->add_child(hb); + + create_btn = memnew(Button); + create_btn->set_text(TTR("New")); + create_btn->set_shortcut(ED_SHORTCUT("project_manager/new_project", TTR("New Project"), KeyModifierMask::CMD_OR_CTRL | Key::N)); + create_btn->connect("pressed", callable_mp(this, &ProjectManager::_new_project)); + hb->add_child(create_btn); + + import_btn = memnew(Button); + import_btn->set_text(TTR("Import")); + import_btn->set_shortcut(ED_SHORTCUT("project_manager/import_project", TTR("Import Project"), KeyModifierMask::CMD_OR_CTRL | Key::I)); + import_btn->connect("pressed", callable_mp(this, &ProjectManager::_import_project)); + hb->add_child(import_btn); + + scan_btn = memnew(Button); + scan_btn->set_text(TTR("Scan")); + scan_btn->set_shortcut(ED_SHORTCUT("project_manager/scan_projects", TTR("Scan Projects"), KeyModifierMask::CMD_OR_CTRL | Key::S)); + scan_btn->connect("pressed", callable_mp(this, &ProjectManager::_scan_projects)); + hb->add_child(scan_btn); + + loading_label = memnew(Label(TTR("Loading, please wait..."))); + loading_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + hb->add_child(loading_label); + // The loading label is shown later. + loading_label->hide(); search_box = memnew(LineEdit); search_box->set_placeholder(TTR("Filter Projects")); @@ -2867,12 +2887,6 @@ ProjectManager::ProjectManager() { search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL); hb->add_child(search_box); - loading_label = memnew(Label(TTR("Loading, please wait..."))); - loading_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); - hb->add_child(loading_label); - // The loading label is shown later. - loading_label->hide(); - Label *sort_label = memnew(Label); sort_label->set_text(TTR("Sort:")); hb->add_child(sort_label); @@ -2880,6 +2894,7 @@ ProjectManager::ProjectManager() { filter_option = memnew(OptionButton); filter_option->set_clip_text(true); filter_option->set_h_size_flags(Control::SIZE_EXPAND_FILL); + filter_option->set_stretch_ratio(0.3); filter_option->connect("item_selected", callable_mp(this, &ProjectManager::_on_order_option_changed)); hb->add_child(filter_option); @@ -2892,41 +2907,28 @@ ProjectManager::ProjectManager() { for (int i = 0; i < sort_filter_titles.size(); i++) { filter_option->add_item(sort_filter_titles[i]); } + } + + { + // A container for the project list and for the side bar with buttons. + HBoxContainer *search_tree_hb = memnew(HBoxContainer); + local_projects_vb->add_child(search_tree_hb); + search_tree_hb->set_v_size_flags(Control::SIZE_EXPAND_FILL); search_panel = memnew(PanelContainer); - search_panel->set_v_size_flags(Control::SIZE_EXPAND_FILL); - search_tree_vb->add_child(search_panel); + search_panel->set_h_size_flags(Control::SIZE_EXPAND_FILL); + search_tree_hb->add_child(search_panel); _project_list = memnew(ProjectList); _project_list->connect(ProjectList::SIGNAL_SELECTION_CHANGED, callable_mp(this, &ProjectManager::_update_project_buttons)); _project_list->connect(ProjectList::SIGNAL_PROJECT_ASK_OPEN, callable_mp(this, &ProjectManager::_open_selected_projects_ask)); _project_list->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); search_panel->add_child(_project_list); - } - { - // Project tab side bar + // The side bar with the edit, run, rename, etc. buttons. VBoxContainer *tree_vb = memnew(VBoxContainer); tree_vb->set_custom_minimum_size(Size2(120, 120)); - local_projects_hb->add_child(tree_vb); - - create_btn = memnew(Button); - create_btn->set_text(TTR("New Project")); - create_btn->set_shortcut(ED_SHORTCUT("project_manager/new_project", TTR("New Project"), KeyModifierMask::CMD_OR_CTRL | Key::N)); - create_btn->connect("pressed", callable_mp(this, &ProjectManager::_new_project)); - tree_vb->add_child(create_btn); - - import_btn = memnew(Button); - import_btn->set_text(TTR("Import")); - import_btn->set_shortcut(ED_SHORTCUT("project_manager/import_project", TTR("Import Project"), KeyModifierMask::CMD_OR_CTRL | Key::I)); - import_btn->connect("pressed", callable_mp(this, &ProjectManager::_import_project)); - tree_vb->add_child(import_btn); - - scan_btn = memnew(Button); - scan_btn->set_text(TTR("Scan")); - scan_btn->set_shortcut(ED_SHORTCUT("project_manager/scan_projects", TTR("Scan Projects"), KeyModifierMask::CMD_OR_CTRL | Key::S)); - scan_btn->connect("pressed", callable_mp(this, &ProjectManager::_scan_projects)); - tree_vb->add_child(scan_btn); + search_tree_hb->add_child(tree_vb); tree_vb->add_child(memnew(HSeparator)); diff --git a/editor/project_manager.h b/editor/project_manager.h index 043178fb6f..46c465f24d 100644 --- a/editor/project_manager.h +++ b/editor/project_manager.h @@ -349,7 +349,7 @@ class ProjectManager : public Control { Button *erase_missing_btn = nullptr; Button *about_btn = nullptr; - HBoxContainer *local_projects_hb = nullptr; + VBoxContainer *local_projects_vb = nullptr; EditorAssetLibrary *asset_library = nullptr; Ref<StyleBox> tag_stylebox; diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp index 0d8453738d..26f326838c 100644 --- a/modules/gdscript/editor/gdscript_docgen.cpp +++ b/modules/gdscript/editor/gdscript_docgen.cpp @@ -101,14 +101,16 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c doc.inherits = p_script->native->get_name(); } - doc.brief_description = p_class->doc_brief_description; - doc.description = p_class->doc_description; - for (const Pair<String, String> &p : p_class->doc_tutorials) { + doc.brief_description = p_class->doc_data.brief; + doc.description = p_class->doc_data.description; + for (const Pair<String, String> &p : p_class->doc_data.tutorials) { DocData::TutorialDoc td; td.title = p.first; td.link = p.second; doc.tutorials.append(td); } + doc.is_deprecated = p_class->doc_data.is_deprecated; + doc.is_experimental = p_class->doc_data.is_experimental; for (const GDP::ClassNode::Member &member : p_class->members) { switch (member.type) { @@ -130,7 +132,9 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c p_script->member_lines[const_name] = m_const->start_line; DocData::ConstantDoc const_doc; - DocData::constant_doc_from_variant(const_doc, const_name, m_const->initializer->reduced_value, m_const->doc_description); + DocData::constant_doc_from_variant(const_doc, const_name, m_const->initializer->reduced_value, m_const->doc_data.description); + const_doc.is_deprecated = m_const->doc_data.is_deprecated; + const_doc.is_experimental = m_const->doc_data.is_experimental; doc.constants.push_back(const_doc); } break; @@ -153,7 +157,9 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c } DocData::MethodDoc method_doc; - DocData::method_doc_from_methodinfo(method_doc, mi, m_func->doc_description); + DocData::method_doc_from_methodinfo(method_doc, mi, m_func->doc_data.description); + method_doc.is_deprecated = m_func->doc_data.is_deprecated; + method_doc.is_experimental = m_func->doc_data.is_experimental; doc.methods.push_back(method_doc); } break; @@ -172,7 +178,9 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c } DocData::MethodDoc signal_doc; - DocData::signal_doc_from_methodinfo(signal_doc, mi, m_signal->doc_description); + DocData::signal_doc_from_methodinfo(signal_doc, mi, m_signal->doc_data.description); + signal_doc.is_deprecated = m_signal->doc_data.is_deprecated; + signal_doc.is_experimental = m_signal->doc_data.is_experimental; doc.signals.push_back(signal_doc); } break; @@ -185,7 +193,9 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c DocData::PropertyDoc prop_doc; prop_doc.name = var_name; - prop_doc.description = m_var->doc_description; + prop_doc.description = m_var->doc_data.description; + prop_doc.is_deprecated = m_var->doc_data.is_deprecated; + prop_doc.is_experimental = m_var->doc_data.is_experimental; GDType dt = m_var->get_datatype(); switch (dt.kind) { @@ -236,15 +246,21 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c p_script->member_lines[name] = m_enum->start_line; - doc.enums[name] = m_enum->doc_description; + DocData::EnumDoc enum_doc; + enum_doc.description = m_enum->doc_data.description; + enum_doc.is_deprecated = m_enum->doc_data.is_deprecated; + enum_doc.is_experimental = m_enum->doc_data.is_experimental; + doc.enums[name] = enum_doc; for (const GDP::EnumNode::Value &val : m_enum->values) { DocData::ConstantDoc const_doc; const_doc.name = val.identifier->name; const_doc.value = String(Variant(val.value)); const_doc.is_value_valid = true; - const_doc.description = val.doc_description; const_doc.enumeration = name; + const_doc.description = val.doc_data.description; + const_doc.is_deprecated = val.doc_data.is_deprecated; + const_doc.is_experimental = val.doc_data.is_experimental; doc.constants.push_back(const_doc); } @@ -257,10 +273,12 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c p_script->member_lines[name] = m_enum_val.identifier->start_line; - DocData::ConstantDoc constant_doc; - constant_doc.enumeration = "@unnamed_enums"; - DocData::constant_doc_from_variant(constant_doc, name, m_enum_val.value, m_enum_val.doc_description); - doc.constants.push_back(constant_doc); + DocData::ConstantDoc const_doc; + DocData::constant_doc_from_variant(const_doc, name, m_enum_val.value, m_enum_val.doc_data.description); + const_doc.enumeration = "@unnamed_enums"; + const_doc.is_deprecated = m_enum_val.doc_data.is_deprecated; + const_doc.is_experimental = m_enum_val.doc_data.is_experimental; + doc.constants.push_back(const_doc); } break; case GDP::ClassNode::Member::GROUP: case GDP::ClassNode::Member::UNDEFINED: diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 01e94ebb22..cb04913620 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -1772,6 +1772,15 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi bool is_constant = p_assignable->type == GDScriptParser::Node::CONSTANT; +#ifdef DEBUG_ENABLED + if (p_assignable->identifier != nullptr && p_assignable->identifier->suite != nullptr && p_assignable->identifier->suite->parent_block != nullptr) { + if (p_assignable->identifier->suite->parent_block->has_local(p_assignable->identifier->name)) { + const GDScriptParser::SuiteNode::Local &local = p_assignable->identifier->suite->parent_block->get_local(p_assignable->identifier->name); + parser->push_warning(p_assignable->identifier, GDScriptWarning::CONFUSABLE_LOCAL_DECLARATION, local.get_name(), p_assignable->identifier->name); + } + } +#endif + GDScriptParser::DataType specified_type; bool has_specified_type = p_assignable->datatype_specifier != nullptr; if (has_specified_type) { @@ -3518,12 +3527,14 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod case GDScriptParser::ClassNode::Member::FUNCTION: { if (is_base && (!base.is_meta_type || member.function->is_static)) { p_identifier->set_datatype(make_callable_type(member.function->info)); + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_FUNCTION; return; } } break; case GDScriptParser::ClassNode::Member::CLASS: { reduce_identifier_from_base_set_class(p_identifier, member.get_datatype()); + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CLASS; return; } @@ -3662,9 +3673,17 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident found_source = true; } break; case GDScriptParser::IdentifierNode::UNDEFINED_SOURCE: + case GDScriptParser::IdentifierNode::MEMBER_FUNCTION: + case GDScriptParser::IdentifierNode::MEMBER_CLASS: break; } +#ifdef DEBUG_ENABLED + if (!found_source && p_identifier->suite != nullptr && p_identifier->suite->has_local(p_identifier->name)) { + parser->push_warning(p_identifier, GDScriptWarning::CONFUSABLE_LOCAL_USAGE, p_identifier->name); + } +#endif + // Not a local, so check members. if (!found_source) { reduce_identifier_from_base(p_identifier); diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 3f571602e8..2a52db4158 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -225,194 +225,211 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code StringName identifier = in->name; - // Try function parameters. - if (codegen.parameters.has(identifier)) { - return codegen.parameters[identifier]; - } - - // Try local variables and constants. - if (!p_initializer && codegen.locals.has(identifier)) { - return codegen.locals[identifier]; - } + switch (in->source) { + // LOCALS. + case GDScriptParser::IdentifierNode::FUNCTION_PARAMETER: + case GDScriptParser::IdentifierNode::LOCAL_VARIABLE: + case GDScriptParser::IdentifierNode::LOCAL_CONSTANT: + case GDScriptParser::IdentifierNode::LOCAL_ITERATOR: + case GDScriptParser::IdentifierNode::LOCAL_BIND: { + // Try function parameters. + if (codegen.parameters.has(identifier)) { + return codegen.parameters[identifier]; + } - // Try class members. - if (_is_class_member_property(codegen, identifier)) { - // Get property. - GDScriptCodeGenerator::Address temp = codegen.add_temporary(_gdtype_from_datatype(p_expression->get_datatype(), codegen.script)); - gen->write_get_member(temp, identifier); - return temp; - } + // Try local variables and constants. + if (!p_initializer && codegen.locals.has(identifier)) { + return codegen.locals[identifier]; + } + } break; - // Try members. - if (!codegen.function_node || !codegen.function_node->is_static) { - // Try member variables. - if (codegen.script->member_indices.has(identifier)) { - if (codegen.script->member_indices[identifier].getter != StringName() && codegen.script->member_indices[identifier].getter != codegen.function_name) { - // Perform getter. - GDScriptCodeGenerator::Address temp = codegen.add_temporary(codegen.script->member_indices[identifier].data_type); - Vector<GDScriptCodeGenerator::Address> args; // No argument needed. - gen->write_call_self(temp, codegen.script->member_indices[identifier].getter, args); + // MEMBERS. + case GDScriptParser::IdentifierNode::MEMBER_VARIABLE: + case GDScriptParser::IdentifierNode::INHERITED_VARIABLE: { + // Try class members. + if (_is_class_member_property(codegen, identifier)) { + // Get property. + GDScriptCodeGenerator::Address temp = codegen.add_temporary(_gdtype_from_datatype(p_expression->get_datatype(), codegen.script)); + gen->write_get_member(temp, identifier); return temp; - } else { - // No getter or inside getter: direct member access. - int idx = codegen.script->member_indices[identifier].index; - return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, idx, codegen.script->get_member_type(identifier)); } - } - } - // Try static variables. - { - GDScript *scr = codegen.script; - while (scr) { - if (scr->static_variables_indices.has(identifier)) { - if (scr->static_variables_indices[identifier].getter != StringName() && scr->static_variables_indices[identifier].getter != codegen.function_name) { - // Perform getter. - GDScriptCodeGenerator::Address temp = codegen.add_temporary(scr->static_variables_indices[identifier].data_type); - GDScriptCodeGenerator::Address class_addr(GDScriptCodeGenerator::Address::CLASS); - Vector<GDScriptCodeGenerator::Address> args; // No argument needed. - gen->write_call(temp, class_addr, scr->static_variables_indices[identifier].getter, args); - return temp; - } else { - // No getter or inside getter: direct variable access. - GDScriptCodeGenerator::Address temp = codegen.add_temporary(scr->static_variables_indices[identifier].data_type); - GDScriptCodeGenerator::Address _class = codegen.add_constant(scr); - int index = scr->static_variables_indices[identifier].index; - gen->write_get_static_variable(temp, _class, index); - return temp; + // Try members. + if (!codegen.function_node || !codegen.function_node->is_static) { + // Try member variables. + if (codegen.script->member_indices.has(identifier)) { + if (codegen.script->member_indices[identifier].getter != StringName() && codegen.script->member_indices[identifier].getter != codegen.function_name) { + // Perform getter. + GDScriptCodeGenerator::Address temp = codegen.add_temporary(codegen.script->member_indices[identifier].data_type); + Vector<GDScriptCodeGenerator::Address> args; // No argument needed. + gen->write_call_self(temp, codegen.script->member_indices[identifier].getter, args); + return temp; + } else { + // No getter or inside getter: direct member access. + int idx = codegen.script->member_indices[identifier].index; + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, idx, codegen.script->get_member_type(identifier)); + } } } - scr = scr->_base; - } - } + } 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; + } + } + base_class = base_class->base_type.class_type; + } - // Try class constants. - { - GDScript *owner = codegen.script; - while (owner) { - GDScript *scr = owner; + // Try in native base. + GDScript *scr = codegen.script; GDScriptNativeClass *nc = nullptr; while (scr) { - if (scr->constants.has(identifier)) { - return codegen.add_constant(scr->constants[identifier]); // TODO: Get type here. - } if (scr->native.is_valid()) { nc = scr->native.ptr(); } scr = scr->_base; } - // Class C++ integer constant. - if (nc) { - bool success = false; - int64_t constant = ClassDB::get_integer_constant(nc->get_name(), identifier, &success); - if (success) { - return codegen.add_constant(constant); - } - } + 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); - owner = owner->_owner; - } - } + gen->write_get_named(temp, identifier, self); + return temp; + } + } break; + case GDScriptParser::IdentifierNode::MEMBER_CONSTANT: + case GDScriptParser::IdentifierNode::MEMBER_CLASS: { + // Try class constants. + GDScript *owner = codegen.script; + 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. + } + if (scr->native.is_valid()) { + nc = scr->native.ptr(); + } + scr = scr->_base; + } - // Try signals and methods (can be made callables). - { - // 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); + // Class C++ integer constant. + if (nc) { + bool success = false; + int64_t constant = ClassDB::get_integer_constant(nc->get_name(), identifier, &success); + if (success) { + return codegen.add_constant(constant); + } + } - gen->write_get_named(temp, identifier, self); - return temp; + owner = owner->_owner; + } + } break; + case GDScriptParser::IdentifierNode::STATIC_VARIABLE: { + // Try static variables. + GDScript *scr = codegen.script; + while (scr) { + if (scr->static_variables_indices.has(identifier)) { + if (scr->static_variables_indices[identifier].getter != StringName() && scr->static_variables_indices[identifier].getter != codegen.function_name) { + // Perform getter. + GDScriptCodeGenerator::Address temp = codegen.add_temporary(scr->static_variables_indices[identifier].data_type); + GDScriptCodeGenerator::Address class_addr(GDScriptCodeGenerator::Address::CLASS); + Vector<GDScriptCodeGenerator::Address> args; // No argument needed. + gen->write_call(temp, class_addr, scr->static_variables_indices[identifier].getter, args); + return temp; + } else { + // No getter or inside getter: direct variable access. + GDScriptCodeGenerator::Address temp = codegen.add_temporary(scr->static_variables_indices[identifier].data_type); + GDScriptCodeGenerator::Address _class = codegen.add_constant(scr); + int index = scr->static_variables_indices[identifier].index; + gen->write_get_static_variable(temp, _class, index); + return temp; + } } + scr = scr->_base; } - base_class = base_class->base_type.class_type; - } + } break; - // Try in native base. - GDScript *scr = codegen.script; - GDScriptNativeClass *nc = nullptr; - while (scr) { - if (scr->native.is_valid()) { - nc = scr->native.ptr(); + // GLOBALS. + case GDScriptParser::IdentifierNode::UNDEFINED_SOURCE: { + // Try globals. + if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { + // If it's an autoload singleton, we postpone to load it at runtime. + // This is so one autoload doesn't try to load another before it's compiled. + HashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + if (autoloads.has(identifier) && autoloads[identifier].is_singleton) { + GDScriptCodeGenerator::Address global = codegen.add_temporary(_gdtype_from_datatype(in->get_datatype(), codegen.script)); + int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; + gen->write_store_global(global, idx); + return global; + } else { + int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; + Variant global = GDScriptLanguage::get_singleton()->get_global_array()[idx]; + return codegen.add_constant(global); + } } - 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); - - gen->write_get_named(temp, identifier, self); - return temp; - } - } - - // Try globals. - if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { - // If it's an autoload singleton, we postpone to load it at runtime. - // This is so one autoload doesn't try to load another before it's compiled. - HashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - if (autoloads.has(identifier) && autoloads[identifier].is_singleton) { - GDScriptCodeGenerator::Address global = codegen.add_temporary(_gdtype_from_datatype(in->get_datatype(), codegen.script)); - int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; - gen->write_store_global(global, idx); - return global; - } else { - int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; - Variant global = GDScriptLanguage::get_singleton()->get_global_array()[idx]; - return codegen.add_constant(global); - } - } - // Try global classes. - if (ScriptServer::is_global_class(identifier)) { - const GDScriptParser::ClassNode *class_node = codegen.class_node; - while (class_node->outer) { - class_node = class_node->outer; - } + // Try global classes. + if (ScriptServer::is_global_class(identifier)) { + const GDScriptParser::ClassNode *class_node = codegen.class_node; + while (class_node->outer) { + class_node = class_node->outer; + } - Ref<Resource> res; + Ref<Resource> res; - if (class_node->identifier && class_node->identifier->name == identifier) { - res = Ref<GDScript>(main_script); - } else { - String global_class_path = ScriptServer::get_global_class_path(identifier); - if (ResourceLoader::get_resource_type(global_class_path) == "GDScript") { - Error err = OK; - res = GDScriptCache::get_full_script(global_class_path, err); - if (err != OK) { - _set_error("Can't load global class " + String(identifier), p_expression); - r_error = ERR_COMPILATION_FAILED; - return GDScriptCodeGenerator::Address(); - } - } else { - res = ResourceLoader::load(global_class_path); - if (res.is_null()) { - _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression); - r_error = ERR_COMPILATION_FAILED; - return GDScriptCodeGenerator::Address(); + if (class_node->identifier && class_node->identifier->name == identifier) { + res = Ref<GDScript>(main_script); + } else { + String global_class_path = ScriptServer::get_global_class_path(identifier); + if (ResourceLoader::get_resource_type(global_class_path) == "GDScript") { + Error err = OK; + res = GDScriptCache::get_full_script(global_class_path, err); + if (err != OK) { + _set_error("Can't load global class " + String(identifier), p_expression); + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); + } + } else { + res = ResourceLoader::load(global_class_path); + if (res.is_null()) { + _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression); + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); + } + } } - } - } - return codegen.add_constant(res); - } + return codegen.add_constant(res); + } #ifdef TOOLS_ENABLED - if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) { - GDScriptCodeGenerator::Address global = codegen.add_temporary(); // TODO: Get type. - gen->write_store_named_global(global, identifier); - return global; - } + if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) { + GDScriptCodeGenerator::Address global = codegen.add_temporary(); // TODO: Get type. + gen->write_store_named_global(global, identifier); + return global; + } #endif + } break; + } + // Not found, error. _set_error("Identifier not found: " + String(identifier), p_expression); r_error = ERR_COMPILATION_FAILED; @@ -2710,20 +2727,21 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri case GDScriptParser::ClassNode::Member::GROUP: { const GDScriptParser::AnnotationNode *annotation = member.annotation; - StringName name = annotation->export_info.name; + // Avoid name conflict. See GH-78252. + StringName name = vformat("@group_%d_%s", p_script->members.size(), annotation->export_info.name); // This is not a normal member, but we need this to keep indices in order. GDScript::MemberInfo minfo; minfo.index = p_script->member_indices.size(); PropertyInfo prop_info; - prop_info.name = name; + prop_info.name = annotation->export_info.name; prop_info.usage = annotation->export_info.usage; prop_info.hint_string = annotation->export_info.hint_string; p_script->member_info[name] = prop_info; p_script->member_indices[name] = minfo; - p_script->members.insert(name); + p_script->members.insert(Variant()); } break; default: diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 47ceee3943..8ad2486e2b 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -1403,7 +1403,7 @@ struct RecursionCheck { } }; -static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type); +static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::IdentifierNode *p_identifier, GDScriptCompletionIdentifier &r_type); static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type); static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type); @@ -1467,7 +1467,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, } break; case GDScriptParser::Node::IDENTIFIER: { const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(p_expression); - found = _guess_identifier_type(p_context, id->name, r_type); + found = _guess_identifier_type(p_context, id, r_type); } break; case GDScriptParser::Node::DICTIONARY: { // Try to recreate the dictionary. @@ -1910,7 +1910,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, return found; } -static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) { +static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::IdentifierNode *p_identifier, GDScriptCompletionIdentifier &r_type) { static int recursion_depth = 0; RecursionCheck recursion(&recursion_depth); if (unlikely(recursion.check())) { @@ -1924,36 +1924,49 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, GDScriptParser::SuiteNode *suite = p_context.current_suite; bool is_function_parameter = false; - if (suite) { - if (suite->has_local(p_identifier)) { - const GDScriptParser::SuiteNode::Local &local = suite->get_local(p_identifier); + bool can_be_local = true; + switch (p_identifier->source) { + case GDScriptParser::IdentifierNode::MEMBER_VARIABLE: + case GDScriptParser::IdentifierNode::MEMBER_CONSTANT: + case GDScriptParser::IdentifierNode::MEMBER_FUNCTION: + case GDScriptParser::IdentifierNode::MEMBER_SIGNAL: + case GDScriptParser::IdentifierNode::MEMBER_CLASS: + case GDScriptParser::IdentifierNode::INHERITED_VARIABLE: + case GDScriptParser::IdentifierNode::STATIC_VARIABLE: + can_be_local = false; + break; + default: + break; + } - id_type = local.get_datatype(); + if (can_be_local && suite && suite->has_local(p_identifier->name)) { + const GDScriptParser::SuiteNode::Local &local = suite->get_local(p_identifier->name); - // Check initializer as the first assignment. - switch (local.type) { - case GDScriptParser::SuiteNode::Local::VARIABLE: - if (local.variable->initializer) { - last_assign_line = local.variable->initializer->end_line; - last_assigned_expression = local.variable->initializer; - } - break; - case GDScriptParser::SuiteNode::Local::CONSTANT: - if (local.constant->initializer) { - last_assign_line = local.constant->initializer->end_line; - last_assigned_expression = local.constant->initializer; - } - break; - case GDScriptParser::SuiteNode::Local::PARAMETER: - if (local.parameter->initializer) { - last_assign_line = local.parameter->initializer->end_line; - last_assigned_expression = local.parameter->initializer; - } - is_function_parameter = true; - break; - default: - break; - } + id_type = local.get_datatype(); + + // Check initializer as the first assignment. + switch (local.type) { + case GDScriptParser::SuiteNode::Local::VARIABLE: + if (local.variable->initializer) { + last_assign_line = local.variable->initializer->end_line; + last_assigned_expression = local.variable->initializer; + } + break; + case GDScriptParser::SuiteNode::Local::CONSTANT: + if (local.constant->initializer) { + last_assign_line = local.constant->initializer->end_line; + last_assigned_expression = local.constant->initializer; + } + break; + case GDScriptParser::SuiteNode::Local::PARAMETER: + if (local.parameter->initializer) { + last_assign_line = local.parameter->initializer->end_line; + last_assigned_expression = local.parameter->initializer; + } + is_function_parameter = true; + break; + default: + break; } } @@ -1968,7 +1981,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::AssignmentNode *assign = static_cast<const GDScriptParser::AssignmentNode *>(suite->statements[i]); if (assign->end_line > last_assign_line && assign->assignee && assign->assigned_value && assign->assignee->type == GDScriptParser::Node::IDENTIFIER) { const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(assign->assignee); - if (id->name == p_identifier) { + if (id->name == p_identifier->name && id->source == p_identifier->source) { last_assign_line = assign->assigned_value->end_line; last_assigned_expression = assign->assigned_value; } @@ -1986,7 +1999,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, // Credit: Zylann. // TODO: this could be hacked to detect ANDed conditions too... const GDScriptParser::TypeTestNode *type_test = static_cast<const GDScriptParser::TypeTestNode *>(suite->parent_if->condition); - if (type_test->operand && type_test->test_type && type_test->operand->type == GDScriptParser::Node::IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(type_test->operand)->name == p_identifier) { + if (type_test->operand && type_test->test_type && type_test->operand->type == GDScriptParser::Node::IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(type_test->operand)->name == p_identifier->name && static_cast<const GDScriptParser::IdentifierNode *>(type_test->operand)->source == p_identifier->source) { // Bingo. GDScriptParser::CompletionContext c = p_context; c.current_line = type_test->operand->start_line; @@ -2022,8 +2035,8 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, case GDScriptParser::DataType::CLASS: if (base_type.class_type->has_function(p_context.current_function->identifier->name)) { GDScriptParser::FunctionNode *parent_function = base_type.class_type->get_member(p_context.current_function->identifier->name).function; - if (parent_function->parameters_indices.has(p_identifier)) { - const GDScriptParser::ParameterNode *parameter = parent_function->parameters[parent_function->parameters_indices[p_identifier]]; + if (parent_function->parameters_indices.has(p_identifier->name)) { + const GDScriptParser::ParameterNode *parameter = parent_function->parameters[parent_function->parameters_indices[p_identifier->name]]; if ((!id_type.is_set() || id_type.is_variant()) && parameter->get_datatype().is_hard_type()) { id_type = parameter->get_datatype(); } @@ -2048,7 +2061,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, MethodInfo info; if (ClassDB::get_method_info(base_type.native_type, p_context.current_function->identifier->name, &info)) { for (const PropertyInfo &E : info.arguments) { - if (E.name == p_identifier) { + if (E.name == p_identifier->name) { r_type = _type_from_property(E); return true; } @@ -2076,14 +2089,14 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, base.type.class_type = p_context.current_class; base.type.is_meta_type = p_context.current_function && p_context.current_function->is_static; - if (_guess_identifier_type_from_base(p_context, base, p_identifier, r_type)) { + if (_guess_identifier_type_from_base(p_context, base, p_identifier->name, r_type)) { return true; } } // Check global scripts. - if (ScriptServer::is_global_class(p_identifier)) { - String script = ScriptServer::get_global_class_path(p_identifier); + if (ScriptServer::is_global_class(p_identifier->name)) { + String script = ScriptServer::get_global_class_path(p_identifier->name); if (script.to_lower().ends_with(".gd")) { Error err = OK; Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(script, GDScriptParserRef::INTERFACE_SOLVED, err); @@ -2099,7 +2112,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, return true; } } else { - Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier)); + Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier->name)); if (scr.is_valid()) { r_type = _type_from_variant(scr); r_type.type.is_meta_type = true; @@ -2110,20 +2123,20 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, } // Check global variables (including autoloads). - if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier)) { - r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier]); + if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier->name)) { + r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier->name]); return true; } // Check ClassDB. - if (ClassDB::class_exists(p_identifier) && ClassDB::is_class_exposed(p_identifier)) { + if (ClassDB::class_exists(p_identifier->name) && ClassDB::is_class_exposed(p_identifier->name)) { r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.kind = GDScriptParser::DataType::NATIVE; - r_type.type.native_type = p_identifier; + r_type.type.native_type = p_identifier->name; r_type.type.is_constant = true; - if (Engine::get_singleton()->has_singleton(p_identifier)) { + if (Engine::get_singleton()->has_singleton(p_identifier->name)) { r_type.type.is_meta_type = false; - r_type.value = Engine::get_singleton()->get_singleton_object(p_identifier); + r_type.value = Engine::get_singleton()->get_singleton_object(p_identifier->name); } else { r_type.type.is_meta_type = true; r_type.value = Variant(); diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 2b91ba8f86..0e602561c6 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -571,8 +571,8 @@ void GDScriptParser::parse_program() { class_doc_line = MIN(class_doc_line, E.key); } } - if (has_comment(class_doc_line)) { - get_class_doc_comment(class_doc_line, head->doc_brief_description, head->doc_description, head->doc_tutorials, false); + if (has_comment(class_doc_line, true)) { + head->doc_data = parse_class_doc_comment(class_doc_line, false); } #endif // TOOLS_ENABLED @@ -771,12 +771,16 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b // Check whether current line has a doc comment if (has_comment(previous.start_line, true)) { - member->doc_description = get_doc_comment(previous.start_line, true); + if constexpr (std::is_same_v<T, ClassNode>) { + member->doc_data = parse_class_doc_comment(previous.start_line, true, true); + } else { + member->doc_data = parse_doc_comment(previous.start_line, true); + } } else if (has_comment(doc_comment_line, true)) { if constexpr (std::is_same_v<T, ClassNode>) { - get_class_doc_comment(doc_comment_line, member->doc_brief_description, member->doc_description, member->doc_tutorials, true); + member->doc_data = parse_class_doc_comment(doc_comment_line, true); } else { - member->doc_description = get_doc_comment(doc_comment_line); + member->doc_data = parse_doc_comment(doc_comment_line); } } #endif // TOOLS_ENABLED @@ -1314,25 +1318,34 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) { #ifdef TOOLS_ENABLED // Enum values documentation. for (int i = 0; i < enum_node->values.size(); i++) { + int doc_comment_line = enum_node->values[i].line; + bool single_line = false; + + if (has_comment(doc_comment_line, true)) { + single_line = true; + } else if (has_comment(doc_comment_line - 1, true)) { + doc_comment_line--; + } else { + continue; + } + if (i == enum_node->values.size() - 1) { // If close bracket is same line as last value. - if (enum_node->values[i].line != previous.start_line && has_comment(enum_node->values[i].line)) { - if (named) { - enum_node->values.write[i].doc_description = get_doc_comment(enum_node->values[i].line, true); - } else { - current_class->set_enum_value_doc(enum_node->values[i].identifier->name, get_doc_comment(enum_node->values[i].line, true)); - } + if (doc_comment_line == previous.start_line) { + break; } } else { // If two values are same line. - if (enum_node->values[i].line != enum_node->values[i + 1].line && has_comment(enum_node->values[i].line)) { - if (named) { - enum_node->values.write[i].doc_description = get_doc_comment(enum_node->values[i].line, true); - } else { - current_class->set_enum_value_doc(enum_node->values[i].identifier->name, get_doc_comment(enum_node->values[i].line, true)); - } + if (doc_comment_line == enum_node->values[i + 1].line) { + continue; } } + + if (named) { + enum_node->values.write[i].doc_data = parse_doc_comment(doc_comment_line, single_line); + } else { + current_class->set_enum_value_doc_data(enum_node->values[i].identifier->name, parse_doc_comment(doc_comment_line, single_line)); + } } #endif // TOOLS_ENABLED @@ -2267,6 +2280,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode IdentifierNode *identifier = alloc_node<IdentifierNode>(); complete_extents(identifier); identifier->name = previous.get_identifier(); +#ifdef DEBUG_ENABLED + identifier->suite = current_suite; +#endif if (current_suite != nullptr && current_suite->has_local(identifier->name)) { const SuiteNode::Local &declaration = current_suite->get_local(identifier->name); @@ -3411,19 +3427,20 @@ bool GDScriptParser::has_comment(int p_line, bool p_must_be_doc) { return tokenizer.get_comments()[p_line].comment.begins_with("##"); } -String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) { +GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool p_single_line) { + MemberDocData result; + const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); - ERR_FAIL_COND_V(!comments.has(p_line), String()); + ERR_FAIL_COND_V(!comments.has(p_line), result); if (p_single_line) { if (comments[p_line].comment.begins_with("##")) { - return comments[p_line].comment.trim_prefix("##").strip_edges(); + result.description = comments[p_line].comment.trim_prefix("##").strip_edges(); + return result; } - return ""; + return result; } - String doc; - int line = p_line; DocLineState state = DOC_LINE_NORMAL; @@ -3451,29 +3468,42 @@ String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) { } String doc_line = comments[line].comment.trim_prefix("##"); - doc += _process_doc_line(doc_line, doc, space_prefix, state); line++; + + if (state == DOC_LINE_NORMAL) { + String stripped_line = doc_line.strip_edges(); + if (stripped_line.begins_with("@deprecated")) { + result.is_deprecated = true; + continue; + } else if (stripped_line.begins_with("@experimental")) { + result.is_experimental = true; + continue; + } + } + + result.description += _process_doc_line(doc_line, result.description, space_prefix, state); } - return doc; + return result; } -void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &p_desc, Vector<Pair<String, String>> &p_tutorials, bool p_inner_class) { +GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, bool p_inner_class, bool p_single_line) { + ClassDocData result; + const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); - if (!comments.has(p_line)) { - return; + ERR_FAIL_COND_V(!comments.has(p_line), result); + + if (p_single_line) { + if (comments[p_line].comment.begins_with("##")) { + result.brief = comments[p_line].comment.trim_prefix("##").strip_edges(); + return result; + } + return result; } - ERR_FAIL_COND(!p_brief.is_empty() || !p_desc.is_empty() || p_tutorials.size() != 0); int line = p_line; DocLineState state = DOC_LINE_NORMAL; - enum Mode { - BRIEF, - DESC, - TUTORIALS, - DONE, - }; - Mode mode = BRIEF; + bool is_in_brief = true; if (p_inner_class) { while (comments.has(line - 1)) { @@ -3500,18 +3530,21 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String & break; } - String doc_line = comments[line++].comment.trim_prefix("##"); - String title, link; // For tutorials. + String doc_line = comments[line].comment.trim_prefix("##"); + line++; if (state == DOC_LINE_NORMAL) { - // Set the read mode. String stripped_line = doc_line.strip_edges(); - if (stripped_line.is_empty()) { - if (mode == BRIEF && !p_brief.is_empty()) { - mode = DESC; - } + + // A blank line separates the description from the brief. + if (is_in_brief && !result.brief.is_empty() && stripped_line.is_empty()) { + is_in_brief = false; continue; - } else if (stripped_line.begins_with("@tutorial")) { + } + + if (stripped_line.begins_with("@tutorial")) { + String title, link; + int begin_scan = String("@tutorial").length(); if (begin_scan >= stripped_line.length()) { continue; // Invalid syntax. @@ -3553,24 +3586,21 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String & link = stripped_line.substr(colon_pos).strip_edges(); } - mode = TUTORIALS; - } else if (mode == TUTORIALS) { // Tutorial docs are single line, we need a @tag after it. - mode = DONE; + result.tutorials.append(Pair<String, String>(title, link)); + continue; + } else if (stripped_line.begins_with("@deprecated")) { + result.is_deprecated = true; + continue; + } else if (stripped_line.begins_with("@experimental")) { + result.is_experimental = true; + continue; } } - switch (mode) { - case BRIEF: - p_brief += _process_doc_line(doc_line, p_brief, space_prefix, state); - break; - case DESC: - p_desc += _process_doc_line(doc_line, p_desc, space_prefix, state); - break; - case TUTORIALS: - p_tutorials.append(Pair<String, String>(title, link)); - break; - case DONE: - break; + if (is_in_brief) { + result.brief += _process_doc_line(doc_line, result.brief, space_prefix, state); + } else { + result.description += _process_doc_line(doc_line, result.description, space_prefix, state); } } @@ -3578,11 +3608,11 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String & const ClassNode::Member &m = current_class->members[0]; int first_member_line = m.get_line(); if (first_member_line == line) { - p_brief = ""; - p_desc = ""; - p_tutorials.clear(); + result = ClassDocData(); // Clear result. } } + + return result; } #endif // TOOLS_ENABLED diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 18757eb9fd..20f5dcf06d 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -257,6 +257,22 @@ public: int line = 0, column = 0; }; +#ifdef TOOLS_ENABLED + struct ClassDocData { + String brief; + String description; + Vector<Pair<String, String>> tutorials; + bool is_deprecated = false; + bool is_experimental = false; + }; + + struct MemberDocData { + String description; + bool is_deprecated = false; + bool is_experimental = false; + }; +#endif // TOOLS_ENABLED + struct Node { enum Type { NONE, @@ -505,7 +521,7 @@ public: int leftmost_column = 0; int rightmost_column = 0; #ifdef TOOLS_ENABLED - String doc_description; + MemberDocData doc_data; #endif // TOOLS_ENABLED }; @@ -513,7 +529,7 @@ public: Vector<Value> values; Variant dictionary; #ifdef TOOLS_ENABLED - String doc_description; + MemberDocData doc_data; #endif // TOOLS_ENABLED EnumNode() { @@ -720,14 +736,12 @@ public: DataType base_type; String fqcn; // Fully-qualified class name. Identifies uniquely any class in the project. #ifdef TOOLS_ENABLED - String doc_description; - String doc_brief_description; - Vector<Pair<String, String>> doc_tutorials; + ClassDocData doc_data; // EnumValue docs are parsed after itself, so we need a method to add/modify the doc property later. - void set_enum_value_doc(const StringName &p_name, const String &p_doc_description) { + void set_enum_value_doc_data(const StringName &p_name, const MemberDocData &p_doc_data) { ERR_FAIL_INDEX(members_indices[p_name], members.size()); - members.write[members_indices[p_name]].enum_value.doc_description = p_doc_description; + members.write[members_indices[p_name]].enum_value.doc_data = p_doc_data; } #endif // TOOLS_ENABLED @@ -753,7 +767,9 @@ public: members.push_back(Member(p_enum_value)); } void add_member_group(AnnotationNode *p_annotation_node) { - members_indices[p_annotation_node->export_info.name] = members.size(); + // Avoid name conflict. See GH-78252. + StringName name = vformat("@group_%d_%s", members.size(), p_annotation_node->export_info.name); + members_indices[name] = members.size(); members.push_back(Member(p_annotation_node)); } @@ -764,7 +780,7 @@ public: struct ConstantNode : public AssignableNode { #ifdef TOOLS_ENABLED - String doc_description; + MemberDocData doc_data; #endif // TOOLS_ENABLED ConstantNode() { @@ -819,7 +835,7 @@ public: LambdaNode *source_lambda = nullptr; #ifdef TOOLS_ENABLED Vector<Variant> default_arg_values; - String doc_description; + MemberDocData doc_data; #endif // TOOLS_ENABLED bool resolved_signature = false; @@ -843,19 +859,24 @@ public: struct IdentifierNode : public ExpressionNode { StringName name; +#ifdef DEBUG_ENABLED + SuiteNode *suite = nullptr; // The block in which the identifier is used. +#endif enum Source { UNDEFINED_SOURCE, FUNCTION_PARAMETER, - LOCAL_CONSTANT, LOCAL_VARIABLE, + LOCAL_CONSTANT, LOCAL_ITERATOR, // `for` loop iterator. LOCAL_BIND, // Pattern bind. - MEMBER_SIGNAL, MEMBER_VARIABLE, - STATIC_VARIABLE, MEMBER_CONSTANT, + MEMBER_FUNCTION, + MEMBER_SIGNAL, + MEMBER_CLASS, INHERITED_VARIABLE, + STATIC_VARIABLE, }; Source source = UNDEFINED_SOURCE; @@ -1006,7 +1027,7 @@ public: Vector<ParameterNode *> parameters; HashMap<StringName, int> parameters_indices; #ifdef TOOLS_ENABLED - String doc_description; + MemberDocData doc_data; #endif // TOOLS_ENABLED SignalNode() { @@ -1211,7 +1232,7 @@ public: int assignments = 0; bool is_static = false; #ifdef TOOLS_ENABLED - String doc_description; + MemberDocData doc_data; #endif // TOOLS_ENABLED VariableNode() { @@ -1486,12 +1507,12 @@ private: ExpressionNode *parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign); TypeNode *parse_type(bool p_allow_void = false); + #ifdef TOOLS_ENABLED - // Doc comments. int class_doc_line = 0x7FFFFFFF; bool has_comment(int p_line, bool p_must_be_doc = false); - String get_doc_comment(int p_line, bool p_single_line = false); - void get_class_doc_comment(int p_line, String &p_brief, String &p_desc, Vector<Pair<String, String>> &p_tutorials, bool p_inner_class); + MemberDocData parse_doc_comment(int p_line, bool p_single_line = false); + ClassDocData parse_class_doc_comment(int p_line, bool p_inner_class, bool p_single_line = false); #endif // TOOLS_ENABLED public: diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 3927a4dd3e..4f374b63b0 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -579,6 +579,24 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() { return make_identifier(name); } + if (!only_ascii) { + // Kept here in case the order with push_error matters. + Token id = make_identifier(name); + +#ifdef DEBUG_ENABLED + // Additional checks for identifiers but only in debug and if it's available in TextServer. + if (TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY)) { + int64_t confusable = TS->is_confusable(name, keyword_list); + if (confusable >= 0) { + push_error(vformat(R"(Identifier "%s" is visually similar to the GDScript keyword "%s" and thus not allowed.)", name, keyword_list[confusable])); + } + } +#endif // DEBUG_ENABLED + + // Cannot be a keyword, as keywords are ASCII only. + return id; + } + // Define some helper macros for the switch case. #define KEYWORD_GROUP_CASE(char) \ break; \ @@ -614,19 +632,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() { } // Not a keyword, so must be an identifier. - Token id = make_identifier(name); - -#ifdef DEBUG_ENABLED - // Additional checks for identifiers but only in debug and if it's available in TextServer. - if (!only_ascii && TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY)) { - int64_t confusable = TS->is_confusable(name, keyword_list); - if (confusable >= 0) { - push_error(vformat(R"(Identifier "%s" is visually similar to the GDScript keyword "%s" and thus not allowed.)", name, keyword_list[confusable])); - } - } -#endif // DEBUG_ENABLED - - return id; + return make_identifier(name); #undef KEYWORD_GROUP_CASE #undef KEYWORD diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index 8de78d2b9a..24aa793c47 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -136,6 +136,12 @@ String GDScriptWarning::get_message() const { case CONFUSABLE_IDENTIFIER: CHECK_SYMBOLS(1); return vformat(R"(The identifier "%s" has misleading characters and might be confused with something else.)", symbols[0]); + case CONFUSABLE_LOCAL_DECLARATION: + CHECK_SYMBOLS(2); + return vformat(R"(The %s "%s" is declared below in the parent block.)", symbols[0], symbols[1]); + case CONFUSABLE_LOCAL_USAGE: + CHECK_SYMBOLS(1); + return vformat(R"(The identifier "%s" will be shadowed below in the block.)", symbols[0]); case INFERENCE_ON_VARIANT: CHECK_SYMBOLS(1); return vformat("The %s type is being inferred from a Variant value, so it will be typed as Variant.", symbols[0]); @@ -213,6 +219,8 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "DEPRECATED_KEYWORD", "RENAMED_IN_GODOT_4_HINT", "CONFUSABLE_IDENTIFIER", + "CONFUSABLE_LOCAL_DECLARATION", + "CONFUSABLE_LOCAL_USAGE", "INFERENCE_ON_VARIANT", "NATIVE_METHOD_OVERRIDE", "GET_NODE_DEFAULT_WITHOUT_ONREADY", diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h index ae6207fcdc..8444d46a88 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.h @@ -83,6 +83,8 @@ public: DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced. RENAMED_IN_GODOT_4_HINT, // A variable or function that could not be found has been renamed in Godot 4. CONFUSABLE_IDENTIFIER, // The identifier contains misleading characters that can be confused. E.g. "usеr" (has Cyrillic "е" instead of Latin "e"). + CONFUSABLE_LOCAL_DECLARATION, // The parent block declares an identifier with the same name below. + CONFUSABLE_LOCAL_USAGE, // The identifier will be shadowed below in the block. INFERENCE_ON_VARIANT, // The declaration uses type inference but the value is typed as Variant. NATIVE_METHOD_OVERRIDE, // The script method overrides a native one, this may not work as intended. GET_NODE_DEFAULT_WITHOUT_ONREADY, // A class variable uses `get_node()` (or the `$` notation) as its default value, but does not use the @onready annotation. @@ -128,6 +130,8 @@ public: WARN, // DEPRECATED_KEYWORD WARN, // RENAMED_IN_GODOT_4_HINT WARN, // CONFUSABLE_IDENTIFIER + WARN, // CONFUSABLE_LOCAL_DECLARATION + WARN, // CONFUSABLE_LOCAL_USAGE ERROR, // INFERENCE_ON_VARIANT // Most likely done by accident, usually inference is trying for a particular type. ERROR, // NATIVE_METHOD_OVERRIDE // May not work as expected. ERROR, // GET_NODE_DEFAULT_WITHOUT_ONREADY // May not work as expected. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var_self.gd b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var_self.gd new file mode 100644 index 0000000000..57ae41922f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var_self.gd @@ -0,0 +1,4 @@ +var v1 = v1 + +func test(): + print(v1) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var_self.out b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var_self.out new file mode 100644 index 0000000000..c337882d9c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var_self.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Could not resolve member "v1": Cyclic reference. diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd index c4108f50de..b000c82717 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd @@ -126,7 +126,7 @@ func test(): assert(a_objects.get_typed_builtin() == TYPE_OBJECT) assert(a_objects.get_typed_script() == A) - var a_passed = (func check_a_passing(a_objects: Array[A]): return a_objects.size()).call(a_objects) + var a_passed = (func check_a_passing(p_objects: Array[A]): return p_objects.size()).call(a_objects) assert(a_passed == 4) var b_passed = (func check_b_passing(basic: Array): return basic[0] != null).call(b_objects) diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_declaration.gd b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_declaration.gd new file mode 100644 index 0000000000..3178f8d496 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_declaration.gd @@ -0,0 +1,6 @@ +func test(): + if true: + var a = 1 + print(a) + var a = 2 + print(a) diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_declaration.out b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_declaration.out new file mode 100644 index 0000000000..7365072ea7 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_declaration.out @@ -0,0 +1,7 @@ +GDTEST_OK +>> WARNING +>> Line: 3 +>> CONFUSABLE_LOCAL_DECLARATION +>> The variable "a" is declared below in the parent block. +1 +2 diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.gd b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.gd new file mode 100644 index 0000000000..4462c067bc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.gd @@ -0,0 +1,6 @@ +var a = 1 + +func test(): + print(a) + var a = 2 + print(a) diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out new file mode 100644 index 0000000000..0e0d607831 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out @@ -0,0 +1,11 @@ +GDTEST_OK +>> WARNING +>> Line: 4 +>> CONFUSABLE_LOCAL_USAGE +>> The identifier "a" will be shadowed below in the block. +>> WARNING +>> Line: 5 +>> SHADOWED_VARIABLE +>> The local variable "a" is shadowing an already-declared variable at line 1. +1 +2 diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.gd b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.gd new file mode 100644 index 0000000000..eef8eb66e6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.gd @@ -0,0 +1,6 @@ +var a = 1 + +func test(): + print(a) + var a = a + 1 + print(a) diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out new file mode 100644 index 0000000000..228a510490 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out @@ -0,0 +1,15 @@ +GDTEST_OK +>> WARNING +>> Line: 4 +>> CONFUSABLE_LOCAL_USAGE +>> The identifier "a" will be shadowed below in the block. +>> WARNING +>> Line: 5 +>> CONFUSABLE_LOCAL_USAGE +>> The identifier "a" will be shadowed below in the block. +>> WARNING +>> Line: 5 +>> SHADOWED_VARIABLE +>> The local variable "a" is shadowing an already-declared variable at line 1. +1 +2 diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.gd b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.gd new file mode 100644 index 0000000000..1f207f27ac --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.gd @@ -0,0 +1,7 @@ +var a = 1 + +func test(): + for _i in 3: + print(a) + var a = 2 + print(a) diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out new file mode 100644 index 0000000000..0d20e9f7a0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out @@ -0,0 +1,15 @@ +GDTEST_OK +>> WARNING +>> Line: 5 +>> CONFUSABLE_LOCAL_USAGE +>> The identifier "a" will be shadowed below in the block. +>> WARNING +>> Line: 6 +>> SHADOWED_VARIABLE +>> The local variable "a" is shadowing an already-declared variable at line 1. +1 +2 +1 +2 +1 +2 diff --git a/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd new file mode 100644 index 0000000000..e46f24cc5f --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd @@ -0,0 +1,17 @@ +extends RefCounted # TODO: Fix standalone annotations parsing. + +# GH-73843 +@export_group("Resource") + +# GH-78252 +@export var prop_1: int +@export_category("prop_1") +@export var prop_2: int + +func test(): + var resource := Resource.new() + prints("Not shadowed:", resource.get_class()) + + for property in get_property_list(): + if property.name in ["prop_1", "prop_2"]: + print(property) diff --git a/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.out b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.out new file mode 100644 index 0000000000..96ae84e986 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.out @@ -0,0 +1,5 @@ +GDTEST_OK +Not shadowed: Resource +{ "name": "prop_1", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } +{ "name": "prop_1", "class_name": &"", "type": 0, "hint": 0, "hint_string": "", "usage": 128 } +{ "name": "prop_2", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } diff --git a/modules/mono/SCsub b/modules/mono/SCsub index a4667f784d..564c5929c7 100644 --- a/modules/mono/SCsub +++ b/modules/mono/SCsub @@ -23,8 +23,6 @@ env_mono.add_source_files(env.modules_sources, "mono_gd/support/*.cpp") if env["platform"] in ["macos", "ios"]: env_mono.add_source_files(env.modules_sources, "mono_gd/support/*.mm") env_mono.add_source_files(env.modules_sources, "mono_gd/support/*.m") -elif env["platform"] == "android": - env_mono.add_source_files(env.modules_sources, "mono_gd/android_mono_config.gen.cpp") if env.editor_build: env_mono.add_source_files(env.modules_sources, "editor/*.cpp") diff --git a/modules/mono/config.py b/modules/mono/config.py index a36083b64b..2b2a8d6235 100644 --- a/modules/mono/config.py +++ b/modules/mono/config.py @@ -1,6 +1,6 @@ # Prior to .NET Core, we supported these: ["windows", "macos", "linuxbsd", "android", "haiku", "web", "ios"] # Eventually support for each them should be added back (except Haiku if not supported by .NET Core) -supported_platforms = ["windows", "macos", "linuxbsd"] +supported_platforms = ["windows", "macos", "linuxbsd", "android"] def can_build(env, platform): diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index ea2d14958b..4d61372ab0 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Security.Cryptography; +using System.Text; using GodotTools.Build; using GodotTools.Core; using GodotTools.Internals; @@ -45,6 +47,17 @@ namespace GodotTools.Export } }, { "default_value", true } + }, + new Godot.Collections.Dictionary() + { + { + "option", new Godot.Collections.Dictionary() + { + { "name", "dotnet/embed_build_outputs" }, + { "type", (int)Variant.Type.Bool } + } + }, + { "default_value", false } } }; } @@ -114,7 +127,7 @@ namespace GodotTools.Export if (!DeterminePlatformFromFeatures(features, out string platform)) throw new NotSupportedException("Target platform not supported."); - if (!new[] { OS.Platforms.Windows, OS.Platforms.LinuxBSD, OS.Platforms.MacOS } + if (!new[] { OS.Platforms.Windows, OS.Platforms.LinuxBSD, OS.Platforms.MacOS, OS.Platforms.Android } .Contains(platform)) { throw new NotImplementedException("Target platform not yet implemented."); @@ -129,15 +142,19 @@ namespace GodotTools.Export { archs.Add("x86_64"); } - else if (features.Contains("x86_32")) + if (features.Contains("x86_32")) { archs.Add("x86_32"); } - else if (features.Contains("arm64")) + if (features.Contains("arm64")) { archs.Add("arm64"); } - else if (features.Contains("universal")) + if (features.Contains("arm32")) + { + archs.Add("arm32"); + } + if (features.Contains("universal")) { if (platform == OS.Platforms.MacOS) { @@ -146,6 +163,8 @@ namespace GodotTools.Export } } + bool embedBuildResults = (bool)GetOption("dotnet/embed_build_outputs") || features.Contains("android"); + foreach (var arch in archs) { string ridOS = DetermineRuntimeIdentifierOS(platform); @@ -190,17 +209,44 @@ namespace GodotTools.Export "Publish succeeded but project assembly not found in the output directory"); } - // Add to the exported project shared object list. + var manifest = new StringBuilder(); + // Add to the exported project shared object list or packed resources. foreach (string file in Directory.GetFiles(publishOutputTempDir, "*", SearchOption.AllDirectories)) { - AddSharedObject(file, tags: null, - Path.Join(projectDataDirName, - Path.GetRelativePath(publishOutputTempDir, Path.GetDirectoryName(file)))); + if (embedBuildResults) + { + var filePath = SanitizeSlashes(Path.GetRelativePath(publishOutputTempDir, file)); + var fileData = File.ReadAllBytes(file); + var hash = Convert.ToBase64String(SHA512.HashData(fileData)); + + manifest.Append($"{filePath}\t{hash}\n"); + + AddFile($"res://.godot/mono/publish/{arch}/{filePath}", fileData, false); + } + else + { + AddSharedObject(file, tags: null, + Path.Join(projectDataDirName, + Path.GetRelativePath(publishOutputTempDir, Path.GetDirectoryName(file)))); + } + } + + if (embedBuildResults) + { + var fileData = Encoding.Default.GetBytes(manifest.ToString()); + AddFile($"res://.godot/mono/publish/{arch}/.dotnet-publish-manifest", fileData, false); } } } + private string SanitizeSlashes(string path) + { + if (Path.DirectorySeparatorChar == '\\') + return path.Replace('\\', '/'); + return path; + } + private string DetermineRuntimeIdentifierOS(string platform) => OS.DotNetOSPlatformMap[platform]; @@ -214,7 +260,7 @@ namespace GodotTools.Export "x86_64" => "x64", "armeabi-v7a" => "arm", "arm64-v8a" => "arm64", - "armv7" => "arm", + "arm32" => "arm", "arm64" => "arm64", _ => throw new ArgumentOutOfRangeException(nameof(arch), arch, "Unexpected architecture") }; diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp index 159cb91d1b..279b5cfed2 100644 --- a/modules/mono/godotsharp_dirs.cpp +++ b/modules/mono/godotsharp_dirs.cpp @@ -141,19 +141,52 @@ private: #else // TOOLS_ENABLED String arch = Engine::get_singleton()->get_architecture_name(); String appname_safe = path::get_csharp_project_name(); - String data_dir_root = exe_dir.path_join("data_" + appname_safe + "_" + arch); - if (!DirAccess::exists(data_dir_root)) { - data_dir_root = exe_dir.path_join("data_Godot_" + arch); - } + String packed_path = "res://.godot/mono/publish/" + arch; + if (DirAccess::exists(packed_path)) { + // The dotnet publish data is packed in the pck/zip. + String data_dir_root = OS::get_singleton()->get_cache_path().path_join("data_" + appname_safe + "_" + arch); + bool has_data = false; + if (!has_data) { + // 1. Try to access the data directly. + String global_packed = ProjectSettings::get_singleton()->globalize_path(packed_path); + if (global_packed.is_absolute_path() && FileAccess::exists(global_packed.path_join(".dotnet-publish-manifest"))) { + data_dir_root = global_packed; + has_data = true; + } + } + if (!has_data) { + // 2. Check if the data was extracted before and is up-to-date. + String packed_manifest = packed_path.path_join(".dotnet-publish-manifest"); + String extracted_manifest = data_dir_root.path_join(".dotnet-publish-manifest"); + if (FileAccess::exists(packed_manifest) && FileAccess::exists(extracted_manifest)) { + if (FileAccess::get_file_as_bytes(packed_manifest) == FileAccess::get_file_as_bytes(extracted_manifest)) { + has_data = true; + } + } + } + if (!has_data) { + // 3. Extract the data to a temporary location to load from there. + Ref<DirAccess> da = DirAccess::create_for_path(packed_path); + ERR_FAIL_NULL(da); + ERR_FAIL_COND(da->copy_dir(packed_path, data_dir_root) != OK); + } + api_assemblies_dir = data_dir_root; + } else { + // The dotnet publish data is in a directory next to the executable. + String data_dir_root = exe_dir.path_join("data_" + appname_safe + "_" + arch); + if (!DirAccess::exists(data_dir_root)) { + data_dir_root = exe_dir.path_join("data_Godot_" + arch); + } #ifdef MACOS_ENABLED - if (!DirAccess::exists(data_dir_root)) { - data_dir_root = res_dir.path_join("data_" + appname_safe + "_" + arch); - } - if (!DirAccess::exists(data_dir_root)) { - data_dir_root = res_dir.path_join("data_Godot_" + arch); - } + if (!DirAccess::exists(data_dir_root)) { + data_dir_root = res_dir.path_join("data_" + appname_safe + "_" + arch); + } + if (!DirAccess::exists(data_dir_root)) { + data_dir_root = res_dir.path_join("data_Godot_" + arch); + } #endif - api_assemblies_dir = data_dir_root; + api_assemblies_dir = data_dir_root; + } #endif } diff --git a/modules/mono/mono_gd/android_mono_config.h b/modules/mono/mono_gd/android_mono_config.h deleted file mode 100644 index bb9c2a5d5b..0000000000 --- a/modules/mono/mono_gd/android_mono_config.h +++ /dev/null @@ -1,43 +0,0 @@ -/**************************************************************************/ -/* android_mono_config.h */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#ifndef ANDROID_MONO_CONFIG_H -#define ANDROID_MONO_CONFIG_H - -#ifdef ANDROID_ENABLED - -#include "core/string/ustring.h" - -// This function is defined in an auto-generated source file -String get_godot_android_mono_config(); - -#endif // ANDROID_ENABLED - -#endif // ANDROID_MONO_CONFIG_H diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 4337478370..5e52f25cf4 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -55,9 +55,7 @@ // TODO mobile #if 0 -#ifdef ANDROID_ENABLED -#include "support/android_support.h" -#elif defined(IOS_ENABLED) +#ifdef IOS_ENABLED #include "support/ios_support.h" #endif #endif @@ -554,10 +552,6 @@ GDMono::~GDMono() { finalizing_scripts_domain = false; runtime_initialized = false; -#if defined(ANDROID_ENABLED) - gdmono::android::support::cleanup(); -#endif - singleton = nullptr; } diff --git a/modules/mono/mono_gd/support/android_support.cpp b/modules/mono/mono_gd/support/android_support.cpp deleted file mode 100644 index 14b442516e..0000000000 --- a/modules/mono/mono_gd/support/android_support.cpp +++ /dev/null @@ -1,720 +0,0 @@ -/**************************************************************************/ -/* android_support.cpp */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#include "android_support.h" - -#if defined(ANDROID_ENABLED) - -#include "../../utils/path_utils.h" -#include "../../utils/string_utils.h" -#include "../gd_mono_cache.h" -#include "../gd_mono_marshal.h" - -#include "core/os/os.h" -#include "core/string/ustring.h" - -#include "java_godot_wrapper.h" -#include "os_android.h" -#include "thread_jandroid.h" - -#include <mono/utils/mono-dl-fallback.h> - -#include <dlfcn.h> // dlopen, dlsym -#include <sys/system_properties.h> -#include <cstddef> - -#if __ANDROID_API__ < 24 -#include "thirdparty/misc/ifaddrs-android.h" -#else -#include <ifaddrs.h> -#endif - -// Warning: JNI boilerplate ahead... continue at your own risk - -namespace gdmono { -namespace android { -namespace support { - -template <typename T> -struct ScopedLocalRef { - JNIEnv *env; - T local_ref; - - _FORCE_INLINE_ T get() const { return local_ref; } - _FORCE_INLINE_ operator T() const { return local_ref; } - _FORCE_INLINE_ operator jvalue() const { return (jvalue)local_ref; } - - _FORCE_INLINE_ operator bool() const { return local_ref != nullptr; } - - _FORCE_INLINE_ bool operator==(std::nullptr_t) const { - return local_ref == nullptr; - } - - _FORCE_INLINE_ bool operator!=(std::nullptr_t) const { - return local_ref != nullptr; - } - - ScopedLocalRef(const ScopedLocalRef &) = delete; - ScopedLocalRef &operator=(const ScopedLocalRef &) = delete; - - ScopedLocalRef(JNIEnv *p_env, T p_local_ref) : - env(p_env), - local_ref(p_local_ref) { - } - - ~ScopedLocalRef() { - if (local_ref) { - env->DeleteLocalRef(local_ref); - } - } -}; - -bool jni_exception_check(JNIEnv *p_env) { - if (p_env->ExceptionCheck()) { - // Print the exception to logcat - p_env->ExceptionDescribe(); - - p_env->ExceptionClear(); - return true; - } - - return false; -} - -String app_native_lib_dir_cache; - -String determine_app_native_lib_dir() { - JNIEnv *env = get_jni_env(); - - ScopedLocalRef<jclass> activityThreadClass(env, env->FindClass("android/app/ActivityThread")); - jmethodID currentActivityThread = env->GetStaticMethodID(activityThreadClass, "currentActivityThread", "()Landroid/app/ActivityThread;"); - ScopedLocalRef<jobject> activityThread(env, env->CallStaticObjectMethod(activityThreadClass, currentActivityThread)); - jmethodID getApplication = env->GetMethodID(activityThreadClass, "getApplication", "()Landroid/app/Application;"); - ScopedLocalRef<jobject> ctx(env, env->CallObjectMethod(activityThread, getApplication)); - - jmethodID getApplicationInfo = env->GetMethodID(env->GetObjectClass(ctx), "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;"); - ScopedLocalRef<jobject> applicationInfo(env, env->CallObjectMethod(ctx, getApplicationInfo)); - jfieldID nativeLibraryDirField = env->GetFieldID(env->GetObjectClass(applicationInfo), "nativeLibraryDir", "Ljava/lang/String;"); - ScopedLocalRef<jstring> nativeLibraryDir(env, (jstring)env->GetObjectField(applicationInfo, nativeLibraryDirField)); - - String result; - - const char *const nativeLibraryDirUtf8 = env->GetStringUTFChars(nativeLibraryDir, nullptr); - if (nativeLibraryDirUtf8) { - result.parse_utf8(nativeLibraryDirUtf8); - env->ReleaseStringUTFChars(nativeLibraryDir, nativeLibraryDirUtf8); - } - - return result; -} - -String get_app_native_lib_dir() { - if (app_native_lib_dir_cache.is_empty()) { - app_native_lib_dir_cache = determine_app_native_lib_dir(); - } - return app_native_lib_dir_cache; -} - -int gd_mono_convert_dl_flags(int flags) { - // from mono's runtime-bootstrap.c - - int lflags = flags & MONO_DL_LOCAL ? 0 : RTLD_GLOBAL; - - if (flags & MONO_DL_LAZY) { - lflags |= RTLD_LAZY; - } else { - lflags |= RTLD_NOW; - } - - return lflags; -} - -#ifndef GD_MONO_SO_NAME -#define GD_MONO_SO_NAME "libmonosgen-2.0.so" -#endif - -const char *mono_so_name = GD_MONO_SO_NAME; -const char *godot_so_name = "libgodot_android.so"; - -void *mono_dl_handle = nullptr; -void *godot_dl_handle = nullptr; - -void *try_dlopen(const String &p_so_path, int p_flags) { - if (!FileAccess::exists(p_so_path)) { - if (OS::get_singleton()->is_stdout_verbose()) { - OS::get_singleton()->print("Cannot find shared library: '%s'\n", p_so_path.utf8().get_data()); - } - return nullptr; - } - - int lflags = gd_mono_convert_dl_flags(p_flags); - - void *handle = dlopen(p_so_path.utf8().get_data(), lflags); - - if (!handle) { - if (OS::get_singleton()->is_stdout_verbose()) { - OS::get_singleton()->print("Failed to open shared library: '%s'. Error: '%s'\n", p_so_path.utf8().get_data(), dlerror()); - } - return nullptr; - } - - if (OS::get_singleton()->is_stdout_verbose()) { - OS::get_singleton()->print("Successfully loaded shared library: '%s'\n", p_so_path.utf8().get_data()); - } - - return handle; -} - -void *gd_mono_android_dlopen(const char *p_name, int p_flags, char **r_err, void *p_user_data) { - if (p_name == nullptr) { - // __Internal - - if (!mono_dl_handle) { - String app_native_lib_dir = get_app_native_lib_dir(); - String so_path = path::join(app_native_lib_dir, mono_so_name); - - mono_dl_handle = try_dlopen(so_path, p_flags); - } - - return mono_dl_handle; - } - - String name = String::utf8(p_name); - - if (name.ends_with(".dll.so") || name.ends_with(".exe.so")) { - String app_native_lib_dir = get_app_native_lib_dir(); - - String orig_so_name = name.get_file(); - String so_name = "lib-aot-" + orig_so_name; - String so_path = path::join(app_native_lib_dir, so_name); - - return try_dlopen(so_path, p_flags); - } - - return nullptr; -} - -void *gd_mono_android_dlsym(void *p_handle, const char *p_name, char **r_err, void *p_user_data) { - void *sym_addr = dlsym(p_handle, p_name); - - if (sym_addr) { - return sym_addr; - } - - if (p_handle == mono_dl_handle && godot_dl_handle) { - // Looking up for '__Internal' P/Invoke. We want to search in both the Mono and Godot shared libraries. - // This is needed to resolve the monodroid P/Invoke functions that are defined at the bottom of the file. - sym_addr = dlsym(godot_dl_handle, p_name); - - if (sym_addr) { - return sym_addr; - } - } - - if (r_err) { - *r_err = str_format_new("%s\n", dlerror()); - } - - return nullptr; -} - -void *gd_mono_android_dlclose(void *p_handle, void *p_user_data) { - dlclose(p_handle); - - // Not sure if this ever happens. Does Mono close the handle for the main module? - if (p_handle == mono_dl_handle) { - mono_dl_handle = nullptr; - } - - return nullptr; -} - -int32_t build_version_sdk_int = 0; - -int32_t get_build_version_sdk_int() { - // The JNI code is the equivalent of: - // - // android.os.Build.VERSION.SDK_INT - - if (build_version_sdk_int == 0) { - JNIEnv *env = get_jni_env(); - - jclass versionClass = env->FindClass("android/os/Build$VERSION"); - ERR_FAIL_NULL_V(versionClass, 0); - - jfieldID sdkIntField = env->GetStaticFieldID(versionClass, "SDK_INT", "I"); - ERR_FAIL_NULL_V(sdkIntField, 0); - - build_version_sdk_int = (int32_t)env->GetStaticIntField(versionClass, sdkIntField); - } - - return build_version_sdk_int; -} - -jobject certStore = nullptr; // KeyStore - -MonoBoolean _gd_mono_init_cert_store() { - // The JNI code is the equivalent of: - // - // try { - // certStoreLocal = KeyStore.getInstance("AndroidCAStore"); - // certStoreLocal.load(null); - // certStore = certStoreLocal; - // return true; - // } catch (Exception e) { - // return false; - // } - - JNIEnv *env = get_jni_env(); - - ScopedLocalRef<jclass> keyStoreClass(env, env->FindClass("java/security/KeyStore")); - - jmethodID getInstance = env->GetStaticMethodID(keyStoreClass, "getInstance", "(Ljava/lang/String;)Ljava/security/KeyStore;"); - jmethodID load = env->GetMethodID(keyStoreClass, "load", "(Ljava/security/KeyStore$LoadStoreParameter;)V"); - - ScopedLocalRef<jstring> androidCAStoreString(env, env->NewStringUTF("AndroidCAStore")); - - ScopedLocalRef<jobject> certStoreLocal(env, env->CallStaticObjectMethod(keyStoreClass, getInstance, androidCAStoreString.get())); - - if (jni_exception_check(env)) { - return 0; - } - - env->CallVoidMethod(certStoreLocal, load, nullptr); - - if (jni_exception_check(env)) { - return 0; - } - - certStore = env->NewGlobalRef(certStoreLocal); - - return 1; -} - -MonoArray *_gd_mono_android_cert_store_lookup(MonoString *p_alias) { - // The JNI code is the equivalent of: - // - // Certificate certificate = certStore.getCertificate(alias); - // if (certificate == null) { - // return null; - // } - // return certificate.getEncoded(); - - MonoError mono_error; - char *alias_utf8 = mono_string_to_utf8_checked(p_alias, &mono_error); - - if (!mono_error_ok(&mono_error)) { - ERR_PRINT(String() + "Failed to convert MonoString* to UTF-8: '" + mono_error_get_message(&mono_error) + "'."); - mono_error_cleanup(&mono_error); - return nullptr; - } - - JNIEnv *env = get_jni_env(); - - ScopedLocalRef<jstring> js_alias(env, env->NewStringUTF(alias_utf8)); - mono_free(alias_utf8); - - ScopedLocalRef<jclass> keyStoreClass(env, env->FindClass("java/security/KeyStore")); - ERR_FAIL_NULL_V(keyStoreClass, nullptr); - ScopedLocalRef<jclass> certificateClass(env, env->FindClass("java/security/cert/Certificate")); - ERR_FAIL_NULL_V(certificateClass, nullptr); - - jmethodID getCertificate = env->GetMethodID(keyStoreClass, "getCertificate", "(Ljava/lang/String;)Ljava/security/cert/Certificate;"); - ERR_FAIL_NULL_V(getCertificate, nullptr); - - jmethodID getEncoded = env->GetMethodID(certificateClass, "getEncoded", "()[B"); - ERR_FAIL_NULL_V(getEncoded, nullptr); - - ScopedLocalRef<jobject> certificate(env, env->CallObjectMethod(certStore, getCertificate, js_alias.get())); - - if (!certificate) { - return nullptr; - } - - ScopedLocalRef<jbyteArray> encoded(env, (jbyteArray)env->CallObjectMethod(certificate, getEncoded)); - jsize encodedLength = env->GetArrayLength(encoded); - - MonoArray *encoded_ret = mono_array_new(mono_domain_get(), mono_get_byte_class(), encodedLength); - uint8_t *dest = (uint8_t *)mono_array_addr(encoded_ret, uint8_t, 0); - - env->GetByteArrayRegion(encoded, 0, encodedLength, reinterpret_cast<jbyte *>(dest)); - - return encoded_ret; -} - -void register_internal_calls() { - GDMonoUtils::add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_init_cert_store", _gd_mono_init_cert_store); - GDMonoUtils::add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_android_cert_store_lookup", _gd_mono_android_cert_store_lookup); -} - -void initialize() { - // We need to set this environment variable to make the monodroid BCL use btls instead of legacy as the default provider - OS::get_singleton()->set_environment("XA_TLS_PROVIDER", "btls"); - - mono_dl_fallback_register(gd_mono_android_dlopen, gd_mono_android_dlsym, gd_mono_android_dlclose, nullptr); - - String app_native_lib_dir = get_app_native_lib_dir(); - String so_path = path::join(app_native_lib_dir, godot_so_name); - - godot_dl_handle = try_dlopen(so_path, gd_mono_convert_dl_flags(MONO_DL_LAZY)); -} - -void cleanup() { - // This is called after shutting down the Mono runtime - - if (mono_dl_handle) { - gd_mono_android_dlclose(mono_dl_handle, nullptr); - } - - if (godot_dl_handle) { - gd_mono_android_dlclose(godot_dl_handle, nullptr); - } - - JNIEnv *env = get_jni_env(); - - if (certStore) { - env->DeleteGlobalRef(certStore); - certStore = nullptr; - } -} -} // namespace support -} // namespace android -} // namespace gdmono - -using namespace gdmono::android::support; - -// The following are P/Invoke functions required by the monodroid profile of the BCL. -// These are P/Invoke functions and not internal calls, hence why they use -// 'mono_bool' and 'const char*' instead of 'MonoBoolean' and 'MonoString*'. - -#define GD_PINVOKE_EXPORT extern "C" __attribute__((visibility("default"))) - -GD_PINVOKE_EXPORT int32_t _monodroid_get_android_api_level() { - return get_build_version_sdk_int(); -} - -GD_PINVOKE_EXPORT void monodroid_free(void *ptr) { - free(ptr); -} - -GD_PINVOKE_EXPORT int32_t monodroid_get_system_property(const char *p_name, char **r_value) { - char prop_value_str[PROP_VALUE_MAX + 1] = { 0 }; - - int len = __system_property_get(p_name, prop_value_str); - - if (r_value) { - if (len >= 0) { - *r_value = (char *)malloc(len + 1); - ERR_FAIL_NULL_V_MSG(*r_value, -1, "Out of memory."); - memcpy(*r_value, prop_value_str, len); - (*r_value)[len] = '\0'; - } else { - *r_value = nullptr; - } - } - - return len; -} - -GD_PINVOKE_EXPORT mono_bool _monodroid_get_network_interface_up_state(const char *p_ifname, mono_bool *r_is_up) { - // The JNI code is the equivalent of: - // - // NetworkInterface.getByName(p_ifname).isUp() - - if (!r_is_up || !p_ifname || strlen(p_ifname) == 0) { - return 0; - } - - *r_is_up = 0; - - JNIEnv *env = get_jni_env(); - - jclass networkInterfaceClass = env->FindClass("java/net/NetworkInterface"); - ERR_FAIL_NULL_V(networkInterfaceClass, 0); - - jmethodID getByName = env->GetStaticMethodID(networkInterfaceClass, "getByName", "(Ljava/lang/String;)Ljava/net/NetworkInterface;"); - ERR_FAIL_NULL_V(getByName, 0); - - jmethodID isUp = env->GetMethodID(networkInterfaceClass, "isUp", "()Z"); - ERR_FAIL_NULL_V(isUp, 0); - - ScopedLocalRef<jstring> js_ifname(env, env->NewStringUTF(p_ifname)); - ScopedLocalRef<jobject> networkInterface(env, env->CallStaticObjectMethod(networkInterfaceClass, getByName, js_ifname.get())); - - if (!networkInterface) { - return 0; - } - - *r_is_up = (mono_bool)env->CallBooleanMethod(networkInterface, isUp); - - return 1; -} - -GD_PINVOKE_EXPORT mono_bool _monodroid_get_network_interface_supports_multicast(const char *p_ifname, mono_bool *r_supports_multicast) { - // The JNI code is the equivalent of: - // - // NetworkInterface.getByName(p_ifname).supportsMulticast() - - if (!r_supports_multicast || !p_ifname || strlen(p_ifname) == 0) { - return 0; - } - - *r_supports_multicast = 0; - - JNIEnv *env = get_jni_env(); - - jclass networkInterfaceClass = env->FindClass("java/net/NetworkInterface"); - ERR_FAIL_NULL_V(networkInterfaceClass, 0); - - jmethodID getByName = env->GetStaticMethodID(networkInterfaceClass, "getByName", "(Ljava/lang/String;)Ljava/net/NetworkInterface;"); - ERR_FAIL_NULL_V(getByName, 0); - - jmethodID supportsMulticast = env->GetMethodID(networkInterfaceClass, "supportsMulticast", "()Z"); - ERR_FAIL_NULL_V(supportsMulticast, 0); - - ScopedLocalRef<jstring> js_ifname(env, env->NewStringUTF(p_ifname)); - ScopedLocalRef<jobject> networkInterface(env, env->CallStaticObjectMethod(networkInterfaceClass, getByName, js_ifname.get())); - - if (!networkInterface) { - return 0; - } - - *r_supports_multicast = (mono_bool)env->CallBooleanMethod(networkInterface, supportsMulticast); - - return 1; -} - -static const int dns_servers_len = 8; - -static void interop_get_active_network_dns_servers(char **r_dns_servers, int *dns_servers_count) { - // The JNI code is the equivalent of: - // - // ConnectivityManager connectivityManager = (ConnectivityManager)getApplicationContext() - // .getSystemService(Context.CONNECTIVITY_SERVICE); - // Network activeNerwork = connectivityManager.getActiveNetwork(); - // LinkProperties linkProperties = connectivityManager.getLinkProperties(activeNerwork); - // List<String> dnsServers = linkProperties.getDnsServers().stream() - // .map(inetAddress -> inetAddress.getHostAddress()).collect(Collectors.toList()); - -#ifdef DEBUG_ENABLED - CRASH_COND(get_build_version_sdk_int() < 23); -#endif - - JNIEnv *env = get_jni_env(); - - GodotJavaWrapper *godot_java = ((OS_Android *)OS::get_singleton())->get_godot_java(); - jobject activity = godot_java->get_activity(); - - ScopedLocalRef<jclass> activityClass(env, env->GetObjectClass(activity)); - ERR_FAIL_NULL(activityClass); - - jmethodID getApplicationContext = env->GetMethodID(activityClass, "getApplicationContext", "()Landroid/content/Context;"); - - ScopedLocalRef<jobject> applicationContext(env, env->CallObjectMethod(activity, getApplicationContext)); - - ScopedLocalRef<jclass> contextClass(env, env->FindClass("android/content/Context")); - ERR_FAIL_NULL(contextClass); - - jfieldID connectivityServiceField = env->GetStaticFieldID(contextClass, "CONNECTIVITY_SERVICE", "Ljava/lang/String;"); - ScopedLocalRef<jstring> connectivityServiceString(env, (jstring)env->GetStaticObjectField(contextClass, connectivityServiceField)); - - jmethodID getSystemService = env->GetMethodID(contextClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); - - ScopedLocalRef<jobject> connectivityManager(env, env->CallObjectMethod(applicationContext, getSystemService, connectivityServiceString.get())); - - if (!connectivityManager) { - return; - } - - ScopedLocalRef<jclass> connectivityManagerClass(env, env->FindClass("android/net/ConnectivityManager")); - ERR_FAIL_NULL(connectivityManagerClass); - - jmethodID getActiveNetwork = env->GetMethodID(connectivityManagerClass, "getActiveNetwork", "()Landroid/net/Network;"); - ERR_FAIL_NULL(getActiveNetwork); - - ScopedLocalRef<jobject> activeNetwork(env, env->CallObjectMethod(connectivityManager, getActiveNetwork)); - - if (!activeNetwork) { - return; - } - - jmethodID getLinkProperties = env->GetMethodID(connectivityManagerClass, - "getLinkProperties", "(Landroid/net/Network;)Landroid/net/LinkProperties;"); - ERR_FAIL_NULL(getLinkProperties); - - ScopedLocalRef<jobject> linkProperties(env, env->CallObjectMethod(connectivityManager, getLinkProperties, activeNetwork.get())); - - if (!linkProperties) { - return; - } - - ScopedLocalRef<jclass> linkPropertiesClass(env, env->FindClass("android/net/LinkProperties")); - ERR_FAIL_NULL(linkPropertiesClass); - - jmethodID getDnsServers = env->GetMethodID(linkPropertiesClass, "getDnsServers", "()Ljava/util/List;"); - ERR_FAIL_NULL(getDnsServers); - - ScopedLocalRef<jobject> dnsServers(env, env->CallObjectMethod(linkProperties, getDnsServers)); - - if (!dnsServers) { - return; - } - - ScopedLocalRef<jclass> listClass(env, env->FindClass("java/util/List")); - ERR_FAIL_NULL(listClass); - - jmethodID listSize = env->GetMethodID(listClass, "size", "()I"); - ERR_FAIL_NULL(listSize); - - int dnsServersCount = env->CallIntMethod(dnsServers, listSize); - - if (dnsServersCount > dns_servers_len) { - dnsServersCount = dns_servers_len; - } - - if (dnsServersCount <= 0) { - return; - } - - jmethodID listGet = env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;"); - ERR_FAIL_NULL(listGet); - - ScopedLocalRef<jclass> inetAddressClass(env, env->FindClass("java/net/InetAddress")); - ERR_FAIL_NULL(inetAddressClass); - - jmethodID getHostAddress = env->GetMethodID(inetAddressClass, "getHostAddress", "()Ljava/lang/String;"); - ERR_FAIL_NULL(getHostAddress); - - for (int i = 0; i < dnsServersCount; i++) { - ScopedLocalRef<jobject> dnsServer(env, env->CallObjectMethod(dnsServers, listGet, (jint)i)); - if (!dnsServer) { - continue; - } - - ScopedLocalRef<jstring> hostAddress(env, (jstring)env->CallObjectMethod(dnsServer, getHostAddress)); - const char *host_address = env->GetStringUTFChars(hostAddress, 0); - - r_dns_servers[i] = strdup(host_address); // freed by the BCL - (*dns_servers_count)++; - - env->ReleaseStringUTFChars(hostAddress, host_address); - } - - // jesus... -} - -GD_PINVOKE_EXPORT int32_t _monodroid_get_dns_servers(void **r_dns_servers_array) { - if (!r_dns_servers_array) { - return -1; - } - - *r_dns_servers_array = nullptr; - - char *dns_servers[dns_servers_len]; - int dns_servers_count = 0; - - if (_monodroid_get_android_api_level() < 26) { - // The 'net.dns*' system properties are no longer available in Android 8.0 (API level 26) and greater: - // https://developer.android.com/about/versions/oreo/android-8.0-changes.html#o-pri - - char prop_name[] = "net.dns*"; - - for (int i = 0; i < dns_servers_len; i++) { - prop_name[7] = (char)(i + 0x31); - char *prop_value; - int32_t len = monodroid_get_system_property(prop_name, &prop_value); - - if (len > 0) { - dns_servers[dns_servers_count] = strndup(prop_value, (size_t)len); // freed by the BCL - dns_servers_count++; - free(prop_value); - } - } - } else { - // Alternative for Oreo and greater - interop_get_active_network_dns_servers(dns_servers, &dns_servers_count); - } - - if (dns_servers_count > 0) { - size_t ret_size = sizeof(char *) * (size_t)dns_servers_count; - *r_dns_servers_array = malloc(ret_size); // freed by the BCL - ERR_FAIL_NULL_V_MSG(*r_dns_servers_array, -1, "Out of memory."); - memcpy(*r_dns_servers_array, dns_servers, ret_size); - } - - return dns_servers_count; -} - -GD_PINVOKE_EXPORT const char *_monodroid_timezone_get_default_id() { - // The JNI code is the equivalent of: - // - // TimeZone.getDefault().getID() - - JNIEnv *env = get_jni_env(); - - ScopedLocalRef<jclass> timeZoneClass(env, env->FindClass("java/util/TimeZone")); - ERR_FAIL_NULL_V(timeZoneClass, nullptr); - - jmethodID getDefault = env->GetStaticMethodID(timeZoneClass, "getDefault", "()Ljava/util/TimeZone;"); - ERR_FAIL_NULL_V(getDefault, nullptr); - - jmethodID getID = env->GetMethodID(timeZoneClass, "getID", "()Ljava/lang/String;"); - ERR_FAIL_NULL_V(getID, nullptr); - - ScopedLocalRef<jobject> defaultTimeZone(env, env->CallStaticObjectMethod(timeZoneClass, getDefault)); - - if (!defaultTimeZone) { - return nullptr; - } - - ScopedLocalRef<jstring> defaultTimeZoneID(env, (jstring)env->CallObjectMethod(defaultTimeZone, getID)); - - if (!defaultTimeZoneID) { - return nullptr; - } - - const char *default_time_zone_id = env->GetStringUTFChars(defaultTimeZoneID, 0); - - char *result = strdup(default_time_zone_id); // freed by the BCL - - env->ReleaseStringUTFChars(defaultTimeZoneID, default_time_zone_id); - - return result; -} - -GD_PINVOKE_EXPORT int32_t _monodroid_getifaddrs(struct ifaddrs **p_ifap) { - return getifaddrs(p_ifap); -} - -GD_PINVOKE_EXPORT void _monodroid_freeifaddrs(struct ifaddrs *p_ifap) { - freeifaddrs(p_ifap); -} - -#endif diff --git a/modules/mono/mono_gd/support/android_support.h b/modules/mono/mono_gd/support/android_support.h deleted file mode 100644 index 5be4bac6e1..0000000000 --- a/modules/mono/mono_gd/support/android_support.h +++ /dev/null @@ -1,54 +0,0 @@ -/**************************************************************************/ -/* android_support.h */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#ifndef ANDROID_SUPPORT_H -#define ANDROID_SUPPORT_H - -#if defined(ANDROID_ENABLED) - -#include "core/string/ustring.h" - -namespace gdmono { -namespace android { -namespace support { - -String get_app_native_lib_dir(); - -void initialize(); -void cleanup(); - -void register_internal_calls(); -} // namespace support -} // namespace android -} // namespace gdmono - -#endif // ANDROID_ENABLED - -#endif // ANDROID_SUPPORT_H diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt index 23de01a191..9c1165bf8a 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -88,16 +88,16 @@ class Godot(private val context: Context) : SensorEventListener { private val mSensorManager: SensorManager by lazy { requireActivity().getSystemService(Context.SENSOR_SERVICE) as SensorManager } - private val mAccelerometer: Sensor by lazy { + private val mAccelerometer: Sensor? by lazy { mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) } - private val mGravity: Sensor by lazy { + private val mGravity: Sensor? by lazy { mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY) } - private val mMagnetometer: Sensor by lazy { + private val mMagnetometer: Sensor? by lazy { mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) } - private val mGyroscope: Sensor by lazy { + private val mGyroscope: Sensor? by lazy { mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) } private val mClipboard: ClipboardManager by lazy { @@ -492,10 +492,18 @@ class Godot(private val context: Context) : SensorEventListener { } renderView!!.onActivityResumed() - mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME) - mSensorManager.registerListener(this, mGravity, SensorManager.SENSOR_DELAY_GAME) - mSensorManager.registerListener(this, mMagnetometer, SensorManager.SENSOR_DELAY_GAME) - mSensorManager.registerListener(this, mGyroscope, SensorManager.SENSOR_DELAY_GAME) + if (mAccelerometer != null) { + mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME) + } + if (mGravity != null) { + mSensorManager.registerListener(this, mGravity, SensorManager.SENSOR_DELAY_GAME) + } + if (mMagnetometer != null) { + mSensorManager.registerListener(this, mMagnetometer, SensorManager.SENSOR_DELAY_GAME) + } + if (mGyroscope != null) { + mSensorManager.registerListener(this, mGyroscope, SensorManager.SENSOR_DELAY_GAME) + } if (useImmersive) { val window = requireActivity().window window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp index 6434682fc2..077bf4e3b7 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -908,13 +908,19 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color { // regular forward for now Vector<Color> c; - c.push_back(clear_color.srgb_to_linear()); // our render buffer - if (rb_data.is_valid()) { - if (p_render_data->render_buffers->get_msaa_3d() != RS::VIEWPORT_MSAA_DISABLED) { - c.push_back(clear_color.srgb_to_linear()); // our resolve buffer + { + Color cc = clear_color.srgb_to_linear(); + if (rb_data.is_valid()) { + cc.a = 0; // For transparent viewport backgrounds. } - if (using_subpass_post_process) { - c.push_back(Color()); // our 2D buffer we're copying into + c.push_back(cc); // Our render buffer. + if (rb_data.is_valid()) { + if (p_render_data->render_buffers->get_msaa_3d() != RS::VIEWPORT_MSAA_DISABLED) { + c.push_back(clear_color.srgb_to_linear()); // Our resolve buffer. + } + if (using_subpass_post_process) { + c.push_back(Color()); // Our 2D buffer we're copying into. + } } } diff --git a/servers/rendering/renderer_rd/shader_rd.cpp b/servers/rendering/renderer_rd/shader_rd.cpp index c85ece6366..eaa84b7614 100644 --- a/servers/rendering/renderer_rd/shader_rd.cpp +++ b/servers/rendering/renderer_rd/shader_rd.cpp @@ -382,7 +382,7 @@ String ShaderRD::_version_get_sha1(Version *p_version) const { } static const char *shader_file_header = "GDSC"; -static const uint32_t cache_file_version = 2; +static const uint32_t cache_file_version = 3; bool ShaderRD::_load_from_cache(Version *p_version) { String sha1 = _version_get_sha1(p_version); diff --git a/servers/rendering/rendering_server_default.cpp b/servers/rendering/rendering_server_default.cpp index ff6bf3a6ba..c4464cb180 100644 --- a/servers/rendering/rendering_server_default.cpp +++ b/servers/rendering/rendering_server_default.cpp @@ -386,6 +386,12 @@ void RenderingServerDefault::draw(bool p_swap_buffers, double frame_step) { } } +void RenderingServerDefault::_call_on_render_thread(const Callable &p_callable) { + Variant ret; + Callable::CallError ce; + p_callable.callp(nullptr, 0, ret, ce); +} + RenderingServerDefault::RenderingServerDefault(bool p_create_thread) : command_queue(p_create_thread) { RenderingServer::init(); diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h index c79d3a64cf..e9b40eb082 100644 --- a/servers/rendering/rendering_server_default.h +++ b/servers/rendering/rendering_server_default.h @@ -97,6 +97,8 @@ class RenderingServerDefault : public RenderingServer { void _free(RID p_rid); + void _call_on_render_thread(const Callable &p_callable); + public: //if editor is redrawing when it shouldn't, enable this and put a breakpoint in _changes_changed() //#define DEBUG_CHANGES @@ -991,6 +993,15 @@ public: virtual void init() override; virtual void finish() override; + virtual void call_on_render_thread(const Callable &p_callable) override { + if (Thread::get_caller_id() == server_thread) { + command_queue.flush_if_pending(); + _call_on_render_thread(p_callable); + } else { + command_queue.push(this, &RenderingServerDefault::_call_on_render_thread, p_callable); + } + } + /* TESTING */ virtual double get_frame_setup_time_cpu() const override; diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index 4a95dc1963..48b38cf2b7 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -2811,6 +2811,8 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("force_draw", "swap_buffers", "frame_step"), &RenderingServer::draw, DEFVAL(true), DEFVAL(0.0)); ClassDB::bind_method(D_METHOD("get_rendering_device"), &RenderingServer::get_rendering_device); ClassDB::bind_method(D_METHOD("create_local_rendering_device"), &RenderingServer::create_local_rendering_device); + + ClassDB::bind_method(D_METHOD("call_on_render_thread", "callable"), &RenderingServer::call_on_render_thread); } void RenderingServer::mesh_add_surface_from_mesh_data(RID p_mesh, const Geometry3D::MeshData &p_mesh_data) { diff --git a/servers/rendering_server.h b/servers/rendering_server.h index 9ea55c31b4..618ceb3091 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -1602,6 +1602,8 @@ public: bool is_render_loop_enabled() const; void set_render_loop_enabled(bool p_enabled); + virtual void call_on_render_thread(const Callable &p_callable) = 0; + RenderingServer(); virtual ~RenderingServer(); |